Skip to content

Improve Global Searching in admin panel#2327

Open
notAreYouScared wants to merge 3 commits into
mainfrom
charles/better-search
Open

Improve Global Searching in admin panel#2327
notAreYouScared wants to merge 3 commits into
mainfrom
charles/better-search

Conversation

@notAreYouScared
Copy link
Copy Markdown
Member

Added filament global search plugin to improve on filaments stock global search.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 9, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

The PR integrates a third-party global search modal plugin by adding the Composer dependency, registering it in the admin panel provider, bundling compiled Tailwind CSS styling with theme variables and utilities, and implementing JavaScript modules for localStorage-backed search history/favorites management and DOM event-driven modal interaction.

Changes

Global Search Modal Feature

Layer / File(s) Summary
Dependencies
composer.json
Adds charrafimed/global-search-modal at version ^5.0 to require section.
Plugin Registration
app/Providers/Filament/AdminPanelProvider.php
Imports GlobalSearchModalPlugin and registers it in the admin panel's plugin list via GlobalSearchModalPlugin::make().
CSS Styling
public/css/charrafimed/global-search-modal/global-search-modal.css
Compiled Tailwind CSS v4 stylesheet defining theme variables (fonts, colors, spacing, radii), base resets, utility classes, modal-scoped selectors for positioning/sizing/scrollbars, @property registrations, and conditional property initialization.
Search State
public/js/charrafimed/global-search-modal/components/global-search-modal-search.js
Exports searchComponent factory managing two localStorage-persisted lists: search_history and favorite_items, with deduplication, length capping, and auto-sync via watchers; provides CRUD methods and optional history removal when favoriting.
Modal Interaction
public/js/charrafimed/global-search-modal/components/global-search-modal-observer.js
Exports observer() function that disables the search input field, intercepts user interactions to dispatch open-global-search-modal custom events, and re-enables the field when receiving modal-closed window events.
Filament client tweaks
public/js/filament/...
Minor client fixes: code block CR/LF normalization change in rich-editor.js, event-name bugfix in schemas.js, and added reset-scoped lifecycle hooks for tabs.js and wizard.js.

Possibly related PRs

  • pelican-dev/panel#2167: Touches public/js/filament/schemas/components/tabs.js with lifecycle/cleanup changes similar to this PR.
  • pelican-dev/panel#1966: Also modifies the Filament tabs component’s export/signature and initialization logic.
  • pelican-dev/panel#1834: Modifies app/Providers/Filament/AdminPanelProvider.php plugin configuration, related to plugin registration changes.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Improve Global Searching in admin panel' accurately describes the main change: adding a global search plugin to enhance Filament's search functionality.
Description check ✅ Passed The description is related to the changeset, mentioning the addition of a filament global search plugin to improve on Filament's stock global search.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
public/js/charrafimed/global-search-modal/components/global-search-modal-observer.js (3)

25-28: 💤 Low value

Consider removing redundant keypress handler.

The keypress event listener is redundant since keydown (registered at line 18) already covers all keyboard input including character keys. The keypress event is also a legacy event that's been deprecated in favor of keydown and beforeinput.

♻️ Proposed fix
         inputElement.setAttribute("readonly", true);
         inputElement.setAttribute("tabindex", "-1");
       }
     },
-        inputElement.addEventListener("keypress", (event) => {
-          event.preventDefault();
-          event.stopPropagation();
-        }, true);
-
         inputElement.setAttribute("readonly", true);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@public/js/charrafimed/global-search-modal/components/global-search-modal-observer.js`
around lines 25 - 28, Remove the redundant "keypress" listener on inputElement:
locate the inputElement.addEventListener("keypress", ...) block and delete it,
leaving the existing "keydown" listener (registered earlier) to handle keyboard
input; ensure no other code depends on the keypress handler before removal and
keep event.preventDefault()/stopPropagation() behavior in the "keydown" handler
if needed.

57-57: 💤 Low value

Pass string value to setAttribute.

setAttribute expects string values. While the number 0 will be coerced to the string "0", it's better to be explicit and pass the string directly for consistency.

♻️ Proposed fix
           inputElement.disabled = false;
           inputElement.readOnly = false;
-          inputElement.setAttribute("tabindex", 0);
+          inputElement.setAttribute("tabindex", "0");
         }
       });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@public/js/charrafimed/global-search-modal/components/global-search-modal-observer.js`
at line 57, The call inputElement.setAttribute("tabindex", 0) passes a number;
change it to pass a string by using "0" so setAttribute receives a string value
(update the setAttribute invocation on inputElement to use "0" instead of 0).

4-4: 💤 Low value

Remove unused property.

The observer property is declared but never used in this module. Consider removing it to keep the code clean.

♻️ Proposed fix
 function observer() {
   return {
-    observer: null,
     modalOpen: false,
     init: function() {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@public/js/charrafimed/global-search-modal/components/global-search-modal-observer.js`
at line 4, The exported object in global-search-modal-observer.js declares an
unused property named "observer"; remove the "observer" property declaration
from the object (and any related/commented code in that module) so the module no
longer contains an unused symbol, and run a quick grep for "observer" in that
file to confirm there are no remaining references before committing.
public/css/charrafimed/global-search-modal/global-search-modal.css (1)

1-1215: ⚡ Quick win

Consider excluding compiled CSS from linting.

This file is compiled Tailwind CSS output (as indicated by the header comment). Stylelint violations in generated/compiled files are expected and should not be manually fixed, as changes would be overwritten on rebuild.

Consider adding this file to your .stylelintignore or similar linting configuration to suppress false positives.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@public/css/charrafimed/global-search-modal/global-search-modal.css` around
lines 1 - 1215, This is generated Tailwind output (see header "/*! tailwindcss
v4.1.12" and large compiled rules like :root, :host), so instead of editing it,
add public/css/charrafimed/global-search-modal/global-search-modal.css to the
stylelint ignore list (or update your lint config to exclude compiled CSS
directories) or add an appropriate generated-file ignore rule; alternatively, if
you prefer a per-file suppression, prepend a stylelint disable directive to the
generated file as part of your build step so lint rules no longer run on this
compiled output.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@public/js/charrafimed/global-search-modal/components/global-search-modal-observer.js`:
- Line 29: The code incorrectly uses inputElement.setAttribute("readonly", true)
and later sets readonly to "false" in listenForModalClose; change these to use
the DOM property or proper boolean attribute handling: replace
setAttribute("readonly", true) with inputElement.readOnly = true, and replace
the code that sets readonly to false (in listenForModalClose) with
inputElement.readOnly = false (or use removeAttribute("readonly") if you prefer
attribute manipulation) so the readonly state is toggled correctly without
creating readonly="false".

In
`@public/js/charrafimed/global-search-modal/components/global-search-modal-search.js`:
- Around line 23-24: The getLocalStorage method currently calls
localStorage.getItem and JSON.parse directly and can throw in private mode,
quota/security errors, or on invalid JSON; wrap the localStorage.getItem +
JSON.parse sequence in a try-catch inside getLocalStorage, catch any exception,
optionally log the error, and return an empty array as the safe fallback so
callers of getLocalStorage (refer to getLocalStorage) never receive a thrown
exception or undefined.

---

Nitpick comments:
In `@public/css/charrafimed/global-search-modal/global-search-modal.css`:
- Around line 1-1215: This is generated Tailwind output (see header "/*!
tailwindcss v4.1.12" and large compiled rules like :root, :host), so instead of
editing it, add
public/css/charrafimed/global-search-modal/global-search-modal.css to the
stylelint ignore list (or update your lint config to exclude compiled CSS
directories) or add an appropriate generated-file ignore rule; alternatively, if
you prefer a per-file suppression, prepend a stylelint disable directive to the
generated file as part of your build step so lint rules no longer run on this
compiled output.

In
`@public/js/charrafimed/global-search-modal/components/global-search-modal-observer.js`:
- Around line 25-28: Remove the redundant "keypress" listener on inputElement:
locate the inputElement.addEventListener("keypress", ...) block and delete it,
leaving the existing "keydown" listener (registered earlier) to handle keyboard
input; ensure no other code depends on the keypress handler before removal and
keep event.preventDefault()/stopPropagation() behavior in the "keydown" handler
if needed.
- Line 57: The call inputElement.setAttribute("tabindex", 0) passes a number;
change it to pass a string by using "0" so setAttribute receives a string value
(update the setAttribute invocation on inputElement to use "0" instead of 0).
- Line 4: The exported object in global-search-modal-observer.js declares an
unused property named "observer"; remove the "observer" property declaration
from the object (and any related/commented code in that module) so the module no
longer contains an unused symbol, and run a quick grep for "observer" in that
file to confirm there are no remaining references before committing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: bac01114-6adb-405b-8d21-2e9b36136975

📥 Commits

Reviewing files that changed from the base of the PR and between 38620a9 and ca0fd55.

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • app/Providers/Filament/AdminPanelProvider.php
  • composer.json
  • public/css/charrafimed/global-search-modal/global-search-modal.css
  • public/js/charrafimed/global-search-modal/components/global-search-modal-observer.js
  • public/js/charrafimed/global-search-modal/components/global-search-modal-search.js

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@public/js/filament/forms/components/rich-editor.js`:
- Line 123: This change was made in a generated Filament bundle
(public/js/filament/forms/components/rich-editor.js) and must not be edited
directly; revert this edit in the generated file and instead make the fix in the
upstream source (the rich editor extension that defines the customBlock node -
look for the mf/$.create call and related methods like addNodeView,
addAttributes, addProseMirrorPlugins), then run the repository's asset
build/publish step to regenerate public/js/filament assets so the corrected code
(e.g., changes to customBlock node behavior, attributes, or button handlers) is
produced into the bundle.

In `@public/js/filament/schemas/components/tabs.js`:
- Line 1: The reset-schema-component-state event handler (boundResetHandler)
reads i.detail.livewireId and i.detail.schemaKey directly which can throw if
detail is missing; update the handler to first null-safe guard the detail (e.g.,
const detail = i.detail ?? {} or use optional chaining) and then compare
detail.livewireId and detail.schemaKey, keeping the existing conditions (also
preserve checks for T and u) and the this.$nextTick behavior; because this file
is a generated asset under public/js/filament, make the change in the Filament
source/override where boundResetHandler is defined and then regenerate the
published assets so the compiled tabs.js contains the null-safe guard.

In `@public/js/filament/schemas/components/wizard.js`:
- Line 1: The reset handler currently dereferences t.detail.livewireId and
t.detail.schemaKey in the boundResetHandler inside the l(...) component, which
can throw if event.detail is missing; update the boundResetHandler to first
check that t.detail is non-null/defined (e.g., if (!t.detail) return or guard
the comparisons), then compare livewireId/schemaKey, and only call
this.$nextTick to reset the step when appropriate; apply this change in the
upstream source for the Filament wizard component (the function l / its
boundResetHandler) so regenerated assets under public/js/filament/ will include
the fix.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0954b4a3-d6ef-4f32-b7dd-4d27ba23abb2

📥 Commits

Reviewing files that changed from the base of the PR and between ca0fd55 and e685170.

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • composer.json
  • public/css/filament/filament/app.css
  • public/js/filament/forms/components/rich-editor.js
  • public/js/filament/schemas/components/tabs.js
  • public/js/filament/schemas/components/wizard.js
  • public/js/filament/schemas/schemas.js
  • public/js/filament/widgets/components/chart.js


`);return!i||!s?!1:t.chain().command(({tr:l})=>(l.delete(r.pos-2,r.pos),!0)).exitCode().run()},ArrowDown:({editor:t})=>{if(!this.options.exitOnArrowDown)return!1;let{state:e}=t,{selection:n,doc:r}=e,{$from:o,empty:i}=n;if(!i||o.parent.type!==this.type||!(o.parentOffset===o.parent.nodeSize-2))return!1;let l=o.after();return l===void 0?!1:r.nodeAt(l)?t.commands.command(({tr:c})=>(c.setSelection(L.near(r.resolve(l))),!0)):t.commands.exitCode()}}},addInputRules(){return[zn({find:v0,type:this.type,getAttributes:t=>({language:t[1]})}),zn({find:M0,type:this.type,getAttributes:t=>({language:t[1]})})]},addProseMirrorPlugins(){return[new P({key:new H("codeBlockVSCodeHandler"),props:{handlePaste:(t,e)=>{if(!e.clipboardData||this.editor.isActive(this.type.name))return!1;let n=e.clipboardData.getData("text/plain"),r=e.clipboardData.getData("vscode-editor-data"),o=r?JSON.parse(r):void 0,i=o?.mode;if(!n||!i)return!1;let{tr:s,schema:l}=t.state,a=l.text(n.replace(/\r\n?/g,`
`));return s.replaceSelectionWith(this.type.create({language:i},a)),s.selection.$from.parent.type!==this.type&&s.setSelection(D.near(s.doc.resolve(Math.max(0,s.selection.from-2)))),s.setMeta("paste",!0),t.dispatch(s),!0}}})]}}),pf=T0;var mf=$.create({name:"customBlock",group:"block",atom:!0,defining:!0,draggable:!0,selectable:!0,isolating:!0,allowGapCursor:!0,inline:!1,addNodeView(){return({editor:t,node:e,getPos:n,HTMLAttributes:r,decorations:o,extension:i})=>{let s=document.createElement("div");s.setAttribute("data-config",e.attrs.config),s.setAttribute("data-id",e.attrs.id),s.setAttribute("data-type","customBlock");let l=document.createElement("div");if(l.className="fi-fo-rich-editor-custom-block-header fi-not-prose",s.appendChild(l),t.isEditable&&typeof e.attrs.config=="object"&&e.attrs.config!==null&&Object.keys(e.attrs.config).length>0){let c=document.createElement("div");c.className="fi-fo-rich-editor-custom-block-edit-btn-ctn",l.appendChild(c);let d=document.createElement("button");d.className="fi-icon-btn",d.type="button",d.innerHTML=i.options.editCustomBlockButtonIconHtml,d.addEventListener("click",()=>i.options.editCustomBlockUsing(e.attrs.id,e.attrs.config)),c.appendChild(d)}let a=document.createElement("p");if(a.className="fi-fo-rich-editor-custom-block-heading",a.textContent=e.attrs.label,l.appendChild(a),t.isEditable){let c=document.createElement("div");c.className="fi-fo-rich-editor-custom-block-delete-btn-ctn",l.appendChild(c);let d=document.createElement("button");d.className="fi-icon-btn",d.type="button",d.innerHTML=i.options.deleteCustomBlockButtonIconHtml,d.addEventListener("click",()=>t.chain().setNodeSelection(n()).deleteSelection().run()),c.appendChild(d)}if(e.attrs.preview){let c=document.createElement("div"),d=["fi-fo-rich-editor-custom-block-preview"];e.attrs.shouldApplyProseStylingToPreview||d.push("fi-not-prose"),c.className=d.join(" "),c.innerHTML=new TextDecoder().decode(Uint8Array.from(atob(e.attrs.preview),u=>u.charCodeAt(0))),s.appendChild(c)}return{dom:s}}},addOptions(){return{deleteCustomBlockButtonIconHtml:null,editCustomBlockButtonIconHtml:null,editCustomBlockUsing:()=>{},insertCustomBlockUsing:()=>{}}},addAttributes(){return{config:{default:null,parseHTML:t=>JSON.parse(t.getAttribute("data-config"))},id:{default:null,parseHTML:t=>t.getAttribute("data-id"),renderHTML:t=>t.id?{"data-id":t.id}:{}},label:{default:null,parseHTML:t=>t.getAttribute("data-label"),rendered:!1},preview:{default:null,parseHTML:t=>t.getAttribute("data-preview"),rendered:!1},shouldApplyProseStylingToPreview:{default:!1,rendered:!1}}},parseHTML(){return[{tag:`div[data-type="${this.name}"]`}]},renderHTML({HTMLAttributes:t}){return["div",O(t)]},addKeyboardShortcuts(){return{Backspace:()=>this.editor.commands.command(({tr:t,state:e})=>{let n=!1,{selection:r}=e,{empty:o,anchor:i}=r;if(!o)return!1;let s=new te,l=0;return e.doc.nodesBetween(i-1,i,(a,c)=>{if(a.type.name===this.name)return n=!0,s=a,l=c,!1}),n})}},addProseMirrorPlugins(){let{insertCustomBlockUsing:t}=this.options;return[new P({props:{handleDrop(e,n){if(!n||(n.preventDefault(),!n.dataTransfer.getData("customBlock")))return!1;let r=n.dataTransfer.getData("customBlock");return t(r,e.posAtCoords({left:n.clientX,top:n.clientY}).pos),!1}}})]}});var Qo=(t,e)=>e.view.domAtPos(t).node.offsetParent!==null,A0=(t,e,n)=>{for(let r=t.depth;r>0;r-=1){let o=t.node(r),i=e(o),s=Qo(t.start(r),n);if(i&&s)return{pos:r>0?t.before(r):0,start:t.start(r),depth:r,node:o}}},gf=(t,e)=>{let{state:n,view:r,extensionManager:o}=t,{schema:i,selection:s}=n,{empty:l,$anchor:a}=s,c=!!o.extensions.find(y=>y.name==="gapCursor");if(!l||a.parent.type!==i.nodes.detailsSummary||!c||e==="right"&&a.parentOffset!==a.parent.nodeSize-2)return!1;let d=qe(y=>y.type===i.nodes.details)(s);if(!d)return!1;let u=on(d.node,y=>y.type===i.nodes.detailsContent);if(!u.length||Qo(d.start+u[0].pos+1,t))return!1;let h=n.doc.resolve(d.pos+d.node.nodeSize),p=ce.findFrom(h,1,!1);if(!p)return!1;let{tr:m}=n,g=new ce(p);return m.setSelection(g),m.scrollIntoView(),r.dispatch(m),!0},yf=$.create({name:"details",content:"detailsSummary detailsContent",group:"block",defining:!0,isolating:!0,allowGapCursor:!1,addOptions(){return{persist:!1,openClassName:"is-open",HTMLAttributes:{}}},addAttributes(){return this.options.persist?{open:{default:!1,parseHTML:t=>t.hasAttribute("open"),renderHTML:({open:t})=>t?{open:""}:{}}}:[]},parseHTML(){return[{tag:"details"}]},renderHTML({HTMLAttributes:t}){return["details",O(this.options.HTMLAttributes,t),0]},...Ht({nodeName:"details",content:"block"}),addNodeView(){return({editor:t,getPos:e,node:n,HTMLAttributes:r})=>{let o=document.createElement("div"),i=O(this.options.HTMLAttributes,r,{"data-type":this.name});Object.entries(i).forEach(([c,d])=>o.setAttribute(c,d));let s=document.createElement("button");s.type="button",o.append(s);let l=document.createElement("div");o.append(l);let a=c=>{if(c!==void 0)if(c){if(o.classList.contains(this.options.openClassName))return;o.classList.add(this.options.openClassName)}else{if(!o.classList.contains(this.options.openClassName))return;o.classList.remove(this.options.openClassName)}else o.classList.toggle(this.options.openClassName);let d=new Event("toggleDetailsContent"),u=l.querySelector(':scope > div[data-type="detailsContent"]');u?.dispatchEvent(d)};return n.attrs.open&&setTimeout(()=>a()),s.addEventListener("click",()=>{if(a(),!this.options.persist){t.commands.focus(void 0,{scrollIntoView:!1});return}if(t.isEditable&&typeof e=="function"){let{from:c,to:d}=t.state.selection;t.chain().command(({tr:u})=>{let f=e();if(!f)return!1;let h=u.doc.nodeAt(f);return h?.type!==this.type?!1:(u.setNodeMarkup(f,void 0,{open:!h.attrs.open}),!0)}).setTextSelection({from:c,to:d}).focus(void 0,{scrollIntoView:!1}).run()}}),{dom:o,contentDOM:l,ignoreMutation(c){return c.type==="selection"?!1:!o.contains(c.target)||o===c.target},update:c=>c.type!==this.type?!1:(c.attrs.open!==void 0&&a(c.attrs.open),!0)}}},addCommands(){return{setDetails:()=>({state:t,chain:e})=>{var n;let{schema:r,selection:o}=t,{$from:i,$to:s}=o,l=i.blockRange(s);if(!l)return!1;let a=t.doc.slice(l.start,l.end);if(!r.nodes.detailsContent.contentMatch.matchFragment(a.content))return!1;let d=((n=a.toJSON())==null?void 0:n.content)||[];return e().insertContentAt({from:l.start,to:l.end},{type:this.name,content:[{type:"detailsSummary"},{type:"detailsContent",content:d}]}).setTextSelection(l.start+2).run()},unsetDetails:()=>({state:t,chain:e})=>{let{selection:n,schema:r}=t,o=qe(y=>y.type===this.type)(n);if(!o)return!1;let i=on(o.node,y=>y.type===r.nodes.detailsSummary),s=on(o.node,y=>y.type===r.nodes.detailsContent);if(!i.length||!s.length)return!1;let l=i[0],a=s[0],c=o.pos,d=t.doc.resolve(c),u=c+o.node.nodeSize,f={from:c,to:u},h=a.node.content.toJSON()||[],p=d.parent.type.contentMatch.defaultType,g=[p?.create(null,l.node.content).toJSON(),...h];return e().insertContentAt(f,g).setTextSelection(c+1).run()}}},addKeyboardShortcuts(){return{Backspace:()=>{let{schema:t,selection:e}=this.editor.state,{empty:n,$anchor:r}=e;return!n||r.parent.type!==t.nodes.detailsSummary?!1:r.parentOffset!==0?this.editor.commands.command(({tr:o})=>{let i=r.pos-1,s=r.pos;return o.delete(i,s),!0}):this.editor.commands.unsetDetails()},Enter:({editor:t})=>{let{state:e,view:n}=t,{schema:r,selection:o}=e,{$head:i}=o;if(i.parent.type!==r.nodes.detailsSummary)return!1;let s=Qo(i.after()+1,t),l=s?e.doc.nodeAt(i.after()):i.node(-2);if(!l)return!1;let a=s?0:i.indexAfter(-1),c=Pn(l.contentMatchAt(a));if(!c||!l.canReplaceWith(a,a,c))return!1;let d=c.createAndFill();if(!d)return!1;let u=s?i.after()+1:i.after(-1),f=e.tr.replaceWith(u,u,d),h=f.doc.resolve(u),p=L.near(h,1);return f.setSelection(p),f.scrollIntoView(),n.dispatch(f),!0},ArrowRight:({editor:t})=>gf(t,"right"),ArrowDown:({editor:t})=>gf(t,"down")}},addProseMirrorPlugins(){return[new P({key:new H("detailsSelection"),appendTransaction:(t,e,n)=>{let{editor:r,type:o}=this;if(r.view.composing||!t.some(y=>y.selectionSet)||!e.selection.empty||!n.selection.empty||!Fo(n,o.name))return;let{$from:a}=n.selection;if(Qo(a.pos,r))return;let d=A0(a,y=>y.type===o,r);if(!d)return;let u=on(d.node,y=>y.type===n.schema.nodes.detailsSummary);if(!u.length)return;let f=u[0],p=(e.selection.from<n.selection.from?"forward":"backward")==="forward"?d.start+f.pos:d.pos+f.pos+f.node.nodeSize,m=D.create(n.doc,p);return n.tr.setSelection(m)}})]}}),bf=$.create({name:"detailsContent",content:"block+",defining:!0,selectable:!1,addOptions(){return{HTMLAttributes:{}}},parseHTML(){return[{tag:`div[data-type="${this.name}"]`}]},renderHTML({HTMLAttributes:t}){return["div",O(this.options.HTMLAttributes,t,{"data-type":this.name}),0]},addNodeView(){return({HTMLAttributes:t})=>{let e=document.createElement("div"),n=O(this.options.HTMLAttributes,t,{"data-type":this.name,hidden:"hidden"});return Object.entries(n).forEach(([r,o])=>e.setAttribute(r,o)),e.addEventListener("toggleDetailsContent",()=>{e.toggleAttribute("hidden")}),{dom:e,contentDOM:e,ignoreMutation(r){return r.type==="selection"?!1:!e.contains(r.target)||e===r.target},update:r=>r.type===this.type}}},addKeyboardShortcuts(){return{Enter:({editor:t})=>{let{state:e,view:n}=t,{selection:r}=e,{$from:o,empty:i}=r,s=qe(z=>z.type===this.type)(r);if(!i||!s||!s.node.childCount)return!1;let l=o.index(s.depth),{childCount:a}=s.node;if(!(a===l+1))return!1;let d=s.node.type.contentMatch.defaultType,u=d?.createAndFill();if(!u)return!1;let f=e.doc.resolve(s.pos+1),h=a-1,p=s.node.child(h),m=f.posAtIndex(h,s.depth);if(!p.eq(u))return!1;let y=o.node(-3);if(!y)return!1;let b=o.indexAfter(-3),w=Pn(y.contentMatchAt(b));if(!w||!y.canReplaceWith(b,b,w))return!1;let C=w.createAndFill();if(!C)return!1;let{tr:x}=e,S=o.after(-2);x.replaceWith(S,S,C);let k=x.doc.resolve(S),E=L.near(k,1);x.setSelection(E);let M=m,A=m+p.nodeSize;return x.delete(M,A),x.scrollIntoView(),n.dispatch(x),!0}}},...Ht({nodeName:"detailsContent"})}),wf=$.create({name:"detailsSummary",content:"text*",defining:!0,selectable:!1,isolating:!0,addOptions(){return{HTMLAttributes:{}}},parseHTML(){return[{tag:"summary"}]},renderHTML({HTMLAttributes:t}){return["summary",O(this.options.HTMLAttributes,t),0]},...Ht({nodeName:"detailsSummary",content:"inline"})});var E0=$.create({name:"doc",topNode:!0,content:"block+",renderMarkdown:(t,e)=>t.content?e.renderChildren(t.content,`
`));return s.replaceSelectionWith(this.type.create({language:i},a)),s.selection.$from.parent.type!==this.type&&s.setSelection(D.near(s.doc.resolve(Math.max(0,s.selection.from-2)))),s.setMeta("paste",!0),t.dispatch(s),!0}}})]}}),pf=T0;var mf=$.create({name:"customBlock",group:"block",atom:!0,defining:!0,draggable:!0,selectable:!0,isolating:!0,allowGapCursor:!0,inline:!1,addNodeView(){return({editor:t,node:e,getPos:n,HTMLAttributes:r,decorations:o,extension:i})=>{let s=document.createElement("div");s.setAttribute("data-config",JSON.stringify(e.attrs.config)),s.setAttribute("data-id",e.attrs.id),s.setAttribute("data-type","customBlock");let l=document.createElement("div");if(l.className="fi-fo-rich-editor-custom-block-header fi-not-prose",s.appendChild(l),t.isEditable&&typeof e.attrs.config=="object"&&e.attrs.config!==null&&Object.keys(e.attrs.config).length>0){let c=document.createElement("div");c.className="fi-fo-rich-editor-custom-block-edit-btn-ctn",l.appendChild(c);let d=document.createElement("button");d.className="fi-icon-btn",d.type="button",d.innerHTML=i.options.editCustomBlockButtonIconHtml,d.addEventListener("click",()=>i.options.editCustomBlockUsing(e.attrs.id,e.attrs.config)),c.appendChild(d)}let a=document.createElement("p");if(a.className="fi-fo-rich-editor-custom-block-heading",a.textContent=e.attrs.label,l.appendChild(a),t.isEditable){let c=document.createElement("div");c.className="fi-fo-rich-editor-custom-block-delete-btn-ctn",l.appendChild(c);let d=document.createElement("button");d.className="fi-icon-btn",d.type="button",d.innerHTML=i.options.deleteCustomBlockButtonIconHtml,d.addEventListener("click",()=>t.chain().setNodeSelection(n()).deleteSelection().run()),c.appendChild(d)}if(e.attrs.preview){let c=document.createElement("div"),d=["fi-fo-rich-editor-custom-block-preview"];e.attrs.shouldApplyProseStylingToPreview||d.push("fi-not-prose"),c.className=d.join(" "),c.innerHTML=new TextDecoder().decode(Uint8Array.from(atob(e.attrs.preview),u=>u.charCodeAt(0))),s.appendChild(c)}return{dom:s}}},addOptions(){return{deleteCustomBlockButtonIconHtml:null,editCustomBlockButtonIconHtml:null,editCustomBlockUsing:()=>{},insertCustomBlockUsing:()=>{}}},addAttributes(){return{config:{default:null,parseHTML:t=>JSON.parse(t.getAttribute("data-config")),renderHTML:t=>t.config?{"data-config":JSON.stringify(t.config)}:{}},id:{default:null,parseHTML:t=>t.getAttribute("data-id"),renderHTML:t=>t.id?{"data-id":t.id}:{}},label:{default:null,parseHTML:t=>t.getAttribute("data-label"),rendered:!1},preview:{default:null,parseHTML:t=>t.getAttribute("data-preview"),rendered:!1},shouldApplyProseStylingToPreview:{default:!1,rendered:!1}}},parseHTML(){return[{tag:`div[data-type="${this.name}"]`}]},renderHTML({HTMLAttributes:t}){return["div",O({"data-type":"customBlock"},t)]},addKeyboardShortcuts(){return{Backspace:()=>this.editor.commands.command(({tr:t,state:e})=>{let n=!1,{selection:r}=e,{empty:o,anchor:i}=r;if(!o)return!1;let s=new te,l=0;return e.doc.nodesBetween(i-1,i,(a,c)=>{if(a.type.name===this.name)return n=!0,s=a,l=c,!1}),n})}},addProseMirrorPlugins(){let{insertCustomBlockUsing:t}=this.options;return[new P({props:{handleDrop(e,n){if(!n||(n.preventDefault(),!n.dataTransfer.getData("customBlock")))return!1;let r=n.dataTransfer.getData("customBlock");return t(r,e.posAtCoords({left:n.clientX,top:n.clientY}).pos),!1}}})]}});var Qo=(t,e)=>e.view.domAtPos(t).node.offsetParent!==null,A0=(t,e,n)=>{for(let r=t.depth;r>0;r-=1){let o=t.node(r),i=e(o),s=Qo(t.start(r),n);if(i&&s)return{pos:r>0?t.before(r):0,start:t.start(r),depth:r,node:o}}},gf=(t,e)=>{let{state:n,view:r,extensionManager:o}=t,{schema:i,selection:s}=n,{empty:l,$anchor:a}=s,c=!!o.extensions.find(y=>y.name==="gapCursor");if(!l||a.parent.type!==i.nodes.detailsSummary||!c||e==="right"&&a.parentOffset!==a.parent.nodeSize-2)return!1;let d=qe(y=>y.type===i.nodes.details)(s);if(!d)return!1;let u=on(d.node,y=>y.type===i.nodes.detailsContent);if(!u.length||Qo(d.start+u[0].pos+1,t))return!1;let h=n.doc.resolve(d.pos+d.node.nodeSize),p=ce.findFrom(h,1,!1);if(!p)return!1;let{tr:m}=n,g=new ce(p);return m.setSelection(g),m.scrollIntoView(),r.dispatch(m),!0},yf=$.create({name:"details",content:"detailsSummary detailsContent",group:"block",defining:!0,isolating:!0,allowGapCursor:!1,addOptions(){return{persist:!1,openClassName:"is-open",HTMLAttributes:{}}},addAttributes(){return this.options.persist?{open:{default:!1,parseHTML:t=>t.hasAttribute("open"),renderHTML:({open:t})=>t?{open:""}:{}}}:[]},parseHTML(){return[{tag:"details"}]},renderHTML({HTMLAttributes:t}){return["details",O(this.options.HTMLAttributes,t),0]},...Ht({nodeName:"details",content:"block"}),addNodeView(){return({editor:t,getPos:e,node:n,HTMLAttributes:r})=>{let o=document.createElement("div"),i=O(this.options.HTMLAttributes,r,{"data-type":this.name});Object.entries(i).forEach(([c,d])=>o.setAttribute(c,d));let s=document.createElement("button");s.type="button",o.append(s);let l=document.createElement("div");o.append(l);let a=c=>{if(c!==void 0)if(c){if(o.classList.contains(this.options.openClassName))return;o.classList.add(this.options.openClassName)}else{if(!o.classList.contains(this.options.openClassName))return;o.classList.remove(this.options.openClassName)}else o.classList.toggle(this.options.openClassName);let d=new Event("toggleDetailsContent"),u=l.querySelector(':scope > div[data-type="detailsContent"]');u?.dispatchEvent(d)};return n.attrs.open&&setTimeout(()=>a()),s.addEventListener("click",()=>{if(a(),!this.options.persist){t.commands.focus(void 0,{scrollIntoView:!1});return}if(t.isEditable&&typeof e=="function"){let{from:c,to:d}=t.state.selection;t.chain().command(({tr:u})=>{let f=e();if(!f)return!1;let h=u.doc.nodeAt(f);return h?.type!==this.type?!1:(u.setNodeMarkup(f,void 0,{open:!h.attrs.open}),!0)}).setTextSelection({from:c,to:d}).focus(void 0,{scrollIntoView:!1}).run()}}),{dom:o,contentDOM:l,ignoreMutation(c){return c.type==="selection"?!1:!o.contains(c.target)||o===c.target},update:c=>c.type!==this.type?!1:(c.attrs.open!==void 0&&a(c.attrs.open),!0)}}},addCommands(){return{setDetails:()=>({state:t,chain:e})=>{var n;let{schema:r,selection:o}=t,{$from:i,$to:s}=o,l=i.blockRange(s);if(!l)return!1;let a=t.doc.slice(l.start,l.end);if(!r.nodes.detailsContent.contentMatch.matchFragment(a.content))return!1;let d=((n=a.toJSON())==null?void 0:n.content)||[];return e().insertContentAt({from:l.start,to:l.end},{type:this.name,content:[{type:"detailsSummary"},{type:"detailsContent",content:d}]}).setTextSelection(l.start+2).run()},unsetDetails:()=>({state:t,chain:e})=>{let{selection:n,schema:r}=t,o=qe(y=>y.type===this.type)(n);if(!o)return!1;let i=on(o.node,y=>y.type===r.nodes.detailsSummary),s=on(o.node,y=>y.type===r.nodes.detailsContent);if(!i.length||!s.length)return!1;let l=i[0],a=s[0],c=o.pos,d=t.doc.resolve(c),u=c+o.node.nodeSize,f={from:c,to:u},h=a.node.content.toJSON()||[],p=d.parent.type.contentMatch.defaultType,g=[p?.create(null,l.node.content).toJSON(),...h];return e().insertContentAt(f,g).setTextSelection(c+1).run()}}},addKeyboardShortcuts(){return{Backspace:()=>{let{schema:t,selection:e}=this.editor.state,{empty:n,$anchor:r}=e;return!n||r.parent.type!==t.nodes.detailsSummary?!1:r.parentOffset!==0?this.editor.commands.command(({tr:o})=>{let i=r.pos-1,s=r.pos;return o.delete(i,s),!0}):this.editor.commands.unsetDetails()},Enter:({editor:t})=>{let{state:e,view:n}=t,{schema:r,selection:o}=e,{$head:i}=o;if(i.parent.type!==r.nodes.detailsSummary)return!1;let s=Qo(i.after()+1,t),l=s?e.doc.nodeAt(i.after()):i.node(-2);if(!l)return!1;let a=s?0:i.indexAfter(-1),c=Pn(l.contentMatchAt(a));if(!c||!l.canReplaceWith(a,a,c))return!1;let d=c.createAndFill();if(!d)return!1;let u=s?i.after()+1:i.after(-1),f=e.tr.replaceWith(u,u,d),h=f.doc.resolve(u),p=L.near(h,1);return f.setSelection(p),f.scrollIntoView(),n.dispatch(f),!0},ArrowRight:({editor:t})=>gf(t,"right"),ArrowDown:({editor:t})=>gf(t,"down")}},addProseMirrorPlugins(){return[new P({key:new H("detailsSelection"),appendTransaction:(t,e,n)=>{let{editor:r,type:o}=this;if(r.view.composing||!t.some(y=>y.selectionSet)||!e.selection.empty||!n.selection.empty||!Fo(n,o.name))return;let{$from:a}=n.selection;if(Qo(a.pos,r))return;let d=A0(a,y=>y.type===o,r);if(!d)return;let u=on(d.node,y=>y.type===n.schema.nodes.detailsSummary);if(!u.length)return;let f=u[0],p=(e.selection.from<n.selection.from?"forward":"backward")==="forward"?d.start+f.pos:d.pos+f.pos+f.node.nodeSize,m=D.create(n.doc,p);return n.tr.setSelection(m)}})]}}),bf=$.create({name:"detailsContent",content:"block+",defining:!0,selectable:!1,addOptions(){return{HTMLAttributes:{}}},parseHTML(){return[{tag:`div[data-type="${this.name}"]`}]},renderHTML({HTMLAttributes:t}){return["div",O(this.options.HTMLAttributes,t,{"data-type":this.name}),0]},addNodeView(){return({HTMLAttributes:t})=>{let e=document.createElement("div"),n=O(this.options.HTMLAttributes,t,{"data-type":this.name,hidden:"hidden"});return Object.entries(n).forEach(([r,o])=>e.setAttribute(r,o)),e.addEventListener("toggleDetailsContent",()=>{e.toggleAttribute("hidden")}),{dom:e,contentDOM:e,ignoreMutation(r){return r.type==="selection"?!1:!e.contains(r.target)||e===r.target},update:r=>r.type===this.type}}},addKeyboardShortcuts(){return{Enter:({editor:t})=>{let{state:e,view:n}=t,{selection:r}=e,{$from:o,empty:i}=r,s=qe(z=>z.type===this.type)(r);if(!i||!s||!s.node.childCount)return!1;let l=o.index(s.depth),{childCount:a}=s.node;if(!(a===l+1))return!1;let d=s.node.type.contentMatch.defaultType,u=d?.createAndFill();if(!u)return!1;let f=e.doc.resolve(s.pos+1),h=a-1,p=s.node.child(h),m=f.posAtIndex(h,s.depth);if(!p.eq(u))return!1;let y=o.node(-3);if(!y)return!1;let b=o.indexAfter(-3),w=Pn(y.contentMatchAt(b));if(!w||!y.canReplaceWith(b,b,w))return!1;let C=w.createAndFill();if(!C)return!1;let{tr:x}=e,S=o.after(-2);x.replaceWith(S,S,C);let k=x.doc.resolve(S),E=L.near(k,1);x.setSelection(E);let M=m,A=m+p.nodeSize;return x.delete(M,A),x.scrollIntoView(),n.dispatch(x),!0}}},...Ht({nodeName:"detailsContent"})}),wf=$.create({name:"detailsSummary",content:"text*",defining:!0,selectable:!1,isolating:!0,addOptions(){return{HTMLAttributes:{}}},parseHTML(){return[{tag:"summary"}]},renderHTML({HTMLAttributes:t}){return["summary",O(this.options.HTMLAttributes,t),0]},...Ht({nodeName:"detailsSummary",content:"inline"})});var E0=$.create({name:"doc",topNode:!0,content:"block+",renderMarkdown:(t,e)=>t.content?e.renderChildren(t.content,`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Avoid direct edits in generated Filament asset bundles.

This file is in public/js/filament/ and appears to be generated/published output. Please move this fix to the source/upstream file and regenerate assets, otherwise this change is fragile and likely to be lost on the next publish.

Based on learnings: files under public/js/filament/ are auto-generated Filament assets and should be changed upstream or via repo configuration, then re-published.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@public/js/filament/forms/components/rich-editor.js` at line 123, This change
was made in a generated Filament bundle
(public/js/filament/forms/components/rich-editor.js) and must not be edited
directly; revert this edit in the generated file and instead make the fix in the
upstream source (the rich editor extension that defines the customBlock node -
look for the mf/$.create call and related methods like addNodeView,
addAttributes, addProseMirrorPlugins), then run the repository's asset
build/publish step to regenerate public/js/filament assets so the corrected code
(e.g., changes to customBlock node behavior, attributes, or button handlers) is
produced into the bundle.

@@ -1 +1 @@
function v({activeTab:w,isScrollable:f,isTabPersistedInQueryString:m,livewireId:g,tab:T,tabQueryStringKey:r}){return{boundResizeHandler:null,isScrollable:f,resizeDebounceTimer:null,tab:T,unsubscribeLivewireHook:null,withinDropdownIndex:null,withinDropdownMounted:!1,init(){let t=this.getTabs(),e=new URLSearchParams(window.location.search);m&&e.has(r)&&t.includes(e.get(r))&&(this.tab=e.get(r)),(!this.tab||!t.includes(this.tab))&&(this.tab=t[w-1]),this.$watch("tab",()=>{this.updateQueryString(),this.autofocusFields()}),this.autofocusFields(!0),this.unsubscribeLivewireHook=Livewire.interceptMessage(({message:i,onSuccess:a})=>{a(()=>{this.$nextTick(()=>{if(i.component.id!==g)return;let l=this.getTabs();l.includes(this.tab)||(this.tab=l[w-1]??this.tab)})})}),f||(this.boundResizeHandler=this.debouncedUpdateTabsWithinDropdown.bind(this),window.addEventListener("resize",this.boundResizeHandler),this.updateTabsWithinDropdown())},calculateAvailableWidth(t){let e=window.getComputedStyle(t);return Math.floor(t.clientWidth)-Math.ceil(parseFloat(e.paddingLeft))*2},calculateContainerGap(t){let e=window.getComputedStyle(t);return Math.ceil(parseFloat(e.columnGap))},calculateDropdownIconWidth(t){let e=t.querySelector(".fi-icon");return Math.ceil(e.clientWidth)},calculateTabItemGap(t){let e=window.getComputedStyle(t);return Math.ceil(parseFloat(e.columnGap)||8)},calculateTabItemPadding(t){let e=window.getComputedStyle(t);return Math.ceil(parseFloat(e.paddingLeft))+Math.ceil(parseFloat(e.paddingRight))},findOverflowIndex(t,e,i,a,l,h){let u=t.map(n=>Math.ceil(n.clientWidth)),b=t.map(n=>{let c=n.querySelector(".fi-tabs-item-label"),s=n.querySelector(".fi-badge"),o=Math.ceil(c.clientWidth),d=s?Math.ceil(s.clientWidth):0;return{label:o,badge:d,total:o+(d>0?a+d:0)}});for(let n=0;n<t.length;n++){let c=u.slice(0,n+1).reduce((p,y)=>p+y,0),s=n*i,o=b.slice(n+1),d=o.length>0,D=d?Math.max(...o.map(p=>p.total)):0,W=d?l+D+a+h+i:0;if(c+s+W>e)return n}return-1},get isDropdownButtonVisible(){return this.withinDropdownMounted?this.withinDropdownIndex===null?!1:this.getTabs().findIndex(e=>e===this.tab)<this.withinDropdownIndex:!0},getTabs(){return this.$refs.tabsData?JSON.parse(this.$refs.tabsData.value):[]},updateQueryString(){if(!m)return;let t=new URL(window.location.href);t.searchParams.set(r,this.tab),history.replaceState(null,document.title,t.toString())},autofocusFields(t=!1){this.$nextTick(()=>{if(t&&document.activeElement&&document.activeElement!==document.body&&this.$el.compareDocumentPosition(document.activeElement)&Node.DOCUMENT_POSITION_PRECEDING)return;let e=this.$el.querySelectorAll(".fi-sc-tabs-tab.fi-active [autofocus]");for(let i of e)if(i.focus(),document.activeElement===i)break})},debouncedUpdateTabsWithinDropdown(){clearTimeout(this.resizeDebounceTimer),this.resizeDebounceTimer=setTimeout(()=>this.updateTabsWithinDropdown(),150)},async updateTabsWithinDropdown(){this.withinDropdownIndex=null,this.withinDropdownMounted=!1,await this.$nextTick();let t=this.$el.querySelector(".fi-tabs"),e=t.querySelector(".fi-tabs-item:last-child"),i=Array.from(t.children).slice(0,-1),a=i.map(s=>s.style.display);i.forEach(s=>s.style.display=""),t.offsetHeight;let l=this.calculateAvailableWidth(t),h=this.calculateContainerGap(t),u=this.calculateDropdownIconWidth(e),b=this.calculateTabItemGap(i[0]),n=this.calculateTabItemPadding(i[0]),c=this.findOverflowIndex(i,l,h,b,n,u);i.forEach((s,o)=>s.style.display=a[o]),c!==-1&&(this.withinDropdownIndex=c),this.withinDropdownMounted=!0},destroy(){this.unsubscribeLivewireHook?.(),this.boundResizeHandler&&window.removeEventListener("resize",this.boundResizeHandler),clearTimeout(this.resizeDebounceTimer)}}}export{v as default};
function x({activeTab:h,isScrollable:m,isTabPersisted:T,isTabPersistedInQueryString:u,livewireId:g,schemaKey:D,tab:W,tabQueryStringKey:r}){return{boundResizeHandler:null,boundResetHandler:null,isScrollable:m,resizeDebounceTimer:null,tab:W,unsubscribeLivewireHook:null,withinDropdownIndex:null,withinDropdownMounted:!1,init(){let t=this.getTabs(),e=new URLSearchParams(window.location.search);u&&e.has(r)&&t.includes(e.get(r))&&(this.tab=e.get(r)),(!this.tab||!t.includes(this.tab))&&(this.tab=t[h-1]),this.$watch("tab",()=>{this.updateQueryString(),this.autofocusFields()}),this.autofocusFields(!0),this.unsubscribeLivewireHook=Livewire.interceptMessage(({message:i,onSuccess:a})=>{a(()=>{this.$nextTick(()=>{if(i.component.id!==g)return;let l=this.getTabs();l.includes(this.tab)||(this.tab=l[h-1]??this.tab)})})}),this.boundResetHandler=i=>{i.detail.livewireId!==g||i.detail.schemaKey!==D||T||u||this.$nextTick(()=>{this.tab=this.getTabs()[h-1]??this.tab})},window.addEventListener("reset-schema-component-state",this.boundResetHandler),m||(this.boundResizeHandler=this.debouncedUpdateTabsWithinDropdown.bind(this),window.addEventListener("resize",this.boundResizeHandler),this.updateTabsWithinDropdown())},calculateAvailableWidth(t){let e=window.getComputedStyle(t);return Math.floor(t.clientWidth)-Math.ceil(parseFloat(e.paddingLeft))*2},calculateContainerGap(t){let e=window.getComputedStyle(t);return Math.ceil(parseFloat(e.columnGap))},calculateDropdownIconWidth(t){let e=t.querySelector(".fi-icon");return Math.ceil(e.clientWidth)},calculateTabItemGap(t){let e=window.getComputedStyle(t);return Math.ceil(parseFloat(e.columnGap)||8)},calculateTabItemPadding(t){let e=window.getComputedStyle(t);return Math.ceil(parseFloat(e.paddingLeft))+Math.ceil(parseFloat(e.paddingRight))},findOverflowIndex(t,e,i,a,l,b){let p=t.map(n=>Math.ceil(n.clientWidth)),w=t.map(n=>{let d=n.querySelector(".fi-tabs-item-label"),s=n.querySelector(".fi-badge"),o=Math.ceil(d.clientWidth),c=s?Math.ceil(s.clientWidth):0;return{label:o,badge:c,total:o+(c>0?a+c:0)}});for(let n=0;n<t.length;n++){let d=p.slice(0,n+1).reduce((f,I)=>f+I,0),s=n*i,o=w.slice(n+1),c=o.length>0,v=c?Math.max(...o.map(f=>f.total)):0,y=c?l+v+a+b+i:0;if(d+s+y>e)return n}return-1},get isDropdownButtonVisible(){return this.withinDropdownMounted?this.withinDropdownIndex===null?!1:this.getTabs().findIndex(e=>e===this.tab)<this.withinDropdownIndex:!0},getTabs(){return this.$refs.tabsData?JSON.parse(this.$refs.tabsData.value):[]},updateQueryString(){if(!u)return;let t=new URL(window.location.href);t.searchParams.set(r,this.tab),history.replaceState(null,document.title,t.toString())},autofocusFields(t=!1){this.$nextTick(()=>{if(t&&document.activeElement&&document.activeElement!==document.body&&this.$el.compareDocumentPosition(document.activeElement)&Node.DOCUMENT_POSITION_PRECEDING)return;let e=this.$el.querySelectorAll(".fi-sc-tabs-tab.fi-active [autofocus]");for(let i of e)if(i.focus(),document.activeElement===i)break})},debouncedUpdateTabsWithinDropdown(){clearTimeout(this.resizeDebounceTimer),this.resizeDebounceTimer=setTimeout(()=>this.updateTabsWithinDropdown(),150)},async updateTabsWithinDropdown(){this.withinDropdownIndex=null,this.withinDropdownMounted=!1,await this.$nextTick();let t=this.$el.querySelector(".fi-tabs"),e=t.querySelector(".fi-tabs-item:last-child"),i=Array.from(t.children).slice(0,-1),a=i.map(s=>s.style.display);i.forEach(s=>s.style.display=""),t.offsetHeight;let l=this.calculateAvailableWidth(t),b=this.calculateContainerGap(t),p=this.calculateDropdownIconWidth(e),w=this.calculateTabItemGap(i[0]),n=this.calculateTabItemPadding(i[0]),d=this.findOverflowIndex(i,l,b,w,n,p);i.forEach((s,o)=>s.style.display=a[o]),d!==-1&&(this.withinDropdownIndex=d),this.withinDropdownMounted=!0},destroy(){this.unsubscribeLivewireHook?.(),this.boundResetHandler&&window.removeEventListener("reset-schema-component-state",this.boundResetHandler),this.boundResizeHandler&&window.removeEventListener("resize",this.boundResizeHandler),clearTimeout(this.resizeDebounceTimer)}}}export{x as default};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard reset-schema-component-state detail access before reading nested fields.

Line 1 reads i.detail.livewireId / i.detail.schemaKey directly. If the event is fired without a detail object, this throws and breaks tab logic. Add a null-safe guard (const detail = i.detail ?? {} or optional chaining) before comparisons.

Based on learnings: files under public/js/filament/ are generated assets, so apply this guard in the Filament source/override and regenerate published assets.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@public/js/filament/schemas/components/tabs.js` at line 1, The
reset-schema-component-state event handler (boundResetHandler) reads
i.detail.livewireId and i.detail.schemaKey directly which can throw if detail is
missing; update the handler to first null-safe guard the detail (e.g., const
detail = i.detail ?? {} or use optional chaining) and then compare
detail.livewireId and detail.schemaKey, keeping the existing conditions (also
preserve checks for T and u) and the this.$nextTick behavior; because this file
is a generated asset under public/js/filament, make the change in the Filament
source/override where boundResetHandler is defined and then regenerate the
published assets so the compiled tabs.js contains the null-safe guard.

@@ -1 +1 @@
function p({isSkippable:i,isStepPersistedInQueryString:n,key:r,startStep:o,stepQueryStringKey:h}){return{step:null,init(){this.step=this.getSteps().at(o-1),this.$watch("step",()=>{this.updateQueryString(),this.autofocusFields()}),this.autofocusFields(!0)},async requestNextStep(){await this.$wire.callSchemaComponentMethod(r,"nextStep",{currentStepIndex:this.getStepIndex(this.step)})},goToNextStep(){let t=this.getStepIndex(this.step)+1;t>=this.getSteps().length||(this.step=this.getSteps()[t],this.scroll())},goToPreviousStep(){let t=this.getStepIndex(this.step)-1;t<0||(this.step=this.getSteps()[t],this.scroll())},goToStep(t){let e=this.getStepIndex(t);e<=-1||!i&&e>this.getStepIndex(this.step)||(this.step=t,this.scroll())},scroll(){this.$nextTick(()=>{this.$refs.header?.children[this.getStepIndex(this.step)].scrollIntoView({behavior:"smooth",block:"start"})})},autofocusFields(t=!1){this.$nextTick(()=>{if(t&&document.activeElement&&document.activeElement!==document.body&&this.$el.compareDocumentPosition(document.activeElement)&Node.DOCUMENT_POSITION_PRECEDING)return;let e=this.$refs[`step-${this.step}`]?.querySelectorAll("[autofocus]")??[];for(let s of e)if(s.focus(),document.activeElement===s)break})},getStepIndex(t){let e=this.getSteps().findIndex(s=>s===t);return e===-1?0:e},getSteps(){return JSON.parse(this.$refs.stepsData.value)},isFirstStep(){return this.getStepIndex(this.step)<=0},isLastStep(){return this.getStepIndex(this.step)+1>=this.getSteps().length},isStepAccessible(t){return i||this.getStepIndex(this.step)>this.getStepIndex(t)},updateQueryString(){if(!n)return;let t=new URL(window.location.href);t.searchParams.set(h,this.step),history.replaceState(null,document.title,t.toString())}}}export{p as default};
function l({isSkippable:i,isStepPersistedInQueryString:n,key:o,livewireId:h,schemaKey:p,startStep:r,stepQueryStringKey:d}){return{boundResetHandler:null,step:null,init(){this.step=this.getSteps().at(r-1),this.$watch("step",()=>{this.updateQueryString(),this.autofocusFields()}),this.autofocusFields(!0),this.boundResetHandler=t=>{t.detail.livewireId!==h||t.detail.schemaKey!==p||n||this.$nextTick(()=>{this.step=this.getSteps().at(r-1)??this.step})},window.addEventListener("reset-schema-component-state",this.boundResetHandler)},async requestNextStep(){await this.$wire.callSchemaComponentMethod(o,"nextStep",{currentStepIndex:this.getStepIndex(this.step)})},goToNextStep(){let t=this.getStepIndex(this.step)+1;t>=this.getSteps().length||(this.step=this.getSteps()[t],this.scroll())},goToPreviousStep(){let t=this.getStepIndex(this.step)-1;t<0||(this.step=this.getSteps()[t],this.scroll())},goToStep(t){let e=this.getStepIndex(t);e<=-1||!i&&e>this.getStepIndex(this.step)||(this.step=t,this.scroll())},scroll(){this.$nextTick(()=>{this.$refs.header?.children[this.getStepIndex(this.step)].scrollIntoView({behavior:"smooth",block:"start"})})},autofocusFields(t=!1){this.$nextTick(()=>{if(t&&document.activeElement&&document.activeElement!==document.body&&this.$el.compareDocumentPosition(document.activeElement)&Node.DOCUMENT_POSITION_PRECEDING)return;let e=this.$refs[`step-${this.step}`]?.querySelectorAll("[autofocus]")??[];for(let s of e)if(s.focus(),document.activeElement===s)break})},getStepIndex(t){let e=this.getSteps().findIndex(s=>s===t);return e===-1?0:e},getSteps(){return JSON.parse(this.$refs.stepsData.value)},isFirstStep(){return this.getStepIndex(this.step)<=0},isLastStep(){return this.getStepIndex(this.step)+1>=this.getSteps().length},isStepAccessible(t){return i||this.getStepIndex(this.step)>this.getStepIndex(t)},updateQueryString(){if(!n)return;let t=new URL(window.location.href);t.searchParams.set(d,this.step),history.replaceState(null,document.title,t.toString())},destroy(){this.boundResetHandler&&window.removeEventListener("reset-schema-component-state",this.boundResetHandler)}}}export{l as default};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Make reset handler resilient to missing event.detail.

Line 1 dereferences t.detail.livewireId / t.detail.schemaKey without checking t.detail. Add a null-safe guard before those reads to avoid runtime errors during reset events.

Based on learnings: files under public/js/filament/ are generated assets, so implement this in the upstream/source layer and republish assets.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@public/js/filament/schemas/components/wizard.js` at line 1, The reset handler
currently dereferences t.detail.livewireId and t.detail.schemaKey in the
boundResetHandler inside the l(...) component, which can throw if event.detail
is missing; update the boundResetHandler to first check that t.detail is
non-null/defined (e.g., if (!t.detail) return or guard the comparisons), then
compare livewireId/schemaKey, and only call this.$nextTick to reset the step
when appropriate; apply this change in the upstream source for the Filament
wizard component (the function l / its boundResetHandler) so regenerated assets
under public/js/filament/ will include the fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant