@kerfjs/ui 5.0.0-beta.77 → 5.0.0-beta.79

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/README.md +9 -4
  2. package/ai/application-ui-diagnostic-ids-v1.json +4 -0
  3. package/ai/compile-time-contracts-v1.json +3 -3
  4. package/ai/component-catalog.json +152 -31
  5. package/ai/component-composition-rules.json +6 -14
  6. package/ai/component-composition.json +123 -29
  7. package/ai/components/README.md +1 -1
  8. package/ai/components/document-baseline.md +1 -1
  9. package/ai/components/nav-stack.md +6 -6
  10. package/ai/components/pane.md +3 -1
  11. package/ai/components/surface-scaffold.md +11 -2
  12. package/ai/components/{tab-scaffold.md → tab-navigator.md} +14 -14
  13. package/ai/components/token-search-field.md +1 -0
  14. package/ai/components/toolbar-control-group.md +2 -0
  15. package/ai/components/toolbar-text.md +2 -0
  16. package/ai/components/toolbar.md +6 -8
  17. package/ai/public-api-signatures-v1.md +68 -140
  18. package/ai/skill.md +15 -4
  19. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  20. package/dist/browser/nav-stack.js +2 -0
  21. package/dist/browser/split-view.js +2 -0
  22. package/dist/browser/tab-navigator.js +4 -0
  23. package/dist/catalog-resources.d.ts +3 -3
  24. package/dist/catalog.d.ts +3 -3
  25. package/dist/catalog.js +3 -3
  26. package/dist/{chunk-WJN3V2H5.js → chunk-2GOCOLQL.js} +18 -4
  27. package/dist/chunk-2GOCOLQL.js.map +1 -0
  28. package/dist/{chunk-QYXKMLHO.js → chunk-3CXRQAYT.js} +2 -2
  29. package/dist/chunk-3CXRQAYT.js.map +1 -0
  30. package/dist/{chunk-G4VKQP7B.js → chunk-3SXWV2IR.js} +5 -1
  31. package/dist/chunk-3SXWV2IR.js.map +1 -0
  32. package/dist/{chunk-VLPE6OPN.js → chunk-62VMNBV2.js} +6 -2
  33. package/dist/chunk-62VMNBV2.js.map +1 -0
  34. package/dist/{chunk-XIZRXI7N.js → chunk-A7DYEERU.js} +13 -1
  35. package/dist/chunk-A7DYEERU.js.map +1 -0
  36. package/dist/{chunk-6VQPLV3O.js → chunk-DSLRNR43.js} +9 -8
  37. package/dist/chunk-DSLRNR43.js.map +1 -0
  38. package/dist/{chunk-AOAW55JN.js → chunk-DXQZ3BEO.js} +4 -1
  39. package/dist/chunk-DXQZ3BEO.js.map +1 -0
  40. package/dist/chunk-IKCUPTR2.js +170 -0
  41. package/dist/chunk-IKCUPTR2.js.map +1 -0
  42. package/dist/{chunk-Z6PE5T7N.js → chunk-JL4BBZP4.js} +4 -3
  43. package/dist/chunk-JL4BBZP4.js.map +1 -0
  44. package/dist/collapsible-panel.js +2 -2
  45. package/dist/index.d.ts +1 -1
  46. package/dist/index.js +8 -8
  47. package/dist/nav-stack-B1Qc2e5q.d.ts +101 -0
  48. package/dist/nav-stack.d.ts +11 -93
  49. package/dist/nav-stack.js +5 -1
  50. package/dist/pane.d.ts +5 -2
  51. package/dist/pane.js +1 -1
  52. package/dist/split-view.d.ts +9 -4
  53. package/dist/split-view.js +7 -3
  54. package/dist/split-view.js.map +1 -1
  55. package/dist/styles/components/actions/toolbar/toolbar.css +7 -14
  56. package/dist/styles/components/actions/toolbar-control-group/toolbar-control-group.css +63 -13
  57. package/dist/styles/components/actions/toolbar-text/toolbar-text.css +8 -1
  58. package/dist/styles/components/forms/token-search-field/token-search-field.css +12 -1
  59. package/dist/styles/components/layout/pane/pane.css +18 -1
  60. package/dist/styles/components/navigation/nav-stack/nav-stack.css +6 -12
  61. package/dist/styles/components/navigation/tab-scaffold/tab-scaffold.css +18 -1
  62. package/dist/styles/components/surfaces/surface-scaffold/surface-scaffold.css +55 -0
  63. package/dist/styles/nav-stack.css +6 -12
  64. package/dist/styles/pane.css +18 -1
  65. package/dist/styles/surface-scaffold.css +55 -0
  66. package/dist/styles/{tab-scaffold.css → tab-navigator.css} +18 -1
  67. package/dist/styles/token-search-field.css +12 -1
  68. package/dist/styles/toolbar-control-group.css +63 -13
  69. package/dist/styles/toolbar-text.css +8 -1
  70. package/dist/styles/toolbar.css +7 -14
  71. package/dist/surface-scaffold-register.d.ts +4 -0
  72. package/dist/surface-scaffold-register.js +59 -0
  73. package/dist/surface-scaffold-register.js.map +1 -0
  74. package/dist/surface-scaffold.d.ts +10 -2
  75. package/dist/surface-scaffold.js +1 -1
  76. package/dist/{tab-scaffold.d.ts → tab-navigator.d.ts} +12 -10
  77. package/dist/{tab-scaffold.js → tab-navigator.js} +4 -3
  78. package/dist/tab-navigator.js.map +1 -0
  79. package/dist/token-search-field.js +1 -1
  80. package/dist/wire-catalog.js +1 -1
  81. package/dist/wire-scroll-dividers.d.ts +3 -3
  82. package/dist/wire-scroll-dividers.js +1 -1
  83. package/dist/wire-tab-navigator.d.ts +11 -0
  84. package/dist/{wire-tab-scaffold.js → wire-tab-navigator.js} +3 -3
  85. package/dist/wire-tab-navigator.js.map +1 -0
  86. package/dist/wire-token-search-fields.js +1 -1
  87. package/dist/wire-toolbar-visibility.js +1 -1
  88. package/dist/workbench.d.ts +5 -13
  89. package/dist/workbench.js +1 -1
  90. package/docs/app-layouts.md +44 -11
  91. package/docs/component-contract.md +9 -2
  92. package/docs/component-selection.md +17 -15
  93. package/docs/design/templates/token-search-field/empty-dark.svg +1 -1
  94. package/docs/design/templates/token-search-field/empty.svg +1 -1
  95. package/docs/design/templates/token-search-field-dark.svg +1 -1
  96. package/docs/design/templates/token-search-field.svg +1 -1
  97. package/docs/design/templates.md +1 -1
  98. package/docs/examples/application-ui-profile.json +4 -4
  99. package/docs/layout.md +31 -8
  100. package/docs/nav-stack.md +30 -18
  101. package/docs/recipes.md +6 -2
  102. package/docs/split-view.md +7 -4
  103. package/docs/surface-scaffold.md +46 -0
  104. package/docs/{tab-scaffold.md → tab-navigator.md} +83 -11
  105. package/docs/type-contracts.md +3 -3
  106. package/docs/ui-analyzer.md +5 -0
  107. package/docs/ui-doctor.md +33 -0
  108. package/docs/workbench.md +3 -2
  109. package/doctor/component-style-checks.mjs +457 -0
  110. package/doctor/index.mjs +43 -13
  111. package/llms.txt +18 -6
  112. package/package.json +16 -11
  113. package/ux-demo/recipes/list-detail-dialog.tsx +4 -0
  114. package/ux-demo/recipes/navigation-stack.tsx +2 -2
  115. package/dist/browser/tab-scaffold.js +0 -4
  116. package/dist/chunk-6VQPLV3O.js.map +0 -1
  117. package/dist/chunk-AOAW55JN.js.map +0 -1
  118. package/dist/chunk-G4VKQP7B.js.map +0 -1
  119. package/dist/chunk-QYXKMLHO.js.map +0 -1
  120. package/dist/chunk-VLPE6OPN.js.map +0 -1
  121. package/dist/chunk-WJN3V2H5.js.map +0 -1
  122. package/dist/chunk-XIZRXI7N.js.map +0 -1
  123. package/dist/chunk-Z4EPMX25.js +0 -123
  124. package/dist/chunk-Z4EPMX25.js.map +0 -1
  125. package/dist/chunk-Z6PE5T7N.js.map +0 -1
  126. package/dist/tab-scaffold.js.map +0 -1
  127. package/dist/wire-tab-scaffold.d.ts +0 -11
  128. package/dist/wire-tab-scaffold.js.map +0 -1
@@ -0,0 +1,59 @@
1
+ import WaDialog from '@awesome.me/webawesome/dist/components/dialog/dialog.js';
2
+
3
+ // src/components/surfaces/surface-scaffold/register/surface-scaffold-register.ts
4
+
5
+ // src/components/surfaces/surface-scaffold/internal/install-dialog-label.ts
6
+ var installed = /* @__PURE__ */ new WeakSet();
7
+ var observed = /* @__PURE__ */ new WeakSet();
8
+ var ownedNames = /* @__PURE__ */ new WeakMap();
9
+ function installDialogLabel(prototype) {
10
+ if (installed.has(prototype)) return;
11
+ installed.add(prototype);
12
+ const target = prototype;
13
+ const update = target.update;
14
+ target.update = function(changed) {
15
+ update.call(this, changed);
16
+ if (!this.parentElement?.classList.contains("kui-dialog-surface")) return;
17
+ if (!observed.has(this)) {
18
+ observed.add(this);
19
+ new MutationObserver(() => syncDialogLabel(this)).observe(this, {
20
+ attributes: true,
21
+ attributeFilter: ["aria-label"]
22
+ });
23
+ }
24
+ syncDialogLabel(this);
25
+ };
26
+ }
27
+ function syncDialogLabel(host) {
28
+ if (!host.parentElement?.classList.contains("kui-dialog-surface")) return;
29
+ const panel = host.dialog;
30
+ if (!panel) return;
31
+ const previous = ownedNames.get(panel);
32
+ const externalName = ["aria-label", "aria-labelledby"].some((attribute2) => {
33
+ const value2 = panel.getAttribute(attribute2);
34
+ return value2 !== null && !(previous?.attribute === attribute2 && previous.value === value2);
35
+ });
36
+ if (externalName) {
37
+ if (previous && panel.getAttribute(previous.attribute) === previous.value)
38
+ panel.removeAttribute(previous.attribute);
39
+ ownedNames.delete(panel);
40
+ return;
41
+ }
42
+ const explicit = host.getAttribute("aria-label")?.trim();
43
+ const title = host.shadowRoot?.getElementById("title");
44
+ const attribute = !explicit && !host.withoutHeader && title ? "aria-labelledby" : "aria-label";
45
+ const value = attribute === "aria-labelledby" ? "title" : explicit || host.label;
46
+ if (previous && previous.attribute !== attribute)
47
+ panel.removeAttribute(previous.attribute);
48
+ if (panel.getAttribute(attribute) !== value)
49
+ panel.setAttribute(attribute, value);
50
+ ownedNames.set(panel, { attribute, value });
51
+ }
52
+
53
+ // src/components/surfaces/surface-scaffold/register/surface-scaffold-register.ts
54
+ installDialogLabel(WaDialog.prototype);
55
+ var surfaceScaffoldElementsRegistered = true;
56
+
57
+ export { surfaceScaffoldElementsRegistered };
58
+
59
+ //# sourceMappingURL=surface-scaffold-register.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/surfaces/surface-scaffold/internal/install-dialog-label.ts","../src/components/surfaces/surface-scaffold/register/surface-scaffold-register.ts"],"names":["attribute","value"],"mappings":"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","file":"surface-scaffold-register.js","sourcesContent":["interface NamedDialog extends HTMLElement {\n dialog?: HTMLDialogElement;\n label: string;\n withoutHeader: boolean;\n update(changed: unknown): void;\n}\n\nconst installed = new WeakSet<object>();\nconst observed = new WeakSet<HTMLElement>();\nconst ownedNames = new WeakMap<\n HTMLElement,\n { attribute: string; value: string }\n>();\n\n/** Repair Web Awesome's missing native dialog name before its first modal open. */\nexport function installDialogLabel(prototype: object): void {\n if (installed.has(prototype)) return;\n installed.add(prototype);\n const target = prototype as NamedDialog;\n const update = target.update;\n target.update = function (this: NamedDialog, changed: unknown): void {\n update.call(this, changed);\n if (!this.parentElement?.classList.contains('kui-dialog-surface')) return;\n if (!observed.has(this)) {\n observed.add(this);\n new MutationObserver(() => syncDialogLabel(this)).observe(this, {\n attributes: true,\n attributeFilter: ['aria-label'],\n });\n }\n syncDialogLabel(this);\n };\n}\n\nfunction syncDialogLabel(host: NamedDialog): void {\n if (!host.parentElement?.classList.contains('kui-dialog-surface')) return;\n const panel = host.dialog;\n if (!panel) return;\n const previous = ownedNames.get(panel);\n const externalName = ['aria-label', 'aria-labelledby'].some((attribute) => {\n const value = panel.getAttribute(attribute);\n return (\n value !== null &&\n !(previous?.attribute === attribute && previous.value === value)\n );\n });\n if (externalName) {\n if (previous && panel.getAttribute(previous.attribute) === previous.value)\n panel.removeAttribute(previous.attribute);\n ownedNames.delete(panel);\n return;\n }\n const explicit = host.getAttribute('aria-label')?.trim();\n const title = host.shadowRoot?.getElementById('title');\n const attribute =\n !explicit && !host.withoutHeader && title\n ? 'aria-labelledby'\n : 'aria-label';\n const value =\n attribute === 'aria-labelledby' ? 'title' : explicit || host.label;\n if (previous && previous.attribute !== attribute)\n panel.removeAttribute(previous.attribute);\n if (panel.getAttribute(attribute) !== value)\n panel.setAttribute(attribute, value);\n ownedNames.set(panel, { attribute, value });\n}\n","import WaDialog from '@awesome.me/webawesome/dist/components/dialog/dialog.js';\n\nimport { installDialogLabel } from '../internal/install-dialog-label.js';\n\ninstallDialogLabel(WaDialog.prototype);\n\n/** Importing this module registers wa-dialog and repairs wrapped native names. */\nexport const surfaceScaffoldElementsRegistered = true;\n"]}
@@ -1,21 +1,29 @@
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 DialogSurfaceSize = 'small' | 'medium' | 'large';
5
6
  type DialogSurfacePresentation = 'modal' | 'side-sheet' | 'fullscreen';
6
7
  type SurfaceInset = 'none' | 'compact' | 'comfortable';
8
+ type DialogSurfaceMaxHeight = CssLength | 'viewport';
7
9
  interface DialogSurfaceProps {
8
10
  children: KerfUiContent;
9
11
  size?: DialogSurfaceSize;
10
12
  presentation?: DialogSurfacePresentation;
13
+ /** Modal preferred width. Takes precedence over size, within the viewport cap. */
14
+ preferredWidth?: CssLength;
11
15
  bodyInset?: SurfaceInset;
12
16
  footerInset?: SurfaceInset;
17
+ /** Modal edge clearance. Omit to retain Web Awesome's default viewport cap. */
18
+ viewportGutter?: CssLength;
19
+ /** Modal height cap, bounded by the dynamic viewport minus both gutters. */
20
+ maxHeight?: DialogSurfaceMaxHeight;
13
21
  className?: string;
14
22
  /** Native named-slot assignment when composed inside a web component. */
15
23
  slot?: string;
16
24
  }
17
25
  /** Configure recurring Web Awesome dialog geometry without consumer ::part() CSS. */
18
- declare function DialogSurface({ children, size, presentation, bodyInset, footerInset, className, slot, }: DialogSurfaceProps): kerfjs.SafeHtml;
26
+ declare function DialogSurface({ children, size, presentation, preferredWidth, bodyInset, footerInset, viewportGutter, maxHeight, className, slot, }: DialogSurfaceProps): kerfjs.SafeHtml;
19
27
  type PopupSurfaceInset = 'standard' | 'compact' | 'list-zero';
20
28
  interface PopupSurfaceProps {
21
29
  children: KerfUiContent;
@@ -27,4 +35,4 @@ interface PopupSurfaceProps {
27
35
  /** Configure recurring Web Awesome dropdown-menu geometry without consumer ::part() CSS. */
28
36
  declare function PopupSurface({ children, inset, className, slot, }: PopupSurfaceProps): kerfjs.SafeHtml;
29
37
 
30
- export { DialogSurface, type DialogSurfacePresentation, type DialogSurfaceProps, type DialogSurfaceSize, PopupSurface, type PopupSurfaceInset, type PopupSurfaceProps, type SurfaceInset };
38
+ export { DialogSurface, type DialogSurfaceMaxHeight, type DialogSurfacePresentation, type DialogSurfaceProps, type DialogSurfaceSize, PopupSurface, type PopupSurfaceInset, type PopupSurfaceProps, type SurfaceInset };
@@ -1,3 +1,3 @@
1
- export { DialogSurface, PopupSurface } from './chunk-XIZRXI7N.js';
1
+ export { DialogSurface, PopupSurface } from './chunk-A7DYEERU.js';
2
2
 
3
3
  //# sourceMappingURL=surface-scaffold.js.map
@@ -2,7 +2,7 @@ import { SafeHtml } from 'kerfjs';
2
2
  import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
3
3
  import { PaneAppearance } from './pane.js';
4
4
 
5
- interface TabScaffoldTabBase<Id extends string> {
5
+ interface TabNavigatorTabBase<Id extends string> {
6
6
  id: Id;
7
7
  label: string;
8
8
  /** Decorative icon shown above the label in the bottom bar. */
@@ -11,9 +11,11 @@ interface TabScaffoldTabBase<Id extends string> {
11
11
  content: KerfUiContent;
12
12
  /** Background of this scene's scrolling work surface. */
13
13
  appearance?: PaneAppearance;
14
+ /** Add an extra 8px inline gutter to a plain scene's scrolling content. */
15
+ deepInset?: boolean;
14
16
  }
15
17
  /** A tab with an optional count or short-status badge. */
16
- interface TabScaffoldTabCountBadge {
18
+ interface TabNavigatorTabCountBadge {
17
19
  /**
18
20
  * Optional count or short status shown as a solid danger `Badge` at the
19
21
  * top-trailing corner of the tab icon (the iOS tab-bar badge). Omitted, `''`,
@@ -31,7 +33,7 @@ interface TabScaffoldTabCountBadge {
31
33
  badgeLabel?: string;
32
34
  }
33
35
  /** A tab with the text-free dot badge (new content without a count). */
34
- interface TabScaffoldTabDotBadge {
36
+ interface TabNavigatorTabDotBadge {
35
37
  /**
36
38
  * `true` shows a solid danger dot `Badge` at the top-trailing corner of the
37
39
  * tab icon — the iOS tab-bar dot for new content without a count.
@@ -45,12 +47,12 @@ interface TabScaffoldTabDotBadge {
45
47
  badgeLabel: string;
46
48
  }
47
49
  /** One bottom-bar destination: its label, optional icon and badge, and content. */
48
- type TabScaffoldTab<Id extends string = string> = TabScaffoldTabBase<Id> & (TabScaffoldTabCountBadge | TabScaffoldTabDotBadge);
49
- interface TabScaffoldProps<Id extends string = string> {
50
+ type TabNavigatorTab<Id extends string = string> = TabNavigatorTabBase<Id> & (TabNavigatorTabCountBadge | TabNavigatorTabDotBadge);
51
+ interface TabNavigatorProps<Id extends string = string> {
50
52
  id: string;
51
53
  /** Accessible name for the tab bar. */
52
54
  label: string;
53
- tabs: readonly TabScaffoldTab<Id>[];
55
+ tabs: readonly TabNavigatorTab<Id>[];
54
56
  /** The controlled active tab id (the app owns selection). */
55
57
  active: NoInfer<Id>;
56
58
  /**
@@ -68,13 +70,13 @@ interface TabScaffoldProps<Id extends string = string> {
68
70
  slot?: string;
69
71
  }
70
72
  /**
71
- * A mobile-first, iOS-like bottom tab scaffold: a bottom tab bar that switches
73
+ * A mobile-first, iOS-like bottom tab navigator: a bottom tab bar that switches
72
74
  * between major sections, each tab keeping its own content (usually a `NavStack`)
73
75
  * mounted so its stack and scroll survive a switch. Controlled — the app owns
74
- * `active`; wire selection with `@kerfjs/ui/wire-tab-scaffold`'s `wireTabScaffold`.
76
+ * `active`; wire selection with `@kerfjs/ui/wire-tab-navigator`'s `wireTabNavigator`.
75
77
  * On larger classes, promote the tabs to a `Workbench` rail or sidebar instead of
76
78
  * a bottom bar. See `docs/23-app-layouts.md` §3.4.
77
79
  */
78
- declare function TabScaffold<Id extends string>({ id, label, tabs, active, chromeDividers, className, slot, }: TabScaffoldProps<Id>): SafeHtml;
80
+ declare function TabNavigator<Id extends string>({ id, label, tabs, active, chromeDividers, className, slot, }: TabNavigatorProps<Id>): SafeHtml;
79
81
 
80
- export { TabScaffold, type TabScaffoldProps, type TabScaffoldTab };
82
+ export { TabNavigator, type TabNavigatorProps, type TabNavigatorTab };
@@ -1,7 +1,7 @@
1
1
  import { Badge } from './chunk-J3AWE5I4.js';
2
2
  import { jsxs, jsx } from 'kerfjs/jsx-runtime';
3
3
 
4
- function TabScaffold({
4
+ function TabNavigator({
5
5
  id,
6
6
  label,
7
7
  tabs,
@@ -27,6 +27,7 @@ function TabScaffold({
27
27
  "data-tab-scaffold-scene": tab.id,
28
28
  "data-active": String(tab.id === active),
29
29
  "data-appearance": tab.appearance === "sunken" ? "sunken" : void 0,
30
+ "data-deep-inset": tab.deepInset ? "true" : void 0,
30
31
  "aria-hidden": String(tab.id !== active),
31
32
  children: tab.content
32
33
  }
@@ -85,6 +86,6 @@ function normalizeBadge(badge) {
85
86
  return void 0;
86
87
  }
87
88
 
88
- export { TabScaffold };
89
+ export { TabNavigator };
89
90
 
90
- //# sourceMappingURL=tab-scaffold.js.map
91
+ //# sourceMappingURL=tab-navigator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/navigation/tab-scaffold/tab-scaffold.tsx"],"names":[],"mappings":"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","file":"tab-navigator.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 TabNavigatorTabBase<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 TabNavigatorTabCountBadge {\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 TabNavigatorTabDotBadge {\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 TabNavigatorTab<Id extends string = string> =\n TabNavigatorTabBase<Id> &\n (TabNavigatorTabCountBadge | TabNavigatorTabDotBadge);\n\nexport interface TabNavigatorProps<Id extends string = string> {\n id: string;\n /** Accessible name for the tab bar. */\n label: string;\n tabs: readonly TabNavigatorTab<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 navigator: 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-navigator`'s `wireTabNavigator`.\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 TabNavigator<Id extends string>({\n id,\n label,\n tabs,\n active,\n chromeDividers = 'scroll',\n className = '',\n slot,\n}: TabNavigatorProps<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"]}
@@ -1,4 +1,4 @@
1
- export { TokenSearchField, placeTokenSearchCaret, readTokenSearchField } from './chunk-G4VKQP7B.js';
1
+ export { TokenSearchField, placeTokenSearchCaret, readTokenSearchField } from './chunk-3SXWV2IR.js';
2
2
 
3
3
 
4
4
 
@@ -1,6 +1,6 @@
1
1
  import { wireWorkbench } from './chunk-5BZBIRQG.js';
2
2
 
3
- import { wireScrollDividers } from './chunk-QYXKMLHO.js';
3
+ import { wireScrollDividers } from './chunk-3CXRQAYT.js';
4
4
 
5
5
 
6
6
  import { delegate } from 'kerfjs';
@@ -28,7 +28,7 @@ interface WireScrollDividersOptions {
28
28
  /**
29
29
  * App-owned scroll arrangements to pair beyond the ones found by structure
30
30
  * (every `Pane`'s header and footer around its content, every `NavStack`'s
31
- * and `TabScaffold`'s chrome around its active region, and every `TabBar`
31
+ * and `TabNavigator`'s chrome around its active region, and every `TabBar`
32
32
  * strip).
33
33
  */
34
34
  targets?: readonly ScrollDividerTarget[];
@@ -48,11 +48,11 @@ type Root = HTMLElement | Document;
48
48
  * - every `Pane` with a header or footer: its header shows a bottom divider and
49
49
  * its footer a top divider (drawn by the Pane, per its `chromeDividers`);
50
50
  * - every `NavStack`'s top chrome and bottom toolbar around its active view,
51
- * and every `TabScaffold`'s bar under its active scene: the chrome shows a
51
+ * and every `TabNavigator`'s bar under its active scene: the chrome shows a
52
52
  * bottom (top chrome) or top (bottom toolbar, bar) divider. The scroller is
53
53
  * whichever element actually scrolls there: the view or scene itself, or,
54
54
  * through a sole child with no chrome of its own on that edge, a `Pane`'s
55
- * content or a nested `NavStack` / `TabScaffold` region (a Pane's own header
55
+ * content or a nested `NavStack` / `TabNavigator` region (a Pane's own header
56
56
  * or footer draws that boundary instead);
57
57
  * - every `TabBar` strip: the bar draws a divider on each side of the strip
58
58
  * whose tabs are scrolled out of view;
@@ -1,3 +1,3 @@
1
- export { wireScrollDividers } from './chunk-QYXKMLHO.js';
1
+ export { wireScrollDividers } from './chunk-3CXRQAYT.js';
2
2
 
3
3
  //# sourceMappingURL=wire-scroll-dividers.js.map
@@ -0,0 +1,11 @@
1
+ interface WireTabNavigatorOptions {
2
+ /** Invoked with the selected tab id when a bottom-bar tab is activated. */
3
+ onSelect: (tabId: string) => void;
4
+ }
5
+ /**
6
+ * Wire a `TabNavigator`'s bottom tab bar: clicking a tab calls `onSelect` with its
7
+ * id (the app then updates its controlled `active`). Returns a disposer.
8
+ */
9
+ declare function wireTabNavigator(root: Element, options: WireTabNavigatorOptions): () => void;
10
+
11
+ export { type WireTabNavigatorOptions, wireTabNavigator };
@@ -1,7 +1,7 @@
1
1
  import { delegate } from 'kerfjs';
2
2
 
3
3
  // src/components/navigation/tab-scaffold/wiring/wire-tab-scaffold.ts
4
- function wireTabScaffold(root, options) {
4
+ function wireTabNavigator(root, options) {
5
5
  const section = root.matches('[data-component="tab-scaffold"]') ? root : root.querySelector('[data-component="tab-scaffold"]');
6
6
  if (!(section instanceof HTMLElement)) return () => {
7
7
  };
@@ -16,6 +16,6 @@ function wireTabScaffold(root, options) {
16
16
  );
17
17
  }
18
18
 
19
- export { wireTabScaffold };
19
+ export { wireTabNavigator };
20
20
 
21
- //# sourceMappingURL=wire-tab-scaffold.js.map
21
+ //# sourceMappingURL=wire-tab-navigator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/navigation/tab-scaffold/wiring/wire-tab-scaffold.ts"],"names":[],"mappings":";;;AAWO,SAAS,gBAAA,CACd,MACA,OAAA,EACY;AACZ,EAAA,MAAM,OAAA,GAAU,KAAK,OAAA,CAAQ,iCAAiC,IAC1D,IAAA,GACA,IAAA,CAAK,cAAc,iCAAiC,CAAA;AACxD,EAAA,IAAI,EAAE,OAAA,YAAmB,WAAA,CAAA,EAAc,OAAO,MAAM;AAAA,EAAC,CAAA;AACrD,EAAA,OAAO,QAAA;AAAA,IACL,OAAA;AAAA,IACA,OAAA;AAAA,IACA,yBAAA;AAAA,IACA,CAAC,QAAQ,MAAA,KAAW;AAClB,MAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,YAAA,CAAa,uBAAuB,CAAA;AACzD,MAAA,IAAI,KAAA,EAAO,OAAA,CAAQ,QAAA,CAAS,KAAK,CAAA;AAAA,IACnC;AAAA,GACF;AACF","file":"wire-tab-navigator.js","sourcesContent":["import { delegate } from 'kerfjs';\n\nexport interface WireTabNavigatorOptions {\n /** Invoked with the selected tab id when a bottom-bar tab is activated. */\n onSelect: (tabId: string) => void;\n}\n\n/**\n * Wire a `TabNavigator`'s bottom tab bar: clicking a tab calls `onSelect` with its\n * id (the app then updates its controlled `active`). Returns a disposer.\n */\nexport function wireTabNavigator(\n root: Element,\n options: WireTabNavigatorOptions,\n): () => void {\n const section = root.matches('[data-component=\"tab-scaffold\"]')\n ? root\n : root.querySelector('[data-component=\"tab-scaffold\"]');\n if (!(section instanceof HTMLElement)) return () => {};\n return delegate(\n section,\n 'click',\n '[data-tab-scaffold-tab]',\n (_event, target) => {\n const tabId = target.getAttribute('data-tab-scaffold-tab');\n if (tabId) options.onSelect(tabId);\n },\n );\n}\n"]}
@@ -1,4 +1,4 @@
1
- export { wireTokenSearchFields } from './chunk-VLPE6OPN.js';
1
+ export { wireTokenSearchFields } from './chunk-62VMNBV2.js';
2
2
 
3
3
 
4
4
 
@@ -1,3 +1,3 @@
1
- export { wireToolbarVisibility } from './chunk-WJN3V2H5.js';
1
+ export { wireToolbarVisibility } from './chunk-2GOCOLQL.js';
2
2
 
3
3
  //# sourceMappingURL=wire-toolbar-visibility.js.map
@@ -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, b as PanelChromePlacement, c as PanelBottomToolbar, d as PanelToggle } from './panel-toolbar-CL9tYOkh.js';
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
@@ -1,4 +1,4 @@
1
- export { Workbench } from './chunk-6VQPLV3O.js';
1
+ export { Workbench } from './chunk-DSLRNR43.js';
2
2
 
3
3
 
4
4
 
@@ -9,7 +9,7 @@ changes. The layouts:
9
9
  - [`NavStack`](nav-stack.md) — push/pop navigation (a single pane is a one-entry stack).
10
10
  - [`SplitView`](split-view.md) — list-detail (two panes, collapsing to a stack).
11
11
  - [`Workbench`](workbench.md) — the Xcode-like collapsible rails + drawer.
12
- - [`TabScaffold`](tab-scaffold.md) — the iOS bottom tab bar (each tab a stack).
12
+ - [`TabNavigator`](tab-navigator.md) — the iOS bottom tab bar (each tab a stack).
13
13
  - [`CollapsiblePanel` + `wireSidebar`](collapsible-panel.md) — a standalone
14
14
  collapsible rail or bottom drawer (with `CollapsiblePanelToggle`), outside a full
15
15
  shell: the standard collapse animation, icon convention, and `wireSidebar`
@@ -45,7 +45,7 @@ the layouts handle the insets for you. There is nothing to configure:
45
45
  - **No double inset.** Whichever region applies an inset clears it for its
46
46
  descendants. A layout region whose only child is a `Pane` or another layout
47
47
  lets that child own the insets. That child fills the region — a `Workbench`
48
- area or panel, a `NavStack` view, a `TabScaffold` scene — so a sole `Pane`'s
48
+ area or panel, a `NavStack` view, a `TabNavigator` scene — so a sole `Pane`'s
49
49
  header stays pinned while its own content scrolls. A `Pane` header or footer whose only child is
50
50
  a `Toolbar` hands the inline insets to the toolbar, so the toolbar's dividers
51
51
  still reach the edge.
@@ -96,15 +96,48 @@ The device insets come from `--kui-safe-area-block-start`, `-block-end`,
96
96
  Override them on `:root` to reserve room for app-owned chrome or to simulate a
97
97
  device in tests.
98
98
 
99
+ ### Edge-to-edge content inside a Pane
100
+
101
+ The Pane's scrolling slot publishes its resolved horizontal padding as
102
+ `--kui-pane-content-inset-inline-start` and
103
+ `--kui-pane-content-inset-inline-end`. Both inherited values include the safe
104
+ area on the corresponding side, plus the extra 8px when `deepInset` is true.
105
+ An app component can use them to let a row background or divider reach the
106
+ Pane edge while its text stays on the ordinary content axis:
107
+
108
+ ```css
109
+ .orders-table {
110
+ margin-inline-start: calc(-1 * var(--kui-pane-content-inset-inline-start));
111
+ margin-inline-end: calc(-1 * var(--kui-pane-content-inset-inline-end));
112
+ }
113
+
114
+ .orders-table th:first-child,
115
+ .orders-table td:first-child {
116
+ padding-inline-start: calc(var(--kui-pane-content-inset-inline-start) + 17px);
117
+ }
118
+
119
+ .orders-table th:last-child,
120
+ .orders-table td:last-child {
121
+ padding-inline-end: calc(var(--kui-pane-content-inset-inline-end) + 17px);
122
+ }
123
+ ```
124
+
125
+ Here 17px is the app's chosen text offset from the content slot: a default
126
+ `ContentItem` uses 8px outer margin, 1px border, and 8px inner padding. Adjust
127
+ that offset to match the neighboring content. Keep the table inside the Pane's
128
+ single scrolling slot; the negative margins change only its inline reach. The
129
+ [`Pane` UX catalog example](../ux-demo/demos/pane.tsx) shows both `deepInset`
130
+ states with the same table component.
131
+
99
132
  ## Decision matrix
100
133
 
101
- | Situation | Layout | Device threshold |
102
- | ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
103
- | Simple app, a few flat sections | `NavStack` with one entry (single pane); add `TabScaffold` for 2–5 co-equal sections on handset | `TabScaffold` on `compact`; promote its tabs to a `Workbench` rail / sidebar `atLeast('desktop')` |
104
- | Drill-down browsing (list → item → sub-item) | `NavStack`; upgrade to `SplitView` once list + detail fit together | `SplitView` two-pane `atLeast('tablet')` landscape / non-`compact`; `NavStack` form on `compact` |
105
- | Two related panes, selecting on the left updates the right | `SplitView` | two panes when not `compact`; collapses to `NavStack` (list → detail) on `compact` |
106
- | Complex tool / editor with peripheral panels (navigator, inspector, console) | `Workbench` | full three-panel `atLeast('desktop')`; on smaller classes present the rails via `NavStack` / overlay drawers, not a shrunken shell |
107
- | Mobile app with 2–5 top-level destinations, each its own drill-down | `TabScaffold`, each tab a `NavStack` | bottom bar on `compact`; promote to a rail / sidebar `atLeast('desktop')` |
134
+ | Situation | Layout | Device threshold |
135
+ | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
136
+ | Simple app, a few flat sections | `NavStack` with one entry (single pane); add `TabNavigator` for 2–5 co-equal sections on handset | `TabNavigator` on `compact`; promote its tabs to a `Workbench` rail / sidebar `atLeast('desktop')` |
137
+ | Drill-down browsing (list → item → sub-item) | `NavStack`; upgrade to `SplitView` once list + detail fit together | `SplitView` two-pane `atLeast('tablet')` landscape / non-`compact`; `NavStack` form on `compact` |
138
+ | Two related panes, selecting on the left updates the right | `SplitView` | two panes when not `compact`; collapses to `NavStack` (list → detail) on `compact` |
139
+ | Complex tool / editor with peripheral panels (navigator, inspector, console) | `Workbench` | full three-panel `atLeast('desktop')`; on smaller classes present the rails via `NavStack` / overlay drawers, not a shrunken shell |
140
+ | Mobile app with 2–5 top-level destinations, each its own drill-down | `TabNavigator`, each tab a `NavStack` | bottom bar on `compact`; promote to a rail / sidebar `atLeast('desktop')` |
108
141
 
109
142
  ### Worked examples
110
143
 
@@ -129,13 +162,13 @@ device in tests.
129
162
  default) and shrink in proportion when the window narrows; the bottom drawer
130
163
  leaves it `mainMinHeight` (120px by default) and shrinks when the window gets
131
164
  shorter.
132
- - **Social app (tabbed):** `TabScaffold` with Home / Search / Profile tabs, each
165
+ - **Social app (tabbed):** `TabNavigator` with Home / Search / Profile tabs, each
133
166
  `content` a `NavStack`. On a tablet/desktop, render the same sections as a
134
167
  `Workbench` left rail instead of a bottom bar.
135
168
 
136
169
  Whatever the layout, call `wireScrollDividers(appRoot)` from
137
170
  `@kerfjs/ui/wire-scroll-dividers` once: every `Pane` a layout renders, every
138
- `NavStack`'s top chrome and bottom toolbar, and every `TabScaffold` bar then
171
+ `NavStack`'s top chrome and bottom toolbar, and every `TabNavigator` bar then
139
172
  draw the line between the pinned chrome and the scrolling content only while
140
173
  content is scrolled beneath them, and toolbars need no divider of their own. See [Scroll dividers](layout.md#scroll-dividers).
141
174
 
@@ -713,7 +713,7 @@ helpers. Its collapse behavior was _rich and error-prone_ — reveal, focus
713
713
  transfer, Escape, empty-blur collapse, focus return — the kind of transient chrome
714
714
  apps kept reimplementing inconsistently, so the helper owns it. Everywhere else the
715
715
  app's state is **domain or persisted, not transient chrome, and stays app-owned**: a
716
- `NavStack`'s view stack is navigation history, a `TabBar`/`TabScaffold`'s selection
716
+ `NavStack`'s view stack is navigation history, a `TabBar`/`TabNavigator`'s selection
717
717
  and tab order are data, a `ResizableRegion`'s committed size and a
718
718
  `Workbench`/`SplitView` rail's `collapsed` flag are persisted layout preferences.
719
719
  Each helper already owns only the _ephemeral mechanics_ around that state —
@@ -890,7 +890,14 @@ leave the item visible under this width policy. Server-rendered content is visib
890
890
  until wiring runs; without browser observers, initial wiring and window resize
891
891
  still update it. Probe context and lengths are reconciled only when their CSSOM
892
892
  values change, so unrelated DOM observers can redraw without feeding an ongoing
893
- mutation loop. Dispose to remove measurement probes and restore prior markers.
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.
894
901
 
895
902
  Hidden controls leave layout and keyboard navigation. A hidden busy group also
896
903
  hides only its own adjacent busy status. Keep primary actions always available;