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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +3 -0
  2. package/ai/application-ui-diagnostic-ids-v1.json +3 -0
  3. package/ai/component-catalog.json +121 -9
  4. package/ai/component-composition.json +102 -6
  5. package/ai/components/nav-stack.md +3 -3
  6. package/ai/components/surface-scaffold.md +11 -2
  7. package/ai/components/toolbar-control-group.md +2 -0
  8. package/ai/components/toolbar-text.md +2 -0
  9. package/ai/components/toolbar.md +2 -0
  10. package/ai/public-api-signatures-v1.md +49 -121
  11. package/ai/skill.md +11 -1
  12. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  13. package/dist/browser/nav-stack.js +2 -0
  14. package/dist/browser/split-view.js +2 -0
  15. package/dist/catalog-resources.d.ts +3 -3
  16. package/dist/catalog.d.ts +3 -3
  17. package/dist/catalog.js +3 -3
  18. package/dist/{chunk-WJN3V2H5.js → chunk-2GOCOLQL.js} +18 -4
  19. package/dist/chunk-2GOCOLQL.js.map +1 -0
  20. package/dist/{chunk-XIZRXI7N.js → chunk-A7DYEERU.js} +13 -1
  21. package/dist/chunk-A7DYEERU.js.map +1 -0
  22. package/dist/{chunk-6VQPLV3O.js → chunk-DSLRNR43.js} +9 -8
  23. package/dist/chunk-DSLRNR43.js.map +1 -0
  24. package/dist/{chunk-AOAW55JN.js → chunk-DXQZ3BEO.js} +4 -1
  25. package/dist/chunk-DXQZ3BEO.js.map +1 -0
  26. package/dist/chunk-IKCUPTR2.js +170 -0
  27. package/dist/chunk-IKCUPTR2.js.map +1 -0
  28. package/dist/{chunk-Z6PE5T7N.js → chunk-JL4BBZP4.js} +4 -3
  29. package/dist/chunk-JL4BBZP4.js.map +1 -0
  30. package/dist/collapsible-panel.js +2 -2
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.js +5 -5
  33. package/dist/nav-stack-B1Qc2e5q.d.ts +101 -0
  34. package/dist/nav-stack.d.ts +11 -93
  35. package/dist/nav-stack.js +5 -1
  36. package/dist/pane.d.ts +5 -2
  37. package/dist/pane.js +1 -1
  38. package/dist/split-view.d.ts +9 -4
  39. package/dist/split-view.js +7 -3
  40. package/dist/split-view.js.map +1 -1
  41. package/dist/styles/components/actions/toolbar-control-group/toolbar-control-group.css +60 -13
  42. package/dist/styles/components/actions/toolbar-text/toolbar-text.css +7 -1
  43. package/dist/styles/components/layout/pane/pane.css +7 -0
  44. package/dist/styles/components/navigation/nav-stack/nav-stack.css +6 -12
  45. package/dist/styles/components/navigation/tab-scaffold/tab-scaffold.css +17 -0
  46. package/dist/styles/components/surfaces/surface-scaffold/surface-scaffold.css +55 -0
  47. package/dist/styles/nav-stack.css +6 -12
  48. package/dist/styles/pane.css +7 -0
  49. package/dist/styles/surface-scaffold.css +55 -0
  50. package/dist/styles/tab-scaffold.css +17 -0
  51. package/dist/styles/toolbar-control-group.css +60 -13
  52. package/dist/styles/toolbar-text.css +7 -1
  53. package/dist/surface-scaffold-register.d.ts +4 -0
  54. package/dist/surface-scaffold-register.js +59 -0
  55. package/dist/surface-scaffold-register.js.map +1 -0
  56. package/dist/surface-scaffold.d.ts +10 -2
  57. package/dist/surface-scaffold.js +1 -1
  58. package/dist/tab-scaffold.d.ts +2 -0
  59. package/dist/tab-scaffold.js +1 -0
  60. package/dist/tab-scaffold.js.map +1 -1
  61. package/dist/wire-toolbar-visibility.js +1 -1
  62. package/dist/workbench.d.ts +5 -13
  63. package/dist/workbench.js +1 -1
  64. package/docs/component-contract.md +8 -1
  65. package/docs/component-selection.md +1 -1
  66. package/docs/layout.md +20 -0
  67. package/docs/nav-stack.md +30 -18
  68. package/docs/recipes.md +5 -1
  69. package/docs/split-view.md +7 -4
  70. package/docs/surface-scaffold.md +46 -0
  71. package/docs/tab-scaffold.md +57 -0
  72. package/docs/ui-doctor.md +23 -0
  73. package/docs/workbench.md +3 -2
  74. package/doctor/component-style-checks.mjs +224 -0
  75. package/doctor/index.mjs +42 -13
  76. package/llms.txt +10 -0
  77. package/package.json +7 -2
  78. package/ux-demo/recipes/list-detail-dialog.tsx +4 -0
  79. package/ux-demo/recipes/navigation-stack.tsx +2 -2
  80. package/dist/chunk-6VQPLV3O.js.map +0 -1
  81. package/dist/chunk-AOAW55JN.js.map +0 -1
  82. package/dist/chunk-WJN3V2H5.js.map +0 -1
  83. package/dist/chunk-XIZRXI7N.js.map +0 -1
  84. package/dist/chunk-Z4EPMX25.js +0 -123
  85. package/dist/chunk-Z4EPMX25.js.map +0 -1
  86. package/dist/chunk-Z6PE5T7N.js.map +0 -1
@@ -1,12 +1,67 @@
1
1
  .kui-dialog-surface > wa-dialog {
2
+ --_kui-dialog-surface-width: 35rem;
3
+ --_kui-dialog-surface-resolved-gutter: max(
4
+ 0px,
5
+ var(--_kui-dialog-surface-gutter, 1rem)
6
+ );
2
7
  --width: min(35rem, calc(100vw - 2rem));
3
8
  }
4
9
  .kui-dialog-surface[data-size="small"] > wa-dialog {
10
+ --_kui-dialog-surface-width: 25rem;
5
11
  --width: min(25rem, calc(100vw - 2rem));
6
12
  }
7
13
  .kui-dialog-surface[data-size="large"] > wa-dialog {
14
+ --_kui-dialog-surface-width: 50rem;
8
15
  --width: min(50rem, calc(100vw - 2rem));
9
16
  }
17
+ .kui-dialog-surface[data-presentation="modal"][data-preferred-width]
18
+ > wa-dialog {
19
+ --width: min(
20
+ var(--_kui-dialog-surface-preferred-width),
21
+ calc(100vw - 2rem)
22
+ );
23
+ }
24
+ .kui-dialog-surface[data-presentation="modal"][data-viewport-gutter]
25
+ > wa-dialog {
26
+ --width: min(
27
+ var(
28
+ --_kui-dialog-surface-preferred-width,
29
+ var(--_kui-dialog-surface-width)
30
+ ),
31
+ max(
32
+ 0px,
33
+ calc(
34
+ 100vw - var(--_kui-dialog-surface-resolved-gutter) -
35
+ var(--_kui-dialog-surface-resolved-gutter)
36
+ )
37
+ )
38
+ );
39
+ }
40
+ /* Web Awesome has a separate max-width clamp; setting --width alone cannot
41
+ provide an 8px phone gutter. Omitted props retain its legacy geometry. */
42
+ .kui-dialog-surface[data-presentation="modal"][data-viewport-gutter]
43
+ > wa-dialog::part(dialog) {
44
+ max-width: max(
45
+ 0px,
46
+ calc(
47
+ 100vw - var(--_kui-dialog-surface-resolved-gutter) -
48
+ var(--_kui-dialog-surface-resolved-gutter)
49
+ )
50
+ );
51
+ }
52
+ .kui-dialog-surface[data-presentation="modal"][data-max-height]
53
+ > wa-dialog::part(dialog) {
54
+ max-height: max(
55
+ 0px,
56
+ min(
57
+ var(--_kui-dialog-surface-max-height, 100dvh),
58
+ calc(
59
+ 100dvh - var(--_kui-dialog-surface-resolved-gutter) -
60
+ var(--_kui-dialog-surface-resolved-gutter)
61
+ )
62
+ )
63
+ );
64
+ }
10
65
  .kui-dialog-surface[data-body-inset="none"] > wa-dialog::part(body),
11
66
  .kui-dialog-surface[data-footer-inset="none"] > wa-dialog::part(footer) {
12
67
  padding: 0;
@@ -71,10 +71,6 @@
71
71
  background: var(--kui-color-surface);
72
72
  }
73
73
 
74
- .kui-nav-stack__header {
75
- min-width: 0;
76
- }
77
-
78
74
  .kui-nav-stack__viewport {
79
75
  --kui-edge-inset-block-start: var(--_kui-nav-stack-safe-block-start);
80
76
  --kui-edge-inset-block-end: var(--_kui-nav-stack-safe-block-end);
@@ -106,11 +102,9 @@
106
102
  inset: 0;
107
103
  }
108
104
 
109
- .kui-nav-stack__view[data-appearance="sunken"] {
110
- background: var(
111
- --kui-sunken-panel-background,
112
- var(--kui-color-surface-lowered)
113
- );
105
+ .kui-nav-stack__view:has(> [data-component="pane"]) {
106
+ display: grid;
107
+ overflow: hidden;
114
108
  }
115
109
 
116
110
  /* Each view is its region's scroll owner: block insets become padding inside
@@ -180,9 +174,9 @@
180
174
  }
181
175
 
182
176
  .kui-nav-stack__bottom {
183
- padding-block-end: var(--_kui-nav-stack-safe-block-end);
184
- padding-inline: var(--_kui-nav-stack-safe-inline-start)
185
- var(--_kui-nav-stack-safe-inline-end);
177
+ --kui-edge-inset-block-end: var(--_kui-nav-stack-safe-block-end);
178
+ --kui-edge-inset-inline-start: var(--_kui-nav-stack-safe-inline-start);
179
+ --kui-edge-inset-inline-end: var(--_kui-nav-stack-safe-inline-end);
186
180
  border-block-start: 1px solid
187
181
  var(--_kui-nav-stack-bottom-divider, transparent);
188
182
  background: var(--kui-color-surface);
@@ -158,6 +158,13 @@
158
158
  var(--_kui-pane-content-block-end);
159
159
  }
160
160
 
161
+ /* Add one outer gutter without changing the inherited item-margin token.
162
+ Nested Row, List, and Grid therefore keep their own default 8px insets. */
163
+ .kui-pane[data-deep-inset="true"] > .kui-pane__content {
164
+ padding-inline: calc(var(--_kui-pane-safe-inline-start) + var(--kui-space-xs))
165
+ calc(var(--_kui-pane-safe-inline-end) + var(--kui-space-xs));
166
+ }
167
+
161
168
  .kui-pane__header ~ .kui-pane__content {
162
169
  --_kui-pane-content-block-start: 0px;
163
170
  }
@@ -1,12 +1,67 @@
1
1
  .kui-dialog-surface > wa-dialog {
2
+ --_kui-dialog-surface-width: 35rem;
3
+ --_kui-dialog-surface-resolved-gutter: max(
4
+ 0px,
5
+ var(--_kui-dialog-surface-gutter, 1rem)
6
+ );
2
7
  --width: min(35rem, calc(100vw - 2rem));
3
8
  }
4
9
  .kui-dialog-surface[data-size="small"] > wa-dialog {
10
+ --_kui-dialog-surface-width: 25rem;
5
11
  --width: min(25rem, calc(100vw - 2rem));
6
12
  }
7
13
  .kui-dialog-surface[data-size="large"] > wa-dialog {
14
+ --_kui-dialog-surface-width: 50rem;
8
15
  --width: min(50rem, calc(100vw - 2rem));
9
16
  }
17
+ .kui-dialog-surface[data-presentation="modal"][data-preferred-width]
18
+ > wa-dialog {
19
+ --width: min(
20
+ var(--_kui-dialog-surface-preferred-width),
21
+ calc(100vw - 2rem)
22
+ );
23
+ }
24
+ .kui-dialog-surface[data-presentation="modal"][data-viewport-gutter]
25
+ > wa-dialog {
26
+ --width: min(
27
+ var(
28
+ --_kui-dialog-surface-preferred-width,
29
+ var(--_kui-dialog-surface-width)
30
+ ),
31
+ max(
32
+ 0px,
33
+ calc(
34
+ 100vw - var(--_kui-dialog-surface-resolved-gutter) -
35
+ var(--_kui-dialog-surface-resolved-gutter)
36
+ )
37
+ )
38
+ );
39
+ }
40
+ /* Web Awesome has a separate max-width clamp; setting --width alone cannot
41
+ provide an 8px phone gutter. Omitted props retain its legacy geometry. */
42
+ .kui-dialog-surface[data-presentation="modal"][data-viewport-gutter]
43
+ > wa-dialog::part(dialog) {
44
+ max-width: max(
45
+ 0px,
46
+ calc(
47
+ 100vw - var(--_kui-dialog-surface-resolved-gutter) -
48
+ var(--_kui-dialog-surface-resolved-gutter)
49
+ )
50
+ );
51
+ }
52
+ .kui-dialog-surface[data-presentation="modal"][data-max-height]
53
+ > wa-dialog::part(dialog) {
54
+ max-height: max(
55
+ 0px,
56
+ min(
57
+ var(--_kui-dialog-surface-max-height, 100dvh),
58
+ calc(
59
+ 100dvh - var(--_kui-dialog-surface-resolved-gutter) -
60
+ var(--_kui-dialog-surface-resolved-gutter)
61
+ )
62
+ )
63
+ );
64
+ }
10
65
  .kui-dialog-surface[data-body-inset="none"] > wa-dialog::part(body),
11
66
  .kui-dialog-surface[data-footer-inset="none"] > wa-dialog::part(footer) {
12
67
  padding: 0;
@@ -73,6 +73,23 @@
73
73
  scroll-padding-block-start: var(--kui-edge-inset-block-start);
74
74
  }
75
75
 
76
+ /* A plain scene adds one outer gutter; a sole Pane or layout keeps ownership
77
+ of its own content and may opt into deepInset there. */
78
+ .kui-tab-scaffold__scene[data-deep-inset="true"]:not(
79
+ :has(
80
+ > :is(
81
+ [data-component="pane"],
82
+ [data-component="nav-stack"],
83
+ [data-component="split-view"],
84
+ [data-component="workbench"],
85
+ [data-component="tab-scaffold"]
86
+ ):only-child
87
+ )
88
+ ) {
89
+ padding-inline: calc(var(--kui-edge-inset-inline-start) + var(--kui-space-xs))
90
+ calc(var(--kui-edge-inset-inline-end) + var(--kui-space-xs));
91
+ }
92
+
76
93
  .kui-tab-scaffold__scene
77
94
  > :not(
78
95
  :is(
@@ -610,17 +610,23 @@
610
610
  wa-button,
611
611
  .kui-select,
612
612
  .kui-toolbar-action-link
613
- ):only-child
613
+ ):nth-child(1 of :where(:not([data-toolbar-visibility-state]))):where(
614
+ :nth-last-child(1 of :not([data-toolbar-visibility-state]))
615
+ )
614
616
  ):hover {
615
617
  background-color: var(--kui-toolbar-control-hover-background);
616
618
  }
617
619
 
618
620
  .kui-toolbar-control-group:not([data-single="true"]):has(
619
- > :is(button, wa-button, .kui-toolbar-action-link):only-child
621
+ > :is(button, wa-button, .kui-toolbar-action-link):nth-child(
622
+ 1 of :where(:not([data-toolbar-visibility-state]))
623
+ ):where(:nth-last-child(1 of :not([data-toolbar-visibility-state])))
620
624
  )
621
625
  > :is(button, wa-button, .kui-toolbar-action-link):hover,
622
626
  .kui-toolbar-control-group:not([data-single="true"]):has(
623
- > :is(button, wa-button):only-child
627
+ > :is(button, wa-button):nth-child(
628
+ 1 of :where(:not([data-toolbar-visibility-state]))
629
+ ):where(:nth-last-child(1 of :not([data-toolbar-visibility-state])))
624
630
  )
625
631
  > wa-button:hover::part(base) {
626
632
  border-color: transparent;
@@ -765,25 +771,41 @@
765
771
 
766
772
  .kui-toolbar-control-group[data-content="avatar"]:is(
767
773
  [data-single="true"],
768
- :has(> :is(button, wa-button):only-child)
774
+ :has(
775
+ > :is(button, wa-button):nth-child(
776
+ 1 of :where(:not([data-toolbar-visibility-state]))
777
+ ):where(:nth-last-child(1 of :not([data-toolbar-visibility-state])))
778
+ )
769
779
  ) {
770
780
  background-image: var(--kui-toolbar-avatar-image);
771
781
  }
772
782
 
773
783
  .kui-toolbar-control-group[data-content="avatar"]:is(
774
784
  [data-single="true"],
775
- :has(> :is(button, wa-button):only-child)
785
+ :has(
786
+ > :is(button, wa-button):nth-child(
787
+ 1 of :where(:not([data-toolbar-visibility-state]))
788
+ ):where(:nth-last-child(1 of :not([data-toolbar-visibility-state])))
789
+ )
776
790
  )
777
791
  > :is(button, wa-button) {
778
792
  color: transparent;
779
793
  }
780
794
 
781
795
  .kui-toolbar-control-group[data-content="avatar"][data-single="false"]:not(
782
- :has(> :is(button, wa-button):only-child)
796
+ :has(
797
+ > :is(button, wa-button):nth-child(
798
+ 1 of :where(:not([data-toolbar-visibility-state]))
799
+ ):where(:nth-last-child(1 of :not([data-toolbar-visibility-state])))
800
+ )
783
801
  )
784
802
  > button[aria-pressed="true"],
785
803
  .kui-toolbar-control-group[data-content="avatar"][data-single="false"]:not(
786
- :has(> :is(button, wa-button):only-child)
804
+ :has(
805
+ > :is(button, wa-button):nth-child(
806
+ 1 of :where(:not([data-toolbar-visibility-state]))
807
+ ):where(:nth-last-child(1 of :not([data-toolbar-visibility-state])))
808
+ )
787
809
  )
788
810
  > wa-button[aria-pressed="true"]::part(base) {
789
811
  color: transparent;
@@ -798,7 +820,10 @@
798
820
 
799
821
  .kui-toolbar-control-group[data-single="true"][data-appearance="contained"][data-tile-tone="neutral"]:has(
800
822
  > svg[aria-hidden="true"]
801
- ):not(:has(> :not(svg[aria-hidden="true"])), :has(> svg ~ svg)) {
823
+ ):not(
824
+ :has(> :not(svg[aria-hidden="true"], [data-toolbar-visibility-state])),
825
+ :has(> svg ~ svg)
826
+ ) {
802
827
  background: var(--kui-color-neutral-fill-quiet);
803
828
  border-color: var(--kui-color-neutral-border-quiet);
804
829
  color: var(--kui-color-neutral-on-quiet);
@@ -806,7 +831,10 @@
806
831
 
807
832
  .kui-toolbar-control-group[data-single="true"][data-appearance="contained"][data-tile-tone="brand"]:has(
808
833
  > svg[aria-hidden="true"]
809
- ):not(:has(> :not(svg[aria-hidden="true"])), :has(> svg ~ svg)) {
834
+ ):not(
835
+ :has(> :not(svg[aria-hidden="true"], [data-toolbar-visibility-state])),
836
+ :has(> svg ~ svg)
837
+ ) {
810
838
  background: var(--kui-color-brand-fill-quiet);
811
839
  border-color: var(--kui-color-brand-border-quiet);
812
840
  color: var(--kui-color-brand-on-quiet);
@@ -814,7 +842,10 @@
814
842
 
815
843
  .kui-toolbar-control-group[data-single="true"][data-appearance="contained"][data-tile-tone="success"]:has(
816
844
  > svg[aria-hidden="true"]
817
- ):not(:has(> :not(svg[aria-hidden="true"])), :has(> svg ~ svg)) {
845
+ ):not(
846
+ :has(> :not(svg[aria-hidden="true"], [data-toolbar-visibility-state])),
847
+ :has(> svg ~ svg)
848
+ ) {
818
849
  background: var(--kui-color-success-fill-quiet);
819
850
  border-color: var(--kui-color-success-border-quiet);
820
851
  color: var(--kui-color-success-on-quiet);
@@ -822,7 +853,10 @@
822
853
 
823
854
  .kui-toolbar-control-group[data-single="true"][data-appearance="contained"][data-tile-tone="warning"]:has(
824
855
  > svg[aria-hidden="true"]
825
- ):not(:has(> :not(svg[aria-hidden="true"])), :has(> svg ~ svg)) {
856
+ ):not(
857
+ :has(> :not(svg[aria-hidden="true"], [data-toolbar-visibility-state])),
858
+ :has(> svg ~ svg)
859
+ ) {
826
860
  background: var(--kui-color-warning-fill-quiet);
827
861
  border-color: var(--kui-color-warning-border-quiet);
828
862
  color: var(--kui-color-warning-on-quiet);
@@ -830,12 +864,25 @@
830
864
 
831
865
  .kui-toolbar-control-group[data-single="true"][data-appearance="contained"][data-tile-tone="danger"]:has(
832
866
  > svg[aria-hidden="true"]
833
- ):not(:has(> :not(svg[aria-hidden="true"])), :has(> svg ~ svg)) {
867
+ ):not(
868
+ :has(> :not(svg[aria-hidden="true"], [data-toolbar-visibility-state])),
869
+ :has(> svg ~ svg)
870
+ ) {
834
871
  background: var(--kui-color-danger-fill-quiet);
835
872
  border-color: var(--kui-color-danger-border-quiet);
836
873
  color: var(--kui-color-danger-on-quiet);
837
874
  }
838
875
  .kui-toolbar-control-group[data-toolbar-width-hidden="true"],
839
- .kui-toolbar-control-group__busy-status[data-toolbar-width-hidden="true"] {
876
+ .kui-toolbar-control-group__busy-status[data-toolbar-width-hidden="true"],
877
+ .kui-toolbar-control-group:has(
878
+ > [data-toolbar-visibility-state][data-toolbar-width-hidden="true"]
879
+ ),
880
+ .kui-toolbar-control-group__busy-status:has(
881
+ > [data-toolbar-visibility-state][data-toolbar-width-hidden="true"]
882
+ ) {
883
+ display: none;
884
+ }
885
+ .kui-toolbar-control-group > [data-toolbar-visibility-state],
886
+ .kui-toolbar-control-group__busy-status > [data-toolbar-visibility-state] {
840
887
  display: none;
841
888
  }
@@ -159,6 +159,12 @@ button.kui-toolbar-text[data-tone="dark"]:hover {
159
159
  line-clamp: none;
160
160
  max-height: calc(var(--kui-toolbar-text-max-lines) * 1.25em);
161
161
  }
162
- .kui-toolbar-text[data-toolbar-width-hidden="true"] {
162
+ .kui-toolbar-text[data-toolbar-width-hidden="true"],
163
+ .kui-toolbar-text:has(
164
+ > [data-toolbar-visibility-state][data-toolbar-width-hidden="true"]
165
+ ) {
166
+ display: none;
167
+ }
168
+ .kui-toolbar-text > [data-toolbar-visibility-state] {
163
169
  display: none;
164
170
  }
@@ -0,0 +1,4 @@
1
+ /** Importing this module registers wa-dialog and repairs wrapped native names. */
2
+ declare const surfaceScaffoldElementsRegistered = true;
3
+
4
+ export { surfaceScaffoldElementsRegistered };
@@ -0,0 +1,59 @@
1
+ import WaDialog from '@awesome.me/webawesome/dist/components/dialog/dialog.js';
2
+
3
+ // src/components/surfaces/surface-scaffold/register/surface-scaffold-register.ts
4
+
5
+ // src/components/surfaces/surface-scaffold/internal/install-dialog-label.ts
6
+ var installed = /* @__PURE__ */ new WeakSet();
7
+ var observed = /* @__PURE__ */ new WeakSet();
8
+ var ownedNames = /* @__PURE__ */ new WeakMap();
9
+ function installDialogLabel(prototype) {
10
+ if (installed.has(prototype)) return;
11
+ installed.add(prototype);
12
+ const target = prototype;
13
+ const update = target.update;
14
+ target.update = function(changed) {
15
+ update.call(this, changed);
16
+ if (!this.parentElement?.classList.contains("kui-dialog-surface")) return;
17
+ if (!observed.has(this)) {
18
+ observed.add(this);
19
+ new MutationObserver(() => syncDialogLabel(this)).observe(this, {
20
+ attributes: true,
21
+ attributeFilter: ["aria-label"]
22
+ });
23
+ }
24
+ syncDialogLabel(this);
25
+ };
26
+ }
27
+ function syncDialogLabel(host) {
28
+ if (!host.parentElement?.classList.contains("kui-dialog-surface")) return;
29
+ const panel = host.dialog;
30
+ if (!panel) return;
31
+ const previous = ownedNames.get(panel);
32
+ const externalName = ["aria-label", "aria-labelledby"].some((attribute2) => {
33
+ const value2 = panel.getAttribute(attribute2);
34
+ return value2 !== null && !(previous?.attribute === attribute2 && previous.value === value2);
35
+ });
36
+ if (externalName) {
37
+ if (previous && panel.getAttribute(previous.attribute) === previous.value)
38
+ panel.removeAttribute(previous.attribute);
39
+ ownedNames.delete(panel);
40
+ return;
41
+ }
42
+ const explicit = host.getAttribute("aria-label")?.trim();
43
+ const title = host.shadowRoot?.getElementById("title");
44
+ const attribute = !explicit && !host.withoutHeader && title ? "aria-labelledby" : "aria-label";
45
+ const value = attribute === "aria-labelledby" ? "title" : explicit || host.label;
46
+ if (previous && previous.attribute !== attribute)
47
+ panel.removeAttribute(previous.attribute);
48
+ if (panel.getAttribute(attribute) !== value)
49
+ panel.setAttribute(attribute, value);
50
+ ownedNames.set(panel, { attribute, value });
51
+ }
52
+
53
+ // src/components/surfaces/surface-scaffold/register/surface-scaffold-register.ts
54
+ installDialogLabel(WaDialog.prototype);
55
+ var surfaceScaffoldElementsRegistered = true;
56
+
57
+ export { surfaceScaffoldElementsRegistered };
58
+
59
+ //# sourceMappingURL=surface-scaffold-register.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/surfaces/surface-scaffold/internal/install-dialog-label.ts","../src/components/surfaces/surface-scaffold/register/surface-scaffold-register.ts"],"names":["attribute","value"],"mappings":"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","file":"surface-scaffold-register.js","sourcesContent":["interface NamedDialog extends HTMLElement {\n dialog?: HTMLDialogElement;\n label: string;\n withoutHeader: boolean;\n update(changed: unknown): void;\n}\n\nconst installed = new WeakSet<object>();\nconst observed = new WeakSet<HTMLElement>();\nconst ownedNames = new WeakMap<\n HTMLElement,\n { attribute: string; value: string }\n>();\n\n/** Repair Web Awesome's missing native dialog name before its first modal open. */\nexport function installDialogLabel(prototype: object): void {\n if (installed.has(prototype)) return;\n installed.add(prototype);\n const target = prototype as NamedDialog;\n const update = target.update;\n target.update = function (this: NamedDialog, changed: unknown): void {\n update.call(this, changed);\n if (!this.parentElement?.classList.contains('kui-dialog-surface')) return;\n if (!observed.has(this)) {\n observed.add(this);\n new MutationObserver(() => syncDialogLabel(this)).observe(this, {\n attributes: true,\n attributeFilter: ['aria-label'],\n });\n }\n syncDialogLabel(this);\n };\n}\n\nfunction syncDialogLabel(host: NamedDialog): void {\n if (!host.parentElement?.classList.contains('kui-dialog-surface')) return;\n const panel = host.dialog;\n if (!panel) return;\n const previous = ownedNames.get(panel);\n const externalName = ['aria-label', 'aria-labelledby'].some((attribute) => {\n const value = panel.getAttribute(attribute);\n return (\n value !== null &&\n !(previous?.attribute === attribute && previous.value === value)\n );\n });\n if (externalName) {\n if (previous && panel.getAttribute(previous.attribute) === previous.value)\n panel.removeAttribute(previous.attribute);\n ownedNames.delete(panel);\n return;\n }\n const explicit = host.getAttribute('aria-label')?.trim();\n const title = host.shadowRoot?.getElementById('title');\n const attribute =\n !explicit && !host.withoutHeader && title\n ? 'aria-labelledby'\n : 'aria-label';\n const value =\n attribute === 'aria-labelledby' ? 'title' : explicit || host.label;\n if (previous && previous.attribute !== attribute)\n panel.removeAttribute(previous.attribute);\n if (panel.getAttribute(attribute) !== value)\n panel.setAttribute(attribute, value);\n ownedNames.set(panel, { attribute, value });\n}\n","import WaDialog from '@awesome.me/webawesome/dist/components/dialog/dialog.js';\n\nimport { installDialogLabel } from '../internal/install-dialog-label.js';\n\ninstallDialogLabel(WaDialog.prototype);\n\n/** Importing this module registers wa-dialog and repairs wrapped native names. */\nexport const surfaceScaffoldElementsRegistered = true;\n"]}
@@ -1,21 +1,29 @@
1
1
  import * as kerfjs from 'kerfjs';
2
2
  import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
3
+ import { CssLength } from './css-values.js';
3
4
 
4
5
  type DialogSurfaceSize = 'small' | 'medium' | 'large';
5
6
  type DialogSurfacePresentation = 'modal' | 'side-sheet' | 'fullscreen';
6
7
  type SurfaceInset = 'none' | 'compact' | 'comfortable';
8
+ type DialogSurfaceMaxHeight = CssLength | 'viewport';
7
9
  interface DialogSurfaceProps {
8
10
  children: KerfUiContent;
9
11
  size?: DialogSurfaceSize;
10
12
  presentation?: DialogSurfacePresentation;
13
+ /** Modal preferred width. Takes precedence over size, within the viewport cap. */
14
+ preferredWidth?: CssLength;
11
15
  bodyInset?: SurfaceInset;
12
16
  footerInset?: SurfaceInset;
17
+ /** Modal edge clearance. Omit to retain Web Awesome's default viewport cap. */
18
+ viewportGutter?: CssLength;
19
+ /** Modal height cap, bounded by the dynamic viewport minus both gutters. */
20
+ maxHeight?: DialogSurfaceMaxHeight;
13
21
  className?: string;
14
22
  /** Native named-slot assignment when composed inside a web component. */
15
23
  slot?: string;
16
24
  }
17
25
  /** Configure recurring Web Awesome dialog geometry without consumer ::part() CSS. */
18
- declare function DialogSurface({ children, size, presentation, bodyInset, footerInset, className, slot, }: DialogSurfaceProps): kerfjs.SafeHtml;
26
+ declare function DialogSurface({ children, size, presentation, preferredWidth, bodyInset, footerInset, viewportGutter, maxHeight, className, slot, }: DialogSurfaceProps): kerfjs.SafeHtml;
19
27
  type PopupSurfaceInset = 'standard' | 'compact' | 'list-zero';
20
28
  interface PopupSurfaceProps {
21
29
  children: KerfUiContent;
@@ -27,4 +35,4 @@ interface PopupSurfaceProps {
27
35
  /** Configure recurring Web Awesome dropdown-menu geometry without consumer ::part() CSS. */
28
36
  declare function PopupSurface({ children, inset, className, slot, }: PopupSurfaceProps): kerfjs.SafeHtml;
29
37
 
30
- export { DialogSurface, type DialogSurfacePresentation, type DialogSurfaceProps, type DialogSurfaceSize, PopupSurface, type PopupSurfaceInset, type PopupSurfaceProps, type SurfaceInset };
38
+ export { DialogSurface, type DialogSurfaceMaxHeight, type DialogSurfacePresentation, type DialogSurfaceProps, type DialogSurfaceSize, PopupSurface, type PopupSurfaceInset, type PopupSurfaceProps, type SurfaceInset };
@@ -1,3 +1,3 @@
1
- export { DialogSurface, PopupSurface } from './chunk-XIZRXI7N.js';
1
+ export { DialogSurface, PopupSurface } from './chunk-A7DYEERU.js';
2
2
 
3
3
  //# sourceMappingURL=surface-scaffold.js.map
@@ -11,6 +11,8 @@ interface TabScaffoldTabBase<Id extends string> {
11
11
  content: KerfUiContent;
12
12
  /** Background of this scene's scrolling work surface. */
13
13
  appearance?: PaneAppearance;
14
+ /** Add an extra 8px inline gutter to a plain scene's scrolling content. */
15
+ deepInset?: boolean;
14
16
  }
15
17
  /** A tab with an optional count or short-status badge. */
16
18
  interface TabScaffoldTabCountBadge {
@@ -27,6 +27,7 @@ function TabScaffold({
27
27
  "data-tab-scaffold-scene": tab.id,
28
28
  "data-active": String(tab.id === active),
29
29
  "data-appearance": tab.appearance === "sunken" ? "sunken" : void 0,
30
+ "data-deep-inset": tab.deepInset ? "true" : void 0,
30
31
  "aria-hidden": String(tab.id !== active),
31
32
  children: tab.content
32
33
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/navigation/tab-scaffold/tab-scaffold.tsx"],"names":[],"mappings":"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","file":"tab-scaffold.js","sourcesContent":["import type { SafeHtml } from 'kerfjs';\n\nimport type { KerfUiContent } from '../../../shared/content/semantic-content.js';\nimport { Badge } from '../../feedback/badge/badge.js';\nimport type { PaneAppearance } from '../../layout/pane/pane.js';\n\ninterface TabScaffoldTabBase<Id extends string> {\n id: Id;\n label: string;\n /** Decorative icon shown above the label in the bottom bar. */\n icon?: SafeHtml;\n /** The tab's content — typically a `NavStack` so each tab keeps its own stack. */\n content: KerfUiContent;\n /** Background of this scene's scrolling work surface. */\n appearance?: PaneAppearance;\n}\n\n/** A tab with an optional count or short-status badge. */\ninterface TabScaffoldTabCountBadge {\n /**\n * Optional count or short status shown as a solid danger `Badge` at the\n * top-trailing corner of the tab icon (the iOS tab-bar badge). Omitted, `''`,\n * or non-finite numbers render no badge. The visual badge is `aria-hidden`;\n * its meaning reaches assistive technology through `badgeLabel`.\n *\n * Pass `true` instead for the text-free dot (new content without a count).\n */\n badge?: string | number;\n /**\n * Localized phrase folded into the tab's accessible name as\n * `\"<label>, <badgeLabel>\"` (for example `\"3 unread\"` → `\"Inbox, 3 unread\"`).\n * Defaults to the badge text itself. Ignored when no badge renders.\n */\n badgeLabel?: string;\n}\n\n/** A tab with the text-free dot badge (new content without a count). */\ninterface TabScaffoldTabDotBadge {\n /**\n * `true` shows a solid danger dot `Badge` at the top-trailing corner of the\n * tab icon — the iOS tab-bar dot for new content without a count.\n */\n badge: true;\n /**\n * Required localized phrase folded into the tab's accessible name as\n * `\"<label>, <badgeLabel>\"` (for example `\"New activity\"` →\n * `\"Feed, New activity\"`). A dot has no text, so this is its only meaning.\n */\n badgeLabel: string;\n}\n\n/** One bottom-bar destination: its label, optional icon and badge, and content. */\nexport type TabScaffoldTab<Id extends string = string> =\n TabScaffoldTabBase<Id> & (TabScaffoldTabCountBadge | TabScaffoldTabDotBadge);\n\nexport interface TabScaffoldProps<Id extends string = string> {\n id: string;\n /** Accessible name for the tab bar. */\n label: string;\n tabs: readonly TabScaffoldTab<Id>[];\n /** The controlled active tab id (the app owns selection). */\n active: NoInfer<Id>;\n /**\n * The line over the tab bar, where it meets the active scene. `scroll`\n * (default) shows it only while more of the scene's content lies below —\n * never at the scroll end or when the content fits — once\n * `wireScrollDividers` (`@kerfjs/ui/wire-scroll-dividers`) is wired above\n * the scaffold; unwired, it does not show. `always` shows it without the\n * wiring; `none` never, even when wired. The line is the bar's own 1px top\n * border, so no state moves the bar or a tab.\n */\n chromeDividers?: 'scroll' | 'always' | 'none';\n className?: string;\n /** Native named-slot assignment when composed inside a web component. */\n slot?: string;\n}\n\n/**\n * A mobile-first, iOS-like bottom tab scaffold: a bottom tab bar that switches\n * between major sections, each tab keeping its own content (usually a `NavStack`)\n * mounted so its stack and scroll survive a switch. Controlled — the app owns\n * `active`; wire selection with `@kerfjs/ui/wire-tab-scaffold`'s `wireTabScaffold`.\n * On larger classes, promote the tabs to a `Workbench` rail or sidebar instead of\n * a bottom bar. See `docs/23-app-layouts.md` §3.4.\n */\nexport function TabScaffold<Id extends string>({\n id,\n label,\n tabs,\n active,\n chromeDividers = 'scroll',\n className = '',\n slot,\n}: TabScaffoldProps<Id>) {\n return (\n <section\n class={`kui-tab-scaffold ${className}`.trim()}\n id={id}\n data-component=\"tab-scaffold\"\n data-tab-scaffold-id={id}\n data-chrome-dividers={\n chromeDividers === 'always' || chromeDividers === 'none'\n ? chromeDividers\n : undefined\n }\n slot={slot}\n >\n <div class=\"kui-tab-scaffold__scenes\">\n {tabs.map((tab) => (\n <div\n class=\"kui-tab-scaffold__scene\"\n data-tab-scaffold-scene={tab.id}\n data-active={String(tab.id === active)}\n data-appearance={tab.appearance === 'sunken' ? 'sunken' : undefined}\n aria-hidden={String(tab.id !== active)}\n >\n {tab.content}\n </div>\n ))}\n </div>\n <nav class=\"kui-tab-scaffold__bar\" role=\"tablist\" aria-label={label}>\n {tabs.map((tab) => {\n const dot = tab.badge === true;\n const badgeText = dot ? undefined : normalizeBadge(tab.badge);\n const hasBadge = dot || badgeText !== undefined;\n const badge = hasBadge && (\n <span\n class=\"kui-tab-scaffold__tab-badge\"\n data-badge-kind={dot ? 'dot' : 'text'}\n >\n {badgeText === undefined ? (\n <Badge tone=\"danger\" size=\"dot\" ariaHidden />\n ) : (\n <Badge\n tone=\"danger\"\n appearance=\"solid\"\n size=\"compact\"\n ariaHidden\n >\n {badgeText}\n </Badge>\n )}\n </span>\n );\n // A dot has no text fallback: without a usable badgeLabel (reachable\n // only from untyped callers) the tab keeps its plain accessible name.\n const badgeLabel = hasBadge\n ? tab.badgeLabel?.trim() || badgeText\n : undefined;\n return (\n <button\n type=\"button\"\n class=\"kui-tab-scaffold__tab\"\n role=\"tab\"\n data-tab-scaffold-tab={tab.id}\n aria-selected={String(tab.id === active)}\n tabindex={tab.id === active ? '0' : '-1'}\n aria-label={\n badgeLabel === undefined\n ? undefined\n : `${tab.label}, ${badgeLabel}`\n }\n data-has-badge={hasBadge ? 'true' : undefined}\n >\n {tab.icon ? (\n <span class=\"kui-tab-scaffold__tab-icon\" aria-hidden=\"true\">\n {tab.icon}\n {badge}\n </span>\n ) : (\n badge\n )}\n <span class=\"kui-tab-scaffold__tab-label\">{tab.label}</span>\n </button>\n );\n })}\n </nav>\n </section>\n );\n}\n\nfunction normalizeBadge(\n badge: string | number | undefined,\n): string | undefined {\n if (typeof badge === 'number')\n return Number.isFinite(badge) ? String(badge) : undefined;\n if (typeof badge === 'string' && badge.trim()) return badge.trim();\n return undefined;\n}\n"]}
1
+ {"version":3,"sources":["../src/components/navigation/tab-scaffold/tab-scaffold.tsx"],"names":[],"mappings":"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","file":"tab-scaffold.js","sourcesContent":["import type { SafeHtml } from 'kerfjs';\n\nimport type { KerfUiContent } from '../../../shared/content/semantic-content.js';\nimport { Badge } from '../../feedback/badge/badge.js';\nimport type { PaneAppearance } from '../../layout/pane/pane.js';\n\ninterface TabScaffoldTabBase<Id extends string> {\n id: Id;\n label: string;\n /** Decorative icon shown above the label in the bottom bar. */\n icon?: SafeHtml;\n /** The tab's content — typically a `NavStack` so each tab keeps its own stack. */\n content: KerfUiContent;\n /** Background of this scene's scrolling work surface. */\n appearance?: PaneAppearance;\n /** Add an extra 8px inline gutter to a plain scene's scrolling content. */\n deepInset?: boolean;\n}\n\n/** A tab with an optional count or short-status badge. */\ninterface TabScaffoldTabCountBadge {\n /**\n * Optional count or short status shown as a solid danger `Badge` at the\n * top-trailing corner of the tab icon (the iOS tab-bar badge). Omitted, `''`,\n * or non-finite numbers render no badge. The visual badge is `aria-hidden`;\n * its meaning reaches assistive technology through `badgeLabel`.\n *\n * Pass `true` instead for the text-free dot (new content without a count).\n */\n badge?: string | number;\n /**\n * Localized phrase folded into the tab's accessible name as\n * `\"<label>, <badgeLabel>\"` (for example `\"3 unread\"` → `\"Inbox, 3 unread\"`).\n * Defaults to the badge text itself. Ignored when no badge renders.\n */\n badgeLabel?: string;\n}\n\n/** A tab with the text-free dot badge (new content without a count). */\ninterface TabScaffoldTabDotBadge {\n /**\n * `true` shows a solid danger dot `Badge` at the top-trailing corner of the\n * tab icon — the iOS tab-bar dot for new content without a count.\n */\n badge: true;\n /**\n * Required localized phrase folded into the tab's accessible name as\n * `\"<label>, <badgeLabel>\"` (for example `\"New activity\"` →\n * `\"Feed, New activity\"`). A dot has no text, so this is its only meaning.\n */\n badgeLabel: string;\n}\n\n/** One bottom-bar destination: its label, optional icon and badge, and content. */\nexport type TabScaffoldTab<Id extends string = string> =\n TabScaffoldTabBase<Id> & (TabScaffoldTabCountBadge | TabScaffoldTabDotBadge);\n\nexport interface TabScaffoldProps<Id extends string = string> {\n id: string;\n /** Accessible name for the tab bar. */\n label: string;\n tabs: readonly TabScaffoldTab<Id>[];\n /** The controlled active tab id (the app owns selection). */\n active: NoInfer<Id>;\n /**\n * The line over the tab bar, where it meets the active scene. `scroll`\n * (default) shows it only while more of the scene's content lies below —\n * never at the scroll end or when the content fits — once\n * `wireScrollDividers` (`@kerfjs/ui/wire-scroll-dividers`) is wired above\n * the scaffold; unwired, it does not show. `always` shows it without the\n * wiring; `none` never, even when wired. The line is the bar's own 1px top\n * border, so no state moves the bar or a tab.\n */\n chromeDividers?: 'scroll' | 'always' | 'none';\n className?: string;\n /** Native named-slot assignment when composed inside a web component. */\n slot?: string;\n}\n\n/**\n * A mobile-first, iOS-like bottom tab scaffold: a bottom tab bar that switches\n * between major sections, each tab keeping its own content (usually a `NavStack`)\n * mounted so its stack and scroll survive a switch. Controlled — the app owns\n * `active`; wire selection with `@kerfjs/ui/wire-tab-scaffold`'s `wireTabScaffold`.\n * On larger classes, promote the tabs to a `Workbench` rail or sidebar instead of\n * a bottom bar. See `docs/23-app-layouts.md` §3.4.\n */\nexport function TabScaffold<Id extends string>({\n id,\n label,\n tabs,\n active,\n chromeDividers = 'scroll',\n className = '',\n slot,\n}: TabScaffoldProps<Id>) {\n return (\n <section\n class={`kui-tab-scaffold ${className}`.trim()}\n id={id}\n data-component=\"tab-scaffold\"\n data-tab-scaffold-id={id}\n data-chrome-dividers={\n chromeDividers === 'always' || chromeDividers === 'none'\n ? chromeDividers\n : undefined\n }\n slot={slot}\n >\n <div class=\"kui-tab-scaffold__scenes\">\n {tabs.map((tab) => (\n <div\n class=\"kui-tab-scaffold__scene\"\n data-tab-scaffold-scene={tab.id}\n data-active={String(tab.id === active)}\n data-appearance={tab.appearance === 'sunken' ? 'sunken' : undefined}\n data-deep-inset={tab.deepInset ? 'true' : undefined}\n aria-hidden={String(tab.id !== active)}\n >\n {tab.content}\n </div>\n ))}\n </div>\n <nav class=\"kui-tab-scaffold__bar\" role=\"tablist\" aria-label={label}>\n {tabs.map((tab) => {\n const dot = tab.badge === true;\n const badgeText = dot ? undefined : normalizeBadge(tab.badge);\n const hasBadge = dot || badgeText !== undefined;\n const badge = hasBadge && (\n <span\n class=\"kui-tab-scaffold__tab-badge\"\n data-badge-kind={dot ? 'dot' : 'text'}\n >\n {badgeText === undefined ? (\n <Badge tone=\"danger\" size=\"dot\" ariaHidden />\n ) : (\n <Badge\n tone=\"danger\"\n appearance=\"solid\"\n size=\"compact\"\n ariaHidden\n >\n {badgeText}\n </Badge>\n )}\n </span>\n );\n // A dot has no text fallback: without a usable badgeLabel (reachable\n // only from untyped callers) the tab keeps its plain accessible name.\n const badgeLabel = hasBadge\n ? tab.badgeLabel?.trim() || badgeText\n : undefined;\n return (\n <button\n type=\"button\"\n class=\"kui-tab-scaffold__tab\"\n role=\"tab\"\n data-tab-scaffold-tab={tab.id}\n aria-selected={String(tab.id === active)}\n tabindex={tab.id === active ? '0' : '-1'}\n aria-label={\n badgeLabel === undefined\n ? undefined\n : `${tab.label}, ${badgeLabel}`\n }\n data-has-badge={hasBadge ? 'true' : undefined}\n >\n {tab.icon ? (\n <span class=\"kui-tab-scaffold__tab-icon\" aria-hidden=\"true\">\n {tab.icon}\n {badge}\n </span>\n ) : (\n badge\n )}\n <span class=\"kui-tab-scaffold__tab-label\">{tab.label}</span>\n </button>\n );\n })}\n </nav>\n </section>\n );\n}\n\nfunction normalizeBadge(\n badge: string | number | undefined,\n): string | undefined {\n if (typeof badge === 'number')\n return Number.isFinite(badge) ? String(badge) : undefined;\n if (typeof badge === 'string' && badge.trim()) return badge.trim();\n return undefined;\n}\n"]}
@@ -1,3 +1,3 @@
1
- export { wireToolbarVisibility } from './chunk-WJN3V2H5.js';
1
+ export { wireToolbarVisibility } from './chunk-2GOCOLQL.js';
2
2
 
3
3
  //# sourceMappingURL=wire-toolbar-visibility.js.map
@@ -1,15 +1,15 @@
1
1
  import { SafeHtml } from 'kerfjs';
2
2
  import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
3
+ import { b as NavPane, N as NavStackProps } from './nav-stack-B1Qc2e5q.js';
3
4
  import { ListConfig } from './list.js';
4
- import { NavStackProps } from './nav-stack.js';
5
5
  import { PaneConfig } from './pane.js';
6
6
  import { ResizableRegionSeparator, ResizableRegionCollapseMotion, ResizableRegionContentOverflow, ResizableRegionPresentation, ResizableRegionRestorePosition } from './resizable-region.js';
7
- import { a as PanelToolbar, b as PanelChromePlacement, c as PanelBottomToolbar, d as PanelToggle } from './panel-toolbar-CL9tYOkh.js';
7
+ import { a as PanelToolbar, c as PanelBottomToolbar, b as PanelChromePlacement, d as PanelToggle } from './panel-toolbar-CL9tYOkh.js';
8
8
  import { ToolbarConfig } from './toolbar.js';
9
+ import './toolbar-text.js';
10
+ import './css-values.js';
9
11
  import './flex-alignment-4ms8ZbV8.js';
10
12
  import './sides-BPSWde0A.js';
11
- import './css-values.js';
12
- import './toolbar-text.js';
13
13
  import './lucide-icon.js';
14
14
  import 'lucide';
15
15
 
@@ -131,19 +131,11 @@ interface WorkbenchPanelBase {
131
131
  restorePosition?: ResizableRegionRestorePosition;
132
132
  }
133
133
  /** The original static panel body: one toolbar, optional fixed chrome, and one Pane. */
134
- interface WorkbenchStaticPanel extends WorkbenchPanelBase {
135
- content: KerfUiContent;
134
+ interface WorkbenchStaticPanel extends WorkbenchPanelBase, NavPane<WorkbenchPanelToolbar, WorkbenchPanelBottomToolbar> {
136
135
  /** The composed toolbar; marked groups and its toggle relocate on collapse. */
137
136
  toolbar?: WorkbenchPanelToolbar;
138
- header?: KerfUiContent;
139
- headerList?: ListConfig;
140
137
  headerPlacement?: WorkbenchChromePlacement;
141
- footer?: KerfUiContent;
142
- footerList?: ListConfig;
143
- bottomToolbar?: WorkbenchPanelBottomToolbar;
144
138
  footerPlacement?: WorkbenchChromePlacement;
145
- /** Configuration for the static toolbar panel's Pane. */
146
- pane?: PaneConfig;
147
139
  navStack?: never;
148
140
  }
149
141
  /** A panel whose active NavStack view supplies the toolbar, fixed header, and scroll body. */
package/dist/workbench.js CHANGED
@@ -1,4 +1,4 @@
1
- export { Workbench } from './chunk-6VQPLV3O.js';
1
+ export { Workbench } from './chunk-DSLRNR43.js';
2
2
 
3
3
 
4
4
 
@@ -890,7 +890,14 @@ leave the item visible under this width policy. Server-rendered content is visib
890
890
  until wiring runs; without browser observers, initial wiring and window resize
891
891
  still update it. Probe context and lengths are reconciled only when their CSSOM
892
892
  values change, so unrelated DOM observers can redraw without feeding an ongoing
893
- mutation loop. Dispose to remove measurement probes and restore prior markers.
893
+ mutation loop. Probes are helper-owned imperative nodes preserved across Kerf
894
+ rerenders, preventing repeated probe creation and resize/DOM observer churn.
895
+ Invisible preserved state children keep actual CSS visibility stable while a render
896
+ removes transient parent markers, so post-render geometry measurements cannot
897
+ feed a show/hide render loop. These children never enter layout, focus or the
898
+ accessible tree. Toolbar replacement or deliberate internal-node removal is
899
+ still repaired. Dispose to
900
+ remove measurement probes and restore prior markers.
894
901
 
895
902
  Hidden controls leave layout and keyboard navigation. A hidden busy group also
896
903
  hides only its own adjacent busy status. Keep primary actions always available;