@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
@@ -6,6 +6,7 @@ import {
6
6
  byIdInHost,
7
7
  collectHosts,
8
8
  scrollIntoViewSafe,
9
+ closestSafe,
9
10
  } from './internal.js';
10
11
 
11
12
  /**
@@ -64,6 +65,7 @@ export function initSources({ root } = {}) {
64
65
  for (const island of islands) {
65
66
  const timers = new Set();
66
67
  const seeded = [];
68
+ const activeSources = new Set();
67
69
 
68
70
  const targetFor = (ref) => {
69
71
  const id = sourceId(ref);
@@ -101,17 +103,25 @@ export function initSources({ root } = {}) {
101
103
  }
102
104
  };
103
105
 
106
+ const clearGeneratedActive = () => {
107
+ for (const source of activeSources) source.classList.remove(ACTIVE);
108
+ activeSources.clear();
109
+ };
110
+
104
111
  const focusSource = (ref, source) => {
105
112
  for (const card of island.querySelectorAll(`.${ACTIVE}`)) card.classList.remove(ACTIVE);
113
+ clearGeneratedActive();
106
114
  for (const timer of timers) clearTimeout(timer);
107
115
  timers.clear();
108
116
 
109
117
  source.classList.add(ACTIVE);
118
+ activeSources.add(source);
110
119
  source.focus?.({ preventScroll: true });
111
120
  scrollIntoViewSafe(source);
112
121
 
113
122
  const timer = setTimeout(() => {
114
123
  source.classList.remove(ACTIVE);
124
+ activeSources.delete(source);
115
125
  timers.delete(timer);
116
126
  }, 1600);
117
127
  timers.add(timer);
@@ -125,7 +135,7 @@ export function initSources({ root } = {}) {
125
135
  };
126
136
 
127
137
  const onClick = (e) => {
128
- const ref = e.target.closest?.(REF_SELECTOR);
138
+ const ref = closestSafe(e.target, REF_SELECTOR);
129
139
  if (!ref || !island.contains(ref)) return;
130
140
  const source = targetFor(ref);
131
141
  if (!source) return;
@@ -134,12 +144,14 @@ export function initSources({ root } = {}) {
134
144
  };
135
145
 
136
146
  const cleanup = bindOnce(island, 'sources', () => {
147
+ const activeState = Array.from(island.querySelectorAll(`.${ACTIVE}`));
137
148
  seed();
138
149
  island.addEventListener('click', onClick);
139
150
  return () => {
140
151
  island.removeEventListener('click', onClick);
141
152
  for (const timer of timers) clearTimeout(timer);
142
153
  timers.clear();
154
+ clearGeneratedActive();
143
155
  for (const item of seeded.splice(0)) {
144
156
  if (item.hadDescribedBy) item.ref.setAttribute('aria-describedby', item.describedBy);
145
157
  else item.ref.removeAttribute('aria-describedby');
@@ -147,7 +159,7 @@ export function initSources({ root } = {}) {
147
159
  else item.ref.removeAttribute('title');
148
160
  if (item.source && item.hadTabindex === false) item.source.removeAttribute('tabindex');
149
161
  }
150
- for (const card of island.querySelectorAll(`.${ACTIVE}`)) card.classList.remove(ACTIVE);
162
+ for (const source of activeState) source.classList.add(ACTIVE);
151
163
  };
152
164
  });
153
165
 
@@ -1 +1 @@
1
- {"version":3,"file":"splitter.d.ts","sourceRoot":"","sources":["splitter.js"],"names":[],"mappings":"AAiLA;;;;;;;;;;;;;GAaG;AACH,wCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAU3C;;;;;WA3La,MAAM;;;;iBACN,UAAU,GAAG,YAAY"}
1
+ {"version":3,"file":"splitter.d.ts","sourceRoot":"","sources":["splitter.js"],"names":[],"mappings":"AAwNA;;;;;;;;;;;;;GAaG;AACH,wCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAU3C;;;;;WAlOa,MAAM;;;;iBACN,UAAU,GAAG,YAAY"}
@@ -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
  }
@@ -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,CAyO3C"}
@@ -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
@@ -41,18 +41,97 @@ 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 (it was unset until the
102
+ // first click — C10).
103
+ for (const sort of table.querySelectorAll('.ui-table__sort')) {
104
+ rememberSorterState(sort);
105
+ if (sort.tagName === 'BUTTON' && !sort.hasAttribute('type')) sort.type = 'button';
106
+ const th = sort.closest('th');
107
+ rememberHeaderState(th);
108
+ if (th && !th.hasAttribute('aria-sort')) th.setAttribute('aria-sort', 'none');
109
+ }
110
+ };
111
+ const rememberTableState = () => {
112
+ const rowOrder = [...tbody.rows];
113
+ rowOrder.forEach(rememberRowState);
114
+ seedSorters();
115
+ const allBox = table.querySelector('[data-bronto-select-all]');
116
+ rememberCheckboxState(allBox);
117
+ table.querySelectorAll('[data-bronto-select]').forEach(rememberCheckboxState);
118
+ return { rowOrder };
119
+ };
120
+ const restoreTableState = (state) => {
121
+ for (const sorter of sorters) restoreAttrs(sorter, sortStates.get(sorter));
122
+ for (const th of headers) restoreAttrs(th, headerStates.get(th));
123
+ for (const row of rows) restoreAttrs(row, rowStates.get(row));
124
+ for (const box of checkboxes) {
125
+ const boxState = checkboxStates.get(box);
126
+ if (!boxState) continue;
127
+ box.checked = boxState.checked;
128
+ box.indeterminate = boxState.indeterminate;
129
+ }
130
+ const ordered = state.rowOrder.filter((row) => row.parentElement === tbody);
131
+ const orderedSet = new Set(ordered);
132
+ const extras = [...tbody.rows].filter((row) => !orderedSet.has(row));
133
+ tbody.append(...ordered, ...extras);
134
+ };
56
135
 
57
136
  const colIndex = (th) => [...th.parentElement.children].indexOf(th);
58
137
  const cellText = (row, i) => row.children[i]?.textContent.trim() ?? '';
@@ -95,8 +174,10 @@ export function initTableSort({ root } = {}) {
95
174
  // Reset the OTHER sortable headers to `none` (not removed) so they keep
96
175
  // announcing sortability; only previously-sortable headers carry aria-sort.
97
176
  headers.forEach((h) => {
177
+ rememberHeaderState(h);
98
178
  if (h !== th && h.hasAttribute('aria-sort')) h.setAttribute('aria-sort', 'none');
99
179
  });
180
+ rememberHeaderState(th);
100
181
  th.setAttribute('aria-sort', dir);
101
182
  const i = colIndex(th);
102
183
  const sign = dir === 'ascending' ? 1 : -1;
@@ -121,6 +202,7 @@ export function initTableSort({ root } = {}) {
121
202
  const boxes = rowBoxes();
122
203
  const on = boxes.filter((b) => b.checked).length;
123
204
  if (allBox) {
205
+ rememberCheckboxState(allBox);
124
206
  allBox.checked = on > 0 && on === boxes.length;
125
207
  allBox.indeterminate = on > 0 && on < boxes.length;
126
208
  }
@@ -129,8 +211,12 @@ export function initTableSort({ root } = {}) {
129
211
  );
130
212
  };
131
213
  const markRow = (box) => {
214
+ rememberCheckboxState(box);
132
215
  const tr = box.closest('tr');
133
- if (tr) tr.setAttribute('aria-selected', String(box.checked));
216
+ if (tr) {
217
+ rememberRowState(tr);
218
+ tr.setAttribute('aria-selected', String(box.checked));
219
+ }
134
220
  };
135
221
 
136
222
  const onClick = (e) => {
@@ -138,8 +224,9 @@ export function initTableSort({ root } = {}) {
138
224
  // keyboard-operable and carries the `::after` sort glyph. The bare
139
225
  // `th[data-sort]` path was mouse-only with no affordance, so it is gone
140
226
  // (C10); `data-sort="num"` is still read from the button or its th.
141
- const sorter = e.target.closest('.ui-table__sort');
227
+ const sorter = closestSafe(e.target, '.ui-table__sort');
142
228
  if (sorter && table.contains(sorter)) {
229
+ rememberSorterState(sorter);
143
230
  const th = sorter.closest('th');
144
231
  const numeric =
145
232
  (sorter.getAttribute('data-sort') || th.getAttribute('data-sort')) === 'num' ||
@@ -150,23 +237,28 @@ export function initTableSort({ root } = {}) {
150
237
  const onChange = (e) => {
151
238
  const t = e.target;
152
239
  if (t.matches?.('[data-bronto-select-all]')) {
240
+ rememberCheckboxState(t);
153
241
  rowBoxes().forEach((b) => {
242
+ rememberCheckboxState(b);
154
243
  b.checked = t.checked;
155
244
  markRow(b);
156
245
  });
157
246
  syncAll();
158
247
  } else if (t.matches?.('[data-bronto-select]')) {
248
+ rememberCheckboxState(t);
159
249
  markRow(t);
160
250
  syncAll();
161
251
  }
162
252
  };
163
253
 
164
254
  const bound = bindOnce(table, 'tableSort', () => {
255
+ const state = rememberTableState();
165
256
  table.addEventListener('click', onClick);
166
257
  table.addEventListener('change', onChange);
167
258
  return () => {
168
259
  table.removeEventListener('click', onClick);
169
260
  table.removeEventListener('change', onChange);
261
+ restoreTableState(state);
170
262
  };
171
263
  });
172
264
  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"}