@kerfjs/ui 5.0.0-beta.54 → 5.0.0-beta.55
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/README.md +3 -2
- package/ai/component-catalog-v2.json +58 -1
- package/ai/component-catalog.json +105 -8
- package/ai/public-api-signatures-v1.md +265 -28
- package/ai/skill.md +6 -4
- package/ai/webawesome-jsx-signatures-v1.md +1 -1
- package/dist/browser/popup-menu.js +3 -0
- package/dist/catalog-resources.d.ts +1 -1
- package/dist/catalog.d.ts +10 -4
- package/dist/catalog.js +243 -300
- package/dist/catalog.js.map +1 -1
- package/dist/{chunk-7IUSV4XG.js → chunk-24PHKUR3.js} +2 -2
- package/dist/chunk-24PHKUR3.js.map +1 -0
- package/dist/chunk-4BIMLITI.js +92 -0
- package/dist/chunk-4BIMLITI.js.map +1 -0
- package/dist/{chunk-ZK5EGVFP.js → chunk-5OLVUEND.js} +12 -2
- package/dist/chunk-5OLVUEND.js.map +1 -0
- package/dist/{chunk-CMUIXJZM.js → chunk-BSKEH3UC.js} +2 -2
- package/dist/{chunk-CMUIXJZM.js.map → chunk-BSKEH3UC.js.map} +1 -1
- package/dist/chunk-EAX2GSS4.js +384 -0
- package/dist/chunk-EAX2GSS4.js.map +1 -0
- package/dist/chunk-KH6VUGHU.js +399 -0
- package/dist/chunk-KH6VUGHU.js.map +1 -0
- package/dist/{chunk-BJJKFVWW.js → chunk-LZXQVUGN.js} +2 -2
- package/dist/chunk-LZXQVUGN.js.map +1 -0
- package/dist/chunk-N7FHFNT6.js +55 -0
- package/dist/chunk-N7FHFNT6.js.map +1 -0
- package/dist/{chunk-KPNTVB7A.js → chunk-R754MHXX.js} +3 -3
- package/dist/{chunk-KPNTVB7A.js.map → chunk-R754MHXX.js.map} +1 -1
- package/dist/{chunk-HJKRCKDR.js → chunk-RNRUVPJ3.js} +4 -2
- package/dist/chunk-RNRUVPJ3.js.map +1 -0
- package/dist/collapsible-panel.d.ts +40 -13
- package/dist/collapsible-panel.js +41 -10
- package/dist/collapsible-panel.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +17 -16
- package/dist/list-header.js +1 -1
- package/dist/list-inset-text.d.ts +7 -1
- package/dist/list-inset-text.js +2 -1
- package/dist/panel-toolbar-BFQxFITu.d.ts +52 -0
- package/dist/popup-menu-register.d.ts +9 -0
- package/dist/popup-menu-register.js +30 -0
- package/dist/popup-menu-register.js.map +1 -0
- package/dist/popup-menu.d.ts +71 -0
- package/dist/popup-menu.js +3 -0
- package/dist/popup-menu.js.map +1 -0
- package/dist/state-banner.js +1 -1
- package/dist/styles/catalog/components/catalog-example.css +0 -16
- package/dist/styles/catalog/components/catalog-stage.css +8 -13
- package/dist/styles/catalog/components/catalog.css +8 -18
- package/dist/styles/catalog.css +0 -5
- package/dist/styles/popup-menu.css +12 -0
- package/dist/styles/state-banner.css +13 -4
- package/dist/styles/styles.css +1 -0
- package/dist/styles/tab-bar.css +31 -3
- package/dist/styles/toolbar-control-group.css +13 -0
- package/dist/styles/toolbar.css +43 -2
- package/dist/styles/workbench.css +31 -0
- package/dist/text.js +1 -1
- package/dist/token-search-field.d.ts +10 -1
- package/dist/token-search-field.js +1 -1
- package/dist/{types-CEsieBZi.d.ts → types-D6vzj-VT.d.ts} +15 -0
- package/dist/wire-catalog.d.ts +13 -1
- package/dist/wire-catalog.js +18 -3
- package/dist/wire-catalog.js.map +1 -1
- package/dist/wire-sidebar.js +6 -3
- package/dist/wire-sidebar.js.map +1 -1
- package/dist/wire-token-search-fields.js +1 -1
- package/dist/wire-workbench.js +1 -350
- package/dist/wire-workbench.js.map +1 -1
- package/dist/workbench.d.ts +107 -6
- package/dist/workbench.js +11 -208
- package/dist/workbench.js.map +1 -1
- package/docs/accessibility.md +12 -1
- package/docs/catalog.md +27 -9
- package/docs/collapsible-panel.md +55 -0
- package/docs/component-contract.md +2 -2
- package/docs/component-selection.md +25 -17
- package/docs/design/templates/state-banner/danger-dark.svg +1 -1
- package/docs/design/templates/state-banner/danger.svg +1 -1
- package/docs/design/templates/state-banner/info-dark.svg +1 -1
- package/docs/design/templates/state-banner/info.svg +1 -1
- package/docs/design/templates/state-banner/success-dark.svg +1 -1
- package/docs/design/templates/state-banner/success.svg +1 -1
- package/docs/design/templates/state-banner/warning-dark.svg +1 -1
- package/docs/design/templates/state-banner/warning.svg +1 -1
- package/docs/design/templates/state-banner-dark.svg +1 -1
- package/docs/design/templates/state-banner.svg +1 -1
- package/docs/layout.md +5 -0
- package/docs/recipes.md +44 -37
- package/docs/surface-scaffold.md +1 -1
- package/docs/ux-demo.md +6 -2
- package/docs/workbench.md +92 -7
- package/llms.txt +6 -5
- package/package.json +13 -2
- package/ux-demo/recipes/app-shell.tsx +141 -223
- package/ux-demo/recipes/collapsible-sidebar.tsx +44 -75
- package/ux-demo/recipes/list-detail-dialog.tsx +150 -54
- package/ux-demo/recipes/mount-recipe.ts +0 -5
- package/ux-demo/recipes/types.ts +0 -1
- package/dist/chunk-7IUSV4XG.js.map +0 -1
- package/dist/chunk-BJJKFVWW.js.map +0 -1
- package/dist/chunk-HJKRCKDR.js.map +0 -1
- package/dist/chunk-ZK5EGVFP.js.map +0 -1
- package/dist/styles/catalog/components/catalog-detail.css +0 -35
- package/dist/styles/catalog/components/catalog-resource-footer.css +0 -37
- package/dist/styles/catalog/components/catalog-secondary-sections.css +0 -38
- package/dist/styles/catalog/components/catalog-section-list.css +0 -22
- package/dist/styles/catalog/components/catalog-sidebar.css +0 -82
package/dist/wire-catalog.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/wire-catalog.ts"],"names":[],"mappings":"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","file":"wire-catalog.js","sourcesContent":["import { delegate } from 'kerfjs';\n\nexport interface WireCatalogOptions {\n /** Invoked with the entry id when a sidebar item or a related-entry option is chosen. */\n onSelect: (id: string) => void;\n /** Invoked when the sidebar collapse/expand control is activated. */\n onToggleSidebar?: () => void;\n /** Invoked when the theme toggle is activated. */\n onToggleTheme?: () => void;\n /** Invoked when the secondary (ecosystem) group's disclosure toggle is activated. */\n onToggleSecondary?: () => void;\n /** When set, `?<urlParam>=<id>` is written on select via `history.replaceState`. */\n urlParam?: string;\n /**\n * Reveal the chosen sidebar row after selection. `true` uses desktop-safe\n * defaults; pass options to customize scroll alignment or the media guard.\n */\n revealSelection?: boolean | CatalogRevealOptions;\n selectAction?: string;\n toggleSidebarAction?: string;\n toggleThemeAction?: string;\n toggleSecondaryAction?: string;\n}\n\nexport interface CatalogRevealOptions {\n /** Scroll alignment within the sidebar. Default `'nearest'`. */\n block?: ScrollLogicalPosition;\n /** Cross-axis alignment. Default `'nearest'`. */\n inline?: ScrollLogicalPosition;\n /** Scroll behavior. Default `'auto'`. */\n behavior?: ScrollBehavior;\n /**\n * Only reveal when this media query matches. Defaults to the Catalog's\n * desktop layout; pass `false` to reveal at every viewport size.\n */\n media?: string | false;\n}\n\nconst catalogDesktopMedia = '(min-width: 52.01rem)';\n\n/**\n * Reveal one Catalog sidebar entry after the controlled render settles without\n * moving focus. Returns a cancellation function for rapid selection changes.\n */\nexport function revealCatalogEntry(\n root: HTMLElement,\n id: string,\n {\n block = 'nearest',\n inline = 'nearest',\n behavior = 'auto',\n media = catalogDesktopMedia,\n }: CatalogRevealOptions = {},\n): () => void {\n const view = root.ownerDocument.defaultView;\n if (!view || (media && !view.matchMedia(media).matches)) return () => {};\n const frame = view.requestAnimationFrame(() => {\n for (const item of root.querySelectorAll<HTMLElement>('[data-item-id]')) {\n if (item.dataset.itemId !== id) continue;\n item.scrollIntoView({ block, inline, behavior });\n break;\n }\n });\n return () => view.cancelAnimationFrame(frame);\n}\n\nfunction pixels(value: string): number {\n const number = Number.parseFloat(value);\n if (!Number.isFinite(number)) return 0;\n return value.trim().endsWith('rem') ? number * 16 : number;\n}\n\nfunction isTransparent(color: string): boolean {\n const normalized = color.replace(/\\s+/g, '');\n return (\n normalized === 'transparent' || /(?:,|\\/)0(?:\\.0+)?\\)$/.test(normalized)\n );\n}\n\nfunction geometrySpecimens(canvas: HTMLElement): Element[] {\n const result: Element[] = [];\n for (const example of canvas.querySelectorAll<HTMLElement>(\n '[data-catalog-example]',\n )) {\n if (example.closest('[data-catalog-geometry-overlay-skip]')) continue;\n for (const child of example.children) {\n if (\n child.matches('[data-catalog-geometry-overlay]') ||\n child.matches('[data-catalog-example-label]') ||\n child.matches('[data-catalog-example-note]')\n )\n continue;\n result.push(child);\n }\n }\n for (const element of canvas.querySelectorAll<HTMLElement>(\n '[data-component]',\n )) {\n if (\n element.closest('[data-catalog-geometry-overlay]') ||\n element.closest('[data-catalog-geometry-overlay-skip]') ||\n element.closest('.kui-catalog-example')\n )\n continue;\n const parent =\n element.parentElement?.closest<HTMLElement>('[data-component]');\n if (parent && canvas.contains(parent)) continue;\n result.push(element);\n }\n return result;\n}\n\nfunction geometryBox(\n document: Document,\n className: string,\n left: number,\n top: number,\n width: number,\n height: number,\n): HTMLElement {\n const element = document.createElement('div');\n element.className = className;\n element.style.transform = `translate(${left}px, ${top}px)`;\n element.style.width = `${Math.max(0, width)}px`;\n element.style.height = `${Math.max(0, height)}px`;\n return element;\n}\n\nfunction effectiveBorderWidth(width: string, style: string): number {\n return style === 'none' || style === 'hidden' ? 0 : pixels(width);\n}\n\n/**\n * Keep a Catalog's opt-in geometry overlay synchronized with its preview.\n * Specimens receive computed border highlights (or a dashed bound when they\n * have no border and are transparent), while positive computed margins use\n * devtools-style orange bands. CSS/stylesheet-only changes are observed too.\n * Returns a disposer.\n */\nexport function wireCatalogGeometryOverlay(root: HTMLElement): () => void {\n const catalog = root.matches('[data-component=\"catalog\"]')\n ? root\n : root.querySelector<HTMLElement>('[data-component=\"catalog\"]');\n const canvas = catalog?.querySelector<HTMLElement>('.kui-catalog__canvas');\n const layer = catalog?.querySelector<HTMLElement>(\n '[data-catalog-geometry-overlay]',\n );\n const view = root.ownerDocument.defaultView;\n if (!catalog || !canvas || !layer || !view) return () => {};\n\n let frame = 0;\n const render = (): void => {\n frame = 0;\n layer.replaceChildren();\n if (catalog.dataset.geometryOverlay !== 'true') {\n syncObservedSpecimens([]);\n return;\n }\n const base = canvas.getBoundingClientRect();\n const specimens = geometrySpecimens(canvas);\n syncObservedSpecimens(specimens);\n for (const [specimenIndex, element] of specimens.entries()) {\n const rect = element.getBoundingClientRect();\n const style = view.getComputedStyle(element);\n const align = pixels(\n style.getPropertyValue('--kui-catalog-example-align'),\n );\n const top = pixels(style.marginTop);\n const right = Math.max(\n 0,\n pixels(style.marginRight) - (style.direction === 'rtl' ? align : 0),\n );\n const bottom = pixels(style.marginBottom);\n const left = Math.max(\n 0,\n pixels(style.marginLeft) - (style.direction === 'rtl' ? 0 : align),\n );\n const x = rect.left - base.left + canvas.scrollLeft;\n const y = rect.top - base.top + canvas.scrollTop;\n const borderWidths = [\n effectiveBorderWidth(style.borderTopWidth, style.borderTopStyle),\n effectiveBorderWidth(style.borderRightWidth, style.borderRightStyle),\n effectiveBorderWidth(style.borderBottomWidth, style.borderBottomStyle),\n effectiveBorderWidth(style.borderLeftWidth, style.borderLeftStyle),\n ];\n if (borderWidths.some((width) => width > 0)) {\n const border = geometryBox(\n root.ownerDocument,\n 'kui-catalog__geometry-border',\n x,\n y,\n rect.width,\n rect.height,\n );\n border.dataset.catalogGeometrySpecimen = String(specimenIndex);\n border.style.borderTopWidth = `${borderWidths[0]}px`;\n border.style.borderRightWidth = `${borderWidths[1]}px`;\n border.style.borderBottomWidth = `${borderWidths[2]}px`;\n border.style.borderLeftWidth = `${borderWidths[3]}px`;\n border.style.borderRadius = style.borderRadius;\n layer.append(border);\n } else if (isTransparent(style.backgroundColor)) {\n const bound = geometryBox(\n root.ownerDocument,\n 'kui-catalog__geometry-bound',\n x,\n y,\n rect.width,\n rect.height,\n );\n bound.dataset.catalogGeometrySpecimen = String(specimenIndex);\n layer.append(bound);\n }\n const margins: Array<\n readonly [string, number, number, number, number, number]\n > = [\n ['top', top, x - left, y - top, rect.width + left + right, top],\n [\n 'bottom',\n bottom,\n x - left,\n y + rect.height,\n rect.width + left + right,\n bottom,\n ],\n ['left', left, x - left, y, left, rect.height],\n ['right', right, x + rect.width, y, right, rect.height],\n ];\n for (const [side, size, marginX, marginY, width, height] of margins) {\n if (size <= 0) continue;\n const margin = geometryBox(\n root.ownerDocument,\n 'kui-catalog__geometry-margin',\n marginX,\n marginY,\n width,\n height,\n );\n margin.dataset.catalogGeometrySpecimen = String(specimenIndex);\n margin.dataset.catalogGeometrySide = side;\n layer.append(margin);\n }\n }\n };\n const schedule = (): void => {\n if (!frame) frame = view.requestAnimationFrame(render);\n };\n const resizeObserver = new view.ResizeObserver(schedule);\n resizeObserver.observe(canvas);\n const observedSpecimens = new Set<Element>();\n const syncObservedSpecimens = (specimens: Element[]): void => {\n const next = new Set(specimens);\n for (const specimen of observedSpecimens) {\n if (next.has(specimen)) continue;\n resizeObserver.unobserve(specimen);\n observedSpecimens.delete(specimen);\n }\n for (const specimen of next) {\n if (observedSpecimens.has(specimen)) continue;\n resizeObserver.observe(specimen);\n observedSpecimens.add(specimen);\n }\n };\n const mutationObserver = new view.MutationObserver((records) => {\n if (\n records.some(\n ({ target }) => target !== layer && !layer.contains(target as Node),\n )\n )\n schedule();\n });\n mutationObserver.observe(catalog, {\n attributes: true,\n childList: true,\n subtree: true,\n });\n mutationObserver.observe(root.ownerDocument.documentElement, {\n attributes: true,\n });\n const documentHead = root.ownerDocument.head;\n if (documentHead)\n mutationObserver.observe(documentHead, {\n attributes: true,\n characterData: true,\n childList: true,\n subtree: true,\n });\n const stylesheetLoaded = (): void => schedule();\n documentHead?.addEventListener('load', stylesheetLoaded, true);\n view.addEventListener('resize', schedule);\n render();\n return () => {\n if (frame) view.cancelAnimationFrame(frame);\n resizeObserver.disconnect();\n mutationObserver.disconnect();\n documentHead?.removeEventListener('load', stylesheetLoaded, true);\n view.removeEventListener('resize', schedule);\n layer.replaceChildren();\n };\n}\n\n/**\n * Wire a {@link Catalog}'s interactions with one delegated listener set: sidebar\n * item selection (and the related-entry popup menu), the sidebar collapse toggle, and\n * the theme toggle. The app owns the `active`/`collapsed`/`theme` signals and updates\n * them in the callbacks; optionally mirror the active id into the URL via `urlParam`.\n * Returns a disposer.\n */\nexport function wireCatalog(\n root: HTMLElement,\n {\n onSelect,\n onToggleSidebar,\n onToggleTheme,\n onToggleSecondary,\n urlParam,\n revealSelection,\n selectAction = 'catalog-select',\n toggleSidebarAction = 'catalog-toggle-sidebar',\n toggleThemeAction = 'catalog-toggle-theme',\n toggleSecondaryAction = 'catalog-toggle-secondary',\n }: WireCatalogOptions,\n): () => void {\n let cancelReveal: (() => void) | undefined;\n const select = (id: string) => {\n onSelect(id);\n const view = root.ownerDocument.defaultView;\n if (urlParam && view) {\n const url = new URL(view.location.href);\n url.searchParams.set(urlParam, id);\n view.history.replaceState(null, '', url);\n }\n if (revealSelection) {\n cancelReveal?.();\n cancelReveal = revealCatalogEntry(\n root,\n id,\n revealSelection === true ? undefined : revealSelection,\n );\n }\n };\n\n const disposers: Array<() => void> = [\n // Sidebar items AND the footer's related-entry popup-menu items both carry\n // `data-action={selectAction}` + `data-item-id`, so one delegated click covers both.\n delegate(\n root,\n 'click',\n `[data-action=\"${selectAction}\"]`,\n (_event, element) => {\n const id = (element as HTMLElement).dataset.itemId;\n if (id) select(id);\n },\n ),\n ];\n if (onToggleSidebar) {\n disposers.push(\n delegate(root, 'click', `[data-action=\"${toggleSidebarAction}\"]`, () =>\n onToggleSidebar(),\n ),\n );\n }\n if (onToggleTheme) {\n disposers.push(\n delegate(root, 'click', `[data-action=\"${toggleThemeAction}\"]`, () =>\n onToggleTheme(),\n ),\n );\n }\n if (onToggleSecondary) {\n disposers.push(\n delegate(root, 'click', `[data-action=\"${toggleSecondaryAction}\"]`, () =>\n onToggleSecondary(),\n ),\n );\n }\n return () => {\n cancelReveal?.();\n for (const dispose of disposers.splice(0)) dispose();\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/wire-catalog.ts"],"names":[],"mappings":"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","file":"wire-catalog.js","sourcesContent":["import { delegate, type Signal } from 'kerfjs';\n\nimport { wireWorkbench } from './wire-workbench.js';\n\nexport interface WireCatalogOptions {\n /** Invoked with the entry id when a sidebar item or a related-entry option is chosen. */\n onSelect: (id: string) => void;\n /** Invoked when the sidebar collapse/expand control is activated. */\n onToggleSidebar?: () => void;\n /** Invoked when the theme toggle is activated. */\n onToggleTheme?: () => void;\n /** Invoked when the secondary (ecosystem) group's disclosure toggle is activated. */\n onToggleSecondary?: () => void;\n /** When set, `?<urlParam>=<id>` is written on select via `history.replaceState`. */\n urlParam?: string;\n /**\n * Reveal the chosen sidebar row after selection. `true` uses desktop-safe\n * defaults; pass options to customize scroll alignment or the media guard.\n */\n revealSelection?: boolean | CatalogRevealOptions;\n selectAction?: string;\n toggleSidebarAction?: string;\n toggleThemeAction?: string;\n toggleSecondaryAction?: string;\n /**\n * The sidebar's app-owned collapsed flag. With it, the catalog's Workbench\n * sidebar is wired like any Workbench rail: it becomes a transient overlay\n * on a small screen (collapsed as the breakpoint applies, closed on Escape\n * or a press outside, focus handed back), and choosing an entry from the\n * open overlay closes it.\n */\n collapsed?: Signal<boolean>;\n /** The Catalog's `id`, when it is not the default `kui-catalog`. */\n id?: string;\n}\n\nexport interface CatalogRevealOptions {\n /** Scroll alignment within the sidebar. Default `'nearest'`. */\n block?: ScrollLogicalPosition;\n /** Cross-axis alignment. Default `'nearest'`. */\n inline?: ScrollLogicalPosition;\n /** Scroll behavior. Default `'auto'`. */\n behavior?: ScrollBehavior;\n /**\n * Only reveal when this media query matches. Defaults to the Catalog's\n * desktop layout; pass `false` to reveal at every viewport size.\n */\n media?: string | false;\n}\n\n// The Workbench keeps the sidebar inline above its `narrow` breakpoint (704px).\nconst catalogDesktopMedia = '(min-width: 44.01rem)';\n\n/**\n * Reveal one Catalog sidebar entry after the controlled render settles without\n * moving focus. Returns a cancellation function for rapid selection changes.\n */\nexport function revealCatalogEntry(\n root: HTMLElement,\n id: string,\n {\n block = 'nearest',\n inline = 'nearest',\n behavior = 'auto',\n media = catalogDesktopMedia,\n }: CatalogRevealOptions = {},\n): () => void {\n const view = root.ownerDocument.defaultView;\n if (!view || (media && !view.matchMedia(media).matches)) return () => {};\n const frame = view.requestAnimationFrame(() => {\n for (const item of root.querySelectorAll<HTMLElement>('[data-item-id]')) {\n if (item.dataset.itemId !== id) continue;\n item.scrollIntoView({ block, inline, behavior });\n break;\n }\n });\n return () => view.cancelAnimationFrame(frame);\n}\n\nfunction pixels(value: string): number {\n const number = Number.parseFloat(value);\n if (!Number.isFinite(number)) return 0;\n return value.trim().endsWith('rem') ? number * 16 : number;\n}\n\nfunction isTransparent(color: string): boolean {\n const normalized = color.replace(/\\s+/g, '');\n return (\n normalized === 'transparent' || /(?:,|\\/)0(?:\\.0+)?\\)$/.test(normalized)\n );\n}\n\nfunction geometrySpecimens(canvas: HTMLElement): Element[] {\n const result: Element[] = [];\n for (const example of canvas.querySelectorAll<HTMLElement>(\n '[data-catalog-example]',\n )) {\n if (example.closest('[data-catalog-geometry-overlay-skip]')) continue;\n for (const child of example.children) {\n if (\n child.matches('[data-catalog-geometry-overlay]') ||\n child.matches('[data-catalog-example-label]') ||\n child.matches('[data-catalog-example-note]')\n )\n continue;\n result.push(child);\n }\n }\n for (const element of canvas.querySelectorAll<HTMLElement>(\n '[data-component]',\n )) {\n if (\n element.closest('[data-catalog-geometry-overlay]') ||\n element.closest('[data-catalog-geometry-overlay-skip]') ||\n element.closest('.kui-catalog-example')\n )\n continue;\n const parent =\n element.parentElement?.closest<HTMLElement>('[data-component]');\n if (parent && canvas.contains(parent)) continue;\n result.push(element);\n }\n return result;\n}\n\nfunction geometryBox(\n document: Document,\n className: string,\n left: number,\n top: number,\n width: number,\n height: number,\n): HTMLElement {\n const element = document.createElement('div');\n element.className = className;\n element.style.transform = `translate(${left}px, ${top}px)`;\n element.style.width = `${Math.max(0, width)}px`;\n element.style.height = `${Math.max(0, height)}px`;\n return element;\n}\n\nfunction effectiveBorderWidth(width: string, style: string): number {\n return style === 'none' || style === 'hidden' ? 0 : pixels(width);\n}\n\n/**\n * Keep a Catalog's opt-in geometry overlay synchronized with its preview.\n * Specimens receive computed border highlights (or a dashed bound when they\n * have no border and are transparent), while positive computed margins use\n * devtools-style orange bands. CSS/stylesheet-only changes are observed too.\n * Returns a disposer.\n */\nexport function wireCatalogGeometryOverlay(root: HTMLElement): () => void {\n const catalog = root.matches('[data-component=\"catalog\"]')\n ? root\n : root.querySelector<HTMLElement>('[data-component=\"catalog\"]');\n const canvas = catalog?.querySelector<HTMLElement>('.kui-catalog__canvas');\n const layer = catalog?.querySelector<HTMLElement>(\n '[data-catalog-geometry-overlay]',\n );\n const view = root.ownerDocument.defaultView;\n if (!catalog || !canvas || !layer || !view) return () => {};\n\n let frame = 0;\n const render = (): void => {\n frame = 0;\n layer.replaceChildren();\n if (catalog.dataset.geometryOverlay !== 'true') {\n syncObservedSpecimens([]);\n return;\n }\n const base = canvas.getBoundingClientRect();\n const specimens = geometrySpecimens(canvas);\n syncObservedSpecimens(specimens);\n for (const [specimenIndex, element] of specimens.entries()) {\n const rect = element.getBoundingClientRect();\n const style = view.getComputedStyle(element);\n const align = pixels(\n style.getPropertyValue('--kui-catalog-example-align'),\n );\n const top = pixels(style.marginTop);\n const right = Math.max(\n 0,\n pixels(style.marginRight) - (style.direction === 'rtl' ? align : 0),\n );\n const bottom = pixels(style.marginBottom);\n const left = Math.max(\n 0,\n pixels(style.marginLeft) - (style.direction === 'rtl' ? 0 : align),\n );\n const x = rect.left - base.left + canvas.scrollLeft;\n const y = rect.top - base.top + canvas.scrollTop;\n const borderWidths = [\n effectiveBorderWidth(style.borderTopWidth, style.borderTopStyle),\n effectiveBorderWidth(style.borderRightWidth, style.borderRightStyle),\n effectiveBorderWidth(style.borderBottomWidth, style.borderBottomStyle),\n effectiveBorderWidth(style.borderLeftWidth, style.borderLeftStyle),\n ];\n if (borderWidths.some((width) => width > 0)) {\n const border = geometryBox(\n root.ownerDocument,\n 'kui-catalog__geometry-border',\n x,\n y,\n rect.width,\n rect.height,\n );\n border.dataset.catalogGeometrySpecimen = String(specimenIndex);\n border.style.borderTopWidth = `${borderWidths[0]}px`;\n border.style.borderRightWidth = `${borderWidths[1]}px`;\n border.style.borderBottomWidth = `${borderWidths[2]}px`;\n border.style.borderLeftWidth = `${borderWidths[3]}px`;\n border.style.borderRadius = style.borderRadius;\n layer.append(border);\n } else if (isTransparent(style.backgroundColor)) {\n const bound = geometryBox(\n root.ownerDocument,\n 'kui-catalog__geometry-bound',\n x,\n y,\n rect.width,\n rect.height,\n );\n bound.dataset.catalogGeometrySpecimen = String(specimenIndex);\n layer.append(bound);\n }\n const margins: Array<\n readonly [string, number, number, number, number, number]\n > = [\n ['top', top, x - left, y - top, rect.width + left + right, top],\n [\n 'bottom',\n bottom,\n x - left,\n y + rect.height,\n rect.width + left + right,\n bottom,\n ],\n ['left', left, x - left, y, left, rect.height],\n ['right', right, x + rect.width, y, right, rect.height],\n ];\n for (const [side, size, marginX, marginY, width, height] of margins) {\n if (size <= 0) continue;\n const margin = geometryBox(\n root.ownerDocument,\n 'kui-catalog__geometry-margin',\n marginX,\n marginY,\n width,\n height,\n );\n margin.dataset.catalogGeometrySpecimen = String(specimenIndex);\n margin.dataset.catalogGeometrySide = side;\n layer.append(margin);\n }\n }\n };\n const schedule = (): void => {\n if (!frame) frame = view.requestAnimationFrame(render);\n };\n const resizeObserver = new view.ResizeObserver(schedule);\n resizeObserver.observe(canvas);\n const observedSpecimens = new Set<Element>();\n const syncObservedSpecimens = (specimens: Element[]): void => {\n const next = new Set(specimens);\n for (const specimen of observedSpecimens) {\n if (next.has(specimen)) continue;\n resizeObserver.unobserve(specimen);\n observedSpecimens.delete(specimen);\n }\n for (const specimen of next) {\n if (observedSpecimens.has(specimen)) continue;\n resizeObserver.observe(specimen);\n observedSpecimens.add(specimen);\n }\n };\n const mutationObserver = new view.MutationObserver((records) => {\n if (\n records.some(\n ({ target }) => target !== layer && !layer.contains(target as Node),\n )\n )\n schedule();\n });\n mutationObserver.observe(catalog, {\n attributes: true,\n childList: true,\n subtree: true,\n });\n mutationObserver.observe(root.ownerDocument.documentElement, {\n attributes: true,\n });\n const documentHead = root.ownerDocument.head;\n if (documentHead)\n mutationObserver.observe(documentHead, {\n attributes: true,\n characterData: true,\n childList: true,\n subtree: true,\n });\n const stylesheetLoaded = (): void => schedule();\n documentHead?.addEventListener('load', stylesheetLoaded, true);\n view.addEventListener('resize', schedule);\n render();\n return () => {\n if (frame) view.cancelAnimationFrame(frame);\n resizeObserver.disconnect();\n mutationObserver.disconnect();\n documentHead?.removeEventListener('load', stylesheetLoaded, true);\n view.removeEventListener('resize', schedule);\n layer.replaceChildren();\n };\n}\n\n/**\n * Wire a {@link Catalog}'s interactions with one delegated listener set: sidebar\n * item selection (and the related-entry popup menu), the sidebar collapse toggle, and\n * the theme toggle. The app owns the `active`/`collapsed`/`theme` signals and updates\n * them in the callbacks; optionally mirror the active id into the URL via `urlParam`.\n * Returns a disposer.\n */\nexport function wireCatalog(\n root: HTMLElement,\n {\n onSelect,\n onToggleSidebar,\n onToggleTheme,\n onToggleSecondary,\n urlParam,\n revealSelection,\n selectAction = 'catalog-select',\n toggleSidebarAction = 'catalog-toggle-sidebar',\n toggleThemeAction = 'catalog-toggle-theme',\n toggleSecondaryAction = 'catalog-toggle-secondary',\n collapsed,\n id: catalogId = 'kui-catalog',\n }: WireCatalogOptions,\n): () => void {\n let cancelReveal: (() => void) | undefined;\n const select = (id: string) => {\n onSelect(id);\n const view = root.ownerDocument.defaultView;\n if (urlParam && view) {\n const url = new URL(view.location.href);\n url.searchParams.set(urlParam, id);\n view.history.replaceState(null, '', url);\n }\n // An open overlay sidebar gives way to the entry just chosen from it.\n const rail = root.ownerDocument.getElementById(`${catalogId}-left-rail`);\n if (\n collapsed &&\n !collapsed.peek() &&\n rail &&\n root.ownerDocument.defaultView?.getComputedStyle(rail).position ===\n 'absolute'\n )\n collapsed.value = true;\n if (revealSelection) {\n cancelReveal?.();\n cancelReveal = revealCatalogEntry(\n root,\n id,\n revealSelection === true ? undefined : revealSelection,\n );\n }\n };\n\n const disposers: Array<() => void> = [\n // Sidebar items AND the footer's related-entry popup-menu items both carry\n // `data-action={selectAction}` + `data-item-id`, so one delegated click covers both.\n delegate(\n root,\n 'click',\n `[data-action=\"${selectAction}\"]`,\n (_event, element) => {\n const id = (element as HTMLElement).dataset.itemId;\n if (id) select(id);\n },\n ),\n ];\n if (onToggleSidebar) {\n disposers.push(\n delegate(root, 'click', `[data-action=\"${toggleSidebarAction}\"]`, () =>\n onToggleSidebar(),\n ),\n );\n }\n if (onToggleTheme) {\n disposers.push(\n delegate(root, 'click', `[data-action=\"${toggleThemeAction}\"]`, () =>\n onToggleTheme(),\n ),\n );\n }\n if (onToggleSecondary) {\n disposers.push(\n delegate(root, 'click', `[data-action=\"${toggleSecondaryAction}\"]`, () =>\n onToggleSecondary(),\n ),\n );\n }\n if (collapsed)\n disposers.push(\n wireWorkbench(root, {\n id: catalogId,\n panels: { leftRail: { collapsed } },\n }),\n );\n return () => {\n cancelReveal?.();\n for (const dispose of disposers.splice(0)) dispose();\n };\n}\n"]}
|
package/dist/wire-sidebar.js
CHANGED
|
@@ -106,9 +106,12 @@ function wireSidebar(root, {
|
|
|
106
106
|
if (!collapsed && element && !replaced) {
|
|
107
107
|
(focusables(element)[0] ?? element).focus({ preventScroll: true });
|
|
108
108
|
} else if (collapsed && !restoreFocus(panel)) {
|
|
109
|
-
|
|
110
|
-
if (panel.collapsed.peek()
|
|
111
|
-
|
|
109
|
+
const retry = (frames) => {
|
|
110
|
+
if (!panel.collapsed.peek() || restoreFocus(panel) || frames === 0)
|
|
111
|
+
return;
|
|
112
|
+
globalThis.requestAnimationFrame(() => retry(frames - 1));
|
|
113
|
+
};
|
|
114
|
+
globalThis.queueMicrotask(() => retry(3));
|
|
112
115
|
}
|
|
113
116
|
})
|
|
114
117
|
);
|
package/dist/wire-sidebar.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/wire-sidebar.ts"],"names":[],"mappings":"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","file":"wire-sidebar.js","sourcesContent":["import { delegate, effect, type ReadonlySignal, type Signal } from 'kerfjs';\n\nimport type { DeviceClass } from './device-class.js';\n\n/** Minimal `localStorage`-shaped store, so the persistence hook is testable. */\nexport interface SidebarStorage {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n}\n\nexport interface WireSidebarPanel {\n /** The panel id — matches `CollapsiblePanel`'s `id` and a toggle's `panelId`. */\n id: string;\n /** The app-owned collapsed signal. `wireSidebar` reads it (focus, overlay) and\n * writes it (toggle, Escape, backdrop, persistence). */\n collapsed: Signal<boolean>;\n /** `data-action` value the panel's toggle button(s) carry. */\n toggleAction: string;\n /** When set, the panel's inline collapsed state is loaded from and saved to\n * `storage` under this key (a persistence hook), so the panel remembers the\n * user's inline choice. A compact overlay's open/closed state is transient\n * and is never persisted. */\n storageKey?: string;\n /** The panel's inline (non-compact) collapsed state when `storage` holds no\n * choice for it; wire-up seeds the signal with it. Defaults to the signal's\n * own value. Set it when the app seeds the signal from the device class\n * (`signal(device.value.compact)`) so a compact first render already starts\n * collapsed while a later crossing to a wide class still restores this\n * inline default. */\n inlineCollapsed?: boolean;\n}\n\nexport interface WireSidebarOptions {\n panels: readonly WireSidebarPanel[];\n /**\n * When provided, the sidebar adopts a compact **overlay** presentation while\n * `deviceClass.compact` is true: an open panel floats over the content with a\n * dismissable backdrop, Escape and backdrop-click collapse it, and focus is\n * trapped within the open panel (the ARIA dialog pattern). Without it the panel\n * is always inline.\n *\n * An overlay is transient and user-initiated: whenever the compact overlay\n * presentation begins (wire-up on a compact device, or a crossing from a wide\n * class), every panel starts collapsed so nothing blocks the page until the\n * user opens it. The inline state is remembered and restored when the device\n * crosses back to a wide class (and on disposal).\n */\n deviceClass?: ReadonlySignal<DeviceClass>;\n /** Compact devices either overlay the panels (default) or hide them in favor\n * of an application-owned responsive replacement. */\n compactPresentation?: 'overlay' | 'hidden';\n /** Collapse the other panels when one opens in compact overlay mode. */\n exclusiveCompact?: boolean;\n /** Persistence store (default `globalThis.localStorage`, if present). */\n storage?: SidebarStorage;\n}\n\nconst FOCUSABLE =\n 'a[href],area[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex=\"-1\"])';\n\nfunction focusables(panel: HTMLElement): HTMLElement[] {\n // Focus only ever targets an OPEN panel, so the selector (which already drops\n // disabled controls and tabindex=\"-1\") is sufficient without a layout-based\n // visibility check that a headless DOM can't answer.\n return [...panel.querySelectorAll<HTMLElement>(FOCUSABLE)];\n}\n\nfunction defaultStorage(): SidebarStorage | undefined {\n try {\n return globalThis.localStorage ?? undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The reusable sidebar-semantics layer for {@link CollapsiblePanel}s: toggle\n * delegation with focus restore, focus-into on open, an optional compact overlay\n * (backdrop + Escape + focus trap) driven by {@link deviceClass}, and an optional\n * persistence hook. The app owns each `collapsed` signal and the layout; this wire\n * owns the interaction. Returns a disposer. See `ui/docs/collapsible-panel.md` and `docs/23-app-layouts.md`.\n */\nexport function wireSidebar(\n root: HTMLElement,\n {\n panels,\n deviceClass,\n compactPresentation = 'overlay',\n exclusiveCompact = true,\n storage = defaultStorage(),\n }: WireSidebarOptions,\n): () => void {\n const ownerDocument = root.ownerDocument;\n const byAction = new Map(panels.map((panel) => [panel.toggleAction, panel]));\n const returnFocus = new Map<string, HTMLElement>();\n const disposers: Array<() => void> = [];\n // Panels whose next collapsed change is presentation-driven, not a user\n // action, so it must not move focus the way a toggle does. A set rather than\n // a flag: writes made inside an effect are batched, so the focus effects run\n // after the writer returns.\n const adapting = new Set<WireSidebarPanel>();\n const overlayActive = (): boolean =>\n Boolean(deviceClass?.value.compact) && compactPresentation === 'overlay';\n\n // Initial-state contract: an overlay is transient and only ever opens on a\n // user action. Entering the overlay presentation (at wire-up or on a wide →\n // compact crossing) remembers each panel's inline state and collapses it;\n // leaving it (compact → wide, or disposal) restores the remembered state.\n let inlineState: Map<WireSidebarPanel, boolean> | undefined;\n const setAll = (value: (panel: WireSidebarPanel) => boolean): void => {\n for (const panel of panels) {\n const next = value(panel);\n if (panel.collapsed.peek() === next) continue;\n adapting.add(panel);\n panel.collapsed.value = next;\n }\n };\n const restoreInline = (): void => {\n const remembered = inlineState;\n if (!remembered) return;\n inlineState = undefined;\n setAll((panel) => remembered.get(panel)!);\n };\n\n // Seed each panel's inline state from storage or `inlineCollapsed`. On a\n // compact overlay device the seed is only remembered: the panel starts\n // collapsed, synchronously and before any effect exists, so the first render\n // after wire-up already has no overlay open.\n if (overlayActive()) inlineState = new Map();\n for (const panel of panels) {\n const stored =\n panel.storageKey && storage ? storage.getItem(panel.storageKey) : null;\n const seed =\n stored === 'true' || stored === 'false'\n ? stored === 'true'\n : panel.inlineCollapsed;\n if (inlineState) {\n inlineState.set(panel, seed ?? panel.collapsed.peek());\n panel.collapsed.value = true;\n } else if (seed !== undefined) {\n panel.collapsed.value = seed;\n }\n }\n\n // Persistence mirrors inline changes only. An overlay's open/closed state is\n // transient, so it is never written.\n for (const panel of panels) {\n if (!panel.storageKey || !storage) continue;\n disposers.push(\n effect(() => {\n const collapsed = panel.collapsed.value;\n if (overlayActive()) return;\n storage.setItem(panel.storageKey!, String(collapsed));\n }),\n );\n }\n\n const panelElement = (id: string): HTMLElement | null =>\n root.querySelector<HTMLElement>(`[data-collapsible-panel=\"${id}\"]`);\n\n const collapse = (panel: WireSidebarPanel): void => {\n if (panel.collapsed.value) return;\n panel.collapsed.value = true;\n };\n\n // Toggle delegation. The trigger is remembered so focus restores to it on close.\n disposers.push(\n delegate(root, 'click', '[data-action]', (_event, element) => {\n const trigger = element as HTMLElement;\n const panel = byAction.get(trigger.dataset.action ?? '');\n if (!panel) return;\n returnFocus.set(panel.id, trigger);\n const next = !panel.collapsed.value;\n panel.collapsed.value = next;\n if (!next && exclusiveCompact && overlayActive()) {\n for (const other of panels)\n if (other !== panel) other.collapsed.value = true;\n }\n }),\n );\n\n // Where focus goes when a panel closes: the trigger that toggled it, unless\n // that trigger sits inside the now-hidden panel (the documented\n // collapse-inside placement) or was re-rendered away. Then it goes to the\n // panel's toggle outside it — the always-visible expand toggle, which an app\n // may render only while the panel is collapsed. Returns whether it moved.\n const restoreFocus = (panel: WireSidebarPanel): boolean => {\n const element = panelElement(panel.id);\n const trigger = returnFocus.get(panel.id);\n const target =\n trigger?.isConnected && !element?.contains(trigger)\n ? trigger\n : [...root.querySelectorAll<HTMLElement>('[data-action]')].find(\n (candidate) =>\n candidate.dataset.action === panel.toggleAction &&\n !element?.contains(candidate),\n );\n target?.focus();\n return target !== undefined;\n };\n\n // Focus: move into the panel when it opens; restore it when it closes.\n for (const panel of panels) {\n let previous = panel.collapsed.value;\n disposers.push(\n effect(() => {\n const collapsed = panel.collapsed.value;\n if (collapsed === previous) return;\n previous = collapsed;\n const element = panelElement(panel.id);\n if (adapting.delete(panel)) {\n // A presentation change, not a user action: only rescue focus that\n // the collapse would strand inside the now-hidden panel.\n if (collapsed && element?.contains(ownerDocument.activeElement))\n restoreFocus(panel);\n return;\n }\n const replaced =\n deviceClass?.value.compact && compactPresentation === 'hidden';\n if (!collapsed && element && !replaced) {\n // Without preventScroll the browser scrolls the panel's clipped,\n // still-sliding content to reveal the focus target, which then\n // unwinds as the slide finishes: the content jumps and drifts back.\n (focusables(element)[0] ?? element).focus({ preventScroll: true });\n } else if (collapsed && !restoreFocus(panel)) {\n // An app that renders after this effect has not produced its\n // expand toggle yet; look again once the current batch settles.\n globalThis.queueMicrotask(() => {\n if (panel.collapsed.peek()) restoreFocus(panel);\n });\n }\n }),\n );\n }\n\n // Compact overlay: a dismissable backdrop + Escape + focus trap while compact\n // and a panel is open.\n if (deviceClass) {\n let backdrop: HTMLButtonElement | undefined;\n // The host state this wire owns, recorded so it can be re-applied: the\n // app re-renders (and morphs) the root for reasons that never touch a\n // panel signal (a nav selection, say), and a morph strips attributes and\n // nodes the template doesn't emit.\n let hostResponsive: string | undefined;\n let hostOverlay: string | undefined;\n let backdropFor: string | undefined;\n const openPanel = (): WireSidebarPanel | undefined =>\n panels.find((panel) => !panel.collapsed.value);\n const removeBackdrop = (): void => {\n backdrop?.remove();\n backdrop = undefined;\n backdropFor = undefined;\n };\n const setHostData = (\n name: 'collapsibleResponsive' | 'collapsibleOverlay',\n value: string | undefined,\n ): void => {\n if (root.dataset[name] === value) return;\n if (value === undefined) delete root.dataset[name];\n else root.dataset[name] = value;\n };\n // Idempotent, so the observer below settles after one corrective write.\n const syncHost = (): void => {\n setHostData('collapsibleResponsive', hostResponsive);\n setHostData('collapsibleOverlay', hostOverlay);\n if (!backdrop || backdropFor === undefined) return;\n const element = panelElement(backdropFor);\n if (element && backdrop.nextElementSibling !== element)\n element.parentElement?.insertBefore(backdrop, element);\n };\n disposers.push(\n effect(() => {\n const compact = deviceClass.value.compact;\n const overlay = compact && compactPresentation === 'overlay';\n // A crossing adapts the panels here, in the same effect that owns the\n // host state. Every panel's state is read so an open or close moves\n // the backdrop; re-renders that touch no panel are covered by the\n // observer below.\n for (const panel of panels) void panel.collapsed.value;\n if (overlay && !inlineState) {\n inlineState = new Map(\n panels.map((panel) => [panel, panel.collapsed.peek()]),\n );\n setAll(() => true);\n } else if (!overlay) {\n restoreInline();\n }\n const open = overlay\n ? panels.find((panel) => !panel.collapsed.peek())\n : undefined;\n hostResponsive = compact ? compactPresentation : 'inline';\n hostOverlay = compact ? String(overlay) : undefined;\n if (open) {\n if (!backdrop) {\n backdrop = ownerDocument.createElement('button');\n backdrop.type = 'button';\n backdrop.className = 'kui-collapsible-panel__backdrop';\n backdrop.setAttribute('aria-label', 'Close');\n // Injected after render: the morph's removal pass must skip it.\n backdrop.dataset.morphPreserve = '';\n backdrop.addEventListener('click', () => {\n const current = openPanel();\n if (current) collapse(current);\n });\n }\n backdropFor = open.id;\n } else {\n removeBackdrop();\n }\n syncHost();\n }),\n );\n const observer = new MutationObserver(syncHost);\n observer.observe(root, {\n attributes: true,\n attributeFilter: [\n 'data-collapsible-responsive',\n 'data-collapsible-overlay',\n ],\n childList: true,\n subtree: true,\n });\n disposers.push(() => observer.disconnect());\n\n const onKeydown = (event: KeyboardEvent): void => {\n if (!deviceClass.value.compact || compactPresentation !== 'overlay')\n return;\n const open = openPanel();\n if (!open) return;\n if (event.key === 'Escape') {\n event.preventDefault();\n collapse(open);\n return;\n }\n if (event.key !== 'Tab') return;\n const element = panelElement(open.id);\n if (!element) return;\n const items = focusables(element);\n if (items.length === 0) return;\n const first = items[0]!;\n const last = items[items.length - 1]!;\n const active = ownerDocument.activeElement;\n if (event.shiftKey && active === first) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && active === last) {\n event.preventDefault();\n first.focus();\n }\n };\n ownerDocument.addEventListener('keydown', onKeydown);\n disposers.push(() =>\n ownerDocument.removeEventListener('keydown', onKeydown),\n );\n disposers.push(removeBackdrop);\n }\n\n return () => {\n for (const dispose of disposers.splice(0)) dispose();\n // After every effect is gone, so handing the inline state back cannot\n // re-create overlay chrome or persist anything.\n restoreInline();\n delete root.dataset.collapsibleOverlay;\n delete root.dataset.collapsibleResponsive;\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/wire-sidebar.ts"],"names":[],"mappings":"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","file":"wire-sidebar.js","sourcesContent":["import { delegate, effect, type ReadonlySignal, type Signal } from 'kerfjs';\n\nimport type { DeviceClass } from './device-class.js';\n\n/** Minimal `localStorage`-shaped store, so the persistence hook is testable. */\nexport interface SidebarStorage {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n}\n\nexport interface WireSidebarPanel {\n /** The panel id — matches `CollapsiblePanel`'s `id` and a toggle's `panelId`. */\n id: string;\n /** The app-owned collapsed signal. `wireSidebar` reads it (focus, overlay) and\n * writes it (toggle, Escape, backdrop, persistence). */\n collapsed: Signal<boolean>;\n /** `data-action` value the panel's toggle button(s) carry. */\n toggleAction: string;\n /** When set, the panel's inline collapsed state is loaded from and saved to\n * `storage` under this key (a persistence hook), so the panel remembers the\n * user's inline choice. A compact overlay's open/closed state is transient\n * and is never persisted. */\n storageKey?: string;\n /** The panel's inline (non-compact) collapsed state when `storage` holds no\n * choice for it; wire-up seeds the signal with it. Defaults to the signal's\n * own value. Set it when the app seeds the signal from the device class\n * (`signal(device.value.compact)`) so a compact first render already starts\n * collapsed while a later crossing to a wide class still restores this\n * inline default. */\n inlineCollapsed?: boolean;\n}\n\nexport interface WireSidebarOptions {\n panels: readonly WireSidebarPanel[];\n /**\n * When provided, the sidebar adopts a compact **overlay** presentation while\n * `deviceClass.compact` is true: an open panel floats over the content with a\n * dismissable backdrop, Escape and backdrop-click collapse it, and focus is\n * trapped within the open panel (the ARIA dialog pattern). Without it the panel\n * is always inline.\n *\n * An overlay is transient and user-initiated: whenever the compact overlay\n * presentation begins (wire-up on a compact device, or a crossing from a wide\n * class), every panel starts collapsed so nothing blocks the page until the\n * user opens it. The inline state is remembered and restored when the device\n * crosses back to a wide class (and on disposal).\n */\n deviceClass?: ReadonlySignal<DeviceClass>;\n /** Compact devices either overlay the panels (default) or hide them in favor\n * of an application-owned responsive replacement. */\n compactPresentation?: 'overlay' | 'hidden';\n /** Collapse the other panels when one opens in compact overlay mode. */\n exclusiveCompact?: boolean;\n /** Persistence store (default `globalThis.localStorage`, if present). */\n storage?: SidebarStorage;\n}\n\nconst FOCUSABLE =\n 'a[href],area[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex=\"-1\"])';\n\nfunction focusables(panel: HTMLElement): HTMLElement[] {\n // Focus only ever targets an OPEN panel, so the selector (which already drops\n // disabled controls and tabindex=\"-1\") is sufficient without a layout-based\n // visibility check that a headless DOM can't answer.\n return [...panel.querySelectorAll<HTMLElement>(FOCUSABLE)];\n}\n\nfunction defaultStorage(): SidebarStorage | undefined {\n try {\n return globalThis.localStorage ?? undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The reusable sidebar-semantics layer for {@link CollapsiblePanel}s: toggle\n * delegation with focus restore, focus-into on open, an optional compact overlay\n * (backdrop + Escape + focus trap) driven by {@link deviceClass}, and an optional\n * persistence hook. The app owns each `collapsed` signal and the layout; this wire\n * owns the interaction. Returns a disposer. See `ui/docs/collapsible-panel.md` and `docs/23-app-layouts.md`.\n */\nexport function wireSidebar(\n root: HTMLElement,\n {\n panels,\n deviceClass,\n compactPresentation = 'overlay',\n exclusiveCompact = true,\n storage = defaultStorage(),\n }: WireSidebarOptions,\n): () => void {\n const ownerDocument = root.ownerDocument;\n const byAction = new Map(panels.map((panel) => [panel.toggleAction, panel]));\n const returnFocus = new Map<string, HTMLElement>();\n const disposers: Array<() => void> = [];\n // Panels whose next collapsed change is presentation-driven, not a user\n // action, so it must not move focus the way a toggle does. A set rather than\n // a flag: writes made inside an effect are batched, so the focus effects run\n // after the writer returns.\n const adapting = new Set<WireSidebarPanel>();\n const overlayActive = (): boolean =>\n Boolean(deviceClass?.value.compact) && compactPresentation === 'overlay';\n\n // Initial-state contract: an overlay is transient and only ever opens on a\n // user action. Entering the overlay presentation (at wire-up or on a wide →\n // compact crossing) remembers each panel's inline state and collapses it;\n // leaving it (compact → wide, or disposal) restores the remembered state.\n let inlineState: Map<WireSidebarPanel, boolean> | undefined;\n const setAll = (value: (panel: WireSidebarPanel) => boolean): void => {\n for (const panel of panels) {\n const next = value(panel);\n if (panel.collapsed.peek() === next) continue;\n adapting.add(panel);\n panel.collapsed.value = next;\n }\n };\n const restoreInline = (): void => {\n const remembered = inlineState;\n if (!remembered) return;\n inlineState = undefined;\n setAll((panel) => remembered.get(panel)!);\n };\n\n // Seed each panel's inline state from storage or `inlineCollapsed`. On a\n // compact overlay device the seed is only remembered: the panel starts\n // collapsed, synchronously and before any effect exists, so the first render\n // after wire-up already has no overlay open.\n if (overlayActive()) inlineState = new Map();\n for (const panel of panels) {\n const stored =\n panel.storageKey && storage ? storage.getItem(panel.storageKey) : null;\n const seed =\n stored === 'true' || stored === 'false'\n ? stored === 'true'\n : panel.inlineCollapsed;\n if (inlineState) {\n inlineState.set(panel, seed ?? panel.collapsed.peek());\n panel.collapsed.value = true;\n } else if (seed !== undefined) {\n panel.collapsed.value = seed;\n }\n }\n\n // Persistence mirrors inline changes only. An overlay's open/closed state is\n // transient, so it is never written.\n for (const panel of panels) {\n if (!panel.storageKey || !storage) continue;\n disposers.push(\n effect(() => {\n const collapsed = panel.collapsed.value;\n if (overlayActive()) return;\n storage.setItem(panel.storageKey!, String(collapsed));\n }),\n );\n }\n\n const panelElement = (id: string): HTMLElement | null =>\n root.querySelector<HTMLElement>(`[data-collapsible-panel=\"${id}\"]`);\n\n const collapse = (panel: WireSidebarPanel): void => {\n if (panel.collapsed.value) return;\n panel.collapsed.value = true;\n };\n\n // Toggle delegation. The trigger is remembered so focus restores to it on close.\n disposers.push(\n delegate(root, 'click', '[data-action]', (_event, element) => {\n const trigger = element as HTMLElement;\n const panel = byAction.get(trigger.dataset.action ?? '');\n if (!panel) return;\n returnFocus.set(panel.id, trigger);\n const next = !panel.collapsed.value;\n panel.collapsed.value = next;\n if (!next && exclusiveCompact && overlayActive()) {\n for (const other of panels)\n if (other !== panel) other.collapsed.value = true;\n }\n }),\n );\n\n // Where focus goes when a panel closes: the trigger that toggled it, unless\n // that trigger sits inside the now-hidden panel (the documented\n // collapse-inside placement) or was re-rendered away. Then it goes to the\n // panel's toggle outside it — the always-visible expand toggle, which an app\n // may render only while the panel is collapsed. Returns whether it moved.\n const restoreFocus = (panel: WireSidebarPanel): boolean => {\n const element = panelElement(panel.id);\n const trigger = returnFocus.get(panel.id);\n const target =\n trigger?.isConnected && !element?.contains(trigger)\n ? trigger\n : [...root.querySelectorAll<HTMLElement>('[data-action]')].find(\n (candidate) =>\n candidate.dataset.action === panel.toggleAction &&\n !element?.contains(candidate),\n );\n target?.focus();\n return target !== undefined;\n };\n\n // Focus: move into the panel when it opens; restore it when it closes.\n for (const panel of panels) {\n let previous = panel.collapsed.value;\n disposers.push(\n effect(() => {\n const collapsed = panel.collapsed.value;\n if (collapsed === previous) return;\n previous = collapsed;\n const element = panelElement(panel.id);\n if (adapting.delete(panel)) {\n // A presentation change, not a user action: only rescue focus that\n // the collapse would strand inside the now-hidden panel.\n if (collapsed && element?.contains(ownerDocument.activeElement))\n restoreFocus(panel);\n return;\n }\n const replaced =\n deviceClass?.value.compact && compactPresentation === 'hidden';\n if (!collapsed && element && !replaced) {\n // Without preventScroll the browser scrolls the panel's clipped,\n // still-sliding content to reveal the focus target, which then\n // unwinds as the slide finishes: the content jumps and drifts back.\n (focusables(element)[0] ?? element).focus({ preventScroll: true });\n } else if (collapsed && !restoreFocus(panel)) {\n // An app that renders after this effect has not produced its\n // expand toggle (or relocated it into its work-area toolbar) yet;\n // look again once the batch settles, then for a few frames.\n const retry = (frames: number): void => {\n if (!panel.collapsed.peek() || restoreFocus(panel) || frames === 0)\n return;\n globalThis.requestAnimationFrame(() => retry(frames - 1));\n };\n globalThis.queueMicrotask(() => retry(3));\n }\n }),\n );\n }\n\n // Compact overlay: a dismissable backdrop + Escape + focus trap while compact\n // and a panel is open.\n if (deviceClass) {\n let backdrop: HTMLButtonElement | undefined;\n // The host state this wire owns, recorded so it can be re-applied: the\n // app re-renders (and morphs) the root for reasons that never touch a\n // panel signal (a nav selection, say), and a morph strips attributes and\n // nodes the template doesn't emit.\n let hostResponsive: string | undefined;\n let hostOverlay: string | undefined;\n let backdropFor: string | undefined;\n const openPanel = (): WireSidebarPanel | undefined =>\n panels.find((panel) => !panel.collapsed.value);\n const removeBackdrop = (): void => {\n backdrop?.remove();\n backdrop = undefined;\n backdropFor = undefined;\n };\n const setHostData = (\n name: 'collapsibleResponsive' | 'collapsibleOverlay',\n value: string | undefined,\n ): void => {\n if (root.dataset[name] === value) return;\n if (value === undefined) delete root.dataset[name];\n else root.dataset[name] = value;\n };\n // Idempotent, so the observer below settles after one corrective write.\n const syncHost = (): void => {\n setHostData('collapsibleResponsive', hostResponsive);\n setHostData('collapsibleOverlay', hostOverlay);\n if (!backdrop || backdropFor === undefined) return;\n const element = panelElement(backdropFor);\n if (element && backdrop.nextElementSibling !== element)\n element.parentElement?.insertBefore(backdrop, element);\n };\n disposers.push(\n effect(() => {\n const compact = deviceClass.value.compact;\n const overlay = compact && compactPresentation === 'overlay';\n // A crossing adapts the panels here, in the same effect that owns the\n // host state. Every panel's state is read so an open or close moves\n // the backdrop; re-renders that touch no panel are covered by the\n // observer below.\n for (const panel of panels) void panel.collapsed.value;\n if (overlay && !inlineState) {\n inlineState = new Map(\n panels.map((panel) => [panel, panel.collapsed.peek()]),\n );\n setAll(() => true);\n } else if (!overlay) {\n restoreInline();\n }\n const open = overlay\n ? panels.find((panel) => !panel.collapsed.peek())\n : undefined;\n hostResponsive = compact ? compactPresentation : 'inline';\n hostOverlay = compact ? String(overlay) : undefined;\n if (open) {\n if (!backdrop) {\n backdrop = ownerDocument.createElement('button');\n backdrop.type = 'button';\n backdrop.className = 'kui-collapsible-panel__backdrop';\n backdrop.setAttribute('aria-label', 'Close');\n // Injected after render: the morph's removal pass must skip it.\n backdrop.dataset.morphPreserve = '';\n backdrop.addEventListener('click', () => {\n const current = openPanel();\n if (current) collapse(current);\n });\n }\n backdropFor = open.id;\n } else {\n removeBackdrop();\n }\n syncHost();\n }),\n );\n const observer = new MutationObserver(syncHost);\n observer.observe(root, {\n attributes: true,\n attributeFilter: [\n 'data-collapsible-responsive',\n 'data-collapsible-overlay',\n ],\n childList: true,\n subtree: true,\n });\n disposers.push(() => observer.disconnect());\n\n const onKeydown = (event: KeyboardEvent): void => {\n if (!deviceClass.value.compact || compactPresentation !== 'overlay')\n return;\n const open = openPanel();\n if (!open) return;\n if (event.key === 'Escape') {\n event.preventDefault();\n collapse(open);\n return;\n }\n if (event.key !== 'Tab') return;\n const element = panelElement(open.id);\n if (!element) return;\n const items = focusables(element);\n if (items.length === 0) return;\n const first = items[0]!;\n const last = items[items.length - 1]!;\n const active = ownerDocument.activeElement;\n if (event.shiftKey && active === first) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && active === last) {\n event.preventDefault();\n first.focus();\n }\n };\n ownerDocument.addEventListener('keydown', onKeydown);\n disposers.push(() =>\n ownerDocument.removeEventListener('keydown', onKeydown),\n );\n disposers.push(removeBackdrop);\n }\n\n return () => {\n for (const dispose of disposers.splice(0)) dispose();\n // After every effect is gone, so handing the inline state back cannot\n // re-create overlay chrome or persist anything.\n restoreInline();\n delete root.dataset.collapsibleOverlay;\n delete root.dataset.collapsibleResponsive;\n };\n}\n"]}
|
package/dist/wire-workbench.js
CHANGED
|
@@ -1,355 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
import { wireResizeHandles } from './chunk-3CGQ7ZNO.js';
|
|
1
|
+
export { wireWorkbench } from './chunk-KH6VUGHU.js';
|
|
3
2
|
|
|
4
|
-
import { effect } from 'kerfjs';
|
|
5
3
|
|
|
6
|
-
var PANEL_SELECTORS = {
|
|
7
|
-
leftRail: ':scope > [data-workbench-rail="left"]',
|
|
8
|
-
rightRail: ':scope > [data-workbench-rail="right"]',
|
|
9
|
-
bottomDrawer: ":scope > .kui-workbench__center > [data-workbench-drawer]"
|
|
10
|
-
};
|
|
11
|
-
var RESTORE_PANELS = {
|
|
12
|
-
leftRail: "left",
|
|
13
|
-
rightRail: "right",
|
|
14
|
-
bottomDrawer: "bottom"
|
|
15
|
-
};
|
|
16
|
-
var FOCUSABLE = 'a[href],area[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
|
|
17
|
-
var focusables = (element) => [...element.querySelectorAll(FOCUSABLE)].filter(
|
|
18
|
-
(control) => typeof control.checkVisibility !== "function" || control.checkVisibility()
|
|
19
|
-
);
|
|
20
|
-
function wireWorkbenchOverlays(root, findWorkbench, panels, exclusive = false) {
|
|
21
|
-
const ownerDocument = root.ownerDocument;
|
|
22
|
-
const disposers = [];
|
|
23
|
-
const openOrder = [];
|
|
24
|
-
const openers = /* @__PURE__ */ new Map();
|
|
25
|
-
let pressedFocus;
|
|
26
|
-
const panelElement = (panel) => findWorkbench()?.querySelector(PANEL_SELECTORS[panel.key]) ?? null;
|
|
27
|
-
const responsive = (element) => element.dataset.presentation === "inline" && element.hasAttribute("data-responsive-overlay-at") && globalThis.getComputedStyle(element).position === "absolute";
|
|
28
|
-
const overlaid = (element) => element.dataset.presentation === "overlay" || responsive(element);
|
|
29
|
-
const restoreTarget = (panel, element) => {
|
|
30
|
-
const opener = openers.get(panel);
|
|
31
|
-
if (opener?.isConnected && !element?.contains(opener) && !panels.some(
|
|
32
|
-
(other) => other.collapsed.peek() && panelElement(other)?.contains(opener)
|
|
33
|
-
))
|
|
34
|
-
return opener;
|
|
35
|
-
const side = RESTORE_PANELS[panel.key];
|
|
36
|
-
const restore = findWorkbench()?.querySelector(
|
|
37
|
-
`.kui-workbench__restore[data-panel="${side}"]`
|
|
38
|
-
)?.querySelector(FOCUSABLE);
|
|
39
|
-
return restore ?? (element ? controller(element) : void 0);
|
|
40
|
-
};
|
|
41
|
-
const controller = (element) => {
|
|
42
|
-
const ids = new Set(
|
|
43
|
-
[element, ...element.querySelectorAll("[id]")].map((node) => node.id)
|
|
44
|
-
);
|
|
45
|
-
ids.delete("");
|
|
46
|
-
return [
|
|
47
|
-
...ownerDocument.querySelectorAll("[aria-controls]")
|
|
48
|
-
].find(
|
|
49
|
-
(control) => !element.contains(control) && control.matches(FOCUSABLE) && control.getAttribute("aria-controls").split(/\s+/).some((id) => ids.has(id))
|
|
50
|
-
);
|
|
51
|
-
};
|
|
52
|
-
const rescueFocus = (panel) => {
|
|
53
|
-
const element = panelElement(panel);
|
|
54
|
-
const target = restoreTarget(panel, element);
|
|
55
|
-
if (target) {
|
|
56
|
-
target.focus();
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
globalThis.queueMicrotask(() => {
|
|
60
|
-
if (!panel.collapsed.peek()) return;
|
|
61
|
-
const later = restoreTarget(panel, panelElement(panel));
|
|
62
|
-
const active = ownerDocument.activeElement;
|
|
63
|
-
if (later) later.focus();
|
|
64
|
-
else if (active instanceof HTMLElement && element?.contains(active))
|
|
65
|
-
active.blur();
|
|
66
|
-
});
|
|
67
|
-
};
|
|
68
|
-
const adapt = (panel, element, collapsed) => {
|
|
69
|
-
if (panel.collapsed.peek() === collapsed) return;
|
|
70
|
-
const stranded = collapsed && element.contains(ownerDocument.activeElement);
|
|
71
|
-
panel.collapsed.value = collapsed;
|
|
72
|
-
const content = element.querySelector(
|
|
73
|
-
":scope > .kui-workbench__panel-content"
|
|
74
|
-
);
|
|
75
|
-
content.style.transition = "none";
|
|
76
|
-
void globalThis.getComputedStyle(content).transform;
|
|
77
|
-
content.removeAttribute("style");
|
|
78
|
-
if (stranded && element.contains(ownerDocument.activeElement))
|
|
79
|
-
rescueFocus(panel);
|
|
80
|
-
};
|
|
81
|
-
const inlineState = /* @__PURE__ */ new Map();
|
|
82
|
-
const sync = () => {
|
|
83
|
-
for (const panel of panels) {
|
|
84
|
-
const element = panelElement(panel);
|
|
85
|
-
if (!element) continue;
|
|
86
|
-
const active = responsive(element);
|
|
87
|
-
if (active && !inlineState.has(panel)) {
|
|
88
|
-
inlineState.set(panel, panel.collapsed.peek());
|
|
89
|
-
adapt(panel, element, true);
|
|
90
|
-
} else if (!active && inlineState.has(panel)) {
|
|
91
|
-
const remembered = inlineState.get(panel);
|
|
92
|
-
inlineState.delete(panel);
|
|
93
|
-
adapt(panel, element, remembered);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
let observed = null;
|
|
98
|
-
const resizeObserver = typeof globalThis.ResizeObserver === "function" ? new globalThis.ResizeObserver(() => sync()) : void 0;
|
|
99
|
-
const track = () => {
|
|
100
|
-
const workbench = findWorkbench();
|
|
101
|
-
if (workbench === observed) return false;
|
|
102
|
-
if (observed) resizeObserver?.unobserve(observed);
|
|
103
|
-
observed = workbench;
|
|
104
|
-
if (workbench) resizeObserver?.observe(workbench);
|
|
105
|
-
return true;
|
|
106
|
-
};
|
|
107
|
-
const mutationObserver = new MutationObserver((records) => {
|
|
108
|
-
if (track() || records.some((record) => record.type === "attributes"))
|
|
109
|
-
sync();
|
|
110
|
-
});
|
|
111
|
-
mutationObserver.observe(root, {
|
|
112
|
-
attributes: true,
|
|
113
|
-
attributeFilter: ["data-presentation", "data-responsive-overlay-at"],
|
|
114
|
-
childList: true,
|
|
115
|
-
subtree: true
|
|
116
|
-
});
|
|
117
|
-
disposers.push(() => {
|
|
118
|
-
mutationObserver.disconnect();
|
|
119
|
-
resizeObserver?.disconnect();
|
|
120
|
-
});
|
|
121
|
-
track();
|
|
122
|
-
sync();
|
|
123
|
-
for (const panel of panels) {
|
|
124
|
-
let previous = panel.collapsed.peek();
|
|
125
|
-
if (!previous) openOrder.push(panel);
|
|
126
|
-
disposers.push(
|
|
127
|
-
effect(() => {
|
|
128
|
-
const collapsed = panel.collapsed.value;
|
|
129
|
-
if (collapsed === previous) return;
|
|
130
|
-
previous = collapsed;
|
|
131
|
-
const index = openOrder.indexOf(panel);
|
|
132
|
-
if (index >= 0) openOrder.splice(index, 1);
|
|
133
|
-
if (collapsed) {
|
|
134
|
-
const active2 = ownerDocument.activeElement;
|
|
135
|
-
if (panelElement(panel)?.contains(active2) || pressedFocus === panel && (active2 === null || active2 === ownerDocument.body))
|
|
136
|
-
rescueFocus(panel);
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
openOrder.push(panel);
|
|
140
|
-
const element = panelElement(panel);
|
|
141
|
-
const active = ownerDocument.activeElement;
|
|
142
|
-
if (active instanceof HTMLElement && active !== ownerDocument.body && !element?.contains(active))
|
|
143
|
-
openers.set(panel, active);
|
|
144
|
-
else openers.delete(panel);
|
|
145
|
-
if (!element || !overlaid(element)) return;
|
|
146
|
-
if (exclusive) {
|
|
147
|
-
for (const [other] of openOverlays())
|
|
148
|
-
if (other !== panel) other.collapsed.value = true;
|
|
149
|
-
}
|
|
150
|
-
(focusables(element)[0] ?? element).focus({ preventScroll: true });
|
|
151
|
-
})
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
const openOverlays = () => openOrder.flatMap((panel) => {
|
|
155
|
-
const element = panelElement(panel);
|
|
156
|
-
return !panel.collapsed.peek() && element && overlaid(element) ? [[panel, element]] : [];
|
|
157
|
-
});
|
|
158
|
-
const onKeydown = (event) => {
|
|
159
|
-
if (event.key !== "Escape" && event.key !== "Tab" || event.defaultPrevented)
|
|
160
|
-
return;
|
|
161
|
-
const open = openOverlays();
|
|
162
|
-
if (open.length === 0) return;
|
|
163
|
-
const active = ownerDocument.activeElement;
|
|
164
|
-
const [panel, element] = open.find(([, candidate]) => candidate.contains(active)) ?? open[open.length - 1];
|
|
165
|
-
if (event.key === "Escape") {
|
|
166
|
-
event.preventDefault();
|
|
167
|
-
panel.collapsed.value = true;
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
const items = focusables(element);
|
|
171
|
-
if (items.length === 0) return;
|
|
172
|
-
const first = items[0];
|
|
173
|
-
const last = items[items.length - 1];
|
|
174
|
-
const inside = element.contains(active);
|
|
175
|
-
if (event.shiftKey && (!inside || active === first)) {
|
|
176
|
-
event.preventDefault();
|
|
177
|
-
last.focus();
|
|
178
|
-
} else if (!event.shiftKey && (!inside || active === last)) {
|
|
179
|
-
event.preventDefault();
|
|
180
|
-
first.focus();
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
let pressed = [];
|
|
184
|
-
const onPointerdown = (event) => {
|
|
185
|
-
const path = event.composedPath();
|
|
186
|
-
pressed = openOverlays().filter(([, element]) => !path.includes(element)).map(([panel]) => panel);
|
|
187
|
-
const active = ownerDocument.activeElement;
|
|
188
|
-
pressedFocus = panels.find((panel) => {
|
|
189
|
-
if (panel.collapsed.peek()) return false;
|
|
190
|
-
const element = panelElement(panel);
|
|
191
|
-
return !!element && path.includes(element) && element.contains(active);
|
|
192
|
-
});
|
|
193
|
-
};
|
|
194
|
-
const onFocusin = () => {
|
|
195
|
-
pressedFocus = void 0;
|
|
196
|
-
};
|
|
197
|
-
const onClick = (event) => {
|
|
198
|
-
const candidates = pressed;
|
|
199
|
-
pressed = [];
|
|
200
|
-
const path = event.composedPath();
|
|
201
|
-
for (const [panel, element] of openOverlays())
|
|
202
|
-
if (candidates.includes(panel) && !path.includes(element))
|
|
203
|
-
panel.collapsed.value = true;
|
|
204
|
-
};
|
|
205
|
-
ownerDocument.addEventListener("keydown", onKeydown);
|
|
206
|
-
ownerDocument.addEventListener("pointerdown", onPointerdown, true);
|
|
207
|
-
ownerDocument.addEventListener("click", onClick);
|
|
208
|
-
ownerDocument.addEventListener("focusin", onFocusin, true);
|
|
209
|
-
disposers.push(() => {
|
|
210
|
-
ownerDocument.removeEventListener("keydown", onKeydown);
|
|
211
|
-
ownerDocument.removeEventListener("pointerdown", onPointerdown, true);
|
|
212
|
-
ownerDocument.removeEventListener("click", onClick);
|
|
213
|
-
ownerDocument.removeEventListener("focusin", onFocusin, true);
|
|
214
|
-
});
|
|
215
|
-
return () => {
|
|
216
|
-
for (const dispose of disposers.splice(0)) dispose();
|
|
217
|
-
for (const [panel, remembered] of inlineState) {
|
|
218
|
-
const element = panelElement(panel);
|
|
219
|
-
if (element) adapt(panel, element, remembered);
|
|
220
|
-
else panel.collapsed.value = remembered;
|
|
221
|
-
}
|
|
222
|
-
inlineState.clear();
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
4
|
|
|
226
|
-
// src/wire-workbench.ts
|
|
227
|
-
function defaultStorage() {
|
|
228
|
-
try {
|
|
229
|
-
return globalThis.localStorage ?? void 0;
|
|
230
|
-
} catch {
|
|
231
|
-
return void 0;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
function storedSize(text) {
|
|
235
|
-
if (text === null) return void 0;
|
|
236
|
-
const size = Number(text);
|
|
237
|
-
return Number.isFinite(size) && size > 0 ? Math.round(size) : void 0;
|
|
238
|
-
}
|
|
239
|
-
var quoted = (value) => `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
240
|
-
var px = (value) => Number.parseFloat(value) || 0;
|
|
241
|
-
var mainRoomLimit = (region, { max }) => {
|
|
242
|
-
const horizontal = region.dataset.axis === "horizontal";
|
|
243
|
-
const container = region.parentElement;
|
|
244
|
-
const workbench = horizontal ? container : container?.parentElement;
|
|
245
|
-
const mainMin = Number(
|
|
246
|
-
horizontal ? workbench?.dataset.mainMinSize : workbench?.dataset.mainMinHeight
|
|
247
|
-
);
|
|
248
|
-
if (!container || !(mainMin > 0)) return max;
|
|
249
|
-
const box = container.getBoundingClientRect();
|
|
250
|
-
const room = horizontal ? box.width : box.height;
|
|
251
|
-
if (room <= 0) return max;
|
|
252
|
-
let others = 0;
|
|
253
|
-
if (horizontal)
|
|
254
|
-
for (const rail of container.children) {
|
|
255
|
-
if (rail === region || !rail.matches(".kui-workbench__rail") || globalThis.getComputedStyle(rail).position === "absolute")
|
|
256
|
-
continue;
|
|
257
|
-
others += rail.getBoundingClientRect().width;
|
|
258
|
-
}
|
|
259
|
-
const style = globalThis.getComputedStyle(region);
|
|
260
|
-
const size = px(region.style.getPropertyValue("--kui-resizable-region-size"));
|
|
261
|
-
const border = style.boxSizing === "border-box" ? 0 : horizontal ? px(style.borderInlineStartWidth) + px(style.borderInlineEndWidth) : px(style.borderBlockStartWidth) + px(style.borderBlockEndWidth);
|
|
262
|
-
const extent = px(style.flexBasis) - size + border;
|
|
263
|
-
return Math.floor(room - Math.min(mainMin, room) - others - extent);
|
|
264
|
-
};
|
|
265
|
-
function wireWorkbench(root, {
|
|
266
|
-
id,
|
|
267
|
-
panels,
|
|
268
|
-
deviceClass,
|
|
269
|
-
storage = defaultStorage(),
|
|
270
|
-
step,
|
|
271
|
-
largeStep,
|
|
272
|
-
onResize,
|
|
273
|
-
dismissOverlays = true,
|
|
274
|
-
exclusiveOverlays = true
|
|
275
|
-
}) {
|
|
276
|
-
const keys = Object.keys(panels);
|
|
277
|
-
const entries = keys.flatMap((key) => {
|
|
278
|
-
const size = panels[key]?.size;
|
|
279
|
-
return size ? [
|
|
280
|
-
{
|
|
281
|
-
key,
|
|
282
|
-
size,
|
|
283
|
-
storageKey: panels[key].storageKey,
|
|
284
|
-
regionId: workbenchRegionId(id, key)
|
|
285
|
-
}
|
|
286
|
-
] : [];
|
|
287
|
-
});
|
|
288
|
-
const byRegion = new Map(entries.map((entry) => [entry.regionId, entry]));
|
|
289
|
-
const disposers = [];
|
|
290
|
-
for (const { size, storageKey } of entries) {
|
|
291
|
-
if (!storageKey || !storage) continue;
|
|
292
|
-
const seed = storedSize(storage.getItem(storageKey));
|
|
293
|
-
if (seed !== void 0) size.value = seed;
|
|
294
|
-
disposers.push(
|
|
295
|
-
effect(() => {
|
|
296
|
-
storage.setItem(storageKey, String(size.value));
|
|
297
|
-
})
|
|
298
|
-
);
|
|
299
|
-
}
|
|
300
|
-
const selector = entries.map(
|
|
301
|
-
({ regionId }) => `[data-resizable="true"][data-region-id=${quoted(regionId)}]`
|
|
302
|
-
).join(", ");
|
|
303
|
-
const start = () => wireResizeHandles(
|
|
304
|
-
root,
|
|
305
|
-
{
|
|
306
|
-
step,
|
|
307
|
-
largeStep,
|
|
308
|
-
onCommit: ({ id: regionId, size, source }) => {
|
|
309
|
-
const entry = byRegion.get(regionId);
|
|
310
|
-
entry.size.value = size;
|
|
311
|
-
onResize?.({ panel: entry.key, size, source });
|
|
312
|
-
}
|
|
313
|
-
},
|
|
314
|
-
selector,
|
|
315
|
-
mainRoomLimit
|
|
316
|
-
);
|
|
317
|
-
let stopResize;
|
|
318
|
-
if (selector) {
|
|
319
|
-
if (deviceClass) {
|
|
320
|
-
disposers.push(
|
|
321
|
-
effect(() => {
|
|
322
|
-
const compact = deviceClass.value.compact;
|
|
323
|
-
stopResize?.();
|
|
324
|
-
stopResize = compact ? void 0 : start();
|
|
325
|
-
})
|
|
326
|
-
);
|
|
327
|
-
} else {
|
|
328
|
-
stopResize = start();
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
const overlayPanels = keys.flatMap((key) => {
|
|
332
|
-
const collapsed = panels[key]?.collapsed;
|
|
333
|
-
return collapsed ? [{ key, collapsed }] : [];
|
|
334
|
-
});
|
|
335
|
-
if (dismissOverlays && overlayPanels.length > 0) {
|
|
336
|
-
const selector2 = `[data-component="workbench"][id=${quoted(id)}]`;
|
|
337
|
-
disposers.push(
|
|
338
|
-
wireWorkbenchOverlays(
|
|
339
|
-
root,
|
|
340
|
-
() => root.matches(selector2) ? root : root.querySelector(selector2),
|
|
341
|
-
overlayPanels,
|
|
342
|
-
exclusiveOverlays
|
|
343
|
-
)
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
return () => {
|
|
347
|
-
stopResize?.();
|
|
348
|
-
stopResize = void 0;
|
|
349
|
-
for (const dispose of disposers.splice(0)) dispose();
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
export { wireWorkbench };
|
|
354
5
|
|
|
355
6
|
//# sourceMappingURL=wire-workbench.js.map
|