@ponchia/ui 0.6.10 → 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 +72 -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 +24 -0
- package/classes/index.d.ts +3 -2
- package/classes/index.js +77 -39
- 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/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/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/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/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/connectors.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { hasDom, resolveHost, noop, bindOnce,
|
|
1
|
+
import { hasDom, resolveHost, noop, bindOnce, collectHosts } from './internal.js';
|
|
2
2
|
import { connectRects, arrowHead, dotMark } from '../connectors/index.js';
|
|
3
3
|
|
|
4
4
|
const SVGNS = 'http://www.w3.org/2000/svg';
|
|
@@ -77,6 +77,46 @@ const syncConnectorEnd = (svg, end, angle) => {
|
|
|
77
77
|
);
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
+
const CONNECTOR_SHAPES = ['straight', 'elbow', 'curve'];
|
|
81
|
+
const CONNECTOR_SIDES = ['top', 'right', 'bottom', 'left', 'center'];
|
|
82
|
+
const CONNECTOR_SHAPE_VALUES = new Set(CONNECTOR_SHAPES);
|
|
83
|
+
const CONNECTOR_SIDE_VALUES = new Set(CONNECTOR_SIDES);
|
|
84
|
+
|
|
85
|
+
const clearConnectorParts = (svg) => {
|
|
86
|
+
svg.querySelector('.ui-connector__path')?.remove();
|
|
87
|
+
svg.querySelector('.ui-connector__end')?.remove();
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const invalidConnectorOptionDetails = (svg) => {
|
|
91
|
+
const details = [];
|
|
92
|
+
const shape = svg.dataset.shape;
|
|
93
|
+
if (shape && !CONNECTOR_SHAPE_VALUES.has(shape)) {
|
|
94
|
+
details.push(`data-shape="${shape}" (allowed: ${CONNECTOR_SHAPES.join('/')})`);
|
|
95
|
+
}
|
|
96
|
+
const fromSide = svg.dataset.fromSide;
|
|
97
|
+
if (fromSide && !CONNECTOR_SIDE_VALUES.has(fromSide)) {
|
|
98
|
+
details.push(`data-from-side="${fromSide}" (allowed: ${CONNECTOR_SIDES.join('/')})`);
|
|
99
|
+
}
|
|
100
|
+
const toSide = svg.dataset.toSide;
|
|
101
|
+
if (toSide && !CONNECTOR_SIDE_VALUES.has(toSide)) {
|
|
102
|
+
details.push(`data-to-side="${toSide}" (allowed: ${CONNECTOR_SIDES.join('/')})`);
|
|
103
|
+
}
|
|
104
|
+
return details;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const warnInvalidConnectorOptions = (svg, warnedInvalidOptions) => {
|
|
108
|
+
const details = invalidConnectorOptionDetails(svg);
|
|
109
|
+
if (!details.length || typeof console === 'undefined') return;
|
|
110
|
+
const signature = details.join('|');
|
|
111
|
+
if (warnedInvalidOptions.get(svg) === signature) return;
|
|
112
|
+
warnedInvalidOptions.set(svg, signature);
|
|
113
|
+
console.warn(
|
|
114
|
+
`[bronto] initConnectors(): invalid connector option ${details.join(
|
|
115
|
+
', ',
|
|
116
|
+
)} - skipping connector.`,
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
|
|
80
120
|
/**
|
|
81
121
|
* Draw + keep leader lines in sync. Each `[data-bronto-connector]` is an
|
|
82
122
|
* `.ui-connector` SVG overlaying a positioned container; `data-from`/`data-to`
|
|
@@ -137,12 +177,29 @@ export function initConnectors({ root } = {}) {
|
|
|
137
177
|
}
|
|
138
178
|
};
|
|
139
179
|
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
180
|
+
const warnedInvalidOptions = new WeakMap();
|
|
181
|
+
const endpointInHost = (el) => host.nodeType === 9 || host.contains(el);
|
|
182
|
+
const endpointStillInHost = (el) => host.contains?.(el) ?? endpointInHost(el);
|
|
183
|
+
const cacheEndpoint = (endpoints, el) => {
|
|
184
|
+
if (el?.id && !endpoints.has(el.id) && endpointInHost(el)) endpoints.set(el.id, el);
|
|
185
|
+
};
|
|
186
|
+
const endpointCache = () => {
|
|
187
|
+
const endpoints = new Map();
|
|
188
|
+
if (host.nodeType !== 9) cacheEndpoint(endpoints, host);
|
|
189
|
+
for (const el of host.querySelectorAll?.('[id]') || []) cacheEndpoint(endpoints, el);
|
|
190
|
+
return endpoints;
|
|
191
|
+
};
|
|
192
|
+
const endpointById = (endpoints, id) => {
|
|
193
|
+
if (!id) return null;
|
|
194
|
+
const el = endpoints.get(id) || null;
|
|
195
|
+
return el && endpointStillInHost(el) ? el : null;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const measureConnector = (endpoints, svg) => {
|
|
199
|
+
const from = endpointById(endpoints, svg.dataset.from);
|
|
200
|
+
const to = endpointById(endpoints, svg.dataset.to);
|
|
201
|
+
if (!from || !to) return { svg, skipped: true };
|
|
202
|
+
try {
|
|
146
203
|
const {
|
|
147
204
|
d,
|
|
148
205
|
to: end,
|
|
@@ -154,40 +211,90 @@ export function initConnectors({ root } = {}) {
|
|
|
154
211
|
fromSide: svg.dataset.fromSide || undefined,
|
|
155
212
|
toSide: svg.dataset.toSide || undefined,
|
|
156
213
|
});
|
|
214
|
+
return { svg, d, end, angle };
|
|
215
|
+
} catch {
|
|
216
|
+
return { svg, invalid: true };
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const draw = (endpoints, connectors) => {
|
|
221
|
+
const measurements = connectors.map((svg) => measureConnector(endpoints, svg));
|
|
222
|
+
for (const result of measurements) {
|
|
223
|
+
const { svg } = result;
|
|
224
|
+
if (result.skipped || result.invalid) {
|
|
225
|
+
clearConnectorParts(svg);
|
|
226
|
+
if (result.invalid) warnInvalidConnectorOptions(svg, warnedInvalidOptions);
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
157
229
|
const path = upsertConnectorPart(svg, '.ui-connector__path', 'ui-connector__path');
|
|
158
|
-
path.setAttribute('d', d);
|
|
230
|
+
path.setAttribute('d', result.d);
|
|
159
231
|
syncDrawPathLength(svg, path);
|
|
160
|
-
syncConnectorEnd(svg, end, angle);
|
|
232
|
+
syncConnectorEnd(svg, result.end, result.angle);
|
|
161
233
|
}
|
|
162
234
|
};
|
|
163
235
|
|
|
236
|
+
const observeEndpoint = (ro, el) => {
|
|
237
|
+
if (el && endpointStillInHost(el)) ro.observe(el);
|
|
238
|
+
};
|
|
239
|
+
|
|
164
240
|
return bindOnce(host, 'connectors', () => {
|
|
165
241
|
const connectors = collectHosts(host, '[data-bronto-connector]');
|
|
166
242
|
if (!connectors.length) return noop;
|
|
243
|
+
const endpoints = endpointCache();
|
|
167
244
|
const states = connectors.map((svg) => ({
|
|
168
245
|
svg,
|
|
169
246
|
path: snapshotPart(svg, '.ui-connector__path'),
|
|
170
247
|
end: snapshotPart(svg, '.ui-connector__end'),
|
|
171
248
|
}));
|
|
172
|
-
draw();
|
|
173
249
|
const view = host.defaultView || host.ownerDocument?.defaultView || null;
|
|
250
|
+
const raf =
|
|
251
|
+
view?.requestAnimationFrame?.bind(view) || globalThis.requestAnimationFrame?.bind(globalThis);
|
|
252
|
+
const caf =
|
|
253
|
+
view?.cancelAnimationFrame?.bind(view) || globalThis.cancelAnimationFrame?.bind(globalThis);
|
|
254
|
+
let frame = null;
|
|
255
|
+
let framePending = false;
|
|
256
|
+
let stopped = false;
|
|
257
|
+
|
|
258
|
+
const drawNow = () => {
|
|
259
|
+
if (!stopped) draw(endpoints, connectors);
|
|
260
|
+
};
|
|
261
|
+
const scheduleDraw = () => {
|
|
262
|
+
if (stopped || framePending) return;
|
|
263
|
+
if (!raf) {
|
|
264
|
+
drawNow();
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
framePending = true;
|
|
268
|
+
frame = raf(() => {
|
|
269
|
+
framePending = false;
|
|
270
|
+
frame = null;
|
|
271
|
+
drawNow();
|
|
272
|
+
});
|
|
273
|
+
};
|
|
274
|
+
const cancelScheduledDraw = () => {
|
|
275
|
+
if (framePending && caf) caf(frame);
|
|
276
|
+
framePending = false;
|
|
277
|
+
frame = null;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
drawNow();
|
|
174
281
|
const RO = view?.ResizeObserver;
|
|
175
|
-
const ro = RO ? new RO(
|
|
282
|
+
const ro = RO ? new RO(scheduleDraw) : null;
|
|
176
283
|
if (ro) {
|
|
177
284
|
for (const svg of connectors) {
|
|
178
285
|
if (svg.parentElement) ro.observe(svg.parentElement);
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (f) ro.observe(f);
|
|
182
|
-
if (t) ro.observe(t);
|
|
286
|
+
observeEndpoint(ro, endpointById(endpoints, svg.dataset.from));
|
|
287
|
+
observeEndpoint(ro, endpointById(endpoints, svg.dataset.to));
|
|
183
288
|
}
|
|
184
289
|
}
|
|
185
|
-
view?.addEventListener('resize',
|
|
186
|
-
view?.addEventListener('scroll',
|
|
290
|
+
view?.addEventListener('resize', scheduleDraw);
|
|
291
|
+
view?.addEventListener('scroll', scheduleDraw, true);
|
|
187
292
|
return () => {
|
|
293
|
+
stopped = true;
|
|
294
|
+
cancelScheduledDraw();
|
|
188
295
|
ro?.disconnect();
|
|
189
|
-
view?.removeEventListener('resize',
|
|
190
|
-
view?.removeEventListener('scroll',
|
|
296
|
+
view?.removeEventListener('resize', scheduleDraw);
|
|
297
|
+
view?.removeEventListener('scroll', scheduleDraw, true);
|
|
191
298
|
for (const state of states) {
|
|
192
299
|
restorePart(state.svg, '.ui-connector__path', state.path);
|
|
193
300
|
restorePart(state.svg, '.ui-connector__end', state.end);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crosshair.d.ts","sourceRoot":"","sources":["crosshair.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"crosshair.d.ts","sourceRoot":"","sources":["crosshair.js"],"names":[],"mappings":"AAIA;;;;;;GAMG;AAEH;;;;;;;;;;;;;;GAcG;AACH,yCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CA8J3C;;;;;OAjLa,MAAM;;;;OACN,MAAM;;;;QACN,MAAM;;;;QACN,MAAM"}
|
package/behaviors/crosshair.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { hasDom, resolveHost, noop, bindOnce, collectHosts } from './internal.js';
|
|
2
2
|
|
|
3
|
+
const rectRight = (rect) => rect.right ?? rect.left + rect.width;
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* @typedef {object} CrosshairMoveDetail
|
|
5
7
|
* @property {number} x Pointer x within the plot, in pixels.
|
|
@@ -34,6 +36,56 @@ export function initCrosshair({ root } = {}) {
|
|
|
34
36
|
for (const plot of plots) {
|
|
35
37
|
const overlay = plot.querySelector('.ui-crosshair');
|
|
36
38
|
let overlayState = null;
|
|
39
|
+
let geometry = null;
|
|
40
|
+
let active = overlay?.classList.contains('is-active') ?? false;
|
|
41
|
+
let writtenX = null;
|
|
42
|
+
let writtenY = null;
|
|
43
|
+
let writtenInline = overlay?.dataset.readoutInline ?? null;
|
|
44
|
+
let writtenBlock = overlay?.dataset.readoutBlock ?? null;
|
|
45
|
+
const view = plot.ownerDocument?.defaultView || null;
|
|
46
|
+
const invalidateGeometry = () => {
|
|
47
|
+
geometry = null;
|
|
48
|
+
};
|
|
49
|
+
const readGeometry = () => {
|
|
50
|
+
const overlayRect = overlay.getBoundingClientRect();
|
|
51
|
+
const r =
|
|
52
|
+
overlayRect.width && overlayRect.height ? overlayRect : plot.getBoundingClientRect();
|
|
53
|
+
if (!r.width || !r.height) return null;
|
|
54
|
+
geometry = {
|
|
55
|
+
left: r.left,
|
|
56
|
+
right: rectRight(r),
|
|
57
|
+
top: r.top,
|
|
58
|
+
width: r.width,
|
|
59
|
+
height: r.height,
|
|
60
|
+
rtl: view?.getComputedStyle?.(overlay).direction === 'rtl',
|
|
61
|
+
};
|
|
62
|
+
return geometry;
|
|
63
|
+
};
|
|
64
|
+
const currentGeometry = () => geometry || readGeometry();
|
|
65
|
+
const writeOverlay = (y, logicalX, inline, block) => {
|
|
66
|
+
const xValue = `${logicalX}px`;
|
|
67
|
+
const yValue = `${y}px`;
|
|
68
|
+
if (writtenX !== xValue) {
|
|
69
|
+
overlay.style.setProperty('--crosshair-x', xValue);
|
|
70
|
+
writtenX = xValue;
|
|
71
|
+
}
|
|
72
|
+
if (writtenY !== yValue) {
|
|
73
|
+
overlay.style.setProperty('--crosshair-y', yValue);
|
|
74
|
+
writtenY = yValue;
|
|
75
|
+
}
|
|
76
|
+
if (writtenInline !== inline) {
|
|
77
|
+
overlay.dataset.readoutInline = inline;
|
|
78
|
+
writtenInline = inline;
|
|
79
|
+
}
|
|
80
|
+
if (writtenBlock !== block) {
|
|
81
|
+
overlay.dataset.readoutBlock = block;
|
|
82
|
+
writtenBlock = block;
|
|
83
|
+
}
|
|
84
|
+
if (!active) {
|
|
85
|
+
overlay.classList.add('is-active');
|
|
86
|
+
active = true;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
37
89
|
const rememberOverlay = () => {
|
|
38
90
|
if (!overlay || overlayState) return;
|
|
39
91
|
overlayState = {
|
|
@@ -71,13 +123,19 @@ export function initCrosshair({ root } = {}) {
|
|
|
71
123
|
else overlay.style.removeProperty('--crosshair-y');
|
|
72
124
|
restoreData('data-readout-inline', overlayState.inline);
|
|
73
125
|
restoreData('data-readout-block', overlayState.block);
|
|
126
|
+
active = overlayState.active;
|
|
127
|
+
writtenX = overlayState.x.value || null;
|
|
128
|
+
writtenY = overlayState.y.value || null;
|
|
129
|
+
writtenInline = overlayState.inline.had ? overlayState.inline.value : null;
|
|
130
|
+
writtenBlock = overlayState.block.had ? overlayState.block.value : null;
|
|
131
|
+
invalidateGeometry();
|
|
74
132
|
overlayState = null;
|
|
75
133
|
};
|
|
76
134
|
const onMove = (e) => {
|
|
77
135
|
if (!overlay) return;
|
|
78
136
|
rememberOverlay();
|
|
79
|
-
const r =
|
|
80
|
-
if (!r
|
|
137
|
+
const r = currentGeometry();
|
|
138
|
+
if (!r) return;
|
|
81
139
|
const x = e.clientX - r.left;
|
|
82
140
|
const y = e.clientY - r.top;
|
|
83
141
|
// The CSS positions the vertical rule / readout with a *logical* inset
|
|
@@ -86,12 +144,12 @@ export function initCrosshair({ root } = {}) {
|
|
|
86
144
|
// Emitting the physical x instead made the RTL rule land off-plot. The
|
|
87
145
|
// public `detail.x`/`fx` stay physical-from-left so host scale-mapping
|
|
88
146
|
// keeps one stable coordinate space regardless of direction.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
147
|
+
writeOverlay(
|
|
148
|
+
y,
|
|
149
|
+
r.rtl ? r.right - e.clientX : x,
|
|
150
|
+
x / r.width > 0.5 ? 'before' : 'after',
|
|
151
|
+
y / r.height > 0.5 ? 'above' : 'below',
|
|
152
|
+
);
|
|
95
153
|
plot.dispatchEvent(
|
|
96
154
|
new CustomEvent('bronto:crosshair:move', {
|
|
97
155
|
bubbles: true,
|
|
@@ -102,6 +160,7 @@ export function initCrosshair({ root } = {}) {
|
|
|
102
160
|
const onLeave = () => {
|
|
103
161
|
if (!overlay) return;
|
|
104
162
|
overlay.classList.remove('is-active');
|
|
163
|
+
active = false;
|
|
105
164
|
plot.dispatchEvent(new CustomEvent('bronto:crosshair:leave', { bubbles: true }));
|
|
106
165
|
};
|
|
107
166
|
cleanups.push(
|
|
@@ -109,9 +168,13 @@ export function initCrosshair({ root } = {}) {
|
|
|
109
168
|
if (!overlay) return noop;
|
|
110
169
|
plot.addEventListener('pointermove', onMove);
|
|
111
170
|
plot.addEventListener('pointerleave', onLeave);
|
|
171
|
+
view?.addEventListener('resize', invalidateGeometry);
|
|
172
|
+
view?.addEventListener('scroll', invalidateGeometry, true);
|
|
112
173
|
return () => {
|
|
113
174
|
plot.removeEventListener('pointermove', onMove);
|
|
114
175
|
plot.removeEventListener('pointerleave', onLeave);
|
|
176
|
+
view?.removeEventListener('resize', invalidateGeometry);
|
|
177
|
+
view?.removeEventListener('scroll', invalidateGeometry, true);
|
|
115
178
|
restoreOverlay();
|
|
116
179
|
};
|
|
117
180
|
}),
|
|
@@ -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,CAgG3C"}
|
package/behaviors/dialog.js
CHANGED
|
@@ -22,7 +22,9 @@ export function initDialog({ root } = {}) {
|
|
|
22
22
|
if (!hasDom()) return noop;
|
|
23
23
|
const host = resolveHost(root);
|
|
24
24
|
if (!host) return noop;
|
|
25
|
-
const
|
|
25
|
+
const doc = host.nodeType === 9 ? host : host.ownerDocument;
|
|
26
|
+
if (!doc) return noop;
|
|
27
|
+
const managedDialogs = new Set();
|
|
26
28
|
const focusRestorers = new Map();
|
|
27
29
|
const canManageDialog = (dlg, origin) => host.contains(origin) || managedDialogs.has(dlg);
|
|
28
30
|
|
|
@@ -33,9 +35,11 @@ export function initDialog({ root } = {}) {
|
|
|
33
35
|
if (previous) {
|
|
34
36
|
dlg.removeEventListener('close', previous);
|
|
35
37
|
focusRestorers.delete(dlg);
|
|
38
|
+
managedDialogs.delete(dlg);
|
|
36
39
|
}
|
|
37
40
|
const restoreFocus = () => {
|
|
38
41
|
focusRestorers.delete(dlg);
|
|
42
|
+
managedDialogs.delete(dlg);
|
|
39
43
|
if (opener.isConnected && typeof opener.focus === 'function') opener.focus();
|
|
40
44
|
};
|
|
41
45
|
try {
|
|
@@ -86,13 +90,26 @@ export function initDialog({ root } = {}) {
|
|
|
86
90
|
if (lightDismiss(target)) e.preventDefault();
|
|
87
91
|
};
|
|
88
92
|
return bindOnce(host, 'dialog', () => {
|
|
89
|
-
|
|
93
|
+
doc.addEventListener('click', onClick);
|
|
90
94
|
return () => {
|
|
91
|
-
|
|
95
|
+
doc.removeEventListener('click', onClick);
|
|
92
96
|
for (const [dlg, restoreFocus] of focusRestorers) {
|
|
97
|
+
// Only pull focus back to the opener when it is currently INSIDE this
|
|
98
|
+
// dialog — closing would otherwise strand it on <body>. If the app has
|
|
99
|
+
// already moved focus elsewhere, leave it where it is.
|
|
100
|
+
const focusInside = dlg.contains(doc.activeElement);
|
|
93
101
|
dlg.removeEventListener('close', restoreFocus);
|
|
102
|
+
if (dlg.open) {
|
|
103
|
+
try {
|
|
104
|
+
dlg.close();
|
|
105
|
+
} catch {
|
|
106
|
+
/* already closed */
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (focusInside) restoreFocus();
|
|
94
110
|
}
|
|
95
111
|
focusRestorers.clear();
|
|
112
|
+
managedDialogs.clear();
|
|
96
113
|
};
|
|
97
114
|
});
|
|
98
115
|
}
|
|
@@ -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":"AA+BA;;;;;;;GAOG;AACH,0CAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAkD3C"}
|
package/behaviors/disclosure.js
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
hasDom,
|
|
3
|
+
resolveHost,
|
|
4
|
+
noop,
|
|
5
|
+
bindOnce,
|
|
6
|
+
byIdInHost,
|
|
7
|
+
closestSafe,
|
|
8
|
+
collectHosts,
|
|
9
|
+
} from './internal.js';
|
|
10
|
+
|
|
11
|
+
const handledDisclosureEvents = new WeakSet();
|
|
12
|
+
const warnedMissingTargets = new WeakSet();
|
|
2
13
|
|
|
3
14
|
const snapshotAttr = (el, name) => ({
|
|
4
15
|
had: el.hasAttribute(name),
|
|
@@ -10,6 +21,14 @@ const restoreAttr = (el, name, state) => {
|
|
|
10
21
|
else el.removeAttribute(name);
|
|
11
22
|
};
|
|
12
23
|
|
|
24
|
+
const warnMissingTarget = (trigger, id) => {
|
|
25
|
+
if (warnedMissingTargets.has(trigger) || typeof console === 'undefined') return;
|
|
26
|
+
warnedMissingTargets.add(trigger);
|
|
27
|
+
console.warn(
|
|
28
|
+
`[bronto] initDisclosure(): no panel found for aria-controls="${id || ''}" - disclosure trigger stays inert.`,
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
13
32
|
/**
|
|
14
33
|
* Disclosure: a `[data-bronto-disclosure]` trigger toggles the element
|
|
15
34
|
* referenced by its `aria-controls` id, keeping `aria-expanded` and the
|
|
@@ -35,16 +54,26 @@ export function initDisclosure({ root } = {}) {
|
|
|
35
54
|
};
|
|
36
55
|
|
|
37
56
|
const onClick = (e) => {
|
|
57
|
+
if (handledDisclosureEvents.has(e)) return;
|
|
38
58
|
const trigger = closestSafe(e.target, '[data-bronto-disclosure]');
|
|
39
59
|
if (!trigger || !host.contains(trigger)) return;
|
|
40
60
|
const id = trigger.getAttribute('aria-controls');
|
|
41
61
|
const panel = byIdInHost(host, id);
|
|
42
|
-
if (!panel)
|
|
62
|
+
if (!panel) {
|
|
63
|
+
warnMissingTarget(trigger, id);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
handledDisclosureEvents.add(e);
|
|
43
67
|
e.preventDefault();
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
68
|
+
const nextOpen = panel.hidden;
|
|
69
|
+
const triggers = collectHosts(host, '[data-bronto-disclosure]').filter(
|
|
70
|
+
(el) => el.getAttribute('aria-controls') === id,
|
|
71
|
+
);
|
|
72
|
+
for (const el of triggers) {
|
|
73
|
+
remember(el, panel);
|
|
74
|
+
el.setAttribute('aria-expanded', String(nextOpen));
|
|
75
|
+
}
|
|
76
|
+
panel.hidden = !nextOpen;
|
|
48
77
|
};
|
|
49
78
|
return bindOnce(host, 'disclosure', () => {
|
|
50
79
|
host.addEventListener('click', onClick);
|
package/behaviors/dismissible.js
CHANGED
|
@@ -15,7 +15,7 @@ export function dismissible({ root } = {}) {
|
|
|
15
15
|
const onClick = (e) => {
|
|
16
16
|
const btn = closestSafe(e.target, '[data-bronto-dismiss]');
|
|
17
17
|
if (!btn || !host.contains(btn)) return;
|
|
18
|
-
const sel = btn.getAttribute('data-bronto-dismiss');
|
|
18
|
+
const sel = btn.getAttribute('data-bronto-dismiss')?.trim();
|
|
19
19
|
const target = sel ? closestSafe(btn, sel) : closestSafe(btn, '[data-bronto-dismissible]');
|
|
20
20
|
if (!target) return;
|
|
21
21
|
e.preventDefault();
|
package/behaviors/forms.d.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {object} FormValidationOpts
|
|
3
|
+
* @property {Document | Element | null} [root]
|
|
4
|
+
* Event-delegation root; default: `document`.
|
|
5
|
+
* @property {string} [summaryTitle]
|
|
6
|
+
* Localized validation-summary title. A summary/form
|
|
7
|
+
* `data-bronto-error-summary-title` attribute overrides it, and an authored
|
|
8
|
+
* `.ui-error-summary__title` child is preserved.
|
|
9
|
+
*/
|
|
1
10
|
/**
|
|
2
11
|
* Accessible form validation glue for `<form data-bronto-validate>`.
|
|
3
12
|
* Progressive enhancement over the native Constraint Validation API —
|
|
@@ -20,8 +29,20 @@
|
|
|
20
29
|
* Pure enhancement: with JS off the form still submits and the browser
|
|
21
30
|
* validates natively. SSR-safe, idempotent; returns a cleanup function.
|
|
22
31
|
*
|
|
23
|
-
* @param {
|
|
32
|
+
* @param {FormValidationOpts} [opts]
|
|
24
33
|
* @returns {import('./internal.js').Cleanup}
|
|
25
34
|
*/
|
|
26
|
-
export function initFormValidation({ root }?:
|
|
35
|
+
export function initFormValidation({ root, summaryTitle }?: FormValidationOpts): import("./internal.js").Cleanup;
|
|
36
|
+
export type FormValidationOpts = {
|
|
37
|
+
/**
|
|
38
|
+
* Event-delegation root; default: `document`.
|
|
39
|
+
*/
|
|
40
|
+
root?: Document | Element | null | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Localized validation-summary title. A summary/form
|
|
43
|
+
* `data-bronto-error-summary-title` attribute overrides it, and an authored
|
|
44
|
+
* `.ui-error-summary__title` child is preserved.
|
|
45
|
+
*/
|
|
46
|
+
summaryTitle?: string | undefined;
|
|
47
|
+
};
|
|
27
48
|
//# sourceMappingURL=forms.d.ts.map
|
package/behaviors/forms.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"forms.d.ts","sourceRoot":"","sources":["forms.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"forms.d.ts","sourceRoot":"","sources":["forms.js"],"names":[],"mappings":"AA8QA;;;;;;;;GAQG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,4DAHW,kBAAkB,GAChB,OAAO,eAAe,EAAE,OAAO,CA4D3C"}
|
package/behaviors/forms.js
CHANGED
|
@@ -59,6 +59,7 @@ function rememberSummary(summary, state) {
|
|
|
59
59
|
attrs: snapshotAttrs(summary, ['role', 'tabindex']),
|
|
60
60
|
children: [...summary.childNodes],
|
|
61
61
|
hidden: summary.hidden,
|
|
62
|
+
title: summary.querySelector('.ui-error-summary__title')?.cloneNode(true) ?? null,
|
|
62
63
|
});
|
|
63
64
|
}
|
|
64
65
|
}
|
|
@@ -141,12 +142,60 @@ function controlsOf(form) {
|
|
|
141
142
|
);
|
|
142
143
|
}
|
|
143
144
|
|
|
145
|
+
function textOf(el) {
|
|
146
|
+
return el?.textContent?.replace(/\s+/g, ' ').trim() || '';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function fieldsetLegend(control, field) {
|
|
150
|
+
return (
|
|
151
|
+
textOf(field?.querySelector('legend')) ||
|
|
152
|
+
textOf(control.closest('fieldset')?.querySelector('legend'))
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function labelledbyText(control) {
|
|
157
|
+
const doc = control.ownerDocument || document;
|
|
158
|
+
return (control.getAttribute('aria-labelledby') || '')
|
|
159
|
+
.split(/\s+/)
|
|
160
|
+
.map((id) => textOf(doc.getElementById(id)))
|
|
161
|
+
.filter(Boolean)
|
|
162
|
+
.join(' ');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function labelsText(control) {
|
|
166
|
+
return control.labels
|
|
167
|
+
? [...control.labels]
|
|
168
|
+
.map((label) => textOf(label))
|
|
169
|
+
.filter(Boolean)
|
|
170
|
+
.join(' ')
|
|
171
|
+
: '';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function controlLabel(control) {
|
|
175
|
+
const field = control.closest('.ui-field');
|
|
176
|
+
// A radio's accessible name is its group legend when present.
|
|
177
|
+
if (control.type === 'radio') {
|
|
178
|
+
const groupLegend = fieldsetLegend(control, field);
|
|
179
|
+
if (groupLegend) return groupLegend;
|
|
180
|
+
}
|
|
181
|
+
return (
|
|
182
|
+
labelledbyText(control) ||
|
|
183
|
+
control.getAttribute('aria-label')?.trim() ||
|
|
184
|
+
labelsText(control) ||
|
|
185
|
+
textOf(field?.querySelector('label, .ui-label')) ||
|
|
186
|
+
fieldsetLegend(control, field) ||
|
|
187
|
+
(control.getAttribute('name') || control.name || '').trim()
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
144
191
|
function summaryItem(control) {
|
|
145
192
|
const id = ensureId(control, 'bronto-field');
|
|
146
193
|
const li = document.createElement('li');
|
|
147
194
|
const a = document.createElement('a');
|
|
195
|
+
const label = controlLabel(control);
|
|
196
|
+
const message = control.validationMessage;
|
|
148
197
|
a.href = `#${id}`;
|
|
149
|
-
a.textContent =
|
|
198
|
+
a.textContent = label ? `${label}: ${message}` : message;
|
|
150
199
|
a.addEventListener('click', (e) => {
|
|
151
200
|
e.preventDefault();
|
|
152
201
|
control.focus();
|
|
@@ -155,7 +204,37 @@ function summaryItem(control) {
|
|
|
155
204
|
return li;
|
|
156
205
|
}
|
|
157
206
|
|
|
158
|
-
function
|
|
207
|
+
function summaryControls(form, invalid) {
|
|
208
|
+
const radioNames = new Set();
|
|
209
|
+
const controls = [];
|
|
210
|
+
for (const control of invalid) {
|
|
211
|
+
if (control.form === form && control.type === 'radio' && control.name) {
|
|
212
|
+
if (radioNames.has(control.name)) continue;
|
|
213
|
+
radioNames.add(control.name);
|
|
214
|
+
}
|
|
215
|
+
controls.push(control);
|
|
216
|
+
}
|
|
217
|
+
return controls;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function nonEmpty(value) {
|
|
221
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function summaryTitleNode(form, summary, state, fallbackTitle) {
|
|
225
|
+
const authored = state.summaryState.get(summary)?.title;
|
|
226
|
+
if (authored) return authored.cloneNode(true);
|
|
227
|
+
const title = document.createElement('p');
|
|
228
|
+
title.className = 'ui-error-summary__title';
|
|
229
|
+
title.textContent =
|
|
230
|
+
nonEmpty(summary.getAttribute('data-bronto-error-summary-title')) ||
|
|
231
|
+
nonEmpty(form.getAttribute('data-bronto-error-summary-title')) ||
|
|
232
|
+
nonEmpty(fallbackTitle) ||
|
|
233
|
+
'There is a problem';
|
|
234
|
+
return title;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function refreshSummary(form, invalid, state, summaryTitle) {
|
|
159
238
|
const summary = form.querySelector('[data-bronto-error-summary]');
|
|
160
239
|
if (!summary) return;
|
|
161
240
|
rememberSummary(summary, state);
|
|
@@ -164,12 +243,10 @@ function refreshSummary(form, invalid, state) {
|
|
|
164
243
|
summary.replaceChildren();
|
|
165
244
|
return;
|
|
166
245
|
}
|
|
167
|
-
const title =
|
|
168
|
-
title.className = 'ui-error-summary__title';
|
|
169
|
-
title.textContent = 'There is a problem';
|
|
246
|
+
const title = summaryTitleNode(form, summary, state, summaryTitle);
|
|
170
247
|
const list = document.createElement('ul');
|
|
171
248
|
list.className = 'ui-error-summary__list';
|
|
172
|
-
list.append(...invalid.map(summaryItem));
|
|
249
|
+
list.append(...summaryControls(form, invalid).map(summaryItem));
|
|
173
250
|
summary.replaceChildren(title, list);
|
|
174
251
|
summary.setAttribute('role', 'alert');
|
|
175
252
|
summary.tabIndex = -1;
|
|
@@ -191,6 +268,16 @@ function restoreValidationState(state) {
|
|
|
191
268
|
for (const [control, attrs] of state.controlState) restoreAttrs(control, attrs);
|
|
192
269
|
}
|
|
193
270
|
|
|
271
|
+
/**
|
|
272
|
+
* @typedef {object} FormValidationOpts
|
|
273
|
+
* @property {Document | Element | null} [root]
|
|
274
|
+
* Event-delegation root; default: `document`.
|
|
275
|
+
* @property {string} [summaryTitle]
|
|
276
|
+
* Localized validation-summary title. A summary/form
|
|
277
|
+
* `data-bronto-error-summary-title` attribute overrides it, and an authored
|
|
278
|
+
* `.ui-error-summary__title` child is preserved.
|
|
279
|
+
*/
|
|
280
|
+
|
|
194
281
|
/**
|
|
195
282
|
* Accessible form validation glue for `<form data-bronto-validate>`.
|
|
196
283
|
* Progressive enhancement over the native Constraint Validation API —
|
|
@@ -213,10 +300,10 @@ function restoreValidationState(state) {
|
|
|
213
300
|
* Pure enhancement: with JS off the form still submits and the browser
|
|
214
301
|
* validates natively. SSR-safe, idempotent; returns a cleanup function.
|
|
215
302
|
*
|
|
216
|
-
* @param {
|
|
303
|
+
* @param {FormValidationOpts} [opts]
|
|
217
304
|
* @returns {import('./internal.js').Cleanup}
|
|
218
305
|
*/
|
|
219
|
-
export function initFormValidation({ root } = {}) {
|
|
306
|
+
export function initFormValidation({ root, summaryTitle } = {}) {
|
|
220
307
|
if (!hasDom()) return noop;
|
|
221
308
|
const host = resolveHost(root);
|
|
222
309
|
if (!host) return noop;
|
|
@@ -227,7 +314,7 @@ export function initFormValidation({ root } = {}) {
|
|
|
227
314
|
if (!form) return;
|
|
228
315
|
suppressNativeValidation(form, state);
|
|
229
316
|
const invalid = controlsOf(form).filter((control) => !validateField(control, state));
|
|
230
|
-
refreshSummary(form, invalid, state);
|
|
317
|
+
refreshSummary(form, invalid, state, summaryTitle);
|
|
231
318
|
if (invalid.length) {
|
|
232
319
|
event.preventDefault();
|
|
233
320
|
const summary = form.querySelector('[data-bronto-error-summary]');
|
|
@@ -248,6 +335,7 @@ export function initFormValidation({ root } = {}) {
|
|
|
248
335
|
form,
|
|
249
336
|
controlsOf(form).filter((candidate) => !candidate.validity.valid),
|
|
250
337
|
state,
|
|
338
|
+
summaryTitle,
|
|
251
339
|
);
|
|
252
340
|
}
|
|
253
341
|
};
|
package/behaviors/glyph.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"glyph.d.ts","sourceRoot":"","sources":["glyph.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"glyph.d.ts","sourceRoot":"","sources":["glyph.js"],"names":[],"mappings":"AA2OA;;;;;;;;;;;;;;;;;;GAkBG;AACH,wCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAwB3C"}
|