@kerfjs/ui 5.0.0-beta.82 → 5.0.0-beta.84

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 (153) hide show
  1. package/README.md +1 -0
  2. package/ai/catalog-authoring.json +1 -3
  3. package/ai/catalog-authoring.schema.json +3 -7
  4. package/ai/component-catalog.json +73 -2
  5. package/ai/component-composition.json +64 -1
  6. package/ai/components/README.md +1 -0
  7. package/ai/components/anchored-surface.md +86 -0
  8. package/ai/components/tab-navigator.md +3 -2
  9. package/ai/public-api-signatures-v1.md +79 -35
  10. package/ai/skill.md +2 -2
  11. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  12. package/dist/anchored-surface.d.ts +30 -0
  13. package/dist/anchored-surface.js +3 -0
  14. package/dist/anchored-surface.js.map +1 -0
  15. package/dist/app-tab.js +1 -1
  16. package/dist/browser/anchored-surface.js +3 -0
  17. package/dist/browser/catalog.js +1 -1
  18. package/dist/catalog-resources.d.ts +2 -2
  19. package/dist/catalog.d.ts +4 -4
  20. package/dist/catalog.js +34 -31
  21. package/dist/catalog.js.map +1 -1
  22. package/dist/{chunk-I4SDIMFA.js → chunk-2YPPNFYT.js} +2 -2
  23. package/dist/{chunk-I4SDIMFA.js.map → chunk-2YPPNFYT.js.map} +1 -1
  24. package/dist/{chunk-VHXHLZ6R.js → chunk-3DXHNLME.js} +3 -3
  25. package/dist/{chunk-VHXHLZ6R.js.map → chunk-3DXHNLME.js.map} +1 -1
  26. package/dist/{chunk-HFQDSSED.js → chunk-3PO7JEK7.js} +4 -4
  27. package/dist/{chunk-HFQDSSED.js.map → chunk-3PO7JEK7.js.map} +1 -1
  28. package/dist/chunk-6IVNMC6R.js +19 -0
  29. package/dist/chunk-6IVNMC6R.js.map +1 -0
  30. package/dist/{chunk-VR7MWD2X.js → chunk-CD5TOO22.js} +4 -13
  31. package/dist/chunk-CD5TOO22.js.map +1 -0
  32. package/dist/{chunk-H7ZJEI3H.js → chunk-DL55BSQX.js} +3 -3
  33. package/dist/{chunk-H7ZJEI3H.js.map → chunk-DL55BSQX.js.map} +1 -1
  34. package/dist/{chunk-VIGVNDXZ.js → chunk-GPCAXYMT.js} +4 -4
  35. package/dist/{chunk-VIGVNDXZ.js.map → chunk-GPCAXYMT.js.map} +1 -1
  36. package/dist/chunk-IOS2NXW7.js +53 -0
  37. package/dist/chunk-IOS2NXW7.js.map +1 -0
  38. package/dist/{chunk-HFLGWMG3.js → chunk-KAJVVN7Q.js} +3 -3
  39. package/dist/{chunk-HFLGWMG3.js.map → chunk-KAJVVN7Q.js.map} +1 -1
  40. package/dist/{chunk-LQKY5S4F.js → chunk-KL3W6FUN.js} +4 -4
  41. package/dist/{chunk-LQKY5S4F.js.map → chunk-KL3W6FUN.js.map} +1 -1
  42. package/dist/{chunk-NVKRZ5DI.js → chunk-LFPW4YC6.js} +4 -4
  43. package/dist/{chunk-NVKRZ5DI.js.map → chunk-LFPW4YC6.js.map} +1 -1
  44. package/dist/{chunk-75I3SNUJ.js → chunk-MN3POJFN.js} +2 -2
  45. package/dist/{chunk-75I3SNUJ.js.map → chunk-MN3POJFN.js.map} +1 -1
  46. package/dist/{chunk-5D3WDMIK.js → chunk-MORVLMDG.js} +4 -16
  47. package/dist/chunk-MORVLMDG.js.map +1 -0
  48. package/dist/{chunk-RRCD3A6G.js → chunk-MTCTX25G.js} +2 -2
  49. package/dist/{chunk-RRCD3A6G.js.map → chunk-MTCTX25G.js.map} +1 -1
  50. package/dist/{chunk-PQQQXJGV.js → chunk-NP3FSLNX.js} +2 -2
  51. package/dist/{chunk-PQQQXJGV.js.map → chunk-NP3FSLNX.js.map} +1 -1
  52. package/dist/{chunk-ARZ4RSQO.js → chunk-PGL6TXFW.js} +3 -3
  53. package/dist/{chunk-ARZ4RSQO.js.map → chunk-PGL6TXFW.js.map} +1 -1
  54. package/dist/{chunk-NVRJ7C3W.js → chunk-QQICWWNX.js} +4 -16
  55. package/dist/chunk-QQICWWNX.js.map +1 -0
  56. package/dist/{chunk-CVKIADIS.js → chunk-SJW5BCEQ.js} +7 -5
  57. package/dist/chunk-SJW5BCEQ.js.map +1 -0
  58. package/dist/{chunk-JZM3G6RT.js → chunk-TTGIHMDR.js} +4 -4
  59. package/dist/{chunk-JZM3G6RT.js.map → chunk-TTGIHMDR.js.map} +1 -1
  60. package/dist/{chunk-LRAIS3YB.js → chunk-U657IHFB.js} +1 -1
  61. package/dist/chunk-U657IHFB.js.map +1 -0
  62. package/dist/{chunk-3SXWV2IR.js → chunk-VLGQIYZ5.js} +2 -2
  63. package/dist/{chunk-3SXWV2IR.js.map → chunk-VLGQIYZ5.js.map} +1 -1
  64. package/dist/{chunk-YLJ6X3AC.js → chunk-XK4E225T.js} +2 -2
  65. package/dist/{chunk-YLJ6X3AC.js.map → chunk-XK4E225T.js.map} +1 -1
  66. package/dist/{chunk-BJLXE56E.js → chunk-XKZPBRU7.js} +4 -4
  67. package/dist/{chunk-BJLXE56E.js.map → chunk-XKZPBRU7.js.map} +1 -1
  68. package/dist/{chunk-62VMNBV2.js → chunk-XNXKIPU5.js} +2 -2
  69. package/dist/{chunk-62VMNBV2.js.map → chunk-XNXKIPU5.js.map} +1 -1
  70. package/dist/{chunk-EFDCALR3.js → chunk-Y43B6DS3.js} +2 -2
  71. package/dist/{chunk-EFDCALR3.js.map → chunk-Y43B6DS3.js.map} +1 -1
  72. package/dist/{chunk-3RE2JMZU.js → chunk-YOKEL75M.js} +2 -2
  73. package/dist/{chunk-3RE2JMZU.js.map → chunk-YOKEL75M.js.map} +1 -1
  74. package/dist/collapsible-panel.js +4 -3
  75. package/dist/collapsible-panel.js.map +1 -1
  76. package/dist/css-values.d.ts +8 -1
  77. package/dist/css-values.js +1 -1
  78. package/dist/disclosure-arrow.js +1 -1
  79. package/dist/empty-state.js +1 -1
  80. package/dist/grid.d.ts +5 -4
  81. package/dist/grid.js +2 -1
  82. package/dist/index.d.ts +2 -0
  83. package/dist/index.js +26 -24
  84. package/dist/list-action-row.js +1 -1
  85. package/dist/list-header.js +1 -1
  86. package/dist/list-item.js +1 -1
  87. package/dist/list.d.ts +5 -4
  88. package/dist/list.js +2 -1
  89. package/dist/loading-spinner.js +1 -1
  90. package/dist/lucide-icon.js +1 -1
  91. package/dist/{nav-stack-B1Qc2e5q.d.ts → nav-stack-BvSKY448.d.ts} +10 -2
  92. package/dist/nav-stack.d.ts +1 -1
  93. package/dist/nav-stack.js +2 -1
  94. package/dist/row.d.ts +5 -4
  95. package/dist/row.js +2 -1
  96. package/dist/select.js +1 -1
  97. package/dist/skeleton.js +1 -1
  98. package/dist/spacer.js +1 -1
  99. package/dist/split-view.d.ts +1 -1
  100. package/dist/split-view.js +2 -1
  101. package/dist/split-view.js.map +1 -1
  102. package/dist/state-banner.js +1 -1
  103. package/dist/styles/anchored-surface.css +15 -0
  104. package/dist/styles/catalog/components/catalog-example.css +1 -2
  105. package/dist/styles/catalog/components/catalog-stage.css +40 -42
  106. package/dist/styles/catalog/components/catalog.css +0 -11
  107. package/dist/styles/components/collections/list/list.css +4 -1
  108. package/dist/styles/components/feedback/anchored-surface/anchored-surface.css +15 -0
  109. package/dist/styles/components/layout/grid/grid.css +4 -1
  110. package/dist/styles/components/layout/pane/pane.css +15 -0
  111. package/dist/styles/components/layout/row/row.css +4 -1
  112. package/dist/styles/components/layout/workbench/workbench.css +17 -0
  113. package/dist/styles/components/navigation/nav-stack/nav-stack.css +21 -5
  114. package/dist/styles/components/navigation/tab-navigator/tab-navigator.css +25 -9
  115. package/dist/styles/grid.css +4 -1
  116. package/dist/styles/list.css +4 -1
  117. package/dist/styles/nav-stack.css +21 -5
  118. package/dist/styles/pane.css +15 -0
  119. package/dist/styles/row.css +4 -1
  120. package/dist/styles/styles.css +1 -0
  121. package/dist/styles/tab-navigator.css +25 -9
  122. package/dist/styles/workbench.css +17 -0
  123. package/dist/tab-navigator.d.ts +10 -3
  124. package/dist/tab-navigator.js +2 -0
  125. package/dist/tab-navigator.js.map +1 -1
  126. package/dist/token-search-field.js +1 -1
  127. package/dist/toolbar-control-group.js +1 -1
  128. package/dist/toolbar-text.js +1 -1
  129. package/dist/{types-C9BubXfz.d.ts → types-Bn-dXLcs.d.ts} +6 -2
  130. package/dist/value-table.js +1 -1
  131. package/dist/wire-catalog.d.ts +1 -9
  132. package/dist/wire-catalog.js +2 -200
  133. package/dist/wire-catalog.js.map +1 -1
  134. package/dist/wire-token-search-fields.js +1 -1
  135. package/dist/workbench.d.ts +1 -1
  136. package/dist/workbench.js +2 -1
  137. package/docs/anchored-surface.md +51 -0
  138. package/docs/catalog.md +38 -82
  139. package/docs/component-contract.md +3 -1
  140. package/docs/component-selection.md +19 -1
  141. package/docs/layout.md +19 -2
  142. package/docs/nav-stack.md +13 -4
  143. package/docs/tab-navigator.md +12 -4
  144. package/docs/type-contracts.md +2 -0
  145. package/docs/ux-demo.md +4 -4
  146. package/docs/workbench.md +2 -0
  147. package/llms.txt +8 -3
  148. package/package.json +7 -1
  149. package/dist/chunk-5D3WDMIK.js.map +0 -1
  150. package/dist/chunk-CVKIADIS.js.map +0 -1
  151. package/dist/chunk-LRAIS3YB.js.map +0 -1
  152. package/dist/chunk-NVRJ7C3W.js.map +0 -1
  153. package/dist/chunk-VR7MWD2X.js.map +0 -1
package/README.md CHANGED
@@ -218,6 +218,7 @@ interchangeable. `ListItem` and `ListActionRow` deliberately expose no raw
218
218
  | `DialogSurface`, `PopupSurface` | `@kerfjs/ui/surface-scaffold` | `@kerfjs/ui/surface-scaffold.css` |
219
219
  | `FloatingToolbar` | `@kerfjs/ui/floating-toolbar` | `@kerfjs/ui/floating-toolbar.css` |
220
220
  | `PopupMenu` (register with `@kerfjs/ui/popup-menu/register`) | `@kerfjs/ui/popup-menu` | `@kerfjs/ui/popup-menu.css` |
221
+ | `openAnchoredSurface`, `openAnchoredSurfaceAt` | `@kerfjs/ui/anchored-surface` | `@kerfjs/ui/anchored-surface.css` |
221
222
  | `ToolbarText` | `@kerfjs/ui/toolbar-text` | `@kerfjs/ui/toolbar-text.css` |
222
223
  | `Text`, `FieldLabel` | `@kerfjs/ui/text` | `@kerfjs/ui/text.css` |
223
224
  | `List` | `@kerfjs/ui/list` | `@kerfjs/ui/list.css` |
@@ -14,7 +14,6 @@
14
14
  "CatalogExample",
15
15
  "CatalogExampleStack",
16
16
  "wireCatalog",
17
- "wireCatalogGeometryOverlay",
18
17
  "revealCatalogEntry"
19
18
  ],
20
19
  "taxonomy": {
@@ -30,7 +29,6 @@
30
29
  },
31
30
  "metadata": {
32
31
  "slot": "rootAttributes",
33
- "demo": "data-demo",
34
- "geometryOverlaySkip": "data-catalog-geometry-overlay-skip"
32
+ "demo": "data-demo"
35
33
  }
36
34
  }
@@ -38,11 +38,10 @@
38
38
  "CatalogExample",
39
39
  "CatalogExampleStack",
40
40
  "wireCatalog",
41
- "wireCatalogGeometryOverlay",
42
41
  "revealCatalogEntry"
43
42
  ]
44
43
  },
45
- "minItems": 6,
44
+ "minItems": 5,
46
45
  "uniqueItems": true
47
46
  },
48
47
  "taxonomy": {
@@ -94,13 +93,10 @@
94
93
  },
95
94
  "metadata": {
96
95
  "type": "object",
97
- "required": ["slot", "demo", "geometryOverlaySkip"],
96
+ "required": ["slot", "demo"],
98
97
  "properties": {
99
98
  "slot": { "const": "rootAttributes" },
100
- "demo": { "const": "data-demo" },
101
- "geometryOverlaySkip": {
102
- "const": "data-catalog-geometry-overlay-skip"
103
- }
99
+ "demo": { "const": "data-demo" }
104
100
  },
105
101
  "additionalProperties": false
106
102
  }
@@ -1059,6 +1059,7 @@
1059
1059
  "routing and persistence",
1060
1060
  "responsive replacement on larger device classes",
1061
1061
  "whether the bar divider follows the scroll, always shows, or never shows (chromeDividers)",
1062
+ "whether the bar's top separator follows a sunken active surface or stays hidden (separator)",
1062
1063
  "optional active-scene focus (tabIndex) and persistent outline (outlined)"
1063
1064
  ],
1064
1065
  "variants": [
@@ -1067,7 +1068,8 @@
1067
1068
  "badged tabs (count or text-free dot)",
1068
1069
  "one NavStack per tab",
1069
1070
  "bar divider only while the active scene continues below it (wired by wireScrollDividers)",
1070
- "permanent or no bar divider (chromeDividers always or none)"
1071
+ "permanent or no bar divider (chromeDividers always or none)",
1072
+ "sunken active-scene separator (separator auto or hidden)"
1071
1073
  ],
1072
1074
  "accessibility": [
1073
1075
  "Give the tablist an accessible label and keep exactly the active tab in the sequential focus order.",
@@ -1081,7 +1083,7 @@
1081
1083
  "padding": "self",
1082
1084
  "notes": [
1083
1085
  "The navigator owns the full-height scene region and safe-area-aware bottom bar; scene content owns its internal geometry.",
1084
- "The bar's top line is scroll state (wireScrollDividers): it shows only while the active scene's content continues below the bar, by coloring the bar's always-present 1px top border."
1086
+ "The bar's reserved 1px top edge separates a sunken active scene or nested Pane by default; on flat surfaces it follows scroll state (wireScrollDividers). separator=hidden suppresses it."
1085
1087
  ]
1086
1088
  },
1087
1089
  "delivery": {
@@ -2568,6 +2570,7 @@
2568
2570
  "rawPolicy": "forbid",
2569
2571
  "examples": [
2570
2572
  "gap=\"xs\"",
2573
+ "gap={{ column: \"none\", row: \"m\" }}",
2571
2574
  "gap={space(\"m\")}",
2572
2575
  "gap={calc(plus(rem(0.25), pct(10)))}"
2573
2576
  ]
@@ -2744,6 +2747,7 @@
2744
2747
  "rawPolicy": "forbid",
2745
2748
  "examples": [
2746
2749
  "gap=\"m\"",
2750
+ "gap={{ column: \"none\", row: \"m\" }}",
2747
2751
  "gap={space(\"m\")}",
2748
2752
  "gap={calc(plus(rem(0.25), pct(10)))}"
2749
2753
  ]
@@ -3087,6 +3091,7 @@
3087
3091
  "rawPolicy": "forbid",
3088
3092
  "examples": [
3089
3093
  "gap=\"xs\"",
3094
+ "gap={{ column: \"none\", row: \"m\" }}",
3090
3095
  "gap={rem(0.25)}",
3091
3096
  "gap={calc(plus(rem(0.25), pct(10)))}"
3092
3097
  ]
@@ -4657,6 +4662,72 @@
4657
4662
  "recipe": "docs/component-selection.md#web-awesome-overlap-policy"
4658
4663
  }
4659
4664
  },
4665
+ {
4666
+ "id": "anchored-surface",
4667
+ "name": "AnchoredSurface",
4668
+ "category": "Feedback",
4669
+ "kind": "component",
4670
+ "source": "kerf",
4671
+ "description": "A styled non-modal surface for app-owned interactive help or popup content, anchored to an element or pointer.",
4672
+ "uses": [],
4673
+ "purpose": "Present arbitrary app-owned content next to an element or pointer with Kerf UI surface styling and kerfjs/overlay lifecycle.",
4674
+ "publicExports": [
4675
+ "openAnchoredSurface",
4676
+ "openAnchoredSurfaceAt"
4677
+ ],
4678
+ "useWhen": [
4679
+ "Show interactive help or other arbitrary content near a control or pointer, including inside a modal dialog."
4680
+ ],
4681
+ "avoidWhen": [
4682
+ "Use PopupMenu for a typed action menu, Select for a choice, and a modal dialog for a task that must block the page."
4683
+ ],
4684
+ "alternatives": [
4685
+ { "id": "popup-menu", "relationship": "nearest" },
4686
+ { "id": "wa-popup", "relationship": "prefer-over" }
4687
+ ],
4688
+ "appOwns": [
4689
+ "content and action policy",
4690
+ "opening trigger and programmatic close after an action",
4691
+ "accessible name"
4692
+ ],
4693
+ "variants": [
4694
+ "element anchor",
4695
+ "pointer anchor",
4696
+ "top or bottom placement",
4697
+ "modal-dialog host"
4698
+ ],
4699
+ "usageExamples": [
4700
+ {
4701
+ "title": "Interactive help",
4702
+ "description": "The trigger owns when to open; the returned handle closes when an action completes.",
4703
+ "code": "const handle = openAnchoredSurface(button, () => <HelpPanel />, { label: 'Field help' });\n// Close from the app's action handler: handle.close();"
4704
+ }
4705
+ ],
4706
+ "accessibility": [
4707
+ "The non-modal surface has role dialog and the required label. Focus moves to the first focusable descendant by default and returns to the opener on close; initialFocus: false leaves focus on the trigger. Escape and outside press dismiss by default."
4708
+ ],
4709
+ "geometry": {
4710
+ "margin": "none",
4711
+ "border": "self",
4712
+ "padding": "self"
4713
+ },
4714
+ "delivery": {
4715
+ "browserImport": "@kerfjs/ui/anchored-surface",
4716
+ "manualCssImport": "@kerfjs/ui/anchored-surface.css",
4717
+ "sideEffects": ["browser-condition-css"]
4718
+ },
4719
+ "wiring": [],
4720
+ "publicClasses": [],
4721
+ "publicTokens": [
4722
+ "--kui-anchored-surface-inset",
4723
+ "--kui-anchored-surface-radius"
4724
+ ],
4725
+ "links": {
4726
+ "catalogRoute": "?component=anchored-surface",
4727
+ "documentation": "docs/anchored-surface.md",
4728
+ "recipe": "docs/anchored-surface.md"
4729
+ }
4730
+ },
4660
4731
  {
4661
4732
  "id": "popup-menu",
4662
4733
  "name": "PopupMenu",
@@ -1013,6 +1013,11 @@
1013
1013
  "owner": "application",
1014
1014
  "required": false
1015
1015
  },
1016
+ {
1017
+ "id": "whether the bar's top separator follows a sunken active surface or stays hidden (separator)",
1018
+ "owner": "application",
1019
+ "required": false
1020
+ },
1016
1021
  {
1017
1022
  "id": "optional active-scene focus (tabIndex) and persistent outline (outlined)",
1018
1023
  "owner": "application",
@@ -1047,7 +1052,7 @@
1047
1052
  "padding": "self",
1048
1053
  "notes": [
1049
1054
  "The navigator owns the full-height scene region and safe-area-aware bottom bar; scene content owns its internal geometry.",
1050
- "The bar's top line is scroll state (wireScrollDividers): it shows only while the active scene's content continues below the bar, by coloring the bar's always-present 1px top border."
1055
+ "The bar's reserved 1px top edge separates a sunken active scene or nested Pane by default; on flat surfaces it follows scroll state (wireScrollDividers). separator=hidden suppresses it."
1051
1056
  ]
1052
1057
  }
1053
1058
  },
@@ -2289,6 +2294,7 @@
2289
2294
  "rawPolicy": "forbid",
2290
2295
  "examples": [
2291
2296
  "gap=\"xs\"",
2297
+ "gap={{ column: \"none\", row: \"m\" }}",
2292
2298
  "gap={space(\"m\")}",
2293
2299
  "gap={calc(plus(rem(0.25), pct(10)))}"
2294
2300
  ]
@@ -2392,6 +2398,7 @@
2392
2398
  "rawPolicy": "forbid",
2393
2399
  "examples": [
2394
2400
  "gap=\"m\"",
2401
+ "gap={{ column: \"none\", row: \"m\" }}",
2395
2402
  "gap={space(\"m\")}",
2396
2403
  "gap={calc(plus(rem(0.25), pct(10)))}"
2397
2404
  ]
@@ -2591,6 +2598,7 @@
2591
2598
  "rawPolicy": "forbid",
2592
2599
  "examples": [
2593
2600
  "gap=\"xs\"",
2601
+ "gap={{ column: \"none\", row: \"m\" }}",
2594
2602
  "gap={rem(0.25)}",
2595
2603
  "gap={calc(plus(rem(0.25), pct(10)))}"
2596
2604
  ]
@@ -3797,6 +3805,61 @@
3797
3805
  "composition": "README.md#web-awesome-theme"
3798
3806
  }
3799
3807
  },
3808
+ {
3809
+ "key": "@kerfjs/ui:anchored-surface",
3810
+ "package": "@kerfjs/ui",
3811
+ "id": "anchored-surface",
3812
+ "name": "AnchoredSurface",
3813
+ "kind": "component",
3814
+ "source": "kerf",
3815
+ "parents": { "mode": "any", "entries": [] },
3816
+ "contexts": ["application-ui"],
3817
+ "zones": [],
3818
+ "children": { "mode": "any", "concepts": [], "requiredConcepts": [] },
3819
+ "state": [
3820
+ {
3821
+ "id": "content and action policy",
3822
+ "owner": "application",
3823
+ "required": false
3824
+ },
3825
+ {
3826
+ "id": "opening trigger and programmatic close after an action",
3827
+ "owner": "application",
3828
+ "required": false
3829
+ },
3830
+ { "id": "accessible name", "owner": "application", "required": false }
3831
+ ],
3832
+ "wiring": {
3833
+ "required": false,
3834
+ "helpers": [],
3835
+ "obligations": [],
3836
+ "stateAttributes": []
3837
+ },
3838
+ "responsive": { "owner": "not-applicable", "behaviors": [] },
3839
+ "layout": {
3840
+ "roles": ["feedback"],
3841
+ "geometry": { "margin": "none", "border": "self", "padding": "self" }
3842
+ },
3843
+ "accessibility": {
3844
+ "obligations": [
3845
+ "The non-modal surface has role dialog and the required label. Focus moves to the first focusable descendant by default and returns to the opener on close; initialFocus: false leaves focus on the trigger. Escape and outside press dismiss by default."
3846
+ ]
3847
+ },
3848
+ "cssValueProps": [],
3849
+ "boundaries": {
3850
+ "rootClass": null,
3851
+ "publicClasses": [],
3852
+ "publicTokens": [
3853
+ "--kui-anchored-surface-inset",
3854
+ "--kui-anchored-surface-radius"
3855
+ ]
3856
+ },
3857
+ "diagnostics": [],
3858
+ "provenance": {
3859
+ "selection": "ai/component-catalog.json",
3860
+ "composition": "generated-permissive-default"
3861
+ }
3862
+ },
3800
3863
  {
3801
3864
  "key": "@kerfjs/ui:popup-menu",
3802
3865
  "package": "@kerfjs/ui",
@@ -60,6 +60,7 @@ Exact prop and callback types live in [`public-api-signatures-v1.md`](../public-
60
60
 
61
61
  ## Kerf UI — Feedback
62
62
 
63
+ - [AnchoredSurface](./anchored-surface.md) — Present arbitrary app-owned content next to an element or pointer with Kerf UI surface styling and kerfjs/overlay lifecycle.
63
64
  - [Feedback](./feedback.md) — Banners, empty states, and labeled progress.
64
65
  - [StateBanner](./state-banner.md) — Present persistent inline feedback or attractive non-status emphasis with a named semantic tone.
65
66
  - [EmptyState](./empty-state.md) — Actionable empty and busy content states.
@@ -0,0 +1,86 @@
1
+ # AnchoredSurface
2
+
3
+ <!-- Generated by scripts/sync-component-docs.mjs from ai/component-catalog.json and ai/component-composition.json. Do not edit; run `npm run catalog:sync`. -->
4
+
5
+ `@kerfjs/ui:anchored-surface` · Kerf UI · component · Feedback
6
+
7
+ A styled non-modal surface for app-owned interactive help or popup content, anchored to an element or pointer.
8
+
9
+ ## When to use
10
+
11
+ Present arbitrary app-owned content next to an element or pointer with Kerf UI surface styling and kerfjs/overlay lifecycle.
12
+
13
+ - Show interactive help or other arbitrary content near a control or pointer, including inside a modal dialog.
14
+
15
+ **Not when:**
16
+
17
+ - Use PopupMenu for a typed action menu, Select for a choice, and a modal dialog for a task that must block the page.
18
+
19
+ **Alternatives:**
20
+
21
+ - [PopupMenu](./popup-menu.md) (nearest)
22
+ - [Popup](./wa-popup.md) (prefer-over)
23
+
24
+ ## Imports
25
+
26
+ - JavaScript: `@kerfjs/ui/anchored-surface` — exports `openAnchoredSurface`, `openAnchoredSurfaceAt`.
27
+ - CSS: the browser build of `@kerfjs/ui/anchored-surface` pulls its CSS automatically; import `@kerfjs/ui/anchored-surface.css` manually only without the `browser` export condition.
28
+ - Declared side effects: browser-condition-css.
29
+
30
+ ## Props
31
+
32
+ Exact prop names and types: [`@kerfjs/ui/anchored-surface`](../public-api-signatures-v1.md#kerfjsuianchored-surface) in `public-api-signatures-v1.md`. Do not infer props from examples.
33
+
34
+ **Variants (configure, do not restyle):**
35
+
36
+ - element anchor
37
+ - pointer anchor
38
+ - top or bottom placement
39
+ - modal-dialog host
40
+
41
+ ## Composition
42
+
43
+ - Parents: any.
44
+ - Contexts: `application-ui`.
45
+ - Children: unrestricted.
46
+
47
+ ## State and wiring
48
+
49
+ **The app owns:** content and action policy; opening trigger and programmatic close after an action; accessible name.
50
+
51
+ No wiring helper.
52
+
53
+ ## Geometry
54
+
55
+ Margin: none · border: self · padding: self (layout role: feedback). `self` means the component already owns it — do not add wrapper padding, margin, or borders around it.
56
+
57
+ ## Accessibility
58
+
59
+ - The non-modal surface has role dialog and the required label. Focus moves to the first focusable descendant by default and returns to the opener on close; initialFocus: false leaves focus on the trigger. Escape and outside press dismiss by default.
60
+
61
+ ## Usage examples
62
+
63
+ ### Interactive help
64
+
65
+ The trigger owns when to open; the returned handle closes when an action completes.
66
+
67
+ ```tsx
68
+ const handle = openAnchoredSurface(button, () => <HelpPanel />, {
69
+ label: 'Field help',
70
+ });
71
+ // Close from the app's action handler: handle.close();
72
+ ```
73
+
74
+ ## Styling boundary
75
+
76
+ The component owns its own styles. Configure it through its props and variants; do not override its internals.
77
+
78
+ Public tokens it reads: `--kui-anchored-surface-inset`, `--kui-anchored-surface-radius`. Set a token only where its public contract allows; prefer a prop.
79
+
80
+ Everything else is internal: `kerf-ui-analyze` reports private or unknown `.kui-*` selectors (`KUI-L001`, `KUI-L010`), unknown or private tokens (`KUI-L002`, `KUI-L012`), and any application rule that restyles the component, forced dimensions included (`KUI-L019`).
81
+
82
+ ## Related
83
+
84
+ - Components: [PopupMenu](./popup-menu.md), [Popup](./wa-popup.md).
85
+ - Docs: [`docs/anchored-surface.md`](../../docs/anchored-surface.md).
86
+ - UX catalog route: `?component=anchored-surface`.
@@ -43,6 +43,7 @@ Exact prop names and types: [`@kerfjs/ui/tab-navigator`](../public-api-signature
43
43
  - one NavStack per tab
44
44
  - bar divider only while the active scene continues below it (wired by wireScrollDividers)
45
45
  - permanent or no bar divider (chromeDividers always or none)
46
+ - sunken active-scene separator (separator auto or hidden)
46
47
 
47
48
  ## Composition
48
49
 
@@ -53,7 +54,7 @@ Exact prop names and types: [`@kerfjs/ui/tab-navigator`](../public-api-signature
53
54
 
54
55
  ## State and wiring
55
56
 
56
- **The app owns:** controlled active tab id; tab labels, icons, and content; tab badge counts or dots and their localized badgeLabel phrases; routing and persistence; responsive replacement on larger device classes; whether the bar divider follows the scroll, always shows, or never shows (chromeDividers); optional active-scene focus (tabIndex) and persistent outline (outlined).
57
+ **The app owns:** controlled active tab id; tab labels, icons, and content; tab badge counts or dots and their localized badgeLabel phrases; routing and persistence; responsive replacement on larger device classes; whether the bar divider follows the scroll, always shows, or never shows (chromeDividers); whether the bar's top separator follows a sunken active surface or stays hidden (separator); optional active-scene focus (tabIndex) and persistent outline (outlined).
57
58
 
58
59
  **Wiring:** `wireTabNavigator`, `wireScrollDividers` is required.
59
60
 
@@ -69,7 +70,7 @@ Exact prop names and types: [`@kerfjs/ui/tab-navigator`](../public-api-signature
69
70
  Margin: none · border: conditional · padding: self (layout role: structure). `self` means the component already owns it — do not add wrapper padding, margin, or borders around it.
70
71
 
71
72
  - The navigator owns the full-height scene region and safe-area-aware bottom bar; scene content owns its internal geometry.
72
- - The bar's top line is scroll state (wireScrollDividers): it shows only while the active scene's content continues below the bar, by coloring the bar's always-present 1px top border.
73
+ - The bar's reserved 1px top edge separates a sunken active scene or nested Pane by default; on flat surfaces it follows scroll state (wireScrollDividers). separator=hidden suppresses it.
73
74
 
74
75
  ## Accessibility
75
76