@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
@@ -7,6 +7,7 @@ import {
7
7
  scrollIntoViewSafe,
8
8
  wrapIndex,
9
9
  collectHosts,
10
+ closestSafe,
10
11
  } from './internal.js';
11
12
 
12
13
  /**
@@ -54,12 +55,80 @@ export function initCombobox({ root } = {}) {
54
55
  const boxes = collectHosts(host, '[data-bronto-combobox]');
55
56
  const cleanups = [];
56
57
 
58
+ const snapshotAttrs = (el, names) => {
59
+ const out = {};
60
+ for (const name of names) {
61
+ out[name] = {
62
+ had: el.hasAttribute(name),
63
+ value: el.getAttribute(name),
64
+ };
65
+ }
66
+ return out;
67
+ };
68
+
69
+ const restoreAttrs = (el, attrs) => {
70
+ for (const [name, attr] of Object.entries(attrs)) {
71
+ if (attr.had) el.setAttribute(name, attr.value);
72
+ else el.removeAttribute(name);
73
+ }
74
+ };
75
+
57
76
  for (const box of boxes) {
58
77
  const input = box.querySelector('[role="combobox"], .ui-combobox__input');
59
78
  const list = box.querySelector('[role="listbox"], .ui-combobox__list');
60
79
  if (!input || !list) continue;
61
80
  const empty = box.querySelector('.ui-combobox__empty');
62
- const listId = list.id || (list.id = `bronto-cb-list-${nextFieldUid()}`);
81
+ const optionStates = new WeakMap();
82
+ let listId = '';
83
+
84
+ const rememberOptionState = (option) => {
85
+ if (optionStates.has(option)) return;
86
+ optionStates.set(option, {
87
+ hidden: option.hidden,
88
+ active: option.classList.contains('is-active'),
89
+ attrs: snapshotAttrs(option, ['id', 'role', 'aria-selected']),
90
+ });
91
+ };
92
+
93
+ const rememberState = () => ({
94
+ input: snapshotAttrs(input, [
95
+ 'role',
96
+ 'aria-controls',
97
+ 'aria-autocomplete',
98
+ 'aria-expanded',
99
+ 'aria-activedescendant',
100
+ 'autocomplete',
101
+ ]),
102
+ list: {
103
+ hidden: list.hidden,
104
+ attrs: snapshotAttrs(list, ['id', 'role', 'aria-label']),
105
+ },
106
+ empty: empty
107
+ ? {
108
+ hidden: empty.hidden,
109
+ attrs: snapshotAttrs(empty, ['role']),
110
+ }
111
+ : null,
112
+ options: optionStates,
113
+ });
114
+
115
+ const restoreState = (state) => {
116
+ restoreAttrs(input, state.input);
117
+ list.hidden = state.list.hidden;
118
+ restoreAttrs(list, state.list.attrs);
119
+ if (empty && state.empty) {
120
+ empty.hidden = state.empty.hidden;
121
+ restoreAttrs(empty, state.empty.attrs);
122
+ }
123
+ for (const option of options) {
124
+ const optionState = state.options.get(option);
125
+ if (!optionState) continue;
126
+ option.hidden = optionState.hidden;
127
+ option.classList.toggle('is-active', optionState.active);
128
+ restoreAttrs(option, optionState.attrs);
129
+ }
130
+ };
131
+
63
132
  // Re-readable so the opt-in MutationObserver (`data-bronto-combobox-live`)
64
133
  // can pick up async/replaced option nodes without a full re-init. `visible`,
65
134
  // `filter`, `move`, etc. close over this binding, so reassigning it is enough.
@@ -67,51 +136,11 @@ export function initCombobox({ root } = {}) {
67
136
  const syncOptions = () => {
68
137
  options = [...list.querySelectorAll('[role="option"], .ui-combobox__option')];
69
138
  options.forEach((o, i) => {
139
+ rememberOptionState(o);
70
140
  if (!o.id) o.id = `${listId}-opt-${i}`;
71
141
  o.setAttribute('role', 'option');
72
142
  });
73
143
  };
74
- syncOptions();
75
- list.setAttribute('role', 'listbox');
76
- // Give the listbox its own accessible name (a bare role=listbox is unnamed
77
- // to a screen reader) by mirroring the input's REAL name. (a11y review C30.)
78
- // The placeholder is deliberately NOT in this chain: the input warning below
79
- // already rejects a placeholder as an inadequate name, so papering the
80
- // listbox over with it would contradict that — if there's no real name the
81
- // listbox stays unnamed and the warning is the signal. (component audit C28.)
82
- if (!list.hasAttribute('aria-label') && !list.hasAttribute('aria-labelledby')) {
83
- const name = input.getAttribute('aria-label') || input.labels?.[0]?.textContent?.trim();
84
- if (name) list.setAttribute('aria-label', name);
85
- }
86
- // A `role="combobox"` with no accessible name is a silent AT failure. A
87
- // placeholder is not a robust name (it can vanish and is ignored by some
88
- // AT), so warn unless there is a real label/aria-label/aria-labelledby/title
89
- // (C7). We can't invent a good name, hence a dev-time warning, not a guess.
90
- const inputNamed =
91
- input.hasAttribute('aria-label') ||
92
- input.hasAttribute('aria-labelledby') ||
93
- !!input.labels?.length ||
94
- input.hasAttribute('title');
95
- if (!inputNamed && typeof console !== 'undefined') {
96
- console.warn(
97
- '[bronto] initCombobox(): the combobox input has no accessible name — add a <label>, aria-label, or aria-labelledby (a placeholder is not enough).',
98
- );
99
- }
100
- input.setAttribute('role', 'combobox');
101
- input.setAttribute('aria-controls', listId);
102
- input.setAttribute('aria-autocomplete', 'list');
103
- input.setAttribute('aria-expanded', 'false');
104
- input.setAttribute('autocomplete', 'off');
105
- list.hidden = true;
106
- // Hide the empty-state at rest: it must only appear once a filter yields no
107
- // matches, never on an idle combobox. Without this an author who omits
108
- // `hidden` on `.ui-combobox__empty` ships a box that reads "No matches"
109
- // before the user has typed anything. (component audit C10.) Make it a
110
- // status live region so its appearance is announced. (component audit C38.)
111
- if (empty) {
112
- empty.hidden = true;
113
- if (!empty.hasAttribute('role')) empty.setAttribute('role', 'status');
114
- }
115
144
 
116
145
  let active = -1;
117
146
  const visible = () => options.filter((o) => !o.hidden);
@@ -243,7 +272,7 @@ export function initCombobox({ root } = {}) {
243
272
  }
244
273
  };
245
274
  const onOptionClick = (e) => {
246
- const opt = e.target.closest('[role="option"], .ui-combobox__option');
275
+ const opt = closestSafe(e.target, '[role="option"], .ui-combobox__option');
247
276
  if (opt) select(opt);
248
277
  };
249
278
  const onDocClick = (e) => {
@@ -251,6 +280,49 @@ export function initCombobox({ root } = {}) {
251
280
  };
252
281
 
253
282
  const bound = bindOnce(box, 'combobox', () => {
283
+ const state = rememberState();
284
+ listId = list.id || (list.id = `bronto-cb-list-${nextFieldUid()}`);
285
+ syncOptions();
286
+ list.setAttribute('role', 'listbox');
287
+ // Give the listbox its own accessible name (a bare role=listbox is unnamed
288
+ // to a screen reader) by mirroring the input's REAL name. (a11y review C30.)
289
+ // The placeholder is deliberately NOT in this chain: the input warning below
290
+ // already rejects a placeholder as an inadequate name, so papering the
291
+ // listbox over with it would contradict that — if there's no real name the
292
+ // listbox stays unnamed and the warning is the signal. (component audit C28.)
293
+ if (!list.hasAttribute('aria-label') && !list.hasAttribute('aria-labelledby')) {
294
+ const name = input.getAttribute('aria-label') || input.labels?.[0]?.textContent?.trim();
295
+ if (name) list.setAttribute('aria-label', name);
296
+ }
297
+ // A `role="combobox"` with no accessible name is a silent AT failure. A
298
+ // placeholder is not a robust name (it can vanish and is ignored by some
299
+ // AT), so warn unless there is a real label/aria-label/aria-labelledby/title
300
+ // (C7). We can't invent a good name, hence a dev-time warning, not a guess.
301
+ const inputNamed =
302
+ input.hasAttribute('aria-label') ||
303
+ input.hasAttribute('aria-labelledby') ||
304
+ !!input.labels?.length ||
305
+ input.hasAttribute('title');
306
+ if (!inputNamed && typeof console !== 'undefined') {
307
+ console.warn(
308
+ '[bronto] initCombobox(): the combobox input has no accessible name — add a <label>, aria-label, or aria-labelledby (a placeholder is not enough).',
309
+ );
310
+ }
311
+ input.setAttribute('role', 'combobox');
312
+ input.setAttribute('aria-controls', listId);
313
+ input.setAttribute('aria-autocomplete', 'list');
314
+ input.setAttribute('aria-expanded', 'false');
315
+ input.setAttribute('autocomplete', 'off');
316
+ // Hide the empty-state at rest: it must only appear once a filter yields no
317
+ // matches, never on an idle combobox. Without this an author who omits
318
+ // `hidden` on `.ui-combobox__empty` ships a box that reads "No matches"
319
+ // before the user has typed anything. (component audit C10.) Make it a
320
+ // status live region so its appearance is announced. (component audit C38.)
321
+ if (empty) {
322
+ empty.hidden = true;
323
+ if (!empty.hasAttribute('role')) empty.setAttribute('role', 'status');
324
+ }
325
+ close();
254
326
  input.addEventListener('input', onInput);
255
327
  input.addEventListener('keydown', onKey);
256
328
  list.addEventListener('click', onOptionClick);
@@ -268,6 +340,8 @@ export function initCombobox({ root } = {}) {
268
340
  input.removeEventListener('keydown', onKey);
269
341
  list.removeEventListener('click', onOptionClick);
270
342
  document.removeEventListener('click', onDocClick);
343
+ restoreState(state);
344
+ active = -1;
271
345
  };
272
346
  });
273
347
  cleanups.push(bound);
@@ -1 +1 @@
1
- {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["command.js"],"names":[],"mappings":"AAWA;;;;GAIG;AAEH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,uCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CA+J3C;;;;;WAzLa,MAAM;;;;WACN,MAAM"}
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["command.js"],"names":[],"mappings":"AAYA;;;;GAIG;AAEH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,uCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CA0N3C;;;;;WApPa,MAAM;;;;WACN,MAAM"}
@@ -7,6 +7,7 @@ import {
7
7
  collectHosts,
8
8
  scrollIntoViewSafe,
9
9
  wrapIndex,
10
+ closestSafe,
10
11
  } from './internal.js';
11
12
 
12
13
  /**
@@ -46,6 +47,24 @@ export function initCommand({ root } = {}) {
46
47
  const palettes = collectHosts(host, '[data-bronto-command]');
47
48
  const cleanups = [];
48
49
 
50
+ const snapshotAttrs = (el, names) => {
51
+ const out = {};
52
+ for (const name of names) {
53
+ out[name] = {
54
+ had: el.hasAttribute(name),
55
+ value: el.getAttribute(name),
56
+ };
57
+ }
58
+ return out;
59
+ };
60
+
61
+ const restoreAttrs = (el, attrs) => {
62
+ for (const [name, attr] of Object.entries(attrs)) {
63
+ if (attr.had) el.setAttribute(name, attr.value);
64
+ else el.removeAttribute(name);
65
+ }
66
+ };
67
+
49
68
  for (const box of palettes) {
50
69
  const input = box.querySelector('.ui-command__input, input');
51
70
  const list = box.querySelector('.ui-command__list, [role="listbox"]');
@@ -54,18 +73,44 @@ export function initCommand({ root } = {}) {
54
73
  const items = [...list.querySelectorAll('.ui-command__item, [role="option"]')];
55
74
  const groups = [...list.querySelectorAll('.ui-command__group')];
56
75
 
57
- const listId = list.id || (list.id = `bronto-cmd-${nextFieldUid()}`);
58
- items.forEach((it, i) => {
59
- if (!it.id) it.id = `${listId}-opt-${i}`;
60
- it.setAttribute('role', 'option');
76
+ const rememberState = () => ({
77
+ input: snapshotAttrs(input, [
78
+ 'role',
79
+ 'aria-controls',
80
+ 'aria-autocomplete',
81
+ 'aria-expanded',
82
+ 'aria-activedescendant',
83
+ 'autocomplete',
84
+ ]),
85
+ list: snapshotAttrs(list, ['id', 'role']),
86
+ empty: empty ? { hidden: empty.hidden } : null,
87
+ groups: groups.map((g) => ({
88
+ el: g,
89
+ hidden: g.hidden,
90
+ attrs: snapshotAttrs(g, ['role']),
91
+ })),
92
+ items: items.map((it) => ({
93
+ el: it,
94
+ hidden: it.hidden,
95
+ active: it.classList.contains('is-active'),
96
+ attrs: snapshotAttrs(it, ['id', 'role', 'aria-selected']),
97
+ })),
61
98
  });
62
- groups.forEach((g) => g.setAttribute('role', 'presentation'));
63
- list.setAttribute('role', 'listbox');
64
- input.setAttribute('role', 'combobox');
65
- input.setAttribute('aria-controls', listId);
66
- input.setAttribute('aria-autocomplete', 'list');
67
- input.setAttribute('aria-expanded', 'true');
68
- input.setAttribute('autocomplete', 'off');
99
+
100
+ const restoreState = (state) => {
101
+ restoreAttrs(input, state.input);
102
+ restoreAttrs(list, state.list);
103
+ if (empty && state.empty) empty.hidden = state.empty.hidden;
104
+ for (const group of state.groups) {
105
+ group.el.hidden = group.hidden;
106
+ restoreAttrs(group.el, group.attrs);
107
+ }
108
+ for (const item of state.items) {
109
+ item.el.hidden = item.hidden;
110
+ item.el.classList.toggle('is-active', item.active);
111
+ restoreAttrs(item.el, item.attrs);
112
+ }
113
+ };
69
114
 
70
115
  let active = -1;
71
116
  const visible = () => items.filter((it) => !it.hidden);
@@ -176,22 +221,37 @@ export function initCommand({ root } = {}) {
176
221
  }
177
222
  };
178
223
  const onClick = (e) => {
179
- const item = e.target.closest('.ui-command__item, [role="option"]');
224
+ const item = closestSafe(e.target, '.ui-command__item, [role="option"]');
180
225
  if (item && list.contains(item)) choose(item);
181
226
  };
182
227
 
183
228
  const bound = bindOnce(box, 'command', () => {
229
+ const state = rememberState();
230
+ const listId = list.id || (list.id = `bronto-cmd-${nextFieldUid()}`);
231
+ items.forEach((it, i) => {
232
+ if (!it.id) it.id = `${listId}-opt-${i}`;
233
+ it.setAttribute('role', 'option');
234
+ });
235
+ groups.forEach((g) => g.setAttribute('role', 'presentation'));
236
+ list.setAttribute('role', 'listbox');
237
+ input.setAttribute('role', 'combobox');
238
+ input.setAttribute('aria-controls', listId);
239
+ input.setAttribute('aria-autocomplete', 'list');
240
+ input.setAttribute('aria-expanded', 'true');
241
+ input.setAttribute('autocomplete', 'off');
184
242
  input.addEventListener('input', onInput);
185
243
  input.addEventListener('keydown', onKey);
186
244
  list.addEventListener('click', onClick);
245
+ // Seed the initial active item (first visible).
246
+ filter();
187
247
  return () => {
188
248
  input.removeEventListener('input', onInput);
189
249
  input.removeEventListener('keydown', onKey);
190
250
  list.removeEventListener('click', onClick);
251
+ restoreState(state);
252
+ active = -1;
191
253
  };
192
254
  });
193
- // Seed the initial active item (first visible).
194
- filter();
195
255
  cleanups.push(bound);
196
256
  }
197
257
 
@@ -1 +1 @@
1
- {"version":3,"file":"connectors.d.ts","sourceRoot":"","sources":["connectors.js"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;GAcG;AACH,0CAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CA+E3C"}
1
+ {"version":3,"file":"connectors.d.ts","sourceRoot":"","sources":["connectors.js"],"names":[],"mappings":"AA2CA;;;;;;;;;;;;;;GAcG;AACH,0CAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CA4H3C"}
@@ -3,6 +3,44 @@ import { connectRects, arrowHead, dotMark } from '../connectors/index.js';
3
3
 
4
4
  const SVGNS = 'http://www.w3.org/2000/svg';
5
5
 
6
+ const snapshotAttrs = (el) =>
7
+ Array.from(el.attributes, ({ name, value }) => ({
8
+ name,
9
+ value,
10
+ }));
11
+
12
+ const restoreAttrs = (el, attrs) => {
13
+ for (const { name } of Array.from(el.attributes)) el.removeAttribute(name);
14
+ for (const { name, value } of attrs) el.setAttribute(name, value);
15
+ };
16
+
17
+ const snapshotPart = (svg, selector) => {
18
+ const node = svg.querySelector(selector);
19
+ if (!node) return { node: null };
20
+ return {
21
+ node,
22
+ attrs: snapshotAttrs(node),
23
+ parent: node.parentNode,
24
+ nextSibling: node.nextSibling,
25
+ textContent: node.textContent,
26
+ };
27
+ };
28
+
29
+ const restorePart = (svg, selector, state) => {
30
+ if (!state.node) {
31
+ svg.querySelector(selector)?.remove();
32
+ return;
33
+ }
34
+
35
+ const parent = state.parent || svg;
36
+ if (state.node.parentNode !== parent) {
37
+ const before = state.nextSibling?.parentNode === parent ? state.nextSibling : null;
38
+ parent.insertBefore(state.node, before);
39
+ }
40
+ restoreAttrs(state.node, state.attrs);
41
+ state.node.textContent = state.textContent;
42
+ };
43
+
6
44
  /**
7
45
  * Draw + keep leader lines in sync. Each `[data-bronto-connector]` is an
8
46
  * `.ui-connector` SVG overlaying a positioned container; `data-from`/`data-to`
@@ -22,26 +60,60 @@ export function initConnectors({ root } = {}) {
22
60
  if (!hasDom()) return noop;
23
61
  const host = resolveHost(root);
24
62
  if (!host) return noop;
25
- const connectors = collectHosts(host, '[data-bronto-connector]');
26
- if (!connectors.length) return noop;
63
+
64
+ const fallbackRect = (svg, el) => {
65
+ const origin = svg.getBoundingClientRect();
66
+ const r = el.getBoundingClientRect();
67
+ const scaleX = origin.width ? (svg.clientWidth || origin.width) / origin.width : 1;
68
+ const scaleY = origin.height ? (svg.clientHeight || origin.height) / origin.height : 1;
69
+ return {
70
+ x: (r.left - origin.left) * scaleX,
71
+ y: (r.top - origin.top) * scaleY,
72
+ width: r.width * scaleX,
73
+ height: r.height * scaleY,
74
+ };
75
+ };
76
+
77
+ const rectInSvg = (svg, el) => {
78
+ const r = el.getBoundingClientRect();
79
+ const matrix = svg.getScreenCTM?.();
80
+ const view = svg.ownerDocument?.defaultView;
81
+ const Point = view?.DOMPoint;
82
+ if (!matrix || !Point) return fallbackRect(svg, el);
83
+
84
+ try {
85
+ const inverse = matrix.inverse();
86
+ const corners = [
87
+ new Point(r.left, r.top),
88
+ new Point(r.right, r.top),
89
+ new Point(r.right, r.bottom),
90
+ new Point(r.left, r.bottom),
91
+ ].map((point) => point.matrixTransform(inverse));
92
+ const xs = corners.map((point) => point.x);
93
+ const ys = corners.map((point) => point.y);
94
+ const left = Math.min(...xs);
95
+ const right = Math.max(...xs);
96
+ const top = Math.min(...ys);
97
+ const bottom = Math.max(...ys);
98
+ return { x: left, y: top, width: right - left, height: bottom - top };
99
+ } catch {
100
+ return fallbackRect(svg, el);
101
+ }
102
+ };
27
103
 
28
104
  const draw = () => {
105
+ const connectors = collectHosts(host, '[data-bronto-connector]');
29
106
  for (const svg of connectors) {
30
107
  const from = byIdInHost(host, svg.dataset.from);
31
108
  const to = byIdInHost(host, svg.dataset.to);
32
109
  if (!from || !to) continue;
33
- const o = svg.getBoundingClientRect();
34
- const rel = (el) => {
35
- const r = el.getBoundingClientRect();
36
- return { x: r.left - o.left, y: r.top - o.top, width: r.width, height: r.height };
37
- };
38
110
  const {
39
111
  d,
40
112
  to: end,
41
113
  angle,
42
114
  } = connectRects({
43
- fromRect: rel(from),
44
- toRect: rel(to),
115
+ fromRect: rectInSvg(svg, from),
116
+ toRect: rectInSvg(svg, to),
45
117
  shape: svg.dataset.shape || 'straight',
46
118
  fromSide: svg.dataset.fromSide || undefined,
47
119
  toSide: svg.dataset.toSide || undefined,
@@ -74,6 +146,13 @@ export function initConnectors({ root } = {}) {
74
146
  };
75
147
 
76
148
  return bindOnce(host, 'connectors', () => {
149
+ const connectors = collectHosts(host, '[data-bronto-connector]');
150
+ if (!connectors.length) return noop;
151
+ const states = connectors.map((svg) => ({
152
+ svg,
153
+ path: snapshotPart(svg, '.ui-connector__path'),
154
+ end: snapshotPart(svg, '.ui-connector__end'),
155
+ }));
77
156
  draw();
78
157
  const view = host.defaultView || host.ownerDocument?.defaultView || null;
79
158
  const RO = view?.ResizeObserver;
@@ -93,6 +172,10 @@ export function initConnectors({ root } = {}) {
93
172
  ro?.disconnect();
94
173
  view?.removeEventListener('resize', draw);
95
174
  view?.removeEventListener('scroll', draw, true);
175
+ for (const state of states) {
176
+ restorePart(state.svg, '.ui-connector__path', state.path);
177
+ restorePart(state.svg, '.ui-connector__end', state.end);
178
+ }
96
179
  };
97
180
  });
98
181
  }
@@ -1 +1 @@
1
- {"version":3,"file":"crosshair.d.ts","sourceRoot":"","sources":["crosshair.js"],"names":[],"mappings":"AAEA;;;;;;GAMG;AAEH;;;;;;;;;;;;;;GAcG;AACH,yCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAmD3C;;;;;OAtEa,MAAM;;;;OACN,MAAM;;;;QACN,MAAM;;;;QACN,MAAM"}
1
+ {"version":3,"file":"crosshair.d.ts","sourceRoot":"","sources":["crosshair.js"],"names":[],"mappings":"AAEA;;;;;;GAMG;AAEH;;;;;;;;;;;;;;GAcG;AACH,yCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAiG3C;;;;;OApHa,MAAM;;;;OACN,MAAM;;;;QACN,MAAM;;;;QACN,MAAM"}
@@ -33,8 +33,49 @@ export function initCrosshair({ root } = {}) {
33
33
  const cleanups = [];
34
34
  for (const plot of plots) {
35
35
  const overlay = plot.querySelector('.ui-crosshair');
36
- if (!overlay) continue;
36
+ let overlayState = null;
37
+ const rememberOverlay = () => {
38
+ if (!overlay || overlayState) return;
39
+ overlayState = {
40
+ active: overlay.classList.contains('is-active'),
41
+ x: {
42
+ value: overlay.style.getPropertyValue('--crosshair-x'),
43
+ priority: overlay.style.getPropertyPriority('--crosshair-x'),
44
+ },
45
+ y: {
46
+ value: overlay.style.getPropertyValue('--crosshair-y'),
47
+ priority: overlay.style.getPropertyPriority('--crosshair-y'),
48
+ },
49
+ inline: {
50
+ had: overlay.hasAttribute('data-readout-inline'),
51
+ value: overlay.getAttribute('data-readout-inline'),
52
+ },
53
+ block: {
54
+ had: overlay.hasAttribute('data-readout-block'),
55
+ value: overlay.getAttribute('data-readout-block'),
56
+ },
57
+ };
58
+ };
59
+ const restoreData = (name, state) => {
60
+ if (state.had) overlay.setAttribute(name, state.value);
61
+ else overlay.removeAttribute(name);
62
+ };
63
+ const restoreOverlay = () => {
64
+ if (!overlay || !overlayState) return;
65
+ overlay.classList.toggle('is-active', overlayState.active);
66
+ if (overlayState.x.value)
67
+ overlay.style.setProperty('--crosshair-x', overlayState.x.value, overlayState.x.priority);
68
+ else overlay.style.removeProperty('--crosshair-x');
69
+ if (overlayState.y.value)
70
+ overlay.style.setProperty('--crosshair-y', overlayState.y.value, overlayState.y.priority);
71
+ else overlay.style.removeProperty('--crosshair-y');
72
+ restoreData('data-readout-inline', overlayState.inline);
73
+ restoreData('data-readout-block', overlayState.block);
74
+ overlayState = null;
75
+ };
37
76
  const onMove = (e) => {
77
+ if (!overlay) return;
78
+ rememberOverlay();
38
79
  const r = plot.getBoundingClientRect();
39
80
  if (!r.width || !r.height) return;
40
81
  const x = e.clientX - r.left;
@@ -48,6 +89,8 @@ export function initCrosshair({ root } = {}) {
48
89
  const rtl = getComputedStyle(plot).direction === 'rtl';
49
90
  overlay.style.setProperty('--crosshair-x', `${rtl ? r.right - e.clientX : x}px`);
50
91
  overlay.style.setProperty('--crosshair-y', `${y}px`);
92
+ overlay.dataset.readoutInline = x / r.width > 0.5 ? 'before' : 'after';
93
+ overlay.dataset.readoutBlock = y / r.height > 0.5 ? 'above' : 'below';
51
94
  overlay.classList.add('is-active');
52
95
  plot.dispatchEvent(
53
96
  new CustomEvent('bronto:crosshair:move', {
@@ -57,16 +100,19 @@ export function initCrosshair({ root } = {}) {
57
100
  );
58
101
  };
59
102
  const onLeave = () => {
103
+ if (!overlay) return;
60
104
  overlay.classList.remove('is-active');
61
105
  plot.dispatchEvent(new CustomEvent('bronto:crosshair:leave', { bubbles: true }));
62
106
  };
63
107
  cleanups.push(
64
108
  bindOnce(plot, 'crosshair', () => {
109
+ if (!overlay) return noop;
65
110
  plot.addEventListener('pointermove', onMove);
66
111
  plot.addEventListener('pointerleave', onLeave);
67
112
  return () => {
68
113
  plot.removeEventListener('pointermove', onMove);
69
114
  plot.removeEventListener('pointerleave', onLeave);
115
+ restoreOverlay();
70
116
  };
71
117
  }),
72
118
  );
@@ -1 +1 @@
1
- {"version":3,"file":"dialog.d.ts","sourceRoot":"","sources":["dialog.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AACH,sCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CA0D3C"}
1
+ {"version":3,"file":"dialog.d.ts","sourceRoot":"","sources":["dialog.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AACH,sCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CA+E3C"}
@@ -23,25 +23,37 @@ export function initDialog({ root } = {}) {
23
23
  const host = resolveHost(root);
24
24
  if (!host) return noop;
25
25
  const managedDialogs = new WeakSet();
26
+ const focusRestorers = new Map();
26
27
  const canManageDialog = (dlg, origin) => host.contains(origin) || managedDialogs.has(dlg);
27
28
 
28
29
  const openFrom = (opener) => {
29
30
  const dlg = byIdInHost(host, opener.getAttribute('data-bronto-open'));
30
- if (!dlg || typeof dlg.showModal !== 'function' || dlg.open) return;
31
+ if (!dlg || typeof dlg.showModal !== 'function' || dlg.open) return false;
32
+ const previous = focusRestorers.get(dlg);
33
+ if (previous) {
34
+ dlg.removeEventListener('close', previous);
35
+ focusRestorers.delete(dlg);
36
+ }
37
+ const restoreFocus = () => {
38
+ focusRestorers.delete(dlg);
39
+ if (opener.isConnected && typeof opener.focus === 'function') opener.focus();
40
+ };
41
+ try {
42
+ dlg.showModal();
43
+ } catch {
44
+ return false;
45
+ }
31
46
  managedDialogs.add(dlg);
32
- dlg.addEventListener(
33
- 'close',
34
- () => {
35
- if (opener.isConnected && typeof opener.focus === 'function') opener.focus();
36
- },
37
- { once: true },
38
- );
39
- dlg.showModal();
47
+ focusRestorers.set(dlg, restoreFocus);
48
+ dlg.addEventListener('close', restoreFocus, { once: true });
49
+ return true;
40
50
  };
41
51
 
42
52
  const closeFrom = (closer) => {
43
53
  const dlg = closer.closest('dialog');
44
- if (dlg && dlg.open && canManageDialog(dlg, closer)) dlg.close();
54
+ if (!dlg || !dlg.open || !canManageDialog(dlg, closer)) return false;
55
+ dlg.close();
56
+ return true;
45
57
  };
46
58
 
47
59
  const lightDismiss = (dlg) => {
@@ -52,26 +64,35 @@ export function initDialog({ root } = {}) {
52
64
  canManageDialog(dlg, dlg)
53
65
  ) {
54
66
  dlg.close();
67
+ return true;
55
68
  }
69
+ return false;
56
70
  };
57
71
 
58
72
  const onClick = (e) => {
59
- const opener = e.target.closest('[data-bronto-open]');
73
+ const target = e.target;
74
+ const opener = target?.closest?.('[data-bronto-open]');
60
75
  if (opener && host.contains(opener)) {
61
- openFrom(opener);
76
+ if (openFrom(opener)) e.preventDefault();
62
77
  return;
63
78
  }
64
- const closer = e.target.closest('[data-bronto-close]');
79
+ const closer = target?.closest?.('[data-bronto-close]');
65
80
  if (closer) {
66
- closeFrom(closer);
81
+ if (closeFrom(closer)) e.preventDefault();
67
82
  return;
68
83
  }
69
84
  // Light-dismiss: a click whose target is the <dialog> itself is the
70
85
  // backdrop (content sits in child elements).
71
- lightDismiss(e.target);
86
+ if (lightDismiss(target)) e.preventDefault();
72
87
  };
73
88
  return bindOnce(host, 'dialog', () => {
74
89
  document.addEventListener('click', onClick);
75
- return () => document.removeEventListener('click', onClick);
90
+ return () => {
91
+ document.removeEventListener('click', onClick);
92
+ for (const [dlg, restoreFocus] of focusRestorers) {
93
+ dlg.removeEventListener('close', restoreFocus);
94
+ }
95
+ focusRestorers.clear();
96
+ };
76
97
  });
77
98
  }
@@ -1 +1 @@
1
- {"version":3,"file":"disclosure.d.ts","sourceRoot":"","sources":["disclosure.js"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,0CAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAoB3C"}
1
+ {"version":3,"file":"disclosure.d.ts","sourceRoot":"","sources":["disclosure.js"],"names":[],"mappings":"AAYA;;;;;;;GAOG;AACH,0CAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAwC3C"}