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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +3 -0
  2. package/ai/application-ui-diagnostic-ids-v1.json +3 -0
  3. package/ai/component-catalog.json +121 -9
  4. package/ai/component-composition.json +102 -6
  5. package/ai/components/nav-stack.md +3 -3
  6. package/ai/components/surface-scaffold.md +11 -2
  7. package/ai/components/toolbar-control-group.md +2 -0
  8. package/ai/components/toolbar-text.md +2 -0
  9. package/ai/components/toolbar.md +2 -0
  10. package/ai/public-api-signatures-v1.md +49 -121
  11. package/ai/skill.md +11 -1
  12. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  13. package/dist/browser/nav-stack.js +2 -0
  14. package/dist/browser/split-view.js +2 -0
  15. package/dist/catalog-resources.d.ts +3 -3
  16. package/dist/catalog.d.ts +3 -3
  17. package/dist/catalog.js +3 -3
  18. package/dist/{chunk-WJN3V2H5.js → chunk-2GOCOLQL.js} +18 -4
  19. package/dist/chunk-2GOCOLQL.js.map +1 -0
  20. package/dist/{chunk-XIZRXI7N.js → chunk-A7DYEERU.js} +13 -1
  21. package/dist/chunk-A7DYEERU.js.map +1 -0
  22. package/dist/{chunk-6VQPLV3O.js → chunk-DSLRNR43.js} +9 -8
  23. package/dist/chunk-DSLRNR43.js.map +1 -0
  24. package/dist/{chunk-AOAW55JN.js → chunk-DXQZ3BEO.js} +4 -1
  25. package/dist/chunk-DXQZ3BEO.js.map +1 -0
  26. package/dist/chunk-IKCUPTR2.js +170 -0
  27. package/dist/chunk-IKCUPTR2.js.map +1 -0
  28. package/dist/{chunk-Z6PE5T7N.js → chunk-JL4BBZP4.js} +4 -3
  29. package/dist/chunk-JL4BBZP4.js.map +1 -0
  30. package/dist/collapsible-panel.js +2 -2
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.js +5 -5
  33. package/dist/nav-stack-B1Qc2e5q.d.ts +101 -0
  34. package/dist/nav-stack.d.ts +11 -93
  35. package/dist/nav-stack.js +5 -1
  36. package/dist/pane.d.ts +5 -2
  37. package/dist/pane.js +1 -1
  38. package/dist/split-view.d.ts +9 -4
  39. package/dist/split-view.js +7 -3
  40. package/dist/split-view.js.map +1 -1
  41. package/dist/styles/components/actions/toolbar-control-group/toolbar-control-group.css +60 -13
  42. package/dist/styles/components/actions/toolbar-text/toolbar-text.css +7 -1
  43. package/dist/styles/components/layout/pane/pane.css +7 -0
  44. package/dist/styles/components/navigation/nav-stack/nav-stack.css +6 -12
  45. package/dist/styles/components/navigation/tab-scaffold/tab-scaffold.css +17 -0
  46. package/dist/styles/components/surfaces/surface-scaffold/surface-scaffold.css +55 -0
  47. package/dist/styles/nav-stack.css +6 -12
  48. package/dist/styles/pane.css +7 -0
  49. package/dist/styles/surface-scaffold.css +55 -0
  50. package/dist/styles/tab-scaffold.css +17 -0
  51. package/dist/styles/toolbar-control-group.css +60 -13
  52. package/dist/styles/toolbar-text.css +7 -1
  53. package/dist/surface-scaffold-register.d.ts +4 -0
  54. package/dist/surface-scaffold-register.js +59 -0
  55. package/dist/surface-scaffold-register.js.map +1 -0
  56. package/dist/surface-scaffold.d.ts +10 -2
  57. package/dist/surface-scaffold.js +1 -1
  58. package/dist/tab-scaffold.d.ts +2 -0
  59. package/dist/tab-scaffold.js +1 -0
  60. package/dist/tab-scaffold.js.map +1 -1
  61. package/dist/wire-toolbar-visibility.js +1 -1
  62. package/dist/workbench.d.ts +5 -13
  63. package/dist/workbench.js +1 -1
  64. package/docs/component-contract.md +8 -1
  65. package/docs/component-selection.md +1 -1
  66. package/docs/layout.md +20 -0
  67. package/docs/nav-stack.md +30 -18
  68. package/docs/recipes.md +5 -1
  69. package/docs/split-view.md +7 -4
  70. package/docs/surface-scaffold.md +46 -0
  71. package/docs/tab-scaffold.md +57 -0
  72. package/docs/ui-doctor.md +23 -0
  73. package/docs/workbench.md +3 -2
  74. package/doctor/component-style-checks.mjs +224 -0
  75. package/doctor/index.mjs +42 -13
  76. package/llms.txt +10 -0
  77. package/package.json +7 -2
  78. package/ux-demo/recipes/list-detail-dialog.tsx +4 -0
  79. package/ux-demo/recipes/navigation-stack.tsx +2 -2
  80. package/dist/chunk-6VQPLV3O.js.map +0 -1
  81. package/dist/chunk-AOAW55JN.js.map +0 -1
  82. package/dist/chunk-WJN3V2H5.js.map +0 -1
  83. package/dist/chunk-XIZRXI7N.js.map +0 -1
  84. package/dist/chunk-Z4EPMX25.js +0 -123
  85. package/dist/chunk-Z4EPMX25.js.map +0 -1
  86. package/dist/chunk-Z6PE5T7N.js.map +0 -1
@@ -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.77` and `kerfjs@5.0.0-beta.77`. 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.78` and `kerfjs@5.0.0-beta.78`. 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/css-values`
6
6
 
@@ -1285,101 +1285,19 @@ export { type ScrollDividerTarget, type WireScrollDividersOptions, wireScrollDiv
1285
1285
  ## `@kerfjs/ui/nav-stack`
1286
1286
 
1287
1287
  ```ts
1288
- import * as kerfjs from 'kerfjs';
1289
- import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
1290
- import { ToolbarConfig } from './toolbar.js';
1291
- import { ToolbarTextSize, HeadingLevel } from './toolbar-text.js';
1292
- import { PaneAppearance } from './pane.js';
1288
+ export { c as NavStack, N as NavStackProps, d as NavStackToolbarConfig, a as NavStackView, e as NavStackViewBottomToolbar, f as NavStackViewToolbar } from './nav-stack-B1Qc2e5q.js';
1289
+ import 'kerfjs';
1290
+ import './semantic-content-BbzjvSu9.js';
1291
+ import './list.js';
1292
+ import './flex-alignment-4ms8ZbV8.js';
1293
1293
  import './sides-BPSWde0A.js';
1294
1294
  import './css-values.js';
1295
-
1296
- /**
1297
- * One entry in a {@link NavStack}. The app owns the stack as an array (usually a
1298
- * signal); `NavStack` renders it and `wireNavStack` animates the transitions.
1299
- */
1300
- interface NavStackView {
1301
- /** Stable identity for keyed reconcile and transition direction. */
1302
- key: string;
1303
- content: KerfUiContent;
1304
- /** Background of this view's scrolling work surface. */
1305
- appearance?: PaneAppearance;
1306
- /** Title shown in the top toolbar for this view. */
1307
- title?: string;
1308
- /** Leading groups for this view's top toolbar, after the back control and before the title. */
1309
- leading?: KerfUiContent;
1310
- /** Center content for this view's top toolbar (placed per `toolbarConfig.centerAlign`). */
1311
- center?: KerfUiContent;
1312
- /** Trailing actions for this view's top toolbar. */
1313
- toolbar?: KerfUiContent;
1314
- /** Fixed chrome below this view's toolbar, above its scrolling content. */
1315
- header?: KerfUiContent;
1316
- /** Bottom toolbar for this view. Cross-fades with the top chrome on navigation. */
1317
- bottomToolbar?: KerfUiContent;
1318
- }
1319
- /**
1320
- * The top toolbar's configuration. Its `ToolbarConfig` forwards to the real
1321
- * `Toolbar` the stack renders; by default that toolbar draws no divider and
1322
- * claims the top and side safe-area edges the stack still touches.
1323
- */
1324
- interface NavStackToolbarConfig extends ToolbarConfig {
1325
- /** Accessible name of the top toolbar (default: none). */
1326
- label?: string;
1327
- /** Size of the view title's `ToolbarText` (default `large`). */
1328
- titleSize?: ToolbarTextSize;
1329
- /** Expose the view title as a heading at this level (default: a plain span). */
1330
- headingLevel?: HeadingLevel;
1331
- }
1332
- interface NavStackProps {
1333
- id: string;
1334
- /** Accessible name for the stack region. */
1335
- label: string;
1336
- /** The stack, root first; the last entry is the active top view. */
1337
- views: NavStackView[];
1338
- /** Accessible label for the icon-only back control (default "Back"). */
1339
- backLabel?: string;
1340
- /** The back control's icon (default a chevron-left `LucideIcon`). */
1341
- backIcon?: KerfUiContent;
1342
- /**
1343
- * Visible text beside the back icon, such as the previous view's title
1344
- * (default: icon only). When set, the text names the control and
1345
- * `backLabel` is not used.
1346
- */
1347
- backText?: string;
1348
- /** The top toolbar's configuration, forwarded to its `Toolbar`. */
1349
- toolbarConfig?: NavStackToolbarConfig;
1350
- /** Persistent last group in every view's top toolbar, supplied by a hosting panel. */
1351
- panelToggle?: KerfUiContent;
1352
- /** Hide the top toolbar entirely (rare — a fully custom-chrome view). */
1353
- hideToolbar?: boolean;
1354
- /** Optional persistent bottom toolbar used when the active view does not provide one. */
1355
- bottomToolbar?: KerfUiContent;
1356
- /**
1357
- * The line under the top chrome and over the bottom toolbar, where they
1358
- * meet the active view. `scroll` (default) shows the chrome's line only
1359
- * while the view's content is scrolled beneath it, and the bottom
1360
- * toolbar's only while more content lies below — never when the content
1361
- * fits — once `wireScrollDividers` (`@kerfjs/ui/wire-scroll-dividers`) is
1362
- * wired above the stack; unwired, neither shows. `always` shows both
1363
- * without the wiring; `none` neither, even when wired. The line is drawn
1364
- * inside the chrome, so no state moves the chrome or the content. A
1365
- * `toolbarConfig.dividerSides` edge is the Toolbar's own and is unaffected.
1366
- */
1367
- chromeDividers?: 'scroll' | 'always' | 'none';
1368
- className?: string;
1369
- /** Native named-slot assignment when composed inside a web component. */
1370
- slot?: string;
1371
- }
1372
- /**
1373
- * A navigation stack (iOS-style push/pop). Renders every entry stacked, the last
1374
- * one active; `@kerfjs/ui/wire-nav-stack`'s `wireNavStack` slides the content and
1375
- * cross-fades the chrome across a change. Its top chrome is a real `Toolbar`: the
1376
- * back control and title lead, the active view's `leading` / `center` / `toolbar`
1377
- * content fills the zones, and `toolbarConfig` configures it. A single-pane
1378
- * layout is a `NavStack` with one entry. See `docs/23-app-layouts.md` §3.1.
1379
- */
1380
- declare function NavStack({ id, label, views, backLabel, backIcon, backText, toolbarConfig, panelToggle, hideToolbar, bottomToolbar, chromeDividers, className, slot, }: NavStackProps): kerfjs.SafeHtml;
1381
-
1382
- export { NavStack, type NavStackProps, type NavStackToolbarConfig, type NavStackView };
1295
+ import './pane.js';
1296
+ import './panel-toolbar-CL9tYOkh.js';
1297
+ import './toolbar.js';
1298
+ import './lucide-icon.js';
1299
+ import 'lucide';
1300
+ import './toolbar-text.js';
1383
1301
  ```
1384
1302
 
1385
1303
  ## `@kerfjs/ui/wire-nav-stack`
@@ -1410,13 +1328,18 @@ export { type WireNavStackOptions, wireNavStack };
1410
1328
  ```ts
1411
1329
  import * as kerfjs from 'kerfjs';
1412
1330
  import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
1413
- import { NavStackProps, NavStackView } from './nav-stack.js';
1331
+ import { N as NavStackProps, a as NavStackView } from './nav-stack-B1Qc2e5q.js';
1414
1332
  import { ResizableRegionProps } from './resizable-region.js';
1415
- import './toolbar.js';
1333
+ import './list.js';
1334
+ import './flex-alignment-4ms8ZbV8.js';
1416
1335
  import './sides-BPSWde0A.js';
1336
+ import './css-values.js';
1417
1337
  import './pane.js';
1338
+ import './panel-toolbar-CL9tYOkh.js';
1339
+ import './toolbar.js';
1340
+ import './lucide-icon.js';
1341
+ import 'lucide';
1418
1342
  import './toolbar-text.js';
1419
- import './css-values.js';
1420
1343
 
1421
1344
  /**
1422
1345
  * The roomy list pane's `ResizableRegion`: its committed `size` and `min` /
@@ -1431,7 +1354,7 @@ interface SplitViewResizable extends Pick<ResizableRegionProps, 'separator' | 'h
1431
1354
  max: number;
1432
1355
  }
1433
1356
  /** One compact view's top- and bottom-toolbar content (see `NavStackView`). */
1434
- type SplitViewCompactViewToolbars = Pick<NavStackView, 'leading' | 'center' | 'toolbar' | 'bottomToolbar'>;
1357
+ type SplitViewCompactViewToolbars = Pick<NavStackView, 'toolbar' | 'bottomToolbar'>;
1435
1358
  /**
1436
1359
  * The compact `NavStack`'s configuration: its toolbar configuration, back
1437
1360
  * control, persistent bottom toolbar, and chrome dividers forward to the
@@ -1512,6 +1435,7 @@ type PaneRootAttributes = Readonly<Record<`data-${string}`, string | undefined>
1512
1435
  'data-chrome-placement'?: never;
1513
1436
  'data-chrome-dividers'?: never;
1514
1437
  'data-appearance'?: never;
1438
+ 'data-deep-inset'?: never;
1515
1439
  }>;
1516
1440
  /** How a Pane's header and footer relate to its scrolling content. */
1517
1441
  type PaneChromePlacement = 'fixed' | 'auto';
@@ -1551,6 +1475,8 @@ interface PaneProps {
1551
1475
  chromeDividers?: PaneChromeDividers;
1552
1476
  /** Paint the scrolling work surface with the shared lowered-surface color. */
1553
1477
  appearance?: PaneAppearance;
1478
+ /** Add an extra 8px inline gutter to scrolling content (default: false). */
1479
+ deepInset?: boolean;
1554
1480
  /** Root semantics. Defaults to `div`. */
1555
1481
  element?: PaneElement;
1556
1482
  /** Scrolling content semantics. Defaults to `div`. */
@@ -1585,14 +1511,14 @@ interface PaneProps {
1585
1511
  * or a CollapsiblePanel) forwards, so the app configures that pane instead of
1586
1512
  * styling it. An omitted or `undefined` field keeps the composite's default.
1587
1513
  */
1588
- type PaneConfig = Pick<PaneProps, 'contentElement' | 'contentLabel' | 'separators' | 'safeAreaEdges' | 'chromeDividers' | 'appearance'>;
1514
+ type PaneConfig = Pick<PaneProps, 'contentElement' | 'contentLabel' | 'separators' | 'safeAreaEdges' | 'chromeDividers' | 'appearance' | 'deepInset'>;
1589
1515
  /**
1590
1516
  * An unpadded application column with optional fixed header/footer slots and one
1591
1517
  * scrolling vertical content owner. Separator lines are independently opt-in on
1592
1518
  * each logical edge, so the same component works as a sidebar, main area,
1593
1519
  * inspector, or dialog column.
1594
1520
  */
1595
- declare function Pane({ header, children, footer, chromePlacement, chromeDividers, appearance, element, contentElement, separators, safeAreaEdges, id, label, contentLabel, className, headerClassName, contentClassName, footerClassName, rootAttributes, slot, }: PaneProps): kerfjs.SafeHtml;
1521
+ declare function Pane({ header, children, footer, chromePlacement, chromeDividers, appearance, deepInset, element, contentElement, separators, safeAreaEdges, id, label, contentLabel, className, headerClassName, contentClassName, footerClassName, rootAttributes, slot, }: PaneProps): kerfjs.SafeHtml;
1596
1522
 
1597
1523
  export { Pane, type PaneAppearance, type PaneChromeDividers, type PaneChromePlacement, type PaneConfig, type PaneContentElement, type PaneElement, type PaneProps, type PaneSeparatorSide };
1598
1524
  ```
@@ -1602,16 +1528,16 @@ export { Pane, type PaneAppearance, type PaneChromeDividers, type PaneChromePlac
1602
1528
  ```ts
1603
1529
  import { SafeHtml } from 'kerfjs';
1604
1530
  import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
1531
+ import { b as NavPane, N as NavStackProps } from './nav-stack-B1Qc2e5q.js';
1605
1532
  import { ListConfig } from './list.js';
1606
- import { NavStackProps } from './nav-stack.js';
1607
1533
  import { PaneConfig } from './pane.js';
1608
1534
  import { ResizableRegionSeparator, ResizableRegionCollapseMotion, ResizableRegionContentOverflow, ResizableRegionPresentation, ResizableRegionRestorePosition } from './resizable-region.js';
1609
- import { a as PanelToolbar, b as PanelChromePlacement, c as PanelBottomToolbar, d as PanelToggle } from './panel-toolbar-CL9tYOkh.js';
1535
+ import { a as PanelToolbar, c as PanelBottomToolbar, b as PanelChromePlacement, d as PanelToggle } from './panel-toolbar-CL9tYOkh.js';
1610
1536
  import { ToolbarConfig } from './toolbar.js';
1537
+ import './toolbar-text.js';
1538
+ import './css-values.js';
1611
1539
  import './flex-alignment-4ms8ZbV8.js';
1612
1540
  import './sides-BPSWde0A.js';
1613
- import './css-values.js';
1614
- import './toolbar-text.js';
1615
1541
  import './lucide-icon.js';
1616
1542
  import 'lucide';
1617
1543
 
@@ -1733,19 +1659,11 @@ interface WorkbenchPanelBase {
1733
1659
  restorePosition?: ResizableRegionRestorePosition;
1734
1660
  }
1735
1661
  /** The original static panel body: one toolbar, optional fixed chrome, and one Pane. */
1736
- interface WorkbenchStaticPanel extends WorkbenchPanelBase {
1737
- content: KerfUiContent;
1662
+ interface WorkbenchStaticPanel extends WorkbenchPanelBase, NavPane<WorkbenchPanelToolbar, WorkbenchPanelBottomToolbar> {
1738
1663
  /** The composed toolbar; marked groups and its toggle relocate on collapse. */
1739
1664
  toolbar?: WorkbenchPanelToolbar;
1740
- header?: KerfUiContent;
1741
- headerList?: ListConfig;
1742
1665
  headerPlacement?: WorkbenchChromePlacement;
1743
- footer?: KerfUiContent;
1744
- footerList?: ListConfig;
1745
- bottomToolbar?: WorkbenchPanelBottomToolbar;
1746
1666
  footerPlacement?: WorkbenchChromePlacement;
1747
- /** Configuration for the static toolbar panel's Pane. */
1748
- pane?: PaneConfig;
1749
1667
  navStack?: never;
1750
1668
  }
1751
1669
  /** A panel whose active NavStack view supplies the toolbar, fixed header, and scroll body. */
@@ -2213,6 +2131,8 @@ interface TabScaffoldTabBase<Id extends string> {
2213
2131
  content: KerfUiContent;
2214
2132
  /** Background of this scene's scrolling work surface. */
2215
2133
  appearance?: PaneAppearance;
2134
+ /** Add an extra 8px inline gutter to a plain scene's scrolling content. */
2135
+ deepInset?: boolean;
2216
2136
  }
2217
2137
  /** A tab with an optional count or short-status badge. */
2218
2138
  interface TabScaffoldTabCountBadge {
@@ -2455,15 +2375,15 @@ import './toolbar.js';
2455
2375
  import './sides-BPSWde0A.js';
2456
2376
  import './pane.js';
2457
2377
  import './workbench.js';
2378
+ import './nav-stack-B1Qc2e5q.js';
2458
2379
  import './list.js';
2459
2380
  import './flex-alignment-4ms8ZbV8.js';
2460
2381
  import './css-values.js';
2461
- import './nav-stack.js';
2462
- import './toolbar-text.js';
2463
- import './resizable-region.js';
2464
2382
  import './panel-toolbar-CL9tYOkh.js';
2465
2383
  import './lucide-icon.js';
2466
2384
  import 'lucide';
2385
+ import './toolbar-text.js';
2386
+ import './resizable-region.js';
2467
2387
 
2468
2388
  /**
2469
2389
  * Controlled, stateless component-catalog shell: a `Workbench` whose left rail
@@ -2550,15 +2470,15 @@ import './semantic-content-BbzjvSu9.js';
2550
2470
  import './sides-BPSWde0A.js';
2551
2471
  import './pane.js';
2552
2472
  import './workbench.js';
2473
+ import './nav-stack-B1Qc2e5q.js';
2553
2474
  import './list.js';
2554
2475
  import './flex-alignment-4ms8ZbV8.js';
2555
2476
  import './css-values.js';
2556
- import './nav-stack.js';
2557
- import './toolbar-text.js';
2558
- import './resizable-region.js';
2559
2477
  import './panel-toolbar-CL9tYOkh.js';
2560
2478
  import './lucide-icon.js';
2561
2479
  import 'lucide';
2480
+ import './toolbar-text.js';
2481
+ import './resizable-region.js';
2562
2482
 
2563
2483
  /**
2564
2484
  * Standard resource labels for a Kerf catalog detail footer. Keep these labels
@@ -3358,22 +3278,30 @@ export { wireToolbarVisibility };
3358
3278
  ```ts
3359
3279
  import * as kerfjs from 'kerfjs';
3360
3280
  import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
3281
+ import { CssLength } from './css-values.js';
3361
3282
 
3362
3283
  type DialogSurfaceSize = 'small' | 'medium' | 'large';
3363
3284
  type DialogSurfacePresentation = 'modal' | 'side-sheet' | 'fullscreen';
3364
3285
  type SurfaceInset = 'none' | 'compact' | 'comfortable';
3286
+ type DialogSurfaceMaxHeight = CssLength | 'viewport';
3365
3287
  interface DialogSurfaceProps {
3366
3288
  children: KerfUiContent;
3367
3289
  size?: DialogSurfaceSize;
3368
3290
  presentation?: DialogSurfacePresentation;
3291
+ /** Modal preferred width. Takes precedence over size, within the viewport cap. */
3292
+ preferredWidth?: CssLength;
3369
3293
  bodyInset?: SurfaceInset;
3370
3294
  footerInset?: SurfaceInset;
3295
+ /** Modal edge clearance. Omit to retain Web Awesome's default viewport cap. */
3296
+ viewportGutter?: CssLength;
3297
+ /** Modal height cap, bounded by the dynamic viewport minus both gutters. */
3298
+ maxHeight?: DialogSurfaceMaxHeight;
3371
3299
  className?: string;
3372
3300
  /** Native named-slot assignment when composed inside a web component. */
3373
3301
  slot?: string;
3374
3302
  }
3375
3303
  /** Configure recurring Web Awesome dialog geometry without consumer ::part() CSS. */
3376
- declare function DialogSurface({ children, size, presentation, bodyInset, footerInset, className, slot, }: DialogSurfaceProps): kerfjs.SafeHtml;
3304
+ declare function DialogSurface({ children, size, presentation, preferredWidth, bodyInset, footerInset, viewportGutter, maxHeight, className, slot, }: DialogSurfaceProps): kerfjs.SafeHtml;
3377
3305
  type PopupSurfaceInset = 'standard' | 'compact' | 'list-zero';
3378
3306
  interface PopupSurfaceProps {
3379
3307
  children: KerfUiContent;
@@ -3385,7 +3313,7 @@ interface PopupSurfaceProps {
3385
3313
  /** Configure recurring Web Awesome dropdown-menu geometry without consumer ::part() CSS. */
3386
3314
  declare function PopupSurface({ children, inset, className, slot, }: PopupSurfaceProps): kerfjs.SafeHtml;
3387
3315
 
3388
- export { DialogSurface, type DialogSurfacePresentation, type DialogSurfaceProps, type DialogSurfaceSize, PopupSurface, type PopupSurfaceInset, type PopupSurfaceProps, type SurfaceInset };
3316
+ export { DialogSurface, type DialogSurfaceMaxHeight, type DialogSurfacePresentation, type DialogSurfaceProps, type DialogSurfaceSize, PopupSurface, type PopupSurfaceInset, type PopupSurfaceProps, type SurfaceInset };
3389
3317
  ```
3390
3318
 
3391
3319
  ## `@kerfjs/ui/text`
package/ai/skill.md CHANGED
@@ -267,7 +267,7 @@ For a rail view heading, use `presentation="title"` for the 36px bold,
267
267
  flush-start trigger and adjacent caret. Set `focusRingInset` when an ancestor
268
268
  clips focus painting; never restyle Select shadow parts from application CSS.
269
269
 
270
- 19. 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. 20. Use the standard direct `Toolbar` composition for a panel, dialog, or page heading, not a private wrapper or custom heading row. Put an optional icon in a `ToolbarControlGroup`, the title directly in the leading zone as extra-large `ToolbarText`, and actions in a trailing group. Set `headingLevel` for page/section landmarks. Supporting copy stays below the toolbar as app-owned content. The app owns modal behavior, focus, dismissal, command policy, ids, and action handling. 21. 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. For `Row.gap`, `Grid.gap`, or `List.gap`, pass a finite shorthand such as `gap="xs"` or `gap="m"`; use `Spacer` for one intentional typed empty width/height or a flexible main-axis gap. When one token is insufficient, import typed builders from the CSS-free `@kerfjs/ui/css-values` subpath. Preserve property grammar: lengths for gap/Spacer/Skeleton geometry, `flex()` for Row/Grid/List flex, and a foreground color for choice icons: `uiColor()` with a `*-on-*` or text token (`success-on-quiet`, `text-quiet`) or `foregroundColorVar()`. Fill tokens (`*-fill-*`) are backgrounds, not foregrounds, and `SelectChoice.color` rejects them. Never cast between brands or emit raw row `style` declarations. `plus` is not standalone and must be wrapped in `calc`; `remify()` remains source-CSS syntax and is never a runtime prop value. See `docs/layout.md` "Spacing scale". 22. 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`; show an unread count on a tab with its `badge` (a number or short string, rendered as the top-trailing danger `Badge` on the icon) plus a localized `badgeLabel` ("3 unread", folded into the tab's name as "Inbox, 3 unread"), never by concatenating the count into `label` or restyling the badge; for new content without a count pass `badge: true` (the text-free dot) with its required `badgeLabel` ("New activity"). 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`. Keep each stack's ordered views controlled; put view-specific bottom chrome on `NavStackView.bottomToolbar` (or use the component-level persistent fallback), then call `wireNavStack` so content slides while the active view's top and bottom chrome cross-fade and focus moves into the new top view/restores on pop. Mark a preferred initial heading or control with `data-nav-focus` when DOM order is not sufficient. The stack's top chrome is a real `Toolbar`: put a view's groups on `NavStackView.leading` / `center` / `toolbar` (trailing), and configure the bar through `toolbarConfig` (`ToolbarConfig` plus `label`, `titleSize`, `headingLevel`) and `backIcon` / `backText`, never with CSS. `SplitView` forwards both of its inner components: `resizable` takes the list `ResizableRegion`'s `separator`, `handleIcon`, `contentOverflow`, and collapse props (`collapsed`, `transitioning`, `collapseMotion`, `restoreControl`, `restorePosition`), and `compactStack` takes the compact stack's `toolbarConfig`, `backIcon`, `backText`, `hideToolbar`, `bottomToolbar`, plus `list` / `detail` view toolbars (`leading`, `center`, `toolbar`, `bottomToolbar`). Complex tool with peripheral panels → `Workbench` (`@kerfjs/ui/workbench`, collapsible rails + drawer) `atLeast('desktop')`, degrading to `NavStack`/overlays below. Workbench panels are fixed-size unless a panel opts in with `resizable` (`true` or `{ min, max }`); then render `size={sizeSignal.value}` and call `wireWorkbench(root, { id, panels: { leftRail: { size: sizeSignal, storageKey? } }, deviceClass })` from `@kerfjs/ui/wire-workbench` for pointer drags, arrow/Shift+arrow/Home/End on the separator, persistence, and no resizing on `compact` classes. Inline rails, fixed or resizable, leave the work area `mainMinSize` px of width (a `Workbench` prop, default 320, `0` off), and an inline bottom drawer leaves it `mainMinHeight` px of height (default 120, `0` off): resizable panels stop growing there, rails shrink in proportion when the Workbench narrows and the drawer when it gets shorter, so do not add your own main-area `min-width` / `min-height`. Rails present as overlays below the `narrow` Workbench container breakpoint (704px) by default — `responsiveOverlayAt: "compact"` moves that to 448px and `"never"` keeps a rail inline — and a bottom drawer opts in the same way; do not derive `presentation` from `deviceClass`, and overlays never resize. At compact widths an overlay rail fills the Workbench less a 44px dismiss strip (`--kui-workbench-overlay-dismiss-margin`) on the side away from its edge; `compactOverlay: "full"` makes one rail fill it. Pass each such panel's `collapsed` signal to `wireWorkbench` too (`panels: { leftRail: { collapsed: navCollapsed } }`, `size` optional) so the overlay is transient like `wireSidebar`'s compact overlay: it collapses when the breakpoint begins to apply (the inline state returns when it ends), takes focus on its first control and keeps Tab inside it while open, and closes on Escape or an outside press with focus returned to its toggle (give each toggle `aria-controls` naming its panel's derived id, `<workbench id>-left-rail` / `-right-rail` / `-bottom-drawer`, so a panel open at wire-up has a focus target too); opening one overlay closes the other open overlays, so they never cover each other's controls; do not hand-roll those listeners (`dismissOverlays: false` opts out of all of it, `exclusiveOverlays: false` of exclusivity only). Collapse stays the app's `collapsed` flag and keeps the size, so an expanded panel returns at its last size. Let the Workbench compose panel toolbars: give each panel `toolbar: { label, title?, leading?, center?, trailing?, toggle: { action, name, showLabel?, hideLabel? } }` and the work area `mainToolbar` (and `mainBottomToolbar` for a drawer). Each of these toolbars takes the `Toolbar`'s configuration (`ToolbarConfig`: `dividerSides`, `centerAlign`, `responsive`, `responsiveAt`, `safeAreaEdges`); configure a composed toolbar through those props (for example `dividerSides: ""` to drop its divider), never with CSS. Configure the composed panes and chrome lists the same way: a panel's `pane` (and `CollapsiblePanel`'s `pane`) and the Workbench's `mainPane` take `PaneConfig` (`contentElement`, `contentLabel`, `separators`, `safeAreaEdges` — for example `{ contentElement: "nav", contentLabel: "Files" }` for a navigator rail), and `mainHeaderList` / `mainFooterList` take `ListConfig` (`gap`, `hAlign`, `vAlign`, `dividerSides`, `textInsets`, `controlInsets`). Open, a panel's toolbar places groups in its leading, center, and trailing zones, with the toggle last; mark a ToolbarControlGroup relocateOnCollapse to keep it available when closed (pass marked groups directly or in arrays rather than JSX fragments); closed, marked groups follow zone order into the work area before the toggle — a left rail's lead `mainToolbar`, a right rail's end it, a drawer's trail `mainBottomToolbar` or float in a corner `FloatingToolbar` — and panel-only groups stay behind. A control sits near where its effect appears: a collapsed drawer's restore belongs in the bottom toolbar or a `FloatingToolbar` beside the drawer, not in a distant top toolbar. Do not hand-place panel toggles in the work-area toolbar or render a second copy, and handle only the toggle's `data-action`. A collapsed panel renders `inert` and `aria-hidden` by itself, on the rail/drawer and its content (its `restoreControl` stays outside and reachable), so do not add your own `tabindex`, `aria-hidden`, or `inert` to hide it. These are declarative (the app owns the stack/selection/collapsed/active state as signals) with disposer-returning `wire…` helpers; each stays out of the barrel, and its CSS-aware browser import loads the layout's CSS plus that of every component it renders (`Pane`, `Toolbar`, `List`, …), so do not hand-import `pane.css` or the other internal stylesheets; the companion `.css` subpath is only for pipelines without the `browser` condition. For a standalone collapsible rail or bottom drawer outside a full shell, use `CollapsiblePanel` + `CollapsiblePanelToggle` (`@kerfjs/ui/collapsible-panel`) with `wireSidebar` (`@kerfjs/ui/wire-sidebar`): the standard collapse animation and per-side icon convention (`PanelLeft*`/`PanelRight*`/`PanelBottom*`) plus toggle, focus, compact overlay, and persistence semantics. Give a standalone panel the same `toolbar` roles and render `CollapsiblePanelRelocated` in your work-area toolbar (first in leading for a left rail, last in trailing for a right rail, a bottom toolbar or a `FloatingToolbar` restoreControl for a drawer) instead of hand-placing a second toggle. A collapsed `CollapsiblePanel`, `ResizableRegion`, or Workbench panel renders `inert` and `aria-hidden` by itself, so do not hide collapsed content yourself either. Likewise an open side overlay (a Workbench rail overlay, a `wireSidebar` compact overlay, an overlay `CollapsiblePanel` or horizontal `ResizableRegion`) hides the covered work area's `FloatingToolbar`s and restore corners by itself until it closes, so do not hide them from overlay state. A compact overlay opens only on a user action: wire-up on a compact device and a wide-to-compact crossing collapse every panel, and a wide crossing restores the inline state. Seed the signal with `signal(device.value.compact)` and pass the panel's `inlineCollapsed` default so the first compact render has no overlay. Dialogs pick the same inner layout, then present per device class (full-screen modal on `compact`, inline on desktop). A list-detail dialog is a Workbench-shaped layout on roomy devices: a full-height list sidebar and a full-height main column, each a `Pane` with its own top `Toolbar`; the main column holds the primary xl title, the close control (`data-dialog="close"` in a `without-header` Web Awesome dialog), and the record actions, never a header spanning both columns. A top-level percentage-height shell needs a definite containing height: opt into `@kerfjs/ui/document.css` and put `.kui-app-root` on the one direct mount container, or provide equivalent application-owned geometry. The global baseline is deliberately absent from `styles.css` and component imports. See `docs/app-layouts.md`, `docs/document-baseline.md`, and `docs/collapsible-panel.md`. 23. Layouts and `Pane` are safe-area aware without configuration: backgrounds, separators, and dividers paint through a device's unsafe areas (a collapsible rail or drawer grows by the inset it docks to), content is padded only on the sides a region actually reaches, and block insets are scroll padding inside the scroller so first/last content can always be scrolled clear. Interior edges get nothing, and a collapsed rail hands its edge back to the center. Never add your own `env(safe-area-inset-*)` padding around a kerf layout or pane — that double-insets. In an app-owned arrangement of panes, pass `Pane.safeAreaEdges` (the sides that pane reaches; `[]` opts out); give an app bar or bottom bar that sits at a screen edge outside a Pane header or footer `Toolbar.safeAreaEdges` for the sides it reaches (for example `['block-start', 'inline-start', 'inline-end']`); the Workbench `mainToolbar` / `mainBottomToolbar` and panel `toolbar` configurations forward the same `safeAreaEdges` to their `Toolbar`. Do not set the internal `--kui-edge-inset-*` context yourself. The device insets are `--kui-safe-area-*` (defaulting to `env()`), overridable on `:root`. See `docs/app-layouts.md` "Safe areas".
270
+ 19. 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. 20. Use the standard direct `Toolbar` composition for a panel, dialog, or page heading, not a private wrapper or custom heading row. Put an optional icon in a `ToolbarControlGroup`, the title directly in the leading zone as extra-large `ToolbarText`, and actions in a trailing group. Set `headingLevel` for page/section landmarks. Supporting copy stays below the toolbar as app-owned content. The app owns modal behavior, focus, dismissal, command policy, ids, and action handling. 21. 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. For `Row.gap`, `Grid.gap`, or `List.gap`, pass a finite shorthand such as `gap="xs"` or `gap="m"`; use `Spacer` for one intentional typed empty width/height or a flexible main-axis gap. When one token is insufficient, import typed builders from the CSS-free `@kerfjs/ui/css-values` subpath. Preserve property grammar: lengths for gap/Spacer/Skeleton geometry, `flex()` for Row/Grid/List flex, and a foreground color for choice icons: `uiColor()` with a `*-on-*` or text token (`success-on-quiet`, `text-quiet`) or `foregroundColorVar()`. Fill tokens (`*-fill-*`) are backgrounds, not foregrounds, and `SelectChoice.color` rejects them. Never cast between brands or emit raw row `style` declarations. `plus` is not standalone and must be wrapped in `calc`; `remify()` remains source-CSS syntax and is never a runtime prop value. See `docs/layout.md` "Spacing scale". 22. 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`; show an unread count on a tab with its `badge` (a number or short string, rendered as the top-trailing danger `Badge` on the icon) plus a localized `badgeLabel` ("3 unread", folded into the tab's name as "Inbox, 3 unread"), never by concatenating the count into `label` or restyling the badge; for new content without a count pass `badge: true` (the text-free dot) with its required `badgeLabel` ("New activity"). 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`. Keep each stack's ordered views controlled; put view-specific bottom chrome on `NavStackView.bottomToolbar` (or use the component-level persistent fallback), then call `wireNavStack` so content slides while the active view's top and bottom chrome cross-fade and focus moves into the new top view/restores on pop. Mark a preferred initial heading or control with `data-nav-focus` when DOM order is not sufficient. The stack's top chrome is a real `Toolbar`: put a view's title and groups on `NavStackView.toolbar` (`title`, `leading`, `center`, `trailing`), where its Toolbar configuration overrides stack-wide `toolbarConfig` defaults; configure `backIcon` / `backText` through the stack, never with CSS. Each view and the stack fallback use a structured `bottomToolbar` with an accessible `label`; a view can also provide `header` / `footer`, `headerList` / `footerList`, and `pane` configuration, which render a Pane around that view's scrolling content. `SplitView` forwards both of its inner components: `resizable` takes the list `ResizableRegion`'s `separator`, `handleIcon`, `contentOverflow`, and collapse props (`collapsed`, `transitioning`, `collapseMotion`, `restoreControl`, `restorePosition`), and `compactStack` takes the compact stack's `toolbarConfig`, `backIcon`, `backText`, `hideToolbar`, `bottomToolbar`, plus `list` / `detail` view toolbars (structured `toolbar` and `bottomToolbar`). Complex tool with peripheral panels → `Workbench` (`@kerfjs/ui/workbench`, collapsible rails + drawer) `atLeast('desktop')`, degrading to `NavStack`/overlays below. Workbench panels are fixed-size unless a panel opts in with `resizable` (`true` or `{ min, max }`); then render `size={sizeSignal.value}` and call `wireWorkbench(root, { id, panels: { leftRail: { size: sizeSignal, storageKey? } }, deviceClass })` from `@kerfjs/ui/wire-workbench` for pointer drags, arrow/Shift+arrow/Home/End on the separator, persistence, and no resizing on `compact` classes. Inline rails, fixed or resizable, leave the work area `mainMinSize` px of width (a `Workbench` prop, default 320, `0` off), and an inline bottom drawer leaves it `mainMinHeight` px of height (default 120, `0` off): resizable panels stop growing there, rails shrink in proportion when the Workbench narrows and the drawer when it gets shorter, so do not add your own main-area `min-width` / `min-height`. Rails present as overlays below the `narrow` Workbench container breakpoint (704px) by default — `responsiveOverlayAt: "compact"` moves that to 448px and `"never"` keeps a rail inline — and a bottom drawer opts in the same way; do not derive `presentation` from `deviceClass`, and overlays never resize. At compact widths an overlay rail fills the Workbench less a 44px dismiss strip (`--kui-workbench-overlay-dismiss-margin`) on the side away from its edge; `compactOverlay: "full"` makes one rail fill it. Pass each such panel's `collapsed` signal to `wireWorkbench` too (`panels: { leftRail: { collapsed: navCollapsed } }`, `size` optional) so the overlay is transient like `wireSidebar`'s compact overlay: it collapses when the breakpoint begins to apply (the inline state returns when it ends), takes focus on its first control and keeps Tab inside it while open, and closes on Escape or an outside press with focus returned to its toggle (give each toggle `aria-controls` naming its panel's derived id, `<workbench id>-left-rail` / `-right-rail` / `-bottom-drawer`, so a panel open at wire-up has a focus target too); opening one overlay closes the other open overlays, so they never cover each other's controls; do not hand-roll those listeners (`dismissOverlays: false` opts out of all of it, `exclusiveOverlays: false` of exclusivity only). Collapse stays the app's `collapsed` flag and keeps the size, so an expanded panel returns at its last size. Let the Workbench compose panel toolbars: give each panel `toolbar: { label, title?, leading?, center?, trailing?, toggle: { action, name, showLabel?, hideLabel? } }` and the work area `mainToolbar` (and `mainBottomToolbar` for a drawer). Each of these toolbars takes the `Toolbar`'s configuration (`ToolbarConfig`: `dividerSides`, `centerAlign`, `responsive`, `responsiveAt`, `safeAreaEdges`); configure a composed toolbar through those props (for example `dividerSides: ""` to drop its divider), never with CSS. Configure the composed panes and chrome lists the same way: a panel's `pane` (and `CollapsiblePanel`'s `pane`) and the Workbench's `mainPane` take `PaneConfig` (`contentElement`, `contentLabel`, `separators`, `safeAreaEdges` — for example `{ contentElement: "nav", contentLabel: "Files" }` for a navigator rail), and `mainHeaderList` / `mainFooterList` take `ListConfig` (`gap`, `hAlign`, `vAlign`, `dividerSides`, `textInsets`, `controlInsets`). Open, a panel's toolbar places groups in its leading, center, and trailing zones, with the toggle last; mark a ToolbarControlGroup relocateOnCollapse to keep it available when closed (pass marked groups directly or in arrays rather than JSX fragments); closed, marked groups follow zone order into the work area before the toggle — a left rail's lead `mainToolbar`, a right rail's end it, a drawer's trail `mainBottomToolbar` or float in a corner `FloatingToolbar` — and panel-only groups stay behind. A control sits near where its effect appears: a collapsed drawer's restore belongs in the bottom toolbar or a `FloatingToolbar` beside the drawer, not in a distant top toolbar. Do not hand-place panel toggles in the work-area toolbar or render a second copy, and handle only the toggle's `data-action`. A collapsed panel renders `inert` and `aria-hidden` by itself, on the rail/drawer and its content (its `restoreControl` stays outside and reachable), so do not add your own `tabindex`, `aria-hidden`, or `inert` to hide it. These are declarative (the app owns the stack/selection/collapsed/active state as signals) with disposer-returning `wire…` helpers; each stays out of the barrel, and its CSS-aware browser import loads the layout's CSS plus that of every component it renders (`Pane`, `Toolbar`, `List`, …), so do not hand-import `pane.css` or the other internal stylesheets; the companion `.css` subpath is only for pipelines without the `browser` condition. For a standalone collapsible rail or bottom drawer outside a full shell, use `CollapsiblePanel` + `CollapsiblePanelToggle` (`@kerfjs/ui/collapsible-panel`) with `wireSidebar` (`@kerfjs/ui/wire-sidebar`): the standard collapse animation and per-side icon convention (`PanelLeft*`/`PanelRight*`/`PanelBottom*`) plus toggle, focus, compact overlay, and persistence semantics. Give a standalone panel the same `toolbar` roles and render `CollapsiblePanelRelocated` in your work-area toolbar (first in leading for a left rail, last in trailing for a right rail, a bottom toolbar or a `FloatingToolbar` restoreControl for a drawer) instead of hand-placing a second toggle. A collapsed `CollapsiblePanel`, `ResizableRegion`, or Workbench panel renders `inert` and `aria-hidden` by itself, so do not hide collapsed content yourself either. Likewise an open side overlay (a Workbench rail overlay, a `wireSidebar` compact overlay, an overlay `CollapsiblePanel` or horizontal `ResizableRegion`) hides the covered work area's `FloatingToolbar`s and restore corners by itself until it closes, so do not hide them from overlay state. A compact overlay opens only on a user action: wire-up on a compact device and a wide-to-compact crossing collapse every panel, and a wide crossing restores the inline state. Seed the signal with `signal(device.value.compact)` and pass the panel's `inlineCollapsed` default so the first compact render has no overlay. Dialogs pick the same inner layout, then present per device class (full-screen modal on `compact`, inline on desktop). A list-detail dialog is a Workbench-shaped layout on roomy devices: a full-height list sidebar and a full-height main column, each a `Pane` with its own top `Toolbar`; the main column holds the primary xl title, the close control (`data-dialog="close"` in a `without-header` Web Awesome dialog), and the record actions, never a header spanning both columns. A top-level percentage-height shell needs a definite containing height: opt into `@kerfjs/ui/document.css` and put `.kui-app-root` on the one direct mount container, or provide equivalent application-owned geometry. The global baseline is deliberately absent from `styles.css` and component imports. See `docs/app-layouts.md`, `docs/document-baseline.md`, and `docs/collapsible-panel.md`. 23. Layouts and `Pane` are safe-area aware without configuration: backgrounds, separators, and dividers paint through a device's unsafe areas (a collapsible rail or drawer grows by the inset it docks to), content is padded only on the sides a region actually reaches, and block insets are scroll padding inside the scroller so first/last content can always be scrolled clear. Interior edges get nothing, and a collapsed rail hands its edge back to the center. Never add your own `env(safe-area-inset-*)` padding around a kerf layout or pane — that double-insets. In an app-owned arrangement of panes, pass `Pane.safeAreaEdges` (the sides that pane reaches; `[]` opts out); give an app bar or bottom bar that sits at a screen edge outside a Pane header or footer `Toolbar.safeAreaEdges` for the sides it reaches (for example `['block-start', 'inline-start', 'inline-end']`); the Workbench `mainToolbar` / `mainBottomToolbar` and panel `toolbar` configurations forward the same `safeAreaEdges` to their `Toolbar`. Do not set the internal `--kui-edge-inset-*` context yourself. The device insets are `--kui-safe-area-*` (defaulting to `env()`), overridable on `:root`. See `docs/app-layouts.md` "Safe areas".
271
271
 
272
272
  For application tabs, `AppTab presentation="icon-only"` retains its accessible `name`, `labelMaxWidth` truncates the visible name, and `attention` colors it through `--kui-app-tab-attention-color`. Configure TabBar strip geometry through public `--kui-tab-bar-strip-*` tokens.
273
273
 
@@ -361,6 +361,10 @@ titles at every size; default tone retains existing identity and heading colors.
361
361
 
362
362
  Navigation transition continuity: `wireNavStack` slides the incoming view over the preserved previous view on push and the outgoing view off on pop, cross-fades both live chrome surfaces with inert snapshots, and settles/cancels an interrupted transition before starting the next. Reduced motion and duration zero settle immediately.
363
363
 
364
+ For a tab scene with drill-down, place a `NavStack` directly in `TabScaffoldTab.content`. Give its views structured `toolbar` and `pane` configuration and omit both stack and view `bottomToolbar`; the scaffold bar remains the only bottom chrome. Wire the scaffold and stack on their respective roots. See `docs/tab-scaffold.md` and the catalog's **NavStack inside a tab** example.
365
+
366
+ Use `Pane.deepInset` (also in `PaneConfig`) or `TabScaffoldTab.deepInset` for a plain scene when its scrolling content needs a 16px outer gutter. The extra 8px is applied to the scroll surface, while nested Row/List/Grid item margins stay 8px. When a tab scene is a sole Pane or NavStack, configure deep inset on that child's Pane; the tab bar and top toolbar stay full width.
367
+
364
368
  Expanded search groups accept `expandedOverflow="visible"` to anchor app-owned suggestion/date/help surfaces below their relative root; collapsed/default groups retain existing clipping. Apps own surface placement/dismissal and use `data-token-search-keep-open` or `collapsible.keepOpenOn` for focus.
365
369
 
366
370
  `ToolbarText` and `ToolbarControlGroup` accept typed `hideBelow`/`showBelow`
@@ -375,3 +379,9 @@ group. See `docs/component-contract.md` "Toolbar width visibility" for lifecycle
375
379
  details.
376
380
 
377
381
  Select menus use native viewport shifting with 10px gutters: default menus grow to content (up to 20rem), while `fitMenu` matches trigger width and long labels wrap. Keep popup sizing and position repairs out of consumer CSS.
382
+
383
+ Modal `DialogSurface` accepts a typed `preferredWidth` (`CssLength`) that overrides `size` while retaining the viewport width cap. Typed `viewportGutter` and `maxHeight` (`CssLength` or `"viewport"`) bound the modal; explicit height caps stay within the dynamic viewport minus both gutters. Omitted props preserve Web Awesome defaults. Side-sheet/fullscreen geometry, application responsive choices, and preview layout remain separately owned.
384
+
385
+ Import `@kerfjs/ui/surface-scaffold/register` once when using `DialogSurface`: it registers `wa-dialog` and repairs Web Awesome 3.12 native dialog names before the first modal opening. Headed dialogs reference the rendered title, including live slotted labels; headerless dialogs use the public `label`. Explicit host `aria-label` and pre-existing native names are preserved. Geometry-only markup stays CSS-free and does not register Web Awesome implicitly.
386
+
387
+ Toolbar visibility measurement probes and invisible state children are helper-owned and survive Kerf rerenders through `data-morph-preserve`. Actual CSS visibility stays stable while rendering removes transient parent markers, preventing geometry-driven render feedback and repeated probe recreation. Toolbar replacement, explicit internal-node removal and disposal retain their existing behavior.
@@ -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.77` 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.78` 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';
@@ -4,6 +4,8 @@ import '../styles/loading-spinner.css';
4
4
  import '../styles/toolbar-control-group.css';
5
5
  import '../styles/skeleton.css';
6
6
  import '../styles/toolbar-text.css';
7
+ import '../styles/list.css';
8
+ import '../styles/pane.css';
7
9
  import '../styles/lucide-icon.css';
8
10
  import '../styles/nav-stack.css';
9
11
  export * from '../nav-stack.js';
@@ -4,6 +4,8 @@ import '../styles/loading-spinner.css';
4
4
  import '../styles/toolbar-control-group.css';
5
5
  import '../styles/skeleton.css';
6
6
  import '../styles/toolbar-text.css';
7
+ import '../styles/list.css';
8
+ import '../styles/pane.css';
7
9
  import '../styles/lucide-icon.css';
8
10
  import '../styles/nav-stack.css';
9
11
  import '../styles/resizable-region.css';
@@ -5,15 +5,15 @@ import './semantic-content-BbzjvSu9.js';
5
5
  import './sides-BPSWde0A.js';
6
6
  import './pane.js';
7
7
  import './workbench.js';
8
+ import './nav-stack-B1Qc2e5q.js';
8
9
  import './list.js';
9
10
  import './flex-alignment-4ms8ZbV8.js';
10
11
  import './css-values.js';
11
- import './nav-stack.js';
12
- import './toolbar-text.js';
13
- import './resizable-region.js';
14
12
  import './panel-toolbar-CL9tYOkh.js';
15
13
  import './lucide-icon.js';
16
14
  import 'lucide';
15
+ import './toolbar-text.js';
16
+ import './resizable-region.js';
17
17
 
18
18
  /**
19
19
  * Standard resource labels for a Kerf catalog detail footer. Keep these labels
package/dist/catalog.d.ts CHANGED
@@ -7,15 +7,15 @@ import './toolbar.js';
7
7
  import './sides-BPSWde0A.js';
8
8
  import './pane.js';
9
9
  import './workbench.js';
10
+ import './nav-stack-B1Qc2e5q.js';
10
11
  import './list.js';
11
12
  import './flex-alignment-4ms8ZbV8.js';
12
13
  import './css-values.js';
13
- import './nav-stack.js';
14
- import './toolbar-text.js';
15
- import './resizable-region.js';
16
14
  import './panel-toolbar-CL9tYOkh.js';
17
15
  import './lucide-icon.js';
18
16
  import 'lucide';
17
+ import './toolbar-text.js';
18
+ import './resizable-region.js';
19
19
 
20
20
  /**
21
21
  * Controlled, stateless component-catalog shell: a `Workbench` whose left rail
package/dist/catalog.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Workbench } from './chunk-6VQPLV3O.js';
1
+ import { Workbench } from './chunk-DSLRNR43.js';
2
2
 
3
3
 
4
4
  import { PopupMenu } from './chunk-NJZMR7HM.js';
@@ -15,11 +15,11 @@ import { ToolbarText } from './chunk-VHXHLZ6R.js';
15
15
 
16
16
 
17
17
 
18
+ import { ToolbarControlGroup, ToolbarActionLink } from './chunk-RRCD3A6G.js';
19
+ import { Toolbar } from './chunk-BYHGRKC3.js';
18
20
 
19
21
  import { List } from './chunk-NVRJ7C3W.js';
20
22
 
21
- import { ToolbarControlGroup, ToolbarActionLink } from './chunk-RRCD3A6G.js';
22
- import { Toolbar } from './chunk-BYHGRKC3.js';
23
23
  import { LucideIcon } from './chunk-PQQQXJGV.js';
24
24
 
25
25
  import { filterDataAttributes } from './chunk-LAZLBC2Q.js';
@@ -3,6 +3,7 @@ var HIDE_BELOW = "data-hide-below";
3
3
  var SHOW_BELOW = "data-show-below";
4
4
  var HIDDEN = "data-toolbar-width-hidden";
5
5
  var PROBE = "data-toolbar-visibility-probe";
6
+ var STATE = "data-toolbar-visibility-state";
6
7
  var LENGTH = "--kui-toolbar-visibility-length";
7
8
  var ITEM = `[data-component="toolbar-control-group"][${HIDE_BELOW}], [data-component="toolbar-control-group"][${SHOW_BELOW}], [data-component="toolbar-text"][${HIDE_BELOW}], [data-component="toolbar-text"][${SHOW_BELOW}]`;
8
9
  var controllers = /* @__PURE__ */ new WeakMap();
@@ -36,10 +37,21 @@ function createController(document) {
36
37
  function write(element, hidden) {
37
38
  let previous = written.get(element);
38
39
  if (!previous) {
39
- previous = { original: element.getAttribute(HIDDEN), value: null };
40
+ const state = document.createElement("span");
41
+ state.setAttribute(STATE, "");
42
+ state.setAttribute("data-morph-preserve", "");
43
+ state.setAttribute("aria-hidden", "true");
44
+ state.hidden = true;
45
+ previous = { original: element.getAttribute(HIDDEN), value: null, state };
40
46
  written.set(element, previous);
41
47
  }
48
+ if (previous.state.parentElement !== element)
49
+ element.append(previous.state);
42
50
  const value = hidden ? "true" : null;
51
+ if (previous.state.getAttribute(HIDDEN) !== value) {
52
+ if (value === null) previous.state.removeAttribute(HIDDEN);
53
+ else previous.state.setAttribute(HIDDEN, value);
54
+ }
43
55
  if (element.getAttribute(HIDDEN) !== value) {
44
56
  if (value === null) element.removeAttribute(HIDDEN);
45
57
  else element.setAttribute(HIDDEN, value);
@@ -48,6 +60,7 @@ function createController(document) {
48
60
  }
49
61
  function restore(element) {
50
62
  const previous = written.get(element);
63
+ previous.state.remove();
51
64
  if (element.getAttribute(HIDDEN) === previous.value) {
52
65
  if (previous.original === null) element.removeAttribute(HIDDEN);
53
66
  else element.setAttribute(HIDDEN, previous.original);
@@ -68,6 +81,7 @@ function createController(document) {
68
81
  function createMeasurement(toolbar) {
69
82
  const box = document.createElement("div");
70
83
  box.setAttribute(PROBE, "");
84
+ box.setAttribute("data-morph-preserve", "");
71
85
  box.setAttribute("aria-hidden", "true");
72
86
  const hide = document.createElement("div");
73
87
  const show = document.createElement("div");
@@ -184,10 +198,10 @@ function createController(document) {
184
198
  }
185
199
  const resizeObserver = view?.ResizeObserver ? new view.ResizeObserver(refresh) : null;
186
200
  const mutationObserver = view?.MutationObserver ? new view.MutationObserver((records) => {
187
- const internal = (node) => node.nodeType === 1 && (node.hasAttribute(PROBE) || Boolean(node.closest(`[${PROBE}]`)));
201
+ const internal = (node) => node.nodeType === 1 && (node.hasAttribute(PROBE) || node.hasAttribute(STATE) || Boolean(node.closest(`[${PROBE}], [${STATE}]`)));
188
202
  if (records.some(
189
203
  (record) => record.type === "childList" && [...record.removedNodes].some(
190
- (node) => node.nodeType === 1 && node.hasAttribute(PROBE)
204
+ (node) => node.nodeType === 1 && (node.hasAttribute(PROBE) || node.hasAttribute(STATE))
191
205
  ) || !internal(record.target) && (record.type !== "childList" || [...record.addedNodes, ...record.removedNodes].some(
192
206
  (node) => !internal(node)
193
207
  ))
@@ -253,4 +267,4 @@ function createController(document) {
253
267
 
254
268
  export { wireToolbarVisibility };
255
269
 
256
- //# sourceMappingURL=chunk-WJN3V2H5.js.map
270
+ //# sourceMappingURL=chunk-2GOCOLQL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/wiring/wire-toolbar-visibility.ts"],"names":[],"mappings":"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","file":"chunk-2GOCOLQL.js","sourcesContent":["type Root = HTMLElement | Document;\n\nconst HIDE_BELOW = 'data-hide-below';\nconst SHOW_BELOW = 'data-show-below';\nconst HIDDEN = 'data-toolbar-width-hidden';\nconst PROBE = 'data-toolbar-visibility-probe';\nconst STATE = 'data-toolbar-visibility-state';\nconst LENGTH = '--kui-toolbar-visibility-length';\nconst ITEM = `[data-component=\"toolbar-control-group\"][${HIDE_BELOW}], [data-component=\"toolbar-control-group\"][${SHOW_BELOW}], [data-component=\"toolbar-text\"][${HIDE_BELOW}], [data-component=\"toolbar-text\"][${SHOW_BELOW}]`;\n\ninterface Measurement {\n toolbar: HTMLElement;\n box: HTMLDivElement;\n hide: HTMLDivElement;\n show: HTMLDivElement;\n}\n\ninterface WrittenAttribute {\n original: string | null;\n value: string | null;\n state: HTMLSpanElement;\n}\n\nconst controllers = new WeakMap<Document, (root: Root) => () => void>();\n\n/**\n * Wire ToolbarText and ToolbarControlGroup width thresholds below root.\n * Below is strict: hideBelow hides at width < threshold; showBelow hides at\n * width >= threshold. Percentages use the closest Toolbar's content box;\n * em and custom properties use the item's own inherited context. Invalid,\n * negative, unresolved, or contradictory thresholds leave the item visible.\n * This helper changes only a component-owned visibility marker. Existing\n * CSS visibility policies still apply. Dispose with the owning application.\n */\nexport function wireToolbarVisibility(root: Root): () => void {\n const document =\n root.nodeType === 9 ? (root as Document) : root.ownerDocument!;\n let register = controllers.get(document);\n if (!register) {\n register = createController(document);\n controllers.set(document, register);\n }\n return register(root);\n}\n\nfunction createController(document: Document) {\n const view = document.defaultView;\n const registrations = new Set<{ root: Root }>();\n const measurements = new Map<HTMLElement, Measurement>();\n const written = new Map<HTMLElement, WrittenAttribute>();\n const observed = new Set<Element>();\n let disposed = false;\n let refreshing = false;\n const normalizedStyle = document.createElement('div').style;\n\n function setStyle(element: HTMLElement, property: string, value: string) {\n // CSSOM can normalize computed font/token values on assignment. Compare\n // that serialization, preserving meaningful blank custom-property tokens.\n normalizedStyle.removeProperty(property);\n normalizedStyle.setProperty(property, value);\n const normalized = normalizedStyle.getPropertyValue(property);\n if (\n element.style.getPropertyValue(property) === normalized &&\n element.style.getPropertyPriority(property) === ''\n )\n return;\n if (normalized === '') element.style.removeProperty(property);\n else element.style.setProperty(property, normalized);\n }\n\n function write(element: HTMLElement, hidden: boolean) {\n let previous = written.get(element);\n if (!previous) {\n const state = document.createElement('span');\n state.setAttribute(STATE, '');\n state.setAttribute('data-morph-preserve', '');\n state.setAttribute('aria-hidden', 'true');\n state.hidden = true;\n previous = { original: element.getAttribute(HIDDEN), value: null, state };\n written.set(element, previous);\n }\n if (previous.state.parentElement !== element)\n element.append(previous.state);\n const value = hidden ? 'true' : null;\n if (previous.state.getAttribute(HIDDEN) !== value) {\n if (value === null) previous.state.removeAttribute(HIDDEN);\n else previous.state.setAttribute(HIDDEN, value);\n }\n if (element.getAttribute(HIDDEN) !== value) {\n if (value === null) element.removeAttribute(HIDDEN);\n else element.setAttribute(HIDDEN, value);\n }\n previous.value = value;\n }\n\n function restore(element: HTMLElement) {\n const previous = written.get(element)!;\n previous.state.remove();\n if (element.getAttribute(HIDDEN) === previous.value) {\n if (previous.original === null) element.removeAttribute(HIDDEN);\n else element.setAttribute(HIDDEN, previous.original);\n }\n written.delete(element);\n }\n\n function contentWidth(toolbar: HTMLElement) {\n const style = view!.getComputedStyle(toolbar);\n const inset = (property: string) =>\n parseFloat(style.getPropertyValue(property)) || 0;\n const padding = inset('padding-left') + inset('padding-right');\n const border = inset('border-left-width') + inset('border-right-width');\n const width = parseFloat(style.width);\n return Math.max(\n 0,\n Number.isFinite(width)\n ? width - (style.boxSizing === 'border-box' ? padding + border : 0)\n : toolbar.getBoundingClientRect().width - padding - border,\n );\n }\n\n function createMeasurement(toolbar: HTMLElement): Measurement {\n const box = document.createElement('div');\n box.setAttribute(PROBE, '');\n // Kerf's render template never owns these injected measurements. Keep\n // them across morphs instead of recreating probes and observers each pass.\n box.setAttribute('data-morph-preserve', '');\n box.setAttribute('aria-hidden', 'true');\n // An out-of-flow clipped box never participates in the toolbar's grid,\n // intrinsic size or scroll overflow. Its explicit width defines % lengths.\n const hide = document.createElement('div');\n const show = document.createElement('div');\n box.append(hide, show);\n toolbar.append(box);\n return { toolbar, box, hide, show };\n }\n\n function resolveLength(\n probe: HTMLElement,\n value: string | null,\n ): number | null {\n if (value === null) return null;\n // The typed private property uses -1px as its invalid/unresolved sentinel.\n // Absolute left's used-value CSSOM alone turns invalid auto into 0px.\n setStyle(probe, LENGTH, value);\n const left = view!.getComputedStyle(probe).left;\n if (!/^-?(?:\\d+(?:\\.\\d+)?|\\.\\d+)px$/.test(left)) return null;\n const pixels = parseFloat(left);\n return Number.isFinite(pixels) && pixels >= 0 ? pixels : null;\n }\n\n function copyContext(\n item: HTMLElement,\n measurement: Measurement,\n width: number,\n ) {\n const style = view!.getComputedStyle(item);\n const { box } = measurement;\n setStyle(box, 'width', `${width}px`);\n for (const property of [\n 'font-size',\n 'font-family',\n 'font-weight',\n 'font-style',\n 'font-stretch',\n 'font-variant',\n 'font-feature-settings',\n 'font-variation-settings',\n 'font-size-adjust',\n 'font-kerning',\n 'line-height',\n ])\n setStyle(box, property, style.getPropertyValue(property));\n // The box remains outside a hidden item, but resolves tokens from that\n // item, including tokens declared on its root rather than the toolbar.\n const properties = new Set<string>();\n for (let index = 0; index < style.length; index++) {\n const name = style.item(index);\n if (!name.startsWith('--')) continue;\n properties.add(name);\n setStyle(box, name, style.getPropertyValue(name));\n }\n for (let index = box.style.length - 1; index >= 0; index--) {\n const name = box.style.item(index);\n if (name.startsWith('--') && !properties.has(name))\n box.style.removeProperty(name);\n }\n }\n\n function refresh() {\n if (disposed || refreshing || !view) return;\n refreshing = true;\n try {\n const items = new Set<HTMLElement>();\n for (const { root } of registrations) {\n root\n .querySelectorAll<HTMLElement>(ITEM)\n .forEach((item) => items.add(item));\n if (root.nodeType === 1 && (root as HTMLElement).matches(ITEM))\n items.add(root as HTMLElement);\n }\n const wanted = new Set<Element>();\n const marked = new Set<HTMLElement>();\n for (const item of items) {\n const toolbar = item.closest<HTMLElement>('[data-component=\"toolbar\"]');\n if (!toolbar) continue;\n let measurement = measurements.get(item);\n if (\n measurement?.toolbar !== toolbar ||\n measurement.box.parentElement !== toolbar\n ) {\n measurement?.box.remove();\n measurement = createMeasurement(toolbar);\n measurements.set(item, measurement);\n }\n const width = contentWidth(toolbar);\n copyContext(item, measurement, width);\n const hideValue = item.getAttribute(HIDE_BELOW);\n const showValue = item.getAttribute(SHOW_BELOW);\n const hide = resolveLength(measurement.hide, hideValue);\n const show = resolveLength(measurement.show, showValue);\n const valid =\n (hideValue === null || hide !== null) &&\n (showValue === null || show !== null) &&\n !(hide !== null && show !== null && hide >= show);\n const hidden =\n valid &&\n ((hide !== null && width < hide) || (show !== null && width >= show));\n write(item, hidden);\n marked.add(item);\n const status = item.nextElementSibling;\n if (\n status instanceof view.HTMLElement &&\n item.matches(\n '[data-component=\"toolbar-control-group\"][data-busy=\"true\"]',\n ) &&\n status.matches(\n '.kui-toolbar-control-group__busy-status[role=\"status\"]',\n )\n ) {\n write(\n status,\n hidden || view.getComputedStyle(item).display === 'none',\n );\n marked.add(status);\n }\n wanted.add(toolbar);\n wanted.add(measurement.hide);\n wanted.add(measurement.show);\n }\n for (const [item, measurement] of measurements)\n if (\n !items.has(item) ||\n item.closest('[data-component=\"toolbar\"]') !== measurement.toolbar\n ) {\n measurement.box.remove();\n measurements.delete(item);\n }\n for (const element of written.keys())\n if (!marked.has(element)) restore(element);\n for (const element of observed)\n if (!wanted.has(element)) {\n resizeObserver?.unobserve(element);\n observed.delete(element);\n }\n for (const element of wanted)\n if (!observed.has(element)) {\n resizeObserver?.observe(element);\n observed.add(element);\n }\n } finally {\n refreshing = false;\n }\n }\n\n const resizeObserver = view?.ResizeObserver\n ? new view.ResizeObserver(refresh)\n : null;\n const mutationObserver = view?.MutationObserver\n ? new view.MutationObserver((records) => {\n const internal = (node: Node) =>\n node.nodeType === 1 &&\n ((node as Element).hasAttribute(PROBE) ||\n (node as Element).hasAttribute(STATE) ||\n Boolean((node as Element).closest(`[${PROBE}], [${STATE}]`)));\n if (\n records.some(\n (record) =>\n (record.type === 'childList' &&\n [...record.removedNodes].some(\n (node) =>\n node.nodeType === 1 &&\n ((node as Element).hasAttribute(PROBE) ||\n (node as Element).hasAttribute(STATE)),\n )) ||\n (!internal(record.target) &&\n (record.type !== 'childList' ||\n [...record.addedNodes, ...record.removedNodes].some(\n (node) => !internal(node),\n ))),\n )\n )\n refresh();\n })\n : null;\n // Ancestor theme/font changes must refresh even outside the wired subtree.\n mutationObserver?.observe(document.documentElement, {\n subtree: true,\n childList: true,\n attributes: true,\n attributeFilter: [\n HIDE_BELOW,\n SHOW_BELOW,\n HIDDEN,\n 'class',\n 'style',\n 'data-component',\n 'data-busy',\n 'data-expanded',\n 'data-visibility',\n 'data-size',\n 'data-tone',\n ],\n });\n // Preference media can change inherited tokens at a fixed viewport width.\n const media = [\n '(prefers-color-scheme: dark)',\n '(prefers-contrast: more)',\n '(prefers-contrast: less)',\n '(forced-colors: active)',\n '(prefers-reduced-motion: reduce)',\n '(pointer: coarse)',\n '(hover: hover)',\n ].flatMap((query) => (view?.matchMedia ? [view.matchMedia(query)] : []));\n for (const preference of media)\n preference.addEventListener('change', refresh);\n document.fonts?.addEventListener('loadingdone', refresh);\n view?.addEventListener('resize', refresh);\n\n return (root: Root) => {\n const registration = { root };\n registrations.add(registration);\n refresh();\n return () => {\n if (!registrations.delete(registration)) return;\n if (registrations.size) refresh();\n else {\n disposed = true;\n mutationObserver?.disconnect();\n resizeObserver?.disconnect();\n for (const preference of media)\n preference.removeEventListener('change', refresh);\n document.fonts?.removeEventListener('loadingdone', refresh);\n view?.removeEventListener('resize', refresh);\n for (const measurement of measurements.values())\n measurement.box.remove();\n for (const element of written.keys()) restore(element);\n measurements.clear();\n observed.clear();\n controllers.delete(document);\n }\n };\n };\n}\n"]}