@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.
- package/ai/public-api-signatures-v1.md +19 -4
- package/ai/skill.md +15 -15
- package/ai/webawesome-jsx-signatures-v1.md +1 -1
- package/dist/app-tab.d.ts +10 -1
- package/dist/app-tab.js +1 -1
- package/dist/catalog.js +4 -4
- package/dist/{chunk-MO56ZZLG.js → chunk-2CE22Q2K.js} +5 -2
- package/dist/chunk-2CE22Q2K.js.map +1 -0
- package/dist/{chunk-UPZ6EEJN.js → chunk-2S7OWAEV.js} +3 -3
- package/dist/{chunk-UPZ6EEJN.js.map → chunk-2S7OWAEV.js.map} +1 -1
- package/dist/{chunk-KX7DP2GO.js → chunk-3S7OUHUN.js} +3 -3
- package/dist/{chunk-KX7DP2GO.js.map → chunk-3S7OUHUN.js.map} +1 -1
- package/dist/{chunk-JMGKM7RK.js → chunk-4XE2CDYB.js} +2 -2
- package/dist/{chunk-JMGKM7RK.js.map → chunk-4XE2CDYB.js.map} +1 -1
- package/dist/{chunk-S4QRSBFI.js → chunk-7XJO4D6A.js} +2 -2
- package/dist/{chunk-S4QRSBFI.js.map → chunk-7XJO4D6A.js.map} +1 -1
- package/dist/{chunk-UBH743HV.js → chunk-ABAXNUXU.js} +3 -3
- package/dist/{chunk-UBH743HV.js.map → chunk-ABAXNUXU.js.map} +1 -1
- package/dist/{chunk-NZXWU4R5.js → chunk-DNCOFEUL.js} +2 -2
- package/dist/{chunk-NZXWU4R5.js.map → chunk-DNCOFEUL.js.map} +1 -1
- package/dist/{chunk-5FZZMIMD.js → chunk-IR53BQUJ.js} +16 -12
- package/dist/chunk-IR53BQUJ.js.map +1 -0
- package/dist/{chunk-JM53SMVF.js → chunk-JGFZ42ZF.js} +3 -3
- package/dist/{chunk-JM53SMVF.js.map → chunk-JGFZ42ZF.js.map} +1 -1
- package/dist/{chunk-ULCDZTWL.js → chunk-KAQUUTZR.js} +4 -2
- package/dist/chunk-KAQUUTZR.js.map +1 -0
- package/dist/{chunk-NMGTHYW3.js → chunk-LIOXPQ7N.js} +4 -4
- package/dist/chunk-LIOXPQ7N.js.map +1 -0
- package/dist/{chunk-DVZCMMB3.js → chunk-M6GLI6A6.js} +3 -3
- package/dist/{chunk-DVZCMMB3.js.map → chunk-M6GLI6A6.js.map} +1 -1
- package/dist/{chunk-D547PH4L.js → chunk-OSCL5TKR.js} +3 -3
- package/dist/{chunk-D547PH4L.js.map → chunk-OSCL5TKR.js.map} +1 -1
- package/dist/{chunk-5PMGQDG7.js → chunk-SV22UIW4.js} +2 -2
- package/dist/{chunk-5PMGQDG7.js.map → chunk-SV22UIW4.js.map} +1 -1
- package/dist/{chunk-AEPXGCNF.js → chunk-T7C2VZUB.js} +2 -2
- package/dist/{chunk-AEPXGCNF.js.map → chunk-T7C2VZUB.js.map} +1 -1
- package/dist/{chunk-O3RCCSQJ.js → chunk-YZV2PTY4.js} +3 -3
- package/dist/{chunk-O3RCCSQJ.js.map → chunk-YZV2PTY4.js.map} +1 -1
- package/dist/css-values.d.ts +3 -1
- package/dist/css-values.js +1 -1
- package/dist/grid.js +1 -1
- package/dist/index.js +15 -15
- package/dist/list-action-row.js +1 -1
- package/dist/list-header.js +1 -1
- package/dist/list-item.js +1 -1
- package/dist/list.js +1 -1
- package/dist/nav-stack.js +1 -1
- package/dist/row.js +1 -1
- package/dist/segmented-control.js +1 -1
- package/dist/select.js +1 -1
- package/dist/skeleton.d.ts +5 -1
- package/dist/skeleton.js +1 -1
- package/dist/spacer.js +1 -1
- package/dist/split-view.js +1 -1
- package/dist/state-banner.js +1 -1
- package/dist/styles/app-tab.css +9 -0
- package/dist/styles/skeleton.css +4 -1
- package/dist/toolbar-text.js +1 -1
- package/dist/value-table.js +1 -1
- package/dist/wire-sidebar.js +1 -1
- package/dist/wire-sidebar.js.map +1 -1
- package/docs/accessibility.md +4 -1
- package/docs/collapsible-panel.md +2 -1
- package/docs/component-contract.md +8 -3
- package/docs/recipes.md +6 -4
- package/docs/ux-demo.md +1 -1
- package/package.json +1 -1
- package/ux-demo/recipes/collapsible-sidebar.tsx +12 -11
- package/ux-demo/recipes/composer-form.tsx +2 -0
- package/ux-demo/recipes/loading-inspector.tsx +4 -4
- package/dist/chunk-5FZZMIMD.js.map +0 -1
- package/dist/chunk-MO56ZZLG.js.map +0 -1
- package/dist/chunk-NMGTHYW3.js.map +0 -1
- package/dist/chunk-ULCDZTWL.js.map +0 -1
package/dist/skeleton.d.ts
CHANGED
|
@@ -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
package/dist/spacer.js
CHANGED
package/dist/split-view.js
CHANGED
package/dist/state-banner.js
CHANGED
package/dist/styles/app-tab.css
CHANGED
|
@@ -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;
|
package/dist/styles/skeleton.css
CHANGED
|
@@ -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%;
|
package/dist/toolbar-text.js
CHANGED
package/dist/value-table.js
CHANGED
package/dist/wire-sidebar.js
CHANGED
|
@@ -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);
|
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 (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"]}
|
package/docs/accessibility.md
CHANGED
|
@@ -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.
|
|
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
|
|
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.
|
|
317
|
-
|
|
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.
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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,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;
|
|
147
|
-
//
|
|
148
|
-
//
|
|
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="
|
|
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
|
-
<
|
|
94
|
-
<Text
|
|
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
|
-
</
|
|
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":";AAsEA,IAAM,YAAA,GAAe;AAAA,EACnB,QAAA;AAAA,EACA,aAAA;AAAA,EACA,QAAA;AAAA,EACA,cAAA;AAAA,EACA,mBAAA;AAAA,EACA,qBAAA;AAAA,EACA,oBAAA;AAAA,EACA,iBAAA;AAAA,EACA,mBAAA;AAAA,EACA,kBAAA;AAAA,EACA,eAAA;AAAA,EACA,eAAA;AAAA,EACA,iBAAA;AAAA,EACA,gBAAA;AAAA,EACA,QAAA;AAAA,EACA,oBAAA;AAAA,EACA,sBAAA;AAAA,EACA,qBAAA;AAAA,EACA,kBAAA;AAAA,EACA,oBAAA;AAAA,EACA,mBAAA;AAAA,EACA,gBAAA;AAAA,EACA,kBAAA;AAAA,EACA,iBAAA;AAAA,EACA,aAAA;AAAA,EACA,qBAAA;AAAA,EACA,uBAAA;AAAA,EACA,sBAAA;AAAA,EACA,mBAAA;AAAA,EACA,qBAAA;AAAA,EACA,oBAAA;AAAA,EACA,iBAAA;AAAA,EACA,mBAAA;AAAA,EACA,kBAAA;AAAA,EACA,KAAA;AAAA,EACA,iBAAA;AAAA,EACA,mBAAA;AAAA,EACA,kBAAA;AAAA,EACA,eAAA;AAAA,EACA,iBAAA;AAAA,EACA,gBAAA;AAAA,EACA,aAAA;AAAA,EACA,aAAA;AAAA,EACA,eAAA;AAAA,EACA,cAAA;AAAA,EACA,UAAA;AAAA,EACA,SAAA;AAAA,EACA,qBAAA;AAAA,EACA,uBAAA;AAAA,EACA,sBAAA;AAAA,EACA,mBAAA;AAAA,EACA,qBAAA;AAAA,EACA,oBAAA;AAAA,EACA,iBAAA;AAAA,EACA,iBAAA;AAAA,EACA,mBAAA;AAAA,EACA,kBAAA;AAAA,EACA,cAAA;AAAA,EACA,SAAA;AAAA,EACA,iBAAA;AAAA,EACA,gBAAA;AAAA,EACA,MAAA;AAAA,EACA,WAAA;AAAA,EACA,YAAA;AAAA,EACA,SAAA;AAAA,EACA,qBAAA;AAAA,EACA,uBAAA;AAAA,EACA,sBAAA;AAAA,EACA,mBAAA;AAAA,EACA,qBAAA;AAAA,EACA,oBAAA;AAAA,EACA,iBAAA;AAAA,EACA,iBAAA;AAAA,EACA,mBAAA;AAAA,EACA,kBAAA;AAAA,EACA;AACF,CAAA;AAuBA,IAAM,cAAA,GAAiB;AAAA,EACrB,IAAA,EAAM,kBAAA;AAAA,EACN,KAAA,EAAO,iBAAA;AAAA,EACP,EAAA,EAAI,gBAAA;AAAA,EACJ,CAAA,EAAG,eAAA;AAAA,EACH,CAAA,EAAG,eAAA;AAAA,EACH,CAAA,EAAG,eAAA;AAAA,EACH,EAAA,EAAI;AACN,CAAA;AAEA,SAAS,eAAA,CAAgB,OAAe,MAAA,EAAwB;AAC9D,EAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,KAAK,CAAA;AACxB,IAAA,MAAM,IAAI,UAAA,CAAW,CAAA,EAAG,MAAM,CAAA,4BAAA,CAA8B,CAAA;AAC9D,EAAA,OAAO,OAAO,MAAA,CAAO,EAAA,CAAG,OAAO,EAAE,CAAA,GAAI,IAAI,KAAK,CAAA;AAChD;AAEA,SAAS,0BAAA,CAA2B,OAAe,MAAA,EAAwB;AACzE,EAAA,MAAM,UAAA,GAAa,eAAA,CAAgB,KAAA,EAAO,MAAM,CAAA;AAChD,EAAA,IAAI,KAAA,GAAQ,CAAA;AACV,IAAA,MAAM,IAAI,UAAA,CAAW,CAAA,EAAG,MAAM,CAAA,qCAAA,CAAuC,CAAA;AACvE,EAAA,OAAO,UAAA;AACT;AAEA,SAAS,0BAAA,CACP,IAAA,EACA,MAAA,EACA,OAAA,EACM;AACN,EAAA,IAAI,CAAC,6BAAA,CAA8B,IAAA,CAAK,IAAI,CAAA;AAC1C,IAAA,MAAM,IAAI,SAAA;AAAA,MACR,CAAA,EAAG,MAAM,CAAA,kDAAA,EAAqD,OAAO,CAAA,CAAA;AAAA,KACvE;AACJ;AAEA,SAAS,SAAA,CAAU,KAAA,EAAe,IAAA,EAAc,MAAA,EAA2B;AACzE,EAAA,OAAO,GAAG,eAAA,CAAgB,KAAA,EAAO,MAAM,CAAC,GAAG,IAAI,CAAA,CAAA;AACjD;AAGO,SAAS,GAAG,KAAA,EAA0B;AAC3C,EAAA,OAAO,SAAA,CAAU,KAAA,EAAO,IAAA,EAAM,IAAI,CAAA;AACpC;AAGO,SAAS,IAAI,KAAA,EAA0B;AAC5C,EAAA,OAAO,SAAA,CAAU,KAAA,EAAO,KAAA,EAAO,KAAK,CAAA;AACtC;AAGO,SAAS,GAAG,KAAA,EAA0B;AAC3C,EAAA,OAAO,SAAA,CAAU,KAAA,EAAO,IAAA,EAAM,IAAI,CAAA;AACpC;AAGO,SAAS,IAAI,KAAA,EAA0B;AAC5C,EAAA,OAAO,SAAA,CAAU,KAAA,EAAO,GAAA,EAAK,KAAK,CAAA;AACpC;AAGO,SAAS,MAAM,IAAA,EAA8B;AAClD,EAAA,MAAM,QAAA,GAAW,eAAe,IAAI,CAAA;AACpC,EAAA,IAAI,QAAA,KAAa,MAAA;AACf,IAAA,MAAM,IAAI,UAAA;AAAA,MACR,CAAA,sCAAA,EAAyC,MAAA,CAAO,IAAI,CAAC,CAAA,CAAA;AAAA,KACvD;AACF,EAAA,OAAO,OAAO,QAAQ,CAAA,CAAA,CAAA;AACxB;AAOO,SAAS,SAAA,CACd,MACA,QAAA,EACW;AACX,EAAA,0BAAA,CAA2B,IAAA,EAAM,aAAa,WAAW,CAAA;AACzD,EAAA,OAAO,CAAA,IAAA,EAAO,IAAI,CAAA,EAAG,QAAA,KAAa,SAAY,EAAA,GAAK,CAAA,EAAA,EAAK,QAAQ,CAAA,CAAE,CAAA,CAAA,CAAA;AACpE;AAGO,SAAS,IAAA,CACd,KAAA,EACA,MAAA,EAAA,GACG,IAAA,EACkB;AACrB,EAAA,OAAO,CAAC,KAAA,EAAO,MAAA,EAAQ,GAAG,IAAI,CAAA,CAAE,KAAK,KAAK,CAAA;AAC5C;AAGO,SAAS,KAAK,UAAA,EAA4C;AAC/D,EAAA,OAAO,QAAQ,UAAU,CAAA,CAAA,CAAA;AAC3B;AAGO,SAAS,IAAA,CACd,IAAA,EACA,MAAA,GAAS,CAAA,EACT,QAAsB,MAAA,EACb;AACT,EAAA,OAAO,CAAA,EAAG,0BAAA,CAA2B,IAAA,EAAM,MAAM,CAAC,CAAA,CAAA,EAAI,0BAAA,CAA2B,MAAA,EAAQ,MAAM,CAAC,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA;AAC3G;AAeO,SAAS,QAAkC,IAAA,EAA2B;AAC3E,EAAA,IAAI,CAAE,YAAA,CAAmC,QAAA,CAAS,IAAI,CAAA;AACpD,IAAA,MAAM,IAAI,UAAA;AAAA,MACR,CAAA,sCAAA,EAAyC,MAAA,CAAO,IAAI,CAAC,CAAA,CAAA;AAAA,KACvD;AACF,EAAA,OAAO,mBAAmB,IAAI,CAAA,CAAA,CAAA;AAChC;AAGO,SAAS,QAAA,CAAS,MAAqB,QAAA,EAA+B;AAC3E,EAAA,0BAAA,CAA2B,IAAA,EAAM,YAAY,aAAa,CAAA;AAC1D,EAAA,OAAO,CAAA,IAAA,EAAO,IAAI,CAAA,EAAG,QAAA,KAAa,SAAY,EAAA,GAAK,CAAA,EAAA,EAAK,QAAQ,CAAA,CAAE,CAAA,CAAA,CAAA;AACpE;AAOO,SAAS,kBAAA,CACd,MACA,QAAA,EACoB;AACpB,EAAA,0BAAA,CAA2B,IAAA,EAAM,sBAAsB,kBAAkB,CAAA;AACzE,EAAA,OAAO,CAAA,IAAA,EAAO,IAAI,CAAA,EAAG,QAAA,KAAa,SAAY,EAAA,GAAK,CAAA,EAAA,EAAK,QAAQ,CAAA,CAAE,CAAA,CAAA,CAAA;AACpE","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"]}
|