@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/combobox.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
scrollIntoViewSafe,
|
|
8
8
|
wrapIndex,
|
|
9
9
|
collectHosts,
|
|
10
|
+
closestSafe,
|
|
10
11
|
} from './internal.js';
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -54,12 +55,80 @@ export function initCombobox({ root } = {}) {
|
|
|
54
55
|
const boxes = collectHosts(host, '[data-bronto-combobox]');
|
|
55
56
|
const cleanups = [];
|
|
56
57
|
|
|
58
|
+
const snapshotAttrs = (el, names) => {
|
|
59
|
+
const out = {};
|
|
60
|
+
for (const name of names) {
|
|
61
|
+
out[name] = {
|
|
62
|
+
had: el.hasAttribute(name),
|
|
63
|
+
value: el.getAttribute(name),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const restoreAttrs = (el, attrs) => {
|
|
70
|
+
for (const [name, attr] of Object.entries(attrs)) {
|
|
71
|
+
if (attr.had) el.setAttribute(name, attr.value);
|
|
72
|
+
else el.removeAttribute(name);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
57
76
|
for (const box of boxes) {
|
|
58
77
|
const input = box.querySelector('[role="combobox"], .ui-combobox__input');
|
|
59
78
|
const list = box.querySelector('[role="listbox"], .ui-combobox__list');
|
|
60
79
|
if (!input || !list) continue;
|
|
61
80
|
const empty = box.querySelector('.ui-combobox__empty');
|
|
62
|
-
const
|
|
81
|
+
const optionStates = new WeakMap();
|
|
82
|
+
let listId = '';
|
|
83
|
+
|
|
84
|
+
const rememberOptionState = (option) => {
|
|
85
|
+
if (optionStates.has(option)) return;
|
|
86
|
+
optionStates.set(option, {
|
|
87
|
+
hidden: option.hidden,
|
|
88
|
+
active: option.classList.contains('is-active'),
|
|
89
|
+
attrs: snapshotAttrs(option, ['id', 'role', 'aria-selected']),
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const rememberState = () => ({
|
|
94
|
+
input: snapshotAttrs(input, [
|
|
95
|
+
'role',
|
|
96
|
+
'aria-controls',
|
|
97
|
+
'aria-autocomplete',
|
|
98
|
+
'aria-expanded',
|
|
99
|
+
'aria-activedescendant',
|
|
100
|
+
'autocomplete',
|
|
101
|
+
]),
|
|
102
|
+
list: {
|
|
103
|
+
hidden: list.hidden,
|
|
104
|
+
attrs: snapshotAttrs(list, ['id', 'role', 'aria-label']),
|
|
105
|
+
},
|
|
106
|
+
empty: empty
|
|
107
|
+
? {
|
|
108
|
+
hidden: empty.hidden,
|
|
109
|
+
attrs: snapshotAttrs(empty, ['role']),
|
|
110
|
+
}
|
|
111
|
+
: null,
|
|
112
|
+
options: optionStates,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const restoreState = (state) => {
|
|
116
|
+
restoreAttrs(input, state.input);
|
|
117
|
+
list.hidden = state.list.hidden;
|
|
118
|
+
restoreAttrs(list, state.list.attrs);
|
|
119
|
+
if (empty && state.empty) {
|
|
120
|
+
empty.hidden = state.empty.hidden;
|
|
121
|
+
restoreAttrs(empty, state.empty.attrs);
|
|
122
|
+
}
|
|
123
|
+
for (const option of options) {
|
|
124
|
+
const optionState = state.options.get(option);
|
|
125
|
+
if (!optionState) continue;
|
|
126
|
+
option.hidden = optionState.hidden;
|
|
127
|
+
option.classList.toggle('is-active', optionState.active);
|
|
128
|
+
restoreAttrs(option, optionState.attrs);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
63
132
|
// Re-readable so the opt-in MutationObserver (`data-bronto-combobox-live`)
|
|
64
133
|
// can pick up async/replaced option nodes without a full re-init. `visible`,
|
|
65
134
|
// `filter`, `move`, etc. close over this binding, so reassigning it is enough.
|
|
@@ -67,51 +136,11 @@ export function initCombobox({ root } = {}) {
|
|
|
67
136
|
const syncOptions = () => {
|
|
68
137
|
options = [...list.querySelectorAll('[role="option"], .ui-combobox__option')];
|
|
69
138
|
options.forEach((o, i) => {
|
|
139
|
+
rememberOptionState(o);
|
|
70
140
|
if (!o.id) o.id = `${listId}-opt-${i}`;
|
|
71
141
|
o.setAttribute('role', 'option');
|
|
72
142
|
});
|
|
73
143
|
};
|
|
74
|
-
syncOptions();
|
|
75
|
-
list.setAttribute('role', 'listbox');
|
|
76
|
-
// Give the listbox its own accessible name (a bare role=listbox is unnamed
|
|
77
|
-
// to a screen reader) by mirroring the input's REAL name. (a11y review C30.)
|
|
78
|
-
// The placeholder is deliberately NOT in this chain: the input warning below
|
|
79
|
-
// already rejects a placeholder as an inadequate name, so papering the
|
|
80
|
-
// listbox over with it would contradict that — if there's no real name the
|
|
81
|
-
// listbox stays unnamed and the warning is the signal. (component audit C28.)
|
|
82
|
-
if (!list.hasAttribute('aria-label') && !list.hasAttribute('aria-labelledby')) {
|
|
83
|
-
const name = input.getAttribute('aria-label') || input.labels?.[0]?.textContent?.trim();
|
|
84
|
-
if (name) list.setAttribute('aria-label', name);
|
|
85
|
-
}
|
|
86
|
-
// A `role="combobox"` with no accessible name is a silent AT failure. A
|
|
87
|
-
// placeholder is not a robust name (it can vanish and is ignored by some
|
|
88
|
-
// AT), so warn unless there is a real label/aria-label/aria-labelledby/title
|
|
89
|
-
// (C7). We can't invent a good name, hence a dev-time warning, not a guess.
|
|
90
|
-
const inputNamed =
|
|
91
|
-
input.hasAttribute('aria-label') ||
|
|
92
|
-
input.hasAttribute('aria-labelledby') ||
|
|
93
|
-
!!input.labels?.length ||
|
|
94
|
-
input.hasAttribute('title');
|
|
95
|
-
if (!inputNamed && typeof console !== 'undefined') {
|
|
96
|
-
console.warn(
|
|
97
|
-
'[bronto] initCombobox(): the combobox input has no accessible name — add a <label>, aria-label, or aria-labelledby (a placeholder is not enough).',
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
input.setAttribute('role', 'combobox');
|
|
101
|
-
input.setAttribute('aria-controls', listId);
|
|
102
|
-
input.setAttribute('aria-autocomplete', 'list');
|
|
103
|
-
input.setAttribute('aria-expanded', 'false');
|
|
104
|
-
input.setAttribute('autocomplete', 'off');
|
|
105
|
-
list.hidden = true;
|
|
106
|
-
// Hide the empty-state at rest: it must only appear once a filter yields no
|
|
107
|
-
// matches, never on an idle combobox. Without this an author who omits
|
|
108
|
-
// `hidden` on `.ui-combobox__empty` ships a box that reads "No matches"
|
|
109
|
-
// before the user has typed anything. (component audit C10.) Make it a
|
|
110
|
-
// status live region so its appearance is announced. (component audit C38.)
|
|
111
|
-
if (empty) {
|
|
112
|
-
empty.hidden = true;
|
|
113
|
-
if (!empty.hasAttribute('role')) empty.setAttribute('role', 'status');
|
|
114
|
-
}
|
|
115
144
|
|
|
116
145
|
let active = -1;
|
|
117
146
|
const visible = () => options.filter((o) => !o.hidden);
|
|
@@ -243,7 +272,7 @@ export function initCombobox({ root } = {}) {
|
|
|
243
272
|
}
|
|
244
273
|
};
|
|
245
274
|
const onOptionClick = (e) => {
|
|
246
|
-
const opt = e.target
|
|
275
|
+
const opt = closestSafe(e.target, '[role="option"], .ui-combobox__option');
|
|
247
276
|
if (opt) select(opt);
|
|
248
277
|
};
|
|
249
278
|
const onDocClick = (e) => {
|
|
@@ -251,6 +280,49 @@ export function initCombobox({ root } = {}) {
|
|
|
251
280
|
};
|
|
252
281
|
|
|
253
282
|
const bound = bindOnce(box, 'combobox', () => {
|
|
283
|
+
const state = rememberState();
|
|
284
|
+
listId = list.id || (list.id = `bronto-cb-list-${nextFieldUid()}`);
|
|
285
|
+
syncOptions();
|
|
286
|
+
list.setAttribute('role', 'listbox');
|
|
287
|
+
// Give the listbox its own accessible name (a bare role=listbox is unnamed
|
|
288
|
+
// to a screen reader) by mirroring the input's REAL name. (a11y review C30.)
|
|
289
|
+
// The placeholder is deliberately NOT in this chain: the input warning below
|
|
290
|
+
// already rejects a placeholder as an inadequate name, so papering the
|
|
291
|
+
// listbox over with it would contradict that — if there's no real name the
|
|
292
|
+
// listbox stays unnamed and the warning is the signal. (component audit C28.)
|
|
293
|
+
if (!list.hasAttribute('aria-label') && !list.hasAttribute('aria-labelledby')) {
|
|
294
|
+
const name = input.getAttribute('aria-label') || input.labels?.[0]?.textContent?.trim();
|
|
295
|
+
if (name) list.setAttribute('aria-label', name);
|
|
296
|
+
}
|
|
297
|
+
// A `role="combobox"` with no accessible name is a silent AT failure. A
|
|
298
|
+
// placeholder is not a robust name (it can vanish and is ignored by some
|
|
299
|
+
// AT), so warn unless there is a real label/aria-label/aria-labelledby/title
|
|
300
|
+
// (C7). We can't invent a good name, hence a dev-time warning, not a guess.
|
|
301
|
+
const inputNamed =
|
|
302
|
+
input.hasAttribute('aria-label') ||
|
|
303
|
+
input.hasAttribute('aria-labelledby') ||
|
|
304
|
+
!!input.labels?.length ||
|
|
305
|
+
input.hasAttribute('title');
|
|
306
|
+
if (!inputNamed && typeof console !== 'undefined') {
|
|
307
|
+
console.warn(
|
|
308
|
+
'[bronto] initCombobox(): the combobox input has no accessible name — add a <label>, aria-label, or aria-labelledby (a placeholder is not enough).',
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
input.setAttribute('role', 'combobox');
|
|
312
|
+
input.setAttribute('aria-controls', listId);
|
|
313
|
+
input.setAttribute('aria-autocomplete', 'list');
|
|
314
|
+
input.setAttribute('aria-expanded', 'false');
|
|
315
|
+
input.setAttribute('autocomplete', 'off');
|
|
316
|
+
// Hide the empty-state at rest: it must only appear once a filter yields no
|
|
317
|
+
// matches, never on an idle combobox. Without this an author who omits
|
|
318
|
+
// `hidden` on `.ui-combobox__empty` ships a box that reads "No matches"
|
|
319
|
+
// before the user has typed anything. (component audit C10.) Make it a
|
|
320
|
+
// status live region so its appearance is announced. (component audit C38.)
|
|
321
|
+
if (empty) {
|
|
322
|
+
empty.hidden = true;
|
|
323
|
+
if (!empty.hasAttribute('role')) empty.setAttribute('role', 'status');
|
|
324
|
+
}
|
|
325
|
+
close();
|
|
254
326
|
input.addEventListener('input', onInput);
|
|
255
327
|
input.addEventListener('keydown', onKey);
|
|
256
328
|
list.addEventListener('click', onOptionClick);
|
|
@@ -268,6 +340,8 @@ export function initCombobox({ root } = {}) {
|
|
|
268
340
|
input.removeEventListener('keydown', onKey);
|
|
269
341
|
list.removeEventListener('click', onOptionClick);
|
|
270
342
|
document.removeEventListener('click', onDocClick);
|
|
343
|
+
restoreState(state);
|
|
344
|
+
active = -1;
|
|
271
345
|
};
|
|
272
346
|
});
|
|
273
347
|
cleanups.push(bound);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["command.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["command.js"],"names":[],"mappings":"AAYA;;;;GAIG;AAEH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,uCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CA0N3C;;;;;WApPa,MAAM;;;;WACN,MAAM"}
|
package/behaviors/command.js
CHANGED
|
@@ -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
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
|
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":"
|
|
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"}
|
package/behaviors/connectors.js
CHANGED
|
@@ -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
|
-
|
|
26
|
-
|
|
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:
|
|
44
|
-
toRect:
|
|
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,
|
|
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"}
|
package/behaviors/crosshair.js
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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"}
|
package/behaviors/dialog.js
CHANGED
|
@@ -23,25 +23,37 @@ export function initDialog({ root } = {}) {
|
|
|
23
23
|
const host = resolveHost(root);
|
|
24
24
|
if (!host) return noop;
|
|
25
25
|
const managedDialogs = new WeakSet();
|
|
26
|
+
const focusRestorers = new Map();
|
|
26
27
|
const canManageDialog = (dlg, origin) => host.contains(origin) || managedDialogs.has(dlg);
|
|
27
28
|
|
|
28
29
|
const openFrom = (opener) => {
|
|
29
30
|
const dlg = byIdInHost(host, opener.getAttribute('data-bronto-open'));
|
|
30
|
-
if (!dlg || typeof dlg.showModal !== 'function' || dlg.open) return;
|
|
31
|
+
if (!dlg || typeof dlg.showModal !== 'function' || dlg.open) return false;
|
|
32
|
+
const previous = focusRestorers.get(dlg);
|
|
33
|
+
if (previous) {
|
|
34
|
+
dlg.removeEventListener('close', previous);
|
|
35
|
+
focusRestorers.delete(dlg);
|
|
36
|
+
}
|
|
37
|
+
const restoreFocus = () => {
|
|
38
|
+
focusRestorers.delete(dlg);
|
|
39
|
+
if (opener.isConnected && typeof opener.focus === 'function') opener.focus();
|
|
40
|
+
};
|
|
41
|
+
try {
|
|
42
|
+
dlg.showModal();
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
31
46
|
managedDialogs.add(dlg);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (opener.isConnected && typeof opener.focus === 'function') opener.focus();
|
|
36
|
-
},
|
|
37
|
-
{ once: true },
|
|
38
|
-
);
|
|
39
|
-
dlg.showModal();
|
|
47
|
+
focusRestorers.set(dlg, restoreFocus);
|
|
48
|
+
dlg.addEventListener('close', restoreFocus, { once: true });
|
|
49
|
+
return true;
|
|
40
50
|
};
|
|
41
51
|
|
|
42
52
|
const closeFrom = (closer) => {
|
|
43
53
|
const dlg = closer.closest('dialog');
|
|
44
|
-
if (dlg
|
|
54
|
+
if (!dlg || !dlg.open || !canManageDialog(dlg, closer)) return false;
|
|
55
|
+
dlg.close();
|
|
56
|
+
return true;
|
|
45
57
|
};
|
|
46
58
|
|
|
47
59
|
const lightDismiss = (dlg) => {
|
|
@@ -52,26 +64,35 @@ export function initDialog({ root } = {}) {
|
|
|
52
64
|
canManageDialog(dlg, dlg)
|
|
53
65
|
) {
|
|
54
66
|
dlg.close();
|
|
67
|
+
return true;
|
|
55
68
|
}
|
|
69
|
+
return false;
|
|
56
70
|
};
|
|
57
71
|
|
|
58
72
|
const onClick = (e) => {
|
|
59
|
-
const
|
|
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 =
|
|
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.
|
|
86
|
+
if (lightDismiss(target)) e.preventDefault();
|
|
72
87
|
};
|
|
73
88
|
return bindOnce(host, 'dialog', () => {
|
|
74
89
|
document.addEventListener('click', onClick);
|
|
75
|
-
return () =>
|
|
90
|
+
return () => {
|
|
91
|
+
document.removeEventListener('click', onClick);
|
|
92
|
+
for (const [dlg, restoreFocus] of focusRestorers) {
|
|
93
|
+
dlg.removeEventListener('close', restoreFocus);
|
|
94
|
+
}
|
|
95
|
+
focusRestorers.clear();
|
|
96
|
+
};
|
|
76
97
|
});
|
|
77
98
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"disclosure.d.ts","sourceRoot":"","sources":["disclosure.js"],"names":[],"mappings":"
|
|
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"}
|