@ponchia/ui 0.6.6 → 0.6.8

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 (160) hide show
  1. package/CHANGELOG.md +175 -6
  2. package/README.md +38 -25
  3. package/annotations/index.d.ts.map +1 -1
  4. package/annotations/index.js +21 -3
  5. package/behaviors/carousel.d.ts.map +1 -1
  6. package/behaviors/carousel.js +91 -32
  7. package/behaviors/combobox.d.ts.map +1 -1
  8. package/behaviors/combobox.js +117 -43
  9. package/behaviors/command.d.ts.map +1 -1
  10. package/behaviors/command.js +74 -14
  11. package/behaviors/connectors.d.ts.map +1 -1
  12. package/behaviors/connectors.js +92 -9
  13. package/behaviors/crosshair.d.ts.map +1 -1
  14. package/behaviors/crosshair.js +47 -1
  15. package/behaviors/dialog.d.ts.map +1 -1
  16. package/behaviors/dialog.js +37 -16
  17. package/behaviors/disclosure.d.ts.map +1 -1
  18. package/behaviors/disclosure.js +33 -3
  19. package/behaviors/dismissible.d.ts.map +1 -1
  20. package/behaviors/dismissible.js +3 -2
  21. package/behaviors/forms.d.ts.map +1 -1
  22. package/behaviors/forms.js +78 -5
  23. package/behaviors/glyph.d.ts.map +1 -1
  24. package/behaviors/glyph.js +17 -2
  25. package/behaviors/index.d.ts +2 -0
  26. package/behaviors/index.d.ts.map +1 -1
  27. package/behaviors/index.js +2 -0
  28. package/behaviors/inert.js +3 -2
  29. package/behaviors/internal.d.ts +2 -1
  30. package/behaviors/internal.d.ts.map +1 -1
  31. package/behaviors/internal.js +25 -4
  32. package/behaviors/legend.d.ts +0 -5
  33. package/behaviors/legend.d.ts.map +1 -1
  34. package/behaviors/legend.js +78 -14
  35. package/behaviors/menu.d.ts.map +1 -1
  36. package/behaviors/menu.js +13 -8
  37. package/behaviors/modal.d.ts.map +1 -1
  38. package/behaviors/modal.js +77 -19
  39. package/behaviors/popover.d.ts +4 -3
  40. package/behaviors/popover.d.ts.map +1 -1
  41. package/behaviors/popover.js +89 -9
  42. package/behaviors/sources.d.ts.map +1 -1
  43. package/behaviors/sources.js +14 -2
  44. package/behaviors/splitter.d.ts +26 -0
  45. package/behaviors/splitter.d.ts.map +1 -0
  46. package/behaviors/splitter.js +239 -0
  47. package/behaviors/spotlight.d.ts.map +1 -1
  48. package/behaviors/spotlight.js +28 -2
  49. package/behaviors/table.d.ts.map +1 -1
  50. package/behaviors/table.js +105 -13
  51. package/behaviors/tabs.d.ts.map +1 -1
  52. package/behaviors/tabs.js +82 -18
  53. package/behaviors/theme.d.ts.map +1 -1
  54. package/behaviors/theme.js +26 -6
  55. package/classes/classes.json +230 -4
  56. package/classes/index.d.ts +64 -3
  57. package/classes/index.js +56 -2
  58. package/classes/vscode.css-custom-data.json +1 -1
  59. package/connectors/index.d.ts +39 -6
  60. package/connectors/index.d.ts.map +1 -1
  61. package/connectors/index.js +67 -9
  62. package/css/analytical.css +3 -1
  63. package/css/annotations.css +12 -0
  64. package/css/app.css +4 -4
  65. package/css/clamp.css +92 -0
  66. package/css/crosshair.css +27 -2
  67. package/css/feedback.css +2 -30
  68. package/css/figure.css +102 -0
  69. package/css/highlights.css +50 -0
  70. package/css/interval.css +90 -0
  71. package/css/navigation.css +12 -0
  72. package/css/primitives.css +2 -3
  73. package/css/report-kit.css +38 -0
  74. package/css/report.css +23 -4
  75. package/css/sidenote.css +12 -2
  76. package/css/site.css +2 -1
  77. package/css/sources.css +5 -0
  78. package/css/state.css +120 -1
  79. package/css/table.css +4 -0
  80. package/css/tokens.css +25 -9
  81. package/css/workbench.css +101 -8
  82. package/dist/bronto.css +1 -1
  83. package/dist/css/analytical.css +1 -1
  84. package/dist/css/annotations.css +1 -1
  85. package/dist/css/app.css +1 -1
  86. package/dist/css/clamp.css +1 -0
  87. package/dist/css/crosshair.css +1 -1
  88. package/dist/css/feedback.css +1 -1
  89. package/dist/css/figure.css +1 -0
  90. package/dist/css/highlights.css +1 -0
  91. package/dist/css/interval.css +1 -0
  92. package/dist/css/navigation.css +1 -1
  93. package/dist/css/primitives.css +1 -1
  94. package/dist/css/report-kit.css +1 -0
  95. package/dist/css/report.css +1 -1
  96. package/dist/css/sidenote.css +1 -1
  97. package/dist/css/site.css +1 -1
  98. package/dist/css/sources.css +1 -1
  99. package/dist/css/state.css +1 -1
  100. package/dist/css/table.css +1 -1
  101. package/dist/css/tokens.css +1 -1
  102. package/dist/css/workbench.css +1 -1
  103. package/docs/adr/0001-color-system.md +3 -2
  104. package/docs/adr/0002-scope-and-2026-baseline.md +1 -1
  105. package/docs/annotations.md +12 -1
  106. package/docs/architecture.md +105 -48
  107. package/docs/clamp.md +49 -0
  108. package/docs/command.md +4 -1
  109. package/docs/connectors.md +16 -0
  110. package/docs/contrast.md +34 -24
  111. package/docs/crosshair.md +1 -1
  112. package/docs/d2.md +37 -0
  113. package/docs/dots.md +4 -1
  114. package/docs/figure.md +71 -0
  115. package/docs/frontier-primitives.md +25 -24
  116. package/docs/glyphs.md +11 -0
  117. package/docs/highlights.md +52 -0
  118. package/docs/interop/tailwind.md +148 -0
  119. package/docs/interval.md +55 -0
  120. package/docs/legends.md +3 -2
  121. package/docs/mermaid.md +6 -0
  122. package/docs/migrations/0.2-to-0.3.md +80 -0
  123. package/docs/migrations/0.3-to-0.4.md +48 -0
  124. package/docs/migrations/0.4-to-0.5.md +96 -0
  125. package/docs/migrations/0.5-to-0.6.md +82 -0
  126. package/docs/package-contract.md +44 -6
  127. package/docs/reference.md +78 -5
  128. package/docs/reporting.md +126 -60
  129. package/docs/sidenote.md +7 -1
  130. package/docs/sources.md +1 -1
  131. package/docs/stability.md +23 -5
  132. package/docs/state.md +67 -10
  133. package/docs/theming.md +12 -4
  134. package/docs/usage.md +47 -13
  135. package/docs/vega.md +4 -4
  136. package/docs/workbench.md +59 -18
  137. package/llms.txt +89 -16
  138. package/package.json +82 -6
  139. package/qwik/index.d.ts +1 -0
  140. package/qwik/index.d.ts.map +1 -1
  141. package/qwik/index.js +26 -21
  142. package/react/index.d.ts +1 -0
  143. package/react/index.d.ts.map +1 -1
  144. package/react/index.js +4 -1
  145. package/schemas/report-claims.v1.schema.json +137 -0
  146. package/solid/index.d.ts +2 -0
  147. package/solid/index.d.ts.map +1 -1
  148. package/solid/index.js +3 -0
  149. package/svelte/index.d.ts +114 -0
  150. package/svelte/index.d.ts.map +1 -0
  151. package/svelte/index.js +193 -0
  152. package/tailwind.css +87 -0
  153. package/tokens/figma.variables.json +2241 -0
  154. package/tokens/index.js +1 -1
  155. package/tokens/index.json +2 -2
  156. package/tokens/resolved.json +3 -3
  157. package/tokens/tokens.dtcg.json +1 -1
  158. package/vue/index.d.ts +116 -0
  159. package/vue/index.d.ts.map +1 -0
  160. package/vue/index.js +228 -0
@@ -1,4 +1,14 @@
1
- import { hasDom, resolveHost, noop, bindOnce, byIdInHost } from './internal.js';
1
+ import { hasDom, resolveHost, noop, bindOnce, byIdInHost, closestSafe } from './internal.js';
2
+
3
+ const snapshotAttr = (el, name) => ({
4
+ had: el.hasAttribute(name),
5
+ value: el.getAttribute(name),
6
+ });
7
+
8
+ const restoreAttr = (el, name, state) => {
9
+ if (state.had) el.setAttribute(name, state.value);
10
+ else el.removeAttribute(name);
11
+ };
2
12
 
3
13
  /**
4
14
  * Disclosure: a `[data-bronto-disclosure]` trigger toggles the element
@@ -12,18 +22,38 @@ export function initDisclosure({ root } = {}) {
12
22
  if (!hasDom()) return noop;
13
23
  const host = resolveHost(root);
14
24
  if (!host) return noop;
25
+ const triggerStates = new Map();
26
+ const panelStates = new Map();
27
+
28
+ const remember = (trigger, panel) => {
29
+ if (!triggerStates.has(trigger)) {
30
+ triggerStates.set(trigger, snapshotAttr(trigger, 'aria-expanded'));
31
+ }
32
+ if (!panelStates.has(panel)) {
33
+ panelStates.set(panel, snapshotAttr(panel, 'hidden'));
34
+ }
35
+ };
36
+
15
37
  const onClick = (e) => {
16
- const trigger = e.target.closest('[data-bronto-disclosure]');
38
+ const trigger = closestSafe(e.target, '[data-bronto-disclosure]');
17
39
  if (!trigger || !host.contains(trigger)) return;
18
40
  const id = trigger.getAttribute('aria-controls');
19
41
  const panel = byIdInHost(host, id);
20
42
  if (!panel) return;
43
+ e.preventDefault();
44
+ remember(trigger, panel);
21
45
  const open = trigger.getAttribute('aria-expanded') === 'true';
22
46
  trigger.setAttribute('aria-expanded', String(!open));
23
47
  panel.hidden = open;
24
48
  };
25
49
  return bindOnce(host, 'disclosure', () => {
26
50
  host.addEventListener('click', onClick);
27
- return () => host.removeEventListener('click', onClick);
51
+ return () => {
52
+ host.removeEventListener('click', onClick);
53
+ for (const [trigger, state] of triggerStates) restoreAttr(trigger, 'aria-expanded', state);
54
+ triggerStates.clear();
55
+ for (const [panel, state] of panelStates) restoreAttr(panel, 'hidden', state);
56
+ panelStates.clear();
57
+ };
28
58
  });
29
59
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dismissible.d.ts","sourceRoot":"","sources":["dismissible.js"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,uCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAmB3C"}
1
+ {"version":3,"file":"dismissible.d.ts","sourceRoot":"","sources":["dismissible.js"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,uCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAoB3C"}
@@ -13,11 +13,12 @@ export function dismissible({ root } = {}) {
13
13
  const host = resolveHost(root);
14
14
  if (!host) return noop;
15
15
  const onClick = (e) => {
16
- const btn = e.target.closest('[data-bronto-dismiss]');
16
+ const btn = closestSafe(e.target, '[data-bronto-dismiss]');
17
17
  if (!btn || !host.contains(btn)) return;
18
18
  const sel = btn.getAttribute('data-bronto-dismiss');
19
- const target = sel ? closestSafe(btn, sel) : btn.closest('[data-bronto-dismissible]');
19
+ const target = sel ? closestSafe(btn, sel) : closestSafe(btn, '[data-bronto-dismissible]');
20
20
  if (!target) return;
21
+ e.preventDefault();
21
22
  const ev = new CustomEvent('bronto:dismiss', { bubbles: true, cancelable: true });
22
23
  if (target.dispatchEvent(ev)) target.remove();
23
24
  };
@@ -1 +1 @@
1
- {"version":3,"file":"forms.d.ts","sourceRoot":"","sources":["forms.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,8CAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CA6K3C"}
1
+ {"version":3,"file":"forms.d.ts","sourceRoot":"","sources":["forms.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,8CAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAsP3C"}
@@ -29,6 +29,59 @@ export function initFormValidation({ root } = {}) {
29
29
  if (!hasDom()) return noop;
30
30
  const host = resolveHost(root);
31
31
  if (!host) return noop;
32
+ let priorNoValidate = new Map();
33
+ let controlState = new Map();
34
+ let slotState = new Map();
35
+ let summaryState = new Map();
36
+
37
+ const suppressNativeValidation = (form) => {
38
+ if (!priorNoValidate.has(form)) priorNoValidate.set(form, form.noValidate);
39
+ form.noValidate = true;
40
+ };
41
+
42
+ const snapshotAttrs = (el, names) => {
43
+ const out = {};
44
+ for (const name of names) {
45
+ out[name] = {
46
+ had: el.hasAttribute(name),
47
+ value: el.getAttribute(name),
48
+ };
49
+ }
50
+ return out;
51
+ };
52
+
53
+ const restoreAttrs = (el, attrs) => {
54
+ for (const [name, attr] of Object.entries(attrs)) {
55
+ if (attr.had) el.setAttribute(name, attr.value);
56
+ else el.removeAttribute(name);
57
+ }
58
+ };
59
+
60
+ const rememberControl = (control) => {
61
+ if (!controlState.has(control)) {
62
+ controlState.set(control, snapshotAttrs(control, ['aria-invalid', 'aria-describedby', 'id']));
63
+ }
64
+ };
65
+
66
+ const rememberSlot = (slot) => {
67
+ if (!slotState.has(slot)) {
68
+ slotState.set(slot, {
69
+ attrs: snapshotAttrs(slot, ['id']),
70
+ text: slot.textContent,
71
+ hadErrorClass: slot.classList.contains('ui-hint--error'),
72
+ });
73
+ }
74
+ };
75
+
76
+ const rememberSummary = (summary) => {
77
+ if (!summaryState.has(summary)) {
78
+ summaryState.set(summary, {
79
+ attrs: snapshotAttrs(summary, ['role', 'tabindex']),
80
+ children: [...summary.childNodes],
81
+ hidden: summary.hidden,
82
+ });
83
+ }
84
+ };
32
85
 
33
86
  const ensureId = (el, prefix) => {
34
87
  if (!el.id) el.id = `${prefix}-${nextFieldUid()}`;
@@ -69,8 +122,10 @@ export function initFormValidation({ root } = {}) {
69
122
 
70
123
  const validateField = (control) => {
71
124
  if (!control.willValidate) return true;
125
+ rememberControl(control);
72
126
  const ok = control.validity.valid;
73
127
  const slot = slotFor(control);
128
+ if (slot) rememberSlot(slot);
74
129
  // Decide the slot TYPE by the `[data-bronto-error]` attribute, NOT the
75
130
  // `.ui-hint` class: the canonical markup is `<p class="ui-hint"
76
131
  // data-bronto-error>`, which carries BOTH. Keying off `.ui-hint` sent that
@@ -118,6 +173,7 @@ export function initFormValidation({ root } = {}) {
118
173
  const refreshSummary = (form, invalid) => {
119
174
  const summary = form.querySelector('[data-bronto-error-summary]');
120
175
  if (!summary) return;
176
+ rememberSummary(summary);
121
177
  if (!invalid.length) {
122
178
  summary.hidden = true;
123
179
  summary.replaceChildren();
@@ -150,7 +206,7 @@ export function initFormValidation({ root } = {}) {
150
206
  const onSubmit = (e) => {
151
207
  const form = e.target.closest?.('[data-bronto-validate]');
152
208
  if (!form) return;
153
- form.noValidate = true;
209
+ suppressNativeValidation(form);
154
210
  const invalid = controlsOf(form).filter((c) => !validateField(c));
155
211
  refreshSummary(form, invalid);
156
212
  if (invalid.length) {
@@ -165,7 +221,7 @@ export function initFormValidation({ root } = {}) {
165
221
  if (!control.willValidate) return;
166
222
  const form = control.closest?.('[data-bronto-validate]');
167
223
  if (!form) return;
168
- form.noValidate = true;
224
+ suppressNativeValidation(form);
169
225
  validateField(control);
170
226
  const summary = form.querySelector('[data-bronto-error-summary]');
171
227
  if (summary && !summary.hidden)
@@ -183,10 +239,12 @@ export function initFormValidation({ root } = {}) {
183
239
  // summary — contradicting the documented contract. (Forms added
184
240
  // after init are still covered by the in-handler set.)
185
241
  const forms = collectHosts(host, '[data-bronto-validate]');
186
- const priorNoValidate = new Map();
242
+ priorNoValidate = new Map();
243
+ controlState = new Map();
244
+ slotState = new Map();
245
+ summaryState = new Map();
187
246
  for (const f of forms) {
188
- priorNoValidate.set(f, f.noValidate);
189
- f.noValidate = true;
247
+ suppressNativeValidation(f);
190
248
  }
191
249
  host.addEventListener('submit', onSubmit, true);
192
250
  host.addEventListener('focusout', onBlur);
@@ -194,6 +252,21 @@ export function initFormValidation({ root } = {}) {
194
252
  host.removeEventListener('submit', onSubmit, true);
195
253
  host.removeEventListener('focusout', onBlur);
196
254
  for (const [f, v] of priorNoValidate) f.noValidate = v;
255
+ priorNoValidate.clear();
256
+ for (const [summary, state] of summaryState) {
257
+ summary.replaceChildren(...state.children);
258
+ summary.hidden = state.hidden;
259
+ restoreAttrs(summary, state.attrs);
260
+ }
261
+ summaryState.clear();
262
+ for (const [slot, state] of slotState) {
263
+ slot.textContent = state.text;
264
+ slot.classList.toggle('ui-hint--error', state.hadErrorClass);
265
+ restoreAttrs(slot, state.attrs);
266
+ }
267
+ slotState.clear();
268
+ for (const [control, attrs] of controlState) restoreAttrs(control, attrs);
269
+ controlState.clear();
197
270
  };
198
271
  });
199
272
  }
@@ -1 +1 @@
1
- {"version":3,"file":"glyph.d.ts","sourceRoot":"","sources":["glyph.js"],"names":[],"mappings":"AAeA;;;;;;;;;;;;;;;;;;GAkBG;AACH,wCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAyJ3C"}
1
+ {"version":3,"file":"glyph.d.ts","sourceRoot":"","sources":["glyph.js"],"names":[],"mappings":"AA6BA;;;;;;;;;;;;;;;;;;GAkBG;AACH,wCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CA0J3C"}
@@ -1,11 +1,25 @@
1
1
  import { hasDom, resolveHost, noop, collectHosts } from './internal.js';
2
2
  import { GLYPH_SIZE, glyphCells, glyphMask } from '../glyphs/glyphs.js';
3
3
 
4
+ const GLYPH_CLEANUP = Symbol('bronto-glyph-cleanup');
5
+
4
6
  function restoreAttr(el, name, prev) {
5
7
  if (prev === null) el.removeAttribute(name);
6
8
  else el.setAttribute(name, prev);
7
9
  }
8
10
 
11
+ function rememberCleanup(el, cleanups, cleanup) {
12
+ let done = false;
13
+ const wrapped = () => {
14
+ if (done) return;
15
+ done = true;
16
+ cleanup();
17
+ if (el[GLYPH_CLEANUP] === wrapped) delete el[GLYPH_CLEANUP];
18
+ };
19
+ el[GLYPH_CLEANUP] = wrapped;
20
+ cleanups.push(wrapped);
21
+ }
22
+
9
23
  // `dot`/`gap`/`size` land in inline CSS, so allow only length/calc syntax —
10
24
  // drop anything with a `;`/`{` that could open a second declaration (mirrors
11
25
  // glyphs.js cssLen). Used for the mask path's --icon-size.
@@ -40,6 +54,7 @@ export function initDotGlyph({ root } = {}) {
40
54
  const cleanups = [];
41
55
 
42
56
  for (const el of els) {
57
+ el[GLYPH_CLEANUP]?.();
43
58
  const name = el.getAttribute('data-bronto-glyph');
44
59
  const label = el.getAttribute('data-bronto-glyph-label');
45
60
 
@@ -67,7 +82,7 @@ export function initDotGlyph({ root } = {}) {
67
82
  el.setAttribute('aria-hidden', 'true');
68
83
  }
69
84
 
70
- cleanups.push(() => {
85
+ rememberCleanup(el, cleanups, () => {
71
86
  if (!hadIcon) el.classList.remove('ui-icon');
72
87
  if (hadMask) el.style.setProperty('--icon-mask', hadMask);
73
88
  else el.style.removeProperty('--icon-mask');
@@ -159,7 +174,7 @@ export function initDotGlyph({ root } = {}) {
159
174
  });
160
175
  el.appendChild(frag);
161
176
 
162
- cleanups.push(() => {
177
+ rememberCleanup(el, cleanups, () => {
163
178
  el.querySelectorAll(':scope > .ui-dotmatrix__cell').forEach((n) => n.remove());
164
179
  if (!hadMatrix) el.classList.remove('ui-dotmatrix');
165
180
  if (animClass && !hadAnimClass) el.classList.remove(animClass);
@@ -18,6 +18,7 @@ export { initSpotlight } from "./spotlight.js";
18
18
  export { initCrosshair } from "./crosshair.js";
19
19
  export { initCommand } from "./command.js";
20
20
  export { initSources } from "./sources.js";
21
+ export { initSplitter } from "./splitter.js";
21
22
  export type Cleanup = import("./internal.js").Cleanup;
22
23
  export type DelegateOpts = import("./internal.js").DelegateOpts;
23
24
  export type ThemeStorageOpts = import("./theme.js").ThemeStorageOpts;
@@ -29,5 +30,6 @@ export type LegendToggleDetail = import("./legend.js").LegendToggleDetail;
29
30
  export type CrosshairMoveDetail = import("./crosshair.js").CrosshairMoveDetail;
30
31
  export type CommandSelectDetail = import("./command.js").CommandSelectDetail;
31
32
  export type SourceFocusDetail = import("./sources.js").SourceFocusDetail;
33
+ export type SplitterResizeDetail = import("./splitter.js").SplitterResizeDetail;
32
34
  export { applyStoredTheme, initThemeToggle } from "./theme.js";
33
35
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;sBAmBa,OAAO,eAAe,EAAE,OAAO;2BAC/B,OAAO,eAAe,EAAE,YAAY;+BACpC,OAAO,YAAY,EAAE,gBAAgB;6BACrC,OAAO,YAAY,EAAE,cAAc;gCACnC,OAAO,YAAY,EAAE,iBAAiB;wBACtC,OAAO,YAAY,EAAE,SAAS;+BAC9B,OAAO,YAAY,EAAE,gBAAgB;iCACrC,OAAO,aAAa,EAAE,kBAAkB;kCACxC,OAAO,gBAAgB,EAAE,mBAAmB;kCAC5C,OAAO,cAAc,EAAE,mBAAmB;gCAC1C,OAAO,cAAc,EAAE,iBAAiB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;sBAmBa,OAAO,eAAe,EAAE,OAAO;2BAC/B,OAAO,eAAe,EAAE,YAAY;+BACpC,OAAO,YAAY,EAAE,gBAAgB;6BACrC,OAAO,YAAY,EAAE,cAAc;gCACnC,OAAO,YAAY,EAAE,iBAAiB;wBACtC,OAAO,YAAY,EAAE,SAAS;+BAC9B,OAAO,YAAY,EAAE,gBAAgB;iCACrC,OAAO,aAAa,EAAE,kBAAkB;kCACxC,OAAO,gBAAgB,EAAE,mBAAmB;kCAC5C,OAAO,cAAc,EAAE,mBAAmB;gCAC1C,OAAO,cAAc,EAAE,iBAAiB;mCACxC,OAAO,eAAe,EAAE,oBAAoB"}
@@ -28,6 +28,7 @@
28
28
  * @typedef {import('./crosshair.js').CrosshairMoveDetail} CrosshairMoveDetail
29
29
  * @typedef {import('./command.js').CommandSelectDetail} CommandSelectDetail
30
30
  * @typedef {import('./sources.js').SourceFocusDetail} SourceFocusDetail
31
+ * @typedef {import('./splitter.js').SplitterResizeDetail} SplitterResizeDetail
31
32
  */
32
33
  export { applyStoredTheme, initThemeToggle } from './theme.js';
33
34
  export { dismissible } from './dismissible.js';
@@ -50,3 +51,4 @@ export { initSpotlight } from './spotlight.js';
50
51
  export { initCrosshair } from './crosshair.js';
51
52
  export { initCommand } from './command.js';
52
53
  export { initSources } from './sources.js';
54
+ export { initSplitter } from './splitter.js';
@@ -1,4 +1,4 @@
1
- import { hasDom, resolveHost, noop, bindOnce } from './internal.js';
1
+ import { hasDom, resolveHost, noop, bindOnce, closestSafe } from './internal.js';
2
2
 
3
3
  const DISABLED = '[aria-disabled="true"]';
4
4
 
@@ -25,9 +25,10 @@ export function initDisabledGuard({ root } = {}) {
25
25
  const host = resolveHost(root);
26
26
  if (!host) return noop;
27
27
  const block = (e) => {
28
- const el = e.target.closest?.(DISABLED);
28
+ const el = closestSafe(e.target, DISABLED);
29
29
  if (el && host.contains(el)) {
30
30
  e.preventDefault();
31
+ e.stopImmediatePropagation?.();
31
32
  e.stopPropagation();
32
33
  }
33
34
  };
@@ -19,7 +19,8 @@ export type Cleanup = () => void;
19
19
  export type DelegateOpts = {
20
20
  /**
21
21
  * Event-delegation root; also scopes which controls are queried. Default: `document`.
22
+ * `null` means a scope was requested but is not ready yet, so the behavior no-ops.
22
23
  */
23
- root?: Document | Element | undefined;
24
+ root?: Document | Element | null | undefined;
24
25
  };
25
26
  //# sourceMappingURL=internal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["internal.js"],"names":[],"mappings":"AA8BA,iEAGC;AAeD,sEAUC;AAED,oDAQC;AAED,yDAMC;AAMD,8DAIC;AAID;;SAMC;AAUD,gDAQC;AAMD,+DAKC;AA9GM,6BAAqB;AAErB,kCAAoD;AAsBpD,uCAAqC;;;;;sBAjC/B,MAAM,IAAI"}
1
+ {"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["internal.js"],"names":[],"mappings":"AAiDA,iEAIC;AAeD,sEAUC;AAED,oDAQC;AAED,yDAOC;AAMD,8DAIC;AAID;;SAMC;AAUD,gDAQC;AAMD,+DAKC;AAlIM,6BAAqB;AAErB,kCAAoD;AAyCpD,uCAAqC;;;;;sBArD/B,MAAM,IAAI"}
@@ -9,16 +9,34 @@
9
9
  * behavior's listeners/observers.
10
10
  *
11
11
  * @typedef {object} DelegateOpts
12
- * @property {Document | Element} [root]
12
+ * @property {Document | Element | null} [root]
13
13
  * Event-delegation root; also scopes which controls are queried. Default: `document`.
14
+ * `null` means a scope was requested but is not ready yet, so the behavior no-ops.
14
15
  */
15
16
 
16
17
  export const noop = () => {};
17
18
 
18
19
  export const hasDom = () => typeof document !== 'undefined';
19
20
 
21
+ function isDelegationHost(value) {
22
+ if (!value || typeof value !== 'object') return false;
23
+ if (value.nodeType === 9) {
24
+ return (
25
+ typeof value.addEventListener === 'function' && typeof value.querySelectorAll === 'function'
26
+ );
27
+ }
28
+ if (value.nodeType === 1) {
29
+ return (
30
+ typeof value.addEventListener === 'function' &&
31
+ typeof value.matches === 'function' &&
32
+ typeof value.querySelectorAll === 'function'
33
+ );
34
+ }
35
+ return false;
36
+ }
37
+
20
38
  // Resolve the delegation host from an init call's `root` option, distinguishing
21
- // three cases so an unattached/null root never silently widens to whole-document
39
+ // cases so an unattached/null root never silently widens to whole-document
22
40
  // delegation (the "scoped island hijacks every control" foot-gun):
23
41
  // • root absent/undefined → no scope requested → delegate from `fallback`
24
42
  // (default `document`). This is the intended global-wiring path.
@@ -26,11 +44,13 @@ export const hasDom = () => typeof document !== 'undefined';
26
44
  // framework ref still null at mount). Return null so the caller no-ops
27
45
  // instead of hijacking the whole document.
28
46
  // • root is an element → use it.
47
+ // • root is anything else → no-op; the public contract is Document | Element.
29
48
  // The bindings (@ponchia/ui/{react,solid,qwik}) emit `root: null` for the
30
49
  // not-ready case precisely so this distinction survives across the boundary.
31
50
  export function resolveHost(root, fallback = document) {
32
51
  if (root === null) return null;
33
- return root || fallback;
52
+ if (root === undefined) return isDelegationHost(fallback) ? fallback : null;
53
+ return isDelegationHost(root) ? root : null;
34
54
  }
35
55
 
36
56
  // Monotonic counter for auto-minted field / list ids, shared across
@@ -70,7 +90,8 @@ export function byIdInHost(host, id) {
70
90
 
71
91
  export function closestSafe(el, selector) {
72
92
  try {
73
- return el.closest(selector);
93
+ const start = el?.nodeType === 1 ? el : el?.parentElement;
94
+ return start?.closest?.(selector) ?? null;
74
95
  } catch {
75
96
  return null;
76
97
  }
@@ -1,8 +1,3 @@
1
- /**
2
- * @typedef {object} LegendToggleDetail
3
- * @property {string | number} series The entry's `data-series`, or its 0-based index when unset.
4
- * @property {boolean} active The new state (`true` ⇒ series shown).
5
- */
6
1
  /**
7
2
  * Wire `[data-bronto-legend]` interactive legends. Each entry is a
8
3
  * `.ui-legend__item` authored as a `<button aria-pressed>`; clicking it (or
@@ -1 +1 @@
1
- {"version":3,"file":"legend.d.ts","sourceRoot":"","sources":["legend.js"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,sCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAmD3C;;;;;YAvEa,MAAM,GAAG,MAAM;;;;YACf,OAAO"}
1
+ {"version":3,"file":"legend.d.ts","sourceRoot":"","sources":["legend.js"],"names":[],"mappings":"AAUA;;;;;;;;;;;;;;;;;GAiBG;AACH,sCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAiH3C;;;;;YAvIa,MAAM,GAAG,MAAM;;;;YACf,OAAO"}
@@ -1,4 +1,4 @@
1
- import { hasDom, resolveHost, noop, bindOnce } from './internal.js';
1
+ import { hasDom, resolveHost, noop, bindOnce, collectHosts, closestSafe } from './internal.js';
2
2
 
3
3
  /**
4
4
  * @typedef {object} LegendToggleDetail
@@ -6,6 +6,8 @@ import { hasDom, resolveHost, noop, bindOnce } from './internal.js';
6
6
  * @property {boolean} active The new state (`true` ⇒ series shown).
7
7
  */
8
8
 
9
+ const handledEvents = new WeakSet();
10
+
9
11
  /**
10
12
  * Wire `[data-bronto-legend]` interactive legends. Each entry is a
11
13
  * `.ui-legend__item` authored as a `<button aria-pressed>`; clicking it (or
@@ -28,16 +30,40 @@ export function initLegend({ root } = {}) {
28
30
  if (!hasDom()) return noop;
29
31
  const host = resolveHost(root);
30
32
  if (!host) return noop;
33
+ const snapshotAttrs = (el, names) => {
34
+ const attrs = {};
35
+ for (const name of names) {
36
+ attrs[name] = {
37
+ had: el.hasAttribute(name),
38
+ value: el.getAttribute(name),
39
+ };
40
+ }
41
+ return attrs;
42
+ };
43
+ const restoreAttrs = (el, attrs) => {
44
+ for (const [name, state] of Object.entries(attrs)) {
45
+ if (state.had) el.setAttribute(name, state.value);
46
+ else el.removeAttribute(name);
47
+ }
48
+ };
49
+ const directItems = (legend) =>
50
+ [...legend.querySelectorAll('.ui-legend__item')].filter(
51
+ (el) => el.closest('[data-bronto-legend]') === legend,
52
+ );
31
53
  const isButton = (el) => el.tagName === 'BUTTON' || el.getAttribute('role') === 'button';
32
- const onClick = (e) => {
33
- const item = e.target.closest('.ui-legend__item');
54
+ const legendFor = (item) => {
34
55
  if (!item || !host.contains(item)) return;
35
56
  const legend = item.closest('[data-bronto-legend]');
36
57
  if (!legend || !host.contains(legend)) return;
58
+ return legend;
59
+ };
60
+ const toggle = (item) => {
61
+ const legend = legendFor(item);
62
+ if (!legend) return false;
37
63
  // The contract requires a real `<button>` (keyboard-operable, focusable). A
38
- // non-button item is mouse-only refuse to toggle it rather than ship a
39
- // pointer-only control (WCAG 2.1.1 C11). The author is warned at bind.
40
- if (!isButton(item)) return;
64
+ // non-button item is mouse-only unless role=button is keyboard-normalized
65
+ // below refuse anything else rather than ship a pointer-only control.
66
+ if (!isButton(item)) return false;
41
67
  const active = item.getAttribute('aria-pressed') !== 'false';
42
68
  const next = !active;
43
69
  item.setAttribute('aria-pressed', String(next));
@@ -53,24 +79,62 @@ export function initLegend({ root } = {}) {
53
79
  detail: { series: item.dataset.series ?? items.indexOf(item), active: next },
54
80
  }),
55
81
  );
82
+ return true;
83
+ };
84
+ const onClick = (e) => {
85
+ if (handledEvents.has(e)) return;
86
+ if (toggle(closestSafe(e.target, '.ui-legend__item'))) handledEvents.add(e);
87
+ };
88
+ const onKey = (e) => {
89
+ if (handledEvents.has(e)) return;
90
+ if (e.key !== 'Enter' && e.key !== ' ') return;
91
+ const item = closestSafe(e.target, '.ui-legend__item');
92
+ if (!item || item.tagName === 'BUTTON' || item.getAttribute('role') !== 'button') return;
93
+ e.preventDefault();
94
+ if (toggle(item)) handledEvents.add(e);
56
95
  };
57
96
  return bindOnce(host, 'legend', () => {
58
- // Warn once per non-button item present at bind: it gets cursor:pointer from
59
- // the CSS but is neither focusable nor keyboard-operable (C11).
97
+ // Normalize role=button entries and warn once per unsupported non-button
98
+ // item present at bind. A real <button> remains the recommended markup.
99
+ const legends = collectHosts(host, '[data-bronto-legend]');
100
+ const itemStates = [];
101
+ for (const legend of legends) {
102
+ for (const el of directItems(legend)) {
103
+ itemStates.push({
104
+ el,
105
+ attrs: snapshotAttrs(el, ['type', 'tabindex', 'aria-pressed']),
106
+ inactive: el.classList.contains('is-inactive'),
107
+ });
108
+ if (el.tagName === 'BUTTON' && !el.hasAttribute('type')) el.setAttribute('type', 'button');
109
+ if (
110
+ el.tagName !== 'BUTTON' &&
111
+ el.getAttribute('role') === 'button' &&
112
+ !el.hasAttribute('tabindex')
113
+ ) {
114
+ el.tabIndex = 0;
115
+ }
116
+ }
117
+ }
60
118
  if (typeof console !== 'undefined') {
61
- for (const legend of host.querySelectorAll?.('[data-bronto-legend]') ?? []) {
62
- const stray = [...legend.querySelectorAll('.ui-legend__item')].some(
63
- (el) => el.closest('[data-bronto-legend]') === legend && !isButton(el),
64
- );
119
+ for (const legend of legends) {
120
+ const stray = directItems(legend).some((el) => !isButton(el));
65
121
  if (stray) {
66
122
  console.warn(
67
- '[bronto] initLegend(): interactive legend entries must be <button> (or role="button") to be keyboard-operable a non-button .ui-legend__item is ignored.',
123
+ '[bronto] initLegend(): interactive legend entries must be <button> or role="button" — unsupported .ui-legend__item controls are ignored.',
68
124
  );
69
125
  break;
70
126
  }
71
127
  }
72
128
  }
73
129
  host.addEventListener('click', onClick);
74
- return () => host.removeEventListener('click', onClick);
130
+ host.addEventListener('keydown', onKey);
131
+ return () => {
132
+ host.removeEventListener('click', onClick);
133
+ host.removeEventListener('keydown', onKey);
134
+ for (const state of itemStates) {
135
+ restoreAttrs(state.el, state.attrs);
136
+ state.el.classList.toggle('is-inactive', state.inactive);
137
+ }
138
+ };
75
139
  });
76
140
  }
@@ -1 +1 @@
1
- {"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["menu.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AACH,oCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAmC3C"}
1
+ {"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["menu.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AACH,oCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAwC3C"}
package/behaviors/menu.js CHANGED
@@ -1,4 +1,4 @@
1
- import { hasDom, resolveHost, noop, bindOnce } from './internal.js';
1
+ import { hasDom, resolveHost, noop, bindOnce, closestSafe, collectHosts } from './internal.js';
2
2
 
3
3
  /**
4
4
  * Dropdown-menu close affordances for a native `<details data-bronto-menu>`
@@ -18,32 +18,37 @@ export function initMenu({ root } = {}) {
18
18
  if (!hasDom()) return noop;
19
19
  const host = resolveHost(root);
20
20
  if (!host) return noop;
21
- const openMenus = () => host.querySelectorAll?.('[data-bronto-menu][open]') ?? [];
21
+ const doc = host.nodeType === 9 ? host : host.ownerDocument || document;
22
+ const openMenus = () => collectHosts(host, '[data-bronto-menu][open]');
22
23
  const shut = (menu) => {
23
24
  if (!menu || !menu.open) return;
24
25
  menu.open = false;
25
26
  menu.querySelector('summary')?.focus();
26
27
  };
27
28
  const onClick = (e) => {
28
- const menu = e.target.closest('[data-bronto-menu]');
29
+ const target = e.target;
30
+ const menu = closestSafe(target, '[data-bronto-menu]');
29
31
  // Activate an item → close its menu (and return focus to summary).
30
- if (menu && e.target.closest('.ui-menu__item')) {
32
+ if (menu && host.contains(menu) && closestSafe(target, '.ui-menu__item')) {
31
33
  shut(menu);
32
34
  return;
33
35
  }
34
36
  // Click outside any open menu → close them all (no focus move).
35
- for (const m of openMenus()) if (!m.contains(e.target)) m.open = false;
37
+ for (const m of openMenus()) if (!m.contains(target)) m.open = false;
36
38
  };
37
39
  const onKey = (e) => {
38
40
  if (e.key !== 'Escape') return;
39
- const menu = e.target.closest?.('[data-bronto-menu][open]') || openMenus()[0];
41
+ const menu = closestSafe(e.target, '[data-bronto-menu][open]') || openMenus()[0];
42
+ if (!menu) return;
43
+ e.preventDefault();
44
+ e.stopPropagation();
40
45
  shut(menu);
41
46
  };
42
47
  return bindOnce(host, 'menu', () => {
43
- host.addEventListener('click', onClick);
48
+ doc.addEventListener('click', onClick);
44
49
  host.addEventListener('keydown', onKey);
45
50
  return () => {
46
- host.removeEventListener('click', onClick);
51
+ doc.removeEventListener('click', onClick);
47
52
  host.removeEventListener('keydown', onKey);
48
53
  };
49
54
  });
@@ -1 +1 @@
1
- {"version":3,"file":"modal.d.ts","sourceRoot":"","sources":["modal.js"],"names":[],"mappings":"AAEA;;;GAGG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAyF3C;;;;;YAvHa,QAAQ"}
1
+ {"version":3,"file":"modal.d.ts","sourceRoot":"","sources":["modal.js"],"names":[],"mappings":"AAsDA;;;GAGG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CA+F3C;;;;;YA7Ha,QAAQ"}