@ponchia/ui 0.6.7 → 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 (90) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/README.md +3 -3
  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 -35
  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 +24 -9
  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 +67 -0
  23. package/behaviors/glyph.d.ts.map +1 -1
  24. package/behaviors/glyph.js +17 -2
  25. package/behaviors/inert.js +3 -2
  26. package/behaviors/internal.d.ts.map +1 -1
  27. package/behaviors/internal.js +2 -1
  28. package/behaviors/legend.d.ts +0 -5
  29. package/behaviors/legend.d.ts.map +1 -1
  30. package/behaviors/legend.js +45 -13
  31. package/behaviors/menu.d.ts.map +1 -1
  32. package/behaviors/menu.js +13 -8
  33. package/behaviors/modal.d.ts.map +1 -1
  34. package/behaviors/modal.js +77 -19
  35. package/behaviors/popover.d.ts +4 -3
  36. package/behaviors/popover.d.ts.map +1 -1
  37. package/behaviors/popover.js +89 -9
  38. package/behaviors/sources.d.ts.map +1 -1
  39. package/behaviors/sources.js +14 -2
  40. package/behaviors/splitter.d.ts.map +1 -1
  41. package/behaviors/splitter.js +149 -110
  42. package/behaviors/spotlight.d.ts.map +1 -1
  43. package/behaviors/spotlight.js +28 -2
  44. package/behaviors/table.d.ts.map +1 -1
  45. package/behaviors/table.js +103 -11
  46. package/behaviors/tabs.d.ts.map +1 -1
  47. package/behaviors/tabs.js +82 -18
  48. package/behaviors/theme.d.ts.map +1 -1
  49. package/behaviors/theme.js +25 -5
  50. package/classes/index.d.ts +15 -2
  51. package/classes/index.js +0 -1
  52. package/connectors/index.d.ts +39 -6
  53. package/connectors/index.d.ts.map +1 -1
  54. package/connectors/index.js +67 -9
  55. package/css/annotations.css +12 -0
  56. package/css/crosshair.css +27 -2
  57. package/css/feedback.css +2 -30
  58. package/css/navigation.css +12 -0
  59. package/css/tokens.css +16 -0
  60. package/dist/bronto.css +1 -1
  61. package/dist/css/analytical.css +1 -1
  62. package/dist/css/annotations.css +1 -1
  63. package/dist/css/crosshair.css +1 -1
  64. package/dist/css/feedback.css +1 -1
  65. package/dist/css/navigation.css +1 -1
  66. package/dist/css/report-kit.css +1 -1
  67. package/dist/css/tokens.css +1 -1
  68. package/docs/adr/0001-color-system.md +3 -2
  69. package/docs/annotations.md +12 -1
  70. package/docs/architecture.md +46 -13
  71. package/docs/command.md +4 -1
  72. package/docs/connectors.md +16 -0
  73. package/docs/crosshair.md +1 -1
  74. package/docs/dots.md +4 -1
  75. package/docs/glyphs.md +11 -0
  76. package/docs/migrations/0.2-to-0.3.md +1 -1
  77. package/docs/package-contract.md +5 -5
  78. package/docs/reporting.md +23 -12
  79. package/docs/stability.md +18 -2
  80. package/docs/theming.md +2 -2
  81. package/docs/usage.md +16 -2
  82. package/docs/vega.md +4 -4
  83. package/llms.txt +10 -5
  84. package/package.json +20 -4
  85. package/svelte/index.d.ts +71 -45
  86. package/svelte/index.d.ts.map +1 -1
  87. package/svelte/index.js +29 -2
  88. package/vue/index.d.ts +42 -5
  89. package/vue/index.d.ts.map +1 -1
  90. package/vue/index.js +32 -1
@@ -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,CAgE3C"}
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"}
@@ -28,20 +28,32 @@ export function initDialog({ root } = {}) {
28
28
 
29
29
  const openFrom = (opener) => {
30
30
  const dlg = byIdInHost(host, opener.getAttribute('data-bronto-open'));
31
- if (!dlg || typeof dlg.showModal !== 'function' || dlg.open) return;
32
- managedDialogs.add(dlg);
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
+ }
33
37
  const restoreFocus = () => {
34
38
  focusRestorers.delete(dlg);
35
39
  if (opener.isConnected && typeof opener.focus === 'function') opener.focus();
36
40
  };
41
+ try {
42
+ dlg.showModal();
43
+ } catch {
44
+ return false;
45
+ }
46
+ managedDialogs.add(dlg);
37
47
  focusRestorers.set(dlg, restoreFocus);
38
48
  dlg.addEventListener('close', restoreFocus, { once: true });
39
- dlg.showModal();
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,23 +64,26 @@ 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);
@@ -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"}
@@ -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,CAmL3C"}
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"}
@@ -30,12 +30,59 @@ export function initFormValidation({ root } = {}) {
30
30
  const host = resolveHost(root);
31
31
  if (!host) return noop;
32
32
  let priorNoValidate = new Map();
33
+ let controlState = new Map();
34
+ let slotState = new Map();
35
+ let summaryState = new Map();
33
36
 
34
37
  const suppressNativeValidation = (form) => {
35
38
  if (!priorNoValidate.has(form)) priorNoValidate.set(form, form.noValidate);
36
39
  form.noValidate = true;
37
40
  };
38
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
+ };
85
+
39
86
  const ensureId = (el, prefix) => {
40
87
  if (!el.id) el.id = `${prefix}-${nextFieldUid()}`;
41
88
  return el.id;
@@ -75,8 +122,10 @@ export function initFormValidation({ root } = {}) {
75
122
 
76
123
  const validateField = (control) => {
77
124
  if (!control.willValidate) return true;
125
+ rememberControl(control);
78
126
  const ok = control.validity.valid;
79
127
  const slot = slotFor(control);
128
+ if (slot) rememberSlot(slot);
80
129
  // Decide the slot TYPE by the `[data-bronto-error]` attribute, NOT the
81
130
  // `.ui-hint` class: the canonical markup is `<p class="ui-hint"
82
131
  // data-bronto-error>`, which carries BOTH. Keying off `.ui-hint` sent that
@@ -124,6 +173,7 @@ export function initFormValidation({ root } = {}) {
124
173
  const refreshSummary = (form, invalid) => {
125
174
  const summary = form.querySelector('[data-bronto-error-summary]');
126
175
  if (!summary) return;
176
+ rememberSummary(summary);
127
177
  if (!invalid.length) {
128
178
  summary.hidden = true;
129
179
  summary.replaceChildren();
@@ -190,6 +240,9 @@ export function initFormValidation({ root } = {}) {
190
240
  // after init are still covered by the in-handler set.)
191
241
  const forms = collectHosts(host, '[data-bronto-validate]');
192
242
  priorNoValidate = new Map();
243
+ controlState = new Map();
244
+ slotState = new Map();
245
+ summaryState = new Map();
193
246
  for (const f of forms) {
194
247
  suppressNativeValidation(f);
195
248
  }
@@ -200,6 +253,20 @@ export function initFormValidation({ root } = {}) {
200
253
  host.removeEventListener('focusout', onBlur);
201
254
  for (const [f, v] of priorNoValidate) f.noValidate = v;
202
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();
203
270
  };
204
271
  });
205
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"}