@ponchia/ui 0.6.7 → 0.6.9

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 (111) hide show
  1. package/CHANGELOG.md +129 -4
  2. package/README.md +4 -4
  3. package/annotations/index.d.ts.map +1 -1
  4. package/annotations/index.js +26 -9
  5. package/behaviors/carousel.d.ts.map +1 -1
  6. package/behaviors/carousel.js +145 -49
  7. package/behaviors/combobox.d.ts.map +1 -1
  8. package/behaviors/combobox.js +220 -92
  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 +131 -32
  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 +211 -140
  23. package/behaviors/glyph.d.ts.map +1 -1
  24. package/behaviors/glyph.js +172 -132
  25. package/behaviors/inert.d.ts +1 -1
  26. package/behaviors/inert.d.ts.map +1 -1
  27. package/behaviors/inert.js +4 -3
  28. package/behaviors/internal.d.ts.map +1 -1
  29. package/behaviors/internal.js +4 -3
  30. package/behaviors/legend.d.ts +0 -5
  31. package/behaviors/legend.d.ts.map +1 -1
  32. package/behaviors/legend.js +45 -13
  33. package/behaviors/menu.d.ts.map +1 -1
  34. package/behaviors/menu.js +13 -8
  35. package/behaviors/modal.d.ts.map +1 -1
  36. package/behaviors/modal.js +77 -19
  37. package/behaviors/popover.d.ts +4 -3
  38. package/behaviors/popover.d.ts.map +1 -1
  39. package/behaviors/popover.js +94 -14
  40. package/behaviors/sources.d.ts.map +1 -1
  41. package/behaviors/sources.js +14 -2
  42. package/behaviors/splitter.d.ts.map +1 -1
  43. package/behaviors/splitter.js +149 -110
  44. package/behaviors/spotlight.d.ts.map +1 -1
  45. package/behaviors/spotlight.js +28 -2
  46. package/behaviors/table.d.ts +1 -1
  47. package/behaviors/table.d.ts.map +1 -1
  48. package/behaviors/table.js +108 -17
  49. package/behaviors/tabs.d.ts.map +1 -1
  50. package/behaviors/tabs.js +84 -20
  51. package/behaviors/theme.d.ts.map +1 -1
  52. package/behaviors/theme.js +25 -5
  53. package/behaviors/toast.js +5 -5
  54. package/classes/index.d.ts +15 -2
  55. package/classes/index.js +48 -35
  56. package/connectors/index.d.ts +41 -8
  57. package/connectors/index.d.ts.map +1 -1
  58. package/connectors/index.js +74 -19
  59. package/css/annotations.css +12 -0
  60. package/css/app.css +3 -4
  61. package/css/base.css +1 -1
  62. package/css/content.css +3 -3
  63. package/css/crosshair.css +27 -2
  64. package/css/disclosure.css +3 -3
  65. package/css/dots.css +4 -4
  66. package/css/feedback.css +8 -37
  67. package/css/forms.css +9 -12
  68. package/css/legend.css +1 -1
  69. package/css/marks.css +1 -1
  70. package/css/motion.css +6 -6
  71. package/css/navigation.css +12 -0
  72. package/css/overlay.css +5 -7
  73. package/css/primitives.css +14 -16
  74. package/css/sidenote.css +2 -2
  75. package/css/table.css +2 -2
  76. package/css/tokens.css +16 -0
  77. package/dist/bronto.css +1 -1
  78. package/dist/css/analytical.css +1 -1
  79. package/dist/css/annotations.css +1 -1
  80. package/dist/css/crosshair.css +1 -1
  81. package/dist/css/feedback.css +1 -1
  82. package/dist/css/navigation.css +1 -1
  83. package/dist/css/report-kit.css +1 -1
  84. package/dist/css/tokens.css +1 -1
  85. package/docs/adr/0001-color-system.md +3 -2
  86. package/docs/annotations.md +21 -1
  87. package/docs/architecture.md +74 -13
  88. package/docs/command.md +4 -1
  89. package/docs/connectors.md +16 -0
  90. package/docs/crosshair.md +1 -1
  91. package/docs/dots.md +4 -1
  92. package/docs/glyphs.md +11 -0
  93. package/docs/interop/react-flow.md +89 -0
  94. package/docs/migrations/0.2-to-0.3.md +1 -1
  95. package/docs/package-contract.md +7 -5
  96. package/docs/reporting.md +23 -12
  97. package/docs/stability.md +85 -9
  98. package/docs/theming.md +2 -2
  99. package/docs/usage.md +16 -2
  100. package/docs/vega.md +4 -4
  101. package/glyphs/glyphs.js +43 -33
  102. package/llms.txt +19 -8
  103. package/package.json +23 -4
  104. package/schemas/report-claims.v1.schema.json +1 -1
  105. package/svelte/index.d.ts +71 -45
  106. package/svelte/index.d.ts.map +1 -1
  107. package/svelte/index.js +29 -2
  108. package/tokens/index.js +2 -2
  109. package/vue/index.d.ts +42 -5
  110. package/vue/index.d.ts.map +1 -1
  111. package/vue/index.js +32 -1
@@ -44,122 +44,159 @@ const dispatchResize = (splitter, detail) => {
44
44
  );
45
45
  };
46
46
 
47
+ const snapshotAttrs = (el, names) => {
48
+ const out = {};
49
+ for (const name of names) {
50
+ out[name] = {
51
+ had: el.hasAttribute(name),
52
+ value: el.getAttribute(name),
53
+ };
54
+ }
55
+ return out;
56
+ };
57
+
58
+ const restoreAttrs = (el, attrs) => {
59
+ for (const [name, attr] of Object.entries(attrs)) {
60
+ if (attr.had) el.setAttribute(name, attr.value);
61
+ else el.removeAttribute(name);
62
+ }
63
+ };
64
+
65
+ const snapshotStyleProp = (el, name) => ({
66
+ value: el.style.getPropertyValue(name),
67
+ priority: el.style.getPropertyPriority(name),
68
+ });
69
+
70
+ const restoreStyleProp = (el, name, prop) => {
71
+ if (prop.value) el.style.setProperty(name, prop.value, prop.priority);
72
+ else el.style.removeProperty(name);
73
+ };
74
+
47
75
  function wireSplitter(splitter) {
48
76
  const handle = splitter.querySelector(HANDLE_SELECTOR);
49
77
  if (!handle) return noop;
50
78
 
51
- const orientation = readOrientation(splitter, handle);
52
- const min = num(handle.getAttribute('aria-valuemin'), DEFAULT_MIN);
53
- const max = Math.max(min, num(handle.getAttribute('aria-valuemax'), DEFAULT_MAX));
54
- let value = clamp(
55
- num(handle.getAttribute('aria-valuenow'), num(readCssValue(splitter), DEFAULT_VALUE)),
56
- min,
57
- max,
58
- );
59
- let activePointer = null;
60
-
61
- const apply = (next, { emit = true } = {}) => {
62
- value = clamp(next, min, max);
63
- const label = fmt(value);
64
- splitter.style.setProperty('--splitter-pos', `${label}%`);
65
- handle.setAttribute('aria-valuenow', label);
66
- if (emit) dispatchResize(splitter, { value, orientation });
67
- };
68
-
69
- if (!handle.hasAttribute('role')) handle.setAttribute('role', 'separator');
70
- if (!handle.hasAttribute('tabindex')) handle.tabIndex = 0;
71
- if (!handle.hasAttribute('aria-orientation'))
72
- handle.setAttribute('aria-orientation', orientation);
73
- if (!handle.hasAttribute('aria-valuemin')) handle.setAttribute('aria-valuemin', fmt(min));
74
- if (!handle.hasAttribute('aria-valuemax')) handle.setAttribute('aria-valuemax', fmt(max));
75
- apply(value, { emit: false });
76
-
77
- const fromPointer = (event) => {
78
- const rect = splitter.getBoundingClientRect();
79
- const size = orientation === 'horizontal' ? rect.height : rect.width;
80
- if (!size) return value;
81
- if (orientation === 'horizontal') {
82
- return ((event.clientY - rect.top) / size) * 100;
83
- }
84
- const view = getView(splitter);
85
- const dir = view?.getComputedStyle?.(splitter).direction;
86
- const x = dir === 'rtl' ? rect.right - event.clientX : event.clientX - rect.left;
87
- return (x / size) * 100;
88
- };
89
-
90
- const capturePointer = (pointerId) => {
91
- if (pointerId === undefined || pointerId === null) return;
92
- try {
93
- handle.setPointerCapture?.(pointerId);
94
- } catch {
95
- /* Pointer capture is an affordance; drag still works through document listeners. */
96
- }
97
- };
98
-
99
- const releasePointer = (pointerId = activePointer) => {
100
- if (pointerId === undefined || pointerId === null) return;
101
- try {
102
- if (!handle.hasPointerCapture || handle.hasPointerCapture(pointerId)) {
103
- handle.releasePointerCapture?.(pointerId);
79
+ return bindOnce(splitter, 'splitter', () => {
80
+ const handleAttrs = snapshotAttrs(handle, [
81
+ 'role',
82
+ 'tabindex',
83
+ 'aria-orientation',
84
+ 'aria-valuemin',
85
+ 'aria-valuemax',
86
+ 'aria-valuenow',
87
+ ]);
88
+ const splitterPos = snapshotStyleProp(splitter, '--splitter-pos');
89
+ const orientation = readOrientation(splitter, handle);
90
+ const min = num(handle.getAttribute('aria-valuemin'), DEFAULT_MIN);
91
+ const max = Math.max(min, num(handle.getAttribute('aria-valuemax'), DEFAULT_MAX));
92
+ let value = clamp(
93
+ num(handle.getAttribute('aria-valuenow'), num(readCssValue(splitter), DEFAULT_VALUE)),
94
+ min,
95
+ max,
96
+ );
97
+ let activePointer = null;
98
+
99
+ const apply = (next, { emit = true } = {}) => {
100
+ value = clamp(next, min, max);
101
+ const label = fmt(value);
102
+ splitter.style.setProperty('--splitter-pos', `${label}%`);
103
+ handle.setAttribute('aria-valuenow', label);
104
+ if (emit) dispatchResize(splitter, { value, orientation });
105
+ };
106
+
107
+ if (!handle.hasAttribute('role')) handle.setAttribute('role', 'separator');
108
+ if (!handle.hasAttribute('tabindex')) handle.tabIndex = 0;
109
+ if (!handle.hasAttribute('aria-orientation'))
110
+ handle.setAttribute('aria-orientation', orientation);
111
+ if (!handle.hasAttribute('aria-valuemin')) handle.setAttribute('aria-valuemin', fmt(min));
112
+ if (!handle.hasAttribute('aria-valuemax')) handle.setAttribute('aria-valuemax', fmt(max));
113
+ apply(value, { emit: false });
114
+
115
+ const fromPointer = (event) => {
116
+ const rect = splitter.getBoundingClientRect();
117
+ const size = orientation === 'horizontal' ? rect.height : rect.width;
118
+ if (!size) return value;
119
+ if (orientation === 'horizontal') {
120
+ return ((event.clientY - rect.top) / size) * 100;
121
+ }
122
+ const view = getView(splitter);
123
+ const dir = view?.getComputedStyle?.(splitter).direction;
124
+ const x = dir === 'rtl' ? rect.right - event.clientX : event.clientX - rect.left;
125
+ return (x / size) * 100;
126
+ };
127
+
128
+ const capturePointer = (pointerId) => {
129
+ if (pointerId === undefined || pointerId === null) return;
130
+ try {
131
+ handle.setPointerCapture?.(pointerId);
132
+ } catch {
133
+ /* Pointer capture is an affordance; drag still works through document listeners. */
104
134
  }
105
- } catch {
106
- /* The element may have been removed or capture may already be gone. */
107
- }
108
- };
109
-
110
- const onKeydown = (event) => {
111
- let next = value;
112
- if (event.key === 'Home') next = min;
113
- else if (event.key === 'End') next = max;
114
- else if (event.key === 'PageUp') next += LARGE_STEP;
115
- else if (event.key === 'PageDown') next -= LARGE_STEP;
116
- else if (event.key === 'ArrowRight' || event.key === 'ArrowDown')
117
- next += event.shiftKey ? LARGE_STEP : STEP;
118
- else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp')
119
- next -= event.shiftKey ? LARGE_STEP : STEP;
120
- else return;
121
- event.preventDefault();
122
- apply(next);
123
- };
124
-
125
- const onPointerMove = (event) => {
126
- if (
127
- activePointer !== null &&
128
- event.pointerId !== undefined &&
129
- event.pointerId !== activePointer
130
- )
131
- return;
132
- apply(fromPointer(event));
133
- };
134
-
135
- const onPointerUp = (event) => {
136
- if (
137
- activePointer !== null &&
138
- event.pointerId !== undefined &&
139
- event.pointerId !== activePointer
140
- )
141
- return;
142
- releasePointer(event.pointerId);
143
- activePointer = null;
144
- handle.classList.remove('is-active');
145
- splitter.ownerDocument.removeEventListener('pointermove', onPointerMove);
146
- splitter.ownerDocument.removeEventListener('pointerup', onPointerUp);
147
- splitter.ownerDocument.removeEventListener('pointercancel', onPointerUp);
148
- };
149
-
150
- const onPointerDown = (event) => {
151
- if (event.button !== undefined && event.button !== 0) return;
152
- event.preventDefault();
153
- activePointer = event.pointerId ?? null;
154
- capturePointer(activePointer);
155
- handle.classList.add('is-active');
156
- apply(fromPointer(event));
157
- splitter.ownerDocument.addEventListener('pointermove', onPointerMove);
158
- splitter.ownerDocument.addEventListener('pointerup', onPointerUp);
159
- splitter.ownerDocument.addEventListener('pointercancel', onPointerUp);
160
- };
135
+ };
136
+
137
+ const releasePointer = (pointerId = activePointer) => {
138
+ if (pointerId === undefined || pointerId === null) return;
139
+ try {
140
+ if (!handle.hasPointerCapture || handle.hasPointerCapture(pointerId)) {
141
+ handle.releasePointerCapture?.(pointerId);
142
+ }
143
+ } catch {
144
+ /* The element may have been removed or capture may already be gone. */
145
+ }
146
+ };
147
+
148
+ const onKeydown = (event) => {
149
+ let next = value;
150
+ if (event.key === 'Home') next = min;
151
+ else if (event.key === 'End') next = max;
152
+ else if (event.key === 'PageUp') next += LARGE_STEP;
153
+ else if (event.key === 'PageDown') next -= LARGE_STEP;
154
+ else if (event.key === 'ArrowRight' || event.key === 'ArrowDown')
155
+ next += event.shiftKey ? LARGE_STEP : STEP;
156
+ else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp')
157
+ next -= event.shiftKey ? LARGE_STEP : STEP;
158
+ else return;
159
+ event.preventDefault();
160
+ apply(next);
161
+ };
162
+
163
+ const onPointerMove = (event) => {
164
+ if (
165
+ activePointer !== null &&
166
+ event.pointerId !== undefined &&
167
+ event.pointerId !== activePointer
168
+ )
169
+ return;
170
+ apply(fromPointer(event));
171
+ };
172
+
173
+ const onPointerUp = (event) => {
174
+ if (
175
+ activePointer !== null &&
176
+ event.pointerId !== undefined &&
177
+ event.pointerId !== activePointer
178
+ )
179
+ return;
180
+ releasePointer(event.pointerId);
181
+ activePointer = null;
182
+ handle.classList.remove('is-active');
183
+ splitter.ownerDocument.removeEventListener('pointermove', onPointerMove);
184
+ splitter.ownerDocument.removeEventListener('pointerup', onPointerUp);
185
+ splitter.ownerDocument.removeEventListener('pointercancel', onPointerUp);
186
+ };
187
+
188
+ const onPointerDown = (event) => {
189
+ if (event.button !== undefined && event.button !== 0) return;
190
+ event.preventDefault();
191
+ activePointer = event.pointerId ?? null;
192
+ capturePointer(activePointer);
193
+ handle.classList.add('is-active');
194
+ apply(fromPointer(event));
195
+ splitter.ownerDocument.addEventListener('pointermove', onPointerMove);
196
+ splitter.ownerDocument.addEventListener('pointerup', onPointerUp);
197
+ splitter.ownerDocument.addEventListener('pointercancel', onPointerUp);
198
+ };
161
199
 
162
- return bindOnce(splitter, 'splitter', () => {
163
200
  handle.addEventListener('keydown', onKeydown);
164
201
  handle.addEventListener('pointerdown', onPointerDown);
165
202
  return () => {
@@ -171,6 +208,8 @@ function wireSplitter(splitter) {
171
208
  releasePointer();
172
209
  handle.classList.remove('is-active');
173
210
  activePointer = null;
211
+ restoreAttrs(handle, handleAttrs);
212
+ restoreStyleProp(splitter, '--splitter-pos', splitterPos);
174
213
  };
175
214
  });
176
215
  }
@@ -1 +1 @@
1
- {"version":3,"file":"spotlight.d.ts","sourceRoot":"","sources":["spotlight.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AACH,yCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAuC3C"}
1
+ {"version":3,"file":"spotlight.d.ts","sourceRoot":"","sources":["spotlight.js"],"names":[],"mappings":"AAsBA;;;;;;;;;;;;;;GAcG;AACH,yCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CA6C3C"}
@@ -1,5 +1,25 @@
1
1
  import { hasDom, resolveHost, noop, bindOnce, byIdInHost, collectHosts } from './internal.js';
2
2
 
3
+ const SPOT_PROPS = ['--spot-x', '--spot-y', '--spot-w', '--spot-h'];
4
+
5
+ const snapshotSpotProps = (spot) =>
6
+ Object.fromEntries(
7
+ SPOT_PROPS.map((name) => [
8
+ name,
9
+ {
10
+ value: spot.style.getPropertyValue(name),
11
+ priority: spot.style.getPropertyPriority(name),
12
+ },
13
+ ]),
14
+ );
15
+
16
+ const restoreSpotProps = (spot, props) => {
17
+ for (const [name, prop] of Object.entries(props)) {
18
+ if (prop.value) spot.style.setProperty(name, prop.value, prop.priority);
19
+ else spot.style.removeProperty(name);
20
+ }
21
+ };
22
+
3
23
  /**
4
24
  * Position a spotlight cutout over a target element. Each
5
25
  * `[data-bronto-spotlight]` is a `.ui-spotlight` overlay; `data-target` is the
@@ -19,10 +39,9 @@ export function initSpotlight({ root } = {}) {
19
39
  if (!hasDom()) return noop;
20
40
  const host = resolveHost(root);
21
41
  if (!host) return noop;
22
- const spots = collectHosts(host, '[data-bronto-spotlight]');
23
- if (!spots.length) return noop;
24
42
 
25
43
  const place = () => {
44
+ const spots = collectHosts(host, '[data-bronto-spotlight]');
26
45
  for (const spot of spots) {
27
46
  const target = byIdInHost(host, spot.dataset.target);
28
47
  if (!target) continue;
@@ -35,6 +54,12 @@ export function initSpotlight({ root } = {}) {
35
54
  };
36
55
 
37
56
  return bindOnce(host, 'spotlight', () => {
57
+ const spots = collectHosts(host, '[data-bronto-spotlight]');
58
+ if (!spots.length) return noop;
59
+ const states = spots.map((spot) => ({
60
+ spot,
61
+ props: snapshotSpotProps(spot),
62
+ }));
38
63
  place();
39
64
  const view = host.defaultView || host.ownerDocument?.defaultView || null;
40
65
  const MO = view?.MutationObserver;
@@ -50,6 +75,7 @@ export function initSpotlight({ root } = {}) {
50
75
  mo?.disconnect();
51
76
  view?.removeEventListener('resize', place);
52
77
  view?.removeEventListener('scroll', place, true);
78
+ for (const state of states) restoreSpotProps(state.spot, state.props);
53
79
  };
54
80
  });
55
81
  }
@@ -27,7 +27,7 @@
27
27
  * canonical number in a `data-sort-value` attribute on the cell. That escape
28
28
  * hatch wins over the parsed text and accepts either a dot ("3.5") or a single
29
29
  * decimal comma ("3,5"). It is a client-side convenience sorter, not a data
30
- * grid. (component audit C3/C5.)
30
+ * grid.
31
31
  *
32
32
  * @param {import('./internal.js').DelegateOpts} [opts]
33
33
  * @returns {import('./internal.js').Cleanup}
@@ -1 +1 @@
1
- {"version":3,"file":"table.d.ts","sourceRoot":"","sources":["table.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,yCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CA6I3C"}
1
+ {"version":3,"file":"table.d.ts","sourceRoot":"","sources":["table.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,yCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAwO3C"}
@@ -1,4 +1,4 @@
1
- import { hasDom, resolveHost, noop, bindOnce, collectHosts } from './internal.js';
1
+ import { hasDom, resolveHost, noop, bindOnce, collectHosts, closestSafe } from './internal.js';
2
2
 
3
3
  /**
4
4
  * Client-side sortable + selectable data table. Wires
@@ -29,7 +29,7 @@ import { hasDom, resolveHost, noop, bindOnce, collectHosts } from './internal.js
29
29
  * canonical number in a `data-sort-value` attribute on the cell. That escape
30
30
  * hatch wins over the parsed text and accepts either a dot ("3.5") or a single
31
31
  * decimal comma ("3,5"). It is a client-side convenience sorter, not a data
32
- * grid. (component audit C3/C5.)
32
+ * grid.
33
33
  *
34
34
  * @param {import('./internal.js').DelegateOpts} [opts]
35
35
  * @returns {import('./internal.js').Cleanup}
@@ -41,18 +41,96 @@ export function initTableSort({ root } = {}) {
41
41
  const tables = collectHosts(host, '[data-bronto-sortable]');
42
42
  const cleanups = [];
43
43
 
44
+ const snapshotAttrs = (el, names) => {
45
+ const out = {};
46
+ for (const name of names) {
47
+ out[name] = {
48
+ had: el.hasAttribute(name),
49
+ value: el.getAttribute(name),
50
+ };
51
+ }
52
+ return out;
53
+ };
54
+
55
+ const restoreAttrs = (el, attrs) => {
56
+ for (const [name, attr] of Object.entries(attrs)) {
57
+ if (attr.had) el.setAttribute(name, attr.value);
58
+ else el.removeAttribute(name);
59
+ }
60
+ };
61
+
44
62
  for (const table of tables) {
45
63
  const tbody = table.tBodies[0];
46
64
  if (!tbody) continue;
47
65
 
48
- // Seed the resting `aria-sort="none"` on every sortable header so AT
49
- // announces the column as sortable from the start (it was unset until the
50
- // first click C10).
51
- for (const sort of table.querySelectorAll('.ui-table__sort')) {
52
- if (sort.tagName === 'BUTTON' && !sort.hasAttribute('type')) sort.type = 'button';
53
- const th = sort.closest('th');
54
- if (th && !th.hasAttribute('aria-sort')) th.setAttribute('aria-sort', 'none');
55
- }
66
+ const headerStates = new WeakMap();
67
+ const headers = [];
68
+ const sortStates = new WeakMap();
69
+ const sorters = [];
70
+ const rowStates = new WeakMap();
71
+ const rows = [];
72
+ const checkboxStates = new WeakMap();
73
+ const checkboxes = [];
74
+
75
+ const rememberHeaderState = (th) => {
76
+ if (!th || headerStates.has(th)) return;
77
+ headerStates.set(th, snapshotAttrs(th, ['aria-sort']));
78
+ headers.push(th);
79
+ };
80
+ const rememberSorterState = (sorter) => {
81
+ if (!sorter || sortStates.has(sorter)) return;
82
+ sortStates.set(sorter, snapshotAttrs(sorter, ['type']));
83
+ sorters.push(sorter);
84
+ };
85
+ const rememberRowState = (row) => {
86
+ if (!row || rowStates.has(row)) return;
87
+ rowStates.set(row, snapshotAttrs(row, ['aria-selected']));
88
+ rows.push(row);
89
+ };
90
+ const rememberCheckboxState = (box) => {
91
+ if (!box || checkboxStates.has(box)) return;
92
+ checkboxStates.set(box, {
93
+ checked: box.checked,
94
+ indeterminate: box.indeterminate,
95
+ });
96
+ checkboxes.push(box);
97
+ rememberRowState(box.closest?.('tr'));
98
+ };
99
+ const seedSorters = () => {
100
+ // Seed the resting `aria-sort="none"` on every sortable header so AT
101
+ // announces the column as sortable from the start.
102
+ for (const sort of table.querySelectorAll('.ui-table__sort')) {
103
+ rememberSorterState(sort);
104
+ if (sort.tagName === 'BUTTON' && !sort.hasAttribute('type')) sort.type = 'button';
105
+ const th = sort.closest('th');
106
+ rememberHeaderState(th);
107
+ if (th && !th.hasAttribute('aria-sort')) th.setAttribute('aria-sort', 'none');
108
+ }
109
+ };
110
+ const rememberTableState = () => {
111
+ const rowOrder = [...tbody.rows];
112
+ rowOrder.forEach(rememberRowState);
113
+ seedSorters();
114
+ const allBox = table.querySelector('[data-bronto-select-all]');
115
+ rememberCheckboxState(allBox);
116
+ table.querySelectorAll('[data-bronto-select]').forEach(rememberCheckboxState);
117
+ return { rowOrder };
118
+ };
119
+ const restoreTableState = (state) => {
120
+ for (const sorter of sorters) restoreAttrs(sorter, sortStates.get(sorter));
121
+ for (const th of headers) restoreAttrs(th, headerStates.get(th));
122
+ for (const row of rows) restoreAttrs(row, rowStates.get(row));
123
+ for (const box of checkboxes) {
124
+ const boxState = checkboxStates.get(box);
125
+ if (!boxState) continue;
126
+ box.checked = boxState.checked;
127
+ box.indeterminate = boxState.indeterminate;
128
+ }
129
+ const ordered = state.rowOrder.filter((row) => row.parentElement === tbody);
130
+ const orderedSet = new Set(ordered);
131
+ const extras = [...tbody.rows].filter((row) => !orderedSet.has(row));
132
+ tbody.append(...ordered, ...extras);
133
+ };
56
134
 
57
135
  const colIndex = (th) => [...th.parentElement.children].indexOf(th);
58
136
  const cellText = (row, i) => row.children[i]?.textContent.trim() ?? '';
@@ -60,7 +138,7 @@ export function initTableSort({ root } = {}) {
60
138
  // authoritative escape hatch; otherwise normalize the display text so the
61
139
  // sign survives (U+2212 / en-em dashes → minus, accounting parens →
62
140
  // negative) and `,` grouping is dropped. Returns 0 for unparseable cells so
63
- // they cluster rather than scatter. (component audit C3.)
141
+ // they cluster rather than scatter.
64
142
  const cellNum = (row, i) => {
65
143
  const cell = row.children[i];
66
144
  const explicit = cell?.getAttribute?.('data-sort-value');
@@ -95,13 +173,15 @@ export function initTableSort({ root } = {}) {
95
173
  // Reset the OTHER sortable headers to `none` (not removed) so they keep
96
174
  // announcing sortability; only previously-sortable headers carry aria-sort.
97
175
  headers.forEach((h) => {
176
+ rememberHeaderState(h);
98
177
  if (h !== th && h.hasAttribute('aria-sort')) h.setAttribute('aria-sort', 'none');
99
178
  });
179
+ rememberHeaderState(th);
100
180
  th.setAttribute('aria-sort', dir);
101
181
  const i = colIndex(th);
102
182
  const sign = dir === 'ascending' ? 1 : -1;
103
- // Empty/sentinel rows sort out of the data set AND must re-append LAST,
104
- // or after a sort they float above the real rows (C29).
183
+ // Empty/sentinel rows sort out of the data set and must re-append last,
184
+ // or after a sort they float above the real rows.
105
185
  const emptyRows = [...tbody.rows].filter((r) => r.classList.contains('ui-table__empty'));
106
186
  const rows = [...tbody.rows].filter((r) => !r.classList.contains('ui-table__empty'));
107
187
  rows.sort((a, b) => {
@@ -121,6 +201,7 @@ export function initTableSort({ root } = {}) {
121
201
  const boxes = rowBoxes();
122
202
  const on = boxes.filter((b) => b.checked).length;
123
203
  if (allBox) {
204
+ rememberCheckboxState(allBox);
124
205
  allBox.checked = on > 0 && on === boxes.length;
125
206
  allBox.indeterminate = on > 0 && on < boxes.length;
126
207
  }
@@ -129,17 +210,22 @@ export function initTableSort({ root } = {}) {
129
210
  );
130
211
  };
131
212
  const markRow = (box) => {
213
+ rememberCheckboxState(box);
132
214
  const tr = box.closest('tr');
133
- if (tr) tr.setAttribute('aria-selected', String(box.checked));
215
+ if (tr) {
216
+ rememberRowState(tr);
217
+ tr.setAttribute('aria-selected', String(box.checked));
218
+ }
134
219
  };
135
220
 
136
221
  const onClick = (e) => {
137
222
  // Only the focusable `.ui-table__sort` button is a sort trigger — it is
138
223
  // keyboard-operable and carries the `::after` sort glyph. The bare
139
- // `th[data-sort]` path was mouse-only with no affordance, so it is gone
140
- // (C10); `data-sort="num"` is still read from the button or its th.
141
- const sorter = e.target.closest('.ui-table__sort');
224
+ // `th[data-sort]` path was mouse-only with no affordance, so it is gone;
225
+ // `data-sort="num"` is still read from the button or its th.
226
+ const sorter = closestSafe(e.target, '.ui-table__sort');
142
227
  if (sorter && table.contains(sorter)) {
228
+ rememberSorterState(sorter);
143
229
  const th = sorter.closest('th');
144
230
  const numeric =
145
231
  (sorter.getAttribute('data-sort') || th.getAttribute('data-sort')) === 'num' ||
@@ -150,23 +236,28 @@ export function initTableSort({ root } = {}) {
150
236
  const onChange = (e) => {
151
237
  const t = e.target;
152
238
  if (t.matches?.('[data-bronto-select-all]')) {
239
+ rememberCheckboxState(t);
153
240
  rowBoxes().forEach((b) => {
241
+ rememberCheckboxState(b);
154
242
  b.checked = t.checked;
155
243
  markRow(b);
156
244
  });
157
245
  syncAll();
158
246
  } else if (t.matches?.('[data-bronto-select]')) {
247
+ rememberCheckboxState(t);
159
248
  markRow(t);
160
249
  syncAll();
161
250
  }
162
251
  };
163
252
 
164
253
  const bound = bindOnce(table, 'tableSort', () => {
254
+ const state = rememberTableState();
165
255
  table.addEventListener('click', onClick);
166
256
  table.addEventListener('change', onChange);
167
257
  return () => {
168
258
  table.removeEventListener('click', onClick);
169
259
  table.removeEventListener('change', onChange);
260
+ restoreTableState(state);
170
261
  };
171
262
  });
172
263
  cleanups.push(bound);
@@ -1 +1 @@
1
- {"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["tabs.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AACH,oCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAuF3C"}
1
+ {"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["tabs.js"],"names":[],"mappings":"AAUA;;;;;;;;;;;;;;;;;GAiBG;AACH,oCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CA+I3C"}