@toolbox-web/grid 2.16.0 → 2.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +1 -1
- package/index.js.map +1 -1
- package/lib/plugins/shell/ShellPlugin.d.ts +26 -0
- package/lib/plugins/shell/index.js +1 -1
- package/lib/plugins/shell/index.js.map +1 -1
- package/lib/plugins/shell/shell-controller.d.ts +20 -0
- package/lib/plugins/shell/shell.d.ts +21 -0
- package/package.json +1 -1
- package/umd/grid.all.umd.js +1 -1
- package/umd/grid.all.umd.js.map +1 -1
- package/umd/grid.umd.js +1 -1
- package/umd/grid.umd.js.map +1 -1
- package/umd/plugins/shell.umd.js +1 -1
- package/umd/plugins/shell.umd.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/shell/shell.ts","../../../../../libs/grid/src/lib/plugins/shell/shell-controller.ts","../../../../../libs/grid/src/lib/plugins/shell/ShellPlugin.ts"],"sourcesContent":["/**\n * Shell infrastructure for grid header bar and tool panels.\n *\n * The shell is an optional wrapper layer that provides:\n * - Header bar with title, plugin content, and toolbar buttons\n * - Tool panels that plugins can register content into\n * - Light DOM parsing for framework-friendly configuration\n */\n\nimport { TOOL_PANEL_DUPLICATE, TOOL_PANEL_MISSING_ATTR, warnDiagnostic } from '../../core/internal/diagnostics';\nimport { escapeHtml, sanitizeHTML } from '../../core/internal/sanitize';\nimport type { IconValue } from '../../core/types';\nimport type { HeaderContentDefinition, ShellConfig, ToolbarContentDefinition, ToolPanelDefinition } from './types';\n\n// #region Types & State\n/**\n * Convert an IconValue to a string for rendering in HTML.\n */\nfunction iconToString(icon: IconValue | undefined): string {\n if (!icon) return '';\n if (typeof icon === 'string') return sanitizeHTML(icon);\n // For HTMLElement, get the outerHTML\n return icon.outerHTML;\n}\n\n/**\n * State for managing shell UI.\n *\n * This interface holds both configuration-like properties (toolPanels, headerContents)\n * and runtime state (isPanelOpen, expandedSections). The Maps allow for efficient\n * registration/unregistration of panels and content.\n */\nexport interface ShellState {\n /** Registered tool panels (from plugins + consumer API) */\n toolPanels: Map<string, ToolPanelDefinition>;\n /** Registered header content (from plugins + consumer API) */\n headerContents: Map<string, HeaderContentDefinition>;\n /** Toolbar content registered via API or light DOM */\n toolbarContents: Map<string, ToolbarContentDefinition>;\n /** Whether a <tbw-grid-tool-buttons> container was found in light DOM */\n hasToolButtonsContainer: boolean;\n /** Light DOM header content elements */\n lightDomHeaderContent: HTMLElement[];\n /** Light DOM header title from <tbw-grid-header title=\"...\"> */\n lightDomTitle: string | null;\n /** IDs of tool panels registered from light DOM (to avoid re-parsing) */\n lightDomToolPanelIds: Set<string>;\n /**\n * IDs of light DOM tool panels whose `render` has already been bound to a\n * framework-adapter renderer. Used by `parseLightDomToolPanels` to detect\n * the FIRST adapter attach (which legitimately needs to swap the vanilla\n * fallback for the real adapter renderer and tear down any in-progress\n * render) vs. subsequent re-parses (which must NOT tear down — doing so\n * unmounts the user's React/Vue/Angular component, losing local state and\n * resetting scroll position inside the panel).\n */\n adapterBoundToolPanelIds: Set<string>;\n /** IDs of toolbar content registered from light DOM (to avoid re-parsing) */\n lightDomToolbarContentIds: Set<string>;\n /** IDs of tool panels registered via registerToolPanel API */\n apiToolPanelIds: Set<string>;\n /** IDs of header content registered via registerHeaderContent API */\n apiHeaderContentIds: Set<string>;\n /** Whether the tool panel sidebar is open */\n isPanelOpen: boolean;\n /** Which accordion sections are expanded (by panel ID) */\n expandedSections: Set<string>;\n /** Whether light DOM header content has been moved to placeholder (perf optimization) */\n lightDomContentMoved: boolean;\n /** Cleanup functions for header content render returns */\n headerContentCleanups: Map<string, () => void>;\n /** Cleanup functions for each panel section's render return */\n panelCleanups: Map<string, () => void>;\n /** Cleanup functions for toolbar content render returns */\n toolbarContentCleanups: Map<string, () => void>;\n /**\n * Element the dropdown popover is currently anchored to (mode: 'dropdown').\n * Tracked so dismissal logic can ignore clicks on the anchor and the\n * `anchor-name` it set can be cleared on close. `null` when no dropdown is open.\n */\n dropdownAnchorEl?: HTMLElement | null;\n /**\n * Unique `anchor-name` assigned to the current dropdown's trigger (and\n * mirrored as `position-anchor` on the popover). Minted per show so multiple\n * grids on one page never collide on a shared anchor name — a collision would\n * position one grid's popover against another grid's trigger. `null` when no\n * dropdown is open.\n */\n dropdownAnchorName?: string | null;\n}\n\n/**\n * Runtime-only shell state (not configuration).\n *\n * Configuration (toolPanels, headerContents, toolbarContents, title) lives in\n * effectiveConfig.shell. This state holds runtime UI state and cleanup functions.\n */\nexport interface ShellRuntimeState {\n /** Whether the tool panel sidebar is currently open */\n isPanelOpen: boolean;\n /** Which accordion sections are currently expanded (by panel ID) */\n expandedSections: Set<string>;\n /** Cleanup functions for header content render returns */\n headerContentCleanups: Map<string, () => void>;\n /** Cleanup functions for each panel section's render return */\n panelCleanups: Map<string, () => void>;\n /** Cleanup functions for toolbar content render returns */\n toolbarContentCleanups: Map<string, () => void>;\n /** IDs of tool panels registered from light DOM (to avoid re-parsing) */\n lightDomToolPanelIds: Set<string>;\n /** IDs of tool panels registered via registerToolPanel API */\n apiToolPanelIds: Set<string>;\n /** Whether a <tbw-grid-tool-buttons> container was found in light DOM */\n hasToolButtonsContainer: boolean;\n}\n\n/**\n * Create initial shell runtime state.\n */\nexport function createShellRuntimeState(): ShellRuntimeState {\n return {\n isPanelOpen: false,\n expandedSections: new Set(),\n headerContentCleanups: new Map(),\n panelCleanups: new Map(),\n toolbarContentCleanups: new Map(),\n lightDomToolPanelIds: new Set(),\n apiToolPanelIds: new Set(),\n hasToolButtonsContainer: false,\n };\n}\n\n/**\n * Create initial shell state.\n */\nexport function createShellState(): ShellState {\n return {\n toolPanels: new Map(),\n headerContents: new Map(),\n toolbarContents: new Map(),\n hasToolButtonsContainer: false,\n lightDomHeaderContent: [],\n lightDomTitle: null,\n lightDomToolPanelIds: new Set(),\n adapterBoundToolPanelIds: new Set(),\n lightDomToolbarContentIds: new Set(),\n apiToolPanelIds: new Set(),\n apiHeaderContentIds: new Set(),\n isPanelOpen: false,\n expandedSections: new Set(),\n headerContentCleanups: new Map(),\n panelCleanups: new Map(),\n toolbarContentCleanups: new Map(),\n lightDomContentMoved: false,\n dropdownAnchorEl: null,\n dropdownAnchorName: null,\n };\n}\n// #endregion\n\n// #region Render Functions\n/**\n * Determine if shell header should be rendered.\n * Reads only from effectiveConfig.shell (single source of truth).\n */\nexport function shouldRenderShellHeader(config: ShellConfig | undefined): boolean {\n // Check if title is configured\n if (config?.header?.title) return true;\n\n // Check if config has toolbar contents\n if (config?.header?.toolbarContents?.length) return true;\n\n // Check if any tool panels are registered\n if (config?.toolPanels?.length) return true;\n\n // Check if any header content is registered\n if (config?.headerContents?.length) return true;\n\n // Check if light DOM has header elements\n if (config?.header?.lightDomContent?.length) return true;\n\n // Check if a toolbar buttons container was found\n if (config?.header?.hasToolButtonsContainer) return true;\n\n return false;\n}\n\n/**\n * Determine whether the visible shell header bar (`.tbw-shell-header`) should\n * be rendered.\n *\n * Distinct from {@link shouldRenderShellHeader}, which decides whether the\n * shell *wrapper* (including the body that hosts the tool panel) exists at\n * all. The header bar can be suppressed via `header.visible: false` while the\n * shell body and tool panels still render — letting consumers drive panels\n * from their own UI (e.g. a utility-column header icon).\n */\nexport function shouldRenderHeaderBar(config: ShellConfig | undefined): boolean {\n return config?.header?.visible !== false;\n}\n\n/**\n * Render the shell header HTML.\n *\n * Toolbar contents come from two sources:\n * 1. Light DOM slot (users provide their own HTML in <tbw-grid-tool-buttons>)\n * 2. Config/API with render function (programmatic insertion)\n *\n * Users have full control over toolbar HTML, styling, and behavior.\n * The only button the grid creates is the tool panel toggle.\n *\n * @param toolPanelIcon - Icon for the tool panel toggle (from grid icon config)\n */\nexport function renderShellHeader(\n config: ShellConfig | undefined,\n state: ShellState,\n toolPanelIcon?: IconValue,\n): string {\n const title = config?.header?.title ?? state.lightDomTitle ?? '';\n const hasTitle = !!title;\n\n // Build tool panel button content: use data-icon for CSS, inject content only for JS overrides\n let toolPanelBtnContent = '';\n if (toolPanelIcon !== undefined) {\n // JS override: inject content but still set data-icon\n toolPanelBtnContent = typeof toolPanelIcon === 'string' ? sanitizeHTML(toolPanelIcon) : toolPanelIcon.outerHTML;\n }\n\n // Get all toolbar contents from effectiveConfig (already merged: config + API + light DOM)\n // The config-manager merges state.toolbarContents into effectiveConfig.shell.header.toolbarContents\n // Also include state.toolbarContents directly for cases where renderShellHeader is called\n // before config-manager has merged (e.g., unit tests, initial render)\n const configContents = config?.header?.toolbarContents ?? [];\n const stateContents = [...state.toolbarContents.values()];\n\n // Merge: use config contents, add state contents that aren't in config\n const configIds = new Set(configContents.map((c) => c.id));\n const allContents = [...configContents];\n for (const content of stateContents) {\n if (!configIds.has(content.id)) {\n allContents.push(content);\n }\n }\n\n const hasCustomContent = allContents.length > 0;\n const hasPanels = state.toolPanels.size > 0;\n // Allow consumers to suppress the built-in toggle (and the separator that\n // only exists to space it from custom toolbar contents) so they can render\n // their own design-system button and wire it to `grid.toggleToolPanel()`.\n // Also suppress when the tool panel is locked open — toggling is disabled.\n // Default true preserves existing behavior.\n const isLocked = config?.toolPanel?.locked === true;\n const showToggle = hasPanels && config?.header?.toolPanelToggle !== false && !isLocked;\n const showSeparator = hasCustomContent && showToggle;\n\n // Sort contents by order for slot placement\n const sortedContents = [...allContents].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));\n\n // Build toolbar HTML\n let toolbarHtml = '';\n\n // Create slots for all contents (unified: config + API + light DOM)\n for (const content of sortedContents) {\n toolbarHtml += `<div class=\"tbw-toolbar-content-slot\" data-toolbar-content=\"${content.id}\"></div>`;\n }\n\n // Separator between custom content and panel toggle\n if (showSeparator) {\n toolbarHtml += '<div class=\"tbw-toolbar-separator\"></div>';\n }\n\n // Single panel toggle button (the ONLY button the grid creates).\n // Suppressed when `shell.header.toolPanelToggle === false` so consumers can\n // BYO toggle button without hiding library DOM via CSS.\n if (showToggle) {\n const isOpen = state.isPanelOpen;\n const toggleClass = isOpen ? 'tbw-toolbar-btn active' : 'tbw-toolbar-btn';\n // type=\"button\" is required: without it a <button> inside a <form>\n // defaults to type=\"submit\" and clicking the toggle would submit the\n // surrounding form. Internal grid buttons must never trigger form\n // submission. See issue #296.\n toolbarHtml += `<button type=\"button\" class=\"${toggleClass}\" data-panel-toggle data-icon=\"tool-panel\" title=\"Settings\" aria-label=\"Toggle settings panel\" aria-pressed=\"${isOpen}\" aria-controls=\"tbw-tool-panel\">${toolPanelBtnContent}</button>`;\n }\n\n return `\n <div class=\"tbw-shell-header\" part=\"shell-header\" role=\"presentation\">\n ${hasTitle ? `<div class=\"tbw-shell-title\">${escapeHtml(title)}</div>` : ''}\n <div class=\"tbw-shell-content\" part=\"shell-content\" role=\"presentation\" data-light-dom-header-content></div>\n <div class=\"tbw-shell-toolbar\" part=\"shell-toolbar\" role=\"presentation\">\n ${toolbarHtml}\n </div>\n </div>\n `;\n}\n\n/**\n * Render the shell body wrapper HTML (contains grid content + accordion-style tool panel).\n * @param icons - Optional icons for expand/collapse chevrons (from grid config)\n */\nexport function renderShellBody(\n config: ShellConfig | undefined,\n state: ShellState,\n gridContentHtml: string,\n icons?: { expand?: IconValue; collapse?: IconValue },\n): string {\n const position = config?.toolPanel?.position ?? 'right';\n const mode = config?.toolPanel?.mode ?? 'overlay';\n const hasPanel = state.toolPanels.size > 0;\n const isOpen = state.isPanelOpen;\n const hasJsExpandIcon = icons?.expand !== undefined;\n const expandIcon = hasJsExpandIcon ? iconToString(icons.expand) : '';\n\n // Sort panels by order for accordion sections\n const sortedPanels = [...state.toolPanels.values()].sort((a, b) => (a.order ?? 100) - (b.order ?? 100));\n const isSinglePanel = sortedPanels.length === 1;\n\n // Build accordion sections HTML\n let accordionHtml = '';\n for (const panel of sortedPanels) {\n const isExpanded = state.expandedSections.has(panel.id);\n const iconHtml = panel.icon ? `<span class=\"tbw-accordion-icon\">${panel.icon}</span>` : '';\n // Hide chevron for single panel (no toggling needed)\n // Always use expandIcon (▶) — CSS rotation handles the expanded state\n const chevronHtml = isSinglePanel\n ? ''\n : `<span class=\"tbw-accordion-chevron\" data-icon=\"expand\">${expandIcon}</span>`;\n // Disable accordion toggle for single panel\n const sectionClasses = `tbw-accordion-section${isExpanded ? ' expanded' : ''}${isSinglePanel ? ' single' : ''}`;\n accordionHtml += `\n <div class=\"${sectionClasses}\" data-section=\"${panel.id}\">\n <button type=\"button\" class=\"tbw-accordion-header\" aria-expanded=\"${isExpanded}\" aria-controls=\"tbw-section-${panel.id}\"${isSinglePanel ? ' aria-disabled=\"true\"' : ''}>\n ${iconHtml}\n <span class=\"tbw-accordion-title\">${panel.title}</span>\n ${chevronHtml}\n </button>\n <div class=\"tbw-accordion-content\" id=\"tbw-section-${panel.id}\" role=\"presentation\"></div>\n </div>\n `;\n }\n\n // Resize handle position depends on panel position\n const resizeHandlePosition = position === 'left' ? 'right' : 'left';\n\n const panelHtml = hasPanel\n ? `\n <aside class=\"tbw-tool-panel${isOpen ? ' open' : ''}\" part=\"tool-panel\" data-position=\"${position}\" role=\"presentation\" id=\"tbw-tool-panel\"${mode === 'dropdown' ? ' popover=\"manual\"' : ''}>\n <div class=\"tbw-tool-panel-resize\" data-resize-handle data-handle-position=\"${resizeHandlePosition}\" aria-hidden=\"true\"></div>\n <div class=\"tbw-tool-panel-content\" role=\"presentation\">\n <div class=\"tbw-accordion\">\n ${accordionHtml}\n </div>\n </div>\n </aside>\n `\n : '';\n\n // For left position, panel comes before content in DOM order\n if (position === 'left') {\n return `\n <div class=\"tbw-shell-body\" data-mode=\"${mode}\">\n ${panelHtml}\n <div class=\"tbw-grid-content\">\n ${gridContentHtml}\n </div>\n </div>\n `;\n }\n\n return `\n <div class=\"tbw-shell-body\" data-mode=\"${mode}\">\n <div class=\"tbw-grid-content\">\n ${gridContentHtml}\n </div>\n ${panelHtml}\n </div>\n `;\n}\n// #endregion\n\n// #region Light DOM Parsing\n/**\n * Parse light DOM shell elements (tbw-grid-header, etc.).\n * Safe to call multiple times - will only parse once when elements are available.\n */\nexport function parseLightDomShell(host: HTMLElement, state: ShellState): void {\n const headerEl = host.querySelector('tbw-grid-header');\n if (!headerEl) return;\n\n // Parse title attribute (only if not already parsed)\n if (!state.lightDomTitle) {\n const title = headerEl.getAttribute('title');\n if (title) {\n state.lightDomTitle = title;\n }\n }\n\n // Parse header content elements - store references but don't set slot (light DOM doesn't use slots)\n const headerContents = headerEl.querySelectorAll('tbw-grid-header-content');\n if (headerContents.length > 0 && state.lightDomHeaderContent.length === 0) {\n state.lightDomHeaderContent = Array.from(headerContents) as HTMLElement[];\n }\n\n // Hide the light DOM header container (it was just for declarative config)\n (headerEl as HTMLElement).style.display = 'none';\n}\n\n/**\n * Callback type for creating a toolbar content renderer from a light DOM container.\n * This is used by framework adapters (Angular, React, etc.) to create renderers\n * from their template syntax.\n */\nexport type ToolbarContentRendererFactory = (\n container: HTMLElement,\n) => ((target: HTMLElement) => void | (() => void)) | undefined;\n\n/**\n * Parse toolbar buttons container element (<tbw-grid-tool-buttons>).\n * This is a content container - we don't parse individual children.\n * The entire container content is registered as a single toolbar content entry.\n *\n * Example:\n * ```html\n * <tbw-grid>\n * <tbw-grid-tool-buttons>\n * <button>My button</button>\n * <button>My other button</button>\n * </tbw-grid-tool-buttons>\n * </tbw-grid>\n * ```\n *\n * The container's children are moved to the toolbar area during render.\n * We treat this as opaque content - users control what goes inside.\n *\n * @param host - The grid host element\n * @param state - Shell state to update\n * @param rendererFactory - Optional factory for creating renderers (used by framework adapters)\n */\nexport function parseLightDomToolButtons(\n host: HTMLElement,\n state: ShellState,\n rendererFactory?: ToolbarContentRendererFactory,\n): void {\n // Look for the toolbar buttons container element\n const toolButtonsContainer = host.querySelector(':scope > tbw-grid-tool-buttons') as HTMLElement | null;\n if (!toolButtonsContainer) return;\n\n // Mark that we found the container (for shouldRenderShellHeader)\n state.hasToolButtonsContainer = true;\n\n // Skip if already registered\n const id = 'light-dom-toolbar-content';\n if (state.lightDomToolbarContentIds.has(id)) return;\n\n // Register as a single content entry with a render function\n const adapterRenderer = rendererFactory?.(toolButtonsContainer);\n\n const contentDef: ToolbarContentDefinition = {\n id,\n order: 0, // Light DOM content comes first\n render:\n adapterRenderer ??\n ((target: HTMLElement) => {\n // Move all children from the light DOM container to the target\n while (toolButtonsContainer.firstChild) {\n target.appendChild(toolButtonsContainer.firstChild);\n }\n // Return cleanup that moves children back to original container\n // This preserves them across full re-renders that destroy the slot\n return () => {\n while (target.firstChild) {\n toolButtonsContainer.appendChild(target.firstChild);\n }\n };\n }),\n };\n\n state.toolbarContents.set(id, contentDef);\n state.lightDomToolbarContentIds.add(id);\n\n // Hide the original container\n toolButtonsContainer.style.display = 'none';\n}\n\n/**\n * Callback type for creating a tool panel renderer from a light DOM element.\n * This is used by framework adapters (Angular, React, etc.) to create renderers\n * from their template syntax.\n */\nexport type ToolPanelRendererFactory = (\n element: HTMLElement,\n) => ((container: HTMLElement) => void | (() => void)) | undefined;\n\n/**\n * Parse light DOM tool panel elements (<tbw-grid-tool-panel>).\n * These can appear as direct children of <tbw-grid> for declarative tool panel configuration.\n *\n * Attributes:\n * - `id` (required): Unique panel identifier\n * - `title` (required): Panel title shown in accordion header\n * - `icon`: Icon for accordion section header (emoji or text)\n * - `tooltip`: Tooltip for accordion section header\n * - `order`: Panel order priority (lower = first, default: 100)\n *\n * For vanilla JS, the element's innerHTML is used as the panel content.\n * For framework adapters, the adapter can provide a custom renderer factory.\n *\n * @param host - The grid host element\n * @param state - Shell state to update\n * @param rendererFactory - Optional factory for creating renderers (used by framework adapters)\n */\nexport function parseLightDomToolPanels(\n host: HTMLElement,\n state: ShellState,\n rendererFactory?: ToolPanelRendererFactory,\n): void {\n const toolPanelElements = host.querySelectorAll(':scope > tbw-grid-tool-panel');\n\n toolPanelElements.forEach((element) => {\n const panelEl = element as HTMLElement;\n const id = panelEl.getAttribute('id');\n const title = panelEl.getAttribute('title');\n\n // Skip if required attributes are missing\n if (!id || !title) {\n warnDiagnostic(\n TOOL_PANEL_MISSING_ATTR,\n `Tool panel missing required id or title attribute: id=\"${id ?? ''}\", title=\"${title ?? ''}\"`,\n );\n return;\n }\n\n const icon = panelEl.getAttribute('icon') ?? undefined;\n const tooltip = panelEl.getAttribute('tooltip') ?? undefined;\n const order = parseInt(panelEl.getAttribute('order') ?? '100', 10);\n\n // Try framework adapter first, then fall back to innerHTML\n let render: (container: HTMLElement) => void | (() => void);\n\n const adapterRenderer = rendererFactory?.(panelEl);\n if (adapterRenderer) {\n render = adapterRenderer;\n } else {\n // Vanilla fallback: use sanitized innerHTML as static content.\n // Light DOM authored markup is generally trusted, but we sanitize\n // defensively in case the panel content was server-rendered from data.\n const content = sanitizeHTML(panelEl.innerHTML.trim());\n render = (container: HTMLElement) => {\n const wrapper = document.createElement('div');\n wrapper.innerHTML = content;\n container.appendChild(wrapper);\n return () => wrapper.remove();\n };\n }\n\n // Check if panel was already parsed\n const existingPanel = state.toolPanels.get(id);\n\n // Precedence (#370): a panel contributed by a plugin (or registered via the\n // grid API) owns its id. A light-DOM <tbw-grid-tool-panel> with the same id\n // is ignored so the owner can rely on its own DOM existing. We detect a\n // foreign owner by the id being present in toolPanels but NOT tracked as a\n // light-DOM id (which would mean this is just a re-parse of our own panel).\n if (existingPanel && !state.lightDomToolPanelIds.has(id)) {\n warnDiagnostic(\n TOOL_PANEL_DUPLICATE,\n `Tool panel \"${id}\" is already provided by a plugin or the grid API; ignoring the matching light-DOM <tbw-grid-tool-panel>.`,\n );\n panelEl.style.display = 'none';\n return;\n }\n\n // If already parsed and we have an adapter renderer, refresh the render\n // function and (potentially) re-read attributes from DOM (Angular may have\n // updated them after the initial parse). However, **only tear down the\n // already-rendered panel content** when something the user can observe has\n // actually changed:\n // - First time the adapter renderer attaches (we may have been using\n // the vanilla innerHTML fallback up to now)\n // - Any attribute the panel header renders from changed\n //\n // Otherwise the user's React/Vue/Angular component is already mounted in\n // the panel and is happily managing its own state. A teardown here unmounts\n // it (losing local state) and re-mounts it on next renderPanelContent —\n // which, for a scrollable panel, resets `scrollTop` to 0. This was the\n // cause of the \"tool panel scroll jumps to top on column visibility toggle\"\n // bug: every `grid.gridConfig = …` setter in the adapter eventually calls\n // back into parseLightDomToolPanels (via #applyGridConfigUpdate), and the\n // previous unconditional `cleanup()` here threw away the user's component\n // tree on every config sync.\n if (existingPanel) {\n if (adapterRenderer) {\n const firstAdapterAttach = !state.adapterBoundToolPanelIds.has(id);\n const attrsChanged =\n existingPanel.order !== order || existingPanel.icon !== icon || existingPanel.tooltip !== tooltip;\n\n // Always keep the render function fresh — adapter renderers capture\n // the current grid element / portal context and the most recent\n // children, so a stale reference would render against an old closure.\n existingPanel.render = render;\n existingPanel.order = order;\n existingPanel.icon = icon;\n existingPanel.tooltip = tooltip;\n state.adapterBoundToolPanelIds.add(id);\n\n if (firstAdapterAttach || attrsChanged) {\n const cleanup = state.panelCleanups.get(id);\n if (cleanup) {\n cleanup();\n state.panelCleanups.delete(id);\n }\n }\n }\n return;\n }\n\n // Register the tool panel\n const panel: ToolPanelDefinition = {\n id,\n title,\n icon,\n tooltip,\n order,\n render,\n };\n\n state.toolPanels.set(id, panel);\n state.lightDomToolPanelIds.add(id);\n if (adapterRenderer) {\n state.adapterBoundToolPanelIds.add(id);\n }\n\n // Hide the light DOM element\n panelEl.style.display = 'none';\n });\n}\n// #endregion\n\n// #region Event Handlers\n/**\n * Set up event listeners for shell toolbar buttons and accordion.\n */\nexport function setupShellEventListeners(\n renderRoot: Element,\n config: ShellConfig | undefined,\n state: ShellState,\n callbacks: {\n onPanelToggle: () => void;\n onSectionToggle: (sectionId: string) => void;\n /** Invoked when the in-panel close (✕) button is clicked. */\n onPanelClose?: () => void;\n },\n): void {\n const toolbar = renderRoot.querySelector('.tbw-shell-toolbar');\n if (toolbar) {\n toolbar.addEventListener('click', (e) => {\n const target = e.target as HTMLElement;\n\n // Handle single panel toggle button\n const panelToggle = target.closest('[data-panel-toggle]') as HTMLElement | null;\n if (panelToggle) {\n callbacks.onPanelToggle();\n return;\n }\n });\n }\n\n // In-panel close (✕) button — only present when the header bar is hidden.\n const panel = renderRoot.querySelector('.tbw-tool-panel');\n if (panel && callbacks.onPanelClose) {\n panel.addEventListener('click', (e) => {\n const target = e.target as HTMLElement;\n if (target.closest('[data-panel-close]')) {\n callbacks.onPanelClose!();\n }\n });\n }\n\n // Accordion header clicks\n const accordion = renderRoot.querySelector('.tbw-accordion');\n if (accordion) {\n accordion.addEventListener('click', (e) => {\n const target = e.target as HTMLElement;\n const header = target.closest('.tbw-accordion-header') as HTMLElement | null;\n if (header) {\n const section = header.closest('[data-section]') as HTMLElement | null;\n const sectionId = section?.getAttribute('data-section');\n if (sectionId) {\n callbacks.onSectionToggle(sectionId);\n }\n }\n });\n }\n}\n\n/**\n * Whether `node` belongs to `gridElement` — directly (light DOM) or via the\n * grid's shadow root (the node's root-node host is the grid). Used to scope\n * click-outside checks so one grid's listener never treats another grid's\n * toggle or panel as \"inside\" itself.\n */\nfunction isInGrid(node: Node, gridElement: Element): boolean {\n if (gridElement.contains(node)) return true;\n const root = node.getRootNode();\n return root instanceof ShadowRoot && root.host === gridElement;\n}\n\n/**\n * Set up a click-outside listener that closes the tool panel when the user\n * clicks anywhere in the document outside the tool panel itself.\n *\n * Only active when `config.toolPanel.closeOnClickOutside` is `true` AND the\n * panel is open. The listener is added on `document` `mousedown` (not `click`)\n * so it fires before focus changes and catches clicks anywhere in the window —\n * including outside the grid. Uses `composedPath()` so clicks inside the\n * grid's shadow DOM are correctly attributed (shadow-retargeted `event.target`\n * would otherwise always be the host element).\n *\n * @returns A cleanup function that removes the listener.\n */\nexport function setupClickOutsideDismiss(\n gridElement: Element,\n config: ShellConfig | undefined,\n state: ShellState,\n onClose: () => void,\n): () => void {\n const mode = config?.toolPanel?.mode;\n\n // Dropdown popovers always light-dismiss on outside click (standard dropdown\n // behavior), regardless of the `closeOnClickOutside` flag. Other modes only\n // dismiss when the consumer opts in.\n if (mode !== 'dropdown' && !config?.toolPanel?.closeOnClickOutside) {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n return () => {};\n }\n\n // In push mode the panel does not overlap grid content, so there is\n // no meaningful \"outside\" to click against. Treat the option as a no-op.\n if (mode === 'push') {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n return () => {};\n }\n\n const handler = (e: Event) => {\n if (!state.isPanelOpen) return;\n\n // Ignore clicks inside the tool panel itself, on the toggle button, or on\n // the dropdown's resolved anchor element (so a consumer's BYO trigger can\n // toggle without the outside-click handler racing the button's own click).\n // composedPath() pierces shadow boundaries so this works for both the\n // light-DOM toggle and the shadow-DOM panel.\n //\n // CRITICAL: the panel/toggle match MUST be scoped to THIS grid via\n // `isInGrid`. With multiple grids on one page, a click on another grid's\n // toggle/panel is an \"outside\" click for us and must still dismiss \\u2014\n // otherwise opening a dropdown in grid B leaves grid A's dropdown stuck\n // open (#375 follow-up).\n const anchorEl = state.dropdownAnchorEl;\n for (const node of e.composedPath()) {\n if (anchorEl != null && node === anchorEl) return;\n if (\n node instanceof Element &&\n (node.classList?.contains('tbw-tool-panel') || node.matches?.('[data-panel-toggle]')) &&\n isInGrid(node, gridElement)\n ) {\n return;\n }\n }\n\n onClose();\n };\n\n document.addEventListener('mousedown', handler);\n return () => document.removeEventListener('mousedown', handler);\n}\n\n/**\n * Set up an Escape-key listener that closes an open overlay tool panel.\n *\n * Active only in `overlay` mode (a push panel is a persistent sidebar, not a\n * transient overlay, so Esc does not dismiss it — the in-panel close button is\n * the affordance there). Yields to more specific handlers: if another listener\n * already called `preventDefault()` on the keydown (e.g. an active cell editor\n * cancelling an edit), the panel is left open.\n *\n * @returns A cleanup function that removes the listener.\n */\nexport function setupEscapeDismiss(\n config: ShellConfig | undefined,\n state: ShellState,\n onClose: () => void,\n): () => void {\n // Push panels are persistent — Esc only dismisses overlay panels.\n if (config?.toolPanel?.mode === 'push') {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n return () => {};\n }\n\n const handler = (e: KeyboardEvent) => {\n if (e.key !== 'Escape' || e.defaultPrevented) return;\n if (!state.isPanelOpen) return;\n onClose();\n };\n\n document.addEventListener('keydown', handler);\n return () => document.removeEventListener('keydown', handler);\n}\n\n/**\n * Set up resize handle for tool panel.\n * Returns a cleanup function to remove event listeners.\n */\nexport function setupToolPanelResize(\n renderRoot: Element,\n config: ShellConfig | undefined,\n onResize: (width: number) => void,\n): () => void {\n const panel = renderRoot.querySelector('.tbw-tool-panel') as HTMLElement | null;\n const handle = renderRoot.querySelector('[data-resize-handle]') as HTMLElement | null;\n const shellBody = renderRoot.querySelector('.tbw-shell-body') as HTMLElement | null;\n if (!panel || !handle || !shellBody) {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n return () => {};\n }\n\n const position = config?.toolPanel?.position ?? 'right';\n const minWidth = 200;\n\n let startX = 0;\n let startWidth = 0;\n let maxWidth = 0;\n let isResizing = false;\n\n const onMouseMove = (e: MouseEvent) => {\n if (!isResizing) return;\n e.preventDefault();\n\n // For right-positioned panel: dragging left (negative clientX change) should expand\n // For left-positioned panel: dragging right (positive clientX change) should expand\n const delta = position === 'left' ? e.clientX - startX : startX - e.clientX;\n const newWidth = Math.min(maxWidth, Math.max(minWidth, startWidth + delta));\n\n panel.style.width = `${newWidth}px`;\n };\n\n const onMouseUp = () => {\n if (!isResizing) return;\n isResizing = false;\n handle.classList.remove('resizing');\n panel.style.transition = ''; // Re-enable transition\n document.body.style.cursor = '';\n document.body.style.userSelect = '';\n\n // Get final width and notify\n const finalWidth = panel.getBoundingClientRect().width;\n onResize(finalWidth);\n\n document.removeEventListener('mousemove', onMouseMove);\n document.removeEventListener('mouseup', onMouseUp);\n };\n\n const onMouseDown = (e: MouseEvent) => {\n e.preventDefault();\n isResizing = true;\n startX = e.clientX;\n startWidth = panel.getBoundingClientRect().width;\n // Calculate max width dynamically based on grid container width\n maxWidth = shellBody.getBoundingClientRect().width - 20; // Leave 20px margin\n handle.classList.add('resizing');\n panel.style.transition = 'none'; // Disable transition for smooth resize\n document.body.style.cursor = 'col-resize';\n document.body.style.userSelect = 'none';\n\n document.addEventListener('mousemove', onMouseMove);\n document.addEventListener('mouseup', onMouseUp);\n };\n\n handle.addEventListener('mousedown', onMouseDown);\n\n // Return cleanup function\n return () => {\n handle.removeEventListener('mousedown', onMouseDown);\n document.removeEventListener('mousemove', onMouseMove);\n document.removeEventListener('mouseup', onMouseUp);\n };\n}\n// #endregion\n\n// #region Content Rendering\n/**\n * Render toolbar content (render functions) into toolbar slots.\n * All contents (config + API + light DOM) are now unified.\n */\nexport function renderCustomToolbarContents(\n renderRoot: Element,\n config: ShellConfig | undefined,\n state: ShellState,\n): void {\n // Merge config contents with state contents (same logic as renderShellHeader)\n const configContents = config?.header?.toolbarContents ?? [];\n const stateContents = [...state.toolbarContents.values()];\n const configIds = new Set(configContents.map((c) => c.id));\n const allContents = [...configContents];\n for (const content of stateContents) {\n if (!configIds.has(content.id)) {\n allContents.push(content);\n }\n }\n\n // Only process contents that need rendering (have render and cleanup not already set)\n for (const content of allContents) {\n // Skip if already rendered (cleanup exists)\n if (state.toolbarContentCleanups.has(content.id)) continue;\n if (!content.render) continue;\n\n const slot = renderRoot.querySelector(`[data-toolbar-content=\"${content.id}\"]`);\n if (!slot) continue;\n\n const cleanup = content.render(slot as HTMLElement);\n if (cleanup) {\n state.toolbarContentCleanups.set(content.id, cleanup);\n }\n }\n}\n\n/**\n * Render header content from plugins into the shell content area.\n * Also moves light DOM header content to the placeholder (once).\n */\nexport function renderHeaderContent(renderRoot: Element, state: ShellState): void {\n // Early exit if nothing to do (most common path after initial render)\n const hasLightDomContent = state.lightDomHeaderContent.length > 0 && !state.lightDomContentMoved;\n const hasPluginContent = state.headerContents.size > 0;\n if (!hasLightDomContent && !hasPluginContent) return;\n\n const contentArea = renderRoot.querySelector('.tbw-shell-content');\n if (!contentArea) return;\n\n // Move light DOM header content to placeholder - only once (perf optimization)\n if (hasLightDomContent) {\n for (const el of state.lightDomHeaderContent) {\n el.style.display = ''; // Show it (was hidden in the original container)\n contentArea.appendChild(el);\n }\n state.lightDomContentMoved = true;\n }\n\n // Sort by order\n const sortedContents = [...state.headerContents.values()].sort((a, b) => (a.order ?? 100) - (b.order ?? 100));\n\n for (const content of sortedContents) {\n // Clean up previous render if any\n const existingCleanup = state.headerContentCleanups.get(content.id);\n if (existingCleanup) {\n existingCleanup();\n state.headerContentCleanups.delete(content.id);\n }\n\n // Check if container already exists\n let container = contentArea.querySelector(`[data-header-content=\"${content.id}\"]`) as HTMLElement | null;\n if (!container) {\n container = document.createElement('div');\n container.setAttribute('data-header-content', content.id);\n contentArea.appendChild(container);\n }\n\n const cleanup = content.render(container);\n if (cleanup) {\n state.headerContentCleanups.set(content.id, cleanup);\n }\n }\n}\n\n/**\n * Render content for expanded accordion sections.\n * @param icons - Optional icons for expand/collapse chevrons (from grid config)\n */\nexport function renderPanelContent(\n renderRoot: Element,\n state: ShellState,\n _icons?: { expand?: IconValue; collapse?: IconValue },\n): void {\n if (!state.isPanelOpen) return;\n\n for (const [panelId, panel] of state.toolPanels) {\n const isExpanded = state.expandedSections.has(panelId);\n const section = renderRoot.querySelector(`[data-section=\"${panelId}\"]`);\n const contentArea = section?.querySelector('.tbw-accordion-content') as HTMLElement | null;\n\n if (!section || !contentArea) continue;\n\n // Update expanded state\n section.classList.toggle('expanded', isExpanded);\n const header = section.querySelector('.tbw-accordion-header');\n if (header) {\n header.setAttribute('aria-expanded', String(isExpanded));\n }\n // Don't swap chevron icon — CSS rotation handles expanded/collapsed state\n\n if (isExpanded) {\n // Check if content is already rendered\n if (contentArea.children.length === 0) {\n // Render panel content\n const cleanup = panel.render(contentArea);\n if (cleanup) {\n state.panelCleanups.set(panelId, cleanup);\n }\n }\n } else {\n // Clean up and clear content when collapsed\n const cleanup = state.panelCleanups.get(panelId);\n if (cleanup) {\n cleanup();\n state.panelCleanups.delete(panelId);\n }\n contentArea.innerHTML = '';\n }\n }\n}\n\n/**\n * Update toolbar button active states.\n */\nexport function updateToolbarActiveStates(renderRoot: Element, state: ShellState): void {\n // Update single panel toggle button\n const panelToggle = renderRoot.querySelector('[data-panel-toggle]');\n if (panelToggle) {\n panelToggle.classList.toggle('active', state.isPanelOpen);\n panelToggle.setAttribute('aria-pressed', String(state.isPanelOpen));\n }\n}\n\n/**\n * Update tool panel open/close state.\n */\nexport function updatePanelState(renderRoot: Element, state: ShellState): void {\n const panel = renderRoot.querySelector('.tbw-tool-panel') as HTMLElement | null;\n if (!panel) return;\n\n panel.classList.toggle('open', state.isPanelOpen);\n\n // Clear inline width when closing (resize sets inline style that overrides CSS)\n if (!state.isPanelOpen) {\n panel.style.width = '';\n }\n}\n\n/** Base CSS anchor name used to feature-detect CSS anchor positioning support. */\nconst DROPDOWN_ANCHOR_NAME = '--tbw-tool-panel-anchor';\n\n/**\n * Monotonic counter for minting unique per-grid dropdown anchor names. A shared\n * constant name collides when two grids on the same page each open a dropdown,\n * pairing one grid's popover with the other grid's trigger (#375 follow-up).\n */\nlet dropdownAnchorSeq = 0;\n\n/** Whether the current environment supports CSS anchor positioning. */\nfunction supportsAnchorPositioning(): boolean {\n return (\n typeof CSS !== 'undefined' &&\n typeof CSS.supports === 'function' &&\n CSS.supports('anchor-name', DROPDOWN_ANCHOR_NAME)\n );\n}\n\n/**\n * Position the dropdown popover with fixed coordinates derived from the\n * anchor's bounding rect. Used only when CSS anchor positioning is\n * unsupported; otherwise placement is driven entirely by `shell.css`.\n */\nfunction positionDropdownFallback(panel: HTMLElement, anchorEl: HTMLElement, corner: boolean): void {\n const rect = anchorEl.getBoundingClientRect();\n const position = panel.getAttribute('data-position') ?? 'right';\n panel.style.position = 'fixed';\n // `corner` anchors to the top of the grid (top corner); otherwise drop below\n // the trigger button.\n panel.style.top = `${Math.round((corner ? rect.top : rect.bottom) + 4)}px`;\n if (position === 'left') {\n panel.style.left = `${Math.round(rect.left)}px`;\n panel.style.right = 'auto';\n } else {\n const viewportWidth = typeof window !== 'undefined' ? window.innerWidth : 0;\n panel.style.left = 'auto';\n panel.style.right = `${Math.round(viewportWidth - rect.right)}px`;\n }\n}\n\n/**\n * Show the tool panel as an anchored dropdown popover (`mode: 'dropdown'`).\n *\n * Reuses the existing `.tbw-tool-panel` aside (the full accordion) and shows it\n * in the top layer via the native Popover API. Placement is handled by CSS\n * anchor positioning when supported, with a JS bounding-rect fallback otherwise.\n *\n * @param anchorEl - Element the popover anchors to.\n * @param corner - When `true`, anchor to the top corner of `anchorEl` (grid\n * corner fallback); otherwise drop directly below it (toggle / custom button).\n */\nexport function showToolPanelDropdown(\n renderRoot: Element,\n state: ShellState,\n anchorEl: HTMLElement,\n corner: boolean,\n): void {\n const panel = renderRoot.querySelector('.tbw-tool-panel') as (HTMLElement & { showPopover?: () => void }) | null;\n if (!panel) return;\n\n state.dropdownAnchorEl = anchorEl;\n panel.dataset.anchor = corner ? 'corner' : 'below';\n\n // Show in the top layer. Guard for environments (older browsers / test DOM)\n // without the native Popover API.\n if (panel.hasAttribute('popover') && typeof panel.showPopover === 'function') {\n try {\n panel.showPopover();\n } catch {\n // Already open or not connected — ignore.\n }\n }\n\n if (supportsAnchorPositioning()) {\n // CSS drives placement; just wire up the anchor-name pairing. Mint a unique\n // name per show so multiple grids (or repeated opens) on one page never\n // collide on a shared anchor name — a collision positions this popover\n // against another grid's trigger instead of its own.\n const anchorName = `${DROPDOWN_ANCHOR_NAME}-${++dropdownAnchorSeq}`;\n state.dropdownAnchorName = anchorName;\n anchorEl.style.setProperty('anchor-name', anchorName);\n panel.style.setProperty('position-anchor', anchorName);\n panel.style.removeProperty('top');\n panel.style.removeProperty('left');\n panel.style.removeProperty('right');\n } else {\n positionDropdownFallback(panel, anchorEl, corner);\n }\n}\n\n/**\n * Hide the dropdown popover and clear all anchor wiring set by\n * {@link showToolPanelDropdown}.\n */\nexport function hideToolPanelDropdown(renderRoot: Element, state: ShellState): void {\n const panel = renderRoot.querySelector('.tbw-tool-panel') as (HTMLElement & { hidePopover?: () => void }) | null;\n if (panel) {\n if (panel.hasAttribute('popover') && typeof panel.hidePopover === 'function') {\n try {\n panel.hidePopover();\n } catch {\n // Not currently open — ignore.\n }\n }\n panel.style.removeProperty('position-anchor');\n panel.style.removeProperty('position');\n panel.style.removeProperty('top');\n panel.style.removeProperty('left');\n panel.style.removeProperty('right');\n delete panel.dataset.anchor;\n }\n if (state.dropdownAnchorEl) {\n state.dropdownAnchorEl.style.removeProperty('anchor-name');\n state.dropdownAnchorEl = null;\n }\n state.dropdownAnchorName = null;\n}\n\n/**\n * Prepare shell state for a full re-render.\n * Runs cleanup functions so content (toolbar buttons, panels, headers)\n * can be restored to their original containers and re-rendered into new DOM.\n */\nexport function prepareForRerender(state: ShellState): void {\n // Run cleanups for toolbar contents (moves elements back to original containers)\n for (const cleanup of state.toolbarContentCleanups.values()) {\n cleanup();\n }\n state.toolbarContentCleanups.clear();\n\n // Run cleanups for panel contents (old DOM will be destroyed)\n for (const cleanup of state.panelCleanups.values()) {\n cleanup();\n }\n state.panelCleanups.clear();\n\n // Run cleanups for header contents (old DOM will be destroyed)\n for (const cleanup of state.headerContentCleanups.values()) {\n cleanup();\n }\n state.headerContentCleanups.clear();\n\n // Allow light DOM content to be re-moved into new DOM\n state.lightDomContentMoved = false;\n}\n\n/**\n * Cleanup all shell state when grid disconnects.\n */\nexport function cleanupShellState(state: ShellState): void {\n // Clean up header content\n for (const cleanup of state.headerContentCleanups.values()) {\n cleanup();\n }\n state.headerContentCleanups.clear();\n\n // Clean up panel content\n for (const cleanup of state.panelCleanups.values()) {\n cleanup();\n }\n state.panelCleanups.clear();\n\n // Clean up toolbar contents\n for (const cleanup of state.toolbarContentCleanups.values()) {\n cleanup();\n }\n state.toolbarContentCleanups.clear();\n\n // Call onDestroy for all toolbar contents\n for (const content of state.toolbarContents.values()) {\n content.onDestroy?.();\n }\n\n // Invoke onClose for all open panels\n if (state.isPanelOpen) {\n for (const sectionId of state.expandedSections) {\n const panel = state.toolPanels.get(sectionId);\n panel?.onClose?.();\n }\n }\n\n // Reset panel state\n state.isPanelOpen = false;\n state.expandedSections.clear();\n\n // Clear registrations\n state.toolPanels.clear();\n state.headerContents.clear();\n state.toolbarContents.clear();\n state.lightDomHeaderContent = [];\n\n // Clear light DOM tracking sets (allow re-parsing)\n state.lightDomToolPanelIds.clear();\n state.adapterBoundToolPanelIds.clear();\n state.lightDomToolbarContentIds.clear();\n\n // Reset move tracking flag (allow re-initialization)\n state.lightDomContentMoved = false;\n}\n// #endregion\n\n// #region Grid HTML Templates\n/**\n * Core grid content HTML template.\n * Uses faux scrollbar pattern for smooth virtualized scrolling.\n */\nexport const GRID_CONTENT_HTML = `\n <div class=\"tbw-scroll-area\">\n <div class=\"rows-body-wrapper\">\n <div class=\"rows-body\" role=\"grid\">\n <div class=\"header\" role=\"rowgroup\">\n <div class=\"header-row\" role=\"row\" part=\"header-row\"></div>\n </div>\n <div class=\"rows-container\" role=\"presentation\">\n <div class=\"rows-viewport\" role=\"presentation\">\n <div class=\"rows\"></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"faux-vscroll\">\n <div class=\"faux-vscroll-spacer\"></div>\n </div>\n <div class=\"tbw-sr-only\" aria-live=\"polite\" aria-atomic=\"true\"></div>\n`;\n// #endregion\n\n// #region DOM Construction\nimport { GridClasses } from '../../core/constants';\nimport {\n buildGridDOM,\n buildShellBody,\n buildShellHeader,\n type ShellBodyOptions,\n type ShellHeaderOptions,\n} from '../../core/internal/dom-builder';\n\n/**\n * Build ShellHeaderOptions and ShellBodyOptions from shell config and runtime state.\n * Shared by buildGridDOMIntoElement and rebuildShellDOM to avoid duplication.\n */\nfunction buildShellOptions(\n shellConfig: ShellConfig,\n runtimeState: { isPanelOpen: boolean; expandedSections: Set<string> },\n icons?: { toolPanel?: IconValue; expand?: IconValue; collapse?: IconValue },\n): { headerOptions: ShellHeaderOptions; bodyOptions: ShellBodyOptions } {\n const toolPanelIcon = icons?.toolPanel !== undefined ? iconToString(icons.toolPanel) : undefined;\n const expandIcon = icons?.expand !== undefined ? iconToString(icons.expand) : undefined;\n const collapseIcon = icons?.collapse !== undefined ? iconToString(icons.collapse) : undefined;\n\n const allContents = shellConfig.header?.toolbarContents ?? [];\n const sortedContents = [...allContents].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));\n\n const allPanels = shellConfig.toolPanels ?? [];\n const sortedPanels = [...allPanels].sort((a, b) => (a.order ?? 100) - (b.order ?? 100));\n\n return {\n headerOptions: {\n title: shellConfig.header?.title ?? undefined,\n hasPanels: sortedPanels.length > 0,\n isPanelOpen: runtimeState.isPanelOpen,\n toolPanelIcon,\n showToggle: shellConfig.header?.toolPanelToggle !== false && shellConfig.toolPanel?.locked !== true,\n configButtons: sortedContents.map((c) => ({\n id: c.id,\n hasElement: false,\n hasRender: !!c.render,\n })),\n apiButtons: [],\n },\n bodyOptions: {\n position: shellConfig.toolPanel?.position ?? 'right',\n mode: shellConfig.toolPanel?.mode ?? 'overlay',\n isPanelOpen: runtimeState.isPanelOpen,\n expandIcon,\n collapseIcon,\n // Render an in-panel close (✕) button when the header bar is hidden, so\n // the panel can be dismissed without the (absent) built-in toggle. A\n // locked panel cannot be closed, so no button in that case. Dropdown mode\n // is excluded: the popover light-dismisses on Esc / click-outside, so a\n // ✕ would be redundant clutter.\n showCloseButton:\n shellConfig.header?.visible === false &&\n shellConfig.toolPanel?.locked !== true &&\n (shellConfig.toolPanel?.mode ?? 'overlay') !== 'dropdown',\n panels: sortedPanels.map((p) => ({\n id: p.id,\n title: p.title,\n icon: iconToString(p.icon),\n isExpanded: runtimeState.expandedSections.has(p.id),\n })),\n },\n };\n}\n\n/**\n * Build the complete grid DOM structure using direct DOM construction.\n * This is 2-3x faster than innerHTML for initial render.\n *\n * @param renderRoot - The element to render into (will be cleared)\n * @param shellConfig - Shell configuration\n * @param runtimeState - Runtime shell state\n * @param icons - Optional icons\n * @returns Whether shell is active (for post-render setup)\n */\nexport function buildGridDOMIntoElement(\n renderRoot: Element,\n shellConfig: ShellConfig | undefined,\n runtimeState: { isPanelOpen: boolean; expandedSections: Set<string> },\n icons?: { toolPanel?: IconValue; expand?: IconValue; collapse?: IconValue },\n): boolean {\n const hasShell = shouldRenderShellHeader(shellConfig);\n\n // Preserve all direct child nodes EXCEPT the grid's own `.tbw-grid-root`\n // (which we are about to rebuild). This keeps user/framework light-DOM\n // intact in its original order — including non-element nodes like comment\n // anchors that Angular's `@if`/`@for` and other structural directives use\n // as positional markers. Filtering by element-tag selectors would discard\n // those anchors and break re-renders of the conditional content.\n const preservedNodes: Node[] = [];\n for (let n: Node | null = renderRoot.firstChild; n; n = n.nextSibling) {\n if (n.nodeType === 1 && (n as Element).classList.contains(GridClasses.ROOT)) continue;\n preservedNodes.push(n);\n }\n\n // Clear existing content (this would delete light DOM nodes, so we preserved them first)\n renderRoot.replaceChildren();\n\n // Re-append preserved nodes in original order (hidden elements are used for config).\n // IMPORTANT: These are prepended before .tbw-grid-root, so `renderRoot.children[0]`\n // is NOT the grid root. Use `querySelector('.tbw-grid-root')` instead.\n for (const n of preservedNodes) {\n renderRoot.appendChild(n);\n }\n\n if (hasShell) {\n const { headerOptions, bodyOptions } = buildShellOptions(shellConfig!, runtimeState, icons);\n const shellHeader = shouldRenderHeaderBar(shellConfig) ? buildShellHeader(headerOptions) : undefined;\n const shellBody = buildShellBody(bodyOptions);\n\n const fragment = buildGridDOM({\n hasShell: true,\n shellHeader,\n shellBody,\n });\n renderRoot.appendChild(fragment);\n } else {\n const fragment = buildGridDOM({ hasShell: false });\n renderRoot.appendChild(fragment);\n }\n\n return hasShell;\n}\n\n/**\n * Surgically rebuild only the shell wrapper (header + tool panel) while\n * preserving the existing `.tbw-grid-root` element and its `.tbw-grid-content`\n * child with all descendants and event listeners intact.\n *\n * This avoids the full `replaceChildren()` nuke that `buildGridDOMIntoElement`\n * performs, so event listeners bound to `.tbw-grid-root` or its grid content\n * descendants (e.g. tooltip's delegated mouseover) remain intact.\n *\n * If no existing `.tbw-grid-root` is found (first render), falls back to\n * `buildGridDOMIntoElement` for a full rebuild.\n */\nexport function rebuildShellDOM(\n renderRoot: Element,\n shellConfig: ShellConfig | undefined,\n runtimeState: { isPanelOpen: boolean; expandedSections: Set<string> },\n icons?: { toolPanel?: IconValue; expand?: IconValue; collapse?: IconValue },\n): boolean {\n // Find the existing grid root and content to preserve\n const existingRoot = renderRoot.querySelector(`.${GridClasses.ROOT}`) as HTMLElement | null;\n const existingContent = existingRoot?.querySelector('.tbw-grid-content');\n\n // If there's no existing root, fall back to full rebuild (first render)\n if (!existingRoot || !existingContent) {\n return buildGridDOMIntoElement(renderRoot, shellConfig, runtimeState, icons);\n }\n\n // Detach grid content so it survives the child replacement\n existingContent.remove();\n\n const hasShell = shouldRenderShellHeader(shellConfig);\n\n // Clear the root's children (shell header, shell body, etc.) but keep the root element itself\n existingRoot.replaceChildren();\n\n if (hasShell) {\n existingRoot.className = `${GridClasses.ROOT} has-shell`;\n\n const { headerOptions, bodyOptions } = buildShellOptions(shellConfig!, runtimeState, icons);\n const shellHeader = shouldRenderHeaderBar(shellConfig) ? buildShellHeader(headerOptions) : undefined;\n const shellBody = buildShellBody(bodyOptions);\n\n // Replace the freshly cloned grid content with the preserved one\n const freshContent = shellBody.querySelector('.tbw-grid-content');\n if (freshContent) {\n freshContent.replaceWith(existingContent);\n }\n\n if (shellHeader) existingRoot.appendChild(shellHeader);\n existingRoot.appendChild(shellBody);\n } else {\n // No shell — place content directly in root\n existingRoot.className = GridClasses.ROOT;\n existingRoot.appendChild(existingContent);\n }\n\n return hasShell;\n}\n// #endregion\n","/**\n * Shell Controller - Tool panel and header content orchestration.\n *\n * Extracted from shell.ts to isolate the stateful controller logic\n * from the rendering/parsing infrastructure.\n *\n * The ShellController manages:\n * - Tool panel open/close/toggle state\n * - Accordion section expand/collapse\n * - Registration of tool panels, header content, and toolbar content\n */\n\nimport {\n HEADER_CONTENT_DUPLICATE,\n NO_TOOL_PANELS,\n TOOL_PANEL_DUPLICATE,\n TOOL_PANEL_NOT_FOUND,\n TOOLBAR_CONTENT_DUPLICATE,\n warnDiagnostic,\n} from '../../core/internal/diagnostics';\nimport type { InternalGrid } from '../../core/types';\nimport {\n hideToolPanelDropdown,\n renderHeaderContent,\n renderPanelContent,\n showToolPanelDropdown,\n updatePanelState,\n updateToolbarActiveStates,\n type ShellState,\n} from './shell';\nimport type {\n HeaderContentDefinition,\n OpenToolPanelOptions,\n ToolbarContentDefinition,\n ToolPanelDefinition,\n} from './types';\n\n// #region ShellController\n/**\n * Controller interface for managing shell/tool panel behavior.\n */\nexport interface ShellController {\n /** Whether the shell has been initialized */\n readonly isInitialized: boolean;\n /** Set the initialized state */\n setInitialized(value: boolean): void;\n /** Whether the tool panel is currently open */\n readonly isPanelOpen: boolean;\n /** Get IDs of expanded accordion sections */\n readonly expandedSections: string[];\n /**\n * Open the tool panel.\n * @param panelId - Optional ID of the section to expand on open. When provided,\n * takes precedence over `shell.toolPanel.defaultOpen`. If the panel is already\n * open with a different section expanded, switches to the requested section.\n * If the panel ID is not registered, a `TBW072` warning is emitted and the\n * call falls back to default behavior (auto-expand `defaultOpen` or first by order).\n * @param options - Optional open options. In `mode: 'dropdown'`, `options.anchor`\n * sets the element the popover anchors to (see {@link OpenToolPanelOptions}).\n */\n openToolPanel(panelId?: string, options?: OpenToolPanelOptions): void;\n /** Close the tool panel */\n closeToolPanel(): void;\n /**\n * Toggle the tool panel.\n * @param options - Optional open options forwarded to {@link openToolPanel}\n * when the panel is currently closed (e.g. `anchor` for dropdown mode).\n */\n toggleToolPanel(options?: OpenToolPanelOptions): void;\n /** Toggle an accordion section */\n toggleToolPanelSection(sectionId: string): void;\n /** Get registered tool panels */\n getToolPanels(): ToolPanelDefinition[];\n /** Register a tool panel */\n registerToolPanel(panel: ToolPanelDefinition): void;\n /** Unregister a tool panel */\n unregisterToolPanel(panelId: string): void;\n /** Get registered header contents */\n getHeaderContents(): HeaderContentDefinition[];\n /** Register header content */\n registerHeaderContent(content: HeaderContentDefinition): void;\n /** Unregister header content */\n unregisterHeaderContent(contentId: string): void;\n /** Get all registered toolbar contents */\n getToolbarContents(): ToolbarContentDefinition[];\n /** Register toolbar content */\n registerToolbarContent(content: ToolbarContentDefinition): void;\n /** Unregister toolbar content */\n unregisterToolbarContent(contentId: string): void;\n}\n\n/**\n * Create a ShellController instance.\n * The controller encapsulates all tool panel orchestration logic.\n */\nexport function createShellController(state: ShellState, grid: InternalGrid): ShellController {\n let initialized = false;\n\n /**\n * Resolve the element a dropdown popover should anchor to. Priority:\n * explicit `anchor` option → built-in toolbar toggle button → grid host\n * corner fallback.\n */\n function resolveDropdownAnchor(explicit?: HTMLElement): { el: HTMLElement; corner: boolean } {\n if (explicit) return { el: explicit, corner: false };\n const toggle = grid._renderRoot.querySelector('[data-panel-toggle]') as HTMLElement | null;\n if (toggle) return { el: toggle, corner: false };\n return { el: grid._hostElement, corner: true };\n }\n\n const controller: ShellController = {\n get isInitialized() {\n return initialized;\n },\n setInitialized(value: boolean) {\n initialized = value;\n },\n\n get isPanelOpen() {\n return state.isPanelOpen;\n },\n\n get expandedSections() {\n return [...state.expandedSections];\n },\n\n openToolPanel(panelId?: string, options?: OpenToolPanelOptions) {\n if (state.toolPanels.size === 0) {\n warnDiagnostic(NO_TOOL_PANELS, 'No tool panels registered', grid.id);\n return;\n }\n\n // Validate explicit panelId; on unknown id, warn and fall back to default behavior.\n let targetSectionId: string | undefined;\n if (panelId !== undefined) {\n if (state.toolPanels.has(panelId)) {\n targetSectionId = panelId;\n } else {\n warnDiagnostic(TOOL_PANEL_NOT_FOUND, `Tool panel \"${panelId}\" not found`, grid.id);\n }\n }\n\n // Fast path: panel already open and requested section already expanded → no-op.\n if (state.isPanelOpen && targetSectionId && state.expandedSections.has(targetSectionId)) {\n return;\n }\n\n // Panel already open but a different section is expanded → switch sections\n // (delegate to toggleToolPanelSection for accordion exclusivity + event emission).\n if (state.isPanelOpen && targetSectionId && !state.expandedSections.has(targetSectionId)) {\n controller.toggleToolPanelSection(targetSectionId);\n return;\n }\n\n if (state.isPanelOpen) return;\n\n state.isPanelOpen = true;\n\n // Decide initial expanded section:\n // 1. Explicit panelId argument (already validated above).\n // 2. shell.toolPanel.defaultOpen if it matches a registered panel.\n // 3. First panel by `order`.\n if (state.expandedSections.size === 0 && state.toolPanels.size > 0) {\n if (targetSectionId) {\n state.expandedSections.add(targetSectionId);\n } else {\n const defaultOpenId = grid.effectiveConfig?.shell?.toolPanel?.defaultOpen;\n if (defaultOpenId && state.toolPanels.has(defaultOpenId)) {\n state.expandedSections.add(defaultOpenId);\n } else {\n const sortedPanels = [...state.toolPanels.values()].sort((a, b) => (a.order ?? 100) - (b.order ?? 100));\n const firstPanel = sortedPanels[0];\n if (firstPanel) {\n state.expandedSections.add(firstPanel.id);\n }\n }\n }\n } else if (targetSectionId && !state.expandedSections.has(targetSectionId)) {\n // Some other section was already pre-expanded (e.g. via config); switch to requested.\n for (const id of [...state.expandedSections]) {\n state.expandedSections.delete(id);\n }\n state.expandedSections.add(targetSectionId);\n }\n\n // Update UI\n const shadow = grid._renderRoot;\n updateToolbarActiveStates(shadow, state);\n updatePanelState(shadow, state);\n\n // Render accordion sections\n renderPanelContent(shadow, state, grid._accordionIcons);\n\n // In dropdown mode, show the panel as an anchored popover in the top layer.\n if (grid.effectiveConfig?.shell?.toolPanel?.mode === 'dropdown') {\n const { el, corner } = resolveDropdownAnchor(options?.anchor);\n showToolPanelDropdown(shadow, state, el, corner);\n }\n\n // Emit event\n grid._emit('tool-panel-open', { sections: controller.expandedSections });\n },\n\n closeToolPanel() {\n if (!state.isPanelOpen) return;\n // When the tool panel is locked open, programmatic and user close\n // requests become no-ops. Accordion sections can still toggle.\n if (grid.effectiveConfig?.shell?.toolPanel?.locked) return;\n\n // Clean up all panel content\n for (const cleanup of state.panelCleanups.values()) {\n cleanup();\n }\n state.panelCleanups.clear();\n\n // Call onClose for all panels\n for (const panel of state.toolPanels.values()) {\n panel.onClose?.();\n }\n\n state.isPanelOpen = false;\n\n // Update UI\n const shadow = grid._renderRoot;\n updateToolbarActiveStates(shadow, state);\n updatePanelState(shadow, state);\n\n // In dropdown mode, hide the popover and clear anchor wiring.\n if (grid.effectiveConfig?.shell?.toolPanel?.mode === 'dropdown') {\n hideToolPanelDropdown(shadow, state);\n }\n\n // Emit event\n grid._emit('tool-panel-close', {});\n },\n\n toggleToolPanel(options?: OpenToolPanelOptions) {\n if (state.isPanelOpen) {\n controller.closeToolPanel();\n } else {\n controller.openToolPanel(undefined, options);\n }\n },\n\n toggleToolPanelSection(sectionId: string) {\n const panel = state.toolPanels.get(sectionId);\n if (!panel) {\n warnDiagnostic(TOOL_PANEL_NOT_FOUND, `Tool panel section \"${sectionId}\" not found`, grid.id);\n return;\n }\n\n // Don't allow toggling when there's only one panel (it should stay expanded)\n if (state.toolPanels.size === 1) {\n return;\n }\n\n const shadow = grid._renderRoot;\n const isExpanded = state.expandedSections.has(sectionId);\n\n if (isExpanded) {\n // Collapsing current section\n const cleanup = state.panelCleanups.get(sectionId);\n if (cleanup) {\n cleanup();\n state.panelCleanups.delete(sectionId);\n }\n panel.onClose?.();\n state.expandedSections.delete(sectionId);\n updateAccordionSectionState(shadow, sectionId, false);\n } else {\n // Expanding - first collapse all others (exclusive accordion)\n for (const [otherId, otherPanel] of state.toolPanels) {\n if (otherId !== sectionId && state.expandedSections.has(otherId)) {\n const cleanup = state.panelCleanups.get(otherId);\n if (cleanup) {\n cleanup();\n state.panelCleanups.delete(otherId);\n }\n otherPanel.onClose?.();\n state.expandedSections.delete(otherId);\n updateAccordionSectionState(shadow, otherId, false);\n // Clear content of collapsed section\n const contentEl = shadow.querySelector(`[data-section=\"${otherId}\"] .tbw-accordion-content`);\n if (contentEl) contentEl.innerHTML = '';\n }\n }\n // Now expand the target section\n state.expandedSections.add(sectionId);\n updateAccordionSectionState(shadow, sectionId, true);\n renderAccordionSectionContent(shadow, state, sectionId);\n }\n\n // Emit event\n grid._emit('tool-panel-section-toggle', { id: sectionId, expanded: !isExpanded });\n },\n\n getToolPanels() {\n return [...state.toolPanels.values()];\n },\n\n registerToolPanel(panel: ToolPanelDefinition) {\n if (state.toolPanels.has(panel.id)) {\n warnDiagnostic(TOOL_PANEL_DUPLICATE, `Tool panel \"${panel.id}\" already registered`, grid.id);\n return;\n }\n state.toolPanels.set(panel.id, panel);\n\n if (initialized) {\n grid.refreshShellHeader?.();\n }\n },\n\n unregisterToolPanel(panelId: string) {\n // Close panel if open and this section is expanded\n if (state.expandedSections.has(panelId)) {\n const cleanup = state.panelCleanups.get(panelId);\n if (cleanup) {\n cleanup();\n state.panelCleanups.delete(panelId);\n }\n state.expandedSections.delete(panelId);\n }\n\n state.toolPanels.delete(panelId);\n\n if (initialized) {\n grid.refreshShellHeader?.();\n }\n },\n\n getHeaderContents() {\n return [...state.headerContents.values()];\n },\n\n registerHeaderContent(content: HeaderContentDefinition) {\n if (state.headerContents.has(content.id)) {\n warnDiagnostic(HEADER_CONTENT_DUPLICATE, `Header content \"${content.id}\" already registered`, grid.id);\n return;\n }\n state.headerContents.set(content.id, content);\n\n if (initialized) {\n renderHeaderContent(grid._renderRoot, state);\n }\n },\n\n unregisterHeaderContent(contentId: string) {\n // Clean up\n const cleanup = state.headerContentCleanups.get(contentId);\n if (cleanup) {\n cleanup();\n state.headerContentCleanups.delete(contentId);\n }\n\n // Call onDestroy\n const content = state.headerContents.get(contentId);\n content?.onDestroy?.();\n\n state.headerContents.delete(contentId);\n\n // Remove DOM element\n const el = grid._renderRoot.querySelector(`[data-header-content=\"${contentId}\"]`);\n el?.remove();\n },\n\n getToolbarContents() {\n return [...state.toolbarContents.values()].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));\n },\n\n registerToolbarContent(content: ToolbarContentDefinition) {\n if (state.toolbarContents.has(content.id)) {\n warnDiagnostic(TOOLBAR_CONTENT_DUPLICATE, `Toolbar content \"${content.id}\" already registered`, grid.id);\n return;\n }\n state.toolbarContents.set(content.id, content);\n\n if (initialized) {\n grid.refreshShellHeader?.();\n }\n },\n\n unregisterToolbarContent(contentId: string) {\n // Clean up\n const cleanup = state.toolbarContentCleanups.get(contentId);\n if (cleanup) {\n cleanup();\n state.toolbarContentCleanups.delete(contentId);\n }\n\n // Call onDestroy if defined\n const content = state.toolbarContents.get(contentId);\n if (content?.onDestroy) {\n content.onDestroy();\n }\n\n state.toolbarContents.delete(contentId);\n\n if (initialized) {\n grid.refreshShellHeader?.();\n }\n },\n };\n\n return controller;\n}\n\n/**\n * Update accordion section visual state.\n */\nfunction updateAccordionSectionState(renderRoot: Element, sectionId: string, expanded: boolean): void {\n const section = renderRoot.querySelector(`[data-section=\"${sectionId}\"]`);\n if (section) {\n section.classList.toggle('expanded', expanded);\n }\n}\n\n/**\n * Render content for a single accordion section.\n */\nfunction renderAccordionSectionContent(renderRoot: Element, state: ShellState, sectionId: string): void {\n const panel = state.toolPanels.get(sectionId);\n if (!panel?.render) return;\n\n const contentEl = renderRoot.querySelector(`[data-section=\"${sectionId}\"] .tbw-accordion-content`);\n if (!contentEl) return;\n\n const cleanup = panel.render(contentEl as HTMLElement);\n if (cleanup) {\n state.panelCleanups.set(sectionId, cleanup);\n }\n}\n// #endregion\n","/**\n * Shell Plugin (Class-based)\n *\n * Owns the grid shell: header bar, toolbar content, and tool panels.\n *\n * **Extraction #370 — Phase 1b (ownership inversion):** this plugin now owns\n * the shell **state** for the grid's lifetime (Approach A: state created once,\n * survives plugin re-inits, torn down on disconnect). Light-DOM parsing,\n * per-render work, and DOM construction move here in the following increments;\n * until each lands, core still drives that part via the resolved plugin\n * instance.\n *\n * @module Plugins/Shell\n */\n\nimport { BaseGridPlugin, type GridElement } from '../../core/plugin/base-plugin';\nimport type { GridConfig, InternalGrid } from '../../core/types';\nimport {\n cleanupShellState,\n createShellState,\n parseLightDomShell,\n parseLightDomToolButtons,\n parseLightDomToolPanels,\n prepareForRerender,\n rebuildShellDOM,\n renderCustomToolbarContents,\n renderHeaderContent,\n renderPanelContent,\n setupClickOutsideDismiss,\n setupEscapeDismiss,\n setupShellEventListeners,\n setupToolPanelResize,\n shouldRenderShellHeader,\n updatePanelState,\n updateToolbarActiveStates,\n type ShellState,\n} from './shell';\nimport { createShellController, type ShellController } from './shell-controller';\nimport styles from './shell.css?inline';\nimport './types';\nimport type {\n HeaderContentDefinition,\n OpenToolPanelOptions,\n ShellConfig,\n ToolbarContentDefinition,\n ToolPanelDefinition,\n} from './types';\n\n/**\n * Shell plugin for `<tbw-grid>`.\n *\n * @see {@link ShellConfig} for all configuration options.\n *\n * @internal Extends BaseGridPlugin.\n * @since 2.x (extraction #370)\n */\nexport class ShellPlugin extends BaseGridPlugin<ShellConfig> {\n /** @internal */\n readonly name = 'shell';\n /** @internal */\n override readonly styles = styles;\n\n // Extraction #370 — Approach A: the plugin owns the shell state for the\n // grid's lifetime. State is created once (via `ensureState`, lazily on first\n // access) and survives plugin re-inits (idempotent attach / non-destructive\n // detach); it is torn down only on grid disconnect via `disposeShellState()`.\n #state?: ShellState;\n #controller?: ShellController;\n\n // Document-level listeners the plugin owns for the shell's interactive\n // affordances. Each `#setupListeners` call cleans the previous registration\n // before re-adding (idempotent across re-wraps); torn down on disconnect.\n #resizeCleanup?: () => void;\n #clickOutsideCleanup?: () => void;\n #escapeDismissCleanup?: () => void;\n\n // The light-DOM shell-change handler (re-parses <tbw-grid-header> /\n // <tbw-grid-tool-buttons> / <tbw-grid-tool-panel> when the framework projects\n // them asynchronously). Registered in `attach` via the generic\n // `_registerLightDomHandler` seam; unregistered on disconnect.\n #shellChangeHandler?: () => void;\n\n /** @internal Whether shell state has been created yet. */\n get hasState(): boolean {\n return this.#state !== undefined;\n }\n\n /**\n * @internal Create the canonical shell state + controller (idempotent).\n * The plugin owns shell-state construction; core only supplies the grid\n * reference (the controller binds to it for UI updates). No-op once state\n * exists, so it is safe to call on every resolve — including before the\n * plugin formally attaches, which keeps pre-connect API calls working.\n */\n ensureState(grid: InternalGrid): void {\n if (this.#state) return;\n const state = createShellState();\n this.#state = state;\n this.#controller = createShellController(state, grid);\n }\n\n /** @internal Canonical shell state. */\n get shellState(): ShellState {\n return this.#state as ShellState;\n }\n\n /** @internal Shell controller. */\n get shellController(): ShellController {\n return this.#controller as ShellController;\n }\n\n // #region Public Shell API\n // The shell plugin's public, programmatic API. Obtain the instance via\n // `grid.getPluginByName('shell')`. These mirror the (now `@deprecated`)\n // `<tbw-grid>` delegate methods, which forward here for the v2.x window and\n // are removed in v3 (extraction #370). Each guards on `hasState` so a call\n // before the shell has initialized is a safe no-op.\n\n /** Whether the tool panel sidebar is currently open. */\n get isToolPanelOpen(): boolean {\n return this.hasState ? this.shellController.isPanelOpen : false;\n }\n\n /** IDs of the currently expanded accordion sections in the tool panel. */\n get expandedToolPanelSections(): string[] {\n return this.hasState ? this.shellController.expandedSections : [];\n }\n\n /**\n * Open the tool panel sidebar.\n * @param panelId - Optional section to expand on open (see {@link ShellController.openToolPanel}).\n * @param options - Optional open options. In `mode: 'dropdown'`, `options.anchor`\n * sets the element the popover anchors to (see {@link OpenToolPanelOptions}).\n */\n openToolPanel(panelId?: string, options?: OpenToolPanelOptions): void {\n this.shellController?.openToolPanel(panelId, options);\n }\n\n /** Close the tool panel sidebar. */\n closeToolPanel(): void {\n this.shellController?.closeToolPanel();\n }\n\n /**\n * Toggle the tool panel sidebar open or closed.\n * @param options - Optional open options forwarded when opening (e.g. `anchor`\n * for dropdown mode; see {@link OpenToolPanelOptions}).\n */\n toggleToolPanel(options?: OpenToolPanelOptions): void {\n this.shellController?.toggleToolPanel(options);\n }\n\n /** Toggle an accordion section expanded or collapsed within the tool panel. */\n toggleToolPanelSection(sectionId: string): void {\n this.shellController?.toggleToolPanelSection(sectionId);\n }\n\n /** Get all registered tool panel definitions. */\n getToolPanels(): ToolPanelDefinition[] {\n return this.hasState ? this.shellController.getToolPanels() : [];\n }\n\n /** Register a custom tool panel section. */\n registerToolPanel(panel: ToolPanelDefinition): void {\n if (!this.hasState) return;\n this.shellState.apiToolPanelIds.add(panel.id);\n this.shellController.registerToolPanel(panel);\n }\n\n /** Unregister a custom tool panel section. */\n unregisterToolPanel(panelId: string): void {\n if (!this.hasState) return;\n this.shellState.apiToolPanelIds.delete(panelId);\n this.shellController.unregisterToolPanel(panelId);\n }\n\n /** Get all registered header content definitions. */\n getHeaderContents(): HeaderContentDefinition[] {\n return this.hasState ? this.shellController.getHeaderContents() : [];\n }\n\n /** Register custom header content (rendered in the shell header bar). */\n registerHeaderContent(content: HeaderContentDefinition): void {\n if (!this.hasState) return;\n this.shellState.apiHeaderContentIds.add(content.id);\n this.shellController.registerHeaderContent(content);\n }\n\n /** Unregister custom header content. */\n unregisterHeaderContent(contentId: string): void {\n if (!this.hasState) return;\n this.shellState.apiHeaderContentIds.delete(contentId);\n this.shellController.unregisterHeaderContent(contentId);\n }\n\n /** Get all registered toolbar content definitions. */\n getToolbarContents(): ToolbarContentDefinition[] {\n return this.hasState ? this.shellController.getToolbarContents() : [];\n }\n\n /** Register custom toolbar content (rendered in the shell toolbar). */\n registerToolbarContent(content: ToolbarContentDefinition): void {\n this.shellController?.registerToolbarContent(content);\n }\n\n /** Unregister custom toolbar content. */\n unregisterToolbarContent(contentId: string): void {\n this.shellController?.unregisterToolbarContent(contentId);\n }\n // #endregion\n\n /** @internal */\n override attach(grid: GridElement): void {\n super.attach(grid);\n // Register the light-DOM shell handlers via the generic seam. Core holds no\n // shell tag knowledge (extraction #370). `registerLightDomHandler` is keyed\n // by tag name, so re-attach simply overwrites — idempotent. Torn down on\n // grid disconnect in `disposeShellState` (Approach A: survives re-inits).\n const handler = (): void => this.#handleLightDomShellChange(grid);\n this.#shellChangeHandler = handler;\n grid._registerLightDomHandler('tbw-grid-header', handler);\n grid._registerLightDomHandler('tbw-grid-tool-buttons', handler);\n grid._registerLightDomHandler('tbw-grid-tool-panel', handler);\n }\n\n /** @internal Parse all light-DOM shell elements into the plugin's state. */\n #parseLightDom(grid: GridElement): void {\n const host = grid._hostElement;\n parseLightDomShell(host, this.shellState);\n parseLightDomToolButtons(host, this.shellState);\n parseLightDomToolPanels(host, this.shellState, grid._getToolPanelRendererFactory());\n }\n\n /**\n * @internal React to a light-DOM shell element being added/removed/mutated\n * (e.g. a framework projecting `<tbw-grid-header title>` asynchronously).\n * Re-parses, and if a title or tool-buttons container newly appeared, re-merges\n * the config (so `processConfig` folds it in) and re-wraps the shell header.\n */\n #handleLightDomShellChange(grid: GridElement): void {\n if (!this.hasState) return;\n const state = this.shellState;\n const hadTitle = state.lightDomTitle;\n const hadToolButtons = state.hasToolButtonsContainer;\n this.#parseLightDom(grid);\n const hasTitle = state.lightDomTitle;\n const hasToolButtons = state.hasToolButtonsContainer;\n\n if ((hasTitle && !hadTitle) || (hasToolButtons && !hadToolButtons)) {\n grid._requestConfigMerge();\n // Re-wrap the shell header to reflect the new title / tool buttons.\n // `afterStructuralRender` rebuilds the chrome AND re-renders all shell\n // contents (header content, toolbar slots, panel), so no manual render is\n // needed here.\n if (grid._renderRoot.querySelector('.tbw-shell-header')) {\n prepareForRerender(state);\n this.afterStructuralRender();\n }\n }\n }\n\n // #region Core transitional bridge (extraction #370, Task 1b — removed at v3)\n // Thin instance methods that let core orchestrate shell light-DOM parsing and\n // re-render WITHOUT a static import of the shell pure functions (the old\n // `TEMP-SHELL-IMPORT-1b` module import). Core invokes them through\n // `#resolveShellPlugin()`, the same transitional bridge used by `#shellState`\n // and the deprecated delegate shims. Deleted once core's merge/render\n // orchestration is fully inverted into hooks (and with the rest at v3).\n\n /** @internal Parse `<tbw-grid-header>` + `<tbw-grid-tool-buttons>` into shell state. */\n _parseLightDomHeader(grid: GridElement): void {\n const host = grid._hostElement;\n parseLightDomShell(host, this.shellState);\n parseLightDomToolButtons(host, this.shellState);\n }\n\n /** @internal Parse `<tbw-grid-tool-panel>` elements into shell state. */\n _parseLightDomToolPanels(grid: GridElement): void {\n parseLightDomToolPanels(grid._hostElement, this.shellState, grid._getToolPanelRendererFactory());\n }\n\n /** @internal Parse all light-DOM shell elements (header + tool buttons + panels). */\n _parseLightDomAll(grid: GridElement): void {\n this._parseLightDomHeader(grid);\n this._parseLightDomToolPanels(grid);\n }\n\n /** @internal Move toolbar buttons back to their original container ahead of a re-render. */\n _prepareForRerender(): void {\n if (!this.hasState) return;\n prepareForRerender(this.shellState);\n }\n\n /** @internal Re-render any open tool-panel sections cleared during plugin re-init (idempotent). */\n _renderOpenPanelContent(grid: GridElement): void {\n if (!this.hasState) return;\n renderPanelContent(grid._renderRoot, this.shellState);\n }\n\n /** @internal Whether the given shell config should render a shell header. */\n _shouldRenderHeader(shellConfig: ShellConfig | undefined): boolean {\n return shouldRenderShellHeader(shellConfig);\n }\n // #endregion\n\n /**\n * @internal Fold the plugin's shell state into the effective config.\n *\n * Called by core's config merge ({@link ConfigManager.merge}) via the neutral\n * `processConfig` plugin hook \\u2014 core holds no shell-config knowledge. Mutates\n * `config.shell` in place from light-DOM-parsed + API-registered shell state\n * (title, header content, tool buttons, tool panels, header/toolbar contents).\n *\n * Extraction #370, Task 1b \\u2014 config ownership inversion: replaces core's old\n * `ConfigManager.#mergeShellConfig`.\n */\n override processConfig(config: GridConfig): void {\n if (!this.hasState) return;\n const state = this.shellState;\n\n // Constructor-supplied shell config is the base layer: the canonical\n // `features: { shell: ... }` opt-in passes the config value here (and an\n // explicit `plugins: [new ShellPlugin(cfg)]` does too). An explicit\n // top-level `gridConfig.shell` overrides it; parsed light-DOM / API state\n // folds on top below. Empty for the auto-registered instance\n // (`new ShellPlugin()`), so existing default-on behavior is unchanged.\n const ctor = this.resolvedConfig;\n\n // Clone shell hierarchy to avoid mutating the original gridConfig.\n config.shell = { ...ctor, ...config.shell };\n config.shell.header = { ...ctor.header, ...config.shell.header };\n\n // Light DOM title (does not override an explicit config title).\n if (state.lightDomTitle && !config.shell.header.title) {\n config.shell.header.title = state.lightDomTitle;\n }\n\n // Light DOM header content elements.\n if (state.lightDomHeaderContent?.length > 0) {\n config.shell.header.lightDomContent = state.lightDomHeaderContent;\n }\n\n // Tool-buttons container presence.\n if (state.hasToolButtonsContainer) {\n config.shell.header.hasToolButtonsContainer = true;\n }\n\n // Tool panels (from plugins + API + Light DOM), sorted by order.\n if (state.toolPanels.size > 0) {\n const panels = Array.from(state.toolPanels.values());\n panels.sort((a, b) => (a.order ?? 100) - (b.order ?? 100));\n config.shell.toolPanels = panels;\n }\n\n // Header contents (from plugins + API), sorted by order.\n if (state.headerContents.size > 0) {\n const contents = Array.from(state.headerContents.values());\n contents.sort((a, b) => (a.order ?? 100) - (b.order ?? 100));\n config.shell.headerContents = contents;\n }\n\n // Toolbar contents: merge ORIGINAL config contents (from the source\n // gridConfig, not a previous merge) with API-registered contents so stale\n // API entries never accumulate. Config takes precedence by id.\n const apiContents = Array.from(state.toolbarContents.values());\n const originalConfigContents = this.grid?._sourceConfig?.shell?.header?.toolbarContents ?? [];\n const configIds = new Set(originalConfigContents.map((c) => c.id));\n const mergedContents = [...originalConfigContents];\n for (const content of apiContents) {\n if (!configIds.has(content.id)) {\n mergedContents.push(content);\n }\n }\n mergedContents.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));\n config.shell.header.toolbarContents = mergedContents;\n }\n\n /**\n * @internal Wrap the freshly built (bare) grid DOM in shell chrome.\n *\n * Fired synchronously by core at the end of every structural (re)build, before\n * paint. Core builds a BARE grid; this hook relocates the existing\n * `.tbw-grid-content` node into shell chrome (header bar, toolbar, tool panel)\n * via {@link rebuildShellDOM}, which preserves the content subtree (and all its\n * listeners / cached refs) intact. No-op when no shell is configured.\n *\n * Extraction #370, Task 1b — DOM ownership inversion: the shell DOM is now\n * constructed by the plugin, not core.\n */\n override afterStructuralRender(): void {\n const grid = this.grid;\n if (!grid || !this.hasState) return;\n const state = this.shellState;\n const hasShell = rebuildShellDOM(\n grid._renderRoot,\n grid.effectiveConfig?.shell,\n { isPanelOpen: state.isPanelOpen, expandedSections: state.expandedSections },\n grid.effectiveConfig?.icons,\n );\n if (hasShell) {\n this.#setupListeners(grid);\n this.shellController.setInitialized(true);\n this.#renderShellContents(grid);\n }\n }\n\n /**\n * @internal Render all shell contents into the freshly built shell chrome.\n *\n * Runs from {@link afterStructuralRender} after the chrome is wrapped and\n * listeners are attached. Renders plugin header content + custom toolbar\n * slots, applies the configured default-open / legacy open-on-load behavior,\n * and restores an already-open panel's content. All render functions operate\n * on the shell DOM (queried from `_renderRoot`), not on the grid-body refs,\n * so they are safe to run before `#afterConnect` caches those.\n *\n * Extraction #370, Task 1b (Step 1b.1.3) — replaces core's per-render shell\n * content block in `#afterConnect` / `#afterShellRefresh`.\n */\n #renderShellContents(grid: GridElement): void {\n const state = this.shellState;\n const renderRoot = grid._renderRoot;\n const shellConfig = grid.effectiveConfig?.shell;\n\n // Render plugin header content + custom toolbar contents (render modes).\n renderHeaderContent(renderRoot, state);\n renderCustomToolbarContents(renderRoot, shellConfig, state);\n\n // Pre-expand the configured default section.\n const toolPanelCfg = shellConfig?.toolPanel;\n const defaultOpen = toolPanelCfg?.defaultOpen;\n if (defaultOpen && state.toolPanels.has(defaultOpen)) {\n state.expandedSections.add(defaultOpen);\n }\n\n // Decide whether the sidebar should be open on load.\n // NOTE: In v2.x `defaultOpen` alone also opens the sidebar (legacy\n // behavior). v3.0.0 will drop that — see issue #259 and the\n // @deprecated note on ToolPanelConfig.defaultOpen.\n // Search marker: TOOLPANEL-OPEN-LEGACY-259\n const shouldOpenOnLoad =\n toolPanelCfg?.locked === true ||\n toolPanelCfg?.initialState === 'open' ||\n // Legacy v2 path — remove in v3.0.0 (#259)\n (toolPanelCfg?.initialState === undefined && defaultOpen !== undefined && state.toolPanels.has(defaultOpen));\n if (!state.isPanelOpen && state.toolPanels.size > 0 && shouldOpenOnLoad) {\n this.openToolPanel();\n }\n\n // Restore panel content if the panel was already open (e.g. after a position\n // change re-render or a plugin re-init that triggered a shell refresh).\n if (state.isPanelOpen) {\n updatePanelState(renderRoot, state);\n renderPanelContent(renderRoot, state, {\n expand: grid.effectiveConfig?.icons?.expand,\n collapse: grid.effectiveConfig?.icons?.collapse,\n });\n updateToolbarActiveStates(renderRoot, state);\n }\n }\n\n /**\n * @internal Attach shell event + dismiss/resize listeners.\n *\n * Cleans any prior document-level registrations before re-adding, so it is\n * safe to call on every re-wrap (initial render, shell refresh, plugin\n * re-init) without leaking or double-attaching listeners.\n */\n #setupListeners(grid: GridElement): void {\n const renderRoot = grid._renderRoot;\n const shellConfig = grid.effectiveConfig?.shell;\n const state = this.shellState;\n\n setupShellEventListeners(renderRoot, shellConfig, state, {\n onPanelToggle: () => this.toggleToolPanel(),\n onSectionToggle: (sectionId: string) => this.toggleToolPanelSection(sectionId),\n onPanelClose: () => this.closeToolPanel(),\n });\n\n // Tool panel resize handle — persists the new width as a CSS variable on\n // the host element.\n this.#resizeCleanup?.();\n this.#resizeCleanup = setupToolPanelResize(renderRoot, shellConfig, (width: number) => {\n grid._hostElement.style.setProperty('--tbw-tool-panel-width', `${width}px`);\n });\n\n // Click-outside dismiss for an open overlay tool panel.\n this.#clickOutsideCleanup?.();\n this.#clickOutsideCleanup = setupClickOutsideDismiss(grid._hostElement, shellConfig, state, () =>\n this.closeToolPanel(),\n );\n\n // Escape-to-close dismiss for overlay tool panels.\n this.#escapeDismissCleanup?.();\n this.#escapeDismissCleanup = setupEscapeDismiss(shellConfig, state, () => this.closeToolPanel());\n }\n\n /** @internal Remove the document-level shell listeners. */\n #teardownListeners(): void {\n this.#resizeCleanup?.();\n this.#resizeCleanup = undefined;\n this.#clickOutsideCleanup?.();\n this.#clickOutsideCleanup = undefined;\n this.#escapeDismissCleanup?.();\n this.#escapeDismissCleanup = undefined;\n }\n\n /** @internal */\n override detach(): void {\n // Non-destructive detach (Approach A): shell state and document listeners\n // survive plugin re-inits. `#setupListeners` cleans-before-add on the next\n // re-wrap, so there is nothing to tear down here. Real teardown happens in\n // `disposeShellState()` on grid disconnect.\n super.detach();\n }\n\n /**\n * @internal Tear down shell state on grid disconnect (Approach A).\n * Idempotent and harmless when no state exists.\n */\n disposeShellState(): void {\n this.#teardownListeners();\n // Unregister the light-DOM shell handlers (registered in `attach`). `detach`\n // does not clear `this.grid`, so the reference is still valid here.\n const grid = this.grid as GridElement | undefined;\n if (grid && this.#shellChangeHandler) {\n grid._unregisterLightDomHandler('tbw-grid-header');\n grid._unregisterLightDomHandler('tbw-grid-tool-buttons');\n grid._unregisterLightDomHandler('tbw-grid-tool-panel');\n this.#shellChangeHandler = undefined;\n }\n if (this.#state) {\n cleanupShellState(this.#state);\n this.#controller?.setInitialized(false);\n }\n }\n}\n"],"names":["iconToString","icon","sanitizeHTML","outerHTML","shouldRenderShellHeader","config","header","title","toolbarContents","length","toolPanels","headerContents","lightDomContent","hasToolButtonsContainer","shouldRenderHeaderBar","visible","parseLightDomShell","host","state","headerEl","querySelector","lightDomTitle","getAttribute","querySelectorAll","lightDomHeaderContent","Array","from","style","display","parseLightDomToolButtons","rendererFactory","toolButtonsContainer","id","lightDomToolbarContentIds","has","contentDef","order","render","target","firstChild","appendChild","set","add","parseLightDomToolPanels","forEach","element","panelEl","warnDiagnostic","TOOL_PANEL_MISSING_ATTR","tooltip","parseInt","adapterRenderer","content","innerHTML","trim","container","wrapper","document","createElement","remove","existingPanel","get","lightDomToolPanelIds","TOOL_PANEL_DUPLICATE","firstAdapterAttach","adapterBoundToolPanelIds","attrsChanged","cleanup","panelCleanups","delete","panel","isInGrid","node","gridElement","contains","root","getRootNode","ShadowRoot","renderHeaderContent","renderRoot","hasLightDomContent","lightDomContentMoved","hasPluginContent","size","contentArea","el","sortedContents","values","sort","a","b","existingCleanup","headerContentCleanups","setAttribute","renderPanelContent","_icons","isPanelOpen","panelId","isExpanded","expandedSections","section","classList","toggle","String","children","updateToolbarActiveStates","panelToggle","updatePanelState","width","DROPDOWN_ANCHOR_NAME","dropdownAnchorSeq","showToolPanelDropdown","anchorEl","corner","dropdownAnchorEl","dataset","anchor","hasAttribute","showPopover","CSS","supports","anchorName","dropdownAnchorName","setProperty","removeProperty","rect","getBoundingClientRect","position","top","Math","round","bottom","left","right","viewportWidth","window","innerWidth","positionDropdownFallback","prepareForRerender","toolbarContentCleanups","clear","buildShellOptions","shellConfig","runtimeState","icons","toolPanelIcon","toolPanel","expandIcon","expand","collapseIcon","collapse","sortedPanels","headerOptions","hasPanels","showToggle","toolPanelToggle","locked","configButtons","map","c","hasElement","hasRender","apiButtons","bodyOptions","mode","showCloseButton","panels","p","rebuildShellDOM","existingRoot","GridClasses","ROOT","existingContent","hasShell","preservedNodes","n","nextSibling","nodeType","push","replaceChildren","shellHeader","buildShellHeader","shellBody","buildShellBody","fragment","buildGridDOM","buildGridDOMIntoElement","className","freshContent","replaceWith","createShellController","grid","initialized","controller","isInitialized","setInitialized","value","openToolPanel","options","NO_TOOL_PANELS","targetSectionId","TOOL_PANEL_NOT_FOUND","toggleToolPanelSection","defaultOpenId","effectiveConfig","shell","defaultOpen","firstPanel","shadow","_renderRoot","_accordionIcons","explicit","_hostElement","resolveDropdownAnchor","_emit","sections","closeToolPanel","onClose","hidePopover","hideToolPanelDropdown","toggleToolPanel","sectionId","updateAccordionSectionState","otherId","otherPanel","contentEl","renderAccordionSectionContent","expanded","getToolPanels","registerToolPanel","refreshShellHeader","unregisterToolPanel","getHeaderContents","registerHeaderContent","HEADER_CONTENT_DUPLICATE","unregisterHeaderContent","contentId","onDestroy","getToolbarContents","registerToolbarContent","TOOLBAR_CONTENT_DUPLICATE","unregisterToolbarContent","ShellPlugin","BaseGridPlugin","name","styles","resizeCleanup","clickOutsideCleanup","escapeDismissCleanup","shellChangeHandler","hasState","this","ensureState","Map","Set","apiToolPanelIds","apiHeaderContentIds","shellState","shellController","isToolPanelOpen","expandedToolPanelSections","attach","super","handler","handleLightDomShellChange","_registerLightDomHandler","parseLightDom","_getToolPanelRendererFactory","hadTitle","hadToolButtons","hasTitle","hasToolButtons","_requestConfigMerge","afterStructuralRender","_parseLightDomHeader","_parseLightDomToolPanels","_parseLightDomAll","_prepareForRerender","_renderOpenPanelContent","_shouldRenderHeader","processConfig","ctor","resolvedConfig","contents","apiContents","originalConfigContents","_sourceConfig","configIds","mergedContents","setupListeners","renderShellContents","configContents","stateContents","allContents","slot","renderCustomToolbarContents","toolPanelCfg","shouldOpenOnLoad","initialState","callbacks","toolbar","addEventListener","e","closest","onPanelToggle","onPanelClose","accordion","onSectionToggle","setupShellEventListeners","onResize","handle","startX","startWidth","maxWidth","isResizing","onMouseMove","preventDefault","delta","clientX","newWidth","min","max","onMouseUp","transition","body","cursor","userSelect","finalWidth","removeEventListener","onMouseDown","setupToolPanelResize","closeOnClickOutside","composedPath","Element","matches","setupClickOutsideDismiss","key","defaultPrevented","setupEscapeDismiss","teardownListeners","detach","disposeShellState","_unregisterLightDomHandler","cleanupShellState"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"shell.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/shell/shell.ts","../../../../../libs/grid/src/lib/plugins/shell/shell-controller.ts","../../../../../libs/grid/src/lib/plugins/shell/ShellPlugin.ts"],"sourcesContent":["/**\n * Shell infrastructure for grid header bar and tool panels.\n *\n * The shell is an optional wrapper layer that provides:\n * - Header bar with title, plugin content, and toolbar buttons\n * - Tool panels that plugins can register content into\n * - Light DOM parsing for framework-friendly configuration\n */\n\nimport { TOOL_PANEL_DUPLICATE, TOOL_PANEL_MISSING_ATTR, warnDiagnostic } from '../../core/internal/diagnostics';\nimport { escapeHtml, sanitizeHTML } from '../../core/internal/sanitize';\nimport type { IconValue } from '../../core/types';\nimport type { HeaderContentDefinition, ShellConfig, ToolbarContentDefinition, ToolPanelDefinition } from './types';\n\n// #region Types & State\n/**\n * Convert an IconValue to a string for rendering in HTML.\n */\nfunction iconToString(icon: IconValue | undefined): string {\n if (!icon) return '';\n if (typeof icon === 'string') return sanitizeHTML(icon);\n // For HTMLElement, get the outerHTML\n return icon.outerHTML;\n}\n\n/**\n * State for managing shell UI.\n *\n * This interface holds both configuration-like properties (toolPanels, headerContents)\n * and runtime state (isPanelOpen, expandedSections). The Maps allow for efficient\n * registration/unregistration of panels and content.\n */\nexport interface ShellState {\n /** Registered tool panels (from plugins + consumer API) */\n toolPanels: Map<string, ToolPanelDefinition>;\n /** Registered header content (from plugins + consumer API) */\n headerContents: Map<string, HeaderContentDefinition>;\n /** Toolbar content registered via API or light DOM */\n toolbarContents: Map<string, ToolbarContentDefinition>;\n /** Whether a <tbw-grid-tool-buttons> container was found in light DOM */\n hasToolButtonsContainer: boolean;\n /** Light DOM header content elements */\n lightDomHeaderContent: HTMLElement[];\n /** Light DOM header title from <tbw-grid-header title=\"...\"> */\n lightDomTitle: string | null;\n /** IDs of tool panels registered from light DOM (to avoid re-parsing) */\n lightDomToolPanelIds: Set<string>;\n /**\n * IDs of light DOM tool panels whose `render` has already been bound to a\n * framework-adapter renderer. Used by `parseLightDomToolPanels` to detect\n * the FIRST adapter attach (which legitimately needs to swap the vanilla\n * fallback for the real adapter renderer and tear down any in-progress\n * render) vs. subsequent re-parses (which must NOT tear down — doing so\n * unmounts the user's React/Vue/Angular component, losing local state and\n * resetting scroll position inside the panel).\n */\n adapterBoundToolPanelIds: Set<string>;\n /** IDs of toolbar content registered from light DOM (to avoid re-parsing) */\n lightDomToolbarContentIds: Set<string>;\n /** IDs of tool panels registered via registerToolPanel API */\n apiToolPanelIds: Set<string>;\n /** IDs of header content registered via registerHeaderContent API */\n apiHeaderContentIds: Set<string>;\n /** Whether the tool panel sidebar is open */\n isPanelOpen: boolean;\n /** Which accordion sections are expanded (by panel ID) */\n expandedSections: Set<string>;\n /** Whether light DOM header content has been moved to placeholder (perf optimization) */\n lightDomContentMoved: boolean;\n /** Cleanup functions for header content render returns */\n headerContentCleanups: Map<string, () => void>;\n /** Cleanup functions for each panel section's render return */\n panelCleanups: Map<string, () => void>;\n /** Cleanup functions for toolbar content render returns */\n toolbarContentCleanups: Map<string, () => void>;\n /**\n * Element the dropdown popover is currently anchored to (mode: 'dropdown').\n * Tracked so dismissal logic can ignore clicks on the anchor and the\n * `anchor-name` it set can be cleared on close. `null` when no dropdown is open.\n */\n dropdownAnchorEl?: HTMLElement | null;\n /**\n * Unique `anchor-name` assigned to the current dropdown's trigger (and\n * mirrored as `position-anchor` on the popover). Minted per show so multiple\n * grids on one page never collide on a shared anchor name — a collision would\n * position one grid's popover against another grid's trigger. `null` when no\n * dropdown is open.\n */\n dropdownAnchorName?: string | null;\n}\n\n/**\n * Runtime-only shell state (not configuration).\n *\n * Configuration (toolPanels, headerContents, toolbarContents, title) lives in\n * effectiveConfig.shell. This state holds runtime UI state and cleanup functions.\n */\nexport interface ShellRuntimeState {\n /** Whether the tool panel sidebar is currently open */\n isPanelOpen: boolean;\n /** Which accordion sections are currently expanded (by panel ID) */\n expandedSections: Set<string>;\n /** Cleanup functions for header content render returns */\n headerContentCleanups: Map<string, () => void>;\n /** Cleanup functions for each panel section's render return */\n panelCleanups: Map<string, () => void>;\n /** Cleanup functions for toolbar content render returns */\n toolbarContentCleanups: Map<string, () => void>;\n /** IDs of tool panels registered from light DOM (to avoid re-parsing) */\n lightDomToolPanelIds: Set<string>;\n /** IDs of tool panels registered via registerToolPanel API */\n apiToolPanelIds: Set<string>;\n /** Whether a <tbw-grid-tool-buttons> container was found in light DOM */\n hasToolButtonsContainer: boolean;\n}\n\n/**\n * Create initial shell runtime state.\n */\nexport function createShellRuntimeState(): ShellRuntimeState {\n return {\n isPanelOpen: false,\n expandedSections: new Set(),\n headerContentCleanups: new Map(),\n panelCleanups: new Map(),\n toolbarContentCleanups: new Map(),\n lightDomToolPanelIds: new Set(),\n apiToolPanelIds: new Set(),\n hasToolButtonsContainer: false,\n };\n}\n\n/**\n * Create initial shell state.\n */\nexport function createShellState(): ShellState {\n return {\n toolPanels: new Map(),\n headerContents: new Map(),\n toolbarContents: new Map(),\n hasToolButtonsContainer: false,\n lightDomHeaderContent: [],\n lightDomTitle: null,\n lightDomToolPanelIds: new Set(),\n adapterBoundToolPanelIds: new Set(),\n lightDomToolbarContentIds: new Set(),\n apiToolPanelIds: new Set(),\n apiHeaderContentIds: new Set(),\n isPanelOpen: false,\n expandedSections: new Set(),\n headerContentCleanups: new Map(),\n panelCleanups: new Map(),\n toolbarContentCleanups: new Map(),\n lightDomContentMoved: false,\n dropdownAnchorEl: null,\n dropdownAnchorName: null,\n };\n}\n// #endregion\n\n// #region Render Functions\n/**\n * Determine if shell header should be rendered.\n * Reads only from effectiveConfig.shell (single source of truth).\n */\nexport function shouldRenderShellHeader(config: ShellConfig | undefined): boolean {\n // Check if title is configured\n if (config?.header?.title) return true;\n\n // Check if config has toolbar contents\n if (config?.header?.toolbarContents?.length) return true;\n\n // Check if any tool panels are registered\n if (config?.toolPanels?.length) return true;\n\n // Check if any header content is registered\n if (config?.headerContents?.length) return true;\n\n // Check if light DOM has header elements\n if (config?.header?.lightDomContent?.length) return true;\n\n // Check if a toolbar buttons container was found\n if (config?.header?.hasToolButtonsContainer) return true;\n\n return false;\n}\n\n/**\n * Determine whether the visible shell header bar (`.tbw-shell-header`) should\n * be rendered.\n *\n * Distinct from {@link shouldRenderShellHeader}, which decides whether the\n * shell *wrapper* (including the body that hosts the tool panel) exists at\n * all. The header bar can be suppressed via `header.visible: false` while the\n * shell body and tool panels still render — letting consumers drive panels\n * from their own UI (e.g. a utility-column header icon).\n */\nexport function shouldRenderHeaderBar(config: ShellConfig | undefined): boolean {\n return config?.header?.visible !== false;\n}\n\n/**\n * Render the shell header HTML.\n *\n * Toolbar contents come from two sources:\n * 1. Light DOM slot (users provide their own HTML in <tbw-grid-tool-buttons>)\n * 2. Config/API with render function (programmatic insertion)\n *\n * Users have full control over toolbar HTML, styling, and behavior.\n * The only button the grid creates is the tool panel toggle.\n *\n * @param toolPanelIcon - Icon for the tool panel toggle (from grid icon config)\n */\nexport function renderShellHeader(\n config: ShellConfig | undefined,\n state: ShellState,\n toolPanelIcon?: IconValue,\n): string {\n const title = config?.header?.title ?? state.lightDomTitle ?? '';\n const hasTitle = !!title;\n\n // Build tool panel button content: use data-icon for CSS, inject content only for JS overrides\n let toolPanelBtnContent = '';\n if (toolPanelIcon !== undefined) {\n // JS override: inject content but still set data-icon\n toolPanelBtnContent = typeof toolPanelIcon === 'string' ? sanitizeHTML(toolPanelIcon) : toolPanelIcon.outerHTML;\n }\n\n // Get all toolbar contents from effectiveConfig (already merged: config + API + light DOM)\n // The config-manager merges state.toolbarContents into effectiveConfig.shell.header.toolbarContents\n // Also include state.toolbarContents directly for cases where renderShellHeader is called\n // before config-manager has merged (e.g., unit tests, initial render)\n const configContents = config?.header?.toolbarContents ?? [];\n const stateContents = [...state.toolbarContents.values()];\n\n // Merge: use config contents, add state contents that aren't in config\n const configIds = new Set(configContents.map((c) => c.id));\n const allContents = [...configContents];\n for (const content of stateContents) {\n if (!configIds.has(content.id)) {\n allContents.push(content);\n }\n }\n\n const hasCustomContent = allContents.length > 0;\n const hasPanels = state.toolPanels.size > 0;\n // Allow consumers to suppress the built-in toggle (and the separator that\n // only exists to space it from custom toolbar contents) so they can render\n // their own design-system button and wire it to `grid.toggleToolPanel()`.\n // Also suppress when the tool panel is locked open — toggling is disabled.\n // Default true preserves existing behavior.\n const isLocked = config?.toolPanel?.locked === true;\n const showToggle = hasPanels && config?.header?.toolPanelToggle !== false && !isLocked;\n const showSeparator = hasCustomContent && showToggle;\n\n // Sort contents by order for slot placement\n const sortedContents = [...allContents].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));\n\n // Build toolbar HTML\n let toolbarHtml = '';\n\n // Create slots for all contents (unified: config + API + light DOM)\n for (const content of sortedContents) {\n toolbarHtml += `<div class=\"tbw-toolbar-content-slot\" data-toolbar-content=\"${content.id}\"></div>`;\n }\n\n // Separator between custom content and panel toggle\n if (showSeparator) {\n toolbarHtml += '<div class=\"tbw-toolbar-separator\"></div>';\n }\n\n // Single panel toggle button (the ONLY button the grid creates).\n // Suppressed when `shell.header.toolPanelToggle === false` so consumers can\n // BYO toggle button without hiding library DOM via CSS.\n if (showToggle) {\n const isOpen = state.isPanelOpen;\n const toggleClass = isOpen ? 'tbw-toolbar-btn active' : 'tbw-toolbar-btn';\n // type=\"button\" is required: without it a <button> inside a <form>\n // defaults to type=\"submit\" and clicking the toggle would submit the\n // surrounding form. Internal grid buttons must never trigger form\n // submission. See issue #296.\n toolbarHtml += `<button type=\"button\" class=\"${toggleClass}\" data-panel-toggle data-icon=\"tool-panel\" title=\"Settings\" aria-label=\"Toggle settings panel\" aria-pressed=\"${isOpen}\" aria-controls=\"tbw-tool-panel\">${toolPanelBtnContent}</button>`;\n }\n\n return `\n <div class=\"tbw-shell-header\" part=\"shell-header\" role=\"presentation\">\n ${hasTitle ? `<div class=\"tbw-shell-title\">${escapeHtml(title)}</div>` : ''}\n <div class=\"tbw-shell-content\" part=\"shell-content\" role=\"presentation\" data-light-dom-header-content></div>\n <div class=\"tbw-shell-toolbar\" part=\"shell-toolbar\" role=\"presentation\">\n ${toolbarHtml}\n </div>\n </div>\n `;\n}\n\n/**\n * Render the shell body wrapper HTML (contains grid content + accordion-style tool panel).\n * @param icons - Optional icons for expand/collapse chevrons (from grid config)\n */\nexport function renderShellBody(\n config: ShellConfig | undefined,\n state: ShellState,\n gridContentHtml: string,\n icons?: { expand?: IconValue; collapse?: IconValue },\n): string {\n const position = config?.toolPanel?.position ?? 'right';\n const mode = config?.toolPanel?.mode ?? 'overlay';\n const hasPanel = state.toolPanels.size > 0;\n const isOpen = state.isPanelOpen;\n const hasJsExpandIcon = icons?.expand !== undefined;\n const expandIcon = hasJsExpandIcon ? iconToString(icons.expand) : '';\n\n // Sort panels by order for accordion sections\n const sortedPanels = [...state.toolPanels.values()].sort((a, b) => (a.order ?? 100) - (b.order ?? 100));\n const isSinglePanel = sortedPanels.length === 1;\n\n // Build accordion sections HTML\n let accordionHtml = '';\n for (const panel of sortedPanels) {\n const isExpanded = state.expandedSections.has(panel.id);\n const iconHtml = panel.icon ? `<span class=\"tbw-accordion-icon\">${panel.icon}</span>` : '';\n // Hide chevron for single panel (no toggling needed)\n // Always use expandIcon (▶) — CSS rotation handles the expanded state\n const chevronHtml = isSinglePanel\n ? ''\n : `<span class=\"tbw-accordion-chevron\" data-icon=\"expand\">${expandIcon}</span>`;\n // Disable accordion toggle for single panel\n const sectionClasses = `tbw-accordion-section${isExpanded ? ' expanded' : ''}${isSinglePanel ? ' single' : ''}`;\n accordionHtml += `\n <div class=\"${sectionClasses}\" data-section=\"${panel.id}\">\n <button type=\"button\" class=\"tbw-accordion-header\" aria-expanded=\"${isExpanded}\" aria-controls=\"tbw-section-${panel.id}\"${isSinglePanel ? ' aria-disabled=\"true\"' : ''}>\n ${iconHtml}\n <span class=\"tbw-accordion-title\">${panel.title}</span>\n ${chevronHtml}\n </button>\n <div class=\"tbw-accordion-content\" id=\"tbw-section-${panel.id}\" role=\"presentation\"></div>\n </div>\n `;\n }\n\n // Resize handle position depends on panel position\n const resizeHandlePosition = position === 'left' ? 'right' : 'left';\n\n const panelHtml = hasPanel\n ? `\n <aside class=\"tbw-tool-panel${isOpen ? ' open' : ''}\" part=\"tool-panel\" data-position=\"${position}\" role=\"presentation\" id=\"tbw-tool-panel\"${mode === 'dropdown' ? ' popover=\"manual\"' : ''}>\n <div class=\"tbw-tool-panel-resize\" data-resize-handle data-handle-position=\"${resizeHandlePosition}\" aria-hidden=\"true\"></div>\n <div class=\"tbw-tool-panel-content\" role=\"presentation\">\n <div class=\"tbw-accordion\">\n ${accordionHtml}\n </div>\n </div>\n </aside>\n `\n : '';\n\n // For left position, panel comes before content in DOM order\n if (position === 'left') {\n return `\n <div class=\"tbw-shell-body\" data-mode=\"${mode}\">\n ${panelHtml}\n <div class=\"tbw-grid-content\">\n ${gridContentHtml}\n </div>\n </div>\n `;\n }\n\n return `\n <div class=\"tbw-shell-body\" data-mode=\"${mode}\">\n <div class=\"tbw-grid-content\">\n ${gridContentHtml}\n </div>\n ${panelHtml}\n </div>\n `;\n}\n// #endregion\n\n// #region Light DOM Parsing\n/**\n * Parse light DOM shell elements (tbw-grid-header, etc.).\n * Safe to call multiple times - will only parse once when elements are available.\n */\nexport function parseLightDomShell(host: HTMLElement, state: ShellState): void {\n const headerEl = host.querySelector('tbw-grid-header');\n if (!headerEl) return;\n\n // Parse title attribute (only if not already parsed)\n if (!state.lightDomTitle) {\n const title = headerEl.getAttribute('title');\n if (title) {\n state.lightDomTitle = title;\n }\n }\n\n // Parse header content elements - store references but don't set slot (light DOM doesn't use slots)\n const headerContents = headerEl.querySelectorAll('tbw-grid-header-content');\n if (headerContents.length > 0 && state.lightDomHeaderContent.length === 0) {\n state.lightDomHeaderContent = Array.from(headerContents) as HTMLElement[];\n }\n\n // Hide the light DOM header container (it was just for declarative config)\n (headerEl as HTMLElement).style.display = 'none';\n}\n\n/**\n * Callback type for creating a toolbar content renderer from a light DOM container.\n * This is used by framework adapters (Angular, React, etc.) to create renderers\n * from their template syntax.\n */\nexport type ToolbarContentRendererFactory = (\n container: HTMLElement,\n) => ((target: HTMLElement) => void | (() => void)) | undefined;\n\n/**\n * Parse toolbar buttons container element (<tbw-grid-tool-buttons>).\n * This is a content container - we don't parse individual children.\n * The entire container content is registered as a single toolbar content entry.\n *\n * Example:\n * ```html\n * <tbw-grid>\n * <tbw-grid-tool-buttons>\n * <button>My button</button>\n * <button>My other button</button>\n * </tbw-grid-tool-buttons>\n * </tbw-grid>\n * ```\n *\n * The container's children are moved to the toolbar area during render.\n * We treat this as opaque content - users control what goes inside.\n *\n * @param host - The grid host element\n * @param state - Shell state to update\n * @param rendererFactory - Optional factory for creating renderers (used by framework adapters)\n */\nexport function parseLightDomToolButtons(\n host: HTMLElement,\n state: ShellState,\n rendererFactory?: ToolbarContentRendererFactory,\n): void {\n // Look for the toolbar buttons container element\n const toolButtonsContainer = host.querySelector(':scope > tbw-grid-tool-buttons') as HTMLElement | null;\n if (!toolButtonsContainer) return;\n\n // Mark that we found the container (for shouldRenderShellHeader)\n state.hasToolButtonsContainer = true;\n\n // Skip if already registered\n const id = 'light-dom-toolbar-content';\n if (state.lightDomToolbarContentIds.has(id)) return;\n\n // Register as a single content entry with a render function\n const adapterRenderer = rendererFactory?.(toolButtonsContainer);\n\n const contentDef: ToolbarContentDefinition = {\n id,\n order: 0, // Light DOM content comes first\n render:\n adapterRenderer ??\n ((target: HTMLElement) => {\n // Move all children from the light DOM container to the target\n while (toolButtonsContainer.firstChild) {\n target.appendChild(toolButtonsContainer.firstChild);\n }\n // Return cleanup that moves children back to original container\n // This preserves them across full re-renders that destroy the slot\n return () => {\n while (target.firstChild) {\n toolButtonsContainer.appendChild(target.firstChild);\n }\n };\n }),\n };\n\n state.toolbarContents.set(id, contentDef);\n state.lightDomToolbarContentIds.add(id);\n\n // Hide the original container\n toolButtonsContainer.style.display = 'none';\n}\n\n/**\n * Callback type for creating a tool panel renderer from a light DOM element.\n * This is used by framework adapters (Angular, React, etc.) to create renderers\n * from their template syntax.\n */\nexport type ToolPanelRendererFactory = (\n element: HTMLElement,\n) => ((container: HTMLElement) => void | (() => void)) | undefined;\n\n/**\n * Parse light DOM tool panel elements (<tbw-grid-tool-panel>).\n * These can appear as direct children of <tbw-grid> for declarative tool panel configuration.\n *\n * Attributes:\n * - `id` (required): Unique panel identifier\n * - `title` (required): Panel title shown in accordion header\n * - `icon`: Icon for accordion section header (emoji or text)\n * - `tooltip`: Tooltip for accordion section header\n * - `order`: Panel order priority (lower = first, default: 100)\n *\n * For vanilla JS, the element's innerHTML is used as the panel content.\n * For framework adapters, the adapter can provide a custom renderer factory.\n *\n * @param host - The grid host element\n * @param state - Shell state to update\n * @param rendererFactory - Optional factory for creating renderers (used by framework adapters)\n */\nexport function parseLightDomToolPanels(\n host: HTMLElement,\n state: ShellState,\n rendererFactory?: ToolPanelRendererFactory,\n): void {\n const toolPanelElements = host.querySelectorAll(':scope > tbw-grid-tool-panel');\n\n toolPanelElements.forEach((element) => {\n const panelEl = element as HTMLElement;\n const id = panelEl.getAttribute('id');\n const title = panelEl.getAttribute('title');\n\n // Skip if required attributes are missing\n if (!id || !title) {\n warnDiagnostic(\n TOOL_PANEL_MISSING_ATTR,\n `Tool panel missing required id or title attribute: id=\"${id ?? ''}\", title=\"${title ?? ''}\"`,\n );\n return;\n }\n\n const icon = panelEl.getAttribute('icon') ?? undefined;\n const tooltip = panelEl.getAttribute('tooltip') ?? undefined;\n const order = parseInt(panelEl.getAttribute('order') ?? '100', 10);\n\n // Try framework adapter first, then fall back to innerHTML\n let render: (container: HTMLElement) => void | (() => void);\n\n const adapterRenderer = rendererFactory?.(panelEl);\n if (adapterRenderer) {\n render = adapterRenderer;\n } else {\n // Vanilla fallback: use sanitized innerHTML as static content.\n // Light DOM authored markup is generally trusted, but we sanitize\n // defensively in case the panel content was server-rendered from data.\n const content = sanitizeHTML(panelEl.innerHTML.trim());\n render = (container: HTMLElement) => {\n const wrapper = document.createElement('div');\n wrapper.innerHTML = content;\n container.appendChild(wrapper);\n return () => wrapper.remove();\n };\n }\n\n // Check if panel was already parsed\n const existingPanel = state.toolPanels.get(id);\n\n // Precedence (#370): a panel contributed by a plugin (or registered via the\n // grid API) owns its id. A light-DOM <tbw-grid-tool-panel> with the same id\n // is ignored so the owner can rely on its own DOM existing. We detect a\n // foreign owner by the id being present in toolPanels but NOT tracked as a\n // light-DOM id (which would mean this is just a re-parse of our own panel).\n if (existingPanel && !state.lightDomToolPanelIds.has(id)) {\n warnDiagnostic(\n TOOL_PANEL_DUPLICATE,\n `Tool panel \"${id}\" is already provided by a plugin or the grid API; ignoring the matching light-DOM <tbw-grid-tool-panel>.`,\n );\n panelEl.style.display = 'none';\n return;\n }\n\n // If already parsed and we have an adapter renderer, refresh the render\n // function and (potentially) re-read attributes from DOM (Angular may have\n // updated them after the initial parse). However, **only tear down the\n // already-rendered panel content** when something the user can observe has\n // actually changed:\n // - First time the adapter renderer attaches (we may have been using\n // the vanilla innerHTML fallback up to now)\n // - Any attribute the panel header renders from changed\n //\n // Otherwise the user's React/Vue/Angular component is already mounted in\n // the panel and is happily managing its own state. A teardown here unmounts\n // it (losing local state) and re-mounts it on next renderPanelContent —\n // which, for a scrollable panel, resets `scrollTop` to 0. This was the\n // cause of the \"tool panel scroll jumps to top on column visibility toggle\"\n // bug: every `grid.gridConfig = …` setter in the adapter eventually calls\n // back into parseLightDomToolPanels (via #applyGridConfigUpdate), and the\n // previous unconditional `cleanup()` here threw away the user's component\n // tree on every config sync.\n if (existingPanel) {\n if (adapterRenderer) {\n const firstAdapterAttach = !state.adapterBoundToolPanelIds.has(id);\n const attrsChanged =\n existingPanel.order !== order || existingPanel.icon !== icon || existingPanel.tooltip !== tooltip;\n\n // Always keep the render function fresh — adapter renderers capture\n // the current grid element / portal context and the most recent\n // children, so a stale reference would render against an old closure.\n existingPanel.render = render;\n existingPanel.order = order;\n existingPanel.icon = icon;\n existingPanel.tooltip = tooltip;\n state.adapterBoundToolPanelIds.add(id);\n\n if (firstAdapterAttach || attrsChanged) {\n const cleanup = state.panelCleanups.get(id);\n if (cleanup) {\n cleanup();\n state.panelCleanups.delete(id);\n }\n }\n }\n return;\n }\n\n // Register the tool panel\n const panel: ToolPanelDefinition = {\n id,\n title,\n icon,\n tooltip,\n order,\n render,\n };\n\n state.toolPanels.set(id, panel);\n state.lightDomToolPanelIds.add(id);\n if (adapterRenderer) {\n state.adapterBoundToolPanelIds.add(id);\n }\n\n // Hide the light DOM element\n panelEl.style.display = 'none';\n });\n}\n// #endregion\n\n// #region Event Handlers\n/**\n * Set up event listeners for shell toolbar buttons and accordion.\n */\nexport function setupShellEventListeners(\n renderRoot: Element,\n config: ShellConfig | undefined,\n state: ShellState,\n callbacks: {\n onPanelToggle: () => void;\n onSectionToggle: (sectionId: string) => void;\n /** Invoked when the in-panel close (✕) button is clicked. */\n onPanelClose?: () => void;\n },\n): void {\n const toolbar = renderRoot.querySelector('.tbw-shell-toolbar');\n if (toolbar) {\n toolbar.addEventListener('click', (e) => {\n const target = e.target as HTMLElement;\n\n // Handle single panel toggle button\n const panelToggle = target.closest('[data-panel-toggle]') as HTMLElement | null;\n if (panelToggle) {\n callbacks.onPanelToggle();\n return;\n }\n });\n }\n\n // In-panel close (✕) button — only present when the header bar is hidden.\n const panel = renderRoot.querySelector('.tbw-tool-panel');\n if (panel && callbacks.onPanelClose) {\n panel.addEventListener('click', (e) => {\n const target = e.target as HTMLElement;\n if (target.closest('[data-panel-close]')) {\n callbacks.onPanelClose!();\n }\n });\n }\n\n // Accordion header clicks\n const accordion = renderRoot.querySelector('.tbw-accordion');\n if (accordion) {\n accordion.addEventListener('click', (e) => {\n const target = e.target as HTMLElement;\n const header = target.closest('.tbw-accordion-header') as HTMLElement | null;\n if (header) {\n const section = header.closest('[data-section]') as HTMLElement | null;\n const sectionId = section?.getAttribute('data-section');\n if (sectionId) {\n callbacks.onSectionToggle(sectionId);\n }\n }\n });\n }\n}\n\n/**\n * Whether `node` belongs to `gridElement` — directly (light DOM) or via the\n * grid's shadow root (the node's root-node host is the grid). Used to scope\n * click-outside checks so one grid's listener never treats another grid's\n * toggle or panel as \"inside\" itself.\n */\nfunction isInGrid(node: Node, gridElement: Element): boolean {\n if (gridElement.contains(node)) return true;\n const root = node.getRootNode();\n return root instanceof ShadowRoot && root.host === gridElement;\n}\n\n/**\n * Set up a click-outside listener that closes the tool panel when the user\n * clicks anywhere in the document outside the tool panel itself.\n *\n * Only active when `config.toolPanel.closeOnClickOutside` is `true` AND the\n * panel is open. The listener is added on `document` `mousedown` (not `click`)\n * so it fires before focus changes and catches clicks anywhere in the window —\n * including outside the grid. Uses `composedPath()` so clicks inside the\n * grid's shadow DOM are correctly attributed (shadow-retargeted `event.target`\n * would otherwise always be the host element).\n *\n * @returns A cleanup function that removes the listener.\n */\nexport function setupClickOutsideDismiss(\n gridElement: Element,\n config: ShellConfig | undefined,\n state: ShellState,\n onClose: () => void,\n): () => void {\n const mode = config?.toolPanel?.mode;\n\n // Dropdown popovers always light-dismiss on outside click (standard dropdown\n // behavior), regardless of the `closeOnClickOutside` flag. Other modes only\n // dismiss when the consumer opts in.\n if (mode !== 'dropdown' && !config?.toolPanel?.closeOnClickOutside) {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n return () => {};\n }\n\n // In push mode the panel does not overlap grid content, so there is\n // no meaningful \"outside\" to click against. Treat the option as a no-op.\n if (mode === 'push') {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n return () => {};\n }\n\n const handler = (e: Event) => {\n if (!state.isPanelOpen) return;\n\n // Ignore clicks inside the tool panel itself, on the toggle button, or on\n // the dropdown's resolved anchor element (so a consumer's BYO trigger can\n // toggle without the outside-click handler racing the button's own click).\n // composedPath() pierces shadow boundaries so this works for both the\n // light-DOM toggle and the shadow-DOM panel.\n //\n // CRITICAL: the panel/toggle match MUST be scoped to THIS grid via\n // `isInGrid`. With multiple grids on one page, a click on another grid's\n // toggle/panel is an \"outside\" click for us and must still dismiss \\u2014\n // otherwise opening a dropdown in grid B leaves grid A's dropdown stuck\n // open (#375 follow-up).\n const anchorEl = state.dropdownAnchorEl;\n for (const node of e.composedPath()) {\n if (anchorEl != null && node === anchorEl) return;\n if (\n node instanceof Element &&\n (node.classList?.contains('tbw-tool-panel') || node.matches?.('[data-panel-toggle]')) &&\n isInGrid(node, gridElement)\n ) {\n return;\n }\n }\n\n onClose();\n };\n\n document.addEventListener('mousedown', handler);\n return () => document.removeEventListener('mousedown', handler);\n}\n\n/**\n * Set up an Escape-key listener that closes an open overlay tool panel.\n *\n * Active only in `overlay` mode (a push panel is a persistent sidebar, not a\n * transient overlay, so Esc does not dismiss it — the in-panel close button is\n * the affordance there). Yields to more specific handlers: if another listener\n * already called `preventDefault()` on the keydown (e.g. an active cell editor\n * cancelling an edit), the panel is left open.\n *\n * @returns A cleanup function that removes the listener.\n */\nexport function setupEscapeDismiss(\n config: ShellConfig | undefined,\n state: ShellState,\n onClose: () => void,\n): () => void {\n // Push panels are persistent — Esc only dismisses overlay panels.\n if (config?.toolPanel?.mode === 'push') {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n return () => {};\n }\n\n const handler = (e: KeyboardEvent) => {\n if (e.key !== 'Escape' || e.defaultPrevented) return;\n if (!state.isPanelOpen) return;\n onClose();\n };\n\n document.addEventListener('keydown', handler);\n return () => document.removeEventListener('keydown', handler);\n}\n\n/**\n * Set up resize handle for tool panel.\n * Returns a cleanup function to remove event listeners.\n */\nexport function setupToolPanelResize(\n renderRoot: Element,\n config: ShellConfig | undefined,\n onResize: (width: number) => void,\n): () => void {\n const panel = renderRoot.querySelector('.tbw-tool-panel') as HTMLElement | null;\n const handle = renderRoot.querySelector('[data-resize-handle]') as HTMLElement | null;\n const shellBody = renderRoot.querySelector('.tbw-shell-body') as HTMLElement | null;\n if (!panel || !handle || !shellBody) {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n return () => {};\n }\n\n const position = config?.toolPanel?.position ?? 'right';\n const minWidth = 200;\n\n let startX = 0;\n let startWidth = 0;\n let maxWidth = 0;\n let isResizing = false;\n\n const onMouseMove = (e: MouseEvent) => {\n if (!isResizing) return;\n e.preventDefault();\n\n // For right-positioned panel: dragging left (negative clientX change) should expand\n // For left-positioned panel: dragging right (positive clientX change) should expand\n const delta = position === 'left' ? e.clientX - startX : startX - e.clientX;\n const newWidth = Math.min(maxWidth, Math.max(minWidth, startWidth + delta));\n\n panel.style.width = `${newWidth}px`;\n };\n\n const onMouseUp = () => {\n if (!isResizing) return;\n isResizing = false;\n handle.classList.remove('resizing');\n panel.style.transition = ''; // Re-enable transition\n document.body.style.cursor = '';\n document.body.style.userSelect = '';\n\n // Get final width and notify\n const finalWidth = panel.getBoundingClientRect().width;\n onResize(finalWidth);\n\n document.removeEventListener('mousemove', onMouseMove);\n document.removeEventListener('mouseup', onMouseUp);\n };\n\n const onMouseDown = (e: MouseEvent) => {\n e.preventDefault();\n isResizing = true;\n startX = e.clientX;\n startWidth = panel.getBoundingClientRect().width;\n // Calculate max width dynamically based on grid container width\n maxWidth = shellBody.getBoundingClientRect().width - 20; // Leave 20px margin\n handle.classList.add('resizing');\n panel.style.transition = 'none'; // Disable transition for smooth resize\n document.body.style.cursor = 'col-resize';\n document.body.style.userSelect = 'none';\n\n document.addEventListener('mousemove', onMouseMove);\n document.addEventListener('mouseup', onMouseUp);\n };\n\n handle.addEventListener('mousedown', onMouseDown);\n\n // Return cleanup function\n return () => {\n handle.removeEventListener('mousedown', onMouseDown);\n document.removeEventListener('mousemove', onMouseMove);\n document.removeEventListener('mouseup', onMouseUp);\n };\n}\n// #endregion\n\n// #region Content Rendering\n/**\n * Render toolbar content (render functions) into toolbar slots.\n * All contents (config + API + light DOM) are now unified.\n */\nexport function renderCustomToolbarContents(\n renderRoot: Element,\n config: ShellConfig | undefined,\n state: ShellState,\n): void {\n // Merge config contents with state contents (same logic as renderShellHeader)\n const configContents = config?.header?.toolbarContents ?? [];\n const stateContents = [...state.toolbarContents.values()];\n const configIds = new Set(configContents.map((c) => c.id));\n const allContents = [...configContents];\n for (const content of stateContents) {\n if (!configIds.has(content.id)) {\n allContents.push(content);\n }\n }\n\n // Only process contents that need rendering (have render and cleanup not already set)\n for (const content of allContents) {\n // Skip if already rendered (cleanup exists)\n if (state.toolbarContentCleanups.has(content.id)) continue;\n if (!content.render) continue;\n\n const slot = renderRoot.querySelector(`[data-toolbar-content=\"${content.id}\"]`);\n if (!slot) continue;\n\n const cleanup = content.render(slot as HTMLElement);\n if (cleanup) {\n state.toolbarContentCleanups.set(content.id, cleanup);\n }\n }\n}\n\n/**\n * Render header content from plugins into the shell content area.\n * Also moves light DOM header content to the placeholder (once).\n */\nexport function renderHeaderContent(renderRoot: Element, state: ShellState): void {\n // Early exit if nothing to do (most common path after initial render)\n const hasLightDomContent = state.lightDomHeaderContent.length > 0 && !state.lightDomContentMoved;\n const hasPluginContent = state.headerContents.size > 0;\n if (!hasLightDomContent && !hasPluginContent) return;\n\n const contentArea = renderRoot.querySelector('.tbw-shell-content');\n if (!contentArea) return;\n\n // Move light DOM header content to placeholder - only once (perf optimization)\n if (hasLightDomContent) {\n for (const el of state.lightDomHeaderContent) {\n el.style.display = ''; // Show it (was hidden in the original container)\n contentArea.appendChild(el);\n }\n state.lightDomContentMoved = true;\n }\n\n // Sort by order\n const sortedContents = [...state.headerContents.values()].sort((a, b) => (a.order ?? 100) - (b.order ?? 100));\n\n for (const content of sortedContents) {\n // Clean up previous render if any\n const existingCleanup = state.headerContentCleanups.get(content.id);\n if (existingCleanup) {\n existingCleanup();\n state.headerContentCleanups.delete(content.id);\n }\n\n // Check if container already exists\n let container = contentArea.querySelector(`[data-header-content=\"${content.id}\"]`) as HTMLElement | null;\n if (!container) {\n container = document.createElement('div');\n container.setAttribute('data-header-content', content.id);\n contentArea.appendChild(container);\n }\n\n const cleanup = content.render(container);\n if (cleanup) {\n state.headerContentCleanups.set(content.id, cleanup);\n }\n }\n}\n\n/**\n * Render content for expanded accordion sections.\n * @param icons - Optional icons for expand/collapse chevrons (from grid config)\n */\nexport function renderPanelContent(\n renderRoot: Element,\n state: ShellState,\n _icons?: { expand?: IconValue; collapse?: IconValue },\n): void {\n if (!state.isPanelOpen) return;\n\n for (const [panelId, panel] of state.toolPanels) {\n const isExpanded = state.expandedSections.has(panelId);\n const section = renderRoot.querySelector(`[data-section=\"${panelId}\"]`);\n const contentArea = section?.querySelector('.tbw-accordion-content') as HTMLElement | null;\n\n if (!section || !contentArea) continue;\n\n // Update expanded state\n section.classList.toggle('expanded', isExpanded);\n const header = section.querySelector('.tbw-accordion-header');\n if (header) {\n header.setAttribute('aria-expanded', String(isExpanded));\n }\n // Don't swap chevron icon — CSS rotation handles expanded/collapsed state\n\n if (isExpanded) {\n // Check if content is already rendered\n if (contentArea.children.length === 0) {\n // Render panel content\n const cleanup = panel.render(contentArea);\n if (cleanup) {\n state.panelCleanups.set(panelId, cleanup);\n }\n }\n } else {\n // Clean up and clear content when collapsed\n const cleanup = state.panelCleanups.get(panelId);\n if (cleanup) {\n cleanup();\n state.panelCleanups.delete(panelId);\n }\n contentArea.innerHTML = '';\n }\n }\n}\n\n/**\n * Update toolbar button active states.\n */\nexport function updateToolbarActiveStates(renderRoot: Element, state: ShellState): void {\n // Update single panel toggle button\n const panelToggle = renderRoot.querySelector('[data-panel-toggle]');\n if (panelToggle) {\n panelToggle.classList.toggle('active', state.isPanelOpen);\n panelToggle.setAttribute('aria-pressed', String(state.isPanelOpen));\n }\n}\n\n/**\n * Update tool panel open/close state.\n */\nexport function updatePanelState(renderRoot: Element, state: ShellState): void {\n const panel = renderRoot.querySelector('.tbw-tool-panel') as HTMLElement | null;\n if (!panel) return;\n\n panel.classList.toggle('open', state.isPanelOpen);\n\n // Clear inline width when closing (resize sets inline style that overrides CSS)\n if (!state.isPanelOpen) {\n panel.style.width = '';\n }\n}\n\n/** Base CSS anchor name used to feature-detect CSS anchor positioning support. */\nconst DROPDOWN_ANCHOR_NAME = '--tbw-tool-panel-anchor';\n\n/**\n * Monotonic counter for minting unique per-grid dropdown anchor names. A shared\n * constant name collides when two grids on the same page each open a dropdown,\n * pairing one grid's popover with the other grid's trigger (#375 follow-up).\n */\nlet dropdownAnchorSeq = 0;\n\n/** Whether the current environment supports CSS anchor positioning. */\nexport function supportsAnchorPositioning(): boolean {\n return (\n typeof CSS !== 'undefined' &&\n typeof CSS.supports === 'function' &&\n CSS.supports('anchor-name', DROPDOWN_ANCHOR_NAME)\n );\n}\n\n/**\n * Position the dropdown popover with fixed coordinates derived from the\n * anchor's bounding rect. Used only when CSS anchor positioning is\n * unsupported; otherwise placement is driven entirely by `shell.css`.\n */\nfunction positionDropdownFallback(panel: HTMLElement, anchorEl: HTMLElement, corner: boolean): void {\n const rect = anchorEl.getBoundingClientRect();\n const position = panel.getAttribute('data-position') ?? 'right';\n panel.style.position = 'fixed';\n // `corner` anchors to the top of the grid (top corner); otherwise drop below\n // the trigger button.\n panel.style.top = `${Math.round((corner ? rect.top : rect.bottom) + 4)}px`;\n if (position === 'left') {\n panel.style.left = `${Math.round(rect.left)}px`;\n panel.style.right = 'auto';\n } else {\n const viewportWidth = typeof window !== 'undefined' ? window.innerWidth : 0;\n panel.style.left = 'auto';\n panel.style.right = `${Math.round(viewportWidth - rect.right)}px`;\n }\n}\n\n/**\n * Show the tool panel as an anchored dropdown popover (`mode: 'dropdown'`).\n *\n * Reuses the existing `.tbw-tool-panel` aside (the full accordion) and shows it\n * in the top layer via the native Popover API. Placement is handled by CSS\n * anchor positioning when supported, with a JS bounding-rect fallback otherwise.\n *\n * @param anchorEl - Element the popover anchors to.\n * @param corner - When `true`, anchor to the top corner of `anchorEl` (grid\n * corner fallback); otherwise drop directly below it (toggle / custom button).\n */\nexport function showToolPanelDropdown(\n renderRoot: Element,\n state: ShellState,\n anchorEl: HTMLElement,\n corner: boolean,\n): void {\n const panel = renderRoot.querySelector('.tbw-tool-panel') as (HTMLElement & { showPopover?: () => void }) | null;\n if (!panel) return;\n\n state.dropdownAnchorEl = anchorEl;\n panel.dataset.anchor = corner ? 'corner' : 'below';\n\n // Show in the top layer. Guard for environments (older browsers / test DOM)\n // without the native Popover API.\n if (panel.hasAttribute('popover') && typeof panel.showPopover === 'function') {\n try {\n panel.showPopover();\n } catch {\n // Already open or not connected — ignore.\n }\n }\n\n if (supportsAnchorPositioning()) {\n // CSS drives placement; just wire up the anchor-name pairing. Mint a unique\n // name per show so multiple grids (or repeated opens) on one page never\n // collide on a shared anchor name — a collision positions this popover\n // against another grid's trigger instead of its own.\n const anchorName = `${DROPDOWN_ANCHOR_NAME}-${++dropdownAnchorSeq}`;\n state.dropdownAnchorName = anchorName;\n anchorEl.style.setProperty('anchor-name', anchorName);\n panel.style.setProperty('position-anchor', anchorName);\n panel.style.removeProperty('top');\n panel.style.removeProperty('left');\n panel.style.removeProperty('right');\n } else {\n positionDropdownFallback(panel, anchorEl, corner);\n }\n}\n\n/**\n * Hide the dropdown popover and clear all anchor wiring set by\n * {@link showToolPanelDropdown}.\n */\nexport function hideToolPanelDropdown(renderRoot: Element, state: ShellState): void {\n const panel = renderRoot.querySelector('.tbw-tool-panel') as (HTMLElement & { hidePopover?: () => void }) | null;\n if (panel) {\n if (panel.hasAttribute('popover') && typeof panel.hidePopover === 'function') {\n try {\n panel.hidePopover();\n } catch {\n // Not currently open — ignore.\n }\n }\n panel.style.removeProperty('position-anchor');\n panel.style.removeProperty('position');\n panel.style.removeProperty('top');\n panel.style.removeProperty('left');\n panel.style.removeProperty('right');\n delete panel.dataset.anchor;\n }\n if (state.dropdownAnchorEl) {\n state.dropdownAnchorEl.style.removeProperty('anchor-name');\n state.dropdownAnchorEl = null;\n }\n state.dropdownAnchorName = null;\n}\n\n/**\n * Re-pair an OPEN dropdown's existing anchor onto a freshly resolved trigger\n * IN PLACE — reusing the already-minted `anchor-name` and leaving the popover\n * shown — instead of hiding and re-showing it.\n *\n * WHY: when an outer framework (e.g. a React column `headerRenderer`) recreates\n * the `[data-panel-toggle]` trigger on a column toggle, the new node lacks the\n * `anchor-name`, so the popover's `position-anchor` no longer resolves and the\n * browser drops it to the corner. Hiding + re-showing (the heavier\n * {@link showToolPanelDropdown} path) mints a NEW name and re-enters the\n * Popover API, which flashes the popover to the corner for a frame. Moving the\n * SAME name to the new node keeps the popover visually anchored with no flash.\n *\n * Returns `true` when the re-pair was applied. The caller must ensure CSS\n * anchor positioning is supported (see {@link supportsAnchorPositioning});\n * otherwise the fixed-coordinate fallback in {@link showToolPanelDropdown} must\n * be used instead.\n */\nexport function repairDropdownAnchor(renderRoot: Element, state: ShellState, trigger: HTMLElement): boolean {\n const anchorName = state.dropdownAnchorName;\n if (!anchorName) return false;\n const panel = renderRoot.querySelector('.tbw-tool-panel') as (HTMLElement & { showPopover?: () => void }) | null;\n if (!panel) return false;\n const prev = state.dropdownAnchorEl;\n if (prev && prev !== trigger) prev.style.removeProperty('anchor-name');\n trigger.style.setProperty('anchor-name', anchorName);\n panel.style.setProperty('position-anchor', anchorName);\n panel.style.removeProperty('top');\n panel.style.removeProperty('left');\n panel.style.removeProperty('right');\n panel.dataset.anchor = 'below';\n state.dropdownAnchorEl = trigger;\n // A structural rebuild (`rebuildShellDOM`) creates a FRESH `.tbw-tool-panel`\n // that is not in the top layer — only its `.open` class arm renders it, which\n // can be clipped / mis-stacked. Re-assert the popover idempotently so both the\n // non-structural path (panel already open → no-op via the catch) and the\n // structural-rebuild path (fresh panel → shown) end up in the top layer.\n if (panel.hasAttribute('popover') && typeof panel.showPopover === 'function') {\n try {\n panel.showPopover();\n } catch {\n // Already open or not connected — ignore.\n }\n }\n return true;\n}\n\n/**\n * Prepare shell state for a full re-render.\n * Runs cleanup functions so content (toolbar buttons, panels, headers)\n * can be restored to their original containers and re-rendered into new DOM.\n */\nexport function prepareForRerender(state: ShellState): void {\n // Run cleanups for toolbar contents (moves elements back to original containers)\n for (const cleanup of state.toolbarContentCleanups.values()) {\n cleanup();\n }\n state.toolbarContentCleanups.clear();\n\n // Run cleanups for panel contents (old DOM will be destroyed)\n for (const cleanup of state.panelCleanups.values()) {\n cleanup();\n }\n state.panelCleanups.clear();\n\n // Run cleanups for header contents (old DOM will be destroyed)\n for (const cleanup of state.headerContentCleanups.values()) {\n cleanup();\n }\n state.headerContentCleanups.clear();\n\n // Allow light DOM content to be re-moved into new DOM\n state.lightDomContentMoved = false;\n}\n\n/**\n * Cleanup all shell state when grid disconnects.\n */\nexport function cleanupShellState(state: ShellState): void {\n // Clean up header content\n for (const cleanup of state.headerContentCleanups.values()) {\n cleanup();\n }\n state.headerContentCleanups.clear();\n\n // Clean up panel content\n for (const cleanup of state.panelCleanups.values()) {\n cleanup();\n }\n state.panelCleanups.clear();\n\n // Clean up toolbar contents\n for (const cleanup of state.toolbarContentCleanups.values()) {\n cleanup();\n }\n state.toolbarContentCleanups.clear();\n\n // Call onDestroy for all toolbar contents\n for (const content of state.toolbarContents.values()) {\n content.onDestroy?.();\n }\n\n // Invoke onClose for all open panels\n if (state.isPanelOpen) {\n for (const sectionId of state.expandedSections) {\n const panel = state.toolPanels.get(sectionId);\n panel?.onClose?.();\n }\n }\n\n // Reset panel state\n state.isPanelOpen = false;\n state.expandedSections.clear();\n\n // Clear registrations\n state.toolPanels.clear();\n state.headerContents.clear();\n state.toolbarContents.clear();\n state.lightDomHeaderContent = [];\n\n // Clear light DOM tracking sets (allow re-parsing)\n state.lightDomToolPanelIds.clear();\n state.adapterBoundToolPanelIds.clear();\n state.lightDomToolbarContentIds.clear();\n\n // Reset move tracking flag (allow re-initialization)\n state.lightDomContentMoved = false;\n}\n// #endregion\n\n// #region Grid HTML Templates\n/**\n * Core grid content HTML template.\n * Uses faux scrollbar pattern for smooth virtualized scrolling.\n */\nexport const GRID_CONTENT_HTML = `\n <div class=\"tbw-scroll-area\">\n <div class=\"rows-body-wrapper\">\n <div class=\"rows-body\" role=\"grid\">\n <div class=\"header\" role=\"rowgroup\">\n <div class=\"header-row\" role=\"row\" part=\"header-row\"></div>\n </div>\n <div class=\"rows-container\" role=\"presentation\">\n <div class=\"rows-viewport\" role=\"presentation\">\n <div class=\"rows\"></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"faux-vscroll\">\n <div class=\"faux-vscroll-spacer\"></div>\n </div>\n <div class=\"tbw-sr-only\" aria-live=\"polite\" aria-atomic=\"true\"></div>\n`;\n// #endregion\n\n// #region DOM Construction\nimport { GridClasses } from '../../core/constants';\nimport {\n buildGridDOM,\n buildShellBody,\n buildShellHeader,\n type ShellBodyOptions,\n type ShellHeaderOptions,\n} from '../../core/internal/dom-builder';\n\n/**\n * Build ShellHeaderOptions and ShellBodyOptions from shell config and runtime state.\n * Shared by buildGridDOMIntoElement and rebuildShellDOM to avoid duplication.\n */\nfunction buildShellOptions(\n shellConfig: ShellConfig,\n runtimeState: { isPanelOpen: boolean; expandedSections: Set<string> },\n icons?: { toolPanel?: IconValue; expand?: IconValue; collapse?: IconValue },\n): { headerOptions: ShellHeaderOptions; bodyOptions: ShellBodyOptions } {\n const toolPanelIcon = icons?.toolPanel !== undefined ? iconToString(icons.toolPanel) : undefined;\n const expandIcon = icons?.expand !== undefined ? iconToString(icons.expand) : undefined;\n const collapseIcon = icons?.collapse !== undefined ? iconToString(icons.collapse) : undefined;\n\n const allContents = shellConfig.header?.toolbarContents ?? [];\n const sortedContents = [...allContents].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));\n\n const allPanels = shellConfig.toolPanels ?? [];\n const sortedPanels = [...allPanels].sort((a, b) => (a.order ?? 100) - (b.order ?? 100));\n\n return {\n headerOptions: {\n title: shellConfig.header?.title ?? undefined,\n hasPanels: sortedPanels.length > 0,\n isPanelOpen: runtimeState.isPanelOpen,\n toolPanelIcon,\n showToggle: shellConfig.header?.toolPanelToggle !== false && shellConfig.toolPanel?.locked !== true,\n configButtons: sortedContents.map((c) => ({\n id: c.id,\n hasElement: false,\n hasRender: !!c.render,\n })),\n apiButtons: [],\n },\n bodyOptions: {\n position: shellConfig.toolPanel?.position ?? 'right',\n mode: shellConfig.toolPanel?.mode ?? 'overlay',\n isPanelOpen: runtimeState.isPanelOpen,\n expandIcon,\n collapseIcon,\n // Render an in-panel close (✕) button when the header bar is hidden, so\n // the panel can be dismissed without the (absent) built-in toggle. A\n // locked panel cannot be closed, so no button in that case. Dropdown mode\n // is excluded: the popover light-dismisses on Esc / click-outside, so a\n // ✕ would be redundant clutter.\n showCloseButton:\n shellConfig.header?.visible === false &&\n shellConfig.toolPanel?.locked !== true &&\n (shellConfig.toolPanel?.mode ?? 'overlay') !== 'dropdown',\n panels: sortedPanels.map((p) => ({\n id: p.id,\n title: p.title,\n icon: iconToString(p.icon),\n isExpanded: runtimeState.expandedSections.has(p.id),\n })),\n },\n };\n}\n\n/**\n * Build the complete grid DOM structure using direct DOM construction.\n * This is 2-3x faster than innerHTML for initial render.\n *\n * @param renderRoot - The element to render into (will be cleared)\n * @param shellConfig - Shell configuration\n * @param runtimeState - Runtime shell state\n * @param icons - Optional icons\n * @returns Whether shell is active (for post-render setup)\n */\nexport function buildGridDOMIntoElement(\n renderRoot: Element,\n shellConfig: ShellConfig | undefined,\n runtimeState: { isPanelOpen: boolean; expandedSections: Set<string> },\n icons?: { toolPanel?: IconValue; expand?: IconValue; collapse?: IconValue },\n): boolean {\n const hasShell = shouldRenderShellHeader(shellConfig);\n\n // Preserve all direct child nodes EXCEPT the grid's own `.tbw-grid-root`\n // (which we are about to rebuild). This keeps user/framework light-DOM\n // intact in its original order — including non-element nodes like comment\n // anchors that Angular's `@if`/`@for` and other structural directives use\n // as positional markers. Filtering by element-tag selectors would discard\n // those anchors and break re-renders of the conditional content.\n const preservedNodes: Node[] = [];\n for (let n: Node | null = renderRoot.firstChild; n; n = n.nextSibling) {\n if (n.nodeType === 1 && (n as Element).classList.contains(GridClasses.ROOT)) continue;\n preservedNodes.push(n);\n }\n\n // Clear existing content (this would delete light DOM nodes, so we preserved them first)\n renderRoot.replaceChildren();\n\n // Re-append preserved nodes in original order (hidden elements are used for config).\n // IMPORTANT: These are prepended before .tbw-grid-root, so `renderRoot.children[0]`\n // is NOT the grid root. Use `querySelector('.tbw-grid-root')` instead.\n for (const n of preservedNodes) {\n renderRoot.appendChild(n);\n }\n\n if (hasShell) {\n const { headerOptions, bodyOptions } = buildShellOptions(shellConfig!, runtimeState, icons);\n const shellHeader = shouldRenderHeaderBar(shellConfig) ? buildShellHeader(headerOptions) : undefined;\n const shellBody = buildShellBody(bodyOptions);\n\n const fragment = buildGridDOM({\n hasShell: true,\n shellHeader,\n shellBody,\n });\n renderRoot.appendChild(fragment);\n } else {\n const fragment = buildGridDOM({ hasShell: false });\n renderRoot.appendChild(fragment);\n }\n\n return hasShell;\n}\n\n/**\n * Surgically rebuild only the shell wrapper (header + tool panel) while\n * preserving the existing `.tbw-grid-root` element and its `.tbw-grid-content`\n * child with all descendants and event listeners intact.\n *\n * This avoids the full `replaceChildren()` nuke that `buildGridDOMIntoElement`\n * performs, so event listeners bound to `.tbw-grid-root` or its grid content\n * descendants (e.g. tooltip's delegated mouseover) remain intact.\n *\n * If no existing `.tbw-grid-root` is found (first render), falls back to\n * `buildGridDOMIntoElement` for a full rebuild.\n */\nexport function rebuildShellDOM(\n renderRoot: Element,\n shellConfig: ShellConfig | undefined,\n runtimeState: { isPanelOpen: boolean; expandedSections: Set<string> },\n icons?: { toolPanel?: IconValue; expand?: IconValue; collapse?: IconValue },\n): boolean {\n // Find the existing grid root and content to preserve\n const existingRoot = renderRoot.querySelector(`.${GridClasses.ROOT}`) as HTMLElement | null;\n const existingContent = existingRoot?.querySelector('.tbw-grid-content');\n\n // If there's no existing root, fall back to full rebuild (first render)\n if (!existingRoot || !existingContent) {\n return buildGridDOMIntoElement(renderRoot, shellConfig, runtimeState, icons);\n }\n\n // Detach grid content so it survives the child replacement\n existingContent.remove();\n\n const hasShell = shouldRenderShellHeader(shellConfig);\n\n // Clear the root's children (shell header, shell body, etc.) but keep the root element itself\n existingRoot.replaceChildren();\n\n if (hasShell) {\n existingRoot.className = `${GridClasses.ROOT} has-shell`;\n\n const { headerOptions, bodyOptions } = buildShellOptions(shellConfig!, runtimeState, icons);\n const shellHeader = shouldRenderHeaderBar(shellConfig) ? buildShellHeader(headerOptions) : undefined;\n const shellBody = buildShellBody(bodyOptions);\n\n // Replace the freshly cloned grid content with the preserved one\n const freshContent = shellBody.querySelector('.tbw-grid-content');\n if (freshContent) {\n freshContent.replaceWith(existingContent);\n }\n\n if (shellHeader) existingRoot.appendChild(shellHeader);\n existingRoot.appendChild(shellBody);\n } else {\n // No shell — place content directly in root\n existingRoot.className = GridClasses.ROOT;\n existingRoot.appendChild(existingContent);\n }\n\n return hasShell;\n}\n// #endregion\n","/**\n * Shell Controller - Tool panel and header content orchestration.\n *\n * Extracted from shell.ts to isolate the stateful controller logic\n * from the rendering/parsing infrastructure.\n *\n * The ShellController manages:\n * - Tool panel open/close/toggle state\n * - Accordion section expand/collapse\n * - Registration of tool panels, header content, and toolbar content\n */\n\nimport {\n HEADER_CONTENT_DUPLICATE,\n NO_TOOL_PANELS,\n TOOL_PANEL_DUPLICATE,\n TOOL_PANEL_NOT_FOUND,\n TOOLBAR_CONTENT_DUPLICATE,\n warnDiagnostic,\n} from '../../core/internal/diagnostics';\nimport type { InternalGrid } from '../../core/types';\nimport {\n hideToolPanelDropdown,\n renderHeaderContent,\n renderPanelContent,\n repairDropdownAnchor,\n showToolPanelDropdown,\n supportsAnchorPositioning,\n updatePanelState,\n updateToolbarActiveStates,\n type ShellState,\n} from './shell';\nimport type {\n HeaderContentDefinition,\n OpenToolPanelOptions,\n ToolbarContentDefinition,\n ToolPanelDefinition,\n} from './types';\n\n// #region ShellController\n/**\n * Controller interface for managing shell/tool panel behavior.\n */\nexport interface ShellController {\n /** Whether the shell has been initialized */\n readonly isInitialized: boolean;\n /** Set the initialized state */\n setInitialized(value: boolean): void;\n /** Whether the tool panel is currently open */\n readonly isPanelOpen: boolean;\n /** Get IDs of expanded accordion sections */\n readonly expandedSections: string[];\n /**\n * Open the tool panel.\n * @param panelId - Optional ID of the section to expand on open. When provided,\n * takes precedence over `shell.toolPanel.defaultOpen`. If the panel is already\n * open with a different section expanded, switches to the requested section.\n * If the panel ID is not registered, a `TBW072` warning is emitted and the\n * call falls back to default behavior (auto-expand `defaultOpen` or first by order).\n * @param options - Optional open options. In `mode: 'dropdown'`, `options.anchor`\n * sets the element the popover anchors to (see {@link OpenToolPanelOptions}).\n */\n openToolPanel(panelId?: string, options?: OpenToolPanelOptions): void;\n /** Close the tool panel */\n closeToolPanel(): void;\n /**\n * Toggle the tool panel.\n * @param options - Optional open options forwarded to {@link openToolPanel}\n * when the panel is currently closed (e.g. `anchor` for dropdown mode).\n */\n toggleToolPanel(options?: OpenToolPanelOptions): void;\n /**\n * Re-pair an open dropdown popover onto its (possibly recreated) trigger\n * after a re-render (`mode: 'dropdown'` only). A column toggle / reorder may\n * recreate the custom column-header button the popover was anchored to,\n * leaving the popover's `position-anchor` dangling so the browser drops it to\n * the corner.\n *\n * Behaviour:\n * - intact pairing → no-op (cheap fast-path, safe to call every render);\n * - a live `[data-panel-toggle]` exists → re-pair the existing anchor onto it\n * IN PLACE (no popover re-show → no flash), or a fixed-coord re-show when\n * CSS anchor positioning is unavailable;\n * - corner-origin dropdown with no trigger → keep it pinned to the corner;\n * - trigger-origin dropdown whose trigger is transiently absent → leave the\n * popover at its last position (the shell's MutationObserver re-pairs it\n * before paint once the trigger reappears), rather than flashing to corner.\n *\n * No-op when the panel is closed or the mode is not `'dropdown'`.\n */\n reanchorOpenDropdown(): void;\n /** Toggle an accordion section */\n toggleToolPanelSection(sectionId: string): void;\n /** Get registered tool panels */\n getToolPanels(): ToolPanelDefinition[];\n /** Register a tool panel */\n registerToolPanel(panel: ToolPanelDefinition): void;\n /** Unregister a tool panel */\n unregisterToolPanel(panelId: string): void;\n /** Get registered header contents */\n getHeaderContents(): HeaderContentDefinition[];\n /** Register header content */\n registerHeaderContent(content: HeaderContentDefinition): void;\n /** Unregister header content */\n unregisterHeaderContent(contentId: string): void;\n /** Get all registered toolbar contents */\n getToolbarContents(): ToolbarContentDefinition[];\n /** Register toolbar content */\n registerToolbarContent(content: ToolbarContentDefinition): void;\n /** Unregister toolbar content */\n unregisterToolbarContent(contentId: string): void;\n}\n\n/**\n * Create a ShellController instance.\n * The controller encapsulates all tool panel orchestration logic.\n */\nexport function createShellController(state: ShellState, grid: InternalGrid): ShellController {\n let initialized = false;\n\n /**\n * Resolve the element a dropdown popover should anchor to. Priority:\n * explicit `anchor` option → built-in toolbar toggle button → grid host\n * corner fallback.\n */\n function resolveDropdownAnchor(explicit?: HTMLElement): { el: HTMLElement; corner: boolean } {\n if (explicit) return { el: explicit, corner: false };\n const toggle = grid._renderRoot.querySelector('[data-panel-toggle]') as HTMLElement | null;\n if (toggle) return { el: toggle, corner: false };\n return { el: grid._hostElement, corner: true };\n }\n\n const controller: ShellController = {\n get isInitialized() {\n return initialized;\n },\n setInitialized(value: boolean) {\n initialized = value;\n },\n\n get isPanelOpen() {\n return state.isPanelOpen;\n },\n\n get expandedSections() {\n return [...state.expandedSections];\n },\n\n openToolPanel(panelId?: string, options?: OpenToolPanelOptions) {\n if (state.toolPanels.size === 0) {\n warnDiagnostic(NO_TOOL_PANELS, 'No tool panels registered', grid.id);\n return;\n }\n\n // Validate explicit panelId; on unknown id, warn and fall back to default behavior.\n let targetSectionId: string | undefined;\n if (panelId !== undefined) {\n if (state.toolPanels.has(panelId)) {\n targetSectionId = panelId;\n } else {\n warnDiagnostic(TOOL_PANEL_NOT_FOUND, `Tool panel \"${panelId}\" not found`, grid.id);\n }\n }\n\n // Fast path: panel already open and requested section already expanded → no-op.\n if (state.isPanelOpen && targetSectionId && state.expandedSections.has(targetSectionId)) {\n return;\n }\n\n // Panel already open but a different section is expanded → switch sections\n // (delegate to toggleToolPanelSection for accordion exclusivity + event emission).\n if (state.isPanelOpen && targetSectionId && !state.expandedSections.has(targetSectionId)) {\n controller.toggleToolPanelSection(targetSectionId);\n return;\n }\n\n if (state.isPanelOpen) return;\n\n state.isPanelOpen = true;\n\n // Decide initial expanded section:\n // 1. Explicit panelId argument (already validated above).\n // 2. shell.toolPanel.defaultOpen if it matches a registered panel.\n // 3. First panel by `order`.\n if (state.expandedSections.size === 0 && state.toolPanels.size > 0) {\n if (targetSectionId) {\n state.expandedSections.add(targetSectionId);\n } else {\n const defaultOpenId = grid.effectiveConfig?.shell?.toolPanel?.defaultOpen;\n if (defaultOpenId && state.toolPanels.has(defaultOpenId)) {\n state.expandedSections.add(defaultOpenId);\n } else {\n const sortedPanels = [...state.toolPanels.values()].sort((a, b) => (a.order ?? 100) - (b.order ?? 100));\n const firstPanel = sortedPanels[0];\n if (firstPanel) {\n state.expandedSections.add(firstPanel.id);\n }\n }\n }\n } else if (targetSectionId && !state.expandedSections.has(targetSectionId)) {\n // Some other section was already pre-expanded (e.g. via config); switch to requested.\n for (const id of [...state.expandedSections]) {\n state.expandedSections.delete(id);\n }\n state.expandedSections.add(targetSectionId);\n }\n\n // Update UI\n const shadow = grid._renderRoot;\n updateToolbarActiveStates(shadow, state);\n updatePanelState(shadow, state);\n\n // Render accordion sections\n renderPanelContent(shadow, state, grid._accordionIcons);\n\n // In dropdown mode, show the panel as an anchored popover in the top layer.\n if (grid.effectiveConfig?.shell?.toolPanel?.mode === 'dropdown') {\n const { el, corner } = resolveDropdownAnchor(options?.anchor);\n showToolPanelDropdown(shadow, state, el, corner);\n }\n\n // Emit event\n grid._emit('tool-panel-open', { sections: controller.expandedSections });\n },\n\n closeToolPanel() {\n if (!state.isPanelOpen) return;\n // When the tool panel is locked open, programmatic and user close\n // requests become no-ops. Accordion sections can still toggle.\n if (grid.effectiveConfig?.shell?.toolPanel?.locked) return;\n\n // Clean up all panel content\n for (const cleanup of state.panelCleanups.values()) {\n cleanup();\n }\n state.panelCleanups.clear();\n\n // Call onClose for all panels\n for (const panel of state.toolPanels.values()) {\n panel.onClose?.();\n }\n\n state.isPanelOpen = false;\n\n // Update UI\n const shadow = grid._renderRoot;\n updateToolbarActiveStates(shadow, state);\n updatePanelState(shadow, state);\n\n // In dropdown mode, hide the popover and clear anchor wiring.\n if (grid.effectiveConfig?.shell?.toolPanel?.mode === 'dropdown') {\n hideToolPanelDropdown(shadow, state);\n }\n\n // Emit event\n grid._emit('tool-panel-close', {});\n },\n\n toggleToolPanel(options?: OpenToolPanelOptions) {\n if (state.isPanelOpen) {\n controller.closeToolPanel();\n } else {\n controller.openToolPanel(undefined, options);\n }\n },\n\n reanchorOpenDropdown() {\n if (!state.isPanelOpen) return;\n if (grid.effectiveConfig?.shell?.toolPanel?.mode !== 'dropdown') return;\n const stored = state.dropdownAnchorEl;\n // Idempotent fast-path: the anchor element is still connected AND still\n // carries the minted `anchor-name`, so the pairing is intact — nothing to\n // do. This keeps the hook cheap when it runs on the `afterRender` path\n // (which fires on every render, e.g. a column toggle), only doing real\n // work when the trigger was actually recreated/detached.\n if (\n stored &&\n stored.isConnected &&\n state.dropdownAnchorName &&\n stored.style.getPropertyValue('anchor-name') === state.dropdownAnchorName\n ) {\n return;\n }\n // Idempotent fast-path (fixed-coordinate fallback mode — CSS anchor\n // positioning unsupported, so `dropdownAnchorName` is never minted). Here\n // re-anchoring means a `getBoundingClientRect()` + re-show on every\n // render/mutation, which is wasteful when nothing was recreated. Skip when\n // the stored trigger is still the SAME connected node (not the corner\n // anchor) AND the CURRENT panel was already positioned (`data-anchor` set).\n // A structural rebuild produces a FRESH `.tbw-tool-panel` with no\n // `data-anchor`, so this never short-circuits the case that still needs a\n // re-show.\n if (!state.dropdownAnchorName && stored && stored.isConnected && stored !== grid._hostElement) {\n const panel = grid._renderRoot.querySelector('.tbw-tool-panel') as HTMLElement | null;\n if (panel?.dataset.anchor === 'below') return;\n }\n // Re-pair to a LIVE custom trigger (`[data-panel-toggle]`) whenever one\n // exists. Prefer the cheap in-place repair: it reuses the already-minted\n // `anchor-name`, keeps the popover shown, and never re-enters the Popover\n // API — so the popover stays visually anchored with NO flash. Falls back\n // to a fixed-coordinate re-show only when CSS anchor positioning is\n // unavailable.\n const fresh = grid._renderRoot.querySelector('[data-panel-toggle]') as HTMLElement | null;\n if (fresh && fresh !== grid._hostElement) {\n if (\n state.dropdownAnchorName &&\n supportsAnchorPositioning() &&\n repairDropdownAnchor(grid._renderRoot, state, fresh)\n ) {\n return;\n }\n showToolPanelDropdown(grid._renderRoot, state, fresh, false);\n return;\n }\n // No live trigger.\n if (stored === grid._hostElement) {\n // Corner-origin dropdown (opened without a custom trigger): keep it\n // pinned to the grid corner across re-renders.\n showToolPanelDropdown(grid._renderRoot, state, grid._hostElement, true);\n return;\n }\n // Trigger-origin dropdown whose trigger is transiently absent (an outer\n // framework, e.g. React, recreates it on a separate commit) — or was\n // permanently removed. Do NOT flash the popover to the corner: leave it\n // at its last position. When the trigger reappears, the shell's\n // MutationObserver re-pairs it before paint (see ShellPlugin).\n },\n\n toggleToolPanelSection(sectionId: string) {\n const panel = state.toolPanels.get(sectionId);\n if (!panel) {\n warnDiagnostic(TOOL_PANEL_NOT_FOUND, `Tool panel section \"${sectionId}\" not found`, grid.id);\n return;\n }\n\n // Don't allow toggling when there's only one panel (it should stay expanded)\n if (state.toolPanels.size === 1) {\n return;\n }\n\n const shadow = grid._renderRoot;\n const isExpanded = state.expandedSections.has(sectionId);\n\n if (isExpanded) {\n // Collapsing current section\n const cleanup = state.panelCleanups.get(sectionId);\n if (cleanup) {\n cleanup();\n state.panelCleanups.delete(sectionId);\n }\n panel.onClose?.();\n state.expandedSections.delete(sectionId);\n updateAccordionSectionState(shadow, sectionId, false);\n } else {\n // Expanding - first collapse all others (exclusive accordion)\n for (const [otherId, otherPanel] of state.toolPanels) {\n if (otherId !== sectionId && state.expandedSections.has(otherId)) {\n const cleanup = state.panelCleanups.get(otherId);\n if (cleanup) {\n cleanup();\n state.panelCleanups.delete(otherId);\n }\n otherPanel.onClose?.();\n state.expandedSections.delete(otherId);\n updateAccordionSectionState(shadow, otherId, false);\n // Clear content of collapsed section\n const contentEl = shadow.querySelector(`[data-section=\"${otherId}\"] .tbw-accordion-content`);\n if (contentEl) contentEl.innerHTML = '';\n }\n }\n // Now expand the target section\n state.expandedSections.add(sectionId);\n updateAccordionSectionState(shadow, sectionId, true);\n renderAccordionSectionContent(shadow, state, sectionId);\n }\n\n // Emit event\n grid._emit('tool-panel-section-toggle', { id: sectionId, expanded: !isExpanded });\n },\n\n getToolPanels() {\n return [...state.toolPanels.values()];\n },\n\n registerToolPanel(panel: ToolPanelDefinition) {\n if (state.toolPanels.has(panel.id)) {\n warnDiagnostic(TOOL_PANEL_DUPLICATE, `Tool panel \"${panel.id}\" already registered`, grid.id);\n return;\n }\n state.toolPanels.set(panel.id, panel);\n\n if (initialized) {\n grid.refreshShellHeader?.();\n }\n },\n\n unregisterToolPanel(panelId: string) {\n // Close panel if open and this section is expanded\n if (state.expandedSections.has(panelId)) {\n const cleanup = state.panelCleanups.get(panelId);\n if (cleanup) {\n cleanup();\n state.panelCleanups.delete(panelId);\n }\n state.expandedSections.delete(panelId);\n }\n\n state.toolPanels.delete(panelId);\n\n if (initialized) {\n grid.refreshShellHeader?.();\n }\n },\n\n getHeaderContents() {\n return [...state.headerContents.values()];\n },\n\n registerHeaderContent(content: HeaderContentDefinition) {\n if (state.headerContents.has(content.id)) {\n warnDiagnostic(HEADER_CONTENT_DUPLICATE, `Header content \"${content.id}\" already registered`, grid.id);\n return;\n }\n state.headerContents.set(content.id, content);\n\n if (initialized) {\n renderHeaderContent(grid._renderRoot, state);\n }\n },\n\n unregisterHeaderContent(contentId: string) {\n // Clean up\n const cleanup = state.headerContentCleanups.get(contentId);\n if (cleanup) {\n cleanup();\n state.headerContentCleanups.delete(contentId);\n }\n\n // Call onDestroy\n const content = state.headerContents.get(contentId);\n content?.onDestroy?.();\n\n state.headerContents.delete(contentId);\n\n // Remove DOM element\n const el = grid._renderRoot.querySelector(`[data-header-content=\"${contentId}\"]`);\n el?.remove();\n },\n\n getToolbarContents() {\n return [...state.toolbarContents.values()].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));\n },\n\n registerToolbarContent(content: ToolbarContentDefinition) {\n if (state.toolbarContents.has(content.id)) {\n warnDiagnostic(TOOLBAR_CONTENT_DUPLICATE, `Toolbar content \"${content.id}\" already registered`, grid.id);\n return;\n }\n state.toolbarContents.set(content.id, content);\n\n if (initialized) {\n grid.refreshShellHeader?.();\n }\n },\n\n unregisterToolbarContent(contentId: string) {\n // Clean up\n const cleanup = state.toolbarContentCleanups.get(contentId);\n if (cleanup) {\n cleanup();\n state.toolbarContentCleanups.delete(contentId);\n }\n\n // Call onDestroy if defined\n const content = state.toolbarContents.get(contentId);\n if (content?.onDestroy) {\n content.onDestroy();\n }\n\n state.toolbarContents.delete(contentId);\n\n if (initialized) {\n grid.refreshShellHeader?.();\n }\n },\n };\n\n return controller;\n}\n\n/**\n * Update accordion section visual state.\n */\nfunction updateAccordionSectionState(renderRoot: Element, sectionId: string, expanded: boolean): void {\n const section = renderRoot.querySelector(`[data-section=\"${sectionId}\"]`);\n if (section) {\n section.classList.toggle('expanded', expanded);\n }\n}\n\n/**\n * Render content for a single accordion section.\n */\nfunction renderAccordionSectionContent(renderRoot: Element, state: ShellState, sectionId: string): void {\n const panel = state.toolPanels.get(sectionId);\n if (!panel?.render) return;\n\n const contentEl = renderRoot.querySelector(`[data-section=\"${sectionId}\"] .tbw-accordion-content`);\n if (!contentEl) return;\n\n const cleanup = panel.render(contentEl as HTMLElement);\n if (cleanup) {\n state.panelCleanups.set(sectionId, cleanup);\n }\n}\n// #endregion\n","/**\n * Shell Plugin (Class-based)\n *\n * Owns the grid shell: header bar, toolbar content, and tool panels.\n *\n * **Extraction #370 — Phase 1b (ownership inversion):** this plugin now owns\n * the shell **state** for the grid's lifetime (Approach A: state created once,\n * survives plugin re-inits, torn down on disconnect). Light-DOM parsing,\n * per-render work, and DOM construction move here in the following increments;\n * until each lands, core still drives that part via the resolved plugin\n * instance.\n *\n * @module Plugins/Shell\n */\n\nimport { BaseGridPlugin, type GridElement } from '../../core/plugin/base-plugin';\nimport type { GridConfig, InternalGrid } from '../../core/types';\nimport {\n cleanupShellState,\n createShellState,\n parseLightDomShell,\n parseLightDomToolButtons,\n parseLightDomToolPanels,\n prepareForRerender,\n rebuildShellDOM,\n renderCustomToolbarContents,\n renderHeaderContent,\n renderPanelContent,\n setupClickOutsideDismiss,\n setupEscapeDismiss,\n setupShellEventListeners,\n setupToolPanelResize,\n shouldRenderShellHeader,\n updatePanelState,\n updateToolbarActiveStates,\n type ShellState,\n} from './shell';\nimport { createShellController, type ShellController } from './shell-controller';\nimport styles from './shell.css?inline';\nimport './types';\nimport type {\n HeaderContentDefinition,\n OpenToolPanelOptions,\n ShellConfig,\n ToolbarContentDefinition,\n ToolPanelDefinition,\n} from './types';\n\n/**\n * Shell plugin for `<tbw-grid>`.\n *\n * @see {@link ShellConfig} for all configuration options.\n *\n * @internal Extends BaseGridPlugin.\n * @since 2.x (extraction #370)\n */\nexport class ShellPlugin extends BaseGridPlugin<ShellConfig> {\n /** @internal */\n readonly name = 'shell';\n /** @internal */\n override readonly styles = styles;\n\n // Extraction #370 — Approach A: the plugin owns the shell state for the\n // grid's lifetime. State is created once (via `ensureState`, lazily on first\n // access) and survives plugin re-inits (idempotent attach / non-destructive\n // detach); it is torn down only on grid disconnect via `disposeShellState()`.\n #state?: ShellState;\n #controller?: ShellController;\n\n // Document-level listeners the plugin owns for the shell's interactive\n // affordances. Each `#setupListeners` call cleans the previous registration\n // before re-adding (idempotent across re-wraps); torn down on disconnect.\n #resizeCleanup?: () => void;\n #clickOutsideCleanup?: () => void;\n #escapeDismissCleanup?: () => void;\n\n // The light-DOM shell-change handler (re-parses <tbw-grid-header> /\n // <tbw-grid-tool-buttons> / <tbw-grid-tool-panel> when the framework projects\n // them asynchronously). Registered in `attach` via the generic\n // `_registerLightDomHandler` seam; unregistered on disconnect.\n #shellChangeHandler?: () => void;\n\n // While a dropdown tool panel is open, this observer watches the render root\n // for the trigger being recreated by an outer framework (e.g. a React column\n // `headerRenderer` re-running on a column toggle) and re-pairs the popover's\n // anchor. MutationObserver callbacks run as a pre-paint microtask, so the\n // re-pair lands BEFORE the browser paints the dangling-anchor frame — no\n // flash. It also fires for every toggle (not a coalesced single rAF), so the\n // dropdown no longer intermittently disappears. Connected lazily while a\n // dropdown is open; disconnected on close and on grid disconnect.\n #reanchorObserver?: MutationObserver;\n #reanchorObserving = false;\n\n /** @internal Whether shell state has been created yet. */\n get hasState(): boolean {\n return this.#state !== undefined;\n }\n\n /**\n * @internal Create the canonical shell state + controller (idempotent).\n * The plugin owns shell-state construction; core only supplies the grid\n * reference (the controller binds to it for UI updates). No-op once state\n * exists, so it is safe to call on every resolve — including before the\n * plugin formally attaches, which keeps pre-connect API calls working.\n */\n ensureState(grid: InternalGrid): void {\n if (this.#state) return;\n const state = createShellState();\n this.#state = state;\n this.#controller = createShellController(state, grid);\n }\n\n /** @internal Canonical shell state. */\n get shellState(): ShellState {\n return this.#state as ShellState;\n }\n\n /** @internal Shell controller. */\n get shellController(): ShellController {\n return this.#controller as ShellController;\n }\n\n // #region Public Shell API\n // The shell plugin's public, programmatic API. Obtain the instance via\n // `grid.getPluginByName('shell')`. These mirror the (now `@deprecated`)\n // `<tbw-grid>` delegate methods, which forward here for the v2.x window and\n // are removed in v3 (extraction #370). Each guards on `hasState` so a call\n // before the shell has initialized is a safe no-op.\n\n /** Whether the tool panel sidebar is currently open. */\n get isToolPanelOpen(): boolean {\n return this.hasState ? this.shellController.isPanelOpen : false;\n }\n\n /** IDs of the currently expanded accordion sections in the tool panel. */\n get expandedToolPanelSections(): string[] {\n return this.hasState ? this.shellController.expandedSections : [];\n }\n\n /**\n * Open the tool panel sidebar.\n * @param panelId - Optional section to expand on open (see {@link ShellController.openToolPanel}).\n * @param options - Optional open options. In `mode: 'dropdown'`, `options.anchor`\n * sets the element the popover anchors to (see {@link OpenToolPanelOptions}).\n */\n openToolPanel(panelId?: string, options?: OpenToolPanelOptions): void {\n this.shellController?.openToolPanel(panelId, options);\n }\n\n /** Close the tool panel sidebar. */\n closeToolPanel(): void {\n this.shellController?.closeToolPanel();\n }\n\n /**\n * Toggle the tool panel sidebar open or closed.\n * @param options - Optional open options forwarded when opening (e.g. `anchor`\n * for dropdown mode; see {@link OpenToolPanelOptions}).\n */\n toggleToolPanel(options?: OpenToolPanelOptions): void {\n this.shellController?.toggleToolPanel(options);\n }\n\n /** Toggle an accordion section expanded or collapsed within the tool panel. */\n toggleToolPanelSection(sectionId: string): void {\n this.shellController?.toggleToolPanelSection(sectionId);\n }\n\n /** Get all registered tool panel definitions. */\n getToolPanels(): ToolPanelDefinition[] {\n return this.hasState ? this.shellController.getToolPanels() : [];\n }\n\n /** Register a custom tool panel section. */\n registerToolPanel(panel: ToolPanelDefinition): void {\n if (!this.hasState) return;\n this.shellState.apiToolPanelIds.add(panel.id);\n this.shellController.registerToolPanel(panel);\n }\n\n /** Unregister a custom tool panel section. */\n unregisterToolPanel(panelId: string): void {\n if (!this.hasState) return;\n this.shellState.apiToolPanelIds.delete(panelId);\n this.shellController.unregisterToolPanel(panelId);\n }\n\n /** Get all registered header content definitions. */\n getHeaderContents(): HeaderContentDefinition[] {\n return this.hasState ? this.shellController.getHeaderContents() : [];\n }\n\n /** Register custom header content (rendered in the shell header bar). */\n registerHeaderContent(content: HeaderContentDefinition): void {\n if (!this.hasState) return;\n this.shellState.apiHeaderContentIds.add(content.id);\n this.shellController.registerHeaderContent(content);\n }\n\n /** Unregister custom header content. */\n unregisterHeaderContent(contentId: string): void {\n if (!this.hasState) return;\n this.shellState.apiHeaderContentIds.delete(contentId);\n this.shellController.unregisterHeaderContent(contentId);\n }\n\n /** Get all registered toolbar content definitions. */\n getToolbarContents(): ToolbarContentDefinition[] {\n return this.hasState ? this.shellController.getToolbarContents() : [];\n }\n\n /** Register custom toolbar content (rendered in the shell toolbar). */\n registerToolbarContent(content: ToolbarContentDefinition): void {\n this.shellController?.registerToolbarContent(content);\n }\n\n /** Unregister custom toolbar content. */\n unregisterToolbarContent(contentId: string): void {\n this.shellController?.unregisterToolbarContent(contentId);\n }\n // #endregion\n\n /** @internal */\n override attach(grid: GridElement): void {\n super.attach(grid);\n // Register the light-DOM shell handlers via the generic seam. Core holds no\n // shell tag knowledge (extraction #370). `registerLightDomHandler` is keyed\n // by tag name, so re-attach simply overwrites — idempotent. Torn down on\n // grid disconnect in `disposeShellState` (Approach A: survives re-inits).\n const handler = (): void => this.#handleLightDomShellChange(grid);\n this.#shellChangeHandler = handler;\n grid._registerLightDomHandler('tbw-grid-header', handler);\n grid._registerLightDomHandler('tbw-grid-tool-buttons', handler);\n grid._registerLightDomHandler('tbw-grid-tool-panel', handler);\n }\n\n /** @internal Parse all light-DOM shell elements into the plugin's state. */\n #parseLightDom(grid: GridElement): void {\n const host = grid._hostElement;\n parseLightDomShell(host, this.shellState);\n parseLightDomToolButtons(host, this.shellState);\n parseLightDomToolPanels(host, this.shellState, grid._getToolPanelRendererFactory());\n }\n\n /**\n * @internal React to a light-DOM shell element being added/removed/mutated\n * (e.g. a framework projecting `<tbw-grid-header title>` asynchronously).\n * Re-parses, and if a title or tool-buttons container newly appeared, re-merges\n * the config (so `processConfig` folds it in) and re-wraps the shell header.\n */\n #handleLightDomShellChange(grid: GridElement): void {\n if (!this.hasState) return;\n const state = this.shellState;\n const hadTitle = state.lightDomTitle;\n const hadToolButtons = state.hasToolButtonsContainer;\n this.#parseLightDom(grid);\n const hasTitle = state.lightDomTitle;\n const hasToolButtons = state.hasToolButtonsContainer;\n\n if ((hasTitle && !hadTitle) || (hasToolButtons && !hadToolButtons)) {\n grid._requestConfigMerge();\n // Re-wrap the shell header to reflect the new title / tool buttons.\n // `afterStructuralRender` rebuilds the chrome AND re-renders all shell\n // contents (header content, toolbar slots, panel), so no manual render is\n // needed here.\n if (grid._renderRoot.querySelector('.tbw-shell-header')) {\n prepareForRerender(state);\n this.afterStructuralRender();\n }\n }\n }\n\n // #region Core transitional bridge (extraction #370, Task 1b — removed at v3)\n // Thin instance methods that let core orchestrate shell light-DOM parsing and\n // re-render WITHOUT a static import of the shell pure functions (the old\n // `TEMP-SHELL-IMPORT-1b` module import). Core invokes them through\n // `#resolveShellPlugin()`, the same transitional bridge used by `#shellState`\n // and the deprecated delegate shims. Deleted once core's merge/render\n // orchestration is fully inverted into hooks (and with the rest at v3).\n\n /** @internal Parse `<tbw-grid-header>` + `<tbw-grid-tool-buttons>` into shell state. */\n _parseLightDomHeader(grid: GridElement): void {\n const host = grid._hostElement;\n parseLightDomShell(host, this.shellState);\n parseLightDomToolButtons(host, this.shellState);\n }\n\n /** @internal Parse `<tbw-grid-tool-panel>` elements into shell state. */\n _parseLightDomToolPanels(grid: GridElement): void {\n parseLightDomToolPanels(grid._hostElement, this.shellState, grid._getToolPanelRendererFactory());\n }\n\n /** @internal Parse all light-DOM shell elements (header + tool buttons + panels). */\n _parseLightDomAll(grid: GridElement): void {\n this._parseLightDomHeader(grid);\n this._parseLightDomToolPanels(grid);\n }\n\n /** @internal Move toolbar buttons back to their original container ahead of a re-render. */\n _prepareForRerender(): void {\n if (!this.hasState) return;\n prepareForRerender(this.shellState);\n }\n\n /** @internal Re-render any open tool-panel sections cleared during plugin re-init (idempotent). */\n _renderOpenPanelContent(grid: GridElement): void {\n if (!this.hasState) return;\n renderPanelContent(grid._renderRoot, this.shellState);\n }\n\n /** @internal Whether the given shell config should render a shell header. */\n _shouldRenderHeader(shellConfig: ShellConfig | undefined): boolean {\n return shouldRenderShellHeader(shellConfig);\n }\n // #endregion\n\n /**\n * @internal Fold the plugin's shell state into the effective config.\n *\n * Called by core's config merge ({@link ConfigManager.merge}) via the neutral\n * `processConfig` plugin hook \\u2014 core holds no shell-config knowledge. Mutates\n * `config.shell` in place from light-DOM-parsed + API-registered shell state\n * (title, header content, tool buttons, tool panels, header/toolbar contents).\n *\n * Extraction #370, Task 1b \\u2014 config ownership inversion: replaces core's old\n * `ConfigManager.#mergeShellConfig`.\n */\n override processConfig(config: GridConfig): void {\n if (!this.hasState) return;\n const state = this.shellState;\n\n // Constructor-supplied shell config is the base layer: the canonical\n // `features: { shell: ... }` opt-in passes the config value here (and an\n // explicit `plugins: [new ShellPlugin(cfg)]` does too). An explicit\n // top-level `gridConfig.shell` overrides it; parsed light-DOM / API state\n // folds on top below. Empty for the auto-registered instance\n // (`new ShellPlugin()`), so existing default-on behavior is unchanged.\n const ctor = this.resolvedConfig;\n\n // Clone shell hierarchy to avoid mutating the original gridConfig.\n config.shell = { ...ctor, ...config.shell };\n config.shell.header = { ...ctor.header, ...config.shell.header };\n\n // Light DOM title (does not override an explicit config title).\n if (state.lightDomTitle && !config.shell.header.title) {\n config.shell.header.title = state.lightDomTitle;\n }\n\n // Light DOM header content elements.\n if (state.lightDomHeaderContent?.length > 0) {\n config.shell.header.lightDomContent = state.lightDomHeaderContent;\n }\n\n // Tool-buttons container presence.\n if (state.hasToolButtonsContainer) {\n config.shell.header.hasToolButtonsContainer = true;\n }\n\n // Tool panels (from plugins + API + Light DOM), sorted by order.\n if (state.toolPanels.size > 0) {\n const panels = Array.from(state.toolPanels.values());\n panels.sort((a, b) => (a.order ?? 100) - (b.order ?? 100));\n config.shell.toolPanels = panels;\n }\n\n // Header contents (from plugins + API), sorted by order.\n if (state.headerContents.size > 0) {\n const contents = Array.from(state.headerContents.values());\n contents.sort((a, b) => (a.order ?? 100) - (b.order ?? 100));\n config.shell.headerContents = contents;\n }\n\n // Toolbar contents: merge ORIGINAL config contents (from the source\n // gridConfig, not a previous merge) with API-registered contents so stale\n // API entries never accumulate. Config takes precedence by id.\n const apiContents = Array.from(state.toolbarContents.values());\n const originalConfigContents = this.grid?._sourceConfig?.shell?.header?.toolbarContents ?? [];\n const configIds = new Set(originalConfigContents.map((c) => c.id));\n const mergedContents = [...originalConfigContents];\n for (const content of apiContents) {\n if (!configIds.has(content.id)) {\n mergedContents.push(content);\n }\n }\n mergedContents.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));\n config.shell.header.toolbarContents = mergedContents;\n }\n\n /**\n * @internal Wrap the freshly built (bare) grid DOM in shell chrome.\n *\n * Fired synchronously by core at the end of every structural (re)build, before\n * paint. Core builds a BARE grid; this hook relocates the existing\n * `.tbw-grid-content` node into shell chrome (header bar, toolbar, tool panel)\n * via {@link rebuildShellDOM}, which preserves the content subtree (and all its\n * listeners / cached refs) intact. No-op when no shell is configured.\n *\n * Extraction #370, Task 1b — DOM ownership inversion: the shell DOM is now\n * constructed by the plugin, not core.\n */\n override afterStructuralRender(): void {\n const grid = this.grid;\n if (!grid || !this.hasState) return;\n const state = this.shellState;\n const hasShell = rebuildShellDOM(\n grid._renderRoot,\n grid.effectiveConfig?.shell,\n { isPanelOpen: state.isPanelOpen, expandedSections: state.expandedSections },\n grid.effectiveConfig?.icons,\n );\n if (hasShell) {\n this.#setupListeners(grid);\n this.shellController.setInitialized(true);\n this.#renderShellContents(grid);\n }\n }\n\n /**\n * @internal Re-anchor an open dropdown tool panel after a NON-structural\n * render.\n *\n * A column-visibility toggle (or reorder) re-renders the grid header WITHOUT\n * a full structural rebuild — it fires `afterRender`, not\n * `afterStructuralRender`. When the column-chooser trigger lives in the grid\n * header (e.g. a custom `[data-panel-toggle]` button in a column\n * `headerRenderer`), that re-render recreates the trigger node, so it loses\n * the `anchor-name` that paired it with the open popover. The popover then\n * collapses to the viewport / grid corner.\n *\n * Two cooperating mechanisms keep the popover anchored with NO flash:\n * 1. A synchronous {@link ShellController.reanchorOpenDropdown} here catches\n * the case where the trigger was recreated synchronously during this\n * render (in-place re-pair, before paint).\n * 2. A {@link MutationObserver} (started here, kept running while the panel is\n * open) catches the case where an outer framework commits the replacement\n * trigger on a LATER task. Its callback runs as a pre-paint microtask, so\n * the re-pair lands before the dangling-anchor frame is painted.\n *\n * `reanchorOpenDropdown()` is idempotent and cheap (early-returns unless a\n * dropdown is open with a broken anchor), so running it on every render and\n * every mutation is safe.\n */\n override afterRender(): void {\n const grid = this.grid;\n if (!grid || !this.hasState) return;\n if (!this.shellState.isPanelOpen) {\n this.#stopReanchorObserver();\n return;\n }\n this.shellController.reanchorOpenDropdown();\n this.#startReanchorObserver(grid);\n }\n\n /** @internal Start (idempotently) the dropdown re-anchor observer. */\n #startReanchorObserver(grid: GridElement): void {\n // Only dropdown mode needs the observer. If the mode changed away from\n // 'dropdown' while the panel is still open, tear down any observer started\n // by an earlier render instead of leaving it watching the render root.\n if (grid.effectiveConfig?.shell?.toolPanel?.mode !== 'dropdown') {\n this.#stopReanchorObserver();\n return;\n }\n if (typeof MutationObserver === 'undefined') return;\n if (!this.#reanchorObserver) {\n this.#reanchorObserver = new MutationObserver(() => {\n if (!this.grid || !this.hasState || !this.shellState.isPanelOpen) {\n this.#stopReanchorObserver();\n return;\n }\n this.shellController.reanchorOpenDropdown();\n });\n }\n if (!this.#reanchorObserving) {\n this.#reanchorObserver.observe(grid._renderRoot, { childList: true, subtree: true });\n this.#reanchorObserving = true;\n }\n }\n\n /** @internal Stop the dropdown re-anchor observer if running. */\n #stopReanchorObserver(): void {\n if (this.#reanchorObserver && this.#reanchorObserving) {\n this.#reanchorObserver.disconnect();\n this.#reanchorObserving = false;\n }\n }\n\n /**\n * @internal Render all shell contents into the freshly built shell chrome.\n *\n * Runs from {@link afterStructuralRender} after the chrome is wrapped and\n * listeners are attached. Renders plugin header content + custom toolbar\n * slots, applies the configured default-open / legacy open-on-load behavior,\n * and restores an already-open panel's content. All render functions operate\n * on the shell DOM (queried from `_renderRoot`), not on the grid-body refs,\n * so they are safe to run before `#afterConnect` caches those.\n *\n * Extraction #370, Task 1b (Step 1b.1.3) — replaces core's per-render shell\n * content block in `#afterConnect` / `#afterShellRefresh`.\n */\n #renderShellContents(grid: GridElement): void {\n const state = this.shellState;\n const renderRoot = grid._renderRoot;\n const shellConfig = grid.effectiveConfig?.shell;\n\n // Render plugin header content + custom toolbar contents (render modes).\n renderHeaderContent(renderRoot, state);\n renderCustomToolbarContents(renderRoot, shellConfig, state);\n\n // Pre-expand the configured default section.\n const toolPanelCfg = shellConfig?.toolPanel;\n const defaultOpen = toolPanelCfg?.defaultOpen;\n if (defaultOpen && state.toolPanels.has(defaultOpen)) {\n state.expandedSections.add(defaultOpen);\n }\n\n // Decide whether the sidebar should be open on load.\n // NOTE: In v2.x `defaultOpen` alone also opens the sidebar (legacy\n // behavior). v3.0.0 will drop that — see issue #259 and the\n // @deprecated note on ToolPanelConfig.defaultOpen.\n // Search marker: TOOLPANEL-OPEN-LEGACY-259\n const shouldOpenOnLoad =\n toolPanelCfg?.locked === true ||\n toolPanelCfg?.initialState === 'open' ||\n // Legacy v2 path — remove in v3.0.0 (#259)\n (toolPanelCfg?.initialState === undefined && defaultOpen !== undefined && state.toolPanels.has(defaultOpen));\n if (!state.isPanelOpen && state.toolPanels.size > 0 && shouldOpenOnLoad) {\n this.openToolPanel();\n }\n\n // Restore panel content if the panel was already open (e.g. after a position\n // change re-render or a plugin re-init that triggered a shell refresh).\n if (state.isPanelOpen) {\n updatePanelState(renderRoot, state);\n renderPanelContent(renderRoot, state, {\n expand: grid.effectiveConfig?.icons?.expand,\n collapse: grid.effectiveConfig?.icons?.collapse,\n });\n updateToolbarActiveStates(renderRoot, state);\n // In dropdown mode `rebuildShellDOM` created a fresh `.tbw-tool-panel`, so\n // the popover must be re-shown in the top layer and re-anchored — the\n // trigger may have been recreated by the same re-render (e.g. a custom\n // column-header button after a column toggle/reorder). Otherwise the\n // popover loses its anchor and collapses to the viewport corner.\n this.shellController.reanchorOpenDropdown();\n }\n }\n\n /**\n * @internal Attach shell event + dismiss/resize listeners.\n *\n * Cleans any prior document-level registrations before re-adding, so it is\n * safe to call on every re-wrap (initial render, shell refresh, plugin\n * re-init) without leaking or double-attaching listeners.\n */\n #setupListeners(grid: GridElement): void {\n const renderRoot = grid._renderRoot;\n const shellConfig = grid.effectiveConfig?.shell;\n const state = this.shellState;\n\n setupShellEventListeners(renderRoot, shellConfig, state, {\n onPanelToggle: () => this.toggleToolPanel(),\n onSectionToggle: (sectionId: string) => this.toggleToolPanelSection(sectionId),\n onPanelClose: () => this.closeToolPanel(),\n });\n\n // Tool panel resize handle — persists the new width as a CSS variable on\n // the host element.\n this.#resizeCleanup?.();\n this.#resizeCleanup = setupToolPanelResize(renderRoot, shellConfig, (width: number) => {\n grid._hostElement.style.setProperty('--tbw-tool-panel-width', `${width}px`);\n });\n\n // Click-outside dismiss for an open overlay tool panel.\n this.#clickOutsideCleanup?.();\n this.#clickOutsideCleanup = setupClickOutsideDismiss(grid._hostElement, shellConfig, state, () =>\n this.closeToolPanel(),\n );\n\n // Escape-to-close dismiss for overlay tool panels.\n this.#escapeDismissCleanup?.();\n this.#escapeDismissCleanup = setupEscapeDismiss(shellConfig, state, () => this.closeToolPanel());\n }\n\n /** @internal Remove the document-level shell listeners. */\n #teardownListeners(): void {\n this.#resizeCleanup?.();\n this.#resizeCleanup = undefined;\n this.#clickOutsideCleanup?.();\n this.#clickOutsideCleanup = undefined;\n this.#escapeDismissCleanup?.();\n this.#escapeDismissCleanup = undefined;\n }\n\n /** @internal */\n override detach(): void {\n // Non-destructive detach (Approach A): shell state and document listeners\n // survive plugin re-inits. `#setupListeners` cleans-before-add on the next\n // re-wrap, so there is nothing to tear down here. Real teardown happens in\n // `disposeShellState()` on grid disconnect.\n super.detach();\n }\n\n /**\n * @internal Tear down shell state on grid disconnect (Approach A).\n * Idempotent and harmless when no state exists.\n */\n disposeShellState(): void {\n this.#teardownListeners();\n this.#stopReanchorObserver();\n this.#reanchorObserver = undefined;\n // Unregister the light-DOM shell handlers (registered in `attach`). `detach`\n // does not clear `this.grid`, so the reference is still valid here.\n const grid = this.grid as GridElement | undefined;\n if (grid && this.#shellChangeHandler) {\n grid._unregisterLightDomHandler('tbw-grid-header');\n grid._unregisterLightDomHandler('tbw-grid-tool-buttons');\n grid._unregisterLightDomHandler('tbw-grid-tool-panel');\n this.#shellChangeHandler = undefined;\n }\n if (this.#state) {\n cleanupShellState(this.#state);\n this.#controller?.setInitialized(false);\n }\n }\n}\n"],"names":["iconToString","icon","sanitizeHTML","outerHTML","shouldRenderShellHeader","config","header","title","toolbarContents","length","toolPanels","headerContents","lightDomContent","hasToolButtonsContainer","shouldRenderHeaderBar","visible","parseLightDomShell","host","state","headerEl","querySelector","lightDomTitle","getAttribute","querySelectorAll","lightDomHeaderContent","Array","from","style","display","parseLightDomToolButtons","rendererFactory","toolButtonsContainer","id","lightDomToolbarContentIds","has","contentDef","order","render","target","firstChild","appendChild","set","add","parseLightDomToolPanels","forEach","element","panelEl","warnDiagnostic","TOOL_PANEL_MISSING_ATTR","tooltip","parseInt","adapterRenderer","content","innerHTML","trim","container","wrapper","document","createElement","remove","existingPanel","get","lightDomToolPanelIds","TOOL_PANEL_DUPLICATE","firstAdapterAttach","adapterBoundToolPanelIds","attrsChanged","cleanup","panelCleanups","delete","panel","isInGrid","node","gridElement","contains","root","getRootNode","ShadowRoot","renderHeaderContent","renderRoot","hasLightDomContent","lightDomContentMoved","hasPluginContent","size","contentArea","el","sortedContents","values","sort","a","b","existingCleanup","headerContentCleanups","setAttribute","renderPanelContent","_icons","isPanelOpen","panelId","isExpanded","expandedSections","section","classList","toggle","String","children","updateToolbarActiveStates","panelToggle","updatePanelState","width","DROPDOWN_ANCHOR_NAME","dropdownAnchorSeq","supportsAnchorPositioning","CSS","supports","showToolPanelDropdown","anchorEl","corner","dropdownAnchorEl","dataset","anchor","hasAttribute","showPopover","anchorName","dropdownAnchorName","setProperty","removeProperty","rect","getBoundingClientRect","position","top","Math","round","bottom","left","right","viewportWidth","window","innerWidth","positionDropdownFallback","prepareForRerender","toolbarContentCleanups","clear","buildShellOptions","shellConfig","runtimeState","icons","toolPanelIcon","toolPanel","expandIcon","expand","collapseIcon","collapse","sortedPanels","headerOptions","hasPanels","showToggle","toolPanelToggle","locked","configButtons","map","c","hasElement","hasRender","apiButtons","bodyOptions","mode","showCloseButton","panels","p","rebuildShellDOM","existingRoot","GridClasses","ROOT","existingContent","hasShell","preservedNodes","n","nextSibling","nodeType","push","replaceChildren","shellHeader","buildShellHeader","shellBody","buildShellBody","fragment","buildGridDOM","buildGridDOMIntoElement","className","freshContent","replaceWith","createShellController","grid","initialized","controller","isInitialized","setInitialized","value","openToolPanel","options","NO_TOOL_PANELS","targetSectionId","TOOL_PANEL_NOT_FOUND","toggleToolPanelSection","defaultOpenId","effectiveConfig","shell","defaultOpen","firstPanel","shadow","_renderRoot","_accordionIcons","explicit","_hostElement","resolveDropdownAnchor","_emit","sections","closeToolPanel","onClose","hidePopover","hideToolPanelDropdown","toggleToolPanel","reanchorOpenDropdown","stored","isConnected","getPropertyValue","fresh","trigger","prev","repairDropdownAnchor","sectionId","updateAccordionSectionState","otherId","otherPanel","contentEl","renderAccordionSectionContent","expanded","getToolPanels","registerToolPanel","refreshShellHeader","unregisterToolPanel","getHeaderContents","registerHeaderContent","HEADER_CONTENT_DUPLICATE","unregisterHeaderContent","contentId","onDestroy","getToolbarContents","registerToolbarContent","TOOLBAR_CONTENT_DUPLICATE","unregisterToolbarContent","ShellPlugin","BaseGridPlugin","name","styles","resizeCleanup","clickOutsideCleanup","escapeDismissCleanup","shellChangeHandler","reanchorObserver","reanchorObserving","hasState","this","ensureState","Map","Set","apiToolPanelIds","apiHeaderContentIds","shellState","shellController","isToolPanelOpen","expandedToolPanelSections","attach","super","handler","handleLightDomShellChange","_registerLightDomHandler","parseLightDom","_getToolPanelRendererFactory","hadTitle","hadToolButtons","hasTitle","hasToolButtons","_requestConfigMerge","afterStructuralRender","_parseLightDomHeader","_parseLightDomToolPanels","_parseLightDomAll","_prepareForRerender","_renderOpenPanelContent","_shouldRenderHeader","processConfig","ctor","resolvedConfig","contents","apiContents","originalConfigContents","_sourceConfig","configIds","mergedContents","setupListeners","renderShellContents","afterRender","startReanchorObserver","stopReanchorObserver","MutationObserver","observe","childList","subtree","disconnect","configContents","stateContents","allContents","slot","renderCustomToolbarContents","toolPanelCfg","shouldOpenOnLoad","initialState","callbacks","toolbar","addEventListener","e","closest","onPanelToggle","onPanelClose","accordion","onSectionToggle","setupShellEventListeners","onResize","handle","startX","startWidth","maxWidth","isResizing","onMouseMove","preventDefault","delta","clientX","newWidth","min","max","onMouseUp","transition","body","cursor","userSelect","finalWidth","removeEventListener","onMouseDown","setupToolPanelResize","closeOnClickOutside","composedPath","Element","matches","setupClickOutsideDismiss","key","defaultPrevented","setupEscapeDismiss","teardownListeners","detach","disposeShellState","_unregisterLightDomHandler","cleanupShellState"],"mappings":"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"}
|