@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.
- package/CHANGELOG.md +175 -6
- package/README.md +38 -25
- package/annotations/index.d.ts.map +1 -1
- package/annotations/index.js +21 -3
- package/behaviors/carousel.d.ts.map +1 -1
- package/behaviors/carousel.js +91 -32
- package/behaviors/combobox.d.ts.map +1 -1
- package/behaviors/combobox.js +117 -43
- package/behaviors/command.d.ts.map +1 -1
- package/behaviors/command.js +74 -14
- package/behaviors/connectors.d.ts.map +1 -1
- package/behaviors/connectors.js +92 -9
- package/behaviors/crosshair.d.ts.map +1 -1
- package/behaviors/crosshair.js +47 -1
- package/behaviors/dialog.d.ts.map +1 -1
- package/behaviors/dialog.js +37 -16
- package/behaviors/disclosure.d.ts.map +1 -1
- package/behaviors/disclosure.js +33 -3
- package/behaviors/dismissible.d.ts.map +1 -1
- package/behaviors/dismissible.js +3 -2
- package/behaviors/forms.d.ts.map +1 -1
- package/behaviors/forms.js +78 -5
- package/behaviors/glyph.d.ts.map +1 -1
- package/behaviors/glyph.js +17 -2
- package/behaviors/index.d.ts +2 -0
- package/behaviors/index.d.ts.map +1 -1
- package/behaviors/index.js +2 -0
- package/behaviors/inert.js +3 -2
- package/behaviors/internal.d.ts +2 -1
- package/behaviors/internal.d.ts.map +1 -1
- package/behaviors/internal.js +25 -4
- package/behaviors/legend.d.ts +0 -5
- package/behaviors/legend.d.ts.map +1 -1
- package/behaviors/legend.js +78 -14
- package/behaviors/menu.d.ts.map +1 -1
- package/behaviors/menu.js +13 -8
- package/behaviors/modal.d.ts.map +1 -1
- package/behaviors/modal.js +77 -19
- package/behaviors/popover.d.ts +4 -3
- package/behaviors/popover.d.ts.map +1 -1
- package/behaviors/popover.js +89 -9
- package/behaviors/sources.d.ts.map +1 -1
- package/behaviors/sources.js +14 -2
- package/behaviors/splitter.d.ts +26 -0
- package/behaviors/splitter.d.ts.map +1 -0
- package/behaviors/splitter.js +239 -0
- package/behaviors/spotlight.d.ts.map +1 -1
- package/behaviors/spotlight.js +28 -2
- package/behaviors/table.d.ts.map +1 -1
- package/behaviors/table.js +105 -13
- package/behaviors/tabs.d.ts.map +1 -1
- package/behaviors/tabs.js +82 -18
- package/behaviors/theme.d.ts.map +1 -1
- package/behaviors/theme.js +26 -6
- package/classes/classes.json +230 -4
- package/classes/index.d.ts +64 -3
- package/classes/index.js +56 -2
- package/classes/vscode.css-custom-data.json +1 -1
- package/connectors/index.d.ts +39 -6
- package/connectors/index.d.ts.map +1 -1
- package/connectors/index.js +67 -9
- package/css/analytical.css +3 -1
- package/css/annotations.css +12 -0
- package/css/app.css +4 -4
- package/css/clamp.css +92 -0
- package/css/crosshair.css +27 -2
- package/css/feedback.css +2 -30
- package/css/figure.css +102 -0
- package/css/highlights.css +50 -0
- package/css/interval.css +90 -0
- package/css/navigation.css +12 -0
- package/css/primitives.css +2 -3
- package/css/report-kit.css +38 -0
- package/css/report.css +23 -4
- package/css/sidenote.css +12 -2
- package/css/site.css +2 -1
- package/css/sources.css +5 -0
- package/css/state.css +120 -1
- package/css/table.css +4 -0
- package/css/tokens.css +25 -9
- package/css/workbench.css +101 -8
- package/dist/bronto.css +1 -1
- package/dist/css/analytical.css +1 -1
- package/dist/css/annotations.css +1 -1
- package/dist/css/app.css +1 -1
- package/dist/css/clamp.css +1 -0
- package/dist/css/crosshair.css +1 -1
- package/dist/css/feedback.css +1 -1
- package/dist/css/figure.css +1 -0
- package/dist/css/highlights.css +1 -0
- package/dist/css/interval.css +1 -0
- package/dist/css/navigation.css +1 -1
- package/dist/css/primitives.css +1 -1
- package/dist/css/report-kit.css +1 -0
- package/dist/css/report.css +1 -1
- package/dist/css/sidenote.css +1 -1
- package/dist/css/site.css +1 -1
- package/dist/css/sources.css +1 -1
- package/dist/css/state.css +1 -1
- package/dist/css/table.css +1 -1
- package/dist/css/tokens.css +1 -1
- package/dist/css/workbench.css +1 -1
- package/docs/adr/0001-color-system.md +3 -2
- package/docs/adr/0002-scope-and-2026-baseline.md +1 -1
- package/docs/annotations.md +12 -1
- package/docs/architecture.md +105 -48
- package/docs/clamp.md +49 -0
- package/docs/command.md +4 -1
- package/docs/connectors.md +16 -0
- package/docs/contrast.md +34 -24
- package/docs/crosshair.md +1 -1
- package/docs/d2.md +37 -0
- package/docs/dots.md +4 -1
- package/docs/figure.md +71 -0
- package/docs/frontier-primitives.md +25 -24
- package/docs/glyphs.md +11 -0
- package/docs/highlights.md +52 -0
- package/docs/interop/tailwind.md +148 -0
- package/docs/interval.md +55 -0
- package/docs/legends.md +3 -2
- package/docs/mermaid.md +6 -0
- package/docs/migrations/0.2-to-0.3.md +80 -0
- package/docs/migrations/0.3-to-0.4.md +48 -0
- package/docs/migrations/0.4-to-0.5.md +96 -0
- package/docs/migrations/0.5-to-0.6.md +82 -0
- package/docs/package-contract.md +44 -6
- package/docs/reference.md +78 -5
- package/docs/reporting.md +126 -60
- package/docs/sidenote.md +7 -1
- package/docs/sources.md +1 -1
- package/docs/stability.md +23 -5
- package/docs/state.md +67 -10
- package/docs/theming.md +12 -4
- package/docs/usage.md +47 -13
- package/docs/vega.md +4 -4
- package/docs/workbench.md +59 -18
- package/llms.txt +89 -16
- package/package.json +82 -6
- package/qwik/index.d.ts +1 -0
- package/qwik/index.d.ts.map +1 -1
- package/qwik/index.js +26 -21
- package/react/index.d.ts +1 -0
- package/react/index.d.ts.map +1 -1
- package/react/index.js +4 -1
- package/schemas/report-claims.v1.schema.json +137 -0
- package/solid/index.d.ts +2 -0
- package/solid/index.d.ts.map +1 -1
- package/solid/index.js +3 -0
- package/svelte/index.d.ts +114 -0
- package/svelte/index.d.ts.map +1 -0
- package/svelte/index.js +193 -0
- package/tailwind.css +87 -0
- package/tokens/figma.variables.json +2241 -0
- package/tokens/index.js +1 -1
- package/tokens/index.json +2 -2
- package/tokens/resolved.json +3 -3
- package/tokens/tokens.dtcg.json +1 -1
- package/vue/index.d.ts +116 -0
- package/vue/index.d.ts.map +1 -0
- package/vue/index.js +228 -0
package/behaviors/spotlight.js
CHANGED
|
@@ -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
|
}
|
package/behaviors/table.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|
package/behaviors/table.js
CHANGED
|
@@ -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,17 +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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
+
};
|
|
55
135
|
|
|
56
136
|
const colIndex = (th) => [...th.parentElement.children].indexOf(th);
|
|
57
137
|
const cellText = (row, i) => row.children[i]?.textContent.trim() ?? '';
|
|
@@ -94,8 +174,10 @@ export function initTableSort({ root } = {}) {
|
|
|
94
174
|
// Reset the OTHER sortable headers to `none` (not removed) so they keep
|
|
95
175
|
// announcing sortability; only previously-sortable headers carry aria-sort.
|
|
96
176
|
headers.forEach((h) => {
|
|
177
|
+
rememberHeaderState(h);
|
|
97
178
|
if (h !== th && h.hasAttribute('aria-sort')) h.setAttribute('aria-sort', 'none');
|
|
98
179
|
});
|
|
180
|
+
rememberHeaderState(th);
|
|
99
181
|
th.setAttribute('aria-sort', dir);
|
|
100
182
|
const i = colIndex(th);
|
|
101
183
|
const sign = dir === 'ascending' ? 1 : -1;
|
|
@@ -110,9 +192,8 @@ export function initTableSort({ root } = {}) {
|
|
|
110
192
|
return cmp * sign;
|
|
111
193
|
});
|
|
112
194
|
// Re-parent in document order: sorted data rows, then any empty/sentinel
|
|
113
|
-
// row last.
|
|
114
|
-
|
|
115
|
-
for (const r of [...rows, ...emptyRows]) tbody.appendChild(r);
|
|
195
|
+
// row last. These are existing <tr> nodes being moved; no markup is parsed.
|
|
196
|
+
tbody.append(...rows, ...emptyRows);
|
|
116
197
|
};
|
|
117
198
|
|
|
118
199
|
const allBox = table.querySelector('[data-bronto-select-all]');
|
|
@@ -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)
|
|
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
|
|
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);
|
package/behaviors/tabs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["tabs.js"],"names":[],"mappings":"
|
|
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"}
|
package/behaviors/tabs.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
hasDom,
|
|
3
|
+
resolveHost,
|
|
4
|
+
noop,
|
|
5
|
+
bindOnce,
|
|
6
|
+
nextFieldUid,
|
|
7
|
+
collectHosts,
|
|
8
|
+
closestSafe,
|
|
9
|
+
} from './internal.js';
|
|
2
10
|
|
|
3
11
|
/**
|
|
4
12
|
* Wire `[data-bronto-tabs]` groups for full keyboard a11y. The framework
|
|
@@ -24,6 +32,23 @@ export function initTabs({ root } = {}) {
|
|
|
24
32
|
if (!host) return noop;
|
|
25
33
|
const cleanups = [];
|
|
26
34
|
const groups = collectHosts(host, '[data-bronto-tabs]');
|
|
35
|
+
const snapshotAttrs = (el, names) => {
|
|
36
|
+
const out = {};
|
|
37
|
+
for (const name of names) {
|
|
38
|
+
out[name] = {
|
|
39
|
+
had: el.hasAttribute(name),
|
|
40
|
+
value: el.getAttribute(name),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
};
|
|
45
|
+
const restoreAttrs = (el, attrs) => {
|
|
46
|
+
for (const [name, attr] of Object.entries(attrs)) {
|
|
47
|
+
if (attr.had) el.setAttribute(name, attr.value);
|
|
48
|
+
else el.removeAttribute(name);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
27
52
|
for (const group of groups) {
|
|
28
53
|
// Own group only — a tab/panel inside a nested [data-bronto-tabs]
|
|
29
54
|
// belongs to that inner group, not this one.
|
|
@@ -31,20 +56,43 @@ export function initTabs({ root } = {}) {
|
|
|
31
56
|
const tabs = [...group.querySelectorAll('.ui-tab')].filter(owned);
|
|
32
57
|
const panels = [...group.querySelectorAll('.ui-tabs__panel')].filter(owned);
|
|
33
58
|
if (!tabs.length) continue;
|
|
34
|
-
const list = group.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
59
|
+
const list = [...group.querySelectorAll('.ui-tabs__list')].find(owned);
|
|
60
|
+
const rememberState = () => ({
|
|
61
|
+
list: list ? snapshotAttrs(list, ['role']) : null,
|
|
62
|
+
tabs: new Map(
|
|
63
|
+
tabs.map((tab) => [
|
|
64
|
+
tab,
|
|
65
|
+
{
|
|
66
|
+
active: tab.classList.contains('is-active'),
|
|
67
|
+
attrs: snapshotAttrs(tab, ['id', 'role', 'aria-selected', 'aria-controls', 'tabindex']),
|
|
68
|
+
},
|
|
69
|
+
]),
|
|
70
|
+
),
|
|
71
|
+
panels: new Map(
|
|
72
|
+
panels.map((panel) => [
|
|
73
|
+
panel,
|
|
74
|
+
{
|
|
75
|
+
hidden: panel.hidden,
|
|
76
|
+
attrs: snapshotAttrs(panel, ['id', 'role', 'aria-labelledby', 'tabindex']),
|
|
77
|
+
},
|
|
78
|
+
]),
|
|
79
|
+
),
|
|
80
|
+
});
|
|
81
|
+
const restoreState = (state) => {
|
|
82
|
+
if (list && state.list) restoreAttrs(list, state.list);
|
|
83
|
+
for (const tab of tabs) {
|
|
84
|
+
const tabState = state.tabs.get(tab);
|
|
85
|
+
if (!tabState) continue;
|
|
86
|
+
tab.classList.toggle('is-active', tabState.active);
|
|
87
|
+
restoreAttrs(tab, tabState.attrs);
|
|
88
|
+
}
|
|
89
|
+
for (const panel of panels) {
|
|
90
|
+
const panelState = state.panels.get(panel);
|
|
91
|
+
if (!panelState) continue;
|
|
92
|
+
panel.hidden = panelState.hidden;
|
|
93
|
+
restoreAttrs(panel, panelState.attrs);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
48
96
|
|
|
49
97
|
const select = (tab) => {
|
|
50
98
|
for (const t of tabs) {
|
|
@@ -70,14 +118,15 @@ export function initTabs({ root } = {}) {
|
|
|
70
118
|
const onClick = (e) => {
|
|
71
119
|
// `tabs` is filtered to this group, so membership (not mere DOM
|
|
72
120
|
// containment) is what isolates nested [data-bronto-tabs] groups.
|
|
73
|
-
const tab = e.target
|
|
121
|
+
const tab = closestSafe(e.target, '.ui-tab');
|
|
74
122
|
if (tab && tabs.includes(tab)) {
|
|
123
|
+
e.preventDefault();
|
|
75
124
|
select(tab);
|
|
76
125
|
tab.focus();
|
|
77
126
|
}
|
|
78
127
|
};
|
|
79
128
|
const onKey = (e) => {
|
|
80
|
-
const i = tabs.indexOf(e.target
|
|
129
|
+
const i = tabs.indexOf(closestSafe(e.target, '.ui-tab'));
|
|
81
130
|
if (i < 0) return;
|
|
82
131
|
let n = i;
|
|
83
132
|
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') n = (i + 1) % tabs.length;
|
|
@@ -90,14 +139,29 @@ export function initTabs({ root } = {}) {
|
|
|
90
139
|
select(tabs[n]);
|
|
91
140
|
tabs[n].focus();
|
|
92
141
|
};
|
|
93
|
-
select(tabs.find((t) => t.classList.contains('is-active')) || tabs[0]);
|
|
94
142
|
cleanups.push(
|
|
95
143
|
bindOnce(group, 'tabs', () => {
|
|
144
|
+
const state = rememberState();
|
|
145
|
+
if (list) list.setAttribute('role', 'tablist');
|
|
146
|
+
|
|
147
|
+
// APG: bind each tab to its panel (aria-controls) and back
|
|
148
|
+
// (aria-labelledby), minting stable ids only where absent.
|
|
149
|
+
for (const t of tabs) {
|
|
150
|
+
const p = panels.find((x) => x.dataset.panel === t.dataset.tab);
|
|
151
|
+
if (!p) continue;
|
|
152
|
+
const n = nextFieldUid();
|
|
153
|
+
if (!t.id) t.id = `bronto-tab-${n}`;
|
|
154
|
+
if (!p.id) p.id = `bronto-tabpanel-${n}`;
|
|
155
|
+
t.setAttribute('aria-controls', p.id);
|
|
156
|
+
p.setAttribute('aria-labelledby', t.id);
|
|
157
|
+
}
|
|
158
|
+
select(tabs.find((t) => t.classList.contains('is-active')) || tabs[0]);
|
|
96
159
|
group.addEventListener('click', onClick);
|
|
97
160
|
group.addEventListener('keydown', onKey);
|
|
98
161
|
return () => {
|
|
99
162
|
group.removeEventListener('click', onClick);
|
|
100
163
|
group.removeEventListener('keydown', onKey);
|
|
164
|
+
restoreState(state);
|
|
101
165
|
};
|
|
102
166
|
}),
|
|
103
167
|
);
|
package/behaviors/theme.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["theme.js"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AAEH;;;;;;;GAOG;AACH,wDAHW,cAAc,GACZ,IAAI,CAahB;AAED;;;;;;;;;;;;;;GAcG;AACH,uDAHW,gBAAgB,GAAG,OAAO,eAAe,EAAE,YAAY,GACrD,OAAO,eAAe,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["theme.js"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AAEH;;;;;;;GAOG;AACH,wDAHW,cAAc,GACZ,IAAI,CAahB;AAED;;;;;;;;;;;;;;GAcG;AACH,uDAHW,gBAAgB,GAAG,OAAO,eAAe,EAAE,YAAY,GACrD,OAAO,eAAe,EAAE,OAAO,CAuE3C;;;;;;;;;;6BAhHY,gBAAgB,GAAG;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE;;;;;WAIpC,OAAO,GAAG,MAAM"}
|
package/behaviors/theme.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { hasDom, resolveHost, noop, bindOnce } from './internal.js';
|
|
1
|
+
import { hasDom, resolveHost, noop, bindOnce, collectHosts, closestSafe } from './internal.js';
|
|
2
2
|
|
|
3
3
|
const THEMES = ['light', 'dark'];
|
|
4
4
|
|
|
@@ -53,7 +53,18 @@ export function initThemeToggle({ storageKey = 'bronto-theme', root } = {}) {
|
|
|
53
53
|
if (!hasDom()) return noop;
|
|
54
54
|
const host = resolveHost(root);
|
|
55
55
|
if (!host) return noop;
|
|
56
|
-
const
|
|
56
|
+
const doc = host.nodeType === 9 ? host : host.ownerDocument || document;
|
|
57
|
+
const docEl = doc.documentElement;
|
|
58
|
+
const toggleStates = new Map();
|
|
59
|
+
|
|
60
|
+
const rememberToggle = (el) => {
|
|
61
|
+
if (!toggleStates.has(el)) {
|
|
62
|
+
toggleStates.set(el, {
|
|
63
|
+
had: el.hasAttribute('aria-pressed'),
|
|
64
|
+
value: el.getAttribute('aria-pressed'),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
};
|
|
57
68
|
|
|
58
69
|
const prefersDark = () =>
|
|
59
70
|
typeof matchMedia === 'function' && matchMedia('(prefers-color-scheme: dark)').matches;
|
|
@@ -66,7 +77,8 @@ export function initThemeToggle({ storageKey = 'bronto-theme', root } = {}) {
|
|
|
66
77
|
|
|
67
78
|
const reflect = () => {
|
|
68
79
|
const c = current();
|
|
69
|
-
host
|
|
80
|
+
collectHosts(host, '[data-bronto-theme-toggle]').forEach((el) => {
|
|
81
|
+
rememberToggle(el);
|
|
70
82
|
const forced = el.getAttribute('data-bronto-theme-toggle');
|
|
71
83
|
// A forced control is "pressed" when its theme is the active one;
|
|
72
84
|
// a plain toggle reflects whether dark is active.
|
|
@@ -76,8 +88,9 @@ export function initThemeToggle({ storageKey = 'bronto-theme', root } = {}) {
|
|
|
76
88
|
};
|
|
77
89
|
|
|
78
90
|
const onClick = (e) => {
|
|
79
|
-
const trigger = e.target
|
|
91
|
+
const trigger = closestSafe(e.target, '[data-bronto-theme-toggle]');
|
|
80
92
|
if (!trigger || !host.contains(trigger)) return;
|
|
93
|
+
e.preventDefault();
|
|
81
94
|
const forced = trigger.getAttribute('data-bronto-theme-toggle');
|
|
82
95
|
const next = THEMES.includes(forced) ? forced : current() === 'dark' ? 'light' : 'dark';
|
|
83
96
|
docEl.setAttribute('data-theme', next);
|
|
@@ -92,10 +105,17 @@ export function initThemeToggle({ storageKey = 'bronto-theme', root } = {}) {
|
|
|
92
105
|
);
|
|
93
106
|
};
|
|
94
107
|
|
|
95
|
-
applyStoredTheme({ storageKey });
|
|
108
|
+
applyStoredTheme({ storageKey, root: docEl });
|
|
96
109
|
reflect();
|
|
97
110
|
return bindOnce(host, 'themeToggle', () => {
|
|
98
111
|
host.addEventListener('click', onClick);
|
|
99
|
-
return () =>
|
|
112
|
+
return () => {
|
|
113
|
+
host.removeEventListener('click', onClick);
|
|
114
|
+
for (const [el, state] of toggleStates) {
|
|
115
|
+
if (state.had) el.setAttribute('aria-pressed', state.value);
|
|
116
|
+
else el.removeAttribute('aria-pressed');
|
|
117
|
+
}
|
|
118
|
+
toggleStates.clear();
|
|
119
|
+
};
|
|
100
120
|
});
|
|
101
121
|
}
|