@kerfjs/ui 5.0.0-beta.24 → 5.0.0-beta.26

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 (149) hide show
  1. package/README.md +44 -23
  2. package/ai/application-ui-diagnostic-ids-v1.json +3 -0
  3. package/ai/application-ui-profile.defaults.json +1 -1
  4. package/ai/compile-time-contracts-v1.json +3 -11
  5. package/ai/component-catalog-v2-overrides.json +7 -37
  6. package/ai/component-catalog-v2.d.ts +1 -0
  7. package/ai/component-catalog-v2.json +415 -91
  8. package/ai/component-catalog-v2.schema.json +2 -1
  9. package/ai/component-catalog.json +602 -158
  10. package/ai/public-api-signatures-v1.md +132 -65
  11. package/ai/skill.md +20 -20
  12. package/ai/webawesome-jsx-signatures-v1.md +2 -1
  13. package/analyzer/cli.mjs +5 -2
  14. package/analyzer/index.d.mts +1 -0
  15. package/analyzer/index.mjs +117 -10
  16. package/dist/app-tab.d.ts +10 -2
  17. package/dist/app-tab.js +1 -1
  18. package/dist/browser/list-action-row.js +1 -0
  19. package/dist/browser/list-item.js +1 -0
  20. package/dist/browser/surface-scaffold.js +3 -0
  21. package/dist/catalog.js +5 -4
  22. package/dist/catalog.js.map +1 -1
  23. package/dist/{chunk-FEAMR7S4.js → chunk-4EF2CMYS.js} +24 -5
  24. package/dist/chunk-4EF2CMYS.js.map +1 -0
  25. package/dist/{chunk-NWSEP5RT.js → chunk-FRVS5RFM.js} +23 -5
  26. package/dist/chunk-FRVS5RFM.js.map +1 -0
  27. package/dist/{chunk-PSKN344N.js → chunk-GWEAPEZI.js} +15 -2
  28. package/dist/chunk-GWEAPEZI.js.map +1 -0
  29. package/dist/chunk-HP2B2L26.js +102 -0
  30. package/dist/chunk-HP2B2L26.js.map +1 -0
  31. package/dist/{chunk-OJWWHFH3.js → chunk-JFGXQBIP.js} +49 -23
  32. package/dist/chunk-JFGXQBIP.js.map +1 -0
  33. package/dist/chunk-OBLTAKSX.js +43 -0
  34. package/dist/chunk-OBLTAKSX.js.map +1 -0
  35. package/dist/{chunk-Y45PPBDV.js → chunk-OT6RPYAP.js} +16 -2
  36. package/dist/chunk-OT6RPYAP.js.map +1 -0
  37. package/dist/{chunk-UFZKLBCF.js → chunk-VCE4MPVE.js} +25 -3
  38. package/dist/chunk-VCE4MPVE.js.map +1 -0
  39. package/dist/{chunk-5VTB2BKP.js → chunk-WNWD54GR.js} +16 -2
  40. package/dist/chunk-WNWD54GR.js.map +1 -0
  41. package/dist/{chunk-M7JBER5U.js → chunk-X2U3QJCJ.js} +8 -2
  42. package/dist/chunk-X2U3QJCJ.js.map +1 -0
  43. package/dist/{chunk-DPDBVKNA.js → chunk-YMKH5XTF.js} +5 -3
  44. package/dist/chunk-YMKH5XTF.js.map +1 -0
  45. package/dist/collapsible-panel.d.ts +9 -1
  46. package/dist/collapsible-panel.js +36 -15
  47. package/dist/collapsible-panel.js.map +1 -1
  48. package/dist/index.d.ts +2 -2
  49. package/dist/index.js +12 -12
  50. package/dist/list-action-row.d.ts +11 -1
  51. package/dist/list-action-row.js +2 -1
  52. package/dist/list-header.d.ts +15 -2
  53. package/dist/list-header.js +1 -1
  54. package/dist/list-item.d.ts +12 -1
  55. package/dist/list-item.js +2 -1
  56. package/dist/resizable-region.d.ts +19 -2
  57. package/dist/resizable-region.js +1 -1
  58. package/dist/select.d.ts +12 -1
  59. package/dist/select.js +1 -1
  60. package/dist/split-view.js +1 -1
  61. package/dist/styles/app-tab.css +26 -0
  62. package/dist/styles/collapsible-panel.css +66 -4
  63. package/dist/styles/list-action-row.css +64 -0
  64. package/dist/styles/list-header.css +32 -0
  65. package/dist/styles/list-item.css +39 -0
  66. package/dist/styles/resizable-region.css +132 -0
  67. package/dist/styles/select.css +68 -0
  68. package/dist/styles/styles.css +1 -1
  69. package/dist/styles/surface-scaffold.css +51 -0
  70. package/dist/styles/tab-bar.css +24 -0
  71. package/dist/styles/toolbar-control-group.css +73 -1
  72. package/dist/styles/toolbar-text.css +1 -1
  73. package/dist/styles/webawesome.css +12 -0
  74. package/dist/styles/workbench.css +89 -0
  75. package/dist/surface-scaffold.d.ts +25 -0
  76. package/dist/surface-scaffold.js +3 -0
  77. package/dist/surface-scaffold.js.map +1 -0
  78. package/dist/tab-bar.d.ts +11 -2
  79. package/dist/tab-bar.js +1 -1
  80. package/dist/toolbar-control-group.d.ts +16 -2
  81. package/dist/toolbar-control-group.js +1 -1
  82. package/dist/webawesome.d.ts +1 -0
  83. package/dist/wire-resizable-regions.js +1 -1
  84. package/dist/wire-sidebar.d.ts +6 -1
  85. package/dist/wire-sidebar.js +27 -8
  86. package/dist/wire-sidebar.js.map +1 -1
  87. package/dist/wire-token-search-fields.js +1 -1
  88. package/dist/workbench.d.ts +8 -0
  89. package/dist/workbench.js +42 -0
  90. package/dist/workbench.js.map +1 -1
  91. package/docs/accessibility.md +55 -12
  92. package/docs/collapsible-panel.md +7 -0
  93. package/docs/component-contract.md +5 -8
  94. package/docs/component-selection.md +63 -33
  95. package/docs/design/templates.md +5 -7
  96. package/docs/examples/component-catalog-extension-v2.json +3 -6
  97. package/docs/examples/component-catalog-extension.json +1 -1
  98. package/docs/layout.md +7 -9
  99. package/docs/recipes.md +8 -7
  100. package/docs/surface-scaffold.md +17 -0
  101. package/docs/type-contracts.md +6 -8
  102. package/docs/ui-analyzer.md +19 -1
  103. package/docs/ui-doctor.md +1 -1
  104. package/docs/ux-demo.md +6 -3
  105. package/docs/webawesome-theme.md +4 -1
  106. package/docs/workbench.md +49 -2
  107. package/doctor/eslint-diagnostics.d.mts +16 -0
  108. package/doctor/eslint-diagnostics.mjs +17 -0
  109. package/doctor/index.mjs +16 -1
  110. package/llms.txt +19 -9
  111. package/package.json +7 -7
  112. package/ux-demo/recipes/app-shell.tsx +36 -23
  113. package/ux-demo/recipes/collapsible-sidebar.tsx +22 -16
  114. package/ux-demo/recipes/composer-form.tsx +15 -6
  115. package/ux-demo/recipes/list-detail-dialog.tsx +31 -15
  116. package/ux-demo/recipes/list-workspace-states.tsx +16 -7
  117. package/ux-demo/recipes/loading-inspector.tsx +39 -18
  118. package/ux-demo/recipes/recipes.css +7 -2
  119. package/ux-demo/recipes/workspace-header.tsx +17 -7
  120. package/dist/browser/panel-header.js +0 -7
  121. package/dist/chunk-5VTB2BKP.js.map +0 -1
  122. package/dist/chunk-7XRDUG4Y.js +0 -58
  123. package/dist/chunk-7XRDUG4Y.js.map +0 -1
  124. package/dist/chunk-DPDBVKNA.js.map +0 -1
  125. package/dist/chunk-FEAMR7S4.js.map +0 -1
  126. package/dist/chunk-M7JBER5U.js.map +0 -1
  127. package/dist/chunk-NENBUYX5.js +0 -79
  128. package/dist/chunk-NENBUYX5.js.map +0 -1
  129. package/dist/chunk-NWSEP5RT.js.map +0 -1
  130. package/dist/chunk-OJWWHFH3.js.map +0 -1
  131. package/dist/chunk-PSKN344N.js.map +0 -1
  132. package/dist/chunk-UFZKLBCF.js.map +0 -1
  133. package/dist/chunk-Y45PPBDV.js.map +0 -1
  134. package/dist/panel-header.d.ts +0 -42
  135. package/dist/panel-header.js +0 -7
  136. package/dist/panel-header.js.map +0 -1
  137. package/dist/styles/panel-header.css +0 -64
  138. package/docs/design/templates/panel-header/icon-actions-dark.svg +0 -1
  139. package/docs/design/templates/panel-header/icon-actions.svg +0 -1
  140. package/docs/design/templates/panel-header/icon-summary-actions-dark.svg +0 -1
  141. package/docs/design/templates/panel-header/icon-summary-actions.svg +0 -1
  142. package/docs/design/templates/panel-header/no-icon-dark.svg +0 -1
  143. package/docs/design/templates/panel-header/no-icon.svg +0 -1
  144. package/docs/design/templates/panel-header/page-heading-dark.svg +0 -1
  145. package/docs/design/templates/panel-header/page-heading.svg +0 -1
  146. package/docs/design/templates/panel-header/title-only-dark.svg +0 -1
  147. package/docs/design/templates/panel-header/title-only.svg +0 -1
  148. package/docs/design/templates/panel-header-dark.svg +0 -1
  149. package/docs/design/templates/panel-header.svg +0 -1
@@ -0,0 +1,51 @@
1
+ .kui-dialog-surface > wa-dialog {
2
+ --width: min(35rem, calc(100vw - 2rem));
3
+ }
4
+ .kui-dialog-surface[data-size="small"] > wa-dialog {
5
+ --width: min(25rem, calc(100vw - 2rem));
6
+ }
7
+ .kui-dialog-surface[data-size="large"] > wa-dialog {
8
+ --width: min(50rem, calc(100vw - 2rem));
9
+ }
10
+ .kui-dialog-surface[data-body-inset="none"] > wa-dialog::part(body),
11
+ .kui-dialog-surface[data-footer-inset="none"] > wa-dialog::part(footer) {
12
+ padding: 0;
13
+ }
14
+ .kui-dialog-surface[data-body-inset="compact"] > wa-dialog::part(body),
15
+ .kui-dialog-surface[data-footer-inset="compact"] > wa-dialog::part(footer) {
16
+ padding: var(--kui-wa-surface-inset, 0.5rem);
17
+ }
18
+ .kui-dialog-surface[data-body-inset="comfortable"] > wa-dialog::part(body),
19
+ .kui-dialog-surface[data-footer-inset="comfortable"] > wa-dialog::part(footer) {
20
+ padding: var(--kui-wa-container-inset, 1rem);
21
+ }
22
+ .kui-dialog-surface[data-presentation="side-sheet"] > wa-dialog::part(dialog) {
23
+ width: min(30rem, 100vw);
24
+ max-width: 100vw;
25
+ height: 100dvh;
26
+ max-height: 100dvh;
27
+ margin-inline-end: 0;
28
+ border-radius: var(--kui-radius-l) 0 0 var(--kui-radius-l);
29
+ }
30
+ .kui-dialog-surface[data-presentation="fullscreen"] > wa-dialog {
31
+ --width: 100vw;
32
+ }
33
+ .kui-dialog-surface[data-presentation="fullscreen"] > wa-dialog::part(dialog) {
34
+ width: 100vw;
35
+ max-width: 100vw;
36
+ height: 100dvh;
37
+ max-height: 100dvh;
38
+ border-radius: 0;
39
+ }
40
+ .kui-popup-surface {
41
+ display: inline-flex;
42
+ }
43
+ .kui-popup-surface[data-inset="standard"] > wa-dropdown::part(menu) {
44
+ padding: var(--kui-wa-surface-inset, 0.5rem);
45
+ }
46
+ .kui-popup-surface[data-inset="compact"] > wa-dropdown::part(menu) {
47
+ padding: 0.25rem;
48
+ }
49
+ .kui-popup-surface[data-inset="list-zero"] > wa-dropdown::part(menu) {
50
+ padding: 0;
51
+ }
@@ -31,6 +31,30 @@
31
31
  gap: 0;
32
32
  overscroll-behavior-inline: contain;
33
33
  }
34
+ .kui-tab-bar[data-presentation="segmented"],
35
+ .kui-tab-bar[data-presentation="inspector"] {
36
+ --kui-tab-bar-background: var(--kui-color-neutral-fill-quiet);
37
+ --kui-tab-bar-border: var(--kui-color-neutral-border-quiet);
38
+ }
39
+ .kui-tab-bar[data-presentation="inspector"] {
40
+ min-height: 2.125rem;
41
+ padding: 0;
42
+ }
43
+ .kui-tab-bar[data-presentation="inspector"] .kui-tab-bar__tabs {
44
+ height: 2.125rem;
45
+ border-radius: var(--kui-radius-s);
46
+ }
47
+ .kui-tab-bar[data-allocation="fill"] [data-component="app-tab"] {
48
+ min-width: 0;
49
+ flex: 1 1 0;
50
+ }
51
+ .kui-tab-bar[data-allocation="fill"] [data-component="app-tab"] > [role="tab"] {
52
+ width: 100%;
53
+ justify-content: center;
54
+ }
55
+ .kui-tab-bar[data-trailing-placement="adjacent"] .kui-tab-bar__tabs {
56
+ flex: 0 1 auto;
57
+ }
34
58
 
35
59
  .kui-tab-bar__tabs::-webkit-scrollbar {
36
60
  display: none;
@@ -55,6 +55,58 @@
55
55
  transparent
56
56
  );
57
57
  }
58
+ .kui-toolbar-control-group[data-size="compact"] {
59
+ --kui-toolbar-group-size: 2.125rem;
60
+ }
61
+ .kui-toolbar-control-group[data-density="tight"] {
62
+ --kui-layout-item-padding: 0.3125rem;
63
+ }
64
+ .kui-toolbar-control-group[data-content="text"] > :is(button, wa-button),
65
+ .kui-toolbar-control-group[data-content="mixed"] > :is(button, wa-button) {
66
+ min-width: max-content;
67
+ }
68
+ .kui-toolbar-control-group[data-content="mixed"] > button {
69
+ display: flex;
70
+ gap: var(--kui-space-xs);
71
+ }
72
+ .kui-toolbar-control-group[data-content="mixed"] {
73
+ gap: 0.5rem;
74
+ }
75
+ .kui-toolbar-control-group[data-content="mixed"] > wa-dropdown {
76
+ padding-inline-start: 0.375rem;
77
+ border-inline-start: 1px solid var(--kui-toolbar-control-border);
78
+ }
79
+ .kui-toolbar-control-group[data-content="avatar"] img {
80
+ width: 1.5rem;
81
+ height: 1.5rem;
82
+ border-radius: var(--kui-radius-pill);
83
+ object-fit: cover;
84
+ }
85
+ .kui-toolbar-control-group[data-content="avatar"][data-scrim="true"] {
86
+ --kui-toolbar-control-background: color-mix(
87
+ in srgb,
88
+ var(--kui-color-neutral-fill-loud) 64%,
89
+ transparent
90
+ );
91
+ --kui-toolbar-control-color: var(--kui-color-neutral-on-loud);
92
+ }
93
+ .kui-toolbar-control-group[data-selected-tone="neutral"] {
94
+ --kui-toolbar-control-selected-color: var(--kui-color-neutral-on-normal);
95
+ --kui-toolbar-control-selected-background: var(
96
+ --kui-color-neutral-fill-normal
97
+ );
98
+ }
99
+ .kui-toolbar-control-group[data-selected-chrome="filled"] {
100
+ --kui-toolbar-control-selected-shadow: none;
101
+ --kui-toolbar-control-selected-border: transparent;
102
+ --kui-toolbar-control-selected-background: var(--kui-color-brand-fill-normal);
103
+ --kui-toolbar-control-selected-color: var(--kui-color-brand-on-normal);
104
+ }
105
+ .kui-toolbar-control-group[data-selected-chrome="outline"] {
106
+ --kui-toolbar-control-selected-shadow: none;
107
+ --kui-toolbar-control-selected-background: transparent;
108
+ --kui-toolbar-control-selected-border: var(--kui-color-brand-border-normal);
109
+ }
58
110
 
59
111
  .kui-toolbar-control-group[data-appearance="borderless"] {
60
112
  /* A borderless group has no base fill to lift from, so the default
@@ -72,6 +124,9 @@
72
124
  .kui-toolbar-control-group > :is(button, wa-button, wa-dropdown) {
73
125
  flex: 0 0 auto;
74
126
  }
127
+ .kui-toolbar-control-group > wa-dropdown {
128
+ display: inline-flex;
129
+ }
75
130
 
76
131
  .kui-toolbar-control-group > button {
77
132
  appearance: none;
@@ -111,6 +166,23 @@
111
166
  min-width: 2.5rem;
112
167
  height: 2.5rem;
113
168
  }
169
+ .kui-toolbar-control-group[data-size="compact"] > button,
170
+ .kui-toolbar-control-group[data-size="compact"] > wa-button,
171
+ .kui-toolbar-control-group[data-size="compact"] > wa-dropdown,
172
+ .kui-toolbar-control-group[data-size="compact"] > wa-dropdown > wa-button,
173
+ .kui-toolbar-control-group[data-size="compact"]
174
+ > wa-dropdown
175
+ > wa-button::part(base),
176
+ .kui-toolbar-control-group[data-size="compact"][data-nested-dropdown="true"]
177
+ wa-dropdown
178
+ wa-button,
179
+ .kui-toolbar-control-group[data-size="compact"][data-nested-dropdown="true"]
180
+ wa-dropdown
181
+ wa-button::part(base) {
182
+ min-width: 2rem;
183
+ height: 2rem;
184
+ min-height: 2rem;
185
+ }
114
186
 
115
187
  .kui-toolbar-control-group[data-single="true"] > wa-dropdown,
116
188
  .kui-toolbar-control-group[data-single="true"] > wa-dropdown > wa-button,
@@ -161,7 +233,7 @@
161
233
 
162
234
  /* A group holding exactly one control behaves like `single` even without the
163
235
  flag: the whole group background changes on hover instead of highlighting an
164
- inner layer inset from the border (KF-J9P1ZD — a PanelHeader's lone Close/
236
+ inner layer inset from the border (KF-J9P1ZD — a heading toolbar's lone Close/
165
237
  Done action). */
166
238
  .kui-toolbar-control-group:not([data-single="true"]):has(
167
239
  > :is(button, wa-button):only-child
@@ -23,7 +23,7 @@
23
23
  }
24
24
 
25
25
  .kui-toolbar-text[data-size="xlarge"] {
26
- /* Matches the page/panel title: the same responsive clamp so a PanelHeader
26
+ /* Matches page/panel titles: the same responsive clamp so a heading toolbar
27
27
  title reads at the page-title scale without a bespoke override. Height grows
28
28
  to the larger text (the fixed 40px would clip its ascenders). */
29
29
  height: auto;
@@ -290,6 +290,18 @@
290
290
  padding: var(--kui-wa-surface-inset);
291
291
  }
292
292
 
293
+ /* Dialog content follows the compact surface inset, while its action region
294
+ retains the roomier homogeneous-group step. Keep these independent of
295
+ Web Awesome's shared --spacing property so applications can tune the
296
+ header without accidentally moving body or footer content. */
297
+ wa-dialog::part(body) {
298
+ padding: var(--kui-wa-surface-inset);
299
+ }
300
+
301
+ wa-dialog::part(footer) {
302
+ padding: var(--kui-wa-container-inset);
303
+ }
304
+
293
305
  wa-callout {
294
306
  padding: var(--kui-wa-surface-inset) !important;
295
307
  }
@@ -1,4 +1,5 @@
1
1
  .kui-workbench {
2
+ position: relative;
2
3
  display: flex;
3
4
  min-width: 0;
4
5
  min-height: 0;
@@ -30,6 +31,11 @@
30
31
  border-inline: 0;
31
32
  }
32
33
 
34
+ .kui-workbench__rail[data-separator="hidden"],
35
+ .kui-workbench__drawer[data-separator="hidden"] {
36
+ border-color: transparent;
37
+ }
38
+
33
39
  .kui-workbench__center {
34
40
  display: flex;
35
41
  flex: 1;
@@ -59,6 +65,17 @@
59
65
  border-block-start: 0;
60
66
  }
61
67
 
68
+ .kui-workbench__rail[data-content-overflow="auto"],
69
+ .kui-workbench__drawer[data-content-overflow="auto"] {
70
+ overflow: auto;
71
+ }
72
+
73
+ .kui-workbench__rail[data-content-overflow="visible"],
74
+ .kui-workbench__drawer[data-content-overflow="visible"] {
75
+ z-index: var(--kui-workbench-popup-z, 5);
76
+ overflow: visible;
77
+ }
78
+
62
79
  /* The fixed-size sliding content. The rail/drawer keeps a definite size on its
63
80
  content so the transform reads as a slide, not a squeeze. */
64
81
  .kui-workbench__rail .kui-workbench__panel-content {
@@ -73,6 +90,19 @@
73
90
  transition: transform 200ms ease;
74
91
  }
75
92
 
93
+ .kui-workbench__rail[data-collapse-motion="none"] .kui-workbench__panel-content,
94
+ .kui-workbench__drawer[data-collapse-motion="none"]
95
+ .kui-workbench__panel-content {
96
+ transition: none;
97
+ }
98
+
99
+ .kui-workbench__rail[data-collapse-motion="fade-slide"][data-collapsed="true"]
100
+ .kui-workbench__panel-content,
101
+ .kui-workbench__drawer[data-collapse-motion="fade-slide"][data-collapsed="true"]
102
+ .kui-workbench__panel-content {
103
+ opacity: 0;
104
+ }
105
+
76
106
  .kui-workbench__rail--left[data-collapsed="true"]
77
107
  .kui-workbench__panel-content {
78
108
  transform: translateX(-100%);
@@ -87,6 +117,65 @@
87
117
  transform: translateY(100%);
88
118
  }
89
119
 
120
+ .kui-workbench__rail[data-presentation="overlay"],
121
+ .kui-workbench__drawer[data-presentation="overlay"] {
122
+ position: absolute;
123
+ z-index: var(--kui-workbench-overlay-z, 41);
124
+ max-width: var(--kui-workbench-overlay-max-width, 85vw);
125
+ max-height: var(--kui-workbench-overlay-max-height, 85vh);
126
+ box-shadow: var(--kui-shadow-l, 0 8px 32px rgb(0 0 0 / 24%));
127
+ }
128
+
129
+ .kui-workbench__rail[data-presentation="overlay"] {
130
+ inset-block: 0;
131
+ }
132
+
133
+ .kui-workbench__rail--left[data-presentation="overlay"] {
134
+ inset-inline-start: 0;
135
+ }
136
+
137
+ .kui-workbench__rail--right[data-presentation="overlay"] {
138
+ inset-inline-end: 0;
139
+ }
140
+
141
+ .kui-workbench__drawer[data-presentation="overlay"] {
142
+ inset-inline: 0;
143
+ inset-block-end: 0;
144
+ }
145
+
146
+ .kui-workbench__rail[data-presentation="overlay"][data-collapsed="true"],
147
+ .kui-workbench__drawer[data-presentation="overlay"][data-collapsed="true"] {
148
+ pointer-events: none;
149
+ }
150
+
151
+ .kui-workbench__rail[data-presentation="hidden"],
152
+ .kui-workbench__drawer[data-presentation="hidden"] {
153
+ display: none;
154
+ }
155
+
156
+ .kui-workbench__restore {
157
+ position: fixed;
158
+ z-index: var(--kui-workbench-restore-z, 42);
159
+ inset-block-end: calc(
160
+ var(--kui-workbench-restore-inset, 1rem) +
161
+ var(--kui-safe-area-block-end, env(safe-area-inset-bottom, 0px))
162
+ );
163
+ }
164
+
165
+ .kui-workbench__restore[data-position="bottom-start"] {
166
+ inset-inline-start: calc(
167
+ var(--kui-workbench-restore-inset, 1rem) +
168
+ var(--kui-safe-area-inline-start, env(safe-area-inset-left, 0px))
169
+ );
170
+ }
171
+
172
+ .kui-workbench__restore[data-position="bottom-end"] {
173
+ inset-inline-end: calc(
174
+ var(--kui-workbench-restore-inset, 1rem) +
175
+ var(--kui-safe-area-inline-end, env(safe-area-inset-right, 0px))
176
+ );
177
+ }
178
+
90
179
  @media (prefers-reduced-motion: reduce) {
91
180
  .kui-workbench__panel-content {
92
181
  transition: none;
@@ -0,0 +1,25 @@
1
+ import { SafeHtml } from 'kerfjs';
2
+
3
+ type DialogSurfaceSize = 'small' | 'medium' | 'large';
4
+ type DialogSurfacePresentation = 'modal' | 'side-sheet' | 'fullscreen';
5
+ type SurfaceInset = 'none' | 'compact' | 'comfortable';
6
+ interface DialogSurfaceProps {
7
+ children: SafeHtml | readonly SafeHtml[];
8
+ size?: DialogSurfaceSize;
9
+ presentation?: DialogSurfacePresentation;
10
+ bodyInset?: SurfaceInset;
11
+ footerInset?: SurfaceInset;
12
+ className?: string;
13
+ }
14
+ /** Configure recurring Web Awesome dialog geometry without consumer ::part() CSS. */
15
+ declare function DialogSurface({ children, size, presentation, bodyInset, footerInset, className, }: DialogSurfaceProps): SafeHtml;
16
+ type PopupSurfaceInset = 'standard' | 'compact' | 'list-zero';
17
+ interface PopupSurfaceProps {
18
+ children: SafeHtml | readonly SafeHtml[];
19
+ inset?: PopupSurfaceInset;
20
+ className?: string;
21
+ }
22
+ /** Configure recurring Web Awesome dropdown-menu geometry without consumer ::part() CSS. */
23
+ declare function PopupSurface({ children, inset, className, }: PopupSurfaceProps): SafeHtml;
24
+
25
+ export { DialogSurface, type DialogSurfacePresentation, type DialogSurfaceProps, type DialogSurfaceSize, PopupSurface, type PopupSurfaceInset, type PopupSurfaceProps, type SurfaceInset };
@@ -0,0 +1,3 @@
1
+ export { DialogSurface, PopupSurface } from './chunk-OBLTAKSX.js';
2
+
3
+ //# sourceMappingURL=surface-scaffold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"surface-scaffold.js"}
package/dist/tab-bar.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  import { SafeHtml } from 'kerfjs';
2
2
 
3
3
  type TabActivation = 'automatic' | 'manual';
4
+ type TabBarAllocation = 'intrinsic' | 'fill';
5
+ type TabBarPresentation = 'rail' | 'segmented' | 'inspector';
6
+ type TabBarTrailingPlacement = 'separate' | 'adjacent';
4
7
  interface TabBarProps {
5
8
  id: string;
6
9
  label: string;
@@ -15,8 +18,14 @@ interface TabBarProps {
15
18
  * selects with Enter / Space / click — use it when selecting a tab is a heavy action.
16
19
  */
17
20
  activation?: TabActivation;
21
+ /** How available strip width is allocated across child AppTabs. */
22
+ allocation?: TabBarAllocation;
23
+ /** Named strip chrome for application rails, segmented tabs, or inspectors. */
24
+ presentation?: TabBarPresentation;
25
+ /** Keep a trailing action beside the final tab or at the far edge of the bar. */
26
+ trailingPlacement?: TabBarTrailingPlacement;
18
27
  }
19
28
  /** Render a controlled tab strip. The application owns selection, order, and persistence. */
20
- declare function TabBar({ id, label, children, leading, trailing, className, activation, }: TabBarProps): SafeHtml;
29
+ declare function TabBar({ id, label, children, leading, trailing, className, activation, allocation, presentation, trailingPlacement, }: TabBarProps): SafeHtml;
21
30
 
22
- export { type TabActivation, TabBar, type TabBarProps };
31
+ export { type TabActivation, TabBar, type TabBarAllocation, type TabBarPresentation, type TabBarProps, type TabBarTrailingPlacement };
package/dist/tab-bar.js CHANGED
@@ -1,3 +1,3 @@
1
- export { TabBar } from './chunk-M7JBER5U.js';
1
+ export { TabBar } from './chunk-X2U3QJCJ.js';
2
2
 
3
3
  //# sourceMappingURL=tab-bar.js.map
@@ -4,6 +4,11 @@ type ToolbarControlGroupAppearance = 'contained' | 'borderless';
4
4
  type ToolbarControlGroupTone = 'default' | 'dark';
5
5
  type ToolbarControlGroupButtonAppearance = 'plain' | 'push';
6
6
  type ToolbarControlGroupShape = 'pill' | 'rounded';
7
+ type ToolbarControlGroupSize = 'default' | 'compact';
8
+ type ToolbarControlGroupDensity = 'comfortable' | 'tight';
9
+ type ToolbarControlGroupContent = 'icon' | 'text' | 'mixed' | 'avatar';
10
+ type ToolbarControlGroupSelectedChrome = 'raised' | 'filled' | 'outline';
11
+ type ToolbarControlGroupSelectedTone = 'brand' | 'neutral';
7
12
  interface ToolbarControlGroupProps {
8
13
  children: SafeHtml | SafeHtml[];
9
14
  label?: string;
@@ -15,7 +20,16 @@ interface ToolbarControlGroupProps {
15
20
  buttonAppearance?: ToolbarControlGroupButtonAppearance;
16
21
  /** Corner shape: fully round `pill` (default) or a softer `rounded` rectangle. */
17
22
  shape?: ToolbarControlGroupShape;
23
+ size?: ToolbarControlGroupSize;
24
+ density?: ToolbarControlGroupDensity;
25
+ content?: ToolbarControlGroupContent;
26
+ selectedChrome?: ToolbarControlGroupSelectedChrome;
27
+ selectedTone?: ToolbarControlGroupSelectedTone;
28
+ /** Size a nested Web Awesome dropdown trigger as part of this group. */
29
+ nestedDropdown?: boolean;
30
+ /** Add contrast behind photo-backed avatar content. */
31
+ scrim?: boolean;
18
32
  }
19
- declare function ToolbarControlGroup({ children, label, className, expanded, single, appearance, tone, buttonAppearance, shape, }: ToolbarControlGroupProps): SafeHtml;
33
+ declare function ToolbarControlGroup({ children, label, className, expanded, single, appearance, tone, buttonAppearance, shape, size, density, content, selectedChrome, selectedTone, nestedDropdown, scrim, }: ToolbarControlGroupProps): SafeHtml;
20
34
 
21
- export { ToolbarControlGroup, type ToolbarControlGroupAppearance, type ToolbarControlGroupButtonAppearance, type ToolbarControlGroupProps, type ToolbarControlGroupShape, type ToolbarControlGroupTone };
35
+ export { ToolbarControlGroup, type ToolbarControlGroupAppearance, type ToolbarControlGroupButtonAppearance, type ToolbarControlGroupContent, type ToolbarControlGroupDensity, type ToolbarControlGroupProps, type ToolbarControlGroupSelectedChrome, type ToolbarControlGroupSelectedTone, type ToolbarControlGroupShape, type ToolbarControlGroupSize, type ToolbarControlGroupTone };
@@ -1,3 +1,3 @@
1
- export { ToolbarControlGroup } from './chunk-5VTB2BKP.js';
1
+ export { ToolbarControlGroup } from './chunk-WNWD54GR.js';
2
2
 
3
3
  //# sourceMappingURL=toolbar-control-group.js.map
@@ -23,6 +23,7 @@ declare module 'kerfjs/jsx-runtime' {
23
23
  'aria-label'?: string;
24
24
  value?: string;
25
25
  placeholder?: string;
26
+ hint?: string;
26
27
  disabled?: boolean;
27
28
  };
28
29
  'wa-option': KerfCustomElement & {
@@ -1,4 +1,4 @@
1
- export { wireResizableRegions } from './chunk-DPDBVKNA.js';
1
+ export { wireResizableRegions } from './chunk-YMKH5XTF.js';
2
2
 
3
3
 
4
4
  //# sourceMappingURL=wire-resizable-regions.js.map
@@ -28,6 +28,11 @@ interface WireSidebarOptions {
28
28
  * is always inline.
29
29
  */
30
30
  deviceClass?: ReadonlySignal<DeviceClass>;
31
+ /** Compact devices either overlay the panels (default) or hide them in favor
32
+ * of an application-owned responsive replacement. */
33
+ compactPresentation?: 'overlay' | 'hidden';
34
+ /** Collapse the other panels when one opens in compact overlay mode. */
35
+ exclusiveCompact?: boolean;
31
36
  /** Persistence store (default `globalThis.localStorage`, if present). */
32
37
  storage?: SidebarStorage;
33
38
  }
@@ -38,6 +43,6 @@ interface WireSidebarOptions {
38
43
  * persistence hook. The app owns each `collapsed` signal and the layout; this wire
39
44
  * owns the interaction. Returns a disposer. See `docs/24-collapsible-panel.md`.
40
45
  */
41
- declare function wireSidebar(root: HTMLElement, { panels, deviceClass, storage }: WireSidebarOptions): () => void;
46
+ declare function wireSidebar(root: HTMLElement, { panels, deviceClass, compactPresentation, exclusiveCompact, storage, }: WireSidebarOptions): () => void;
42
47
 
43
48
  export { type SidebarStorage, type WireSidebarOptions, type WireSidebarPanel, wireSidebar };
@@ -12,7 +12,13 @@ function defaultStorage() {
12
12
  return void 0;
13
13
  }
14
14
  }
15
- function wireSidebar(root, { panels, deviceClass, storage = defaultStorage() }) {
15
+ function wireSidebar(root, {
16
+ panels,
17
+ deviceClass,
18
+ compactPresentation = "overlay",
19
+ exclusiveCompact = true,
20
+ storage = defaultStorage()
21
+ }) {
16
22
  const ownerDocument = root.ownerDocument;
17
23
  const byAction = new Map(panels.map((panel) => [panel.toggleAction, panel]));
18
24
  const returnFocus = /* @__PURE__ */ new Map();
@@ -39,7 +45,12 @@ function wireSidebar(root, { panels, deviceClass, storage = defaultStorage() })
39
45
  const panel = byAction.get(trigger.dataset.action ?? "");
40
46
  if (!panel) return;
41
47
  returnFocus.set(panel.id, trigger);
42
- panel.collapsed.value = !panel.collapsed.value;
48
+ const next = !panel.collapsed.value;
49
+ panel.collapsed.value = next;
50
+ if (!next && exclusiveCompact && deviceClass?.value.compact && compactPresentation === "overlay") {
51
+ for (const other of panels)
52
+ if (other !== panel) other.collapsed.value = true;
53
+ }
43
54
  })
44
55
  );
45
56
  for (const panel of panels) {
@@ -50,7 +61,8 @@ function wireSidebar(root, { panels, deviceClass, storage = defaultStorage() })
50
61
  if (collapsed === previous) return;
51
62
  previous = collapsed;
52
63
  const element = panelElement(panel.id);
53
- if (!collapsed && element) {
64
+ const replaced = deviceClass?.value.compact && compactPresentation === "hidden";
65
+ if (!collapsed && element && !replaced) {
54
66
  (focusables(element)[0] ?? element).focus();
55
67
  } else if (collapsed) {
56
68
  returnFocus.get(panel.id)?.focus();
@@ -68,15 +80,20 @@ function wireSidebar(root, { panels, deviceClass, storage = defaultStorage() })
68
80
  disposers.push(
69
81
  effect(() => {
70
82
  const compact = deviceClass.value.compact;
71
- const open = compact ? openPanel() : void 0;
72
- root.dataset.collapsibleOverlay = String(compact);
73
- if (compact && open) {
83
+ const overlay = compact && compactPresentation === "overlay";
84
+ const open = overlay ? openPanel() : void 0;
85
+ root.dataset.collapsibleResponsive = compact ? compactPresentation : "inline";
86
+ root.dataset.collapsibleOverlay = String(overlay);
87
+ if (overlay && open) {
74
88
  if (!backdrop) {
75
89
  backdrop = ownerDocument.createElement("button");
76
90
  backdrop.type = "button";
77
91
  backdrop.className = "kui-collapsible-panel__backdrop";
78
92
  backdrop.setAttribute("aria-label", "Close");
79
- backdrop.addEventListener("click", () => collapse(open));
93
+ backdrop.addEventListener("click", () => {
94
+ const current = openPanel();
95
+ if (current) collapse(current);
96
+ });
80
97
  const element = panelElement(open.id);
81
98
  element?.parentElement?.insertBefore(backdrop, element);
82
99
  }
@@ -87,7 +104,8 @@ function wireSidebar(root, { panels, deviceClass, storage = defaultStorage() })
87
104
  })
88
105
  );
89
106
  const onKeydown = (event) => {
90
- if (!deviceClass.value.compact) return;
107
+ if (!deviceClass.value.compact || compactPresentation !== "overlay")
108
+ return;
91
109
  const open = openPanel();
92
110
  if (!open) return;
93
111
  if (event.key === "Escape") {
@@ -120,6 +138,7 @@ function wireSidebar(root, { panels, deviceClass, storage = defaultStorage() })
120
138
  return () => {
121
139
  for (const dispose of disposers.splice(0)) dispose();
122
140
  delete root.dataset.collapsibleOverlay;
141
+ delete root.dataset.collapsibleResponsive;
123
142
  };
124
143
  }
125
144
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/wire-sidebar.ts"],"names":[],"mappings":"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","file":"wire-sidebar.js","sourcesContent":["import { delegate, effect, type ReadonlySignal, type Signal } from 'kerfjs';\n\nimport type { DeviceClass } from './device-class.js';\n\n/** Minimal `localStorage`-shaped store, so the persistence hook is testable. */\nexport interface SidebarStorage {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n}\n\nexport interface WireSidebarPanel {\n /** The panel id — matches `CollapsiblePanel`'s `id` and a toggle's `panelId`. */\n id: string;\n /** The app-owned collapsed signal. `wireSidebar` reads it (focus, overlay) and\n * writes it (toggle, Escape, backdrop, persistence). */\n collapsed: Signal<boolean>;\n /** `data-action` value the panel's toggle button(s) carry. */\n toggleAction: string;\n /** When set, the collapsed state is loaded from and saved to `storage` under\n * this key (a persistence hook), so the panel remembers its state. */\n storageKey?: string;\n}\n\nexport interface WireSidebarOptions {\n panels: readonly WireSidebarPanel[];\n /**\n * When provided, the sidebar adopts a compact **overlay** presentation while\n * `deviceClass.compact` is true: an open panel floats over the content with a\n * dismissable backdrop, Escape and backdrop-click collapse it, and focus is\n * trapped within the open panel (the ARIA dialog pattern). Without it the panel\n * is always inline.\n */\n deviceClass?: ReadonlySignal<DeviceClass>;\n /** Persistence store (default `globalThis.localStorage`, if present). */\n storage?: SidebarStorage;\n}\n\nconst FOCUSABLE =\n 'a[href],area[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex=\"-1\"])';\n\nfunction focusables(panel: HTMLElement): HTMLElement[] {\n // Focus only ever targets an OPEN panel, so the selector (which already drops\n // disabled controls and tabindex=\"-1\") is sufficient without a layout-based\n // visibility check that a headless DOM can't answer.\n return [...panel.querySelectorAll<HTMLElement>(FOCUSABLE)];\n}\n\nfunction defaultStorage(): SidebarStorage | undefined {\n try {\n return globalThis.localStorage ?? undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The reusable sidebar-semantics layer for {@link CollapsiblePanel}s: toggle\n * delegation with focus restore, focus-into on open, an optional compact overlay\n * (backdrop + Escape + focus trap) driven by {@link deviceClass}, and an optional\n * persistence hook. The app owns each `collapsed` signal and the layout; this wire\n * owns the interaction. Returns a disposer. See `docs/24-collapsible-panel.md`.\n */\nexport function wireSidebar(\n root: HTMLElement,\n { panels, deviceClass, storage = defaultStorage() }: WireSidebarOptions,\n): () => void {\n const ownerDocument = root.ownerDocument;\n const byAction = new Map(panels.map((panel) => [panel.toggleAction, panel]));\n const returnFocus = new Map<string, HTMLElement>();\n const disposers: Array<() => void> = [];\n\n // Persistence: seed from storage, then mirror on change.\n for (const panel of panels) {\n if (!panel.storageKey || !storage) continue;\n const stored = storage.getItem(panel.storageKey);\n if (stored === 'true' || stored === 'false')\n panel.collapsed.value = stored === 'true';\n disposers.push(\n effect(() => {\n storage.setItem(panel.storageKey!, String(panel.collapsed.value));\n }),\n );\n }\n\n const panelElement = (id: string): HTMLElement | null =>\n root.querySelector<HTMLElement>(`[data-collapsible-panel=\"${id}\"]`);\n\n const collapse = (panel: WireSidebarPanel): void => {\n if (panel.collapsed.value) return;\n panel.collapsed.value = true;\n };\n\n // Toggle delegation. The trigger is remembered so focus restores to it on close.\n disposers.push(\n delegate(root, 'click', '[data-action]', (_event, element) => {\n const trigger = element as HTMLElement;\n const panel = byAction.get(trigger.dataset.action ?? '');\n if (!panel) return;\n returnFocus.set(panel.id, trigger);\n panel.collapsed.value = !panel.collapsed.value;\n }),\n );\n\n // Focus: move into the panel when it opens; restore to the trigger when it closes.\n for (const panel of panels) {\n let previous = panel.collapsed.value;\n disposers.push(\n effect(() => {\n const collapsed = panel.collapsed.value;\n if (collapsed === previous) return;\n previous = collapsed;\n const element = panelElement(panel.id);\n if (!collapsed && element) {\n (focusables(element)[0] ?? element).focus();\n } else if (collapsed) {\n returnFocus.get(panel.id)?.focus();\n }\n }),\n );\n }\n\n // Compact overlay: a dismissable backdrop + Escape + focus trap while compact\n // and a panel is open.\n if (deviceClass) {\n let backdrop: HTMLButtonElement | undefined;\n const openPanel = (): WireSidebarPanel | undefined =>\n panels.find((panel) => !panel.collapsed.value);\n const removeBackdrop = (): void => {\n backdrop?.remove();\n backdrop = undefined;\n };\n disposers.push(\n effect(() => {\n const compact = deviceClass.value.compact;\n const open = compact ? openPanel() : undefined;\n root.dataset.collapsibleOverlay = String(compact);\n if (compact && open) {\n if (!backdrop) {\n backdrop = ownerDocument.createElement('button');\n backdrop.type = 'button';\n backdrop.className = 'kui-collapsible-panel__backdrop';\n backdrop.setAttribute('aria-label', 'Close');\n backdrop.addEventListener('click', () => collapse(open));\n const element = panelElement(open.id);\n element?.parentElement?.insertBefore(backdrop, element);\n }\n } else {\n removeBackdrop();\n if (!compact) delete root.dataset.collapsibleOverlay;\n }\n }),\n );\n\n const onKeydown = (event: KeyboardEvent): void => {\n if (!deviceClass.value.compact) return;\n const open = openPanel();\n if (!open) return;\n if (event.key === 'Escape') {\n event.preventDefault();\n collapse(open);\n return;\n }\n if (event.key !== 'Tab') return;\n const element = panelElement(open.id);\n if (!element) return;\n const items = focusables(element);\n if (items.length === 0) return;\n const first = items[0]!;\n const last = items[items.length - 1]!;\n const active = ownerDocument.activeElement;\n if (event.shiftKey && active === first) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && active === last) {\n event.preventDefault();\n first.focus();\n }\n };\n ownerDocument.addEventListener('keydown', onKeydown);\n disposers.push(() =>\n ownerDocument.removeEventListener('keydown', onKeydown),\n );\n disposers.push(removeBackdrop);\n }\n\n return () => {\n for (const dispose of disposers.splice(0)) dispose();\n delete root.dataset.collapsibleOverlay;\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/wire-sidebar.ts"],"names":[],"mappings":"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","file":"wire-sidebar.js","sourcesContent":["import { delegate, effect, type ReadonlySignal, type Signal } from 'kerfjs';\n\nimport type { DeviceClass } from './device-class.js';\n\n/** Minimal `localStorage`-shaped store, so the persistence hook is testable. */\nexport interface SidebarStorage {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n}\n\nexport interface WireSidebarPanel {\n /** The panel id — matches `CollapsiblePanel`'s `id` and a toggle's `panelId`. */\n id: string;\n /** The app-owned collapsed signal. `wireSidebar` reads it (focus, overlay) and\n * writes it (toggle, Escape, backdrop, persistence). */\n collapsed: Signal<boolean>;\n /** `data-action` value the panel's toggle button(s) carry. */\n toggleAction: string;\n /** When set, the collapsed state is loaded from and saved to `storage` under\n * this key (a persistence hook), so the panel remembers its state. */\n storageKey?: string;\n}\n\nexport interface WireSidebarOptions {\n panels: readonly WireSidebarPanel[];\n /**\n * When provided, the sidebar adopts a compact **overlay** presentation while\n * `deviceClass.compact` is true: an open panel floats over the content with a\n * dismissable backdrop, Escape and backdrop-click collapse it, and focus is\n * trapped within the open panel (the ARIA dialog pattern). Without it the panel\n * is always inline.\n */\n deviceClass?: ReadonlySignal<DeviceClass>;\n /** Compact devices either overlay the panels (default) or hide them in favor\n * of an application-owned responsive replacement. */\n compactPresentation?: 'overlay' | 'hidden';\n /** Collapse the other panels when one opens in compact overlay mode. */\n exclusiveCompact?: boolean;\n /** Persistence store (default `globalThis.localStorage`, if present). */\n storage?: SidebarStorage;\n}\n\nconst FOCUSABLE =\n 'a[href],area[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex=\"-1\"])';\n\nfunction focusables(panel: HTMLElement): HTMLElement[] {\n // Focus only ever targets an OPEN panel, so the selector (which already drops\n // disabled controls and tabindex=\"-1\") is sufficient without a layout-based\n // visibility check that a headless DOM can't answer.\n return [...panel.querySelectorAll<HTMLElement>(FOCUSABLE)];\n}\n\nfunction defaultStorage(): SidebarStorage | undefined {\n try {\n return globalThis.localStorage ?? undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The reusable sidebar-semantics layer for {@link CollapsiblePanel}s: toggle\n * delegation with focus restore, focus-into on open, an optional compact overlay\n * (backdrop + Escape + focus trap) driven by {@link deviceClass}, and an optional\n * persistence hook. The app owns each `collapsed` signal and the layout; this wire\n * owns the interaction. Returns a disposer. See `docs/24-collapsible-panel.md`.\n */\nexport function wireSidebar(\n root: HTMLElement,\n {\n panels,\n deviceClass,\n compactPresentation = 'overlay',\n exclusiveCompact = true,\n storage = defaultStorage(),\n }: WireSidebarOptions,\n): () => void {\n const ownerDocument = root.ownerDocument;\n const byAction = new Map(panels.map((panel) => [panel.toggleAction, panel]));\n const returnFocus = new Map<string, HTMLElement>();\n const disposers: Array<() => void> = [];\n\n // Persistence: seed from storage, then mirror on change.\n for (const panel of panels) {\n if (!panel.storageKey || !storage) continue;\n const stored = storage.getItem(panel.storageKey);\n if (stored === 'true' || stored === 'false')\n panel.collapsed.value = stored === 'true';\n disposers.push(\n effect(() => {\n storage.setItem(panel.storageKey!, String(panel.collapsed.value));\n }),\n );\n }\n\n const panelElement = (id: string): HTMLElement | null =>\n root.querySelector<HTMLElement>(`[data-collapsible-panel=\"${id}\"]`);\n\n const collapse = (panel: WireSidebarPanel): void => {\n if (panel.collapsed.value) return;\n panel.collapsed.value = true;\n };\n\n // Toggle delegation. The trigger is remembered so focus restores to it on close.\n disposers.push(\n delegate(root, 'click', '[data-action]', (_event, element) => {\n const trigger = element as HTMLElement;\n const panel = byAction.get(trigger.dataset.action ?? '');\n if (!panel) return;\n returnFocus.set(panel.id, trigger);\n const next = !panel.collapsed.value;\n panel.collapsed.value = next;\n if (\n !next &&\n exclusiveCompact &&\n deviceClass?.value.compact &&\n compactPresentation === 'overlay'\n ) {\n for (const other of panels)\n if (other !== panel) other.collapsed.value = true;\n }\n }),\n );\n\n // Focus: move into the panel when it opens; restore to the trigger when it closes.\n for (const panel of panels) {\n let previous = panel.collapsed.value;\n disposers.push(\n effect(() => {\n const collapsed = panel.collapsed.value;\n if (collapsed === previous) return;\n previous = collapsed;\n const element = panelElement(panel.id);\n const replaced =\n deviceClass?.value.compact && compactPresentation === 'hidden';\n if (!collapsed && element && !replaced) {\n (focusables(element)[0] ?? element).focus();\n } else if (collapsed) {\n returnFocus.get(panel.id)?.focus();\n }\n }),\n );\n }\n\n // Compact overlay: a dismissable backdrop + Escape + focus trap while compact\n // and a panel is open.\n if (deviceClass) {\n let backdrop: HTMLButtonElement | undefined;\n const openPanel = (): WireSidebarPanel | undefined =>\n panels.find((panel) => !panel.collapsed.value);\n const removeBackdrop = (): void => {\n backdrop?.remove();\n backdrop = undefined;\n };\n disposers.push(\n effect(() => {\n const compact = deviceClass.value.compact;\n const overlay = compact && compactPresentation === 'overlay';\n const open = overlay ? openPanel() : undefined;\n root.dataset.collapsibleResponsive = compact\n ? compactPresentation\n : 'inline';\n root.dataset.collapsibleOverlay = String(overlay);\n if (overlay && open) {\n if (!backdrop) {\n backdrop = ownerDocument.createElement('button');\n backdrop.type = 'button';\n backdrop.className = 'kui-collapsible-panel__backdrop';\n backdrop.setAttribute('aria-label', 'Close');\n backdrop.addEventListener('click', () => {\n const current = openPanel();\n if (current) collapse(current);\n });\n const element = panelElement(open.id);\n element?.parentElement?.insertBefore(backdrop, element);\n }\n } else {\n removeBackdrop();\n if (!compact) delete root.dataset.collapsibleOverlay;\n }\n }),\n );\n\n const onKeydown = (event: KeyboardEvent): void => {\n if (!deviceClass.value.compact || compactPresentation !== 'overlay')\n return;\n const open = openPanel();\n if (!open) return;\n if (event.key === 'Escape') {\n event.preventDefault();\n collapse(open);\n return;\n }\n if (event.key !== 'Tab') return;\n const element = panelElement(open.id);\n if (!element) return;\n const items = focusables(element);\n if (items.length === 0) return;\n const first = items[0]!;\n const last = items[items.length - 1]!;\n const active = ownerDocument.activeElement;\n if (event.shiftKey && active === first) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && active === last) {\n event.preventDefault();\n first.focus();\n }\n };\n ownerDocument.addEventListener('keydown', onKeydown);\n disposers.push(() =>\n ownerDocument.removeEventListener('keydown', onKeydown),\n );\n disposers.push(removeBackdrop);\n }\n\n return () => {\n for (const dispose of disposers.splice(0)) dispose();\n delete root.dataset.collapsibleOverlay;\n delete root.dataset.collapsibleResponsive;\n };\n}\n"]}
@@ -1,4 +1,4 @@
1
- export { wireTokenSearchFields } from './chunk-OJWWHFH3.js';
1
+ export { wireTokenSearchFields } from './chunk-JFGXQBIP.js';
2
2
 
3
3
 
4
4
 
@@ -1,4 +1,5 @@
1
1
  import { SafeHtml } from 'kerfjs';
2
+ import { ResizableRegionSeparator, ResizableRegionCollapseMotion, ResizableRegionContentOverflow, ResizableRegionPresentation, ResizableRegionRestorePosition } from './resizable-region.js';
2
3
 
3
4
  /** A collapsible Workbench panel — a side rail or the bottom drawer. */
4
5
  interface WorkbenchPanel {
@@ -9,6 +10,13 @@ interface WorkbenchPanel {
9
10
  size?: number;
10
11
  /** Accessible name for the panel region. */
11
12
  label?: string;
13
+ separator?: ResizableRegionSeparator;
14
+ collapseMotion?: ResizableRegionCollapseMotion;
15
+ contentOverflow?: ResizableRegionContentOverflow;
16
+ presentation?: ResizableRegionPresentation;
17
+ /** Control shown in a safe-area-aware viewport corner while collapsed. */
18
+ restoreControl?: SafeHtml;
19
+ restorePosition?: ResizableRegionRestorePosition;
12
20
  }
13
21
  interface WorkbenchProps {
14
22
  id: string;