@kerfjs/ui 5.0.0-beta.3 → 5.0.0-beta.6

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 (97) hide show
  1. package/README.md +12 -9
  2. package/ai/component-catalog.json +314 -172
  3. package/ai/public-api-signatures-v1.md +333 -26
  4. package/ai/skill.md +17 -9
  5. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  6. package/dist/browser/{dialog-header.js → panel-header.js} +3 -2
  7. package/dist/{chunk-GY5WH7TO.js → chunk-24Z2XE6C.js} +3 -3
  8. package/dist/chunk-24Z2XE6C.js.map +1 -0
  9. package/dist/chunk-2RJBFNB6.js +172 -0
  10. package/dist/chunk-2RJBFNB6.js.map +1 -0
  11. package/dist/chunk-KZKSBUKC.js +19 -0
  12. package/dist/chunk-KZKSBUKC.js.map +1 -0
  13. package/dist/chunk-RBTVBGRD.js +23 -0
  14. package/dist/chunk-RBTVBGRD.js.map +1 -0
  15. package/dist/device-class.d.ts +62 -0
  16. package/dist/device-class.js +75 -0
  17. package/dist/device-class.js.map +1 -0
  18. package/dist/index.d.ts +2 -3
  19. package/dist/index.js +8 -9
  20. package/dist/nav-stack.d.ts +38 -0
  21. package/dist/nav-stack.js +4 -0
  22. package/dist/nav-stack.js.map +1 -0
  23. package/dist/panel-header.d.ts +26 -0
  24. package/dist/panel-header.js +6 -0
  25. package/dist/panel-header.js.map +1 -0
  26. package/dist/split-view.d.ts +42 -0
  27. package/dist/split-view.js +20 -0
  28. package/dist/split-view.js.map +1 -0
  29. package/dist/styles/foundation.css +21 -0
  30. package/dist/styles/menu-action-row.css +6 -0
  31. package/dist/styles/menu-header.css +19 -2
  32. package/dist/styles/menu-item.css +5 -0
  33. package/dist/styles/nav-stack.css +112 -0
  34. package/dist/styles/panel-header.css +59 -0
  35. package/dist/styles/split-view.css +35 -0
  36. package/dist/styles/styles.css +1 -2
  37. package/dist/styles/tab-scaffold.css +84 -0
  38. package/dist/styles/toolbar-control-group.css +52 -2
  39. package/dist/styles/toolbar-text.css +11 -0
  40. package/dist/styles/toolbar.css +9 -0
  41. package/dist/styles/workbench.css +94 -0
  42. package/dist/tab-scaffold.d.ts +30 -0
  43. package/dist/tab-scaffold.js +16 -0
  44. package/dist/tab-scaffold.js.map +1 -0
  45. package/dist/toolbar-text.d.ts +4 -2
  46. package/dist/toolbar-text.js +1 -1
  47. package/dist/wire-nav-stack.d.ts +15 -0
  48. package/dist/wire-nav-stack.js +88 -0
  49. package/dist/wire-nav-stack.js.map +1 -0
  50. package/dist/wire-tab-scaffold.d.ts +11 -0
  51. package/dist/wire-tab-scaffold.js +16 -0
  52. package/dist/wire-tab-scaffold.js.map +1 -0
  53. package/dist/wire-token-search-fields.d.ts +46 -4
  54. package/dist/wire-token-search-fields.js +1 -1
  55. package/dist/workbench.d.ts +33 -0
  56. package/dist/workbench.js +17 -0
  57. package/dist/workbench.js.map +1 -0
  58. package/docs/accessibility.md +10 -10
  59. package/docs/app-layouts.md +57 -0
  60. package/docs/component-contract.md +37 -9
  61. package/docs/component-selection.md +57 -8
  62. package/docs/design-philosophy.md +23 -1
  63. package/docs/device-class.md +54 -0
  64. package/docs/layout.md +44 -8
  65. package/docs/nav-stack.md +47 -0
  66. package/docs/recipes.md +29 -9
  67. package/docs/split-view.md +49 -0
  68. package/docs/tab-scaffold.md +41 -0
  69. package/docs/ux-demo.md +11 -9
  70. package/docs/workbench.md +47 -0
  71. package/llms.txt +107 -43
  72. package/package.json +37 -11
  73. package/ux-demo/recipes/app-shell.tsx +3 -3
  74. package/ux-demo/recipes/composer-form.tsx +5 -4
  75. package/ux-demo/recipes/list-workspace-states.tsx +2 -2
  76. package/ux-demo/recipes/loaders.ts +2 -0
  77. package/ux-demo/recipes/master-detail-dialog.tsx +2 -2
  78. package/ux-demo/recipes/mount-recipe.ts +3 -0
  79. package/ux-demo/recipes/navigation-stack.tsx +76 -0
  80. package/ux-demo/recipes/recipes.css +40 -13
  81. package/ux-demo/recipes/workspace-header.tsx +2 -2
  82. package/dist/browser/page-header.js +0 -3
  83. package/dist/chunk-2PES33HS.js +0 -13
  84. package/dist/chunk-2PES33HS.js.map +0 -1
  85. package/dist/chunk-GY5WH7TO.js.map +0 -1
  86. package/dist/chunk-H5AGGVU5.js +0 -75
  87. package/dist/chunk-H5AGGVU5.js.map +0 -1
  88. package/dist/chunk-K3G72I6D.js +0 -24
  89. package/dist/chunk-K3G72I6D.js.map +0 -1
  90. package/dist/dialog-header.d.ts +0 -15
  91. package/dist/dialog-header.js +0 -5
  92. package/dist/dialog-header.js.map +0 -1
  93. package/dist/page-header.d.ts +0 -9
  94. package/dist/page-header.js +0 -3
  95. package/dist/page-header.js.map +0 -1
  96. package/dist/styles/dialog-header.css +0 -87
  97. package/dist/styles/page-header.css +0 -26
@@ -1,6 +1,6 @@
1
1
  # Public API signatures for the UI authoring corpus
2
2
 
3
- Generated from emitted declarations for `@kerfjs/ui@5.0.0-beta.3` and `kerfjs@5.0.0-beta.3`. This bounded reference covers only APIs used by the seven-task corpus. It is interface evidence, not an implementation or runtime guarantee.
3
+ Generated from emitted declarations for `@kerfjs/ui@5.0.0-beta.6` and `kerfjs@5.0.0-beta.6`. This bounded reference covers only APIs used by the seven-task corpus. It is interface evidence, not an implementation or runtime guarantee.
4
4
 
5
5
  ## `@kerfjs/ui/disclosure-arrow`
6
6
 
@@ -44,13 +44,15 @@ export { Toolbar, type ToolbarProps };
44
44
  ```ts
45
45
  import * as kerfjs from 'kerfjs';
46
46
 
47
- type ToolbarTextSize = 'large' | 'default' | 'small';
47
+ type ToolbarTextSize = 'xlarge' | 'large' | 'default' | 'small';
48
48
  interface ToolbarTextProps {
49
49
  text: string;
50
50
  size?: ToolbarTextSize;
51
51
  className?: string;
52
+ /** Optional id, e.g. so a dialog can reference the title via aria-labelledby. */
53
+ id?: string;
52
54
  }
53
- declare function ToolbarText({ text, size, className }: ToolbarTextProps): kerfjs.SafeHtml;
55
+ declare function ToolbarText({ text, size, className, id }: ToolbarTextProps): kerfjs.SafeHtml;
54
56
 
55
57
  export { ToolbarText, type ToolbarTextProps, type ToolbarTextSize };
56
58
  ```
@@ -212,26 +214,12 @@ declare function MenuItem({ label, icon, trailing, selected, action, itemId, cla
212
214
  export { MenuItem, type MenuItemProps };
213
215
  ```
214
216
 
215
- ## `@kerfjs/ui/page-header`
217
+ ## `@kerfjs/ui/panel-header`
216
218
 
217
219
  ```ts
218
220
  import { SafeHtml } from 'kerfjs';
219
221
 
220
- interface PageHeaderProps {
221
- title: string | SafeHtml;
222
- action?: SafeHtml;
223
- }
224
- declare function PageHeader({ title, action }: PageHeaderProps): SafeHtml;
225
-
226
- export { PageHeader, type PageHeaderProps };
227
- ```
228
-
229
- ## `@kerfjs/ui/dialog-header`
230
-
231
- ```ts
232
- import { SafeHtml } from 'kerfjs';
233
-
234
- interface DialogHeaderProps {
222
+ interface PanelHeaderProps {
235
223
  title: string;
236
224
  titleId: string;
237
225
  summary?: string;
@@ -239,11 +227,22 @@ interface DialogHeaderProps {
239
227
  icon?: SafeHtml;
240
228
  iconClassName?: string;
241
229
  actions?: SafeHtml;
242
- actionsLabel?: string;
243
230
  }
244
- declare function DialogHeader({ title, titleId, summary, summaryId, icon, iconClassName, actions, actionsLabel }: DialogHeaderProps): SafeHtml;
231
+ /**
232
+ * The heading of a panel, dialog, or page: a plain `Toolbar` whose leading zone
233
+ * holds an optional icon control group and the title as extra-large `ToolbarText`,
234
+ * whose trailing zone holds the app's action controls, and with an optional
235
+ * subtitle on its own row, left-aligned with the title.
236
+ *
237
+ * PanelHeader overrides no Toolbar styles — it is just a Toolbar with an xl title.
238
+ * The only styling it adds is the icon group's fill/border color and the subtitle.
239
+ * When no icon is provided, the icon group is omitted entirely. The `actions` slot
240
+ * is passed straight into the toolbar's trailing zone; the app supplies whatever
241
+ * trailing controls it needs (typically a `ToolbarControlGroup`).
242
+ */
243
+ declare function PanelHeader({ title, titleId, summary, summaryId, icon, iconClassName, actions }: PanelHeaderProps): SafeHtml;
245
244
 
246
- export { DialogHeader, type DialogHeaderProps };
245
+ export { PanelHeader, type PanelHeaderProps };
247
246
  ```
248
247
 
249
248
  ## `@kerfjs/ui/value-table`
@@ -343,6 +342,205 @@ declare function wireTabBars(root: HTMLElement | Document, { onReorder }: WireTa
343
342
  export { type TabDropPosition, type TabReorder, type TabReorderSource, type WireTabBarsOptions, reorderTabs, wireTabBars };
344
343
  ```
345
344
 
345
+ ## `@kerfjs/ui/nav-stack`
346
+
347
+ ```ts
348
+ import { SafeHtml } from 'kerfjs';
349
+
350
+ /**
351
+ * One entry in a {@link NavStack}. The app owns the stack as an array (usually a
352
+ * signal); `NavStack` renders it and `wireNavStack` animates the transitions.
353
+ */
354
+ interface NavStackView {
355
+ /** Stable identity for keyed reconcile and transition direction. */
356
+ key: string;
357
+ content: SafeHtml;
358
+ /** Title shown in the top toolbar for this view. */
359
+ title?: string;
360
+ /** Trailing actions for this view's top toolbar. */
361
+ toolbar?: SafeHtml;
362
+ }
363
+ interface NavStackProps {
364
+ id: string;
365
+ /** Accessible name for the stack region. */
366
+ label: string;
367
+ /** The stack, root first; the last entry is the active top view. */
368
+ views: NavStackView[];
369
+ /** Accessible label for the back control (default "Back"). */
370
+ backLabel?: string;
371
+ /** Hide the top toolbar entirely (rare — a fully custom-chrome view). */
372
+ hideToolbar?: boolean;
373
+ /** Optional persistent bottom toolbar. */
374
+ bottomToolbar?: SafeHtml;
375
+ className?: string;
376
+ }
377
+ /**
378
+ * A navigation stack (iOS-style push/pop). Renders every entry stacked, the last
379
+ * one active; `@kerfjs/ui/wire-nav-stack`'s `wireNavStack` slides the content and
380
+ * cross-fades the chrome across a change. A single-pane layout is a `NavStack`
381
+ * with one entry. See `docs/23-app-layouts.md` §3.1.
382
+ */
383
+ declare function NavStack({ id, label, views, backLabel, hideToolbar, bottomToolbar, className }: NavStackProps): SafeHtml;
384
+
385
+ export { NavStack, type NavStackProps, type NavStackView };
386
+ ```
387
+
388
+ ## `@kerfjs/ui/wire-nav-stack`
389
+
390
+ ```ts
391
+ interface WireNavStackOptions {
392
+ /** Invoked when the back control is activated. The app pops its own stack. */
393
+ onBack?: () => void;
394
+ /** Transition duration in ms (default 200). Set 0 to disable animation. */
395
+ duration?: number;
396
+ }
397
+ /**
398
+ * Animate a `NavStack`'s push/pop transitions and wire its back control. The app
399
+ * owns the stack (a signal of `NavStackView[]`) and re-renders `NavStack` when it
400
+ * changes; this helper slides the content and settles the chrome across each
401
+ * change, and calls `onBack` when the back control is used. Returns a disposer.
402
+ */
403
+ declare function wireNavStack(root: Element, options?: WireNavStackOptions): () => void;
404
+
405
+ export { type WireNavStackOptions, wireNavStack };
406
+ ```
407
+
408
+ ## `@kerfjs/ui/split-view`
409
+
410
+ ```ts
411
+ import { SafeHtml } from 'kerfjs';
412
+
413
+ interface SplitViewResizable {
414
+ size: number;
415
+ min: number;
416
+ max: number;
417
+ }
418
+ interface SplitViewProps {
419
+ id: string;
420
+ label: string;
421
+ /** The list (primary) pane. */
422
+ list: SafeHtml;
423
+ /** The detail (secondary) pane. */
424
+ detail: SafeHtml;
425
+ /**
426
+ * Compact ("one pane at a time") classes — a handset or portrait tablet.
427
+ * Derive from `deviceClass().value.compact`. When true the split collapses to
428
+ * a `NavStack`: the list is the root and the detail is pushed over it.
429
+ */
430
+ compact?: boolean;
431
+ /** In compact mode, whether the detail is currently pushed over the list. */
432
+ detailActive?: boolean;
433
+ /** Title/label for the list (compact NavStack root + region label). */
434
+ listTitle?: string;
435
+ /** Title/label for the detail (compact NavStack pushed view + region label). */
436
+ detailTitle?: string;
437
+ /** Back label for the compact NavStack (default "Back"). */
438
+ backLabel?: string;
439
+ /** A resizable separator on roomy classes (min/max px). Omit for a fixed split. */
440
+ resizable?: SplitViewResizable;
441
+ className?: string;
442
+ }
443
+ /**
444
+ * A list-detail (master-detail) split. On roomy classes it shows both panes side
445
+ * by side with an optional resizable separator; on compact classes it collapses
446
+ * to a `NavStack` (list → detail). See `docs/23-app-layouts.md` §3.2. Compose the
447
+ * resizable wiring with `wireResizableRegions` and the compact back with
448
+ * `wireNavStack`.
449
+ */
450
+ declare function SplitView({ id, label, list, detail, compact, detailActive, listTitle, detailTitle, backLabel, resizable, className }: SplitViewProps): SafeHtml;
451
+
452
+ export { SplitView, type SplitViewProps, type SplitViewResizable };
453
+ ```
454
+
455
+ ## `@kerfjs/ui/workbench`
456
+
457
+ ```ts
458
+ import { SafeHtml } from 'kerfjs';
459
+
460
+ /** A collapsible Workbench panel — a side rail or the bottom drawer. */
461
+ interface WorkbenchPanel {
462
+ content: SafeHtml;
463
+ /** Whether the panel is currently collapsed (the app owns this). */
464
+ collapsed?: boolean;
465
+ /** Rail width, or drawer height, in px. Overrides the CSS default. */
466
+ size?: number;
467
+ /** Accessible name for the panel region. */
468
+ label?: string;
469
+ }
470
+ interface WorkbenchProps {
471
+ id: string;
472
+ label: string;
473
+ /** The central work area. */
474
+ main: SafeHtml;
475
+ leftRail?: WorkbenchPanel;
476
+ rightRail?: WorkbenchPanel;
477
+ bottomDrawer?: WorkbenchPanel;
478
+ className?: string;
479
+ }
480
+ /**
481
+ * The Xcode-like multi-panel workspace: a collapsible left rail, right rail, and
482
+ * bottom drawer around a central work area (any absent). Collapsing snaps the
483
+ * panel's track to zero in one reflow while its fixed-size content slides out via
484
+ * a composited transform — the instant-width / sliding-content technique, so the
485
+ * work area relayouts once, not per frame. The app owns each `collapsed` flag;
486
+ * the collapse is pure CSS (no wire). See `docs/23-app-layouts.md` §3.3.
487
+ */
488
+ declare function Workbench({ id, label, main, leftRail, rightRail, bottomDrawer, className }: WorkbenchProps): SafeHtml;
489
+
490
+ export { Workbench, type WorkbenchPanel, type WorkbenchProps };
491
+ ```
492
+
493
+ ## `@kerfjs/ui/tab-scaffold`
494
+
495
+ ```ts
496
+ import { SafeHtml } from 'kerfjs';
497
+
498
+ interface TabScaffoldTab {
499
+ id: string;
500
+ label: string;
501
+ /** Decorative icon shown above the label in the bottom bar. */
502
+ icon?: SafeHtml;
503
+ /** The tab's content — typically a `NavStack` so each tab keeps its own stack. */
504
+ content: SafeHtml;
505
+ }
506
+ interface TabScaffoldProps {
507
+ id: string;
508
+ /** Accessible name for the tab bar. */
509
+ label: string;
510
+ tabs: TabScaffoldTab[];
511
+ /** The controlled active tab id (the app owns selection). */
512
+ active: string;
513
+ className?: string;
514
+ }
515
+ /**
516
+ * A mobile-first, iOS-like bottom tab scaffold: a bottom tab bar that switches
517
+ * between major sections, each tab keeping its own content (usually a `NavStack`)
518
+ * mounted so its stack and scroll survive a switch. Controlled — the app owns
519
+ * `active`; wire selection with `@kerfjs/ui/wire-tab-scaffold`'s `wireTabScaffold`.
520
+ * On larger classes, promote the tabs to a `Workbench` rail or sidebar instead of
521
+ * a bottom bar. See `docs/23-app-layouts.md` §3.4.
522
+ */
523
+ declare function TabScaffold({ id, label, tabs, active, className }: TabScaffoldProps): SafeHtml;
524
+
525
+ export { TabScaffold, type TabScaffoldProps, type TabScaffoldTab };
526
+ ```
527
+
528
+ ## `@kerfjs/ui/wire-tab-scaffold`
529
+
530
+ ```ts
531
+ interface WireTabScaffoldOptions {
532
+ /** Invoked with the selected tab id when a bottom-bar tab is activated. */
533
+ onSelect: (tabId: string) => void;
534
+ }
535
+ /**
536
+ * Wire a `TabScaffold`'s bottom tab bar: clicking a tab calls `onSelect` with its
537
+ * id (the app then updates its controlled `active`). Returns a disposer.
538
+ */
539
+ declare function wireTabScaffold(root: Element, options: WireTabScaffoldOptions): () => void;
540
+
541
+ export { type WireTabScaffoldOptions, wireTabScaffold };
542
+ ```
543
+
346
544
  ## `@kerfjs/ui/resizable-region`
347
545
 
348
546
  ```ts
@@ -391,6 +589,73 @@ declare function wireResizableRegions(root: HTMLElement, { step, largeStep, onPr
391
589
  export { type ResizeCommit, type WireResizableRegionsOptions, wireResizableRegions };
392
590
  ```
393
591
 
592
+ ## `@kerfjs/ui/device-class`
593
+
594
+ ```ts
595
+ import { ReadonlySignal } from 'kerfjs';
596
+
597
+ /**
598
+ * Reactive device-class detection for `@kerfjs/ui` (see `docs/23-app-layouts.md`
599
+ * §2). `deviceClass()` returns a `ReadonlySignal<DeviceClass>` describing the
600
+ * current viewport as a size bucket × orientation × viewport-segment count, so a
601
+ * layout can pick its presentation reactively instead of hand-wiring `matchMedia`.
602
+ *
603
+ * One shared viewport source backs every reader; the pure `classifyViewport`
604
+ * core is DOM-free and directly unit-tested.
605
+ */
606
+ type DeviceSize = 'xs-mobile' | 'mobile' | 'tablet' | 'desktop' | 'xl-desktop';
607
+ type DeviceOrientation = 'portrait' | 'landscape';
608
+ /** Minimum widths (px) at which each larger bucket begins. `xs-mobile` is 0. */
609
+ interface DeviceBreakpoints {
610
+ mobile: number;
611
+ tablet: number;
612
+ desktop: number;
613
+ 'xl-desktop': number;
614
+ }
615
+ interface DeviceClass {
616
+ size: DeviceSize;
617
+ orientation: DeviceOrientation;
618
+ /** Horizontal viewport segments (foldables / dual-screen); 1 on ordinary devices. */
619
+ segments: number;
620
+ /** Vertical viewport segments; 1 on ordinary devices. */
621
+ verticalSegments: number;
622
+ /** Small phones — `xs-mobile` or `mobile`. */
623
+ handset: boolean;
624
+ /** "One pane at a time" — a handset or a portrait tablet. */
625
+ compact: boolean;
626
+ /** True when the current size is `size` or larger, e.g. `atLeast('tablet')`. */
627
+ atLeast(size: DeviceSize): boolean;
628
+ }
629
+ /** A raw viewport snapshot, before breakpoints are applied. */
630
+ interface Viewport {
631
+ width: number;
632
+ height: number;
633
+ segments: number;
634
+ verticalSegments: number;
635
+ }
636
+ interface DeviceClassOptions {
637
+ /** Override any of the default bucket thresholds. */
638
+ breakpoints?: Partial<DeviceBreakpoints>;
639
+ /** The viewport assumed when there is no DOM (SSR). Defaults to 1024×768, one segment. */
640
+ ssr?: Partial<Viewport>;
641
+ }
642
+ declare const DEFAULT_BREAKPOINTS: DeviceBreakpoints;
643
+ /**
644
+ * Classify a raw viewport into a {@link DeviceClass}. Pure and DOM-free — the
645
+ * single source of truth for the bucketing rules.
646
+ */
647
+ declare function classifyViewport(width: number, orientation: DeviceOrientation, segments?: number, verticalSegments?: number, breakpoints?: DeviceBreakpoints): DeviceClass;
648
+ /**
649
+ * A reactive signal of the current {@link DeviceClass}. Reading it inside an
650
+ * `effect`/`computed` re-runs when the viewport crosses a breakpoint, rotates,
651
+ * or changes its segment count. Without a DOM it resolves to `options.ssr`
652
+ * (default 1024×768, landscape, one segment).
653
+ */
654
+ declare function deviceClass(options?: DeviceClassOptions): ReadonlySignal<DeviceClass>;
655
+
656
+ export { DEFAULT_BREAKPOINTS, type DeviceBreakpoints, type DeviceClass, type DeviceClassOptions, type DeviceOrientation, type DeviceSize, type Viewport, classifyViewport, deviceClass };
657
+ ```
658
+
394
659
  ## `@kerfjs/ui/segmented-control`
395
660
 
396
661
  ```ts
@@ -528,17 +793,59 @@ export { TokenSearchField, type TokenSearchFieldProps, type TokenSearchFieldValu
528
793
  ## `@kerfjs/ui/wire-token-search-fields`
529
794
 
530
795
  ```ts
796
+ import { Signal } from 'kerfjs';
797
+
531
798
  interface TokenSearchSubmit {
532
799
  id: string;
533
800
  editor: HTMLElement;
534
801
  }
802
+ /**
803
+ * Managed collapsible behavior for the iconic TokenSearchField. Every piece is on
804
+ * by default; disable a specific one to own it in the app. Provide `signals` to
805
+ * drive app-owned `expanded` signals per field id instead of helper-created ones.
806
+ */
807
+ interface TokenSearchCollapsibleOptions {
808
+ /** Expand the field and focus its editor when the iconic trigger is activated. Default: true. */
809
+ expandOnActivate?: boolean;
810
+ /** Collapse the field when focus leaves it while it is empty. Default: true. */
811
+ collapseOnEmptyBlur?: boolean;
812
+ /** Collapse an empty field on Escape and restore focus to its trigger. Default: true. */
813
+ collapseOnEscape?: boolean;
814
+ /** Focus the editor on expand and the trigger on Escape-collapse. Default: true. */
815
+ manageFocus?: boolean;
816
+ /** App-owned `expanded` signals keyed by field id; adopted instead of helper-created. */
817
+ signals?: Readonly<Record<string, Signal<boolean>>>;
818
+ }
535
819
  interface WireTokenSearchFieldsOptions {
536
- onSubmit: (submission: TokenSearchSubmit) => void;
820
+ onSubmit?: (submission: TokenSearchSubmit) => void;
821
+ /** Managed collapsible transient behavior. `true`/omitted = on with defaults; `false` = fully off. */
822
+ collapsible?: boolean | TokenSearchCollapsibleOptions;
537
823
  }
538
- /** Keep TokenSearchField wrapping, submit Enter, and preserve its caret across controlled token deletion. */
539
- declare function wireTokenSearchFields(root: HTMLElement, { onSubmit }: WireTokenSearchFieldsOptions): () => void;
824
+ /**
825
+ * The value returned from {@link wireTokenSearchFields}: call it (or `dispose()`) to
826
+ * tear down. When collapsible behavior is managed, it also exposes the transient
827
+ * `expanded` state per field id so the app can read it in render, hand in its own
828
+ * signal, or drive it imperatively.
829
+ */
830
+ interface TokenSearchFieldsHandle {
831
+ (): void;
832
+ dispose(): void;
833
+ /** The managed `expanded` signal for a field id (adopted or helper-created); undefined when unmanaged. */
834
+ expanded(id: string): Signal<boolean> | undefined;
835
+ /** Expand the field (and, when focus is managed, focus its editor). */
836
+ open(id: string): void;
837
+ /** Collapse the field (and, when focus is managed, restore focus to its trigger). */
838
+ close(id: string): void;
839
+ }
840
+ /**
841
+ * Wire every TokenSearchField under `root`: submit on Enter, preserve the caret across
842
+ * controlled token deletion, and (by default) manage the collapsible field's transient
843
+ * expand/collapse/focus. Returns a {@link TokenSearchFieldsHandle} — a disposer that also
844
+ * exposes the managed `expanded` state per field id.
845
+ */
846
+ declare function wireTokenSearchFields(root: HTMLElement, { onSubmit, collapsible }?: WireTokenSearchFieldsOptions): TokenSearchFieldsHandle;
540
847
 
541
- export { type TokenSearchSubmit, type WireTokenSearchFieldsOptions, wireTokenSearchFields };
848
+ export { type TokenSearchCollapsibleOptions, type TokenSearchFieldsHandle, type TokenSearchSubmit, type WireTokenSearchFieldsOptions, wireTokenSearchFields };
542
849
  ```
543
850
 
544
851
  ## `kerfjs/actions`
package/ai/skill.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: kerf-ui
3
3
  description: Build interfaces with kerfjs and the @kerfjs/ui production component package. Use whenever code imports @kerfjs/ui or a task asks for Kerf UI components.
4
- kerf-ui-skill-version: 1.32.0
4
+ kerf-ui-skill-version: 1.34.0
5
5
  ---
6
6
 
7
7
  # Building with @kerfjs/ui
@@ -27,9 +27,9 @@ Quick routing:
27
27
 
28
28
  | Need | Choose | Nearest alternatives / boundary |
29
29
  | --- | --- | --- |
30
- | Navigation row | `MenuItem` | Use an ordinary link or button when sidebar/menu anatomy and state do not apply. |
31
- | Navigation row with a trailing action | `MenuActionRow` | Use `MenuItem` when the trailing region is dormant; never put controls inside either component's SafeHtml slots. |
32
- | Page chrome or heading | `Toolbar`, `PageHeader`, or `DialogHeader` | Toolbar is persistent chrome; page and dialog headers identify different scopes. |
30
+ | Navigation row | `MenuItem` | Use an ordinary link or button when sidebar/menu anatomy and state do not apply. Multiline leading icons stay aligned with the first text line. |
31
+ | Navigation row with a trailing action | `MenuActionRow` | Use `MenuItem` when the trailing region is dormant; never put controls inside either component's SafeHtml slots. Multiline leading icons stay aligned with the first text line. |
32
+ | Page, panel, or dialog heading | `Toolbar` or `PanelHeader` | Toolbar is persistent chrome; `PanelHeader` heads a panel, dialog, or page. |
33
33
  | Exclusive choice | `TabBar`, `SegmentedControl`, or `Select` | Tabs switch tabpanels; segments expose a few choices; Select handles a longer value list. |
34
34
  | Status or absent content | `StateBanner` or `EmptyState` | Web Awesome callouts suit ecosystem content; toasts are transient and never the only record of important state. |
35
35
  | Adjustable or static columns | `ResizableRegion` or CSS grid | Use the component only for a user-operable controlled separator. |
@@ -56,15 +56,18 @@ Hard rules:
56
56
  8. `ResizableRegion` uses `wireResizableRegions()` for Arrow, Shift+Arrow, Home/End, and pointer behavior. The app owns size persistence. `handleIcon` replaces decorative dormant glyph content only.
57
57
  9. Use controlled `SegmentedControl` for a small exclusive choice set. Select `appearance="toolbar"` when nesting it inside `ToolbarControlGroup`; use rounded or pill shapes for standalone contexts. Handle its action, update `value`, keep meaningful choice labels, and preserve every enabled native button in sequential Tab order.
58
58
  10. Compose `AppTab` inside controlled `TabBar`; call `wireTabBars()` once and retain its disposer. It owns same-bar drag mechanics, including proximity-based horizontal edge autoscroll, while the app applies `onReorder` and owns order, selection, panels, close policy, routing, and persistence. Use runtime-filtered `rootAttributes` for domain metadata and keep an optional `closeIcon` decorative and noninteractive.
59
- 11. Use token-controlled `TokenSearchField` when free text and removable structured filters share one editor. Editable text stays DOM-owned between token changes. The leading icon, first text line, clear action, and trailing slot share one fixed row when content wraps. The app owns parsing and suggestions; call `readTokenSearchField()` on input, empty `textContent` on clear, use `placeTokenSearchCaret()` after explicit controlled focus changes, and call `wireTokenSearchFields()` once so Enter submits without inserting a line break and keyboard chip deletion restores focus plus the text-relative caret after controlled replacement. Enable `collapsible` for an animated iconic closed state, standalone or inside `ToolbarControlGroup`; the field keeps text or tokens expanded while the app controls transient `expanded` state, focus transfer, and focusout timing.
59
+ 11. Use token-controlled `TokenSearchField` when free text and removable structured filters share one editor. Editable text stays DOM-owned between token changes. The leading icon, first text line, clear action, and trailing slot share one fixed row when content wraps. The app owns parsing and suggestions; call `readTokenSearchField()` on input, empty `textContent` on clear, use `placeTokenSearchCaret()` after explicit controlled focus changes, and call `wireTokenSearchFields()` once so Enter submits without inserting a line break and keyboard chip deletion restores focus plus the text-relative caret after controlled replacement. Enable `collapsible` for an animated iconic closed state, standalone or inside `ToolbarControlGroup`; the field keeps text or tokens expanded, and `wireTokenSearchFields()` manages the transient expand/collapse/focus by default (activate to reveal + focus, Escape or empty blur to collapse). Bind the field's `expanded` to the returned handle's `expanded(id)` signal or adopt your own via `collapsible.signals`; opt a behavior out only when the app must own it. Do not hand-roll the open handler, the focusout collapse, or a clear-button mousedown guard — the helper owns them.
60
60
  12. Demo work uses public production component subpaths and their browser-selected CSS. Give every public visual component its own category-grouped catalog route; list themed third-party components under a clearly labeled collapsible ecosystem section, with a focused route for each. Project its deterministic repository-relative demo source and existing documentation path so the detail can expose `View demo source` and `Read guidance` links without a runtime export; also derive first-party component implementation paths from their canonical browser imports, and label Web Awesome documentation as Kerf integration guidance. Declare direct `uses` relationships so `Used by` stays derivable, and theme shell chrome through the same semantic tokens as the stage instead of drawing a substitute.
61
61
  13. The Web Awesome theme makes Tooltip and Popover arrowless by default. Keep that default unless a pointer materially clarifies the anchor; opt back in with `--wa-tooltip-arrow-size`, `--kui-wa-popover-arrow-size`, or a popover's public `--arrow-size`, and use `without-arrow` when local no-arrow intent should survive theme changes.
62
62
  14. Treat the complete Web Awesome catalog as support coverage, not a recommendation list. Consider Popup when it replaces custom anchored positioning. Prefer Kerf `Select` over direct Dropdown/Dropdown Item/Select/Option composition, `SegmentedControl` over Button Group, `TabBar` or `SegmentedControl` over Web Awesome Tabs, `LucideIcon` over Web Awesome Icon, and `ResizableRegion` over Split Panel. Use Tree/Tree Item, Animated Image, and Comparison only for a specific required behavior; avoid Zoomable Frame.
63
63
  15. Build sidebars, main areas, inspectors, and dialogs from `@kerfjs/ui/layout.css`: an unpadded `.kui-pane`, optional `.kui-pane__toolbar`, one scrolling `.kui-pane__content`, and optional `.kui-pane__footer`. Add `.kui-content` for 24px major vertical separation and `.kui-content-item` for a child-owned 8px inline margin, 1px transparent-or-visible border, 8px padding, and 12px radius. Use the pill modifier for 22px. Do not pad pane shells or duplicate item geometry in wrappers.
64
- 16. Wrap every toolbar item, including dormant text, in `ToolbarControlGroup`. A group remains 44px outside (`calc(2px + remify(42px))`) when its border/background are transparent; use 8px between groups and inside items. Split dormant and interactive regions: `MenuHeader` fills the available inline width and keeps its label and mutually exclusive semantic count or non-count `badge` together, with an independent logical-end 44px action unless disclosure mode makes the title cluster the button. Its action visual defaults to 18px through `--kui-menu-header-action-icon-size`; never shrink the target to match it. Pass every non-negative safe-integer section quantity through `count` with a localized full spoken `countLabel`; never concatenate it into `label` or put a number in `badge`. `MenuItem.trailing` is dormant; use `MenuActionRow` when primary and trailing actions need sibling 44px native buttons. Its `label`, `icon`, and `trailingActionIcon` slots are also dormant and cannot contain controls. Let panes relocate at narrow widths instead of shrinking targets.
65
- 17. When a recurring concept has no matching export or production recipe, keep its semantics in a thin application adapter while reusing the public layout vocabulary. The composer recipe uses one visible form surface, exactly three transparent 8/1/8 content sections, 24px major rhythm, and a conditional StateBanner as its only nested semantic surface; do not turn every section into a card. The application-local `../docs/examples/command-palette-adapter.tsx` is reference source for one such missing concept, not an `@kerfjs/ui` runtime export or catalog recipe. The application owns its registration, ranking, history, permissions, availability, shortcut policy, focus policy, dispatch, and copy. If a missing concept recurs across products, open an upstream component or recipe request.
64
+ 16. Keep a visible collapsible pane's collapse control in its own toolbar. When hidden, move its restore control into the adjacent main toolbar on the corresponding logical edge: leading for an inline-start sidebar and trailing for an inline-end inspector. Collapse the pane completely; do not preserve an empty icon-only rail.
65
+ 16. The only direct children of a `Toolbar` zone (`leading`/`center`/`trailing`) are `ToolbarText` (identity/title text) and `ToolbarControlGroup`; never drop bare buttons, inputs, links, or arbitrary markup straight into a zone. `SegmentedControl`, `Select`, a collapsible `TokenSearchField`, and Web Awesome controls all live inside a `ToolbarControlGroup`. A popup menu in a toolbar is a `single` `ToolbarControlGroup` wrapping a `wa-dropdown` whose `slot="trigger"` `wa-button` is the toolbar button and whose `wa-dropdown-item`s are the menu, with the dropdown kept under `data-morph-skip-children`. `PanelHeader` composes these for you as a panel/dialog/page heading. Wrap every toolbar item, including dormant text, in `ToolbarControlGroup`. A group remains 44px outside (`calc(2px + remify(42px))`) when its border/background are transparent; use 8px between groups and inside items. Split dormant and interactive regions: `MenuHeader` fills the available inline width and keeps its label and mutually exclusive semantic count or non-count `badge` together, with an independent logical-end 44px action unless disclosure mode makes the title cluster the button. Its action visual defaults to 18px through `--kui-menu-header-action-icon-size`; never shrink the target to match it. Pass every non-negative safe-integer section quantity through `count` with a localized full spoken `countLabel`; never concatenate it into `label` or put a number in `badge`. `MenuItem.trailing` is dormant; use `MenuActionRow` when primary and trailing actions need sibling 44px native buttons. Its `label`, `icon`, and `trailingActionIcon` slots are also dormant and cannot contain controls. Let panes relocate at narrow widths instead of shrinking targets.
66
+ 17. When a recurring concept has no matching export or production recipe, keep its semantics in a thin application adapter while reusing the public layout vocabulary. The composer recipe uses one visible form surface, `PanelHeader` title/summary ids, shared 8px field/action gutters, 24px major rhythm, and a conditional StateBanner as its only nested semantic surface; do not turn every section into a card or double-inset intrinsically bordered controls. The application-local `../docs/examples/command-palette-adapter.tsx` is reference source for one such missing concept, not an `@kerfjs/ui` runtime export or catalog recipe. The application owns its registration, ranking, history, permissions, availability, shortcut policy, focus policy, dispatch, and copy. If a missing concept recurs across products, open an upstream component or recipe request.
66
67
  18. Compose `ValueTable` from typed `ValueTableRow` entries instead of handwritten `dt`/`dd` wrappers. Pass `icon` for the optional 24px leading visual; the row owns 8px of root-scaled top and bottom padding, the 8px iconless or 40px icon-bearing separator start, and the common 8px right inset.
67
- 19. Use `DialogHeader` for dialog hierarchy, not a custom heading row. Its real top `Toolbar` puts the optional 24px icon in a 34px circular background and the first title line in a borderless `ToolbarControlGroup`, centered against the automatic action `ToolbarControlGroup`; pass action children directly and add a localized `actionsLabel` when that group needs a name. Its optional `summary`/`summaryId` row stays below the title. The app owns modal behavior, focus, dismissal, command policy, and action handling.
68
+ 19. Use `PanelHeader` for a panel, dialog, or page heading, not a custom heading row. It is a plain top `Toolbar` (overriding no Toolbar styles): the leading zone holds an optional icon (a normal bordered `ToolbarControlGroup` given a brand fill with a matching border) and the title as extra-large `ToolbarText`; the app's trailing controls (typically a `ToolbarControlGroup`) go straight in the trailing zone, and the icon group is omitted when no icon is passed. Its optional `summary`/`summaryId` row stays below the title. The app owns modal behavior, focus, dismissal, command policy, and action handling.
69
+ 20. Space with the official five-step scale, picked by how connected two elements are — not by eye. `0` `--kui-space-none` = no separation (one unit); `4px` `--kui-space-2xs` = very minor air on a connected cluster; `8px` `--kui-space-xs` = standard, between elements within a group; `16px` `--kui-space-m` = minor, between homogeneous groups; `24px` `--kui-space-l` = major, between heterogeneous groups (the `.kui-content` rhythm). The 8px-vs-24px distinction is inside-a-group vs between-major-differing-regions. `--kui-space-s` (12px) and `--kui-space-xl` (32px) are off-scale exceptions; prefer the five canonical remify-authored tokens. See `docs/layout.md` "Spacing scale".
70
+ 21. Pick a whole-screen layout from the opt-in, tree-shakeable subpaths by data + interaction + device, and derive responsiveness from `@kerfjs/ui/device-class`'s `deviceClass()` (`compact` = handset or portrait tablet = one pane at a time). Simple/flat → `NavStack` with one entry (single pane), plus `TabScaffold` (`@kerfjs/ui/tab-scaffold`, iOS bottom tabs, each tab its own `NavStack`) for 2–5 co-equal sections on `compact`. Drill-down → `NavStack` (`@kerfjs/ui/nav-stack`), upgrading to `SplitView` (`@kerfjs/ui/split-view`, list-detail) once both panes fit (`atLeast('tablet')` landscape); `SplitView` collapses to a `NavStack` on `compact`. Complex tool with peripheral panels → `Workbench` (`@kerfjs/ui/workbench`, collapsible rails + drawer) `atLeast('desktop')`, degrading to `NavStack`/overlays below. These are declarative (the app owns the stack/selection/collapsed/active state as signals) with disposer-returning `wire…` helpers; each ships a companion CSS import and stays out of the barrel. Dialogs pick the same inner layout, then present per device class (full-screen modal on `compact`, inline on desktop). See `docs/app-layouts.md`.
68
71
 
69
72
  Common mistakes:
70
73
 
@@ -88,4 +91,9 @@ Common mistakes:
88
91
  | Let the document, pane, and list all scroll | Keep fixed chrome outside one `.kui-pane__content` scroll owner per pane |
89
92
  | Invent an `@kerfjs/ui` command-palette export | Keep the semantics application-local, reuse public layout vocabulary, and propose recurring behavior upstream |
90
93
  | Handwrite `ValueTable` row wrappers or compensate their padding or separators | Compose `ValueTableRow`; its root-scaled block padding, optional icon hook, and separator geometry are public contract |
91
- | Rebuild a dialog heading or pre-wrap new `DialogHeader.actions` in groups | Pass action children directly; `DialogHeader` supplies aligned identity/action groups and keeps the optional subtitle below |
94
+ | Rebuild a panel/dialog heading row by hand | Use `PanelHeader`: a plain toolbar with an optional bordered icon group, an xl title, trailing controls you pass, and the optional subtitle below |
95
+ | Force a `width`/`height`/`padding` on a component to size or space it | Let it size to its content and tokens; a forced box leaves a halo or a stretched oval — adjust an icon-size or spacing token, not the box |
96
+ | Wrap a component or region in a card, border, backdrop, or outline to "contain" it | Let it sit on the surface; add a `.kui-content-item` only for a real distinction — hierarchy comes from alignment, spacing, and type first |
97
+ | Add another container's padding on top of a content-item's own margin | Pick one owner of the inset; a pane has no padding and its `.kui-content` children own the 8/1/8 geometry — stacking them double-insets |
98
+ | Keep chrome, a label, or a readout that aids no decision | Delete it; every element must help a person decide or act |
99
+ | Override a component's default size or color because it "looks off" | Trust the default (a LucideIcon is 24px) and fix the surrounding layout instead |
@@ -1,6 +1,6 @@
1
1
  # Web Awesome JSX signatures for the UI authoring corpus
2
2
 
3
- Generated from the emitted `@kerfjs/ui@5.0.0-beta.3` declaration boundary. Import `@kerfjs/ui/webawesome` for type effects when authoring direct `wa-*` JSX. The module emits no runtime behavior and does not register custom elements.
3
+ Generated from the emitted `@kerfjs/ui@5.0.0-beta.6` declaration boundary. Import `@kerfjs/ui/webawesome` for type effects when authoring direct `wa-*` JSX. The module emits no runtime behavior and does not register custom elements.
4
4
 
5
5
  ```ts
6
6
  import { KerfCustomElement } from 'kerfjs/jsx-runtime';
@@ -1,5 +1,6 @@
1
1
  import '../styles/foundation.css';
2
2
  import '../styles/toolbar.css';
3
3
  import '../styles/toolbar-control-group.css';
4
- import '../styles/dialog-header.css';
5
- export * from '../dialog-header.js';
4
+ import '../styles/toolbar-text.css';
5
+ import '../styles/panel-header.css';
6
+ export * from '../panel-header.js';
@@ -1,10 +1,10 @@
1
1
  import { jsx } from 'kerfjs/jsx-runtime';
2
2
 
3
3
  // src/toolbar-text.tsx
4
- function ToolbarText({ text, size = "default", className = "" }) {
5
- return /* @__PURE__ */ jsx("span", { class: `kui-toolbar-text ${className}`.trim(), "data-component": "toolbar-text", "data-size": size, children: text });
4
+ function ToolbarText({ text, size = "default", className = "", id }) {
5
+ return /* @__PURE__ */ jsx("span", { class: `kui-toolbar-text ${className}`.trim(), "data-component": "toolbar-text", "data-size": size, id, children: text });
6
6
  }
7
7
 
8
8
  export { ToolbarText };
9
9
 
10
- //# sourceMappingURL=chunk-GY5WH7TO.js.map
10
+ //# sourceMappingURL=chunk-24Z2XE6C.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/toolbar-text.tsx"],"names":[],"mappings":";;;AAUO,SAAS,WAAA,CAAY,EAAE,IAAA,EAAM,IAAA,GAAO,WAAW,SAAA,GAAY,EAAA,EAAI,IAAG,EAAqB;AAC5F,EAAA,uBAAO,GAAA,CAAC,MAAA,EAAA,EAAK,KAAA,EAAO,CAAA,iBAAA,EAAoB,SAAS,CAAA,CAAA,CAAG,IAAA,EAAK,EAAG,gBAAA,EAAe,cAAA,EAAe,WAAA,EAAW,IAAA,EAAM,IAAS,QAAA,EAAA,IAAA,EAAK,CAAA;AAC3H","file":"chunk-24Z2XE6C.js","sourcesContent":["export type ToolbarTextSize = 'xlarge' | 'large' | 'default' | 'small';\n\nexport interface ToolbarTextProps {\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\nexport function ToolbarText({ text, size = 'default', className = '', id }: ToolbarTextProps) {\n return <span class={`kui-toolbar-text ${className}`.trim()} data-component=\"toolbar-text\" data-size={size} id={id}>{text}</span>;\n}\n"]}