@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.
- package/README.md +44 -23
- package/ai/application-ui-diagnostic-ids-v1.json +3 -0
- package/ai/application-ui-profile.defaults.json +1 -1
- package/ai/compile-time-contracts-v1.json +3 -11
- package/ai/component-catalog-v2-overrides.json +7 -37
- package/ai/component-catalog-v2.d.ts +1 -0
- package/ai/component-catalog-v2.json +415 -91
- package/ai/component-catalog-v2.schema.json +2 -1
- package/ai/component-catalog.json +602 -158
- package/ai/public-api-signatures-v1.md +132 -65
- package/ai/skill.md +20 -20
- package/ai/webawesome-jsx-signatures-v1.md +2 -1
- package/analyzer/cli.mjs +5 -2
- package/analyzer/index.d.mts +1 -0
- package/analyzer/index.mjs +117 -10
- package/dist/app-tab.d.ts +10 -2
- package/dist/app-tab.js +1 -1
- package/dist/browser/list-action-row.js +1 -0
- package/dist/browser/list-item.js +1 -0
- package/dist/browser/surface-scaffold.js +3 -0
- package/dist/catalog.js +5 -4
- package/dist/catalog.js.map +1 -1
- package/dist/{chunk-FEAMR7S4.js → chunk-4EF2CMYS.js} +24 -5
- package/dist/chunk-4EF2CMYS.js.map +1 -0
- package/dist/{chunk-NWSEP5RT.js → chunk-FRVS5RFM.js} +23 -5
- package/dist/chunk-FRVS5RFM.js.map +1 -0
- package/dist/{chunk-PSKN344N.js → chunk-GWEAPEZI.js} +15 -2
- package/dist/chunk-GWEAPEZI.js.map +1 -0
- package/dist/chunk-HP2B2L26.js +102 -0
- package/dist/chunk-HP2B2L26.js.map +1 -0
- package/dist/{chunk-OJWWHFH3.js → chunk-JFGXQBIP.js} +49 -23
- package/dist/chunk-JFGXQBIP.js.map +1 -0
- package/dist/chunk-OBLTAKSX.js +43 -0
- package/dist/chunk-OBLTAKSX.js.map +1 -0
- package/dist/{chunk-Y45PPBDV.js → chunk-OT6RPYAP.js} +16 -2
- package/dist/chunk-OT6RPYAP.js.map +1 -0
- package/dist/{chunk-UFZKLBCF.js → chunk-VCE4MPVE.js} +25 -3
- package/dist/chunk-VCE4MPVE.js.map +1 -0
- package/dist/{chunk-5VTB2BKP.js → chunk-WNWD54GR.js} +16 -2
- package/dist/chunk-WNWD54GR.js.map +1 -0
- package/dist/{chunk-M7JBER5U.js → chunk-X2U3QJCJ.js} +8 -2
- package/dist/chunk-X2U3QJCJ.js.map +1 -0
- package/dist/{chunk-DPDBVKNA.js → chunk-YMKH5XTF.js} +5 -3
- package/dist/chunk-YMKH5XTF.js.map +1 -0
- package/dist/collapsible-panel.d.ts +9 -1
- package/dist/collapsible-panel.js +36 -15
- package/dist/collapsible-panel.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +12 -12
- package/dist/list-action-row.d.ts +11 -1
- package/dist/list-action-row.js +2 -1
- package/dist/list-header.d.ts +15 -2
- package/dist/list-header.js +1 -1
- package/dist/list-item.d.ts +12 -1
- package/dist/list-item.js +2 -1
- package/dist/resizable-region.d.ts +19 -2
- package/dist/resizable-region.js +1 -1
- package/dist/select.d.ts +12 -1
- package/dist/select.js +1 -1
- package/dist/split-view.js +1 -1
- package/dist/styles/app-tab.css +26 -0
- package/dist/styles/collapsible-panel.css +66 -4
- package/dist/styles/list-action-row.css +64 -0
- package/dist/styles/list-header.css +32 -0
- package/dist/styles/list-item.css +39 -0
- package/dist/styles/resizable-region.css +132 -0
- package/dist/styles/select.css +68 -0
- package/dist/styles/styles.css +1 -1
- package/dist/styles/surface-scaffold.css +51 -0
- package/dist/styles/tab-bar.css +24 -0
- package/dist/styles/toolbar-control-group.css +73 -1
- package/dist/styles/toolbar-text.css +1 -1
- package/dist/styles/webawesome.css +12 -0
- package/dist/styles/workbench.css +89 -0
- package/dist/surface-scaffold.d.ts +25 -0
- package/dist/surface-scaffold.js +3 -0
- package/dist/surface-scaffold.js.map +1 -0
- package/dist/tab-bar.d.ts +11 -2
- package/dist/tab-bar.js +1 -1
- package/dist/toolbar-control-group.d.ts +16 -2
- package/dist/toolbar-control-group.js +1 -1
- package/dist/webawesome.d.ts +1 -0
- package/dist/wire-resizable-regions.js +1 -1
- package/dist/wire-sidebar.d.ts +6 -1
- package/dist/wire-sidebar.js +27 -8
- package/dist/wire-sidebar.js.map +1 -1
- package/dist/wire-token-search-fields.js +1 -1
- package/dist/workbench.d.ts +8 -0
- package/dist/workbench.js +42 -0
- package/dist/workbench.js.map +1 -1
- package/docs/accessibility.md +55 -12
- package/docs/collapsible-panel.md +7 -0
- package/docs/component-contract.md +5 -8
- package/docs/component-selection.md +63 -33
- package/docs/design/templates.md +5 -7
- package/docs/examples/component-catalog-extension-v2.json +3 -6
- package/docs/examples/component-catalog-extension.json +1 -1
- package/docs/layout.md +7 -9
- package/docs/recipes.md +8 -7
- package/docs/surface-scaffold.md +17 -0
- package/docs/type-contracts.md +6 -8
- package/docs/ui-analyzer.md +19 -1
- package/docs/ui-doctor.md +1 -1
- package/docs/ux-demo.md +6 -3
- package/docs/webawesome-theme.md +4 -1
- package/docs/workbench.md +49 -2
- package/doctor/eslint-diagnostics.d.mts +16 -0
- package/doctor/eslint-diagnostics.mjs +17 -0
- package/doctor/index.mjs +16 -1
- package/llms.txt +19 -9
- package/package.json +7 -7
- package/ux-demo/recipes/app-shell.tsx +36 -23
- package/ux-demo/recipes/collapsible-sidebar.tsx +22 -16
- package/ux-demo/recipes/composer-form.tsx +15 -6
- package/ux-demo/recipes/list-detail-dialog.tsx +31 -15
- package/ux-demo/recipes/list-workspace-states.tsx +16 -7
- package/ux-demo/recipes/loading-inspector.tsx +39 -18
- package/ux-demo/recipes/recipes.css +7 -2
- package/ux-demo/recipes/workspace-header.tsx +17 -7
- package/dist/browser/panel-header.js +0 -7
- package/dist/chunk-5VTB2BKP.js.map +0 -1
- package/dist/chunk-7XRDUG4Y.js +0 -58
- package/dist/chunk-7XRDUG4Y.js.map +0 -1
- package/dist/chunk-DPDBVKNA.js.map +0 -1
- package/dist/chunk-FEAMR7S4.js.map +0 -1
- package/dist/chunk-M7JBER5U.js.map +0 -1
- package/dist/chunk-NENBUYX5.js +0 -79
- package/dist/chunk-NENBUYX5.js.map +0 -1
- package/dist/chunk-NWSEP5RT.js.map +0 -1
- package/dist/chunk-OJWWHFH3.js.map +0 -1
- package/dist/chunk-PSKN344N.js.map +0 -1
- package/dist/chunk-UFZKLBCF.js.map +0 -1
- package/dist/chunk-Y45PPBDV.js.map +0 -1
- package/dist/panel-header.d.ts +0 -42
- package/dist/panel-header.js +0 -7
- package/dist/panel-header.js.map +0 -1
- package/dist/styles/panel-header.css +0 -64
- package/docs/design/templates/panel-header/icon-actions-dark.svg +0 -1
- package/docs/design/templates/panel-header/icon-actions.svg +0 -1
- package/docs/design/templates/panel-header/icon-summary-actions-dark.svg +0 -1
- package/docs/design/templates/panel-header/icon-summary-actions.svg +0 -1
- package/docs/design/templates/panel-header/no-icon-dark.svg +0 -1
- package/docs/design/templates/panel-header/no-icon.svg +0 -1
- package/docs/design/templates/panel-header/page-heading-dark.svg +0 -1
- package/docs/design/templates/panel-header/page-heading.svg +0 -1
- package/docs/design/templates/panel-header/title-only-dark.svg +0 -1
- package/docs/design/templates/panel-header/title-only.svg +0 -1
- package/docs/design/templates/panel-header-dark.svg +0 -1
- 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
|
+
}
|
package/dist/styles/tab-bar.css
CHANGED
|
@@ -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
|
|
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
|
|
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 @@
|
|
|
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
|
@@ -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 };
|
package/dist/webawesome.d.ts
CHANGED
package/dist/wire-sidebar.d.ts
CHANGED
|
@@ -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 };
|
package/dist/wire-sidebar.js
CHANGED
|
@@ -12,7 +12,13 @@ function defaultStorage() {
|
|
|
12
12
|
return void 0;
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
-
function wireSidebar(root, {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
72
|
-
|
|
73
|
-
|
|
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", () =>
|
|
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)
|
|
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
|
|
package/dist/wire-sidebar.js.map
CHANGED
|
@@ -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":";;;AA0CA,IAAM,SAAA,GACJ,iJAAA;AAEF,SAAS,WAAW,KAAA,EAAmC;AAIrD,EAAA,OAAO,CAAC,GAAG,KAAA,CAAM,gBAAA,CAA8B,SAAS,CAAC,CAAA;AAC3D;AAEA,SAAS,cAAA,GAA6C;AACpD,EAAA,IAAI;AACF,IAAA,OAAO,WAAW,YAAA,IAAgB,KAAA,CAAA;AAAA,EACpC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,MAAA;AAAA,EACT;AACF;AASO,SAAS,YACd,IAAA,EACA;AAAA,EACE,MAAA;AAAA,EACA,WAAA;AAAA,EACA,mBAAA,GAAsB,SAAA;AAAA,EACtB,gBAAA,GAAmB,IAAA;AAAA,EACnB,UAAU,cAAA;AACZ,CAAA,EACY;AACZ,EAAA,MAAM,gBAAgB,IAAA,CAAK,aAAA;AAC3B,EAAA,MAAM,QAAA,GAAW,IAAI,GAAA,CAAI,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,KAAU,CAAC,KAAA,CAAM,YAAA,EAAc,KAAK,CAAC,CAAC,CAAA;AAC3E,EAAA,MAAM,WAAA,uBAAkB,GAAA,EAAyB;AACjD,EAAA,MAAM,YAA+B,EAAC;AAGtC,EAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,IAAA,IAAI,CAAC,KAAA,CAAM,UAAA,IAAc,CAAC,OAAA,EAAS;AACnC,IAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,OAAA,CAAQ,KAAA,CAAM,UAAU,CAAA;AAC/C,IAAA,IAAI,MAAA,KAAW,UAAU,MAAA,KAAW,OAAA;AAClC,MAAA,KAAA,CAAM,SAAA,CAAU,QAAQ,MAAA,KAAW,MAAA;AACrC,IAAA,SAAA,CAAU,IAAA;AAAA,MACR,OAAO,MAAM;AACX,QAAA,OAAA,CAAQ,QAAQ,KAAA,CAAM,UAAA,EAAa,OAAO,KAAA,CAAM,SAAA,CAAU,KAAK,CAAC,CAAA;AAAA,MAClE,CAAC;AAAA,KACH;AAAA,EACF;AAEA,EAAA,MAAM,eAAe,CAAC,EAAA,KACpB,KAAK,aAAA,CAA2B,CAAA,yBAAA,EAA4B,EAAE,CAAA,EAAA,CAAI,CAAA;AAEpE,EAAA,MAAM,QAAA,GAAW,CAAC,KAAA,KAAkC;AAClD,IAAA,IAAI,KAAA,CAAM,UAAU,KAAA,EAAO;AAC3B,IAAA,KAAA,CAAM,UAAU,KAAA,GAAQ,IAAA;AAAA,EAC1B,CAAA;AAGA,EAAA,SAAA,CAAU,IAAA;AAAA,IACR,SAAS,IAAA,EAAM,OAAA,EAAS,eAAA,EAAiB,CAAC,QAAQ,OAAA,KAAY;AAC5D,MAAA,MAAM,OAAA,GAAU,OAAA;AAChB,MAAA,MAAM,QAAQ,QAAA,CAAS,GAAA,CAAI,OAAA,CAAQ,OAAA,CAAQ,UAAU,EAAE,CAAA;AACvD,MAAA,IAAI,CAAC,KAAA,EAAO;AACZ,MAAA,WAAA,CAAY,GAAA,CAAI,KAAA,CAAM,EAAA,EAAI,OAAO,CAAA;AACjC,MAAA,MAAM,IAAA,GAAO,CAAC,KAAA,CAAM,SAAA,CAAU,KAAA;AAC9B,MAAA,KAAA,CAAM,UAAU,KAAA,GAAQ,IAAA;AACxB,MAAA,IACE,CAAC,IAAA,IACD,gBAAA,IACA,aAAa,KAAA,CAAM,OAAA,IACnB,wBAAwB,SAAA,EACxB;AACA,QAAA,KAAA,MAAW,KAAA,IAAS,MAAA;AAClB,UAAA,IAAI,KAAA,KAAU,KAAA,EAAO,KAAA,CAAM,SAAA,CAAU,KAAA,GAAQ,IAAA;AAAA,MACjD;AAAA,IACF,CAAC;AAAA,GACH;AAGA,EAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,IAAA,IAAI,QAAA,GAAW,MAAM,SAAA,CAAU,KAAA;AAC/B,IAAA,SAAA,CAAU,IAAA;AAAA,MACR,OAAO,MAAM;AACX,QAAA,MAAM,SAAA,GAAY,MAAM,SAAA,CAAU,KAAA;AAClC,QAAA,IAAI,cAAc,QAAA,EAAU;AAC5B,QAAA,QAAA,GAAW,SAAA;AACX,QAAA,MAAM,OAAA,GAAU,YAAA,CAAa,KAAA,CAAM,EAAE,CAAA;AACrC,QAAA,MAAM,QAAA,GACJ,WAAA,EAAa,KAAA,CAAM,OAAA,IAAW,mBAAA,KAAwB,QAAA;AACxD,QAAA,IAAI,CAAC,SAAA,IAAa,OAAA,IAAW,CAAC,QAAA,EAAU;AACtC,UAAA,CAAC,WAAW,OAAO,CAAA,CAAE,CAAC,CAAA,IAAK,SAAS,KAAA,EAAM;AAAA,QAC5C,WAAW,SAAA,EAAW;AACpB,UAAA,WAAA,CAAY,GAAA,CAAI,KAAA,CAAM,EAAE,CAAA,EAAG,KAAA,EAAM;AAAA,QACnC;AAAA,MACF,CAAC;AAAA,KACH;AAAA,EACF;AAIA,EAAA,IAAI,WAAA,EAAa;AACf,IAAA,IAAI,QAAA;AACJ,IAAA,MAAM,SAAA,GAAY,MAChB,MAAA,CAAO,IAAA,CAAK,CAAC,KAAA,KAAU,CAAC,KAAA,CAAM,SAAA,CAAU,KAAK,CAAA;AAC/C,IAAA,MAAM,iBAAiB,MAAY;AACjC,MAAA,QAAA,EAAU,MAAA,EAAO;AACjB,MAAA,QAAA,GAAW,MAAA;AAAA,IACb,CAAA;AACA,IAAA,SAAA,CAAU,IAAA;AAAA,MACR,OAAO,MAAM;AACX,QAAA,MAAM,OAAA,GAAU,YAAY,KAAA,CAAM,OAAA;AAClC,QAAA,MAAM,OAAA,GAAU,WAAW,mBAAA,KAAwB,SAAA;AACnD,QAAA,MAAM,IAAA,GAAO,OAAA,GAAU,SAAA,EAAU,GAAI,MAAA;AACrC,QAAA,IAAA,CAAK,OAAA,CAAQ,qBAAA,GAAwB,OAAA,GACjC,mBAAA,GACA,QAAA;AACJ,QAAA,IAAA,CAAK,OAAA,CAAQ,kBAAA,GAAqB,MAAA,CAAO,OAAO,CAAA;AAChD,QAAA,IAAI,WAAW,IAAA,EAAM;AACnB,UAAA,IAAI,CAAC,QAAA,EAAU;AACb,YAAA,QAAA,GAAW,aAAA,CAAc,cAAc,QAAQ,CAAA;AAC/C,YAAA,QAAA,CAAS,IAAA,GAAO,QAAA;AAChB,YAAA,QAAA,CAAS,SAAA,GAAY,iCAAA;AACrB,YAAA,QAAA,CAAS,YAAA,CAAa,cAAc,OAAO,CAAA;AAC3C,YAAA,QAAA,CAAS,gBAAA,CAAiB,SAAS,MAAM;AACvC,cAAA,MAAM,UAAU,SAAA,EAAU;AAC1B,cAAA,IAAI,OAAA,WAAkB,OAAO,CAAA;AAAA,YAC/B,CAAC,CAAA;AACD,YAAA,MAAM,OAAA,GAAU,YAAA,CAAa,IAAA,CAAK,EAAE,CAAA;AACpC,YAAA,OAAA,EAAS,aAAA,EAAe,YAAA,CAAa,QAAA,EAAU,OAAO,CAAA;AAAA,UACxD;AAAA,QACF,CAAA,MAAO;AACL,UAAA,cAAA,EAAe;AACf,UAAA,IAAI,CAAC,OAAA,EAAS,OAAO,IAAA,CAAK,OAAA,CAAQ,kBAAA;AAAA,QACpC;AAAA,MACF,CAAC;AAAA,KACH;AAEA,IAAA,MAAM,SAAA,GAAY,CAAC,KAAA,KAA+B;AAChD,MAAA,IAAI,CAAC,WAAA,CAAY,KAAA,CAAM,OAAA,IAAW,mBAAA,KAAwB,SAAA;AACxD,QAAA;AACF,MAAA,MAAM,OAAO,SAAA,EAAU;AACvB,MAAA,IAAI,CAAC,IAAA,EAAM;AACX,MAAA,IAAI,KAAA,CAAM,QAAQ,QAAA,EAAU;AAC1B,QAAA,KAAA,CAAM,cAAA,EAAe;AACrB,QAAA,QAAA,CAAS,IAAI,CAAA;AACb,QAAA;AAAA,MACF;AACA,MAAA,IAAI,KAAA,CAAM,QAAQ,KAAA,EAAO;AACzB,MAAA,MAAM,OAAA,GAAU,YAAA,CAAa,IAAA,CAAK,EAAE,CAAA;AACpC,MAAA,IAAI,CAAC,OAAA,EAAS;AACd,MAAA,MAAM,KAAA,GAAQ,WAAW,OAAO,CAAA;AAChC,MAAA,IAAI,KAAA,CAAM,WAAW,CAAA,EAAG;AACxB,MAAA,MAAM,KAAA,GAAQ,MAAM,CAAC,CAAA;AACrB,MAAA,MAAM,IAAA,GAAO,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,CAAC,CAAA;AACnC,MAAA,MAAM,SAAS,aAAA,CAAc,aAAA;AAC7B,MAAA,IAAI,KAAA,CAAM,QAAA,IAAY,MAAA,KAAW,KAAA,EAAO;AACtC,QAAA,KAAA,CAAM,cAAA,EAAe;AACrB,QAAA,IAAA,CAAK,KAAA,EAAM;AAAA,MACb,CAAA,MAAA,IAAW,CAAC,KAAA,CAAM,QAAA,IAAY,WAAW,IAAA,EAAM;AAC7C,QAAA,KAAA,CAAM,cAAA,EAAe;AACrB,QAAA,KAAA,CAAM,KAAA,EAAM;AAAA,MACd;AAAA,IACF,CAAA;AACA,IAAA,aAAA,CAAc,gBAAA,CAAiB,WAAW,SAAS,CAAA;AACnD,IAAA,SAAA,CAAU,IAAA;AAAA,MAAK,MACb,aAAA,CAAc,mBAAA,CAAoB,SAAA,EAAW,SAAS;AAAA,KACxD;AACA,IAAA,SAAA,CAAU,KAAK,cAAc,CAAA;AAAA,EAC/B;AAEA,EAAA,OAAO,MAAM;AACX,IAAA,KAAA,MAAW,OAAA,IAAW,SAAA,CAAU,MAAA,CAAO,CAAC,GAAG,OAAA,EAAQ;AACnD,IAAA,OAAO,KAAK,OAAA,CAAQ,kBAAA;AACpB,IAAA,OAAO,KAAK,OAAA,CAAQ,qBAAA;AAAA,EACtB,CAAA;AACF","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"]}
|
package/dist/workbench.d.ts
CHANGED
|
@@ -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;
|