@kerfjs/ui 5.0.0-beta.71 → 5.0.0-beta.73

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 (91) hide show
  1. package/ai/component-catalog.json +6 -3
  2. package/ai/component-composition.json +7 -1
  3. package/ai/components/toolbar-text.md +3 -1
  4. package/ai/public-api-signatures-v1.md +16 -11
  5. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  6. package/analyzer/component-ownership.mjs +41 -13
  7. package/analyzer/index.mjs +40 -11
  8. package/dist/app-tab.js +1 -1
  9. package/dist/badge.js +1 -1
  10. package/dist/catalog.js +6 -6
  11. package/dist/{chunk-K67Y2JNF.js → chunk-2IGHGE3M.js} +3 -3
  12. package/dist/{chunk-K67Y2JNF.js.map → chunk-2IGHGE3M.js.map} +1 -1
  13. package/dist/{chunk-RNQNEENY.js → chunk-2RMDP7HQ.js} +2 -2
  14. package/dist/{chunk-RNQNEENY.js.map → chunk-2RMDP7HQ.js.map} +1 -1
  15. package/dist/{chunk-KCCEH7RF.js → chunk-33EOFAUN.js} +4 -4
  16. package/dist/{chunk-KCCEH7RF.js.map → chunk-33EOFAUN.js.map} +1 -1
  17. package/dist/{chunk-47CKKLBE.js → chunk-3DOHW3K4.js} +2 -2
  18. package/dist/{chunk-47CKKLBE.js.map → chunk-3DOHW3K4.js.map} +1 -1
  19. package/dist/{chunk-J3KGJ6P6.js → chunk-4NG4Y2Q6.js} +1 -1
  20. package/dist/{chunk-J3KGJ6P6.js.map → chunk-4NG4Y2Q6.js.map} +1 -1
  21. package/dist/{chunk-NC4CFOID.js → chunk-6LAQWWEX.js} +3 -3
  22. package/dist/{chunk-NC4CFOID.js.map → chunk-6LAQWWEX.js.map} +1 -1
  23. package/dist/{chunk-Q6YU3BT7.js → chunk-7KHKLG5C.js} +2 -2
  24. package/dist/{chunk-Q6YU3BT7.js.map → chunk-7KHKLG5C.js.map} +1 -1
  25. package/dist/{chunk-MP2GDBQZ.js → chunk-ALGYHYXP.js} +1 -1
  26. package/dist/{chunk-MP2GDBQZ.js.map → chunk-ALGYHYXP.js.map} +1 -1
  27. package/dist/{chunk-QJ5VFCW3.js → chunk-BGD7EHUE.js} +2 -2
  28. package/dist/{chunk-QJ5VFCW3.js.map → chunk-BGD7EHUE.js.map} +1 -1
  29. package/dist/{chunk-UIDKY4UB.js → chunk-DPAUKNB6.js} +2 -2
  30. package/dist/{chunk-UIDKY4UB.js.map → chunk-DPAUKNB6.js.map} +1 -1
  31. package/dist/{chunk-M6DEEBEF.js → chunk-HFBI4ZGP.js} +3 -3
  32. package/dist/{chunk-M6DEEBEF.js.map → chunk-HFBI4ZGP.js.map} +1 -1
  33. package/dist/{chunk-KVBRMEPM.js → chunk-I6FTU27I.js} +1 -1
  34. package/dist/{chunk-KVBRMEPM.js.map → chunk-I6FTU27I.js.map} +1 -1
  35. package/dist/{chunk-V3DX3K65.js → chunk-IJNYCPAU.js} +20 -3
  36. package/dist/chunk-IJNYCPAU.js.map +1 -0
  37. package/dist/{chunk-P7D3EMXM.js → chunk-KFJSNWO4.js} +2 -2
  38. package/dist/{chunk-P7D3EMXM.js.map → chunk-KFJSNWO4.js.map} +1 -1
  39. package/dist/{chunk-MEY33VWB.js → chunk-KZRR7NNV.js} +1 -1
  40. package/dist/{chunk-MEY33VWB.js.map → chunk-KZRR7NNV.js.map} +1 -1
  41. package/dist/{chunk-DC6CN2DS.js → chunk-L67OYMGE.js} +3 -3
  42. package/dist/{chunk-DC6CN2DS.js.map → chunk-L67OYMGE.js.map} +1 -1
  43. package/dist/{chunk-GLDWRCAM.js → chunk-RT5WKYX3.js} +3 -3
  44. package/dist/{chunk-GLDWRCAM.js.map → chunk-RT5WKYX3.js.map} +1 -1
  45. package/dist/{chunk-KLJKU6ZL.js → chunk-TDN5EE3S.js} +1 -1
  46. package/dist/chunk-TDN5EE3S.js.map +1 -0
  47. package/dist/{chunk-AHEG27M7.js → chunk-TT5ROTQK.js} +1 -1
  48. package/dist/{chunk-AHEG27M7.js.map → chunk-TT5ROTQK.js.map} +1 -1
  49. package/dist/{chunk-TJK44RQC.js → chunk-V4WLBOVJ.js} +2 -2
  50. package/dist/{chunk-TJK44RQC.js.map → chunk-V4WLBOVJ.js.map} +1 -1
  51. package/dist/{chunk-IA7TMD4Y.js → chunk-ZLKZFKLW.js} +2 -2
  52. package/dist/{chunk-IA7TMD4Y.js.map → chunk-ZLKZFKLW.js.map} +1 -1
  53. package/dist/{chunk-7OKX3EF6.js → chunk-ZSJOLL3S.js} +3 -3
  54. package/dist/{chunk-7OKX3EF6.js.map → chunk-ZSJOLL3S.js.map} +1 -1
  55. package/dist/collapsible-panel.js +2 -2
  56. package/dist/empty-state.js +1 -1
  57. package/dist/index.js +17 -17
  58. package/dist/list-action-row.js +1 -1
  59. package/dist/list-header.js +1 -1
  60. package/dist/list-item.js +1 -1
  61. package/dist/loading-spinner.js +1 -1
  62. package/dist/nav-stack.js +1 -1
  63. package/dist/popup-menu.js +1 -1
  64. package/dist/resizable-region.js +1 -1
  65. package/dist/select.js +1 -1
  66. package/dist/skeleton.js +1 -1
  67. package/dist/spacer.js +1 -1
  68. package/dist/split-view.js +2 -2
  69. package/dist/state-banner.js +1 -1
  70. package/dist/styles/chip.css +6 -0
  71. package/dist/styles/toolbar-text.css +20 -0
  72. package/dist/tab-scaffold.js +1 -1
  73. package/dist/toolbar-control-group.js +1 -1
  74. package/dist/toolbar-text.d.ts +15 -10
  75. package/dist/toolbar-text.js +1 -1
  76. package/dist/value-table.js +1 -1
  77. package/dist/wire-catalog.js +1 -1
  78. package/dist/wire-resizable-regions.js +1 -1
  79. package/dist/wire-workbench.js +1 -1
  80. package/dist/workbench.js +1 -1
  81. package/docs/accessibility.md +5 -0
  82. package/docs/component-contract.md +6 -0
  83. package/docs/component-selection.md +5 -0
  84. package/docs/layout.md +4 -0
  85. package/docs/ui-analyzer.md +4 -0
  86. package/docs/ui-doctor.md +8 -2
  87. package/docs/ux-demo.md +1 -1
  88. package/package.json +1 -1
  89. package/ux-demo/recipes/composer-form.tsx +2 -2
  90. package/dist/chunk-KLJKU6ZL.js.map +0 -1
  91. package/dist/chunk-V3DX3K65.js.map +0 -1
@@ -1,4 +1,4 @@
1
- export { ListHeader } from './chunk-M6DEEBEF.js';
1
+ export { ListHeader } from './chunk-HFBI4ZGP.js';
2
2
 
3
3
 
4
4
 
package/dist/list-item.js CHANGED
@@ -1,4 +1,4 @@
1
- export { ListItem, ListItemLink } from './chunk-NC4CFOID.js';
1
+ export { ListItem, ListItemLink } from './chunk-6LAQWWEX.js';
2
2
 
3
3
 
4
4
 
@@ -1,4 +1,4 @@
1
- export { LoadingSpinner } from './chunk-KVBRMEPM.js';
1
+ export { LoadingSpinner } from './chunk-I6FTU27I.js';
2
2
 
3
3
 
4
4
  //# sourceMappingURL=loading-spinner.js.map
package/dist/nav-stack.js CHANGED
@@ -1,4 +1,4 @@
1
- export { NavStack } from './chunk-DC6CN2DS.js';
1
+ export { NavStack } from './chunk-L67OYMGE.js';
2
2
 
3
3
 
4
4
 
@@ -1,3 +1,3 @@
1
- export { PopupMenu, closePopupMenu, openPopupMenuAt } from './chunk-J3KGJ6P6.js';
1
+ export { PopupMenu, closePopupMenu, openPopupMenuAt } from './chunk-4NG4Y2Q6.js';
2
2
 
3
3
  //# sourceMappingURL=popup-menu.js.map
@@ -1,3 +1,3 @@
1
- export { ResizableRegion, clampRegionSize, resizeRegionFromPointer } from './chunk-KLJKU6ZL.js';
1
+ export { ResizableRegion, clampRegionSize, resizeRegionFromPointer } from './chunk-TDN5EE3S.js';
2
2
 
3
3
  //# sourceMappingURL=resizable-region.js.map
package/dist/select.js CHANGED
@@ -1,4 +1,4 @@
1
- export { Select } from './chunk-K67Y2JNF.js';
1
+ export { Select } from './chunk-2IGHGE3M.js';
2
2
 
3
3
 
4
4
 
package/dist/skeleton.js CHANGED
@@ -1,4 +1,4 @@
1
- export { Skeleton } from './chunk-AHEG27M7.js';
1
+ export { Skeleton } from './chunk-TT5ROTQK.js';
2
2
 
3
3
 
4
4
  //# sourceMappingURL=skeleton.js.map
package/dist/spacer.js CHANGED
@@ -1,4 +1,4 @@
1
- export { Spacer } from './chunk-MEY33VWB.js';
1
+ export { Spacer } from './chunk-KZRR7NNV.js';
2
2
 
3
3
 
4
4
  //# sourceMappingURL=spacer.js.map
@@ -1,5 +1,5 @@
1
- import { NavStack } from './chunk-DC6CN2DS.js';
2
- import { ResizableRegion } from './chunk-KLJKU6ZL.js';
1
+ import { NavStack } from './chunk-L67OYMGE.js';
2
+ import { ResizableRegion } from './chunk-TDN5EE3S.js';
3
3
 
4
4
 
5
5
 
@@ -1,4 +1,4 @@
1
- export { StateBanner } from './chunk-GLDWRCAM.js';
1
+ export { StateBanner } from './chunk-RT5WKYX3.js';
2
2
 
3
3
 
4
4
 
@@ -22,6 +22,12 @@
22
22
  white-space: nowrap;
23
23
  }
24
24
 
25
+ /* A normal status chip stays readable beside a shrinkable toolbar title.
26
+ Only the explicit truncate variant gives up its label width. */
27
+ .kui-chip:not([data-truncate]) {
28
+ flex-shrink: 0;
29
+ }
30
+
25
31
  .kui-chip[data-tone="brand"] {
26
32
  --_kui-chip-fill-quiet: var(--kui-color-brand-fill-quiet);
27
33
  --_kui-chip-fill-solid: var(--kui-color-brand-on-fill);
@@ -12,6 +12,26 @@
12
12
  align-items: center;
13
13
  }
14
14
 
15
+ button.kui-toolbar-text {
16
+ appearance: none;
17
+ border: 0;
18
+ border-radius: 0.375rem;
19
+ background: transparent;
20
+ font-family: inherit;
21
+ text-align: start;
22
+ justify-content: flex-start;
23
+ cursor: pointer;
24
+ }
25
+
26
+ button.kui-toolbar-text:hover {
27
+ background: var(--kui-color-neutral-fill-normal);
28
+ }
29
+
30
+ button.kui-toolbar-text:focus-visible {
31
+ outline: var(--kui-focus-ring);
32
+ outline-offset: 0.125rem;
33
+ }
34
+
15
35
  /* In a Toolbar zone, a title fills the toolbar's control band (the
16
36
  `--_kui-toolbar-band` the Toolbar provides) and centers its first line in
17
37
  it; further lines of a wrapped title continue below the band. These rules
@@ -1,4 +1,4 @@
1
- import { Badge } from './chunk-MP2GDBQZ.js';
1
+ import { Badge } from './chunk-ALGYHYXP.js';
2
2
  import { jsxs, jsx } from 'kerfjs/jsx-runtime';
3
3
 
4
4
  function TabScaffold({
@@ -1,4 +1,4 @@
1
- export { ToolbarActionLink, ToolbarControlGroup } from './chunk-QJ5VFCW3.js';
1
+ export { ToolbarActionLink, ToolbarControlGroup } from './chunk-BGD7EHUE.js';
2
2
 
3
3
 
4
4
 
@@ -9,14 +9,6 @@ interface ToolbarTextBaseProps {
9
9
  className?: string;
10
10
  /** Optional id, e.g. so a dialog can reference the title via aria-labelledby. */
11
11
  id?: string;
12
- /**
13
- * Expose heading semantics (`role="heading"` + `aria-level`) so the text acts as
14
- * a heading landmark — e.g. a page's primary title. Omit to keep the plain span
15
- * (the default), which suits a dialog title referenced via `aria-labelledby`.
16
- */
17
- headingLevel?: HeadingLevel;
18
- /** Render the text as an unanimated loading skeleton instead of its value. */
19
- placeholder?: boolean;
20
12
  /**
21
13
  * Show a trailing ellipsis (…) where the text is truncated — on the single line
22
14
  * (default), or at the `maxLines` boundary when wrapping. Set false to hard-clip
@@ -40,7 +32,20 @@ type ToolbarTextWrappingProps = {
40
32
  wrap?: false;
41
33
  maxLines?: never;
42
34
  };
43
- type ToolbarTextProps = ToolbarTextBaseProps & ToolbarTextWrappingProps;
44
- declare function ToolbarText({ text, size, className, id, headingLevel, placeholder, wrap, ellipsis, maxLines, fill, slot, }: ToolbarTextProps): kerfjs.SafeHtml;
35
+ type ToolbarTextProps = ToolbarTextBaseProps & (({
36
+ action?: never;
37
+ /** Expose `role="heading"` and `aria-level` on read-only text. */
38
+ headingLevel?: HeadingLevel;
39
+ /** Render an unanimated loading skeleton instead of the text. */
40
+ placeholder?: boolean;
41
+ } & ToolbarTextWrappingProps) | {
42
+ /** Render a native button with this delegated action; the app owns editing. */
43
+ action: string;
44
+ headingLevel?: never;
45
+ placeholder?: never;
46
+ wrap?: never;
47
+ maxLines?: never;
48
+ });
49
+ declare function ToolbarText({ text, size, className, id, headingLevel, placeholder, wrap, ellipsis, maxLines, fill, slot, action, }: ToolbarTextProps): kerfjs.SafeHtml;
45
50
 
46
51
  export { type HeadingLevel, ToolbarText, type ToolbarTextProps, type ToolbarTextSize };
@@ -1,4 +1,4 @@
1
- export { ToolbarText } from './chunk-V3DX3K65.js';
1
+ export { ToolbarText } from './chunk-IJNYCPAU.js';
2
2
 
3
3
 
4
4
 
@@ -1,4 +1,4 @@
1
- export { ValueTable, ValueTableRow } from './chunk-P7D3EMXM.js';
1
+ export { ValueTable, ValueTableRow } from './chunk-KFJSNWO4.js';
2
2
 
3
3
 
4
4
 
@@ -1,4 +1,4 @@
1
- import { wireWorkbench } from './chunk-TJK44RQC.js';
1
+ import { wireWorkbench } from './chunk-V4WLBOVJ.js';
2
2
 
3
3
  import { wireScrollDividers } from './chunk-6DFDTWGG.js';
4
4
 
@@ -1,4 +1,4 @@
1
- export { wireResizableRegions } from './chunk-UIDKY4UB.js';
1
+ export { wireResizableRegions } from './chunk-DPAUKNB6.js';
2
2
 
3
3
 
4
4
 
@@ -1,4 +1,4 @@
1
- export { wireWorkbench } from './chunk-TJK44RQC.js';
1
+ export { wireWorkbench } from './chunk-V4WLBOVJ.js';
2
2
 
3
3
 
4
4
 
package/dist/workbench.js CHANGED
@@ -1,4 +1,4 @@
1
- export { Workbench } from './chunk-KCCEH7RF.js';
1
+ export { Workbench } from './chunk-33EOFAUN.js';
2
2
 
3
3
 
4
4
 
@@ -322,6 +322,11 @@ role/level pair used throughout Kerf UI. Keep the levels meaningful and
322
322
  non-skipping within a view. Pass trailing
323
323
  controls as a labeled `ToolbarControlGroup` when that group needs an accessible
324
324
  name. Keep supporting copy as app-owned content below the toolbar.
325
+ An editable title can use `ToolbarText action="edit-title"`; it renders a native
326
+ button whose full text remains its accessible name even when visually truncated.
327
+ Click, Enter, and Space activate the delegated action. The app owns the input
328
+ shown while editing and its focus lifecycle. Because the button cannot also be
329
+ a heading landmark, expose a heading elsewhere when the view needs one.
325
330
 
326
331
  ## FloatingToolbar
327
332
 
@@ -622,6 +622,12 @@ zone holds an optional icon `ToolbarControlGroup` and a direct extra-large
622
622
  `ToolbarText`; app actions belong in a trailing `ToolbarControlGroup`. Omit the
623
623
  icon group when it has no content. Supporting copy is app-owned content below
624
624
  the toolbar so it cannot pull the icon or actions out of alignment.
625
+ For an editable title, `ToolbarText action="edit-title"` renders a native button
626
+ directly in the leading zone. It keeps content width when there is room, shrinks
627
+ and ellipsizes beside a status chip when space is tight, and emits the delegated
628
+ action on click or keyboard activation. The application owns the editing control
629
+ and the swap between display and edit states. An actionable title is a button,
630
+ so place any required heading landmark on another element in the view.
625
631
 
626
632
  `ValueTable` composes typed `ValueTableRow` entries. A row owns its `dt`/`dd`
627
633
  semantics and may receive a leading `SafeHtml` icon. Every row keeps 8px of
@@ -391,6 +391,11 @@ zone children are `ToolbarText` (identity/title text) and `ToolbarControlGroup`
391
391
  standalone `wa-button` for the application's primary action. It keeps its own
392
392
  Web Awesome brand, danger, size, and hover chrome, while Toolbar aligns it in
393
393
  the control band and wraps it with other trailing items when space is tight.
394
+ For a title that opens an editor, set `action` on a direct `ToolbarText`: it
395
+ renders a native button that keeps its content width when possible and
396
+ ellipsizes before an adjacent status chip when the leading zone narrows. The
397
+ application handles the delegated action and replaces it with its own input
398
+ while editing. Use a separate heading landmark if the view needs one.
394
399
  Do not drop other bare buttons, inputs, links, or arbitrary markup straight
395
400
  into a zone; wrap controls in a `ToolbarControlGroup` so they get the shared
396
401
  toolbar geometry, hover/pressed treatment, and grouping. `SegmentedControl`,
package/docs/layout.md CHANGED
@@ -309,6 +309,10 @@ zone holds an optional icon `ToolbarControlGroup` and a direct extra-large
309
309
  `ToolbarText`; actions belong in a trailing group. Omit empty groups. Supporting
310
310
  copy is app-owned content below the toolbar and aligns with the intended content
311
311
  edge.
312
+ When the title itself opens editing, set `ToolbarText action="edit-title"` and
313
+ render it directly in the leading zone beside any status chip. The button
314
+ shrinks and ellipsizes at the space left by its siblings without filling unused
315
+ space; the app swaps its own input into the edit state.
312
316
 
313
317
  ## Spacing scale
314
318
 
@@ -237,6 +237,10 @@ stylesheet co-own its classes, whether the stylesheet was declared or directly
237
237
  imported; a source that imports none of an owner's stylesheets remains foreign.
238
238
  When several entries infer the same class from a shared stylesheet, the
239
239
  analyzer does not arbitrarily assign that class to one of them for `KUI-L019`.
240
+ It reports one `KUI-L019` for a rule whose subject is that class or a dynamic
241
+ member of its BEM block when the stylesheet is foreign to every co-owner. The
242
+ finding names the co-owner set in `evidence.components`; their shared
243
+ stylesheet remains allowed.
240
244
  Other sibling files are foreign.
241
245
  Paths are relative to the entry's package root. Set
242
246
  `implicitComponentOwnership: true` to apply the same source and stylesheet
package/docs/ui-doctor.md CHANGED
@@ -67,7 +67,9 @@ block. An owned root or styled literal owns its whole `block__*` and `block--*`
67
67
  family, including dynamic names and classes placed on imported components.
68
68
  Modules sharing one of those stylesheets co-own its classes, so their
69
69
  own markup does not borrow from one another. A class inferred by several
70
- co-owners has no arbitrary single owner for CSS subject diagnostics.
70
+ co-owners has no arbitrary single owner for CSS subject diagnostics. A
71
+ stylesheet foreign to all co-owners receives one `KUI-L019` per subject rule,
72
+ including dynamic BEM modifiers; `evidence.components` names the co-owner set.
71
73
  Set `"implicitComponentOwnership": true` to include uncataloged modules
72
74
  that directly import CSS. Component mode reports sibling CSS and hook-class
73
75
  restyles while allowing a component's own stylesheet and module.
@@ -76,7 +78,11 @@ For an entry stylesheet shared by modules without direct CSS imports, configure
76
78
  These package-relative paths define one owner: its stylesheet and every listed
77
79
  source file or directory prefix. Doctor reports sibling CSS restyles and markup
78
80
  class use as `KUI-L019` and `KUI-L023`. Source paths ending in `/` match a
79
- directory subtree; other paths match exactly.
81
+ directory subtree; other paths match exactly. When implicit ownership is also
82
+ enabled, the group takes precedence over listed source modules and any direct
83
+ import of its stylesheet. An unrelated importer still borrows the group's
84
+ classes; its other stylesheets can retain their own implicit ownership.
85
+
80
86
  Set `"ownershipContext": "any"` to also report a same-package sibling's class
81
87
  used as ancestor or sibling context or within `:has()`, `:is()`, `:where()`, or
82
88
  `:not()`. The default `"subject"` keeps subject-only context policy; classes
package/docs/ux-demo.md CHANGED
@@ -23,7 +23,7 @@ deterministic state, and semantic layout owners from the [recipe guide](./recipe
23
23
  - It imports public component subpaths, exercising their browser-selected reachable CSS rather than a catalog-only style path.
24
24
  - The `ListActionRow` route demonstrates sibling primary/trailing native controls, independent disabled states, controlled selection, first-line leading-icon alignment for wrapped labels, and an application-owned native popover relationship without nesting controls. The `ListItem` route demonstrates the same multiline leading-icon anchor alongside its single-control navigation contract.
25
25
  - The `PopupMenu` route's context example opens one app-owned menu either at the row's right-click point or below its adjacent, keyboard-operable Demand actions button. Its interactive row/button composition opts out of the catalog geometry overlay so the overlay's border and margin highlights do not masquerade as a row inset during activation. The Nested decisions example shows the automatic phone-width submenu placement above or below its parent list, with native ArrowLeft return, Escape dismissal, and child selection.
26
- - The `AppTab` and `TabBar` routes are focused component specimens: AppTab shows its selected, compact, segmented, icon-only, truncation, placeholder, pending, metadata, and close-icon axes inside the required tablist context; TabBar shows its rail, segmented, inspector, allocation, placement, and accessory-zone axes, including an adjacent action plus a separately pinned end action whose tabs overflow at narrow widths, without application readouts. The separate `Application tabs` composition route demonstrates controlled selection, matching panel content, overflow, adding (a new tab opens `pending` with a spinner and stays selectable while its panel shows a `Skeleton` heading until a simulated load completes), closing, keyboard/pointer reordering, and application-owned ordering. The `ResizableRegion` route likewise replaces only the decorative handle glyph while retaining separator semantics and shared wiring.
26
+ - The `AppTab` and `TabBar` routes are focused component specimens: AppTab shows its selected, compact, segmented, icon-only, truncation, placeholder, pending, metadata, and close-icon axes inside the required tablist context; TabBar shows its rail, segmented, inspector, allocation, placement, and accessory-zone axes, including an adjacent action plus a separately pinned end action whose tabs overflow at narrow widths, without application readouts. The pinned leading-tab specimen uses native strip scrolling so momentum and the final tab's end position remain natural; `snapTabs` stays an opt-in for applications that want whole-tab settling. The separate `Application tabs` composition route demonstrates controlled selection, matching panel content, overflow, adding (a new tab opens `pending` with a spinner and stays selectable while its panel shows a `Skeleton` heading until a simulated load completes), closing, keyboard/pointer reordering, and application-owned ordering. The `ResizableRegion` route likewise replaces only the decorative handle glyph while retaining separator semantics and shared wiring.
27
27
  - Every first-party visual component and every free Web Awesome 3.12 component has its own stable `?component=<id>` URL and focused demo. Composite header, menu, and feedback scenarios remain addressable alongside their subcomponents; the root opens the first Kerf component. The former aggregate Web Awesome theme specimen is intentionally omitted because its component families are covered by those focused routes.
28
28
  - `ai/component-catalog.json` is the canonical catalog metadata. A deterministic checked-in projection supplies the demo's unique id, category, kind (`component`, `composition`, or `recipe`), source (`kerf` or `webawesome`), name, description, recommendation, direct `uses` dependencies, existing documentation path, and demo-source path; first-party, recipe, and Web Awesome renderer maps are exhaustively typed against its generated id unions. Functional groups follow their authored importance order; components precede compositions inside each group, equal-importance entries use alphabetical order, and the Recipes group comes last. Composition names stay concise and project a visible `Composition` sidebar tag. Non-supported Web Awesome recommendations project to a visible `Discouraged` tag, with Popup as the deliberate conditional exception. Demo paths resolve to the matching `ui/ux-demo/demos/<id>.tsx` or `ui/ux-demo/recipes/*.tsx` file, or to `ui/ux-demo/webawesome-demos.tsx` for Web Awesome entries. `ui/ux-demo/main.tsx` owns only the catalog shell, shared state, routing, and interaction wiring. First-party component entries additionally derive `ui/src/<public-subpath>.tsx` from their canonical `delivery.browserImport` or opt-in `delivery.moduleImport`.
29
29
  - Every detail footer groups compact `Demo source` and `Guidance` actions; first-party component details also expose `Component source`. Web Awesome details say `Integration guidance` to distinguish the local integration contract from the upstream component implementation. Each button retains its repository-relative path in the DOM for verification and uses a deploy-safe absolute GitHub blob URL with a contextual new-tab name; the demo does not depend on a local source server or add a package runtime export. At phone widths, the resource group and related-component selector stack as full-width rows, with any resource overflow contained locally so labels cannot overlap the selector or widen the document.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kerfjs/ui",
3
- "version": "5.0.0-beta.71",
3
+ "version": "5.0.0-beta.73",
4
4
  "description": "Accessible, composable UI primitives for kerf applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -100,7 +100,7 @@ export const createRecipe: RecipeFactory = (announce) => {
100
100
  hint="Summarize the outcome in one line."
101
101
  required
102
102
  value={title.value}
103
- ></wa-input>
103
+ />
104
104
  <wa-textarea
105
105
  name="recipe-body"
106
106
  label="Details"
@@ -109,7 +109,7 @@ export const createRecipe: RecipeFactory = (announce) => {
109
109
  maxlength="400"
110
110
  with-count
111
111
  value={body.value}
112
- ></wa-textarea>
112
+ />
113
113
  <Select<string>
114
114
  name="recipe-audience"
115
115
  value={audience.value}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/resize-grip.tsx","../src/resizable-region.tsx"],"names":["jsxs","Fragment","jsx"],"mappings":"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","file":"chunk-KLJKU6ZL.js","sourcesContent":["import type { ResizableRegionAxis } from './resizable-region.js';\n\n/**\n * The dormant two-line grip a resize separator shows on hover and focus. Shared\n * by `ResizableRegion` and resizable `Workbench` panels so both separators read\n * the same. Internal.\n */\nexport function ResizeGrip({ axis }: { axis: ResizableRegionAxis }) {\n return (\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n >\n {axis === 'horizontal' ? (\n <>\n <path d=\"M9 6v12\"></path>\n <path d=\"M15 6v12\"></path>\n </>\n ) : (\n <>\n <path d=\"M6 9h12\"></path>\n <path d=\"M6 15h12\"></path>\n </>\n )}\n </svg>\n );\n}\n","import type { SafeHtml } from 'kerfjs';\n\nimport type { CssLength } from './css-values.js';\nimport { ResizeGrip } from './resize-grip.js';\nimport type { KerfUiContent } from './semantic-content.js';\n\nexport type ResizableRegionAxis = 'horizontal' | 'vertical';\nexport type ResizableRegionEdge = 'start' | 'end';\nexport type ResizableRegionSeparator = 'auto' | 'hidden';\nexport type ResizableRegionCollapseMotion = 'none' | 'slide' | 'fade-slide';\nexport type ResizableRegionContentOverflow = 'clip' | 'auto' | 'visible';\nexport type ResizableRegionPresentation = 'inline' | 'overlay' | 'hidden';\nexport type ResizableRegionRestorePosition =\n 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';\nexport type ResizableRegionResponsiveFillAt = 'compact' | 'narrow';\n\nexport interface ResizableRegionProps {\n id: string;\n label: string;\n size: number;\n min: number;\n max: number;\n axis?: ResizableRegionAxis;\n edge?: ResizableRegionEdge;\n /** Snap the track to zero. The region renders `inert` and `aria-hidden`\n * while collapsed, so neither Tab nor assistive technology reaches it or\n * lands on an empty landmark; `restoreControl` renders outside the region. */\n collapsed?: boolean;\n transitioning?: boolean;\n /** Whether the separator line is painted. The resize hit target remains available. */\n separator?: ResizableRegionSeparator;\n /** Keep the track change instant while optionally sliding the fixed-size content. */\n collapseMotion?: ResizableRegionCollapseMotion;\n /** Use `visible` while an anchored popup must escape the content box; it raises the region to the popup layer. */\n contentOverflow?: ResizableRegionContentOverflow;\n /** Inline layout, an edge overlay, or a responsive replacement that removes the region. */\n presentation?: ResizableRegionPresentation;\n /** Always-available control rendered while collapsed, outside the clipped region. */\n restoreControl?: SafeHtml;\n /** Safe-area-aware corner of the region's container (not the viewport) for `restoreControl`. */\n restorePosition?: ResizableRegionRestorePosition;\n /** Position a restore control in its container corner or leave it in normal flow. */\n restorePlacement?: 'corner' | 'inline';\n /** Corner distance before the container's safe-area inset. */\n restoreInset?: CssLength;\n /** Fill the available inline track and hide the separator below a container breakpoint. */\n responsiveFillAt?: ResizableRegionResponsiveFillAt;\n /** Decorative dormant content for the separator handle. Must not contain interactive descendants. */\n handleIcon?: SafeHtml;\n children: KerfUiContent;\n}\n\nexport const clampRegionSize = (size: number, min: number, max: number) =>\n Math.min(max, Math.max(min, Math.round(size)));\nexport const resizeRegionFromPointer = (\n startSize: number,\n delta: number,\n edge: ResizableRegionEdge,\n) => startSize + delta * (edge === 'start' ? -1 : 1);\n\nexport function ResizableRegion({\n id,\n label,\n size,\n min,\n max,\n axis = 'horizontal',\n edge = 'end',\n collapsed = false,\n transitioning = false,\n separator = 'auto',\n collapseMotion = 'slide',\n contentOverflow = 'clip',\n presentation = 'inline',\n restoreControl,\n restorePosition = axis === 'horizontal' && edge === 'end'\n ? 'bottom-start'\n : 'bottom-end',\n restorePlacement = 'corner',\n restoreInset,\n responsiveFillAt,\n handleIcon,\n children,\n}: ResizableRegionProps) {\n const expandedSize = clampRegionSize(size, min, max);\n const resolved = collapsed ? 0 : expandedSize;\n const orientation = axis === 'horizontal' ? 'vertical' : 'horizontal';\n return (\n <>\n <section\n class=\"kui-resizable-region\"\n data-component=\"resizable-region\"\n data-region-id={id}\n data-axis={axis}\n data-edge={edge}\n data-collapsed={String(collapsed)}\n data-transitioning={String(transitioning)}\n data-separator={separator}\n data-collapse-motion={collapseMotion}\n data-content-overflow={contentOverflow}\n data-presentation={presentation}\n data-responsive-fill-at={responsiveFillAt}\n style={`--kui-resizable-region-size:${resolved}px;--kui-resizable-region-expanded-size:${expandedSize}px`}\n aria-label={label}\n // Collapsed, the labeled region leaves the accessibility tree whole\n // (its content is inert and its separator already hidden), so no\n // empty landmark stays behind; the restore control is a sibling.\n aria-hidden={\n collapsed || presentation === 'hidden' ? 'true' : undefined\n }\n inert={collapsed}\n >\n <div class=\"kui-resizable-region__content\" inert={collapsed}>\n {children}\n </div>\n <div\n class=\"kui-resizable-region__handle\"\n role=\"separator\"\n tabindex={collapsed || presentation !== 'inline' ? '-1' : '0'}\n aria-label={`Resize ${label}`}\n aria-orientation={orientation}\n aria-valuemin={collapsed ? 0 : min}\n aria-valuemax={max}\n aria-valuenow={resolved}\n aria-hidden={\n collapsed || presentation !== 'inline' ? 'true' : undefined\n }\n data-kui-resize-handle\n data-region-id={id}\n >\n <span class=\"kui-resizable-region__handle-icon\" aria-hidden=\"true\">\n {handleIcon ?? <ResizeGrip axis={axis} />}\n </span>\n </div>\n </section>\n {collapsed && restoreControl && presentation !== 'hidden' && (\n <div\n class=\"kui-resizable-region__restore\"\n data-region-restore={id}\n data-position={restorePosition}\n data-placement={restorePlacement}\n style={\n restoreInset\n ? `--kui-resizable-region-restore-inset:${restoreInset}`\n : undefined\n }\n >\n {restoreControl}\n </div>\n )}\n </>\n );\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/toolbar-text.tsx"],"names":[],"mappings":";;;;AAqDO,SAAS,WAAA,CAAY;AAAA,EAC1B,IAAA;AAAA,EACA,IAAA,GAAO,SAAA;AAAA,EACP,SAAA,GAAY,EAAA;AAAA,EACZ,EAAA;AAAA,EACA,YAAA;AAAA,EACA,WAAA,GAAc,KAAA;AAAA,EACd,IAAA,GAAO,KAAA;AAAA,EACP,QAAA,GAAW,IAAA;AAAA,EACX,QAAA,GAAW,IAAA;AAAA,EACX,IAAA,GAAO,KAAA;AAAA,EACP;AACF,CAAA,EAAqB;AACnB,EAAA,MAAM,MAAA,GAAS,IAAA,IAAQ,QAAA,IAAY,IAAA,IAAQ,QAAA,GAAW,CAAA;AACtD,EAAA,uBACE,GAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAO,CAAA,iBAAA,EAAoB,SAAS,CAAA,CAAA,CAAG,IAAA,EAAK;AAAA,MAC5C,gBAAA,EAAe,cAAA;AAAA,MACf,WAAA,EAAW,IAAA;AAAA,MACX,WAAA,EAAW,OAAO,MAAA,GAAS,MAAA;AAAA,MAC3B,WAAA,EAAW,OAAO,MAAA,GAAS,MAAA;AAAA,MAC3B,eAAA,EAAe,WAAW,MAAA,GAAY,OAAA;AAAA,MACtC,gBAAA,EAAgB,MAAA,GAAS,MAAA,CAAO,QAAQ,CAAA,GAAI,MAAA;AAAA,MAC5C,KAAA,EAAO,MAAA,GAAS,CAAA,6BAAA,EAAgC,QAAQ,CAAA,CAAA,GAAK,MAAA;AAAA,MAC7D,kBAAA,EAAkB,cAAc,MAAA,GAAS,MAAA;AAAA,MACzC,EAAA;AAAA,MACA,IAAA,EAAM,eAAe,SAAA,GAAY,MAAA;AAAA,MACjC,YAAA,EAAY,YAAA,GAAe,MAAA,CAAO,YAAY,CAAA,GAAI,MAAA;AAAA,MAClD,WAAA,EAAW,cAAc,MAAA,GAAS,MAAA;AAAA,MAClC,IAAA;AAAA,MAEC,QAAA,EAAA,WAAA,mBACC,GAAA,CAAC,QAAA,EAAA,EAAS,KAAA,EAAO,EAAA,CAAG,CAAC,CAAA,EAAG,CAAA,mBAExB,GAAA,CAAC,MAAA,EAAA,EAAK,KAAA,EAAM,wBAAA,EAA0B,QAAA,EAAA,IAAA,EAAK;AAAA;AAAA,GAE/C;AAEJ","file":"chunk-V3DX3K65.js","sourcesContent":["import { em } from './css-values.js';\nimport { Skeleton } from './skeleton.js';\n\nexport type ToolbarTextSize =\n 'xlarge' | 'xlarge-fixed' | 'large' | 'default' | 'small' | 'xsmall';\n\n/** ARIA heading level for a title exposed as a heading landmark. */\nexport type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;\n\ninterface ToolbarTextBaseProps {\n text: string;\n size?: ToolbarTextSize;\n className?: string;\n /** Optional id, e.g. so a dialog can reference the title via aria-labelledby. */\n id?: string;\n /**\n * Expose heading semantics (`role=\"heading\"` + `aria-level`) so the text acts as\n * a heading landmark — e.g. a page's primary title. Omit to keep the plain span\n * (the default), which suits a dialog title referenced via `aria-labelledby`.\n */\n headingLevel?: HeadingLevel;\n /** Render the text as an unanimated loading skeleton instead of its value. */\n placeholder?: boolean;\n /**\n * Show a trailing ellipsis (…) where the text is truncated — on the single line\n * (default), or at the `maxLines` boundary when wrapping. Set false to hard-clip\n * instead. Default true.\n */\n ellipsis?: boolean;\n /**\n * Grow to fill the free space of the flex row it sits in (a Toolbar zone),\n * truncating at the space left by its siblings instead of taking only its\n * text's width. Default false.\n */\n fill?: boolean;\n /** Native named-slot assignment when composed inside a web component. */\n slot?: string;\n /**\n * Cap wrapped text to this many lines, truncating past it. Only takes effect with\n * `wrap`; ignored on a single line. `null`/omitted wraps without a line cap. Default null.\n */\n}\n\ntype ToolbarTextWrappingProps =\n | {\n /** Wrap onto multiple lines; combine with `maxLines` to cap them. */\n wrap: true;\n maxLines?: number | null;\n }\n | { wrap?: false; maxLines?: never };\n\nexport type ToolbarTextProps = ToolbarTextBaseProps & ToolbarTextWrappingProps;\n\nexport function ToolbarText({\n text,\n size = 'default',\n className = '',\n id,\n headingLevel,\n placeholder = false,\n wrap = false,\n ellipsis = true,\n maxLines = null,\n fill = false,\n slot,\n}: ToolbarTextProps) {\n const capped = wrap && maxLines != null && maxLines > 0;\n return (\n <span\n class={`kui-toolbar-text ${className}`.trim()}\n data-component=\"toolbar-text\"\n data-size={size}\n data-fill={fill ? 'true' : undefined}\n data-wrap={wrap ? 'true' : undefined}\n data-ellipsis={ellipsis ? undefined : 'false'}\n data-max-lines={capped ? String(maxLines) : undefined}\n style={capped ? `--kui-toolbar-text-max-lines:${maxLines}` : undefined}\n data-placeholder={placeholder ? 'true' : undefined}\n id={id}\n role={headingLevel ? 'heading' : undefined}\n aria-level={headingLevel ? String(headingLevel) : undefined}\n aria-busy={placeholder ? 'true' : undefined}\n slot={slot}\n >\n {placeholder ? (\n <Skeleton width={em(8)} />\n ) : (\n <span class=\"kui-toolbar-text__text\">{text}</span>\n )}\n </span>\n );\n}\n"]}