@ponchia/ui 0.6.9 → 0.6.11
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 +92 -0
- package/README.md +38 -25
- package/annotations/index.d.ts +15 -15
- package/annotations/index.d.ts.map +1 -1
- package/annotations/index.js +52 -34
- package/behaviors/carousel.d.ts +7 -3
- package/behaviors/carousel.d.ts.map +1 -1
- package/behaviors/carousel.js +157 -27
- package/behaviors/combobox.d.ts +1 -1
- package/behaviors/combobox.d.ts.map +1 -1
- package/behaviors/combobox.js +46 -23
- package/behaviors/command.d.ts +1 -1
- package/behaviors/command.d.ts.map +1 -1
- package/behaviors/command.js +63 -23
- package/behaviors/connectors.d.ts.map +1 -1
- package/behaviors/connectors.js +126 -19
- package/behaviors/crosshair.d.ts.map +1 -1
- package/behaviors/crosshair.js +71 -8
- package/behaviors/dialog.d.ts.map +1 -1
- package/behaviors/dialog.js +20 -3
- package/behaviors/disclosure.d.ts.map +1 -1
- package/behaviors/disclosure.js +35 -6
- package/behaviors/dismissible.js +1 -1
- package/behaviors/forms.d.ts +23 -2
- package/behaviors/forms.d.ts.map +1 -1
- package/behaviors/forms.js +97 -9
- package/behaviors/glyph.d.ts.map +1 -1
- package/behaviors/glyph.js +56 -5
- package/behaviors/internal.d.ts.map +1 -1
- package/behaviors/internal.js +52 -5
- package/behaviors/menu.d.ts.map +1 -1
- package/behaviors/menu.js +2 -1
- package/behaviors/modal.d.ts.map +1 -1
- package/behaviors/modal.js +25 -9
- package/behaviors/popover.d.ts.map +1 -1
- package/behaviors/popover.js +8 -6
- package/behaviors/sources.d.ts.map +1 -1
- package/behaviors/sources.js +24 -3
- package/behaviors/splitter.d.ts.map +1 -1
- package/behaviors/splitter.js +27 -6
- package/behaviors/table.d.ts.map +1 -1
- package/behaviors/table.js +44 -7
- package/behaviors/tabs.d.ts.map +1 -1
- package/behaviors/tabs.js +51 -14
- package/behaviors/theme.d.ts.map +1 -1
- package/behaviors/theme.js +64 -4
- package/behaviors/toast.d.ts +6 -1
- package/behaviors/toast.d.ts.map +1 -1
- package/behaviors/toast.js +48 -12
- package/classes/classes.json +57 -2
- package/classes/index.d.ts +13 -2
- package/classes/index.js +88 -40
- package/connectors/index.d.ts +4 -4
- package/connectors/index.d.ts.map +1 -1
- package/connectors/index.js +14 -12
- package/css/annotations.css +1 -0
- package/css/app.css +7 -0
- package/css/base.css +3 -0
- package/css/bullet.css +41 -7
- package/css/code.css +14 -0
- package/css/command.css +10 -0
- package/css/dataviz.css +27 -0
- package/css/diff.css +2 -0
- package/css/disclosure.css +8 -0
- package/css/dots.css +1 -1
- package/css/feedback.css +9 -0
- package/css/interval.css +20 -2
- package/css/legend.css +10 -9
- package/css/marks.css +1 -0
- package/css/motion.css +2 -0
- package/css/overlay.css +14 -2
- package/css/primitives.css +1 -1
- package/css/report.css +3 -0
- package/css/sources.css +4 -4
- package/css/spotlight.css +6 -0
- package/css/table.css +19 -0
- package/css/term.css +4 -1
- package/css/tokens.css +8 -13
- package/css/workbench.css +128 -0
- package/dist/bronto.css +1 -1
- package/dist/css/analytical.css +1 -1
- package/dist/css/app.css +1 -1
- package/dist/css/bullet.css +1 -1
- package/dist/css/code.css +1 -1
- package/dist/css/command.css +1 -1
- package/dist/css/dataviz.css +1 -1
- package/dist/css/diff.css +1 -1
- package/dist/css/disclosure.css +1 -1
- package/dist/css/dots.css +1 -1
- package/dist/css/feedback.css +1 -1
- package/dist/css/interval.css +1 -1
- package/dist/css/legend.css +1 -1
- package/dist/css/marks.css +1 -1
- package/dist/css/overlay.css +1 -1
- package/dist/css/primitives.css +1 -1
- package/dist/css/report-kit.css +1 -1
- package/dist/css/sources.css +1 -1
- package/dist/css/spotlight.css +1 -1
- package/dist/css/table.css +1 -1
- package/dist/css/term.css +1 -1
- package/dist/css/tokens.css +1 -1
- package/dist/css/workbench.css +1 -1
- package/docs/annotations.md +27 -0
- package/docs/architecture.md +5 -3
- package/docs/bullet.md +6 -1
- package/docs/clamp.md +5 -0
- package/docs/command.md +3 -2
- package/docs/contrast.md +3 -3
- package/docs/crosshair.md +6 -0
- package/docs/dots.md +10 -3
- package/docs/figure.md +7 -0
- package/docs/glyphs.md +14 -2
- package/docs/highlights.md +9 -0
- package/docs/interval.md +6 -0
- package/docs/mermaid.md +5 -3
- package/docs/package-contract.md +24 -1
- package/docs/reference.md +21 -1
- package/docs/reporting.md +8 -8
- package/docs/selection.md +9 -0
- package/docs/sources.md +5 -0
- package/docs/state.md +6 -0
- package/docs/textref.md +18 -13
- package/docs/theming.md +18 -8
- package/docs/toc.md +6 -0
- package/docs/tree.md +9 -2
- package/docs/usage.md +2 -2
- package/docs/vega.md +5 -3
- package/docs/workbench.md +56 -9
- package/glyphs/glyphs.js +62 -8
- package/index.d.ts +1 -0
- package/llms.txt +18 -14
- package/package.json +98 -6
- package/qwik/index.d.ts +4 -3
- package/qwik/index.d.ts.map +1 -1
- package/qwik/index.js +7 -5
- package/react/index.d.ts +4 -3
- package/react/index.d.ts.map +1 -1
- package/react/index.js +3 -2
- package/solid/index.d.ts +7 -5
- package/solid/index.d.ts.map +1 -1
- package/solid/index.js +11 -7
- package/tokens/vega.d.ts +1 -1
- package/tokens/vega.js +3 -2
- package/vue/index.d.ts.map +1 -1
- package/vue/index.js +37 -3
package/behaviors/glyph.js
CHANGED
|
@@ -30,11 +30,62 @@ function rememberCleanup(el, cleanups, cleanup) {
|
|
|
30
30
|
cleanups.push(wrapped);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
const CSS_LENGTH_UNITS =
|
|
34
|
+
'(?:px|r?em|ch|ex|cap|ic|lh|rlh|vw|vh|vi|vb|vmin|vmax|' +
|
|
35
|
+
'svw|svh|svi|svb|svmin|svmax|lvw|lvh|lvi|lvb|lvmin|lvmax|' +
|
|
36
|
+
'dvw|dvh|dvi|dvb|dvmin|dvmax|cm|mm|q|in|pc|pt|%)';
|
|
37
|
+
const CSS_NUMBER = '[-+]?(?:\\d*\\.\\d+|\\d+)';
|
|
38
|
+
const CSS_LENGTH_RE = new RegExp(`^(?:0|${CSS_NUMBER}${CSS_LENGTH_UNITS})$`, 'i');
|
|
39
|
+
const CSS_CALC_VALUE_RE = new RegExp(`^${CSS_NUMBER}(?:${CSS_LENGTH_UNITS})?`, 'i');
|
|
40
|
+
|
|
41
|
+
function isCalcLength(expr) {
|
|
42
|
+
let rest = expr.trim();
|
|
43
|
+
let depth = 0;
|
|
44
|
+
let sawLength = false;
|
|
45
|
+
let expectValue = true;
|
|
46
|
+
|
|
47
|
+
while (rest) {
|
|
48
|
+
if (expectValue) {
|
|
49
|
+
if (rest[0] === '(') {
|
|
50
|
+
depth += 1;
|
|
51
|
+
rest = rest.slice(1).trim();
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const token = rest.match(CSS_CALC_VALUE_RE)?.[0];
|
|
55
|
+
if (!token) return false;
|
|
56
|
+
if (CSS_LENGTH_RE.test(token)) sawLength = true;
|
|
57
|
+
rest = rest.slice(token.length).trim();
|
|
58
|
+
expectValue = false;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (rest[0] === ')') {
|
|
63
|
+
depth -= 1;
|
|
64
|
+
if (depth < 0) return false;
|
|
65
|
+
rest = rest.slice(1).trim();
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const op = rest.match(/^[+\-*/]/)?.[0];
|
|
70
|
+
if (!op) return false;
|
|
71
|
+
rest = rest.slice(op.length).trim();
|
|
72
|
+
expectValue = true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!sawLength) return false;
|
|
76
|
+
if (expectValue) return false;
|
|
77
|
+
return depth === 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// `size` lands in inline CSS, so accept only concrete CSS lengths or calc()
|
|
81
|
+
// expressions (mirrors glyphs.js cssLen). Used for the mask path's --icon-size.
|
|
36
82
|
function cssLen(v) {
|
|
37
|
-
|
|
83
|
+
const value = String(v ?? '').trim();
|
|
84
|
+
if (CSS_LENGTH_RE.test(value)) return value;
|
|
85
|
+
if (value.toLowerCase().startsWith('calc(') && value.endsWith(')')) {
|
|
86
|
+
return isCalcLength(value.slice(5, -1)) ? value : '';
|
|
87
|
+
}
|
|
88
|
+
return '';
|
|
38
89
|
}
|
|
39
90
|
|
|
40
91
|
function applyGlyphA11y(el, label) {
|
|
@@ -214,7 +265,7 @@ export function initDotGlyph({ root } = {}) {
|
|
|
214
265
|
const label = el.getAttribute('data-bronto-glyph-label');
|
|
215
266
|
|
|
216
267
|
// One-node mask path — the icon-at-scale counterpart to the 256-cell grid.
|
|
217
|
-
if (el.getAttribute('data-bronto-glyph-render') === 'mask') {
|
|
268
|
+
if (el.getAttribute('data-bronto-glyph-render')?.trim() === 'mask') {
|
|
218
269
|
expandMaskGlyph(el, name, label, cleanups);
|
|
219
270
|
continue;
|
|
220
271
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["internal.js"],"names":[],"mappings":"AAiDA,iEAIC;AAeD,
|
|
1
|
+
{"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["internal.js"],"names":[],"mappings":"AAiDA,iEAIC;AAeD,sEAaC;AAED,oDAUC;AAED,yDAOC;AAMD,8DAIC;AAID;;SAMC;AAkDD,gDAUC;AAMD,+DAKC;AAjLM,6BAAqB;AAErB,kCAAoD;AAyCpD,uCAAqC;;;;;sBArD/B,MAAM,IAAI"}
|
package/behaviors/internal.js
CHANGED
|
@@ -70,7 +70,10 @@ export function bindOnce(target, key, add) {
|
|
|
70
70
|
const reg = target[BOUND] || (target[BOUND] = Object.create(null));
|
|
71
71
|
if (reg[key]) reg[key]();
|
|
72
72
|
const remove = add();
|
|
73
|
+
let done = false;
|
|
73
74
|
const cleanup = () => {
|
|
75
|
+
if (done) return;
|
|
76
|
+
done = true;
|
|
74
77
|
remove();
|
|
75
78
|
if (reg[key] === cleanup) delete reg[key];
|
|
76
79
|
};
|
|
@@ -80,11 +83,13 @@ export function bindOnce(target, key, add) {
|
|
|
80
83
|
|
|
81
84
|
export function byIdInHost(host, id) {
|
|
82
85
|
if (!id) return null;
|
|
83
|
-
|
|
86
|
+
const doc = host.nodeType === 9 ? host : host.ownerDocument;
|
|
87
|
+
if (host === doc) return doc.getElementById(id);
|
|
84
88
|
if (host.id === id) return host;
|
|
85
89
|
return (
|
|
86
90
|
Array.from(host.querySelectorAll?.('[id]') || []).find((el) => el.id === id) ||
|
|
87
|
-
|
|
91
|
+
doc?.getElementById(id) ||
|
|
92
|
+
null
|
|
88
93
|
);
|
|
89
94
|
}
|
|
90
95
|
|
|
@@ -122,11 +127,53 @@ export function scrollIntoViewSafe(el, opts = { block: 'nearest' }) {
|
|
|
122
127
|
// itself on open). Focus the first focusable descendant, else make the
|
|
123
128
|
// container programmatically focusable and focus it, so a content-only
|
|
124
129
|
// panel/modal still receives focus.
|
|
125
|
-
const FOCUSABLE =
|
|
126
|
-
|
|
130
|
+
const FOCUSABLE = 'a[href], button, input, select, textarea, [tabindex]';
|
|
131
|
+
|
|
132
|
+
function isInsideDisabledFieldset(el) {
|
|
133
|
+
return Boolean(el.closest?.('fieldset[disabled]'));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function isRendered(el) {
|
|
137
|
+
try {
|
|
138
|
+
if (
|
|
139
|
+
typeof el.checkVisibility === 'function' &&
|
|
140
|
+
!el.checkVisibility({ checkVisibilityCSS: true, visibilityProperty: true })
|
|
141
|
+
) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
} catch {
|
|
145
|
+
/* fall through to conservative checks */
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const view = el.ownerDocument?.defaultView;
|
|
149
|
+
const style = view?.getComputedStyle?.(el);
|
|
150
|
+
if (style?.display === 'none') return false;
|
|
151
|
+
if (style?.visibility === 'hidden' || style?.visibility === 'collapse') return false;
|
|
152
|
+
|
|
153
|
+
const docHasLayout = Boolean(el.ownerDocument?.documentElement?.getClientRects?.().length);
|
|
154
|
+
if (
|
|
155
|
+
docHasLayout &&
|
|
156
|
+
'offsetParent' in el &&
|
|
157
|
+
el.offsetParent === null &&
|
|
158
|
+
el.getClientRects?.().length === 0
|
|
159
|
+
) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function isFocusableCandidate(el) {
|
|
166
|
+
if (el.closest?.('[hidden], [inert]')) return false;
|
|
167
|
+
if ('disabled' in el && el.disabled) return false;
|
|
168
|
+
if (isInsideDisabledFieldset(el)) return false;
|
|
169
|
+
if (el.tabIndex < 0) return false;
|
|
170
|
+
return isRendered(el);
|
|
171
|
+
}
|
|
127
172
|
|
|
128
173
|
export function focusInto(container) {
|
|
129
|
-
const first = container.
|
|
174
|
+
const first = Array.from(container.querySelectorAll?.(FOCUSABLE) || []).find(
|
|
175
|
+
isFocusableCandidate,
|
|
176
|
+
);
|
|
130
177
|
if (first) {
|
|
131
178
|
first.focus?.();
|
|
132
179
|
return;
|
package/behaviors/menu.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["menu.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AACH,oCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["menu.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AACH,oCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAyC3C"}
|
package/behaviors/menu.js
CHANGED
|
@@ -18,7 +18,8 @@ export function initMenu({ root } = {}) {
|
|
|
18
18
|
if (!hasDom()) return noop;
|
|
19
19
|
const host = resolveHost(root);
|
|
20
20
|
if (!host) return noop;
|
|
21
|
-
const doc = host.nodeType === 9 ? host : host.ownerDocument
|
|
21
|
+
const doc = host.nodeType === 9 ? host : host.ownerDocument;
|
|
22
|
+
if (!doc) return noop;
|
|
22
23
|
const openMenus = () => collectHosts(host, '[data-bronto-menu][open]');
|
|
23
24
|
const shut = (menu) => {
|
|
24
25
|
if (!menu || !menu.open) return;
|
package/behaviors/modal.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"modal.d.ts","sourceRoot":"","sources":["modal.js"],"names":[],"mappings":"AAsDA;;;GAGG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CA+
|
|
1
|
+
{"version":3,"file":"modal.d.ts","sourceRoot":"","sources":["modal.js"],"names":[],"mappings":"AAsDA;;;GAGG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CA+G3C;;;;;YA7Ia,QAAQ"}
|
package/behaviors/modal.js
CHANGED
|
@@ -94,6 +94,10 @@ export function initModal({ root } = {}) {
|
|
|
94
94
|
const cleanups = [];
|
|
95
95
|
|
|
96
96
|
for (const modal of modals) {
|
|
97
|
+
const doc = modal.ownerDocument;
|
|
98
|
+
if (!doc) continue;
|
|
99
|
+
const view = doc.defaultView;
|
|
100
|
+
const isNativeDialog = modal.localName === 'dialog';
|
|
97
101
|
let opener = null;
|
|
98
102
|
let inerted = [];
|
|
99
103
|
|
|
@@ -103,10 +107,10 @@ export function initModal({ root } = {}) {
|
|
|
103
107
|
// app inerted for its own reasons.
|
|
104
108
|
const trap = () => {
|
|
105
109
|
if (opener) return; // already trapped
|
|
106
|
-
opener =
|
|
110
|
+
opener = doc.activeElement;
|
|
107
111
|
pushActiveModal(modal);
|
|
108
112
|
let el = modal;
|
|
109
|
-
while (el && el.parentElement && el !==
|
|
113
|
+
while (el && el.parentElement && el !== doc.body) {
|
|
110
114
|
for (const sib of el.parentElement.children) {
|
|
111
115
|
if (sib !== el && !sib.inert) {
|
|
112
116
|
sib.inert = true;
|
|
@@ -128,14 +132,23 @@ export function initModal({ root } = {}) {
|
|
|
128
132
|
if (back?.isConnected && typeof back.focus === 'function') back.focus();
|
|
129
133
|
};
|
|
130
134
|
|
|
131
|
-
const sync = () =>
|
|
135
|
+
const sync = () => {
|
|
136
|
+
if (modal.classList.contains('is-open')) {
|
|
137
|
+
if (!isNativeDialog) modal.hidden = false;
|
|
138
|
+
trap();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
release();
|
|
142
|
+
if (!isNativeDialog) modal.hidden = true;
|
|
143
|
+
};
|
|
132
144
|
|
|
133
145
|
const onKey = (e) => {
|
|
134
146
|
if (e.key === 'Escape' && opener) {
|
|
135
147
|
if (activeModals.at(-1) !== modal) return;
|
|
136
148
|
if (insideOpenPopover(e.target, modal)) return;
|
|
149
|
+
const ModalCloseEvent = view?.CustomEvent ?? CustomEvent;
|
|
137
150
|
modal.dispatchEvent(
|
|
138
|
-
new
|
|
151
|
+
new ModalCloseEvent('bronto:modal:close', {
|
|
139
152
|
detail: { reason: 'escape' },
|
|
140
153
|
bubbles: true,
|
|
141
154
|
cancelable: true,
|
|
@@ -146,7 +159,7 @@ export function initModal({ root } = {}) {
|
|
|
146
159
|
|
|
147
160
|
cleanups.push(
|
|
148
161
|
bindOnce(modal, 'modal', () => {
|
|
149
|
-
const attrs = snapshotAttrs(modal, ['role', 'aria-modal', 'tabindex']);
|
|
162
|
+
const attrs = snapshotAttrs(modal, ['role', 'aria-modal', 'tabindex', 'hidden']);
|
|
150
163
|
|
|
151
164
|
// A controlled modal must announce AS a modal dialog, not a generic group —
|
|
152
165
|
// parity with initPopover. Apply a dialog role + aria-modal (unless the
|
|
@@ -164,13 +177,16 @@ export function initModal({ root } = {}) {
|
|
|
164
177
|
);
|
|
165
178
|
}
|
|
166
179
|
|
|
167
|
-
const
|
|
180
|
+
const Observer =
|
|
181
|
+
view?.MutationObserver ??
|
|
182
|
+
(typeof MutationObserver === 'function' ? MutationObserver : null);
|
|
183
|
+
const observer = Observer ? new Observer(sync) : null;
|
|
168
184
|
observer?.observe(modal, { attributes: true, attributeFilter: ['class'] });
|
|
169
|
-
|
|
170
|
-
|
|
185
|
+
doc.addEventListener('keydown', onKey, true);
|
|
186
|
+
sync();
|
|
171
187
|
return () => {
|
|
172
188
|
observer?.disconnect();
|
|
173
|
-
|
|
189
|
+
doc.removeEventListener('keydown', onKey, true);
|
|
174
190
|
release();
|
|
175
191
|
restoreAttrs(modal, attrs);
|
|
176
192
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"popover.d.ts","sourceRoot":"","sources":["popover.js"],"names":[],"mappings":"AAuCA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,uCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"popover.d.ts","sourceRoot":"","sources":["popover.js"],"names":[],"mappings":"AAuCA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,uCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAoM3C"}
|
package/behaviors/popover.js
CHANGED
|
@@ -68,7 +68,9 @@ export function initPopover({ root } = {}) {
|
|
|
68
68
|
if (!hasDom()) return noop;
|
|
69
69
|
const host = resolveHost(root);
|
|
70
70
|
if (!host) return noop;
|
|
71
|
-
const
|
|
71
|
+
const doc = host.nodeType === 9 ? host : host.ownerDocument;
|
|
72
|
+
if (!doc) return noop;
|
|
73
|
+
const view = doc.defaultView;
|
|
72
74
|
const GAP = 8;
|
|
73
75
|
let openPanel = null;
|
|
74
76
|
let openTrigger = null;
|
|
@@ -127,7 +129,7 @@ export function initPopover({ root } = {}) {
|
|
|
127
129
|
// Only steal focus back to the trigger when focus is still inside the panel
|
|
128
130
|
// (Escape / programmatic re-toggle). An outside-click leaves focus where the
|
|
129
131
|
// click landed — deliberate intent to move on, per the doc contract.
|
|
130
|
-
const focusWasInside = panel.contains(
|
|
132
|
+
const focusWasInside = panel.contains(doc.activeElement);
|
|
131
133
|
openPanel = openTrigger = null;
|
|
132
134
|
if (panel.hasAttribute('popover') && typeof panel.hidePopover === 'function') {
|
|
133
135
|
try {
|
|
@@ -235,8 +237,8 @@ export function initPopover({ root } = {}) {
|
|
|
235
237
|
|
|
236
238
|
return bindOnce(host, 'popover', () => {
|
|
237
239
|
seed();
|
|
238
|
-
|
|
239
|
-
|
|
240
|
+
doc.addEventListener('click', onClick);
|
|
241
|
+
doc.addEventListener('keydown', onKey);
|
|
240
242
|
view?.addEventListener('scroll', onReflow, true);
|
|
241
243
|
view?.addEventListener('resize', onReflow);
|
|
242
244
|
return () => {
|
|
@@ -250,8 +252,8 @@ export function initPopover({ root } = {}) {
|
|
|
250
252
|
restoreStyle(panel, state.style);
|
|
251
253
|
}
|
|
252
254
|
panelStates.clear();
|
|
253
|
-
|
|
254
|
-
|
|
255
|
+
doc.removeEventListener('click', onClick);
|
|
256
|
+
doc.removeEventListener('keydown', onKey);
|
|
255
257
|
view?.removeEventListener('scroll', onReflow, true);
|
|
256
258
|
view?.removeEventListener('resize', onReflow);
|
|
257
259
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sources.d.ts","sourceRoot":"","sources":["sources.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sources.d.ts","sourceRoot":"","sources":["sources.js"],"names":[],"mappings":"AAqDA;;;;;;;;;;;GAWG;AACH,uCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CA+H3C;;;;;QAlLa,MAAM;;;;cACN,OAAO;;;;YACP,OAAO"}
|
package/behaviors/sources.js
CHANGED
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
resolveHost,
|
|
4
4
|
noop,
|
|
5
5
|
bindOnce,
|
|
6
|
-
byIdInHost,
|
|
7
6
|
collectHosts,
|
|
8
7
|
scrollIntoViewSafe,
|
|
9
8
|
closestSafe,
|
|
@@ -42,6 +41,16 @@ function sourcePreview(source) {
|
|
|
42
41
|
return [title, meta, excerpt].filter(Boolean).join(' — ');
|
|
43
42
|
}
|
|
44
43
|
|
|
44
|
+
function idMapFor(island) {
|
|
45
|
+
const ids = new Map();
|
|
46
|
+
const add = (el) => {
|
|
47
|
+
if (el?.id && !ids.has(el.id)) ids.set(el.id, el);
|
|
48
|
+
};
|
|
49
|
+
add(island);
|
|
50
|
+
for (const el of island.querySelectorAll?.('[id]') || []) add(el);
|
|
51
|
+
return ids;
|
|
52
|
+
}
|
|
53
|
+
|
|
45
54
|
/**
|
|
46
55
|
* Source/citation affordances for the `sources.css` trust layer. The behavior
|
|
47
56
|
* is deliberately small: within each `[data-bronto-sources]` island it resolves
|
|
@@ -66,11 +75,23 @@ export function initSources({ root } = {}) {
|
|
|
66
75
|
const timers = new Set();
|
|
67
76
|
const seeded = [];
|
|
68
77
|
const activeSources = new Set();
|
|
78
|
+
const sourcesById = idMapFor(island);
|
|
79
|
+
const previewBySource = new WeakMap();
|
|
69
80
|
|
|
70
81
|
const targetFor = (ref) => {
|
|
71
82
|
const id = sourceId(ref);
|
|
72
83
|
if (!id) return null;
|
|
73
|
-
|
|
84
|
+
const source = sourcesById.get(id) || null;
|
|
85
|
+
return source && island.contains(source) ? source : null;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const previewFor = (source) => {
|
|
89
|
+
let preview = previewBySource.get(source);
|
|
90
|
+
if (preview === undefined) {
|
|
91
|
+
preview = sourcePreview(source);
|
|
92
|
+
previewBySource.set(source, preview);
|
|
93
|
+
}
|
|
94
|
+
return preview;
|
|
74
95
|
};
|
|
75
96
|
|
|
76
97
|
const seed = () => {
|
|
@@ -81,7 +102,7 @@ export function initSources({ root } = {}) {
|
|
|
81
102
|
const describedBy = ref.getAttribute('aria-describedby') || '';
|
|
82
103
|
const describedIds = describedBy.split(/\s+/).filter(Boolean);
|
|
83
104
|
const title = ref.getAttribute('title');
|
|
84
|
-
const preview =
|
|
105
|
+
const preview = previewFor(source);
|
|
85
106
|
const prior = {
|
|
86
107
|
ref,
|
|
87
108
|
describedBy,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"splitter.d.ts","sourceRoot":"","sources":["splitter.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"splitter.d.ts","sourceRoot":"","sources":["splitter.js"],"names":[],"mappings":"AA6OA;;;;;;;;;;;;;GAaG;AACH,wCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAU3C;;;;;WAvPa,MAAM;;;;iBACN,UAAU,GAAG,YAAY"}
|
package/behaviors/splitter.js
CHANGED
|
@@ -14,7 +14,16 @@ const LARGE_STEP = 10;
|
|
|
14
14
|
* @property {'vertical' | 'horizontal'} orientation Splitter orientation.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
const NUMBER_RE = /^[-+]?(?:(?:\d+\.?\d*)|\.\d+)(?:e[-+]?\d+)?$/i;
|
|
18
|
+
|
|
17
19
|
const num = (v, fallback) => {
|
|
20
|
+
const value = String(v ?? '').trim();
|
|
21
|
+
if (!NUMBER_RE.test(value)) return fallback;
|
|
22
|
+
const n = Number(value);
|
|
23
|
+
return Number.isFinite(n) ? n : fallback;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const cssNum = (v, fallback) => {
|
|
18
27
|
const n = Number.parseFloat(String(v ?? '').trim());
|
|
19
28
|
return Number.isFinite(n) ? n : fallback;
|
|
20
29
|
};
|
|
@@ -26,7 +35,7 @@ const clamp = (v, min, max) => Math.min(max, Math.max(min, v));
|
|
|
26
35
|
const readCssValue = (splitter) => splitter.style.getPropertyValue('--splitter-pos');
|
|
27
36
|
|
|
28
37
|
const readOrientation = (splitter, handle) => {
|
|
29
|
-
const data = splitter.getAttribute('data-bronto-splitter');
|
|
38
|
+
const data = splitter.getAttribute('data-bronto-splitter')?.trim();
|
|
30
39
|
if (data === 'horizontal' || data === 'vertical') return data;
|
|
31
40
|
if (splitter.classList.contains('ui-splitter--horizontal')) return 'horizontal';
|
|
32
41
|
if (splitter.classList.contains('ui-splitter--vertical')) return 'vertical';
|
|
@@ -87,13 +96,25 @@ function wireSplitter(splitter) {
|
|
|
87
96
|
]);
|
|
88
97
|
const splitterPos = snapshotStyleProp(splitter, '--splitter-pos');
|
|
89
98
|
const orientation = readOrientation(splitter, handle);
|
|
90
|
-
const
|
|
91
|
-
const
|
|
99
|
+
const minAttr = handle.getAttribute('aria-valuemin');
|
|
100
|
+
const maxAttr = handle.getAttribute('aria-valuemax');
|
|
101
|
+
const min = num(minAttr, DEFAULT_MIN);
|
|
102
|
+
const max = Math.max(min, num(maxAttr, DEFAULT_MAX));
|
|
92
103
|
let value = clamp(
|
|
93
|
-
num(handle.getAttribute('aria-valuenow'),
|
|
104
|
+
num(handle.getAttribute('aria-valuenow'), cssNum(readCssValue(splitter), DEFAULT_VALUE)),
|
|
94
105
|
min,
|
|
95
106
|
max,
|
|
96
107
|
);
|
|
108
|
+
const syncRangeAttr = (name, authored, normalized) => {
|
|
109
|
+
const parsed = num(authored, Number.NaN);
|
|
110
|
+
if (
|
|
111
|
+
authored === null ||
|
|
112
|
+
!Number.isFinite(parsed) ||
|
|
113
|
+
String(authored).trim() !== fmt(normalized)
|
|
114
|
+
) {
|
|
115
|
+
handle.setAttribute(name, fmt(normalized));
|
|
116
|
+
}
|
|
117
|
+
};
|
|
97
118
|
let activePointer = null;
|
|
98
119
|
|
|
99
120
|
const apply = (next, { emit = true } = {}) => {
|
|
@@ -108,8 +129,8 @@ function wireSplitter(splitter) {
|
|
|
108
129
|
if (!handle.hasAttribute('tabindex')) handle.tabIndex = 0;
|
|
109
130
|
if (!handle.hasAttribute('aria-orientation'))
|
|
110
131
|
handle.setAttribute('aria-orientation', orientation);
|
|
111
|
-
|
|
112
|
-
|
|
132
|
+
syncRangeAttr('aria-valuemin', minAttr, min);
|
|
133
|
+
syncRangeAttr('aria-valuemax', maxAttr, max);
|
|
113
134
|
apply(value, { emit: false });
|
|
114
135
|
|
|
115
136
|
const fromPointer = (event) => {
|
package/behaviors/table.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table.d.ts","sourceRoot":"","sources":["table.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"table.d.ts","sourceRoot":"","sources":["table.js"],"names":[],"mappings":"AAqBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,yCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CA0P3C"}
|
package/behaviors/table.js
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
import { hasDom, resolveHost, noop, bindOnce, collectHosts, closestSafe } from './internal.js';
|
|
2
2
|
|
|
3
|
+
const localeOf = (el) => {
|
|
4
|
+
const locale =
|
|
5
|
+
closestSafe(el, '[lang]')?.getAttribute('lang')?.trim() ||
|
|
6
|
+
el?.ownerDocument?.documentElement?.getAttribute('lang')?.trim();
|
|
7
|
+
return locale || undefined;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const textComparatorFor = (el) => {
|
|
11
|
+
const options = { sensitivity: 'base', numeric: false };
|
|
12
|
+
if (typeof Intl === 'undefined' || typeof Intl.Collator !== 'function') {
|
|
13
|
+
return (a, b) => a.localeCompare(b);
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
return new Intl.Collator(localeOf(el), options).compare;
|
|
17
|
+
} catch {
|
|
18
|
+
return new Intl.Collator(undefined, options).compare;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
3
22
|
/**
|
|
4
23
|
* Client-side sortable + selectable data table. Wires
|
|
5
24
|
* `[data-bronto-sortable]`:
|
|
@@ -71,6 +90,8 @@ export function initTableSort({ root } = {}) {
|
|
|
71
90
|
const rows = [];
|
|
72
91
|
const checkboxStates = new WeakMap();
|
|
73
92
|
const checkboxes = [];
|
|
93
|
+
const misplacedSorters = new WeakSet();
|
|
94
|
+
const compareText = textComparatorFor(table);
|
|
74
95
|
|
|
75
96
|
const rememberHeaderState = (th) => {
|
|
76
97
|
if (!th || headerStates.has(th)) return;
|
|
@@ -134,6 +155,15 @@ export function initTableSort({ root } = {}) {
|
|
|
134
155
|
|
|
135
156
|
const colIndex = (th) => [...th.parentElement.children].indexOf(th);
|
|
136
157
|
const cellText = (row, i) => row.children[i]?.textContent.trim() ?? '';
|
|
158
|
+
const warnMisplacedSorter = (sorter) => {
|
|
159
|
+
if (misplacedSorters.has(sorter)) return;
|
|
160
|
+
misplacedSorters.add(sorter);
|
|
161
|
+
if (typeof console !== 'undefined') {
|
|
162
|
+
console.warn(
|
|
163
|
+
'[bronto] initTableSort(): .ui-table__sort must be placed inside a <th>; ignoring this sorter.',
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
137
167
|
// Numeric value of a cell for sorting. A `data-sort-value` attribute is the
|
|
138
168
|
// authoritative escape hatch; otherwise normalize the display text so the
|
|
139
169
|
// sign survives (U+2212 / en-em dashes → minus, accounting parens →
|
|
@@ -143,7 +173,7 @@ export function initTableSort({ root } = {}) {
|
|
|
143
173
|
const cell = row.children[i];
|
|
144
174
|
const explicit = cell?.getAttribute?.('data-sort-value');
|
|
145
175
|
if (explicit != null && explicit.trim() !== '') {
|
|
146
|
-
const raw = explicit.trim();
|
|
176
|
+
const raw = explicit.trim().replace(/[−–—]/g, '-');
|
|
147
177
|
let v = Number(raw);
|
|
148
178
|
// The escape hatch must actually handle the case the doc names it for: a
|
|
149
179
|
// European decimal comma. `Number("3,5")` is NaN, which silently fell
|
|
@@ -184,15 +214,18 @@ export function initTableSort({ root } = {}) {
|
|
|
184
214
|
// or after a sort they float above the real rows.
|
|
185
215
|
const emptyRows = [...tbody.rows].filter((r) => r.classList.contains('ui-table__empty'));
|
|
186
216
|
const rows = [...tbody.rows].filter((r) => !r.classList.contains('ui-table__empty'));
|
|
187
|
-
rows.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
217
|
+
const keyedRows = rows.map((row, index) => ({
|
|
218
|
+
row,
|
|
219
|
+
index,
|
|
220
|
+
key: numeric ? cellNum(row, i) : cellText(row, i),
|
|
221
|
+
}));
|
|
222
|
+
keyedRows.sort((a, b) => {
|
|
223
|
+
const cmp = numeric ? a.key - b.key : compareText(a.key, b.key);
|
|
224
|
+
return cmp === 0 ? a.index - b.index : cmp * sign;
|
|
192
225
|
});
|
|
193
226
|
// Re-parent in document order: sorted data rows, then any empty/sentinel
|
|
194
227
|
// row last. These are existing <tr> nodes being moved; no markup is parsed.
|
|
195
|
-
tbody.append(...
|
|
228
|
+
tbody.append(...keyedRows.map(({ row }) => row), ...emptyRows);
|
|
196
229
|
};
|
|
197
230
|
|
|
198
231
|
const allBox = table.querySelector('[data-bronto-select-all]');
|
|
@@ -227,6 +260,10 @@ export function initTableSort({ root } = {}) {
|
|
|
227
260
|
if (sorter && table.contains(sorter)) {
|
|
228
261
|
rememberSorterState(sorter);
|
|
229
262
|
const th = sorter.closest('th');
|
|
263
|
+
if (!th) {
|
|
264
|
+
warnMisplacedSorter(sorter);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
230
267
|
const numeric =
|
|
231
268
|
(sorter.getAttribute('data-sort') || th.getAttribute('data-sort')) === 'num' ||
|
|
232
269
|
th.classList.contains('is-num');
|
package/behaviors/tabs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,CAoL3C"}
|
package/behaviors/tabs.js
CHANGED
|
@@ -57,6 +57,17 @@ export function initTabs({ root } = {}) {
|
|
|
57
57
|
const panels = [...group.querySelectorAll('.ui-tabs__panel')].filter(owned);
|
|
58
58
|
if (!tabs.length) continue;
|
|
59
59
|
const list = [...group.querySelectorAll('.ui-tabs__list')].find(owned);
|
|
60
|
+
const isNativeDisabled = (tab) => {
|
|
61
|
+
try {
|
|
62
|
+
if (tab.matches?.(':disabled')) return true;
|
|
63
|
+
} catch {
|
|
64
|
+
/* fall through to the native property */
|
|
65
|
+
}
|
|
66
|
+
return Boolean('disabled' in tab && tab.disabled);
|
|
67
|
+
};
|
|
68
|
+
const isReachable = (tab) => !tab.hidden && !isNativeDisabled(tab);
|
|
69
|
+
const isAriaDisabled = (tab) => tab.getAttribute('aria-disabled') === 'true';
|
|
70
|
+
const reachableTabs = () => tabs.filter(isReachable);
|
|
60
71
|
const rememberState = () => ({
|
|
61
72
|
list: list ? snapshotAttrs(list, ['role']) : null,
|
|
62
73
|
tabs: new Map(
|
|
@@ -94,17 +105,31 @@ export function initTabs({ root } = {}) {
|
|
|
94
105
|
}
|
|
95
106
|
};
|
|
96
107
|
|
|
97
|
-
|
|
108
|
+
let selectedTab = null;
|
|
109
|
+
const syncTabs = (selected, tabStop) => {
|
|
98
110
|
for (const t of tabs) {
|
|
99
|
-
const on = t ===
|
|
111
|
+
const on = t === selected;
|
|
100
112
|
t.classList.toggle('is-active', on);
|
|
101
113
|
t.setAttribute('role', 'tab');
|
|
102
114
|
t.setAttribute('aria-selected', String(on));
|
|
103
|
-
t.tabIndex =
|
|
115
|
+
t.tabIndex = t === tabStop ? 0 : -1;
|
|
104
116
|
}
|
|
117
|
+
};
|
|
118
|
+
const moveTabStop = (tab) => {
|
|
119
|
+
const candidates = reachableTabs();
|
|
120
|
+
const next = candidates.includes(tab) ? tab : candidates[0] || null;
|
|
121
|
+
if (!next) return false;
|
|
122
|
+
syncTabs(selectedTab, next);
|
|
123
|
+
return true;
|
|
124
|
+
};
|
|
125
|
+
const select = (tab) => {
|
|
126
|
+
const candidates = reachableTabs();
|
|
127
|
+
if (!candidates.includes(tab) || isAriaDisabled(tab)) return false;
|
|
128
|
+
selectedTab = tab;
|
|
129
|
+
syncTabs(tab, tab);
|
|
105
130
|
// Only retarget panels when this tab actually controls one. A panel-less
|
|
106
131
|
// tab must not hide every panel; leave the prior panel visible.
|
|
107
|
-
if (!panels.some((p) => p.dataset.panel === tab.dataset.tab)) return;
|
|
132
|
+
if (!panels.some((p) => p.dataset.panel === tab.dataset.tab)) return true;
|
|
108
133
|
for (const p of panels) {
|
|
109
134
|
p.setAttribute('role', 'tabpanel');
|
|
110
135
|
const shown = p.dataset.panel === tab.dataset.tab;
|
|
@@ -114,6 +139,7 @@ export function initTabs({ root } = {}) {
|
|
|
114
139
|
if (shown) p.tabIndex = 0;
|
|
115
140
|
else p.removeAttribute('tabindex');
|
|
116
141
|
}
|
|
142
|
+
return true;
|
|
117
143
|
};
|
|
118
144
|
const onClick = (e) => {
|
|
119
145
|
// `tabs` is filtered to this group, so membership (not mere DOM
|
|
@@ -121,23 +147,28 @@ export function initTabs({ root } = {}) {
|
|
|
121
147
|
const tab = closestSafe(e.target, '.ui-tab');
|
|
122
148
|
if (tab && tabs.includes(tab)) {
|
|
123
149
|
e.preventDefault();
|
|
124
|
-
select(tab);
|
|
125
|
-
tab.focus();
|
|
150
|
+
const handled = select(tab) || moveTabStop(tab);
|
|
151
|
+
if (handled) tab.focus();
|
|
126
152
|
}
|
|
127
153
|
};
|
|
128
154
|
const onKey = (e) => {
|
|
129
|
-
const
|
|
155
|
+
const candidates = reachableTabs();
|
|
156
|
+
const i = candidates.indexOf(closestSafe(e.target, '.ui-tab'));
|
|
130
157
|
if (i < 0) return;
|
|
158
|
+
const orientation =
|
|
159
|
+
list?.getAttribute('aria-orientation') === 'vertical' ? 'vertical' : 'horizontal';
|
|
160
|
+
const nextKey = orientation === 'vertical' ? 'ArrowDown' : 'ArrowRight';
|
|
161
|
+
const prevKey = orientation === 'vertical' ? 'ArrowUp' : 'ArrowLeft';
|
|
131
162
|
let n = i;
|
|
132
|
-
if (e.key ===
|
|
133
|
-
else if (e.key ===
|
|
134
|
-
n = (i - 1 + tabs.length) % tabs.length;
|
|
163
|
+
if (e.key === nextKey) n = (i + 1) % candidates.length;
|
|
164
|
+
else if (e.key === prevKey) n = (i - 1 + candidates.length) % candidates.length;
|
|
135
165
|
else if (e.key === 'Home') n = 0;
|
|
136
|
-
else if (e.key === 'End') n =
|
|
166
|
+
else if (e.key === 'End') n = candidates.length - 1;
|
|
137
167
|
else return;
|
|
138
168
|
e.preventDefault();
|
|
139
|
-
|
|
140
|
-
|
|
169
|
+
const next = candidates[n];
|
|
170
|
+
if (!select(next)) moveTabStop(next);
|
|
171
|
+
next.focus();
|
|
141
172
|
};
|
|
142
173
|
cleanups.push(
|
|
143
174
|
bindOnce(group, 'tabs', () => {
|
|
@@ -155,7 +186,13 @@ export function initTabs({ root } = {}) {
|
|
|
155
186
|
t.setAttribute('aria-controls', p.id);
|
|
156
187
|
p.setAttribute('aria-labelledby', t.id);
|
|
157
188
|
}
|
|
158
|
-
|
|
189
|
+
const candidates = reachableTabs();
|
|
190
|
+
const initial =
|
|
191
|
+
candidates.find((t) => t.classList.contains('is-active') && !isAriaDisabled(t)) ||
|
|
192
|
+
candidates.find((t) => !isAriaDisabled(t)) ||
|
|
193
|
+
candidates[0];
|
|
194
|
+
if (initial && !select(initial)) moveTabStop(initial);
|
|
195
|
+
else if (!initial) syncTabs(null, null);
|
|
159
196
|
group.addEventListener('click', onClick);
|
|
160
197
|
group.addEventListener('keydown', onKey);
|
|
161
198
|
return () => {
|