@kerfjs/ui 5.0.0-beta.51 → 5.0.0-beta.52

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.
Files changed (74) hide show
  1. package/ai/public-api-signatures-v1.md +19 -4
  2. package/ai/skill.md +15 -15
  3. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  4. package/dist/app-tab.d.ts +10 -1
  5. package/dist/app-tab.js +1 -1
  6. package/dist/catalog.js +4 -4
  7. package/dist/{chunk-MO56ZZLG.js → chunk-2CE22Q2K.js} +5 -2
  8. package/dist/chunk-2CE22Q2K.js.map +1 -0
  9. package/dist/{chunk-UPZ6EEJN.js → chunk-2S7OWAEV.js} +3 -3
  10. package/dist/{chunk-UPZ6EEJN.js.map → chunk-2S7OWAEV.js.map} +1 -1
  11. package/dist/{chunk-KX7DP2GO.js → chunk-3S7OUHUN.js} +3 -3
  12. package/dist/{chunk-KX7DP2GO.js.map → chunk-3S7OUHUN.js.map} +1 -1
  13. package/dist/{chunk-JMGKM7RK.js → chunk-4XE2CDYB.js} +2 -2
  14. package/dist/{chunk-JMGKM7RK.js.map → chunk-4XE2CDYB.js.map} +1 -1
  15. package/dist/{chunk-S4QRSBFI.js → chunk-7XJO4D6A.js} +2 -2
  16. package/dist/{chunk-S4QRSBFI.js.map → chunk-7XJO4D6A.js.map} +1 -1
  17. package/dist/{chunk-UBH743HV.js → chunk-ABAXNUXU.js} +3 -3
  18. package/dist/{chunk-UBH743HV.js.map → chunk-ABAXNUXU.js.map} +1 -1
  19. package/dist/{chunk-NZXWU4R5.js → chunk-DNCOFEUL.js} +2 -2
  20. package/dist/{chunk-NZXWU4R5.js.map → chunk-DNCOFEUL.js.map} +1 -1
  21. package/dist/{chunk-5FZZMIMD.js → chunk-IR53BQUJ.js} +16 -12
  22. package/dist/chunk-IR53BQUJ.js.map +1 -0
  23. package/dist/{chunk-JM53SMVF.js → chunk-JGFZ42ZF.js} +3 -3
  24. package/dist/{chunk-JM53SMVF.js.map → chunk-JGFZ42ZF.js.map} +1 -1
  25. package/dist/{chunk-ULCDZTWL.js → chunk-KAQUUTZR.js} +4 -2
  26. package/dist/chunk-KAQUUTZR.js.map +1 -0
  27. package/dist/{chunk-NMGTHYW3.js → chunk-LIOXPQ7N.js} +4 -4
  28. package/dist/chunk-LIOXPQ7N.js.map +1 -0
  29. package/dist/{chunk-DVZCMMB3.js → chunk-M6GLI6A6.js} +3 -3
  30. package/dist/{chunk-DVZCMMB3.js.map → chunk-M6GLI6A6.js.map} +1 -1
  31. package/dist/{chunk-D547PH4L.js → chunk-OSCL5TKR.js} +3 -3
  32. package/dist/{chunk-D547PH4L.js.map → chunk-OSCL5TKR.js.map} +1 -1
  33. package/dist/{chunk-5PMGQDG7.js → chunk-SV22UIW4.js} +2 -2
  34. package/dist/{chunk-5PMGQDG7.js.map → chunk-SV22UIW4.js.map} +1 -1
  35. package/dist/{chunk-AEPXGCNF.js → chunk-T7C2VZUB.js} +2 -2
  36. package/dist/{chunk-AEPXGCNF.js.map → chunk-T7C2VZUB.js.map} +1 -1
  37. package/dist/{chunk-O3RCCSQJ.js → chunk-YZV2PTY4.js} +3 -3
  38. package/dist/{chunk-O3RCCSQJ.js.map → chunk-YZV2PTY4.js.map} +1 -1
  39. package/dist/css-values.d.ts +3 -1
  40. package/dist/css-values.js +1 -1
  41. package/dist/grid.js +1 -1
  42. package/dist/index.js +15 -15
  43. package/dist/list-action-row.js +1 -1
  44. package/dist/list-header.js +1 -1
  45. package/dist/list-item.js +1 -1
  46. package/dist/list.js +1 -1
  47. package/dist/nav-stack.js +1 -1
  48. package/dist/row.js +1 -1
  49. package/dist/segmented-control.js +1 -1
  50. package/dist/select.js +1 -1
  51. package/dist/skeleton.d.ts +5 -1
  52. package/dist/skeleton.js +1 -1
  53. package/dist/spacer.js +1 -1
  54. package/dist/split-view.js +1 -1
  55. package/dist/state-banner.js +1 -1
  56. package/dist/styles/app-tab.css +9 -0
  57. package/dist/styles/skeleton.css +4 -1
  58. package/dist/toolbar-text.js +1 -1
  59. package/dist/value-table.js +1 -1
  60. package/dist/wire-sidebar.js +1 -1
  61. package/dist/wire-sidebar.js.map +1 -1
  62. package/docs/accessibility.md +4 -1
  63. package/docs/collapsible-panel.md +2 -1
  64. package/docs/component-contract.md +8 -3
  65. package/docs/recipes.md +6 -4
  66. package/docs/ux-demo.md +1 -1
  67. package/package.json +1 -1
  68. package/ux-demo/recipes/collapsible-sidebar.tsx +12 -11
  69. package/ux-demo/recipes/composer-form.tsx +2 -0
  70. package/ux-demo/recipes/loading-inspector.tsx +4 -4
  71. package/dist/chunk-5FZZMIMD.js.map +0 -1
  72. package/dist/chunk-MO56ZZLG.js.map +0 -1
  73. package/dist/chunk-NMGTHYW3.js.map +0 -1
  74. package/dist/chunk-ULCDZTWL.js.map +0 -1
@@ -10,6 +10,10 @@ interface SkeletonProps {
10
10
  radius?: CssLength;
11
11
  /** Render this many stacked lines (the last one shorter), for multi-line text. */
12
12
  lines?: number;
13
+ /** Lay the block out as `display: block` instead of the default
14
+ * `inline-block`, so it takes no text line of its own: no baseline gap
15
+ * below it, and it centers like an icon in a slot. Defaults to `false`. */
16
+ block?: boolean;
13
17
  /** Accessible label. Omit to keep the block decorative (`aria-hidden`). */
14
18
  label?: string;
15
19
  className?: string;
@@ -22,6 +26,6 @@ interface SkeletonProps {
22
26
  * `placeholder` prop. Decorative by default (`aria-hidden`); pass `label` to
23
27
  * announce it. Sizes to its slot unless `width`/`height` are given.
24
28
  */
25
- declare function Skeleton({ width, height, radius, lines, label, className, slot, }: SkeletonProps): kerfjs.SafeHtml;
29
+ declare function Skeleton({ width, height, radius, lines, block, label, className, slot, }: SkeletonProps): kerfjs.SafeHtml;
26
30
 
27
31
  export { Skeleton, type SkeletonProps };
package/dist/skeleton.js CHANGED
@@ -1,4 +1,4 @@
1
- export { Skeleton } from './chunk-ULCDZTWL.js';
1
+ export { Skeleton } from './chunk-KAQUUTZR.js';
2
2
 
3
3
 
4
4
  //# sourceMappingURL=skeleton.js.map
package/dist/spacer.js CHANGED
@@ -1,4 +1,4 @@
1
- export { Spacer } from './chunk-JMGKM7RK.js';
1
+ export { Spacer } from './chunk-4XE2CDYB.js';
2
2
 
3
3
 
4
4
  //# sourceMappingURL=spacer.js.map
@@ -1,4 +1,4 @@
1
- import { NavStack } from './chunk-5PMGQDG7.js';
1
+ import { NavStack } from './chunk-SV22UIW4.js';
2
2
  import { ResizableRegion } from './chunk-VOCPXGQN.js';
3
3
 
4
4
 
@@ -1,4 +1,4 @@
1
- export { StateBanner } from './chunk-JM53SMVF.js';
1
+ export { StateBanner } from './chunk-JGFZ42ZF.js';
2
2
 
3
3
 
4
4
 
@@ -148,6 +148,15 @@
148
148
  .kui-app-tab__close:hover {
149
149
  color: var(--kui-color-text);
150
150
  }
151
+ /* A dormant tab keeps its close button's space, so it matches the live tab's
152
+ geometry, but never shows a control it has disabled. */
153
+ .kui-app-tab:is([data-placeholder="true"], [data-pending="true"])
154
+ .kui-app-tab__close {
155
+ visibility: hidden;
156
+ }
157
+ .kui-app-tab[data-pending="true"] .kui-app-tab__name {
158
+ color: var(--kui-color-text-quiet);
159
+ }
151
160
  .kui-app-tab__close-icon {
152
161
  display: grid;
153
162
  width: 0.6375rem;
@@ -5,7 +5,6 @@
5
5
  width: 100%;
6
6
  max-width: 100%;
7
7
  height: 1em;
8
- min-width: 1.5rem;
9
8
  border-radius: var(--kui-skeleton-radius);
10
9
  /* Deliberately unanimated: a flat, subtle fill that reads as "not yet loaded"
11
10
  without drawing the eye or implying progress. */
@@ -13,6 +12,10 @@
13
12
  vertical-align: middle;
14
13
  }
15
14
 
15
+ .kui-skeleton[data-block="true"] {
16
+ display: block;
17
+ }
18
+
16
19
  .kui-skeleton-lines {
17
20
  display: flex;
18
21
  width: 100%;
@@ -1,4 +1,4 @@
1
- export { ToolbarText } from './chunk-O3RCCSQJ.js';
1
+ export { ToolbarText } from './chunk-YZV2PTY4.js';
2
2
 
3
3
 
4
4
 
@@ -1,4 +1,4 @@
1
- export { ValueTable, ValueTableRow } from './chunk-UPZ6EEJN.js';
1
+ export { ValueTable, ValueTableRow } from './chunk-2S7OWAEV.js';
2
2
 
3
3
 
4
4
 
@@ -104,7 +104,7 @@ function wireSidebar(root, {
104
104
  }
105
105
  const replaced = deviceClass?.value.compact && compactPresentation === "hidden";
106
106
  if (!collapsed && element && !replaced) {
107
- (focusables(element)[0] ?? element).focus();
107
+ (focusables(element)[0] ?? element).focus({ preventScroll: true });
108
108
  } else if (collapsed && !restoreFocus(panel)) {
109
109
  globalThis.queueMicrotask(() => {
110
110
  if (panel.collapsed.peek()) restoreFocus(panel);
@@ -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 (focusables(element)[0] ?? element).focus();\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 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"]}
@@ -178,7 +178,10 @@ trailing, and end actions remain visible and do not shrink. The component owns
178
178
  that geometry; applications must not override its anatomy to create the split.
179
179
  `AppTab` provides compact 32px, segmented, truncating-label, and icon-only
180
180
  presentations. Icon-only tabs keep the required `name` as the tab button's
181
- accessible name while visually hiding the duplicate label. These props own
181
+ accessible name while visually hiding the duplicate label. A `pending` tab (known,
182
+ still opening) keeps its visible name as the tab button's accessible name, so a
183
+ trailing spinner's own label is not folded into it; like `placeholder`, it is
184
+ `aria-busy`, disabled, out of the tab order, and not draggable. These props own
182
185
  component appearance only; the application still owns the bar's outer placement.
183
186
  `AppTab.rootAttributes` accepts application `data-*` metadata only; runtime
184
187
  filtering rejects roles plus case variants of component- or wiring-owned
@@ -41,7 +41,8 @@ Import the panel CSS (`@kerfjs/ui/collapsible-panel.css`) alongside `foundation.
41
41
  `{ id, collapsed, toggleAction, storageKey?, inlineCollapsed? }`. It:
42
42
  - **toggles** the panel's `collapsed` signal when any `[data-action=toggleAction]`
43
43
  button is clicked, and remembers the trigger;
44
- - **manages focus** — moves focus into the panel when it opens, and restores it
44
+ - **manages focus** — moves focus into the panel when it opens (without
45
+ scrolling, so the sliding content doesn't jump), and restores it
45
46
  to the trigger when it closes. When that trigger is the collapse toggle inside
46
47
  the now-hidden panel, focus goes to the panel's toggle outside it instead, so
47
48
  an app may render its expand toggle only while the panel is collapsed (one
@@ -313,8 +313,10 @@ spacing shorthand such as `gap="2xs"`, or pass a `CssLength` created by the
313
313
  CSS-free `@kerfjs/ui/css-values` helpers: `space('m')`, `rem(0.25)`,
314
314
  `lengthVar('--app-gap')`, or `calc(plus(rem(0.25), pct(10)))`. `plus` returns a
315
315
  non-standalone `CssLengthExpression`; only `calc` turns it into a complete
316
- length. Raw CSS strings and source-only `remify(4px)` are not valid runtime
317
- props. The `CssLength` name intentionally includes percentages for UI dimension
316
+ length. `remify(18)` is the runtime twin of source CSS's `remify(18px)`: it
317
+ takes a pixel count and returns a `CssLength` in rem (`1.125rem`) against the
318
+ same fixed 16px baseline. Raw CSS strings, and the source-only `remify(4px)`
319
+ string form, are not valid runtime props. The `CssLength` name intentionally includes percentages for UI dimension
318
320
  props. Its opaque string brand improves authoring correctness; it is not a CSS
319
321
  sanitizer or security boundary.
320
322
  `Row.flex` and `List.flex` likewise accept their boolean default, finite keywords, or `CssFlex`
@@ -451,7 +453,10 @@ unanimated `Skeleton` block and disabling its own interactive controls
451
453
  identical to the populated component, so a parent composes a faithful loading view —
452
454
  an inspector, a detail pane — from placeholder children without hand-rebuilding
453
455
  markup. The application still owns the loading lifecycle (when to pass `placeholder`)
454
- and which slots are unknown; the standalone `Skeleton` covers custom slots. It is a
456
+ and which slots are unknown; the standalone `Skeleton` covers custom slots. A
457
+ `Skeleton` is `inline-block` so it sits in a text line; pass `block` to lay it out
458
+ as `display: block` where it stands in for a box, such as an icon, so it takes no
459
+ line of its own and centers in its slot. It is a
455
460
  placeholder, not progress: use `LoadingSpinner` for known busy activity, and never
456
461
  animate the skeleton.
457
462
 
package/docs/recipes.md CHANGED
@@ -164,10 +164,12 @@ A mini app frame whose left navigation rail and bottom activity drawer are
164
164
  standalone `CollapsiblePanel`s (`@kerfjs/ui/collapsible-panel`) driven by
165
165
  `wireSidebar` (`@kerfjs/ui/wire-sidebar`). `CollapsiblePanelToggle` supplies the
166
166
  standard per-side glyph: a collapse toggle lives inside each panel, and the
167
- always-visible main header shows that panel's expand toggle only while it is
168
- collapsed, so exactly one control owns each action and a collapsed panel is still
169
- reachable. `wireSidebar` owns the toggle delegation, moves focus into a panel on
170
- open, restores it on close (to the trigger, or to the panel's expand toggle when
167
+ panel's expand toggle shows only while it is collapsed, so exactly one control
168
+ owns each action and a collapsed panel is still reachable. The rail's expand
169
+ toggle sits in the always-visible main header; the drawer's floats in a
170
+ `FloatingToolbar` at the bottom edge the drawer opens from, next to where the
171
+ action happens. `wireSidebar` owns the toggle delegation, moves focus into a
172
+ panel on open, restores it on close (to the trigger, or to the panel's expand toggle when
171
173
  the trigger sits inside the collapsed panel), and — when a `deviceClass()` reports
172
174
  `compact` — presents the panels as a dismissable **overlay** (backdrop, Escape
173
175
  and backdrop-click collapse, and a trapped Tab ring, the ARIA dialog pattern).
package/docs/ux-demo.md CHANGED
@@ -18,7 +18,7 @@ deterministic state, and semantic layout owners from the [recipe guide](./recipe
18
18
 
19
19
  - It imports public component subpaths, exercising their browser-selected reachable CSS rather than a catalog-only style path.
20
20
  - The `ListActionRow` route demonstrates sibling primary/trailing native controls, independent disabled states, controlled selection, first-line leading-icon alignment for wrapped labels, and an application-owned native popover relationship without nesting controls. The `ListItem` route demonstrates the same multiline leading-icon anchor alongside its single-control navigation contract.
21
- - The `AppTab` and `TabBar` routes are focused component specimens: AppTab shows its selected, compact, segmented, icon-only, truncation, placeholder, metadata, and close-icon axes inside the required tablist context; TabBar shows its rail, segmented, inspector, allocation, placement, and accessory-zone axes, including an adjacent action plus a separately pinned end action whose tabs overflow at narrow widths, without application readouts. The separate `Application tabs` composition route demonstrates controlled selection, matching panel content, overflow, adding, closing, keyboard/pointer reordering, and application-owned ordering. The `ResizableRegion` route likewise replaces only the decorative handle glyph while retaining separator semantics and shared wiring.
21
+ - The `AppTab` and `TabBar` routes are focused component specimens: AppTab shows its selected, compact, segmented, icon-only, truncation, placeholder, pending, metadata, and close-icon axes inside the required tablist context; TabBar shows its rail, segmented, inspector, allocation, placement, and accessory-zone axes, including an adjacent action plus a separately pinned end action whose tabs overflow at narrow widths, without application readouts. The separate `Application tabs` composition route demonstrates controlled selection, matching panel content, overflow, adding, closing, keyboard/pointer reordering, and application-owned ordering. The `ResizableRegion` route likewise replaces only the decorative handle glyph while retaining separator semantics and shared wiring.
22
22
  - Every first-party visual component and every free Web Awesome 3.12 component has its own stable `?component=<id>` URL and focused demo. Composite header, menu, and feedback scenarios remain addressable alongside their subcomponents; the root opens the first Kerf component. The former aggregate Web Awesome theme specimen is intentionally omitted because its component families are covered by those focused routes.
23
23
  - `ai/component-catalog.json` is the canonical catalog metadata. A deterministic checked-in projection supplies the demo's unique id, category, kind (`component`, `composition`, or `recipe`), source (`kerf` or `webawesome`), name, description, recommendation, direct `uses` dependencies, existing documentation path, and demo-source path; first-party, recipe, and Web Awesome renderer maps are exhaustively typed against its generated id unions. Functional groups follow their authored importance order; components precede compositions inside each group, equal-importance entries use alphabetical order, and the Recipes group comes last. Composition names stay concise and project a visible `Composition` sidebar tag. Non-supported Web Awesome recommendations project to a visible `Discouraged` tag, with Popup as the deliberate conditional exception. Demo paths resolve to the matching `ui/ux-demo/demos/<id>.tsx` or `ui/ux-demo/recipes/*.tsx` file, or to `ui/ux-demo/webawesome-demos.tsx` for Web Awesome entries. `ui/ux-demo/main.tsx` owns only the catalog shell, shared state, routing, and interaction wiring. First-party component entries additionally derive `ui/src/<public-subpath>.tsx` from their canonical `delivery.browserImport` or opt-in `delivery.moduleImport`.
24
24
  - Every detail footer groups compact `Demo source` and `Guidance` actions; first-party component details also expose `Component source`. Web Awesome details say `Integration guidance` to distinguish the local integration contract from the upstream component implementation. Each button retains its repository-relative path in the DOM for verification and uses a deploy-safe absolute GitHub blob URL with a contextual new-tab name; the demo does not depend on a local source server or add a package runtime export. At phone widths, the resource group and related-component selector stack as full-width rows, with any resource overflow contained locally so labels cannot overlap the selector or widen the document.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kerfjs/ui",
3
- "version": "5.0.0-beta.51",
3
+ "version": "5.0.0-beta.52",
4
4
  "description": "Accessible, composable UI primitives for kerf applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,11 +1,13 @@
1
1
  import '@kerfjs/ui/layout.css';
2
2
  import '@kerfjs/ui/collapsible-panel.css';
3
+ import '@kerfjs/ui/floating-toolbar.css';
3
4
 
4
5
  import {
5
6
  CollapsiblePanel,
6
7
  CollapsiblePanelToggle,
7
8
  } from '@kerfjs/ui/collapsible-panel';
8
9
  import { deviceClass } from '@kerfjs/ui/device-class';
10
+ import { FloatingToolbar } from '@kerfjs/ui/floating-toolbar';
9
11
  import { Grid } from '@kerfjs/ui/grid';
10
12
  import { List } from '@kerfjs/ui/list';
11
13
  import { ListHeader } from '@kerfjs/ui/list-header';
@@ -143,9 +145,10 @@ export const createRecipe: RecipeFactory = (announce) => {
143
145
  </CollapsiblePanel>
144
146
  );
145
147
 
146
- // Each panel's own header holds its collapse toggle; the always-visible main
147
- // header holds its expand toggle only while it is collapsed, so exactly one
148
- // control owns each action. wireSidebar hands focus between them.
148
+ // Each panel's own header holds its collapse toggle; its expand toggle shows
149
+ // only while it is collapsed, so exactly one control owns each action. The
150
+ // rail's sits in the main header; the drawer's floats at the bottom edge the
151
+ // drawer opens from. wireSidebar hands focus between them.
149
152
  const main = () => (
150
153
  <Pane
151
154
  element="main"
@@ -167,13 +170,6 @@ export const createRecipe: RecipeFactory = (announce) => {
167
170
  />
168
171
  </>
169
172
  }
170
- trailing={
171
- drawerCollapsed.value ? (
172
- <ToolbarControlGroup appearance="borderless" single>
173
- {drawerToggle()}
174
- </ToolbarControlGroup>
175
- ) : undefined
176
- }
177
173
  />
178
174
  }
179
175
  >
@@ -203,7 +199,7 @@ export const createRecipe: RecipeFactory = (announce) => {
203
199
  header={
204
200
  <Toolbar
205
201
  label="Activity"
206
- dividerSides="b"
202
+ dividerSides=""
207
203
  leading={<ToolbarText text="Activity" size="small" />}
208
204
  trailing={
209
205
  <ToolbarControlGroup appearance="borderless" single>
@@ -250,6 +246,11 @@ export const createRecipe: RecipeFactory = (announce) => {
250
246
  </Grid>
251
247
  {drawer()}
252
248
  </List>
249
+ {drawerCollapsed.value ? (
250
+ <FloatingToolbar label="Activity drawer">
251
+ <ToolbarControlGroup single>{drawerToggle()}</ToolbarControlGroup>
252
+ </FloatingToolbar>
253
+ ) : null}
253
254
  </Row>
254
255
  );
255
256
 
@@ -5,6 +5,7 @@ import '@awesome.me/webawesome/dist/components/button/button.js';
5
5
  import '@awesome.me/webawesome/dist/components/input/input.js';
6
6
  import '@awesome.me/webawesome/dist/components/textarea/textarea.js';
7
7
 
8
+ import { Spacer } from '@kerfjs/ui';
8
9
  import { List } from '@kerfjs/ui/list';
9
10
  import { ListInsetText } from '@kerfjs/ui/list-inset-text';
10
11
  import { Row } from '@kerfjs/ui/row';
@@ -122,6 +123,7 @@ export const createRecipe: RecipeFactory = (announce) => {
122
123
  />
123
124
  </List>
124
125
  <Row gap="xs" wrap controlInsets="rl">
126
+ <Spacer flex />
125
127
  <wa-button
126
128
  appearance="outlined"
127
129
  data-action="recipe-action"
@@ -1,9 +1,9 @@
1
1
  import '@kerfjs/ui/layout.css';
2
2
  import '@kerfjs/ui/select/register';
3
3
 
4
+ import { Row } from '@kerfjs/ui';
4
5
  import { em } from '@kerfjs/ui/css-values';
5
6
  import { List } from '@kerfjs/ui/list';
6
- import { ListInsetText } from '@kerfjs/ui/list-inset-text';
7
7
  import { ListItem } from '@kerfjs/ui/list-item';
8
8
  import { LucideIcon } from '@kerfjs/ui/lucide-icon';
9
9
  import { Pane } from '@kerfjs/ui/pane';
@@ -90,15 +90,15 @@ export const createRecipe: RecipeFactory = (announce) => {
90
90
  </ToolbarControlGroup>
91
91
  }
92
92
  />
93
- <ListInsetText sides="rl">
94
- <Text variant="span" tone="quiet" id="recipe-inspector-summary">
93
+ <Row controlInsets="trbl">
94
+ <Text tone="quiet" id="recipe-inspector-summary">
95
95
  {p ? (
96
96
  <Skeleton width={em(18)} />
97
97
  ) : (
98
98
  'Restore keyboard focus after a dialog closes'
99
99
  )}
100
100
  </Text>
101
- </ListInsetText>
101
+ </Row>
102
102
  </>
103
103
  }
104
104
  >
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/app-tab.tsx"],"names":[],"mappings":"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","file":"chunk-5FZZMIMD.js","sourcesContent":["import type { SafeHtml } from 'kerfjs';\n\nimport { em } from './css-values.js';\nimport { filterDataAttributes } from './extension-attributes.js';\nimport type { KerfUiContent } from './semantic-content.js';\nimport { Skeleton } from './skeleton.js';\n\nconst PROTECTED_ROOT_DATA_ATTRIBUTES = new Set([\n 'data-component',\n 'data-action',\n 'data-tab-id',\n 'data-selected',\n 'data-tab-dragging',\n 'data-tab-drop-position',\n]);\n\ntype AppTabRootAttributes = Readonly<\n Record<`data-${string}`, string | undefined> & {\n 'data-component'?: never;\n 'data-action'?: never;\n 'data-tab-id'?: never;\n 'data-selected'?: never;\n 'data-tab-dragging'?: never;\n 'data-tab-drop-position'?: never;\n }\n>;\n\nexport type AppTabPresentation = 'pill' | 'segmented' | 'icon-only';\nexport type AppTabSize = 'default' | 'compact';\n\nexport interface AppTabProps {\n id: string;\n name: string;\n selected?: boolean;\n closable?: boolean;\n draggable?: boolean;\n leading?: KerfUiContent;\n trailing?: KerfUiContent;\n /** Visual treatment within a TabBar. Icon-only tabs retain `name` as their accessible name. */\n presentation?: AppTabPresentation;\n /** Compact tabs use the 32px application-rail height. */\n size?: AppTabSize;\n /** Maximum visible label width in CSS pixels before ellipsis. */\n labelMaxWidth?: number;\n /** Decorative dormant content for the close button. Must not contain interactive descendants. */\n closeIcon?: SafeHtml;\n selectAction?: string;\n closeAction?: string;\n className?: string;\n /** Render as an unanimated loading skeleton, disabling select/close and dragging. */\n placeholder?: boolean;\n rootAttributes?: AppTabRootAttributes;\n /** Native named-slot assignment when composed inside a web component. */\n slot?: string;\n}\n\nfunction CloseIcon() {\n return (\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2.25\"\n stroke-linecap=\"round\"\n >\n <path d=\"m7 7 10 10\"></path>\n <path d=\"M17 7 7 17\"></path>\n </svg>\n );\n}\n\nexport function AppTab({\n id,\n name,\n selected = false,\n closable = true,\n draggable = false,\n leading,\n trailing,\n presentation = 'pill',\n size = 'default',\n labelMaxWidth,\n closeIcon,\n selectAction = 'select-tab',\n closeAction = 'close-tab',\n className = '',\n placeholder = false,\n rootAttributes = {},\n slot,\n}: AppTabProps) {\n const keyshortcuts = [\n closable ? 'Delete Backspace' : '',\n draggable ? 'Alt+Shift+ArrowLeft Alt+Shift+ArrowRight' : '',\n ]\n .filter(Boolean)\n .join(' ');\n const extensionAttributes = filterDataAttributes(\n rootAttributes,\n PROTECTED_ROOT_DATA_ATTRIBUTES,\n );\n return (\n <div\n {...extensionAttributes}\n class={`kui-app-tab ${className}`.trim()}\n data-component=\"app-tab\"\n data-tab-id={id}\n data-selected={String(selected)}\n data-presentation={presentation}\n data-size={size}\n data-placeholder={placeholder ? 'true' : undefined}\n draggable={placeholder ? 'false' : draggable ? 'true' : 'false'}\n aria-busy={placeholder ? 'true' : undefined}\n style={\n labelMaxWidth === undefined\n ? undefined\n : `--kui-app-tab-label-max-width:${labelMaxWidth}px`\n }\n slot={slot}\n >\n {closable && (\n <button\n type=\"button\"\n tabindex=\"-1\"\n class=\"kui-app-tab__close\"\n data-action={placeholder ? undefined : closeAction}\n data-tab-id={id}\n disabled={placeholder || undefined}\n aria-label={`Close ${name}`}\n title={`Close ${name}`}\n >\n <span class=\"kui-app-tab__close-icon\" aria-hidden=\"true\">\n {closeIcon ?? <CloseIcon />}\n </span>\n </button>\n )}\n <button\n type=\"button\"\n class=\"kui-app-tab__select\"\n role=\"tab\"\n aria-selected={String(selected)}\n aria-label={presentation === 'icon-only' ? name : undefined}\n aria-keyshortcuts={placeholder ? undefined : keyshortcuts || undefined}\n data-action={placeholder ? undefined : selectAction}\n data-tab-id={id}\n disabled={placeholder || undefined}\n tabindex={placeholder ? '-1' : selected ? '0' : '-1'}\n >\n {leading}\n <span\n class=\"kui-app-tab__name\"\n aria-hidden={presentation === 'icon-only' ? 'true' : undefined}\n >\n {placeholder ? <Skeleton width={em(7)} /> : name}\n </span>\n {closable || trailing ? (\n <span class=\"kui-app-tab__trailing\">{trailing}</span>\n ) : undefined}\n </button>\n </div>\n );\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/css-values.ts"],"names":[],"mappings":"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","file":"chunk-MO56ZZLG.js","sourcesContent":["declare const cssValueBrand: unique symbol;\ndeclare const cssLengthBrand: unique symbol;\ndeclare const cssLengthExpressionBrand: unique symbol;\ndeclare const cssFlexBrand: unique symbol;\ndeclare const cssColorBrand: unique symbol;\ndeclare const cssForegroundColorBrand: unique symbol;\n\n/**\n * A complete typed CSS value minted by a property-specific Kerf UI builder.\n *\n * This brand is an authoring correctness aid, not a sanitizer or security\n * boundary. Prefer the narrower property grammar, such as {@link CssLength},\n * whenever one is available.\n */\nexport type CssValue = string & {\n readonly [cssValueBrand]: 'CssValue';\n};\n\n/**\n * A complete CSS length-percentage value suitable for dimension-valued UI\n * props. Despite the concise name, percentages are intentionally included.\n */\nexport type CssLength = CssValue & {\n readonly [cssLengthBrand]: 'CssLength';\n};\n\n/**\n * An incomplete arithmetic expression. Wrap it with {@link calc} before\n * passing it to a prop that accepts {@link CssLength}.\n */\nexport type CssLengthExpression = string & {\n readonly [cssLengthExpressionBrand]: 'CssLengthExpression';\n};\n\n/** A complete CSS `flex` shorthand. It is not interchangeable with a length. */\nexport type CssFlex = CssValue & {\n readonly [cssFlexBrand]: 'CssFlex';\n};\n\n/** A complete CSS color value. It is not interchangeable with a length. */\nexport type CssColor = CssValue & {\n readonly [cssColorBrand]: 'CssColor';\n};\n\n/**\n * A complete CSS color meant to paint a foreground: text or an icon drawn over\n * a surface. Mint it with {@link uiColor} and a foreground token name\n * ({@link UiForegroundColorName}) or with {@link foregroundColorVar}. It is a\n * {@link CssColor}, but a plain `CssColor` is not a foreground color: fill,\n * border, and surface tokens (including the `success` / `warning` / `danger` /\n * `pop` fill aliases) are pale backgrounds that leave a foreground nearly\n * invisible.\n */\nexport type CssForegroundColor = CssColor & {\n readonly [cssForegroundColorBrand]: 'CssForegroundColor';\n};\n\nexport type CssFlexKeyword = 'none' | 'auto' | 'initial';\nexport type CssFlexBasis =\n | CssLength\n | 'auto'\n | 'content'\n | 'min-content'\n | 'max-content'\n | 'fit-content';\nexport type CssSizeKeyword =\n 'auto' | 'min-content' | 'max-content' | 'fit-content';\n/** A complete width/height value accepted by dimension-valued UI props. */\nexport type CssSize = CssLength | CssSizeKeyword;\n\nconst uiColorNames = [\n 'accent',\n 'accent-text',\n 'border',\n 'border-quiet',\n 'brand-border-loud',\n 'brand-border-normal',\n 'brand-border-quiet',\n 'brand-fill-loud',\n 'brand-fill-normal',\n 'brand-fill-quiet',\n 'brand-on-fill',\n 'brand-on-loud',\n 'brand-on-normal',\n 'brand-on-quiet',\n 'danger',\n 'danger-border-loud',\n 'danger-border-normal',\n 'danger-border-quiet',\n 'danger-fill-loud',\n 'danger-fill-normal',\n 'danger-fill-quiet',\n 'danger-on-loud',\n 'danger-on-normal',\n 'danger-on-quiet',\n 'danger-text',\n 'neutral-border-loud',\n 'neutral-border-normal',\n 'neutral-border-quiet',\n 'neutral-fill-loud',\n 'neutral-fill-normal',\n 'neutral-fill-quiet',\n 'neutral-on-loud',\n 'neutral-on-normal',\n 'neutral-on-quiet',\n 'pop',\n 'pop-border-loud',\n 'pop-border-normal',\n 'pop-border-quiet',\n 'pop-fill-loud',\n 'pop-fill-normal',\n 'pop-fill-quiet',\n 'pop-on-fill',\n 'pop-on-loud',\n 'pop-on-normal',\n 'pop-on-quiet',\n 'pop-text',\n 'success',\n 'success-border-loud',\n 'success-border-normal',\n 'success-border-quiet',\n 'success-fill-loud',\n 'success-fill-normal',\n 'success-fill-quiet',\n 'success-on-fill',\n 'success-on-loud',\n 'success-on-normal',\n 'success-on-quiet',\n 'success-text',\n 'surface',\n 'surface-lowered',\n 'surface-raised',\n 'text',\n 'text-link',\n 'text-quiet',\n 'warning',\n 'warning-border-loud',\n 'warning-border-normal',\n 'warning-border-quiet',\n 'warning-fill-loud',\n 'warning-fill-normal',\n 'warning-fill-quiet',\n 'warning-on-fill',\n 'warning-on-loud',\n 'warning-on-normal',\n 'warning-on-quiet',\n 'warning-text',\n] as const;\n\n/** Names of the public `--kui-color-*` semantic tokens. */\nexport type UiColorName = (typeof uiColorNames)[number];\n\n/**\n * The semantic tokens that paint a foreground: the `*-on-*` roles, the text\n * roles, and their `*-text` compatibility aliases. The bare `success`,\n * `warning`, `danger`, `pop`, and `accent` aliases are quiet fills, not\n * foregrounds.\n */\nexport type UiForegroundColorName = Extract<\n UiColorName,\n | `${string}-on-${string}`\n | `${string}-text`\n | 'text'\n | 'text-quiet'\n | 'text-link'\n>;\n\n/** Kerf UI's complete spacing-token vocabulary. `s` and `xl` are exceptions. */\nexport type UiSpaceName = 'none' | '2xs' | 'xs' | 's' | 'm' | 'l' | 'xl';\n\nconst spaceVariables = {\n none: '--kui-space-none',\n '2xs': '--kui-space-2xs',\n xs: '--kui-space-xs',\n s: '--kui-space-s',\n m: '--kui-space-m',\n l: '--kui-space-l',\n xl: '--kui-space-xl',\n} as const satisfies Record<UiSpaceName, string>;\n\nfunction serializeNumber(value: number, helper: string): string {\n if (!Number.isFinite(value))\n throw new RangeError(`${helper}() requires a finite number.`);\n return String(Object.is(value, -0) ? 0 : value);\n}\n\nfunction serializeNonnegativeNumber(value: number, helper: string): string {\n const serialized = serializeNumber(value, helper);\n if (value < 0)\n throw new RangeError(`${helper}() requires nonnegative flex factors.`);\n return serialized;\n}\n\nfunction validateCustomPropertyName(\n name: string,\n helper: string,\n example: string,\n): void {\n if (!/^--[A-Za-z_][A-Za-z0-9_-]*$/.test(name))\n throw new TypeError(\n `${helper}() requires an ASCII custom property name such as ${example}.`,\n );\n}\n\nfunction dimension(value: number, unit: string, helper: string): CssLength {\n return `${serializeNumber(value, helper)}${unit}` as CssLength;\n}\n\n/** Create a complete pixel length. */\nexport function px(value: number): CssLength {\n return dimension(value, 'px', 'px');\n}\n\n/** Create a complete root-font-relative length. */\nexport function rem(value: number): CssLength {\n return dimension(value, 'rem', 'rem');\n}\n\n/** Create a complete current-font-relative length. */\nexport function em(value: number): CssLength {\n return dimension(value, 'em', 'em');\n}\n\n/** Create a complete percentage length. */\nexport function pct(value: number): CssLength {\n return dimension(value, '%', 'pct');\n}\n\n/** Resolve a Kerf UI spacing step to its public custom property. */\nexport function space(name: UiSpaceName): CssLength {\n const variable = spaceVariables[name];\n if (variable === undefined)\n throw new RangeError(\n `space() received unknown spacing name ${String(name)}.`,\n );\n return `var(${variable})` as CssLength;\n}\n\n/**\n * Reference an application-owned custom property whose contract is a CSS\n * length-percentage. The deliberately narrow name grammar keeps this helper\n * from becoming an arbitrary CSS-string constructor.\n */\nexport function lengthVar(\n name: `--${string}`,\n fallback?: CssLength,\n): CssLength {\n validateCustomPropertyName(name, 'lengthVar', '--app-gap');\n return `var(${name}${fallback === undefined ? '' : `, ${fallback}`})` as CssLength;\n}\n\n/** Combine two or more complete lengths into a non-standalone sum. */\nexport function plus(\n first: CssLength,\n second: CssLength,\n ...rest: readonly CssLength[]\n): CssLengthExpression {\n return [first, second, ...rest].join(' + ') as CssLengthExpression;\n}\n\n/** Turn a typed length expression into a complete CSS `calc()` value. */\nexport function calc(expression: CssLengthExpression): CssLength {\n return `calc(${expression})` as CssLength;\n}\n\n/** Build a complete, structured CSS flex shorthand. */\nexport function flex(\n grow: number,\n shrink = 1,\n basis: CssFlexBasis = 'auto',\n): CssFlex {\n return `${serializeNonnegativeNumber(grow, 'flex')} ${serializeNonnegativeNumber(shrink, 'flex')} ${basis}` as CssFlex;\n}\n\n/**\n * The brand {@link uiColor} returns for a token name: a\n * {@link CssForegroundColor} for a foreground token, otherwise a plain\n * {@link CssColor}.\n */\nexport type UiColor<Name extends UiColorName> =\n Name extends UiForegroundColorName ? CssForegroundColor : CssColor;\n\n/**\n * Resolve a public Kerf UI semantic color token. A foreground token name\n * (`success-on-quiet`, `text-quiet`, …) returns a {@link CssForegroundColor};\n * any other token returns a plain {@link CssColor}.\n */\nexport function uiColor<Name extends UiColorName>(name: Name): UiColor<Name> {\n if (!(uiColorNames as readonly string[]).includes(name))\n throw new RangeError(\n `uiColor() received unknown color name ${String(name)}.`,\n );\n return `var(--kui-color-${name})` as UiColor<Name>;\n}\n\n/** Reference an application-owned custom property whose contract is a color. */\nexport function colorVar(name: `--${string}`, fallback?: CssColor): CssColor {\n validateCustomPropertyName(name, 'colorVar', '--app-color');\n return `var(${name}${fallback === undefined ? '' : `, ${fallback}`})` as CssColor;\n}\n\n/**\n * Reference an application-owned custom property whose contract is a\n * foreground color (text or an icon over a surface). Use it where a prop\n * accepts only {@link CssForegroundColor}, such as `SelectChoice.color`.\n */\nexport function foregroundColorVar(\n name: `--${string}`,\n fallback?: CssForegroundColor,\n): CssForegroundColor {\n validateCustomPropertyName(name, 'foregroundColorVar', '--app-icon-color');\n return `var(${name}${fallback === undefined ? '' : `, ${fallback}`})` as CssForegroundColor;\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/list-item.tsx"],"names":[],"mappings":"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","file":"chunk-NMGTHYW3.js","sourcesContent":["import type { SafeHtml } from 'kerfjs';\n\nimport { em } from './css-values.js';\nimport { filterDataAttributes } from './extension-attributes.js';\nimport { LoadingSpinner } from './loading-spinner.js';\nimport type { KerfUiContent } from './semantic-content.js';\nimport { Skeleton } from './skeleton.js';\n\nconst PROTECTED_ROOT_DATA_ATTRIBUTES = new Set([\n 'data-component',\n 'data-action',\n 'data-item-id',\n 'data-has-icon',\n 'data-has-description',\n 'data-multiline',\n 'data-density',\n 'data-divider',\n 'data-busy',\n 'data-state',\n]);\n\ntype ListItemRootAttributes = Readonly<\n Record<`data-${string}`, string | undefined> & {\n 'data-component'?: never;\n 'data-action'?: never;\n 'data-item-id'?: never;\n 'data-has-icon'?: never;\n 'data-has-description'?: never;\n 'data-multiline'?: never;\n 'data-density'?: never;\n 'data-divider'?: never;\n 'data-busy'?: never;\n 'data-state'?: never;\n }\n>;\n\nexport interface ListItemProps {\n label: string | SafeHtml;\n /** App-owned supporting text rendered in the component's stable label stack. */\n description?: string | SafeHtml;\n icon?: SafeHtml;\n trailing?: KerfUiContent;\n /** Dormant status metadata rendered before trailing content. */\n status?: string | SafeHtml;\n /** Show a progress indicator and expose the row as busy without replacing its content. */\n busy?: boolean;\n density?: 'standard' | 'compact';\n divider?: 'none' | 'before' | 'after' | 'both';\n selected?: boolean;\n action: string;\n itemId?: string;\n className?: string;\n pressed?: boolean;\n accessibleLabel?: string;\n title?: string;\n multiline?: boolean;\n state?: string;\n disabled?: boolean;\n tabIndex?: number;\n /** Render the row as an unanimated loading skeleton, disabling its action. */\n placeholder?: boolean;\n rootAttributes?: ListItemRootAttributes;\n /** Native named-slot assignment when composed inside a web component. */\n slot?: string;\n}\n\nexport function ListItem({\n label,\n description,\n icon,\n trailing,\n status,\n busy = false,\n density = 'standard',\n divider = 'none',\n selected = false,\n action,\n itemId,\n className = '',\n pressed,\n accessibleLabel,\n title,\n multiline = false,\n state,\n disabled = false,\n tabIndex,\n placeholder = false,\n rootAttributes = {},\n slot,\n}: ListItemProps) {\n const extensionAttributes = filterDataAttributes(\n rootAttributes,\n PROTECTED_ROOT_DATA_ATTRIBUTES,\n );\n return (\n <button\n {...extensionAttributes}\n type=\"button\"\n class={`kui-list-item ${className}`.trim()}\n title={placeholder ? undefined : title}\n disabled={disabled || placeholder}\n tabindex={placeholder ? -1 : tabIndex}\n data-component=\"list-item\"\n data-action={placeholder ? undefined : action}\n data-item-id={itemId}\n data-has-icon={String(Boolean(icon))}\n data-has-description={String(Boolean(description))}\n data-multiline={multiline ? 'true' : undefined}\n data-density={density}\n data-divider={divider}\n data-busy={busy ? 'true' : undefined}\n data-state={state}\n data-placeholder={placeholder ? 'true' : undefined}\n aria-label={accessibleLabel}\n aria-current={selected ? 'page' : undefined}\n aria-pressed={pressed === undefined ? undefined : String(pressed)}\n aria-busy={placeholder || busy ? 'true' : undefined}\n slot={slot}\n >\n {icon && (\n <span class=\"kui-list-item__icon\">\n {placeholder ? <Skeleton width={em(1)} height={em(1)} /> : icon}\n </span>\n )}\n <span class=\"kui-list-item__label\">\n <span class=\"kui-list-item__primary-label\">\n {placeholder ? <Skeleton width={em(9)} /> : label}\n </span>\n {!placeholder && description && (\n <span class=\"kui-list-item__description\">{description}</span>\n )}\n </span>\n {(busy || status || trailing) && (\n <span class=\"kui-list-item__trailing\">\n {placeholder ? (\n <Skeleton width={em(2.5)} />\n ) : (\n <>\n {busy && <LoadingSpinner />}\n {status && <span class=\"kui-list-item__status\">{status}</span>}\n {trailing}\n </>\n )}\n </span>\n )}\n </button>\n );\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/skeleton.tsx"],"names":[],"mappings":";;;AAkBA,SAAS,UAAA,CACP,KAAA,EACA,MAAA,EACA,MAAA,EACoB;AACpB,EAAA,MAAM,KAAA,GAAQ;AAAA,IACZ,KAAA,IAAS,SAAS,KAAK,CAAA,CAAA;AAAA,IACvB,MAAA,IAAU,UAAU,MAAM,CAAA,CAAA;AAAA,IAC1B,MAAA,IAAU,yBAAyB,MAAM,CAAA;AAAA,GAC3C,CAAE,OAAO,OAAO,CAAA;AAChB,EAAA,OAAO,KAAA,CAAM,MAAA,GAAS,KAAA,CAAM,IAAA,CAAK,GAAG,CAAA,GAAI,MAAA;AAC1C;AAQO,SAAS,QAAA,CAAS;AAAA,EACvB,KAAA;AAAA,EACA,MAAA;AAAA,EACA,MAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,SAAA,GAAY,EAAA;AAAA,EACZ;AACF,CAAA,EAAkB;AAChB,EAAA,MAAM,IAAA,GAAO;AAAA,IACX,IAAA,EAAM,QAAQ,KAAA,GAAQ,MAAA;AAAA,IACtB,YAAA,EAAc,KAAA;AAAA,IACd,aAAA,EAAe,QAAQ,MAAA,GAAa;AAAA,GACtC;AACA,EAAA,IAAI,KAAA,IAAS,QAAQ,CAAA,EAAG;AACtB,IAAA,uBACE,GAAA;AAAA,MAAC,MAAA;AAAA,MAAA;AAAA,QACC,KAAA,EAAO,CAAA,mBAAA,EAAsB,SAAS,CAAA,CAAA,CAAG,IAAA,EAAK;AAAA,QAC9C,gBAAA,EAAe,UAAA;AAAA,QACf,KAAA,EAAO,KAAA,GAAQ,CAAA,MAAA,EAAS,KAAK,CAAA,CAAA,GAAK,MAAA;AAAA,QACjC,GAAG,IAAA;AAAA,QACJ,IAAA;AAAA,QAEC,QAAA,EAAA,KAAA,CAAM,KAAK,EAAE,MAAA,EAAQ,OAAM,EAAG,CAAC,GAAG,KAAA,qBACjC,GAAA;AAAA,UAAC,MAAA;AAAA,UAAA;AAAA,YACC,KAAA,EAAM,cAAA;AAAA,YACN,aAAA,EAAY,MAAA;AAAA,YACZ,KAAA,EAAO,UAAA;AAAA,cACL,KAAA,KAAU,KAAA,GAAQ,CAAA,GAAI,GAAA,CAAI,EAAE,CAAA,GAAI,MAAA;AAAA,cAChC,MAAA;AAAA,cACA;AAAA;AACF;AAAA,SAEH;AAAA;AAAA,KACH;AAAA,EAEJ;AACA,EAAA,uBACE,GAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAO,CAAA,aAAA,EAAgB,SAAS,CAAA,CAAA,CAAG,IAAA,EAAK;AAAA,MACxC,gBAAA,EAAe,UAAA;AAAA,MACf,KAAA,EAAO,UAAA,CAAW,KAAA,EAAO,MAAA,EAAQ,MAAM,CAAA;AAAA,MACtC,GAAG,IAAA;AAAA,MACJ;AAAA;AAAA,GACD;AAEL","file":"chunk-ULCDZTWL.js","sourcesContent":["import { type CssLength, type CssSize, pct } from './css-values.js';\n\nexport interface SkeletonProps {\n /** Typed width or intrinsic sizing keyword. Defaults to filling its slot. */\n width?: CssSize;\n /** Typed height or intrinsic sizing keyword. Defaults to a single text line. */\n height?: CssSize;\n /** Typed corner-radius override. Defaults to the small radius token. */\n radius?: CssLength;\n /** Render this many stacked lines (the last one shorter), for multi-line text. */\n lines?: number;\n /** Accessible label. Omit to keep the block decorative (`aria-hidden`). */\n label?: string;\n className?: string;\n /** Native named-slot assignment when composed inside a web component. */\n slot?: string;\n}\n\nfunction blockStyle(\n width?: CssSize,\n height?: CssSize,\n radius?: CssLength,\n): string | undefined {\n const parts = [\n width && `width:${width}`,\n height && `height:${height}`,\n radius && `--kui-skeleton-radius:${radius}`,\n ].filter(Boolean);\n return parts.length ? parts.join(';') : undefined;\n}\n\n/**\n * A subtle, deliberately **unanimated** loading placeholder block. Use it for a\n * value slot whose content is not yet known, on its own or via a component's\n * `placeholder` prop. Decorative by default (`aria-hidden`); pass `label` to\n * announce it. Sizes to its slot unless `width`/`height` are given.\n */\nexport function Skeleton({\n width,\n height,\n radius,\n lines,\n label,\n className = '',\n slot,\n}: SkeletonProps) {\n const a11y = {\n role: label ? 'img' : undefined,\n 'aria-label': label,\n 'aria-hidden': label ? undefined : ('true' as const),\n };\n if (lines && lines > 1) {\n return (\n <span\n class={`kui-skeleton-lines ${className}`.trim()}\n data-component=\"skeleton\"\n style={width ? `width:${width}` : undefined}\n {...a11y}\n slot={slot}\n >\n {Array.from({ length: lines }, (_, index) => (\n <span\n class=\"kui-skeleton\"\n aria-hidden=\"true\"\n style={blockStyle(\n index === lines - 1 ? pct(60) : undefined,\n height,\n radius,\n )}\n ></span>\n ))}\n </span>\n );\n }\n return (\n <span\n class={`kui-skeleton ${className}`.trim()}\n data-component=\"skeleton\"\n style={blockStyle(width, height, radius)}\n {...a11y}\n slot={slot}\n ></span>\n );\n}\n"]}