@kerfjs/ui 5.0.0-beta.76 → 5.0.0-beta.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/ai/application-ui-diagnostic-ids-v1.json +3 -0
- package/ai/component-catalog.json +333 -28
- package/ai/component-composition.json +224 -17
- package/ai/components/nav-stack.md +4 -3
- package/ai/components/select.md +1 -0
- package/ai/components/split-view.md +1 -0
- package/ai/components/surface-scaffold.md +11 -2
- package/ai/components/text.md +4 -2
- package/ai/components/toolbar-control-group.md +22 -3
- package/ai/components/toolbar-text.md +21 -4
- package/ai/components/toolbar.md +12 -4
- package/ai/public-api-signatures-v1.md +99 -127
- package/ai/skill.md +37 -3
- package/ai/webawesome-jsx-signatures-v1.md +1 -1
- package/dist/browser/nav-stack.js +2 -0
- package/dist/browser/split-view.js +2 -0
- package/dist/catalog-resources.d.ts +3 -3
- package/dist/catalog.d.ts +3 -3
- package/dist/catalog.js +6 -6
- package/dist/chunk-2GOCOLQL.js +270 -0
- package/dist/chunk-2GOCOLQL.js.map +1 -0
- package/dist/{chunk-ZVYA6TLR.js → chunk-4Y4L5YEI.js} +3 -1
- package/dist/chunk-4Y4L5YEI.js.map +1 -0
- package/dist/{chunk-XIZRXI7N.js → chunk-A7DYEERU.js} +13 -1
- package/dist/chunk-A7DYEERU.js.map +1 -0
- package/dist/{chunk-7QWIJEK5.js → chunk-DSLRNR43.js} +9 -8
- package/dist/chunk-DSLRNR43.js.map +1 -0
- package/dist/{chunk-AOAW55JN.js → chunk-DXQZ3BEO.js} +4 -1
- package/dist/chunk-DXQZ3BEO.js.map +1 -0
- package/dist/chunk-IKCUPTR2.js +170 -0
- package/dist/chunk-IKCUPTR2.js.map +1 -0
- package/dist/{chunk-HGSTMZZ6.js → chunk-JL4BBZP4.js} +5 -4
- package/dist/chunk-JL4BBZP4.js.map +1 -0
- package/dist/{chunk-IVLTEBBT.js → chunk-M6BWQMQY.js} +2 -2
- package/dist/{chunk-IVLTEBBT.js.map → chunk-M6BWQMQY.js.map} +1 -1
- package/dist/{chunk-72LLWYP2.js → chunk-NVKRZ5DI.js} +1 -1
- package/dist/chunk-NVKRZ5DI.js.map +1 -0
- package/dist/{chunk-GO2CZX4M.js → chunk-RRCD3A6G.js} +13 -5
- package/dist/chunk-RRCD3A6G.js.map +1 -0
- package/dist/{chunk-5PAVAR4B.js → chunk-VHXHLZ6R.js} +10 -1
- package/dist/chunk-VHXHLZ6R.js.map +1 -0
- package/dist/collapsible-panel.js +2 -2
- package/dist/index.d.ts +4 -3
- package/dist/index.js +9 -8
- package/dist/list-header.d.ts +1 -0
- package/dist/list-header.js +1 -1
- package/dist/nav-stack-B1Qc2e5q.d.ts +101 -0
- package/dist/nav-stack.d.ts +12 -93
- package/dist/nav-stack.js +5 -1
- package/dist/pane.d.ts +5 -2
- package/dist/pane.js +1 -1
- package/dist/select-register.js +3 -0
- package/dist/select-register.js.map +1 -1
- package/dist/select.d.ts +1 -0
- package/dist/select.js +1 -1
- package/dist/split-view.d.ts +9 -4
- package/dist/split-view.js +7 -3
- package/dist/split-view.js.map +1 -1
- package/dist/styles/components/actions/toolbar/toolbar.css +32 -0
- package/dist/styles/components/actions/toolbar-control-group/toolbar-control-group.css +105 -7
- package/dist/styles/components/actions/toolbar-text/toolbar-text.css +17 -0
- package/dist/styles/components/layout/pane/pane.css +7 -0
- package/dist/styles/components/navigation/nav-stack/nav-stack.css +17 -12
- package/dist/styles/components/navigation/tab-scaffold/tab-scaffold.css +17 -0
- package/dist/styles/components/surfaces/surface-scaffold/surface-scaffold.css +55 -0
- package/dist/styles/components/typography/text/text.css +19 -0
- package/dist/styles/nav-stack.css +17 -12
- package/dist/styles/pane.css +7 -0
- package/dist/styles/surface-scaffold.css +55 -0
- package/dist/styles/tab-scaffold.css +17 -0
- package/dist/styles/text.css +19 -0
- package/dist/styles/toolbar-control-group.css +105 -7
- package/dist/styles/toolbar-text.css +17 -0
- package/dist/styles/toolbar.css +32 -0
- package/dist/surface-scaffold-register.d.ts +4 -0
- package/dist/surface-scaffold-register.js +59 -0
- package/dist/surface-scaffold-register.js.map +1 -0
- package/dist/surface-scaffold.d.ts +10 -2
- package/dist/surface-scaffold.js +1 -1
- package/dist/tab-scaffold.d.ts +2 -0
- package/dist/tab-scaffold.js +1 -0
- package/dist/tab-scaffold.js.map +1 -1
- package/dist/text.d.ts +5 -2
- package/dist/text.js +1 -1
- package/dist/toolbar-control-group.d.ts +14 -2
- package/dist/toolbar-control-group.js +1 -1
- package/dist/toolbar-text.d.ts +10 -2
- package/dist/toolbar-text.js +1 -1
- package/dist/wire-nav-stack.js +58 -22
- package/dist/wire-nav-stack.js.map +1 -1
- package/dist/wire-toolbar-visibility.d.ts +13 -0
- package/dist/wire-toolbar-visibility.js +3 -0
- package/dist/wire-toolbar-visibility.js.map +1 -0
- package/dist/workbench.d.ts +5 -13
- package/dist/workbench.js +1 -1
- package/docs/accessibility.md +15 -0
- package/docs/collapsible-panel.md +4 -0
- package/docs/component-contract.md +38 -0
- package/docs/component-selection.md +56 -2
- package/docs/design/templates/toolbar/width-visibility-320-dark.svg +1 -0
- package/docs/design/templates/toolbar/width-visibility-320.svg +1 -0
- package/docs/design/templates/toolbar/width-visibility-600-dark.svg +1 -0
- package/docs/design/templates/toolbar/width-visibility-600.svg +1 -0
- package/docs/design/templates/toolbar-control-group/icon-tile-brand-dark.svg +1 -0
- package/docs/design/templates/toolbar-control-group/icon-tile-brand.svg +1 -0
- package/docs/design/templates/toolbar-control-group/icon-tile-danger-dark.svg +1 -0
- package/docs/design/templates/toolbar-control-group/icon-tile-danger.svg +1 -0
- package/docs/design/templates/toolbar-control-group/icon-tile-neutral-dark.svg +1 -0
- package/docs/design/templates/toolbar-control-group/icon-tile-neutral.svg +1 -0
- package/docs/design/templates/toolbar-control-group/icon-tile-success-dark.svg +1 -0
- package/docs/design/templates/toolbar-control-group/icon-tile-success.svg +1 -0
- package/docs/design/templates/toolbar-control-group/icon-tile-warning-dark.svg +1 -0
- package/docs/design/templates/toolbar-control-group/icon-tile-warning.svg +1 -0
- package/docs/design/templates/toolbar-control-group-dark.svg +1 -1
- package/docs/design/templates/toolbar-control-group.svg +1 -1
- package/docs/design/templates/toolbar-dark.svg +1 -1
- package/docs/design/templates/toolbar-text/dark-tone-dark.svg +1 -0
- package/docs/design/templates/toolbar-text/dark-tone.svg +1 -0
- package/docs/design/templates/toolbar-text-dark.svg +1 -1
- package/docs/design/templates/toolbar-text.svg +1 -1
- package/docs/design/templates/toolbar.svg +1 -1
- package/docs/design/templates.md +2 -0
- package/docs/layout.md +32 -1
- package/docs/nav-stack.md +35 -19
- package/docs/recipes.md +5 -1
- package/docs/split-view.md +7 -4
- package/docs/surface-scaffold.md +46 -0
- package/docs/tab-scaffold.md +57 -0
- package/docs/ui-doctor.md +23 -0
- package/docs/workbench.md +3 -2
- package/doctor/component-style-checks.mjs +224 -0
- package/doctor/index.mjs +42 -13
- package/llms.txt +34 -0
- package/package.json +11 -2
- package/ux-demo/recipes/list-detail-dialog.tsx +4 -0
- package/ux-demo/recipes/navigation-stack.tsx +2 -2
- package/dist/chunk-5PAVAR4B.js.map +0 -1
- package/dist/chunk-72LLWYP2.js.map +0 -1
- package/dist/chunk-7QWIJEK5.js.map +0 -1
- package/dist/chunk-AOAW55JN.js.map +0 -1
- package/dist/chunk-GO2CZX4M.js.map +0 -1
- package/dist/chunk-HGSTMZZ6.js.map +0 -1
- package/dist/chunk-SJY7KC45.js +0 -123
- package/dist/chunk-SJY7KC45.js.map +0 -1
- package/dist/chunk-XIZRXI7N.js.map +0 -1
- package/dist/chunk-ZVYA6TLR.js.map +0 -1
package/dist/tab-scaffold.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/navigation/tab-scaffold/tab-scaffold.tsx"],"names":[],"mappings":"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","file":"tab-scaffold.js","sourcesContent":["import type { SafeHtml } from 'kerfjs';\n\nimport type { KerfUiContent } from '../../../shared/content/semantic-content.js';\nimport { Badge } from '../../feedback/badge/badge.js';\nimport type { PaneAppearance } from '../../layout/pane/pane.js';\n\ninterface TabScaffoldTabBase<Id extends string> {\n id: Id;\n label: string;\n /** Decorative icon shown above the label in the bottom bar. */\n icon?: SafeHtml;\n /** The tab's content — typically a `NavStack` so each tab keeps its own stack. */\n content: KerfUiContent;\n /** Background of this scene's scrolling work surface. */\n appearance?: PaneAppearance;\n}\n\n/** A tab with an optional count or short-status badge. */\ninterface TabScaffoldTabCountBadge {\n /**\n * Optional count or short status shown as a solid danger `Badge` at the\n * top-trailing corner of the tab icon (the iOS tab-bar badge). Omitted, `''`,\n * or non-finite numbers render no badge. The visual badge is `aria-hidden`;\n * its meaning reaches assistive technology through `badgeLabel`.\n *\n * Pass `true` instead for the text-free dot (new content without a count).\n */\n badge?: string | number;\n /**\n * Localized phrase folded into the tab's accessible name as\n * `\"<label>, <badgeLabel>\"` (for example `\"3 unread\"` → `\"Inbox, 3 unread\"`).\n * Defaults to the badge text itself. Ignored when no badge renders.\n */\n badgeLabel?: string;\n}\n\n/** A tab with the text-free dot badge (new content without a count). */\ninterface TabScaffoldTabDotBadge {\n /**\n * `true` shows a solid danger dot `Badge` at the top-trailing corner of the\n * tab icon — the iOS tab-bar dot for new content without a count.\n */\n badge: true;\n /**\n * Required localized phrase folded into the tab's accessible name as\n * `\"<label>, <badgeLabel>\"` (for example `\"New activity\"` →\n * `\"Feed, New activity\"`). A dot has no text, so this is its only meaning.\n */\n badgeLabel: string;\n}\n\n/** One bottom-bar destination: its label, optional icon and badge, and content. */\nexport type TabScaffoldTab<Id extends string = string> =\n TabScaffoldTabBase<Id> & (TabScaffoldTabCountBadge | TabScaffoldTabDotBadge);\n\nexport interface TabScaffoldProps<Id extends string = string> {\n id: string;\n /** Accessible name for the tab bar. */\n label: string;\n tabs: readonly TabScaffoldTab<Id>[];\n /** The controlled active tab id (the app owns selection). */\n active: NoInfer<Id>;\n /**\n * The line over the tab bar, where it meets the active scene. `scroll`\n * (default) shows it only while more of the scene's content lies below —\n * never at the scroll end or when the content fits — once\n * `wireScrollDividers` (`@kerfjs/ui/wire-scroll-dividers`) is wired above\n * the scaffold; unwired, it does not show. `always` shows it without the\n * wiring; `none` never, even when wired. The line is the bar's own 1px top\n * border, so no state moves the bar or a tab.\n */\n chromeDividers?: 'scroll' | 'always' | 'none';\n className?: string;\n /** Native named-slot assignment when composed inside a web component. */\n slot?: string;\n}\n\n/**\n * A mobile-first, iOS-like bottom tab scaffold: a bottom tab bar that switches\n * between major sections, each tab keeping its own content (usually a `NavStack`)\n * mounted so its stack and scroll survive a switch. Controlled — the app owns\n * `active`; wire selection with `@kerfjs/ui/wire-tab-scaffold`'s `wireTabScaffold`.\n * On larger classes, promote the tabs to a `Workbench` rail or sidebar instead of\n * a bottom bar. See `docs/23-app-layouts.md` §3.4.\n */\nexport function TabScaffold<Id extends string>({\n id,\n label,\n tabs,\n active,\n chromeDividers = 'scroll',\n className = '',\n slot,\n}: TabScaffoldProps<Id>) {\n return (\n <section\n class={`kui-tab-scaffold ${className}`.trim()}\n id={id}\n data-component=\"tab-scaffold\"\n data-tab-scaffold-id={id}\n data-chrome-dividers={\n chromeDividers === 'always' || chromeDividers === 'none'\n ? chromeDividers\n : undefined\n }\n slot={slot}\n >\n <div class=\"kui-tab-scaffold__scenes\">\n {tabs.map((tab) => (\n <div\n class=\"kui-tab-scaffold__scene\"\n data-tab-scaffold-scene={tab.id}\n data-active={String(tab.id === active)}\n data-appearance={tab.appearance === 'sunken' ? 'sunken' : undefined}\n aria-hidden={String(tab.id !== active)}\n >\n {tab.content}\n </div>\n ))}\n </div>\n <nav class=\"kui-tab-scaffold__bar\" role=\"tablist\" aria-label={label}>\n {tabs.map((tab) => {\n const dot = tab.badge === true;\n const badgeText = dot ? undefined : normalizeBadge(tab.badge);\n const hasBadge = dot || badgeText !== undefined;\n const badge = hasBadge && (\n <span\n class=\"kui-tab-scaffold__tab-badge\"\n data-badge-kind={dot ? 'dot' : 'text'}\n >\n {badgeText === undefined ? (\n <Badge tone=\"danger\" size=\"dot\" ariaHidden />\n ) : (\n <Badge\n tone=\"danger\"\n appearance=\"solid\"\n size=\"compact\"\n ariaHidden\n >\n {badgeText}\n </Badge>\n )}\n </span>\n );\n // A dot has no text fallback: without a usable badgeLabel (reachable\n // only from untyped callers) the tab keeps its plain accessible name.\n const badgeLabel = hasBadge\n ? tab.badgeLabel?.trim() || badgeText\n : undefined;\n return (\n <button\n type=\"button\"\n class=\"kui-tab-scaffold__tab\"\n role=\"tab\"\n data-tab-scaffold-tab={tab.id}\n aria-selected={String(tab.id === active)}\n tabindex={tab.id === active ? '0' : '-1'}\n aria-label={\n badgeLabel === undefined\n ? undefined\n : `${tab.label}, ${badgeLabel}`\n }\n data-has-badge={hasBadge ? 'true' : undefined}\n >\n {tab.icon ? (\n <span class=\"kui-tab-scaffold__tab-icon\" aria-hidden=\"true\">\n {tab.icon}\n {badge}\n </span>\n ) : (\n badge\n )}\n <span class=\"kui-tab-scaffold__tab-label\">{tab.label}</span>\n </button>\n );\n })}\n </nav>\n </section>\n );\n}\n\nfunction normalizeBadge(\n badge: string | number | undefined,\n): string | undefined {\n if (typeof badge === 'number')\n return Number.isFinite(badge) ? String(badge) : undefined;\n if (typeof badge === 'string' && badge.trim()) return badge.trim();\n return undefined;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/components/navigation/tab-scaffold/tab-scaffold.tsx"],"names":[],"mappings":"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","file":"tab-scaffold.js","sourcesContent":["import type { SafeHtml } from 'kerfjs';\n\nimport type { KerfUiContent } from '../../../shared/content/semantic-content.js';\nimport { Badge } from '../../feedback/badge/badge.js';\nimport type { PaneAppearance } from '../../layout/pane/pane.js';\n\ninterface TabScaffoldTabBase<Id extends string> {\n id: Id;\n label: string;\n /** Decorative icon shown above the label in the bottom bar. */\n icon?: SafeHtml;\n /** The tab's content — typically a `NavStack` so each tab keeps its own stack. */\n content: KerfUiContent;\n /** Background of this scene's scrolling work surface. */\n appearance?: PaneAppearance;\n /** Add an extra 8px inline gutter to a plain scene's scrolling content. */\n deepInset?: boolean;\n}\n\n/** A tab with an optional count or short-status badge. */\ninterface TabScaffoldTabCountBadge {\n /**\n * Optional count or short status shown as a solid danger `Badge` at the\n * top-trailing corner of the tab icon (the iOS tab-bar badge). Omitted, `''`,\n * or non-finite numbers render no badge. The visual badge is `aria-hidden`;\n * its meaning reaches assistive technology through `badgeLabel`.\n *\n * Pass `true` instead for the text-free dot (new content without a count).\n */\n badge?: string | number;\n /**\n * Localized phrase folded into the tab's accessible name as\n * `\"<label>, <badgeLabel>\"` (for example `\"3 unread\"` → `\"Inbox, 3 unread\"`).\n * Defaults to the badge text itself. Ignored when no badge renders.\n */\n badgeLabel?: string;\n}\n\n/** A tab with the text-free dot badge (new content without a count). */\ninterface TabScaffoldTabDotBadge {\n /**\n * `true` shows a solid danger dot `Badge` at the top-trailing corner of the\n * tab icon — the iOS tab-bar dot for new content without a count.\n */\n badge: true;\n /**\n * Required localized phrase folded into the tab's accessible name as\n * `\"<label>, <badgeLabel>\"` (for example `\"New activity\"` →\n * `\"Feed, New activity\"`). A dot has no text, so this is its only meaning.\n */\n badgeLabel: string;\n}\n\n/** One bottom-bar destination: its label, optional icon and badge, and content. */\nexport type TabScaffoldTab<Id extends string = string> =\n TabScaffoldTabBase<Id> & (TabScaffoldTabCountBadge | TabScaffoldTabDotBadge);\n\nexport interface TabScaffoldProps<Id extends string = string> {\n id: string;\n /** Accessible name for the tab bar. */\n label: string;\n tabs: readonly TabScaffoldTab<Id>[];\n /** The controlled active tab id (the app owns selection). */\n active: NoInfer<Id>;\n /**\n * The line over the tab bar, where it meets the active scene. `scroll`\n * (default) shows it only while more of the scene's content lies below —\n * never at the scroll end or when the content fits — once\n * `wireScrollDividers` (`@kerfjs/ui/wire-scroll-dividers`) is wired above\n * the scaffold; unwired, it does not show. `always` shows it without the\n * wiring; `none` never, even when wired. The line is the bar's own 1px top\n * border, so no state moves the bar or a tab.\n */\n chromeDividers?: 'scroll' | 'always' | 'none';\n className?: string;\n /** Native named-slot assignment when composed inside a web component. */\n slot?: string;\n}\n\n/**\n * A mobile-first, iOS-like bottom tab scaffold: a bottom tab bar that switches\n * between major sections, each tab keeping its own content (usually a `NavStack`)\n * mounted so its stack and scroll survive a switch. Controlled — the app owns\n * `active`; wire selection with `@kerfjs/ui/wire-tab-scaffold`'s `wireTabScaffold`.\n * On larger classes, promote the tabs to a `Workbench` rail or sidebar instead of\n * a bottom bar. See `docs/23-app-layouts.md` §3.4.\n */\nexport function TabScaffold<Id extends string>({\n id,\n label,\n tabs,\n active,\n chromeDividers = 'scroll',\n className = '',\n slot,\n}: TabScaffoldProps<Id>) {\n return (\n <section\n class={`kui-tab-scaffold ${className}`.trim()}\n id={id}\n data-component=\"tab-scaffold\"\n data-tab-scaffold-id={id}\n data-chrome-dividers={\n chromeDividers === 'always' || chromeDividers === 'none'\n ? chromeDividers\n : undefined\n }\n slot={slot}\n >\n <div class=\"kui-tab-scaffold__scenes\">\n {tabs.map((tab) => (\n <div\n class=\"kui-tab-scaffold__scene\"\n data-tab-scaffold-scene={tab.id}\n data-active={String(tab.id === active)}\n data-appearance={tab.appearance === 'sunken' ? 'sunken' : undefined}\n data-deep-inset={tab.deepInset ? 'true' : undefined}\n aria-hidden={String(tab.id !== active)}\n >\n {tab.content}\n </div>\n ))}\n </div>\n <nav class=\"kui-tab-scaffold__bar\" role=\"tablist\" aria-label={label}>\n {tabs.map((tab) => {\n const dot = tab.badge === true;\n const badgeText = dot ? undefined : normalizeBadge(tab.badge);\n const hasBadge = dot || badgeText !== undefined;\n const badge = hasBadge && (\n <span\n class=\"kui-tab-scaffold__tab-badge\"\n data-badge-kind={dot ? 'dot' : 'text'}\n >\n {badgeText === undefined ? (\n <Badge tone=\"danger\" size=\"dot\" ariaHidden />\n ) : (\n <Badge\n tone=\"danger\"\n appearance=\"solid\"\n size=\"compact\"\n ariaHidden\n >\n {badgeText}\n </Badge>\n )}\n </span>\n );\n // A dot has no text fallback: without a usable badgeLabel (reachable\n // only from untyped callers) the tab keeps its plain accessible name.\n const badgeLabel = hasBadge\n ? tab.badgeLabel?.trim() || badgeText\n : undefined;\n return (\n <button\n type=\"button\"\n class=\"kui-tab-scaffold__tab\"\n role=\"tab\"\n data-tab-scaffold-tab={tab.id}\n aria-selected={String(tab.id === active)}\n tabindex={tab.id === active ? '0' : '-1'}\n aria-label={\n badgeLabel === undefined\n ? undefined\n : `${tab.label}, ${badgeLabel}`\n }\n data-has-badge={hasBadge ? 'true' : undefined}\n >\n {tab.icon ? (\n <span class=\"kui-tab-scaffold__tab-icon\" aria-hidden=\"true\">\n {tab.icon}\n {badge}\n </span>\n ) : (\n badge\n )}\n <span class=\"kui-tab-scaffold__tab-label\">{tab.label}</span>\n </button>\n );\n })}\n </nav>\n </section>\n );\n}\n\nfunction normalizeBadge(\n badge: string | number | undefined,\n): string | undefined {\n if (typeof badge === 'number')\n return Number.isFinite(badge) ? String(badge) : undefined;\n if (typeof badge === 'string' && badge.trim()) return badge.trim();\n return undefined;\n}\n"]}
|
package/dist/text.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as kerfjs from 'kerfjs';
|
|
2
2
|
import { KerfBaseAttrs } from 'kerfjs/jsx-runtime';
|
|
3
3
|
import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
|
|
4
|
+
import { S as Sides } from './sides-BPSWde0A.js';
|
|
4
5
|
import { CssForegroundColor } from './css-values.js';
|
|
5
6
|
|
|
6
7
|
type FieldLabelProps = Omit<KerfBaseAttrs, 'children' | 'class' | 'className'> & {
|
|
@@ -32,6 +33,8 @@ type TextCommonProps = Omit<KerfBaseAttrs, 'children' | 'class' | 'className'> &
|
|
|
32
33
|
font?: TextFont;
|
|
33
34
|
/** Transparent alignment border or no border when embedded in owner chrome. */
|
|
34
35
|
border?: TextBorder;
|
|
36
|
+
/** Add the shared control gutter outside selected physical edges. */
|
|
37
|
+
controlMargins?: Sides;
|
|
35
38
|
/** Remove the block variant's item padding and border in compact content. */
|
|
36
39
|
flush?: boolean;
|
|
37
40
|
/** Use compact leading for short dialog or metadata copy. */
|
|
@@ -55,6 +58,6 @@ type TextProps = TextCommonProps & ({
|
|
|
55
58
|
* content-item padding; `span` adds no box geometry.
|
|
56
59
|
* All ordinary native heading/paragraph attributes pass through to the element.
|
|
57
60
|
*/
|
|
58
|
-
declare function Text({ variant: Variant, tone, color, size, font, border, flush, lineHeight, wrap, maxLines, children, class: classValue, className, style, ...attributes }: TextProps): kerfjs.SafeHtml;
|
|
61
|
+
declare function Text({ variant: Variant, tone, color, size, font, border, flush, controlMargins, lineHeight, wrap, maxLines, children, class: classValue, className, style, ...attributes }: TextProps): kerfjs.SafeHtml;
|
|
59
62
|
|
|
60
|
-
export { FieldLabel, type FieldLabelProps, Text, type TextBorder, type TextContent, type TextFont, type TextLineHeight, type TextProps, type TextSize, type TextTone, type TextVariant, type TextWrap };
|
|
63
|
+
export { FieldLabel, type FieldLabelProps, Sides, Text, type TextBorder, type TextContent, type TextFont, type TextLineHeight, type TextProps, type TextSize, type TextTone, type TextVariant, type TextWrap };
|
package/dist/text.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import * as kerfjs from 'kerfjs';
|
|
2
2
|
import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
|
|
3
|
+
import { CssLength } from './css-values.js';
|
|
3
4
|
|
|
4
5
|
type ToolbarControlGroupAppearance = 'contained' | 'borderless';
|
|
5
6
|
type ToolbarControlGroupTone = 'default' | 'dark';
|
|
7
|
+
/** Quiet palette for a contained, single decorative icon tile. */
|
|
8
|
+
type ToolbarControlGroupTileTone = 'neutral' | 'brand' | 'success' | 'warning' | 'danger';
|
|
6
9
|
type ToolbarControlGroupButtonAppearance = 'plain' | 'push';
|
|
7
10
|
type ToolbarControlGroupShape = 'pill' | 'rounded';
|
|
8
11
|
type ToolbarControlGroupSize = 'default' | 'compact';
|
|
@@ -12,6 +15,7 @@ type ToolbarControlGroupFocusRing = 'control' | 'outline' | 'halo';
|
|
|
12
15
|
type ToolbarControlGroupSelectedChrome = 'raised' | 'filled' | 'outline';
|
|
13
16
|
type ToolbarControlGroupSelectedTone = 'brand' | 'neutral' | 'pop';
|
|
14
17
|
type ToolbarControlGroupOverflow = 'visible' | 'scroll' | 'wrap';
|
|
18
|
+
type ToolbarControlGroupExpandedOverflow = 'clip' | 'visible';
|
|
15
19
|
type ToolbarControlGroupMenuInset = 'standard' | 'compact' | 'list-zero';
|
|
16
20
|
type ToolbarControlGroupVisibility = 'always' | 'compact-only' | 'hide-collapsed-tiny' | 'yield-to-expanded-sibling';
|
|
17
21
|
type ToolbarControlGroupSizing = 'content' | 'grow' | 'fill';
|
|
@@ -40,6 +44,8 @@ interface ToolbarControlGroupProps {
|
|
|
40
44
|
single?: boolean;
|
|
41
45
|
appearance?: ToolbarControlGroupAppearance;
|
|
42
46
|
tone?: ToolbarControlGroupTone;
|
|
47
|
+
/** Quiet fill, matching border and foreground for a contained single group whose only child is a decorative SVG. Ignored for interactive or mixed content. */
|
|
48
|
+
tileTone?: ToolbarControlGroupTileTone;
|
|
43
49
|
buttonAppearance?: ToolbarControlGroupButtonAppearance;
|
|
44
50
|
/** Corner shape: fully round `pill` (default) or a softer `rounded` rectangle. */
|
|
45
51
|
shape?: ToolbarControlGroupShape;
|
|
@@ -56,8 +62,14 @@ interface ToolbarControlGroupProps {
|
|
|
56
62
|
menuInset?: ToolbarControlGroupMenuInset;
|
|
57
63
|
/** Keep an overlong row inside the available width by scrolling or wrapping. */
|
|
58
64
|
overflow?: ToolbarControlGroupOverflow;
|
|
65
|
+
/** Let app-owned anchored surfaces extend outside an expanded search group. Collapsed searches retain their clipping. */
|
|
66
|
+
expandedOverflow?: ToolbarControlGroupExpandedOverflow;
|
|
59
67
|
/** Responsive visibility owned by the enclosing Toolbar container. */
|
|
60
68
|
visibility?: ToolbarControlGroupVisibility;
|
|
69
|
+
/** Hide below this Toolbar content width; requires wireToolbarVisibility. */
|
|
70
|
+
hideBelow?: CssLength;
|
|
71
|
+
/** Show only below this Toolbar content width; requires wireToolbarVisibility. */
|
|
72
|
+
showBelow?: CssLength;
|
|
61
73
|
/** Intrinsic (default), grow from a basis, or occupy a full wrapping row. */
|
|
62
74
|
sizing?: ToolbarControlGroupSizing;
|
|
63
75
|
/** Place this group at the end of its flex row in a wrapping Toolbar zone. */
|
|
@@ -80,6 +92,6 @@ interface ToolbarControlGroupProps {
|
|
|
80
92
|
/** Native named-slot assignment when composed inside a web component. */
|
|
81
93
|
slot?: string;
|
|
82
94
|
}
|
|
83
|
-
declare function ToolbarControlGroup({ children, label, className, expanded, single, appearance, tone, buttonAppearance, shape, size, density, content, focusRing, selectedChrome, selectedTone, nestedDropdown, menuInset, overflow, visibility, sizing, placement, growBasis, relocateOnCollapse, scrim, busy, busyLabel, avatarImage, slot, }: ToolbarControlGroupProps): kerfjs.SafeHtml;
|
|
95
|
+
declare function ToolbarControlGroup({ children, label, className, expanded, single, appearance, tone, tileTone, buttonAppearance, shape, size, density, content, focusRing, selectedChrome, selectedTone, nestedDropdown, menuInset, overflow, expandedOverflow, visibility, hideBelow, showBelow, sizing, placement, growBasis, relocateOnCollapse, scrim, busy, busyLabel, avatarImage, slot, }: ToolbarControlGroupProps): kerfjs.SafeHtml;
|
|
84
96
|
|
|
85
|
-
export { ToolbarActionLink, type ToolbarActionLinkProps, ToolbarControlGroup, type ToolbarControlGroupAppearance, type ToolbarControlGroupButtonAppearance, type ToolbarControlGroupContent, type ToolbarControlGroupDensity, type ToolbarControlGroupFocusRing, type ToolbarControlGroupMenuInset, type ToolbarControlGroupOverflow, type ToolbarControlGroupPlacement, type ToolbarControlGroupProps, type ToolbarControlGroupSelectedChrome, type ToolbarControlGroupSelectedTone, type ToolbarControlGroupShape, type ToolbarControlGroupSize, type ToolbarControlGroupSizing, type ToolbarControlGroupTone, type ToolbarControlGroupVisibility };
|
|
97
|
+
export { ToolbarActionLink, type ToolbarActionLinkProps, ToolbarControlGroup, type ToolbarControlGroupAppearance, type ToolbarControlGroupButtonAppearance, type ToolbarControlGroupContent, type ToolbarControlGroupDensity, type ToolbarControlGroupExpandedOverflow, type ToolbarControlGroupFocusRing, type ToolbarControlGroupMenuInset, type ToolbarControlGroupOverflow, type ToolbarControlGroupPlacement, type ToolbarControlGroupProps, type ToolbarControlGroupSelectedChrome, type ToolbarControlGroupSelectedTone, type ToolbarControlGroupShape, type ToolbarControlGroupSize, type ToolbarControlGroupSizing, type ToolbarControlGroupTileTone, type ToolbarControlGroupTone, type ToolbarControlGroupVisibility };
|
package/dist/toolbar-text.d.ts
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import * as kerfjs from 'kerfjs';
|
|
2
|
+
import { CssLength } from './css-values.js';
|
|
2
3
|
|
|
3
4
|
type ToolbarTextSize = 'xlarge' | 'xlarge-fixed' | 'large' | 'default' | 'small' | 'xsmall';
|
|
5
|
+
type ToolbarTextTone = 'default' | 'dark';
|
|
4
6
|
/** ARIA heading level for a title exposed as a heading landmark. */
|
|
5
7
|
type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
|
|
6
8
|
interface ToolbarTextBaseProps {
|
|
7
9
|
text: string;
|
|
8
10
|
size?: ToolbarTextSize;
|
|
11
|
+
/** Use dark-toolbar foregrounds on a dark or photo-backed toolbar. */
|
|
12
|
+
tone?: ToolbarTextTone;
|
|
13
|
+
/** Hide below this Toolbar content width; requires wireToolbarVisibility. */
|
|
14
|
+
hideBelow?: CssLength;
|
|
15
|
+
/** Show only below this Toolbar content width; requires wireToolbarVisibility. */
|
|
16
|
+
showBelow?: CssLength;
|
|
9
17
|
className?: string;
|
|
10
18
|
/** Optional id, e.g. so a dialog can reference the title via aria-labelledby. */
|
|
11
19
|
id?: string;
|
|
@@ -46,6 +54,6 @@ type ToolbarTextProps = ToolbarTextBaseProps & (({
|
|
|
46
54
|
wrap?: never;
|
|
47
55
|
maxLines?: never;
|
|
48
56
|
});
|
|
49
|
-
declare function ToolbarText({ text, size, className, id, headingLevel, placeholder, wrap, ellipsis, maxLines, fill, slot, action, }: ToolbarTextProps): kerfjs.SafeHtml;
|
|
57
|
+
declare function ToolbarText({ text, size, tone, hideBelow, showBelow, className, id, headingLevel, placeholder, wrap, ellipsis, maxLines, fill, slot, action, }: ToolbarTextProps): kerfjs.SafeHtml;
|
|
50
58
|
|
|
51
|
-
export { type HeadingLevel, ToolbarText, type ToolbarTextProps, type ToolbarTextSize };
|
|
59
|
+
export { type HeadingLevel, ToolbarText, type ToolbarTextProps, type ToolbarTextSize, type ToolbarTextTone };
|
package/dist/toolbar-text.js
CHANGED
package/dist/wire-nav-stack.js
CHANGED
|
@@ -27,7 +27,7 @@ function reducedMotion(view) {
|
|
|
27
27
|
function chromeOf(section) {
|
|
28
28
|
return Array.from(
|
|
29
29
|
section.querySelectorAll(
|
|
30
|
-
":scope > [data-nav-stack-chrome], :scope > [data-nav-stack-bottom]"
|
|
30
|
+
":scope > [data-nav-stack-chrome]:not([data-nav-chrome-copy]), :scope > [data-nav-stack-bottom]:not([data-nav-chrome-copy])"
|
|
31
31
|
)
|
|
32
32
|
);
|
|
33
33
|
}
|
|
@@ -56,6 +56,32 @@ function wireNavStack(root, options = {}) {
|
|
|
56
56
|
);
|
|
57
57
|
let activeKey = topView(viewport)?.dataset.navKey;
|
|
58
58
|
const timers = /* @__PURE__ */ new Set();
|
|
59
|
+
const frames = /* @__PURE__ */ new Set();
|
|
60
|
+
const frame = (fn) => {
|
|
61
|
+
const id = view.requestAnimationFrame(() => {
|
|
62
|
+
frames.delete(id);
|
|
63
|
+
fn();
|
|
64
|
+
});
|
|
65
|
+
frames.add(id);
|
|
66
|
+
};
|
|
67
|
+
const finishTransition = () => {
|
|
68
|
+
for (const id of frames) view.cancelAnimationFrame(id);
|
|
69
|
+
frames.clear();
|
|
70
|
+
for (const id of timers) view.clearTimeout(id);
|
|
71
|
+
timers.clear();
|
|
72
|
+
section.querySelectorAll(":scope > [data-nav-chrome-copy]").forEach((element) => element.remove());
|
|
73
|
+
chromeOf(section).forEach(
|
|
74
|
+
(element) => element.classList.remove("kui-nav-stack__chrome--entering")
|
|
75
|
+
);
|
|
76
|
+
viewsOf(viewport).forEach((element) => {
|
|
77
|
+
if (element.dataset.navExiting === "true") element.remove();
|
|
78
|
+
else {
|
|
79
|
+
element.classList.remove("kui-nav-stack__view--entering");
|
|
80
|
+
delete element.dataset.navRevealed;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
delete section.dataset.navChromeTransition;
|
|
84
|
+
};
|
|
59
85
|
const rememberedFocus = /* @__PURE__ */ new Map();
|
|
60
86
|
const generatedFallbacks = /* @__PURE__ */ new Map();
|
|
61
87
|
let chromeSnapshots = chromeOf(section).map(
|
|
@@ -78,16 +104,12 @@ function wireNavStack(root, options = {}) {
|
|
|
78
104
|
el.classList.add("kui-nav-stack__view--entering");
|
|
79
105
|
if (animated) {
|
|
80
106
|
void el.offsetWidth;
|
|
81
|
-
|
|
82
|
-
() => el.classList.remove("kui-nav-stack__view--entering")
|
|
83
|
-
);
|
|
107
|
+
frame(() => el.classList.remove("kui-nav-stack__view--entering"));
|
|
84
108
|
} else {
|
|
85
109
|
el.classList.remove("kui-nav-stack__view--entering");
|
|
86
110
|
}
|
|
87
111
|
} else if (animated) {
|
|
88
|
-
|
|
89
|
-
() => el.classList.add("kui-nav-stack__view--exiting")
|
|
90
|
-
);
|
|
112
|
+
frame(() => el.classList.add("kui-nav-stack__view--exiting"));
|
|
91
113
|
} else {
|
|
92
114
|
el.classList.add("kui-nav-stack__view--exiting");
|
|
93
115
|
}
|
|
@@ -150,7 +172,7 @@ function wireNavStack(root, options = {}) {
|
|
|
150
172
|
(element) => element.classList.add("kui-nav-stack__chrome--entering")
|
|
151
173
|
);
|
|
152
174
|
void section.offsetWidth;
|
|
153
|
-
|
|
175
|
+
frame(() => {
|
|
154
176
|
current.forEach(
|
|
155
177
|
(element) => element.classList.remove("kui-nav-stack__chrome--entering")
|
|
156
178
|
);
|
|
@@ -162,35 +184,47 @@ function wireNavStack(root, options = {}) {
|
|
|
162
184
|
copies.forEach((copy) => copy.remove());
|
|
163
185
|
delete section.dataset.navChromeTransition;
|
|
164
186
|
});
|
|
165
|
-
chromeSnapshots = current.map(
|
|
166
|
-
|
|
167
|
-
|
|
187
|
+
chromeSnapshots = current.map((element) => {
|
|
188
|
+
const snapshot = element.cloneNode(true);
|
|
189
|
+
snapshot.classList.remove("kui-nav-stack__chrome--entering");
|
|
190
|
+
return snapshot;
|
|
191
|
+
});
|
|
168
192
|
};
|
|
169
193
|
const observer = new MutationObserver((records) => {
|
|
170
194
|
const removed = [];
|
|
171
|
-
let added = false;
|
|
172
195
|
for (const record of records) {
|
|
196
|
+
if (record.target !== viewport) continue;
|
|
173
197
|
record.removedNodes.forEach((node) => {
|
|
174
198
|
if (node instanceof HTMLElement && node.classList.contains("kui-nav-stack__view") && node.dataset.navExiting !== "true")
|
|
175
199
|
removed.push(node);
|
|
176
200
|
});
|
|
177
|
-
record.addedNodes.forEach((node) => {
|
|
178
|
-
if (node instanceof HTMLElement && node.classList.contains("kui-nav-stack__view"))
|
|
179
|
-
added = true;
|
|
180
|
-
});
|
|
181
201
|
}
|
|
182
202
|
const top = topView(viewport);
|
|
183
203
|
const currentKey = top?.dataset.navKey;
|
|
184
204
|
const poppedTop = removed.find((node) => node.dataset.navKey === activeKey);
|
|
185
205
|
if (poppedTop && currentKey !== activeKey) {
|
|
206
|
+
finishTransition();
|
|
207
|
+
poppedTop.classList.remove(
|
|
208
|
+
"kui-nav-stack__view--entering",
|
|
209
|
+
"kui-nav-stack__view--exiting"
|
|
210
|
+
);
|
|
186
211
|
poppedTop.dataset.navExiting = "true";
|
|
187
212
|
poppedTop.setAttribute("aria-hidden", "true");
|
|
188
213
|
viewport.append(poppedTop);
|
|
214
|
+
void poppedTop.offsetWidth;
|
|
189
215
|
play(poppedTop, "exiting");
|
|
190
216
|
crossFadeChrome();
|
|
191
217
|
if (top) focusInto(top);
|
|
192
218
|
settle(() => poppedTop.remove());
|
|
193
|
-
} else if (
|
|
219
|
+
} else if (currentKey !== activeKey && top) {
|
|
220
|
+
finishTransition();
|
|
221
|
+
const previous = viewsOf(viewport).find(
|
|
222
|
+
(element) => element.dataset.navKey === activeKey
|
|
223
|
+
);
|
|
224
|
+
if (previous) {
|
|
225
|
+
previous.dataset.navRevealed = "true";
|
|
226
|
+
settle(() => delete previous.dataset.navRevealed);
|
|
227
|
+
}
|
|
194
228
|
rememberFocusedView(section.ownerDocument.activeElement);
|
|
195
229
|
play(top, "entering");
|
|
196
230
|
crossFadeChrome();
|
|
@@ -198,15 +232,17 @@ function wireNavStack(root, options = {}) {
|
|
|
198
232
|
}
|
|
199
233
|
activeKey = currentKey;
|
|
200
234
|
});
|
|
201
|
-
observer.observe(viewport, {
|
|
235
|
+
observer.observe(viewport, {
|
|
236
|
+
childList: true,
|
|
237
|
+
subtree: true,
|
|
238
|
+
attributes: true,
|
|
239
|
+
attributeFilter: ["data-nav-key", "data-nav-active"]
|
|
240
|
+
});
|
|
202
241
|
return () => {
|
|
203
242
|
disposeBack();
|
|
204
243
|
section.removeEventListener("focusin", onFocusIn);
|
|
205
244
|
observer.disconnect();
|
|
206
|
-
|
|
207
|
-
timers.clear();
|
|
208
|
-
section.querySelectorAll("[data-nav-chrome-copy]").forEach((copy) => copy.remove());
|
|
209
|
-
delete section.dataset.navChromeTransition;
|
|
245
|
+
finishTransition();
|
|
210
246
|
for (const [element, previousTabIndex] of generatedFallbacks) {
|
|
211
247
|
if (!element.isConnected || element.tabIndex !== -1) continue;
|
|
212
248
|
if (previousTabIndex === null) element.removeAttribute("tabindex");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/navigation/nav-stack/wiring/wire-nav-stack.ts"],"names":[],"mappings":";;;AASA,IAAM,gBAAA,GAAmB,GAAA;AACzB,IAAM,kBAAA,GAAqB;AAAA,EACzB,SAAA;AAAA,EACA,wBAAA;AAAA,EACA,uBAAA;AAAA,EACA,wBAAA;AAAA,EACA,0BAAA;AAAA,EACA;AACF,CAAA,CAAE,KAAK,GAAG,CAAA;AAEV,SAAS,QAAQ,QAAA,EAAkC;AACjD,EAAA,OAAO,KAAA,CAAM,IAAA;AAAA,IACX,QAAA,CAAS,iBAA8B,+BAA+B;AAAA,GACxE;AACF;AAEA,SAAS,QAAQ,QAAA,EAA4C;AAC3D,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,QAAQ,CAAA,CAAE,MAAA;AAAA,IAC7B,CAAC,IAAA,KAAS,IAAA,CAAK,OAAA,CAAQ,UAAA,KAAe;AAAA,GACxC;AACA,EAAA,OAAO,IAAA,CAAK,IAAA,CAAK,MAAA,GAAS,CAAC,CAAA;AAC7B;AAEA,SAAS,cAAc,IAAA,EAAuB;AAC5C,EAAA,OACE,OAAO,IAAA,CAAK,UAAA,KAAe,cAC3B,IAAA,CAAK,UAAA,CAAW,kCAAkC,CAAA,CAAE,OAAA;AAExD;AAEA,SAAS,SAAS,OAAA,EAAqC;AACrD,EAAA,OAAO,KAAA,CAAM,IAAA;AAAA,IACX,OAAA,CAAQ,gBAAA;AAAA,MACN;AAAA;AACF,GACF;AACF;AAEA,SAAS,qBACP,OAAA,EACwB;AACxB,EAAA,OACE,OAAA,YAAmB,WAAA,IACnB,OAAA,CAAQ,WAAA,IACR,CAAC,OAAA,CAAQ,OAAA,CAAQ,oCAAoC,CAAA,IACrD,CAAC,OAAA,CAAQ,OAAA,CAAQ,yCAAyC,CAAA;AAE9D;AAWO,SAAS,YAAA,CACd,IAAA,EACA,OAAA,GAA+B,EAAC,EACpB;AACZ,EAAA,MAAM,OAAA,GAAU,KAAK,OAAA,CAAQ,8BAA8B,IACvD,IAAA,GACA,IAAA,CAAK,cAAc,8BAA8B,CAAA;AACrD,EAAA,MAAM,QAAA,GAAW,OAAA,EAAS,aAAA,CAAc,2BAA2B,CAAA;AACnE,EAAA,IAAI,EAAE,OAAA,YAAmB,WAAA,CAAA,IAAgB,CAAC,QAAA,SAAiB,MAAM;AAAA,EAAC,CAAA;AAElE,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,aAAA,CAAc,WAAA,IAAe,MAAA;AAClD,EAAA,MAAM,QAAA,GAAW,QAAQ,QAAA,IAAY,gBAAA;AACrC,EAAA,MAAM,gBAAA,GAAmB,QAAQ,KAAA,CAAM,gBAAA;AAAA,IACrC;AAAA,GACF;AACA,EAAA,OAAA,CAAQ,KAAA,CAAM,WAAA;AAAA,IACZ,qCAAA;AAAA,IACA,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,QAAQ,CAAC,CAAA,EAAA;AAAA,GAC1B;AACA,EAAA,MAAM,WAAA,GAAc,QAAA;AAAA,IAAS,OAAA;AAAA,IAAS,OAAA;AAAA,IAAS,iBAAA;AAAA,IAAmB,MAChE,QAAQ,MAAA;AAAS,GACnB;AAEA,EAAA,IAAI,SAAA,GAAY,OAAA,CAAQ,QAAQ,CAAA,EAAG,OAAA,CAAQ,MAAA;AAC3C,EAAA,MAAM,MAAA,uBAAa,GAAA,EAAY;AAC/B,EAAA,MAAM,eAAA,uBAAsB,GAAA,EAAyB;AACrD,EAAA,MAAM,kBAAA,uBAAyB,GAAA,EAAgC;AAC/D,EAAA,IAAI,eAAA,GAAkB,QAAA,CAAS,OAAO,CAAA,CAAE,GAAA;AAAA,IAAI,CAAC,OAAA,KAC3C,OAAA,CAAQ,SAAA,CAAU,IAAI;AAAA,GACxB;AAEA,EAAA,MAAM,MAAA,GAAS,CAAC,EAAA,KAAyB;AACvC,IAAA,IAAI,QAAA,IAAY,CAAA,IAAK,aAAA,CAAc,IAAI,CAAA,EAAG;AACxC,MAAA,EAAA,EAAG;AACH,MAAA;AAAA,IACF;AACA,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,UAAA,CAAW,MAAM;AAClC,MAAA,MAAA,CAAO,OAAO,KAAK,CAAA;AACnB,MAAA,EAAA,EAAG;AAAA,IACL,GAAG,QAAQ,CAAA;AACX,IAAA,MAAA,CAAO,IAAI,KAAK,CAAA;AAAA,EAClB,CAAA;AAEA,EAAA,MAAM,IAAA,GAAO,CAAC,EAAA,EAAiB,IAAA,KAAuC;AACpE,IAAA,MAAM,QAAA,GAAW,QAAA,GAAW,CAAA,IAAK,CAAC,cAAc,IAAI,CAAA;AACpD,IAAA,IAAI,SAAS,UAAA,EAAY;AAGvB,MAAA,EAAA,CAAG,SAAA,CAAU,IAAI,+BAA+B,CAAA;AAChD,MAAA,IAAI,QAAA,EAAU;AAEZ,QAAA,KAAK,EAAA,CAAG,WAAA;AACR,QAAA,IAAA,CAAK,qBAAA;AAAA,UAAsB,MACzB,EAAA,CAAG,SAAA,CAAU,MAAA,CAAO,+BAA+B;AAAA,SACrD;AAAA,MACF,CAAA,MAAO;AACL,QAAA,EAAA,CAAG,SAAA,CAAU,OAAO,+BAA+B,CAAA;AAAA,MACrD;AAAA,IACF,WAAW,QAAA,EAAU;AAGnB,MAAA,IAAA,CAAK,qBAAA;AAAA,QAAsB,MACzB,EAAA,CAAG,SAAA,CAAU,GAAA,CAAI,8BAA8B;AAAA,OACjD;AAAA,IACF,CAAA,MAAO;AACL,MAAA,EAAA,CAAG,SAAA,CAAU,IAAI,8BAA8B,CAAA;AAAA,IACjD;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,mBAAA,GAAsB,CAAC,MAAA,KAAiC;AAC5D,IAAA,IAAI,EAAE,kBAAkB,WAAA,CAAA,EAAc;AACtC,IAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,OAAA,CAAqB,sBAAsB,CAAA;AAChE,IAAA,MAAM,GAAA,GAAM,OAAO,OAAA,CAAQ,MAAA;AAC3B,IAAA,IAAI,GAAA,IAAO,KAAA,KAAU,MAAA,IAAU,QAAA,CAAS,SAAS,KAAK,CAAA;AACpD,MAAA,eAAA,CAAgB,GAAA,CAAI,KAAK,MAAM,CAAA;AAAA,EACnC,CAAA;AAEA,EAAA,MAAM,SAAA,GAAY,CAAC,UAAA,KAAkC;AACnD,IAAA,MAAM,GAAA,GAAM,WAAW,OAAA,CAAQ,MAAA;AAC/B,IAAA,MAAM,UAAA,GAAa,GAAA,GAAM,eAAA,CAAgB,GAAA,CAAI,GAAG,CAAA,GAAI,MAAA;AACpD,IAAA,MAAM,YAAY,KAAA,CAAM,IAAA;AAAA,MACtB,UAAA,CAAW,iBAA8B,kBAAkB;AAAA,KAC7D,CAAE,KAAK,oBAAoB,CAAA;AAC3B,IAAA,MAAM,QAAQ,KAAA,CAAM,IAAA;AAAA,MAClB,UAAA,CAAW,iBAA8B,kBAAkB;AAAA,KAC7D,CAAE,KAAK,oBAAoB,CAAA;AAC3B,IAAA,MAAM,MAAA,GAAA,CACH,oBAAA,CAAqB,UAAU,CAAA,IAAK,UAAA,CAAW,SAAS,UAAU,CAAA,GAC/D,UAAA,GACA,MAAA,KACJ,SAAA,IACA,KAAA;AACF,IAAA,IAAI,MAAA,EAAQ;AACV,MAAA,MAAA,CAAO,KAAA,CAAM,EAAE,aAAA,EAAe,IAAA,EAAM,CAAA;AACpC,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,CAAC,kBAAA,CAAmB,GAAA,CAAI,UAAU,CAAA,EAAG;AACvC,MAAA,kBAAA,CAAmB,GAAA;AAAA,QACjB,UAAA;AAAA,QACA,WAAW,YAAA,CAAa,UAAU,IAC9B,UAAA,CAAW,YAAA,CAAa,UAAU,CAAA,GAClC;AAAA,OACN;AACA,MAAA,UAAA,CAAW,QAAA,GAAW,EAAA;AAAA,IACxB;AACA,IAAA,UAAA,CAAW,KAAA,CAAM,EAAE,aAAA,EAAe,IAAA,EAAM,CAAA;AAAA,EAC1C,CAAA;AAEA,EAAA,MAAM,SAAA,GAAY,CAAC,KAAA,KACjB,mBAAA,CAAoB,MAAM,MAAwB,CAAA;AACpD,EAAA,OAAA,CAAQ,gBAAA,CAAiB,WAAW,SAAS,CAAA;AAC7C,EAAA,mBAAA,CAAoB,OAAA,CAAQ,cAAc,aAAa,CAAA;AAEvD,EAAA,MAAM,kBAAkB,MAAY;AAClC,IAAA,MAAM,OAAA,GAAU,SAAS,OAAO,CAAA;AAChC,IAAA,MAAM,QAAA,GAAW,QAAA,GAAW,CAAA,IAAK,CAAC,cAAc,IAAI,CAAA;AACpD,IAAA,IAAI,CAAC,QAAA,EAAU;AACb,MAAA,eAAA,GAAkB,OAAA,CAAQ,GAAA;AAAA,QAAI,CAAC,OAAA,KAC7B,OAAA,CAAQ,SAAA,CAAU,IAAI;AAAA,OACxB;AACA,MAAA;AAAA,IACF;AAEA,IAAA,OAAA,CAAQ,QAAQ,mBAAA,GAAsB,MAAA;AACtC,IAAA,MAAM,MAAA,GAAS,eAAA,CAAgB,GAAA,CAAI,CAAC,QAAA,KAAa;AAC/C,MAAA,MAAM,IAAA,GAAO,QAAA,CAAS,SAAA,CAAU,IAAI,CAAA;AACpC,MAAA,IAAA,CAAK,SAAA,CAAU,IAAI,4BAA4B,CAAA;AAC/C,MAAA,IAAA,CAAK,QAAQ,aAAA,GAAgB,EAAA;AAC7B,MAAA,IAAA,CAAK,YAAA,CAAa,eAAe,MAAM,CAAA;AACvC,MAAA,IAAA,CAAK,YAAA,CAAa,SAAS,EAAE,CAAA;AAC7B,MAAA,IAAI,IAAA,CAAK,aAAa,uBAAuB,CAAA;AAC3C,QAAA,IAAA,CAAK,SAAA,CAAU,IAAI,oCAAoC,CAAA;AACzD,MAAA,OAAA,CAAQ,OAAO,IAAI,CAAA;AACnB,MAAA,OAAO,IAAA;AAAA,IACT,CAAC,CAAA;AAED,IAAA,OAAA,CAAQ,OAAA;AAAA,MAAQ,CAAC,OAAA,KACf,OAAA,CAAQ,SAAA,CAAU,IAAI,iCAAiC;AAAA,KACzD;AACA,IAAA,KAAK,OAAA,CAAQ,WAAA;AACb,IAAA,IAAA,CAAK,sBAAsB,MAAM;AAC/B,MAAA,OAAA,CAAQ,OAAA;AAAA,QAAQ,CAAC,OAAA,KACf,OAAA,CAAQ,SAAA,CAAU,OAAO,iCAAiC;AAAA,OAC5D;AACA,MAAA,MAAA,CAAO,OAAA;AAAA,QAAQ,CAAC,IAAA,KACd,IAAA,CAAK,SAAA,CAAU,IAAI,qCAAqC;AAAA,OAC1D;AAAA,IACF,CAAC,CAAA;AACD,IAAA,MAAA,CAAO,MAAM;AACX,MAAA,MAAA,CAAO,OAAA,CAAQ,CAAC,IAAA,KAAS,IAAA,CAAK,QAAQ,CAAA;AACtC,MAAA,OAAO,QAAQ,OAAA,CAAQ,mBAAA;AAAA,IACzB,CAAC,CAAA;AACD,IAAA,eAAA,GAAkB,OAAA,CAAQ,GAAA;AAAA,MAAI,CAAC,OAAA,KAC7B,OAAA,CAAQ,SAAA,CAAU,IAAI;AAAA,KACxB;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,QAAA,GAAW,IAAI,gBAAA,CAAiB,CAAC,OAAA,KAAY;AACjD,IAAA,MAAM,UAAyB,EAAC;AAChC,IAAA,IAAI,KAAA,GAAQ,KAAA;AACZ,IAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,MAAA,MAAA,CAAO,YAAA,CAAa,OAAA,CAAQ,CAAC,IAAA,KAAS;AACpC,QAAA,IACE,IAAA,YAAgB,eAChB,IAAA,CAAK,SAAA,CAAU,SAAS,qBAAqB,CAAA,IAC7C,IAAA,CAAK,OAAA,CAAQ,UAAA,KAAe,MAAA;AAE5B,UAAA,OAAA,CAAQ,KAAK,IAAI,CAAA;AAAA,MACrB,CAAC,CAAA;AACD,MAAA,MAAA,CAAO,UAAA,CAAW,OAAA,CAAQ,CAAC,IAAA,KAAS;AAClC,QAAA,IACE,IAAA,YAAgB,WAAA,IAChB,IAAA,CAAK,SAAA,CAAU,SAAS,qBAAqB,CAAA;AAE7C,UAAA,KAAA,GAAQ,IAAA;AAAA,MACZ,CAAC,CAAA;AAAA,IACH;AAEA,IAAA,MAAM,GAAA,GAAM,QAAQ,QAAQ,CAAA;AAC5B,IAAA,MAAM,UAAA,GAAa,KAAK,OAAA,CAAQ,MAAA;AAChC,IAAA,MAAM,SAAA,GAAY,QAAQ,IAAA,CAAK,CAAC,SAAS,IAAA,CAAK,OAAA,CAAQ,WAAW,SAAS,CAAA;AAE1E,IAAA,IAAI,SAAA,IAAa,eAAe,SAAA,EAAW;AAEzC,MAAA,SAAA,CAAU,QAAQ,UAAA,GAAa,MAAA;AAC/B,MAAA,SAAA,CAAU,YAAA,CAAa,eAAe,MAAM,CAAA;AAC5C,MAAA,QAAA,CAAS,OAAO,SAAS,CAAA;AACzB,MAAA,IAAA,CAAK,WAAW,SAAS,CAAA;AACzB,MAAA,eAAA,EAAgB;AAChB,MAAA,IAAI,GAAA,YAAe,GAAG,CAAA;AACtB,MAAA,MAAA,CAAO,MAAM,SAAA,CAAU,MAAA,EAAQ,CAAA;AAAA,IACjC,CAAA,MAAA,IAAW,KAAA,IAAS,UAAA,KAAe,SAAA,IAAa,GAAA,EAAK;AAEnD,MAAA,mBAAA,CAAoB,OAAA,CAAQ,cAAc,aAAa,CAAA;AACvD,MAAA,IAAA,CAAK,KAAK,UAAU,CAAA;AACpB,MAAA,eAAA,EAAgB;AAChB,MAAA,SAAA,CAAU,GAAG,CAAA;AAAA,IACf;AAEA,IAAA,SAAA,GAAY,UAAA;AAAA,EACd,CAAC,CAAA;AACD,EAAA,QAAA,CAAS,OAAA,CAAQ,QAAA,EAAU,EAAE,SAAA,EAAW,MAAM,CAAA;AAE9C,EAAA,OAAO,MAAM;AACX,IAAA,WAAA,EAAY;AACZ,IAAA,OAAA,CAAQ,mBAAA,CAAoB,WAAW,SAAS,CAAA;AAChD,IAAA,QAAA,CAAS,UAAA,EAAW;AACpB,IAAA,KAAA,MAAW,KAAA,IAAS,MAAA,EAAQ,IAAA,CAAK,YAAA,CAAa,KAAK,CAAA;AACnD,IAAA,MAAA,CAAO,KAAA,EAAM;AACb,IAAA,OAAA,CACG,gBAAA,CAA8B,wBAAwB,CAAA,CACtD,OAAA,CAAQ,CAAC,IAAA,KAAS,IAAA,CAAK,QAAQ,CAAA;AAClC,IAAA,OAAO,QAAQ,OAAA,CAAQ,mBAAA;AACvB,IAAA,KAAA,MAAW,CAAC,OAAA,EAAS,gBAAgB,CAAA,IAAK,kBAAA,EAAoB;AAC5D,MAAA,IAAI,CAAC,OAAA,CAAQ,WAAA,IAAe,OAAA,CAAQ,aAAa,EAAA,EAAI;AACrD,MAAA,IAAI,gBAAA,KAAqB,IAAA,EAAM,OAAA,CAAQ,eAAA,CAAgB,UAAU,CAAA;AAAA,WAC5D,OAAA,CAAQ,YAAA,CAAa,UAAA,EAAY,gBAAgB,CAAA;AAAA,IACxD;AACA,IAAA,kBAAA,CAAmB,KAAA,EAAM;AACzB,IAAA,eAAA,CAAgB,KAAA,EAAM;AACtB,IAAA,IAAI,gBAAA;AACF,MAAA,OAAA,CAAQ,KAAA,CAAM,WAAA;AAAA,QACZ,qCAAA;AAAA,QACA;AAAA,OACF;AAAA,SACG,OAAA,CAAQ,KAAA,CAAM,cAAA,CAAe,qCAAqC,CAAA;AAAA,EACzE,CAAA;AACF","file":"wire-nav-stack.js","sourcesContent":["import { delegate } from 'kerfjs';\n\nexport interface WireNavStackOptions {\n /** Invoked when the back control is activated. The app pops its own stack. */\n onBack?: () => void;\n /** Transition duration in ms (default 200). Set 0 to disable animation. */\n duration?: number;\n}\n\nconst DEFAULT_DURATION = 200;\nconst FOCUSABLE_SELECTOR = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(',');\n\nfunction viewsOf(viewport: Element): HTMLElement[] {\n return Array.from(\n viewport.querySelectorAll<HTMLElement>(':scope > .kui-nav-stack__view'),\n );\n}\n\nfunction topView(viewport: Element): HTMLElement | undefined {\n const live = viewsOf(viewport).filter(\n (view) => view.dataset.navExiting !== 'true',\n );\n return live[live.length - 1];\n}\n\nfunction reducedMotion(view: Window): boolean {\n return (\n typeof view.matchMedia === 'function' &&\n view.matchMedia('(prefers-reduced-motion: reduce)').matches\n );\n}\n\nfunction chromeOf(section: HTMLElement): HTMLElement[] {\n return Array.from(\n section.querySelectorAll<HTMLElement>(\n ':scope > [data-nav-stack-chrome], :scope > [data-nav-stack-bottom]',\n ),\n );\n}\n\nfunction availableFocusTarget(\n element: Element | null | undefined,\n): element is HTMLElement {\n return (\n element instanceof HTMLElement &&\n element.isConnected &&\n !element.matches('[disabled], [aria-disabled=\"true\"]') &&\n !element.closest('[inert], [hidden], [aria-hidden=\"true\"]')\n );\n}\n\n/**\n * Animate a `NavStack`'s push/pop transitions and wire its back control. The app\n * owns the stack (a signal of `NavStackView[]`) and re-renders `NavStack` when it\n * changes; this helper slides the content and settles the chrome across each\n * change, moves focus into every new top view, restores that view's last focused\n * descendant on pop, and calls `onBack` when the back control is used. Mark a\n * preferred initial target with `data-nav-focus`; otherwise the first focusable\n * descendant (or the view itself) receives focus. Returns a disposer.\n */\nexport function wireNavStack(\n root: Element,\n options: WireNavStackOptions = {},\n): () => void {\n const section = root.matches('[data-component=\"nav-stack\"]')\n ? root\n : root.querySelector('[data-component=\"nav-stack\"]');\n const viewport = section?.querySelector('[data-nav-stack-viewport]');\n if (!(section instanceof HTMLElement) || !viewport) return () => {};\n\n const view = section.ownerDocument.defaultView ?? window;\n const duration = options.duration ?? DEFAULT_DURATION;\n const previousDuration = section.style.getPropertyValue(\n '--kui-nav-stack-transition-duration',\n );\n section.style.setProperty(\n '--kui-nav-stack-transition-duration',\n `${Math.max(0, duration)}ms`,\n );\n const disposeBack = delegate(section, 'click', '[data-nav-back]', () =>\n options.onBack?.(),\n );\n\n let activeKey = topView(viewport)?.dataset.navKey;\n const timers = new Set<number>();\n const rememberedFocus = new Map<string, HTMLElement>();\n const generatedFallbacks = new Map<HTMLElement, string | null>();\n let chromeSnapshots = chromeOf(section).map((element) =>\n element.cloneNode(true),\n ) as HTMLElement[];\n\n const settle = (fn: () => void): void => {\n if (duration <= 0 || reducedMotion(view)) {\n fn();\n return;\n }\n const timer = view.setTimeout(() => {\n timers.delete(timer);\n fn();\n }, duration);\n timers.add(timer);\n };\n\n const play = (el: HTMLElement, kind: 'entering' | 'exiting'): void => {\n const animated = duration > 0 && !reducedMotion(view);\n if (kind === 'entering') {\n // Push: start the incoming view off the trailing edge, then release it so\n // it slides to rest (translateX(100%) → 0).\n el.classList.add('kui-nav-stack__view--entering');\n if (animated) {\n // Force a reflow so the starting transform applies before we clear it.\n void el.offsetWidth;\n view.requestAnimationFrame(() =>\n el.classList.remove('kui-nav-stack__view--entering'),\n );\n } else {\n el.classList.remove('kui-nav-stack__view--entering');\n }\n } else if (animated) {\n // Pop: the outgoing view is at rest; add the off-edge class on the next\n // frame so it slides OUT (translateX(0) → 100%), not in.\n view.requestAnimationFrame(() =>\n el.classList.add('kui-nav-stack__view--exiting'),\n );\n } else {\n el.classList.add('kui-nav-stack__view--exiting');\n }\n };\n\n const rememberFocusedView = (target: Element | null): void => {\n if (!(target instanceof HTMLElement)) return;\n const owner = target.closest<HTMLElement>('.kui-nav-stack__view');\n const key = owner?.dataset.navKey;\n if (key && owner !== target && viewport.contains(owner))\n rememberedFocus.set(key, target);\n };\n\n const focusInto = (targetView: HTMLElement): void => {\n const key = targetView.dataset.navKey;\n const remembered = key ? rememberedFocus.get(key) : undefined;\n const preferred = Array.from(\n targetView.querySelectorAll<HTMLElement>('[data-nav-focus]'),\n ).find(availableFocusTarget);\n const first = Array.from(\n targetView.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR),\n ).find(availableFocusTarget);\n const target =\n (availableFocusTarget(remembered) && targetView.contains(remembered)\n ? remembered\n : undefined) ??\n preferred ??\n first;\n if (target) {\n target.focus({ preventScroll: true });\n return;\n }\n\n if (!generatedFallbacks.has(targetView)) {\n generatedFallbacks.set(\n targetView,\n targetView.hasAttribute('tabindex')\n ? targetView.getAttribute('tabindex')\n : null,\n );\n targetView.tabIndex = -1;\n }\n targetView.focus({ preventScroll: true });\n };\n\n const onFocusIn = (event: Event): void =>\n rememberFocusedView(event.target as Element | null);\n section.addEventListener('focusin', onFocusIn);\n rememberFocusedView(section.ownerDocument.activeElement);\n\n const crossFadeChrome = (): void => {\n const current = chromeOf(section);\n const animated = duration > 0 && !reducedMotion(view);\n if (!animated) {\n chromeSnapshots = current.map((element) =>\n element.cloneNode(true),\n ) as HTMLElement[];\n return;\n }\n\n section.dataset.navChromeTransition = 'true';\n const copies = chromeSnapshots.map((snapshot) => {\n const copy = snapshot.cloneNode(true) as HTMLElement;\n copy.classList.add('kui-nav-stack__chrome-copy');\n copy.dataset.navChromeCopy = '';\n copy.setAttribute('aria-hidden', 'true');\n copy.setAttribute('inert', '');\n if (copy.hasAttribute('data-nav-stack-bottom'))\n copy.classList.add('kui-nav-stack__chrome-copy--bottom');\n section.append(copy);\n return copy;\n });\n\n current.forEach((element) =>\n element.classList.add('kui-nav-stack__chrome--entering'),\n );\n void section.offsetWidth;\n view.requestAnimationFrame(() => {\n current.forEach((element) =>\n element.classList.remove('kui-nav-stack__chrome--entering'),\n );\n copies.forEach((copy) =>\n copy.classList.add('kui-nav-stack__chrome-copy--exiting'),\n );\n });\n settle(() => {\n copies.forEach((copy) => copy.remove());\n delete section.dataset.navChromeTransition;\n });\n chromeSnapshots = current.map((element) =>\n element.cloneNode(true),\n ) as HTMLElement[];\n };\n\n const observer = new MutationObserver((records) => {\n const removed: HTMLElement[] = [];\n let added = false;\n for (const record of records) {\n record.removedNodes.forEach((node) => {\n if (\n node instanceof HTMLElement &&\n node.classList.contains('kui-nav-stack__view') &&\n node.dataset.navExiting !== 'true'\n )\n removed.push(node);\n });\n record.addedNodes.forEach((node) => {\n if (\n node instanceof HTMLElement &&\n node.classList.contains('kui-nav-stack__view')\n )\n added = true;\n });\n }\n\n const top = topView(viewport);\n const currentKey = top?.dataset.navKey;\n const poppedTop = removed.find((node) => node.dataset.navKey === activeKey);\n\n if (poppedTop && currentKey !== activeKey) {\n // Pop: bring the removed node back briefly to slide it out over the revealed view.\n poppedTop.dataset.navExiting = 'true';\n poppedTop.setAttribute('aria-hidden', 'true');\n viewport.append(poppedTop);\n play(poppedTop, 'exiting');\n crossFadeChrome();\n if (top) focusInto(top);\n settle(() => poppedTop.remove());\n } else if (added && currentKey !== activeKey && top) {\n // Push: slide the new top in.\n rememberFocusedView(section.ownerDocument.activeElement);\n play(top, 'entering');\n crossFadeChrome();\n focusInto(top);\n }\n\n activeKey = currentKey;\n });\n observer.observe(viewport, { childList: true });\n\n return () => {\n disposeBack();\n section.removeEventListener('focusin', onFocusIn);\n observer.disconnect();\n for (const timer of timers) view.clearTimeout(timer);\n timers.clear();\n section\n .querySelectorAll<HTMLElement>('[data-nav-chrome-copy]')\n .forEach((copy) => copy.remove());\n delete section.dataset.navChromeTransition;\n for (const [element, previousTabIndex] of generatedFallbacks) {\n if (!element.isConnected || element.tabIndex !== -1) continue;\n if (previousTabIndex === null) element.removeAttribute('tabindex');\n else element.setAttribute('tabindex', previousTabIndex);\n }\n generatedFallbacks.clear();\n rememberedFocus.clear();\n if (previousDuration)\n section.style.setProperty(\n '--kui-nav-stack-transition-duration',\n previousDuration,\n );\n else section.style.removeProperty('--kui-nav-stack-transition-duration');\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/components/navigation/nav-stack/wiring/wire-nav-stack.ts"],"names":[],"mappings":"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","file":"wire-nav-stack.js","sourcesContent":["import { delegate } from 'kerfjs';\n\nexport interface WireNavStackOptions {\n /** Invoked when the back control is activated. The app pops its own stack. */\n onBack?: () => void;\n /** Transition duration in ms (default 200). Set 0 to disable animation. */\n duration?: number;\n}\n\nconst DEFAULT_DURATION = 200;\nconst FOCUSABLE_SELECTOR = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(',');\n\nfunction viewsOf(viewport: Element): HTMLElement[] {\n return Array.from(\n viewport.querySelectorAll<HTMLElement>(':scope > .kui-nav-stack__view'),\n );\n}\n\nfunction topView(viewport: Element): HTMLElement | undefined {\n const live = viewsOf(viewport).filter(\n (view) => view.dataset.navExiting !== 'true',\n );\n return live[live.length - 1];\n}\n\nfunction reducedMotion(view: Window): boolean {\n return (\n typeof view.matchMedia === 'function' &&\n view.matchMedia('(prefers-reduced-motion: reduce)').matches\n );\n}\n\nfunction chromeOf(section: HTMLElement): HTMLElement[] {\n return Array.from(\n section.querySelectorAll<HTMLElement>(\n ':scope > [data-nav-stack-chrome]:not([data-nav-chrome-copy]), :scope > [data-nav-stack-bottom]:not([data-nav-chrome-copy])',\n ),\n );\n}\n\nfunction availableFocusTarget(\n element: Element | null | undefined,\n): element is HTMLElement {\n return (\n element instanceof HTMLElement &&\n element.isConnected &&\n !element.matches('[disabled], [aria-disabled=\"true\"]') &&\n !element.closest('[inert], [hidden], [aria-hidden=\"true\"]')\n );\n}\n\n/**\n * Animate a `NavStack`'s push/pop transitions and wire its back control. The app\n * owns the stack (a signal of `NavStackView[]`) and re-renders `NavStack` when it\n * changes; this helper slides the content and settles the chrome across each\n * change, moves focus into every new top view, restores that view's last focused\n * descendant on pop, and calls `onBack` when the back control is used. Mark a\n * preferred initial target with `data-nav-focus`; otherwise the first focusable\n * descendant (or the view itself) receives focus. Returns a disposer.\n */\nexport function wireNavStack(\n root: Element,\n options: WireNavStackOptions = {},\n): () => void {\n const section = root.matches('[data-component=\"nav-stack\"]')\n ? root\n : root.querySelector('[data-component=\"nav-stack\"]');\n const viewport = section?.querySelector('[data-nav-stack-viewport]');\n if (!(section instanceof HTMLElement) || !viewport) return () => {};\n\n const view = section.ownerDocument.defaultView ?? window;\n const duration = options.duration ?? DEFAULT_DURATION;\n const previousDuration = section.style.getPropertyValue(\n '--kui-nav-stack-transition-duration',\n );\n section.style.setProperty(\n '--kui-nav-stack-transition-duration',\n `${Math.max(0, duration)}ms`,\n );\n const disposeBack = delegate(section, 'click', '[data-nav-back]', () =>\n options.onBack?.(),\n );\n\n let activeKey = topView(viewport)?.dataset.navKey;\n const timers = new Set<number>();\n const frames = new Set<number>();\n const frame = (fn: () => void): void => {\n const id = view.requestAnimationFrame(() => {\n frames.delete(id);\n fn();\n });\n frames.add(id);\n };\n const finishTransition = (): void => {\n for (const id of frames) view.cancelAnimationFrame(id);\n frames.clear();\n for (const id of timers) view.clearTimeout(id);\n timers.clear();\n section\n .querySelectorAll<HTMLElement>(':scope > [data-nav-chrome-copy]')\n .forEach((element) => element.remove());\n chromeOf(section).forEach((element) =>\n element.classList.remove('kui-nav-stack__chrome--entering'),\n );\n viewsOf(viewport).forEach((element) => {\n if (element.dataset.navExiting === 'true') element.remove();\n else {\n element.classList.remove('kui-nav-stack__view--entering');\n delete element.dataset.navRevealed;\n }\n });\n delete section.dataset.navChromeTransition;\n };\n const rememberedFocus = new Map<string, HTMLElement>();\n const generatedFallbacks = new Map<HTMLElement, string | null>();\n let chromeSnapshots = chromeOf(section).map((element) =>\n element.cloneNode(true),\n ) as HTMLElement[];\n\n const settle = (fn: () => void): void => {\n if (duration <= 0 || reducedMotion(view)) {\n fn();\n return;\n }\n const timer = view.setTimeout(() => {\n timers.delete(timer);\n fn();\n }, duration);\n timers.add(timer);\n };\n\n const play = (el: HTMLElement, kind: 'entering' | 'exiting'): void => {\n const animated = duration > 0 && !reducedMotion(view);\n if (kind === 'entering') {\n // Push: start the incoming view off the trailing edge, then release it so\n // it slides to rest (translateX(100%) → 0).\n el.classList.add('kui-nav-stack__view--entering');\n if (animated) {\n // Force a reflow so the starting transform applies before we clear it.\n void el.offsetWidth;\n frame(() => el.classList.remove('kui-nav-stack__view--entering'));\n } else {\n el.classList.remove('kui-nav-stack__view--entering');\n }\n } else if (animated) {\n // Pop: the outgoing view is at rest; add the off-edge class on the next\n // frame so it slides OUT (translateX(0) → 100%), not in.\n frame(() => el.classList.add('kui-nav-stack__view--exiting'));\n } else {\n el.classList.add('kui-nav-stack__view--exiting');\n }\n };\n\n const rememberFocusedView = (target: Element | null): void => {\n if (!(target instanceof HTMLElement)) return;\n const owner = target.closest<HTMLElement>('.kui-nav-stack__view');\n const key = owner?.dataset.navKey;\n if (key && owner !== target && viewport.contains(owner))\n rememberedFocus.set(key, target);\n };\n\n const focusInto = (targetView: HTMLElement): void => {\n const key = targetView.dataset.navKey;\n const remembered = key ? rememberedFocus.get(key) : undefined;\n const preferred = Array.from(\n targetView.querySelectorAll<HTMLElement>('[data-nav-focus]'),\n ).find(availableFocusTarget);\n const first = Array.from(\n targetView.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR),\n ).find(availableFocusTarget);\n const target =\n (availableFocusTarget(remembered) && targetView.contains(remembered)\n ? remembered\n : undefined) ??\n preferred ??\n first;\n if (target) {\n target.focus({ preventScroll: true });\n return;\n }\n\n if (!generatedFallbacks.has(targetView)) {\n generatedFallbacks.set(\n targetView,\n targetView.hasAttribute('tabindex')\n ? targetView.getAttribute('tabindex')\n : null,\n );\n targetView.tabIndex = -1;\n }\n targetView.focus({ preventScroll: true });\n };\n\n const onFocusIn = (event: Event): void =>\n rememberFocusedView(event.target as Element | null);\n section.addEventListener('focusin', onFocusIn);\n rememberFocusedView(section.ownerDocument.activeElement);\n\n const crossFadeChrome = (): void => {\n const current = chromeOf(section);\n const animated = duration > 0 && !reducedMotion(view);\n if (!animated) {\n chromeSnapshots = current.map((element) =>\n element.cloneNode(true),\n ) as HTMLElement[];\n return;\n }\n\n section.dataset.navChromeTransition = 'true';\n const copies = chromeSnapshots.map((snapshot) => {\n const copy = snapshot.cloneNode(true) as HTMLElement;\n copy.classList.add('kui-nav-stack__chrome-copy');\n copy.dataset.navChromeCopy = '';\n copy.setAttribute('aria-hidden', 'true');\n copy.setAttribute('inert', '');\n if (copy.hasAttribute('data-nav-stack-bottom'))\n copy.classList.add('kui-nav-stack__chrome-copy--bottom');\n section.append(copy);\n return copy;\n });\n\n current.forEach((element) =>\n element.classList.add('kui-nav-stack__chrome--entering'),\n );\n void section.offsetWidth;\n frame(() => {\n current.forEach((element) =>\n element.classList.remove('kui-nav-stack__chrome--entering'),\n );\n copies.forEach((copy) =>\n copy.classList.add('kui-nav-stack__chrome-copy--exiting'),\n );\n });\n settle(() => {\n copies.forEach((copy) => copy.remove());\n delete section.dataset.navChromeTransition;\n });\n chromeSnapshots = current.map((element) => {\n const snapshot = element.cloneNode(true) as HTMLElement;\n snapshot.classList.remove('kui-nav-stack__chrome--entering');\n return snapshot;\n });\n };\n\n const observer = new MutationObserver((records) => {\n const removed: HTMLElement[] = [];\n for (const record of records) {\n if (record.target !== viewport) continue;\n record.removedNodes.forEach((node) => {\n if (\n node instanceof HTMLElement &&\n node.classList.contains('kui-nav-stack__view') &&\n node.dataset.navExiting !== 'true'\n )\n removed.push(node);\n });\n }\n\n const top = topView(viewport);\n const currentKey = top?.dataset.navKey;\n const poppedTop = removed.find((node) => node.dataset.navKey === activeKey);\n\n if (poppedTop && currentKey !== activeKey) {\n finishTransition();\n // Pop: bring the removed node back briefly to slide it out over the revealed view.\n poppedTop.classList.remove(\n 'kui-nav-stack__view--entering',\n 'kui-nav-stack__view--exiting',\n );\n poppedTop.dataset.navExiting = 'true';\n poppedTop.setAttribute('aria-hidden', 'true');\n viewport.append(poppedTop);\n void poppedTop.offsetWidth;\n play(poppedTop, 'exiting');\n crossFadeChrome();\n if (top) focusInto(top);\n settle(() => poppedTop.remove());\n } else if (currentKey !== activeKey && top) {\n finishTransition();\n const previous = viewsOf(viewport).find(\n (element) => element.dataset.navKey === activeKey,\n );\n if (previous) {\n previous.dataset.navRevealed = 'true';\n settle(() => delete previous.dataset.navRevealed);\n }\n // Push: slide the new top in.\n rememberFocusedView(section.ownerDocument.activeElement);\n play(top, 'entering');\n crossFadeChrome();\n focusInto(top);\n }\n\n activeKey = currentKey;\n });\n observer.observe(viewport, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['data-nav-key', 'data-nav-active'],\n });\n\n return () => {\n disposeBack();\n section.removeEventListener('focusin', onFocusIn);\n observer.disconnect();\n finishTransition();\n for (const [element, previousTabIndex] of generatedFallbacks) {\n if (!element.isConnected || element.tabIndex !== -1) continue;\n if (previousTabIndex === null) element.removeAttribute('tabindex');\n else element.setAttribute('tabindex', previousTabIndex);\n }\n generatedFallbacks.clear();\n rememberedFocus.clear();\n if (previousDuration)\n section.style.setProperty(\n '--kui-nav-stack-transition-duration',\n previousDuration,\n );\n else section.style.removeProperty('--kui-nav-stack-transition-duration');\n };\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
type Root = HTMLElement | Document;
|
|
2
|
+
/**
|
|
3
|
+
* Wire ToolbarText and ToolbarControlGroup width thresholds below root.
|
|
4
|
+
* Below is strict: hideBelow hides at width < threshold; showBelow hides at
|
|
5
|
+
* width >= threshold. Percentages use the closest Toolbar's content box;
|
|
6
|
+
* em and custom properties use the item's own inherited context. Invalid,
|
|
7
|
+
* negative, unresolved, or contradictory thresholds leave the item visible.
|
|
8
|
+
* This helper changes only a component-owned visibility marker. Existing
|
|
9
|
+
* CSS visibility policies still apply. Dispose with the owning application.
|
|
10
|
+
*/
|
|
11
|
+
declare function wireToolbarVisibility(root: Root): () => void;
|
|
12
|
+
|
|
13
|
+
export { wireToolbarVisibility };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"wire-toolbar-visibility.js"}
|
package/dist/workbench.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { SafeHtml } from 'kerfjs';
|
|
2
2
|
import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
|
|
3
|
+
import { b as NavPane, N as NavStackProps } from './nav-stack-B1Qc2e5q.js';
|
|
3
4
|
import { ListConfig } from './list.js';
|
|
4
|
-
import { NavStackProps } from './nav-stack.js';
|
|
5
5
|
import { PaneConfig } from './pane.js';
|
|
6
6
|
import { ResizableRegionSeparator, ResizableRegionCollapseMotion, ResizableRegionContentOverflow, ResizableRegionPresentation, ResizableRegionRestorePosition } from './resizable-region.js';
|
|
7
|
-
import { a as PanelToolbar,
|
|
7
|
+
import { a as PanelToolbar, c as PanelBottomToolbar, b as PanelChromePlacement, d as PanelToggle } from './panel-toolbar-CL9tYOkh.js';
|
|
8
8
|
import { ToolbarConfig } from './toolbar.js';
|
|
9
|
+
import './toolbar-text.js';
|
|
10
|
+
import './css-values.js';
|
|
9
11
|
import './flex-alignment-4ms8ZbV8.js';
|
|
10
12
|
import './sides-BPSWde0A.js';
|
|
11
|
-
import './css-values.js';
|
|
12
|
-
import './toolbar-text.js';
|
|
13
13
|
import './lucide-icon.js';
|
|
14
14
|
import 'lucide';
|
|
15
15
|
|
|
@@ -131,19 +131,11 @@ interface WorkbenchPanelBase {
|
|
|
131
131
|
restorePosition?: ResizableRegionRestorePosition;
|
|
132
132
|
}
|
|
133
133
|
/** The original static panel body: one toolbar, optional fixed chrome, and one Pane. */
|
|
134
|
-
interface WorkbenchStaticPanel extends WorkbenchPanelBase {
|
|
135
|
-
content: KerfUiContent;
|
|
134
|
+
interface WorkbenchStaticPanel extends WorkbenchPanelBase, NavPane<WorkbenchPanelToolbar, WorkbenchPanelBottomToolbar> {
|
|
136
135
|
/** The composed toolbar; marked groups and its toggle relocate on collapse. */
|
|
137
136
|
toolbar?: WorkbenchPanelToolbar;
|
|
138
|
-
header?: KerfUiContent;
|
|
139
|
-
headerList?: ListConfig;
|
|
140
137
|
headerPlacement?: WorkbenchChromePlacement;
|
|
141
|
-
footer?: KerfUiContent;
|
|
142
|
-
footerList?: ListConfig;
|
|
143
|
-
bottomToolbar?: WorkbenchPanelBottomToolbar;
|
|
144
138
|
footerPlacement?: WorkbenchChromePlacement;
|
|
145
|
-
/** Configuration for the static toolbar panel's Pane. */
|
|
146
|
-
pane?: PaneConfig;
|
|
147
139
|
navStack?: never;
|
|
148
140
|
}
|
|
149
141
|
/** A panel whose active NavStack view supplies the toolbar, fixed header, and scroll body. */
|
package/dist/workbench.js
CHANGED
package/docs/accessibility.md
CHANGED
|
@@ -568,3 +568,18 @@ pattern). See [`workbench.md`](workbench.md).
|
|
|
568
568
|
## Verification matrix
|
|
569
569
|
|
|
570
570
|
For each changed component, inspect default, hover, focus, disabled, selected/pressed, busy/error, long-content, wide, narrow, light, dark, increased-contrast, reduced-motion, keyboard-only, and 200%-zoom states where applicable. DOM order must match reading and focus order, with no clipping or unreachable action.
|
|
571
|
+
|
|
572
|
+
Text contrast checks must measure the painted solid-color backdrop, compositing
|
|
573
|
+
translucent fills from the document canvas through every ancestor and the
|
|
574
|
+
component itself before comparing the foreground. Raw RGB channels from an
|
|
575
|
+
`rgba()` value are not the rendered background. The shared browser helper in
|
|
576
|
+
`tests/browser/painted-contrast.ts` resolves CSS color syntax through canvas and
|
|
577
|
+
uses the evaluator's WCAG ratio calculation; its browser regressions cover
|
|
578
|
+
nested fills, transparent foregrounds, shadow-host backdrops, theme changes,
|
|
579
|
+
and genuinely insufficient contrast. Background images (including the catalog
|
|
580
|
+
checkerboard), filters, and group opacity remain separate visual-review concerns;
|
|
581
|
+
this helper measures only the solid CSS color layers.
|
|
582
|
+
|
|
583
|
+
Separator checks resolve the documented semantic border token in the component's
|
|
584
|
+
context instead of pinning an old opaque palette value, while still verifying
|
|
585
|
+
that the line has its intended width and a visible color.
|
|
@@ -142,6 +142,10 @@ top toolbar over a `Pane`:
|
|
|
142
142
|
work area when the panel closes; the standard `toggle: { action, name,
|
|
143
143
|
showLabel?, hideLabel? }` is always the final group.
|
|
144
144
|
|
|
145
|
+
Busy groups keep their inert control, spinner, and adjacent live status together
|
|
146
|
+
when relocated. Clearing `busy` restores the same action in its current toolbar;
|
|
147
|
+
expanding the panel returns both control and status to the panel toolbar.
|
|
148
|
+
|
|
145
149
|
The panel can also take `header` under its toolbar, `footer` below its content,
|
|
146
150
|
`bottomToolbar` below that, `headerList` / `footerList` for those fixed content
|
|
147
151
|
stacks, and `headerPlacement` / `footerPlacement` (`fixed`, `scroll`, or `auto`).
|
|
@@ -317,6 +317,8 @@ Both row components align a leading icon with the first label line when
|
|
|
317
317
|
`multiline` allows the label to wrap; additional lines extend below that fixed
|
|
318
318
|
visual anchor.
|
|
319
319
|
|
|
320
|
+
`ToolbarControlGroup.tileTone` selects `neutral`, `brand`, `success`, `warning`, or `danger` quiet fill, matching quiet border, and on-quiet foreground for a non-interactive icon tile. Compose a contained `single` group whose only child is a direct decorative LucideIcon (`aria-hidden="true"`). Omission preserves the normal group palette. The prop does not recolor interactive, mixed, multi-control, or borderless groups; keep real actions in controls with their existing focus/hover/pressed treatments.
|
|
321
|
+
|
|
320
322
|
### Public CSS anatomy
|
|
321
323
|
|
|
322
324
|
The `publicClasses` array on each entry in
|
|
@@ -577,6 +579,8 @@ compact segment never becomes an oval. An icon-only `Select` joins a group as
|
|
|
577
579
|
one more segment with its siblings' inset, radius, hover, and focus ring; only a
|
|
578
580
|
lone Select grows the group to its icon-and-caret pill.
|
|
579
581
|
|
|
582
|
+
Select menus use native viewport shifting with 10px gutters: default menus grow to content (up to 20rem), while `fitMenu` matches trigger width and long labels wrap. Keep popup sizing and position repairs out of consumer CSS.
|
|
583
|
+
|
|
580
584
|
For a rail view heading, use `Select presentation="title"`: its intrinsic
|
|
581
585
|
36px borderless trigger has a bold large label flush with its leading edge and
|
|
582
586
|
a caret beside that label. Set `focusRingInset` when a NavStack or panel clips
|
|
@@ -864,3 +868,37 @@ Keep product adapters outside the package: connection-state maps, ticket empty-s
|
|
|
864
868
|
## Testing contract
|
|
865
869
|
|
|
866
870
|
Each behavior has focused unit coverage and a real-browser flow through the production-backed catalog. Consumer bundle tests enforce subpath CSS reachability, transitive component styles, root/SSR isolation, optional registration boundaries, and the CSS-only Web Awesome theme boundary. `npm run check:css-ownership` rejects application selectors into Kerf or Web Awesome components, a non-minimal catalog compatibility stylesheet, or a return of the shared recipe stylesheet. Visual evidence supplements—never replaces—keyboard, focus, state, and event assertions.
|
|
871
|
+
|
|
872
|
+
### Toolbar width visibility
|
|
873
|
+
|
|
874
|
+
`ToolbarText` and `ToolbarControlGroup` accept typed `hideBelow` and
|
|
875
|
+
`showBelow` CSS lengths. Call `wireToolbarVisibility(root)` from
|
|
876
|
+
`@kerfjs/ui/wire-toolbar-visibility` after mounting, and call its disposer when
|
|
877
|
+
the application unmounts. The nearest Toolbar's **content-box width**, excluding
|
|
878
|
+
padding, borders, and safe-area insets, determines visibility. `hideBelow={px(416)}`
|
|
879
|
+
hides strictly below 416px; `showBelow={px(416)}` shows strictly below 416px. At
|
|
880
|
+
416px the first is visible and the second hidden. Together they define the visible
|
|
881
|
+
interval `[hideBelow, showBelow)`. Width rules intersect existing visibility and
|
|
882
|
+
expanded-state rules; they never force a CSS-hidden group visible.
|
|
883
|
+
|
|
884
|
+
Use `px`, `rem`, `em`, `pct`, `space`, `lengthVar`, or `calc`/`plus` to construct
|
|
885
|
+
lengths. Percentages resolve against that content box, `em` uses the child item's
|
|
886
|
+
font size, and custom properties use its inherited and own token context. The
|
|
887
|
+
helper follows resizes, theme/token/font changes, inserted items, nested toolbars,
|
|
888
|
+
and relocated groups. Invalid, unresolved, negative, or contradictory thresholds
|
|
889
|
+
leave the item visible under this width policy. Server-rendered content is visible
|
|
890
|
+
until wiring runs; without browser observers, initial wiring and window resize
|
|
891
|
+
still update it. Probe context and lengths are reconciled only when their CSSOM
|
|
892
|
+
values change, so unrelated DOM observers can redraw without feeding an ongoing
|
|
893
|
+
mutation loop. Probes are helper-owned imperative nodes preserved across Kerf
|
|
894
|
+
rerenders, preventing repeated probe creation and resize/DOM observer churn.
|
|
895
|
+
Invisible preserved state children keep actual CSS visibility stable while a render
|
|
896
|
+
removes transient parent markers, so post-render geometry measurements cannot
|
|
897
|
+
feed a show/hide render loop. These children never enter layout, focus or the
|
|
898
|
+
accessible tree. Toolbar replacement or deliberate internal-node removal is
|
|
899
|
+
still repaired. Dispose to
|
|
900
|
+
remove measurement probes and restore prior markers.
|
|
901
|
+
|
|
902
|
+
Hidden controls leave layout and keyboard navigation. A hidden busy group also
|
|
903
|
+
hides only its own adjacent busy status. Keep primary actions always available;
|
|
904
|
+
show a complementary overflow group for secondary actions at narrow widths.
|