@ponchia/ui 0.6.8 → 0.6.10
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 +79 -4
- package/README.md +2 -2
- package/annotations/index.d.ts.map +1 -1
- package/annotations/index.js +5 -6
- package/behaviors/carousel.d.ts.map +1 -1
- package/behaviors/carousel.js +100 -60
- package/behaviors/combobox.d.ts.map +1 -1
- package/behaviors/combobox.js +167 -113
- package/behaviors/connectors.d.ts.map +1 -1
- package/behaviors/connectors.js +39 -23
- package/behaviors/forms.d.ts.map +1 -1
- package/behaviors/forms.js +211 -207
- package/behaviors/glyph.d.ts.map +1 -1
- package/behaviors/glyph.js +157 -132
- package/behaviors/inert.d.ts +1 -1
- package/behaviors/inert.d.ts.map +1 -1
- package/behaviors/inert.js +1 -1
- package/behaviors/internal.js +2 -2
- package/behaviors/modal.js +1 -1
- package/behaviors/popover.js +5 -5
- package/behaviors/table.d.ts +1 -1
- package/behaviors/table.d.ts.map +1 -1
- package/behaviors/table.js +7 -8
- package/behaviors/tabs.js +2 -2
- package/behaviors/toast.js +5 -5
- package/classes/classes.json +33 -2
- package/classes/index.d.ts +10 -0
- package/classes/index.js +59 -35
- package/connectors/index.d.ts +2 -2
- package/connectors/index.d.ts.map +1 -1
- package/connectors/index.js +7 -10
- package/css/app.css +3 -4
- package/css/base.css +1 -1
- package/css/content.css +3 -3
- package/css/disclosure.css +3 -3
- package/css/dots.css +4 -4
- package/css/feedback.css +6 -7
- package/css/forms.css +9 -12
- package/css/legend.css +1 -1
- package/css/marks.css +1 -1
- package/css/motion.css +6 -6
- package/css/overlay.css +5 -7
- package/css/primitives.css +14 -16
- package/css/sidenote.css +2 -2
- package/css/table.css +2 -2
- package/css/workbench.css +128 -0
- package/dist/css/workbench.css +1 -1
- package/docs/annotations.md +36 -0
- package/docs/architecture.md +28 -0
- package/docs/interop/react-flow.md +89 -0
- package/docs/package-contract.md +2 -0
- package/docs/reference.md +21 -1
- package/docs/reporting.md +8 -8
- package/docs/stability.md +67 -7
- package/docs/workbench.md +56 -9
- package/glyphs/glyphs.js +43 -33
- package/llms.txt +10 -4
- package/package.json +5 -2
- package/schemas/report-claims.v1.schema.json +1 -1
- package/tokens/index.js +2 -2
package/behaviors/glyph.js
CHANGED
|
@@ -8,6 +8,16 @@ function restoreAttr(el, name, prev) {
|
|
|
8
8
|
else el.setAttribute(name, prev);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
function restoreStyleProp(el, name, prev) {
|
|
12
|
+
if (prev) el.style.setProperty(name, prev);
|
|
13
|
+
else el.style.removeProperty(name);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function cleanupEmptyClassAndStyle(el) {
|
|
17
|
+
if (el.getAttribute('class') === '') el.removeAttribute('class');
|
|
18
|
+
if (el.getAttribute('style') === '') el.removeAttribute('style');
|
|
19
|
+
}
|
|
20
|
+
|
|
11
21
|
function rememberCleanup(el, cleanups, cleanup) {
|
|
12
22
|
let done = false;
|
|
13
23
|
const wrapped = () => {
|
|
@@ -27,6 +37,151 @@ function cssLen(v) {
|
|
|
27
37
|
return v && /^[\w.%+\-*/()\s,]+$/.test(v) ? v : '';
|
|
28
38
|
}
|
|
29
39
|
|
|
40
|
+
function applyGlyphA11y(el, label) {
|
|
41
|
+
if (label) {
|
|
42
|
+
el.setAttribute('role', 'img');
|
|
43
|
+
el.setAttribute('aria-label', label);
|
|
44
|
+
el.removeAttribute('aria-hidden');
|
|
45
|
+
} else {
|
|
46
|
+
el.setAttribute('aria-hidden', 'true');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function expandMaskGlyph(el, name, label, cleanups) {
|
|
51
|
+
if (el.classList.contains('ui-icon') && el.style.getPropertyValue('--icon-mask')) return;
|
|
52
|
+
const mask = glyphMask(name);
|
|
53
|
+
if (!mask) return; // unknown glyph — leave the placeholder as-is
|
|
54
|
+
|
|
55
|
+
const hadIcon = el.classList.contains('ui-icon');
|
|
56
|
+
const hadMask = el.style.getPropertyValue('--icon-mask');
|
|
57
|
+
const hadSize = el.style.getPropertyValue('--icon-size');
|
|
58
|
+
const hadAriaHidden = el.getAttribute('aria-hidden');
|
|
59
|
+
const hadRole = el.getAttribute('role');
|
|
60
|
+
const hadAriaLabel = el.getAttribute('aria-label');
|
|
61
|
+
const size = cssLen(el.getAttribute('data-bronto-glyph-size'));
|
|
62
|
+
|
|
63
|
+
el.classList.add('ui-icon');
|
|
64
|
+
el.style.setProperty('--icon-mask', mask);
|
|
65
|
+
if (size) el.style.setProperty('--icon-size', size);
|
|
66
|
+
applyGlyphA11y(el, label);
|
|
67
|
+
|
|
68
|
+
rememberCleanup(el, cleanups, () => {
|
|
69
|
+
if (!hadIcon) el.classList.remove('ui-icon');
|
|
70
|
+
restoreStyleProp(el, '--icon-mask', hadMask);
|
|
71
|
+
restoreStyleProp(el, '--icon-size', hadSize);
|
|
72
|
+
restoreAttr(el, 'aria-hidden', hadAriaHidden);
|
|
73
|
+
restoreAttr(el, 'role', hadRole);
|
|
74
|
+
restoreAttr(el, 'aria-label', hadAriaLabel);
|
|
75
|
+
cleanupEmptyClassAndStyle(el);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function glyphAnimClass(animAttr) {
|
|
80
|
+
if (animAttr === 'reveal') return 'ui-dotmatrix--reveal';
|
|
81
|
+
if (animAttr === 'pulse') return 'ui-dotmatrix--pulse';
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function authoredDotSize(el) {
|
|
86
|
+
return (
|
|
87
|
+
el.style.getPropertyValue('--dotmatrix-dot') ||
|
|
88
|
+
(typeof getComputedStyle === 'function'
|
|
89
|
+
? getComputedStyle(el).getPropertyValue('--dotmatrix-dot').trim()
|
|
90
|
+
: '')
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function applyDefaultDotScale(el, solid, hadGap) {
|
|
95
|
+
const setDefaultDot = !authoredDotSize(el);
|
|
96
|
+
let setDefaultGap = false;
|
|
97
|
+
if (setDefaultDot) {
|
|
98
|
+
el.style.setProperty('--dotmatrix-dot', '0.08em');
|
|
99
|
+
if (!solid && !hadGap) {
|
|
100
|
+
el.style.setProperty('--dotmatrix-gap', '0.02em'); // tight, so it reads as one glyph
|
|
101
|
+
setDefaultGap = true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return { setDefaultDot, setDefaultGap };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function dotCellClass(cell) {
|
|
108
|
+
if (!cell.on) return 'ui-dotmatrix__cell';
|
|
109
|
+
if (cell.tone === 'hot') return 'ui-dotmatrix__cell ui-dotmatrix__cell--hot';
|
|
110
|
+
if (cell.tone === 'accent') return 'ui-dotmatrix__cell ui-dotmatrix__cell--accent';
|
|
111
|
+
return 'ui-dotmatrix__cell';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function appendGlyphCells(el, cells, { solid, animAttr }) {
|
|
115
|
+
const frag = document.createDocumentFragment();
|
|
116
|
+
cells.forEach((cell, i) => {
|
|
117
|
+
const span = document.createElement('span');
|
|
118
|
+
span.className = dotCellClass(cell);
|
|
119
|
+
if (!cell.on && solid) span.style.background = 'transparent'; // glyph-only
|
|
120
|
+
if (animAttr === 'reveal') span.style.setProperty('--i', String(i)); // scan stagger
|
|
121
|
+
frag.appendChild(span);
|
|
122
|
+
});
|
|
123
|
+
el.appendChild(frag);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function expandCellGlyph(el, name, label, cleanups) {
|
|
127
|
+
// Scope to DIRECT-child cells (the ones we append) — so a placeholder that
|
|
128
|
+
// legitimately nests its own .ui-dotmatrix is neither mis-read as already
|
|
129
|
+
// expanded here nor have its inner cells removed by cleanup below.
|
|
130
|
+
if (el.querySelector(':scope > .ui-dotmatrix__cell')) return; // already expanded
|
|
131
|
+
const cells = glyphCells(name);
|
|
132
|
+
if (!cells.length) return; // unknown glyph — leave the placeholder as-is
|
|
133
|
+
|
|
134
|
+
// `data-bronto-glyph-solid` → square, gapless pixel glyph (legible small),
|
|
135
|
+
// the DOM counterpart to renderGlyph's `solid` option. Implies glyph-only.
|
|
136
|
+
const solid = el.hasAttribute('data-bronto-glyph-solid');
|
|
137
|
+
// `data-bronto-glyph-anim="reveal|pulse"` → decorative animation (the DOM
|
|
138
|
+
// counterpart to renderGlyph's `anim`; reduced-motion-safe via CSS).
|
|
139
|
+
const animAttr = el.getAttribute('data-bronto-glyph-anim');
|
|
140
|
+
const animClass = glyphAnimClass(animAttr);
|
|
141
|
+
const hadAnimClass = animClass ? el.classList.contains(animClass) : false;
|
|
142
|
+
const hadMatrix = el.classList.contains('ui-dotmatrix');
|
|
143
|
+
const hadCols = el.style.getPropertyValue('--dotmatrix-cols');
|
|
144
|
+
const hadRadius = el.style.getPropertyValue('--dotmatrix-dot-radius');
|
|
145
|
+
const hadGap = el.style.getPropertyValue('--dotmatrix-gap');
|
|
146
|
+
const hadAriaHidden = el.getAttribute('aria-hidden');
|
|
147
|
+
const hadRole = el.getAttribute('role');
|
|
148
|
+
const hadAriaLabel = el.getAttribute('aria-label');
|
|
149
|
+
|
|
150
|
+
el.classList.add('ui-dotmatrix');
|
|
151
|
+
if (animClass) el.classList.add(animClass);
|
|
152
|
+
el.style.setProperty('--dotmatrix-cols', String(GLYPH_SIZE));
|
|
153
|
+
if (solid) {
|
|
154
|
+
el.style.setProperty('--dotmatrix-dot-radius', '0');
|
|
155
|
+
el.style.setProperty('--dotmatrix-gap', '0');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Without a track size the grid cells default to `1fr`, so the 16×16 matrix
|
|
159
|
+
// balloons to fill its container (full-bleed) — asymmetric with the mask
|
|
160
|
+
// path's safe 1em. If the author set no `--dotmatrix-dot` (inline OR via the
|
|
161
|
+
// cascade), default it to an intrinsic icon scale so a forgotten size
|
|
162
|
+
// degrades to ~icon, not full-bleed.
|
|
163
|
+
const defaults = applyDefaultDotScale(el, solid, hadGap);
|
|
164
|
+
applyGlyphA11y(el, label);
|
|
165
|
+
appendGlyphCells(el, cells, { solid, animAttr });
|
|
166
|
+
|
|
167
|
+
rememberCleanup(el, cleanups, () => {
|
|
168
|
+
el.querySelectorAll(':scope > .ui-dotmatrix__cell').forEach((n) => n.remove());
|
|
169
|
+
if (!hadMatrix) el.classList.remove('ui-dotmatrix');
|
|
170
|
+
if (animClass && !hadAnimClass) el.classList.remove(animClass);
|
|
171
|
+
if (solid) {
|
|
172
|
+
restoreStyleProp(el, '--dotmatrix-dot-radius', hadRadius);
|
|
173
|
+
restoreStyleProp(el, '--dotmatrix-gap', hadGap);
|
|
174
|
+
}
|
|
175
|
+
if (defaults.setDefaultDot) el.style.removeProperty('--dotmatrix-dot');
|
|
176
|
+
if (defaults.setDefaultGap) el.style.removeProperty('--dotmatrix-gap');
|
|
177
|
+
restoreStyleProp(el, '--dotmatrix-cols', hadCols);
|
|
178
|
+
restoreAttr(el, 'aria-hidden', hadAriaHidden);
|
|
179
|
+
restoreAttr(el, 'role', hadRole);
|
|
180
|
+
restoreAttr(el, 'aria-label', hadAriaLabel);
|
|
181
|
+
cleanupEmptyClassAndStyle(el);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
30
185
|
/**
|
|
31
186
|
* Expand `[data-bronto-glyph="name"]` placeholders into a `.ui-dotmatrix`
|
|
32
187
|
* grid of GLYPH_SIZE² cells — the DOM counterpart to renderGlyph() from
|
|
@@ -60,141 +215,11 @@ export function initDotGlyph({ root } = {}) {
|
|
|
60
215
|
|
|
61
216
|
// One-node mask path — the icon-at-scale counterpart to the 256-cell grid.
|
|
62
217
|
if (el.getAttribute('data-bronto-glyph-render') === 'mask') {
|
|
63
|
-
|
|
64
|
-
const mask = glyphMask(name);
|
|
65
|
-
if (!mask) continue; // unknown glyph — leave the placeholder as-is
|
|
66
|
-
const hadIcon = el.classList.contains('ui-icon');
|
|
67
|
-
const hadMask = el.style.getPropertyValue('--icon-mask');
|
|
68
|
-
const hadSize = el.style.getPropertyValue('--icon-size');
|
|
69
|
-
const hadAriaHiddenM = el.getAttribute('aria-hidden');
|
|
70
|
-
const hadRoleM = el.getAttribute('role');
|
|
71
|
-
const hadAriaLabelM = el.getAttribute('aria-label');
|
|
72
|
-
const sizeM = cssLen(el.getAttribute('data-bronto-glyph-size'));
|
|
73
|
-
|
|
74
|
-
el.classList.add('ui-icon');
|
|
75
|
-
el.style.setProperty('--icon-mask', mask);
|
|
76
|
-
if (sizeM) el.style.setProperty('--icon-size', sizeM);
|
|
77
|
-
if (label) {
|
|
78
|
-
el.setAttribute('role', 'img');
|
|
79
|
-
el.setAttribute('aria-label', label);
|
|
80
|
-
el.removeAttribute('aria-hidden');
|
|
81
|
-
} else {
|
|
82
|
-
el.setAttribute('aria-hidden', 'true');
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
rememberCleanup(el, cleanups, () => {
|
|
86
|
-
if (!hadIcon) el.classList.remove('ui-icon');
|
|
87
|
-
if (hadMask) el.style.setProperty('--icon-mask', hadMask);
|
|
88
|
-
else el.style.removeProperty('--icon-mask');
|
|
89
|
-
if (sizeM && !hadSize) el.style.removeProperty('--icon-size');
|
|
90
|
-
else if (hadSize) el.style.setProperty('--icon-size', hadSize);
|
|
91
|
-
restoreAttr(el, 'aria-hidden', hadAriaHiddenM);
|
|
92
|
-
restoreAttr(el, 'role', hadRoleM);
|
|
93
|
-
restoreAttr(el, 'aria-label', hadAriaLabelM);
|
|
94
|
-
if (el.getAttribute('class') === '') el.removeAttribute('class');
|
|
95
|
-
if (el.getAttribute('style') === '') el.removeAttribute('style');
|
|
96
|
-
});
|
|
218
|
+
expandMaskGlyph(el, name, label, cleanups);
|
|
97
219
|
continue;
|
|
98
220
|
}
|
|
99
221
|
|
|
100
|
-
|
|
101
|
-
// legitimately nests its own .ui-dotmatrix is neither mis-read as already
|
|
102
|
-
// expanded here nor have its inner cells removed by cleanup below.
|
|
103
|
-
if (el.querySelector(':scope > .ui-dotmatrix__cell')) continue; // already expanded
|
|
104
|
-
const cells = glyphCells(name);
|
|
105
|
-
if (!cells.length) continue; // unknown glyph — leave the placeholder as-is
|
|
106
|
-
// `data-bronto-glyph-solid` → square, gapless pixel glyph (legible small),
|
|
107
|
-
// the DOM counterpart to renderGlyph's `solid` option. Implies glyph-only.
|
|
108
|
-
const solid = el.hasAttribute('data-bronto-glyph-solid');
|
|
109
|
-
// `data-bronto-glyph-anim="reveal|pulse"` → decorative animation (the DOM
|
|
110
|
-
// counterpart to renderGlyph's `anim`; reduced-motion-safe via CSS).
|
|
111
|
-
const animAttr = el.getAttribute('data-bronto-glyph-anim');
|
|
112
|
-
const animClass =
|
|
113
|
-
animAttr === 'reveal'
|
|
114
|
-
? 'ui-dotmatrix--reveal'
|
|
115
|
-
: animAttr === 'pulse'
|
|
116
|
-
? 'ui-dotmatrix--pulse'
|
|
117
|
-
: null;
|
|
118
|
-
const hadAnimClass = animClass ? el.classList.contains(animClass) : false;
|
|
119
|
-
const hadMatrix = el.classList.contains('ui-dotmatrix');
|
|
120
|
-
const hadCols = el.style.getPropertyValue('--dotmatrix-cols');
|
|
121
|
-
const hadRadius = el.style.getPropertyValue('--dotmatrix-dot-radius');
|
|
122
|
-
const hadGap = el.style.getPropertyValue('--dotmatrix-gap');
|
|
123
|
-
const hadAriaHidden = el.getAttribute('aria-hidden');
|
|
124
|
-
const hadRole = el.getAttribute('role');
|
|
125
|
-
const hadAriaLabel = el.getAttribute('aria-label');
|
|
126
|
-
|
|
127
|
-
el.classList.add('ui-dotmatrix');
|
|
128
|
-
if (animClass) el.classList.add(animClass);
|
|
129
|
-
el.style.setProperty('--dotmatrix-cols', String(GLYPH_SIZE));
|
|
130
|
-
if (solid) {
|
|
131
|
-
el.style.setProperty('--dotmatrix-dot-radius', '0');
|
|
132
|
-
el.style.setProperty('--dotmatrix-gap', '0');
|
|
133
|
-
}
|
|
134
|
-
// Without a track size the grid cells default to `1fr`, so the 16×16 matrix
|
|
135
|
-
// balloons to fill its container (full-bleed) — asymmetric with the mask
|
|
136
|
-
// path's safe 1em. If the author set no `--dotmatrix-dot` (inline OR via the
|
|
137
|
-
// cascade), default it to an intrinsic icon scale so a forgotten size
|
|
138
|
-
// degrades to ~icon, not full-bleed. (component audit C9.)
|
|
139
|
-
const authoredDot =
|
|
140
|
-
el.style.getPropertyValue('--dotmatrix-dot') ||
|
|
141
|
-
(typeof getComputedStyle === 'function'
|
|
142
|
-
? getComputedStyle(el).getPropertyValue('--dotmatrix-dot').trim()
|
|
143
|
-
: '');
|
|
144
|
-
const setDefaultDot = !authoredDot;
|
|
145
|
-
let setDefaultGap = false;
|
|
146
|
-
if (setDefaultDot) {
|
|
147
|
-
el.style.setProperty('--dotmatrix-dot', '0.08em');
|
|
148
|
-
if (!solid && !hadGap) {
|
|
149
|
-
el.style.setProperty('--dotmatrix-gap', '0.02em'); // tight, so it reads as one glyph
|
|
150
|
-
setDefaultGap = true;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
if (label) {
|
|
154
|
-
el.setAttribute('role', 'img');
|
|
155
|
-
el.setAttribute('aria-label', label);
|
|
156
|
-
el.removeAttribute('aria-hidden'); // a labelled img must not also be hidden
|
|
157
|
-
} else {
|
|
158
|
-
el.setAttribute('aria-hidden', 'true');
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const frag = document.createDocumentFragment();
|
|
162
|
-
cells.forEach((c, i) => {
|
|
163
|
-
const span = document.createElement('span');
|
|
164
|
-
span.className = !c.on
|
|
165
|
-
? 'ui-dotmatrix__cell'
|
|
166
|
-
: c.tone === 'hot'
|
|
167
|
-
? 'ui-dotmatrix__cell ui-dotmatrix__cell--hot'
|
|
168
|
-
: c.tone === 'accent'
|
|
169
|
-
? 'ui-dotmatrix__cell ui-dotmatrix__cell--accent'
|
|
170
|
-
: 'ui-dotmatrix__cell';
|
|
171
|
-
if (!c.on && solid) span.style.background = 'transparent'; // glyph-only
|
|
172
|
-
if (animAttr === 'reveal') span.style.setProperty('--i', String(i)); // scan stagger
|
|
173
|
-
frag.appendChild(span);
|
|
174
|
-
});
|
|
175
|
-
el.appendChild(frag);
|
|
176
|
-
|
|
177
|
-
rememberCleanup(el, cleanups, () => {
|
|
178
|
-
el.querySelectorAll(':scope > .ui-dotmatrix__cell').forEach((n) => n.remove());
|
|
179
|
-
if (!hadMatrix) el.classList.remove('ui-dotmatrix');
|
|
180
|
-
if (animClass && !hadAnimClass) el.classList.remove(animClass);
|
|
181
|
-
if (solid) {
|
|
182
|
-
if (hadRadius) el.style.setProperty('--dotmatrix-dot-radius', hadRadius);
|
|
183
|
-
else el.style.removeProperty('--dotmatrix-dot-radius');
|
|
184
|
-
if (hadGap) el.style.setProperty('--dotmatrix-gap', hadGap);
|
|
185
|
-
else el.style.removeProperty('--dotmatrix-gap');
|
|
186
|
-
}
|
|
187
|
-
if (setDefaultDot) el.style.removeProperty('--dotmatrix-dot');
|
|
188
|
-
if (setDefaultGap) el.style.removeProperty('--dotmatrix-gap');
|
|
189
|
-
if (hadCols) el.style.setProperty('--dotmatrix-cols', hadCols);
|
|
190
|
-
else el.style.removeProperty('--dotmatrix-cols');
|
|
191
|
-
restoreAttr(el, 'aria-hidden', hadAriaHidden);
|
|
192
|
-
restoreAttr(el, 'role', hadRole);
|
|
193
|
-
restoreAttr(el, 'aria-label', hadAriaLabel);
|
|
194
|
-
// Don't leave behind empty class=""/style="" we ourselves created.
|
|
195
|
-
if (el.getAttribute('class') === '') el.removeAttribute('class');
|
|
196
|
-
if (el.getAttribute('style') === '') el.removeAttribute('style');
|
|
197
|
-
});
|
|
222
|
+
expandCellGlyph(el, name, label, cleanups);
|
|
198
223
|
}
|
|
199
224
|
|
|
200
225
|
return () => cleanups.forEach((fn) => fn());
|
package/behaviors/inert.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Wire once near the root, like {@link applyStoredTheme}. Capturing listeners
|
|
13
13
|
* intercept activation before any component handler (tabs, pagination, menus)
|
|
14
|
-
* sees it.
|
|
14
|
+
* sees it.
|
|
15
15
|
*
|
|
16
16
|
* @param {import('./internal.js').DelegateOpts} [opts]
|
|
17
17
|
* @returns {import('./internal.js').Cleanup}
|
package/behaviors/inert.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inert.d.ts","sourceRoot":"","sources":["inert.js"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;GAiBG;AACH,6CAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"inert.d.ts","sourceRoot":"","sources":["inert.js"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;GAiBG;AACH,6CAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CA0B3C"}
|
package/behaviors/inert.js
CHANGED
|
@@ -15,7 +15,7 @@ const DISABLED = '[aria-disabled="true"]';
|
|
|
15
15
|
*
|
|
16
16
|
* Wire once near the root, like {@link applyStoredTheme}. Capturing listeners
|
|
17
17
|
* intercept activation before any component handler (tabs, pagination, menus)
|
|
18
|
-
* sees it.
|
|
18
|
+
* sees it.
|
|
19
19
|
*
|
|
20
20
|
* @param {import('./internal.js').DelegateOpts} [opts]
|
|
21
21
|
* @returns {import('./internal.js').Cleanup}
|
package/behaviors/internal.js
CHANGED
|
@@ -121,7 +121,7 @@ export function scrollIntoViewSafe(el, opts = { block: 'nearest' }) {
|
|
|
121
121
|
// by the modal and popover focus paths (a dialog/modal must move focus into
|
|
122
122
|
// itself on open). Focus the first focusable descendant, else make the
|
|
123
123
|
// container programmatically focusable and focus it, so a content-only
|
|
124
|
-
// panel/modal still receives focus.
|
|
124
|
+
// panel/modal still receives focus.
|
|
125
125
|
const FOCUSABLE =
|
|
126
126
|
'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
127
127
|
|
|
@@ -138,7 +138,7 @@ export function focusInto(container) {
|
|
|
138
138
|
// Wrap an index by `delta` within [0, len), the roving keyboard math shared by
|
|
139
139
|
// the combobox and command listboxes (a -1 `cur` lands on the first/last as
|
|
140
140
|
// before). Only this core is shared — the surrounding setActive/filter/group
|
|
141
|
-
// logic diverges between the two for real reasons.
|
|
141
|
+
// logic diverges between the two for real reasons.
|
|
142
142
|
export function wrapIndex(cur, delta, len) {
|
|
143
143
|
let next = cur + delta;
|
|
144
144
|
if (next < 0) next = len - 1;
|
package/behaviors/modal.js
CHANGED
|
@@ -151,7 +151,7 @@ export function initModal({ root } = {}) {
|
|
|
151
151
|
// A controlled modal must announce AS a modal dialog, not a generic group —
|
|
152
152
|
// parity with initPopover. Apply a dialog role + aria-modal (unless the
|
|
153
153
|
// author set a role), and dev-warn on a missing accessible name since we
|
|
154
|
-
// can't invent a good one.
|
|
154
|
+
// can't invent a good one.
|
|
155
155
|
if (!modal.hasAttribute('role')) modal.setAttribute('role', 'dialog');
|
|
156
156
|
if (!modal.hasAttribute('aria-modal')) modal.setAttribute('aria-modal', 'true');
|
|
157
157
|
const named =
|
package/behaviors/popover.js
CHANGED
|
@@ -94,9 +94,9 @@ export function initPopover({ root } = {}) {
|
|
|
94
94
|
}
|
|
95
95
|
};
|
|
96
96
|
|
|
97
|
-
// The trigger advertises `aria-haspopup="dialog"`, so the open panel must
|
|
98
|
-
// dialog: a role, an accessible name, and focus moved into it
|
|
99
|
-
// shared `focusInto` in internal.js.
|
|
97
|
+
// The trigger advertises `aria-haspopup="dialog"`, so the open panel must be
|
|
98
|
+
// a dialog: a role, an accessible name, and focus moved into it. See the
|
|
99
|
+
// shared `focusInto` helper in internal.js.
|
|
100
100
|
|
|
101
101
|
const place = (trigger, panel) => {
|
|
102
102
|
const r = trigger.getBoundingClientRect();
|
|
@@ -148,7 +148,7 @@ export function initPopover({ root } = {}) {
|
|
|
148
148
|
rememberPanel(panel);
|
|
149
149
|
// Live up to the advertised `aria-haspopup="dialog"`: give the panel a
|
|
150
150
|
// dialog role (unless the author set one) so AT announces it as the promised
|
|
151
|
-
// dialog rather than a generic group
|
|
151
|
+
// dialog rather than a generic group.
|
|
152
152
|
if (!panel.hasAttribute('role')) panel.setAttribute('role', 'dialog');
|
|
153
153
|
trigger.setAttribute('aria-controls', panel.id);
|
|
154
154
|
trigger.setAttribute('aria-expanded', 'true');
|
|
@@ -211,7 +211,7 @@ export function initPopover({ root } = {}) {
|
|
|
211
211
|
trigger.setAttribute('aria-controls', panel.id);
|
|
212
212
|
if (!trigger.hasAttribute('aria-expanded')) trigger.setAttribute('aria-expanded', 'false');
|
|
213
213
|
// A dialog with no accessible name is announced as just "dialog". We can't
|
|
214
|
-
// invent a good name, so warn the author at dev time
|
|
214
|
+
// invent a good name, so warn the author at dev time.
|
|
215
215
|
const named =
|
|
216
216
|
panel.hasAttribute('aria-label') ||
|
|
217
217
|
panel.hasAttribute('aria-labelledby') ||
|
package/behaviors/table.d.ts
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* canonical number in a `data-sort-value` attribute on the cell. That escape
|
|
28
28
|
* hatch wins over the parsed text and accepts either a dot ("3.5") or a single
|
|
29
29
|
* decimal comma ("3,5"). It is a client-side convenience sorter, not a data
|
|
30
|
-
* grid.
|
|
30
|
+
* grid.
|
|
31
31
|
*
|
|
32
32
|
* @param {import('./internal.js').DelegateOpts} [opts]
|
|
33
33
|
* @returns {import('./internal.js').Cleanup}
|
package/behaviors/table.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table.d.ts","sourceRoot":"","sources":["table.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,yCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"table.d.ts","sourceRoot":"","sources":["table.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,yCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAwO3C"}
|
package/behaviors/table.js
CHANGED
|
@@ -29,7 +29,7 @@ import { hasDom, resolveHost, noop, bindOnce, collectHosts, closestSafe } from '
|
|
|
29
29
|
* canonical number in a `data-sort-value` attribute on the cell. That escape
|
|
30
30
|
* hatch wins over the parsed text and accepts either a dot ("3.5") or a single
|
|
31
31
|
* decimal comma ("3,5"). It is a client-side convenience sorter, not a data
|
|
32
|
-
* grid.
|
|
32
|
+
* grid.
|
|
33
33
|
*
|
|
34
34
|
* @param {import('./internal.js').DelegateOpts} [opts]
|
|
35
35
|
* @returns {import('./internal.js').Cleanup}
|
|
@@ -98,8 +98,7 @@ export function initTableSort({ root } = {}) {
|
|
|
98
98
|
};
|
|
99
99
|
const seedSorters = () => {
|
|
100
100
|
// Seed the resting `aria-sort="none"` on every sortable header so AT
|
|
101
|
-
// announces the column as sortable from the start
|
|
102
|
-
// first click — C10).
|
|
101
|
+
// announces the column as sortable from the start.
|
|
103
102
|
for (const sort of table.querySelectorAll('.ui-table__sort')) {
|
|
104
103
|
rememberSorterState(sort);
|
|
105
104
|
if (sort.tagName === 'BUTTON' && !sort.hasAttribute('type')) sort.type = 'button';
|
|
@@ -139,7 +138,7 @@ export function initTableSort({ root } = {}) {
|
|
|
139
138
|
// authoritative escape hatch; otherwise normalize the display text so the
|
|
140
139
|
// sign survives (U+2212 / en-em dashes → minus, accounting parens →
|
|
141
140
|
// negative) and `,` grouping is dropped. Returns 0 for unparseable cells so
|
|
142
|
-
// they cluster rather than scatter.
|
|
141
|
+
// they cluster rather than scatter.
|
|
143
142
|
const cellNum = (row, i) => {
|
|
144
143
|
const cell = row.children[i];
|
|
145
144
|
const explicit = cell?.getAttribute?.('data-sort-value');
|
|
@@ -181,8 +180,8 @@ export function initTableSort({ root } = {}) {
|
|
|
181
180
|
th.setAttribute('aria-sort', dir);
|
|
182
181
|
const i = colIndex(th);
|
|
183
182
|
const sign = dir === 'ascending' ? 1 : -1;
|
|
184
|
-
// Empty/sentinel rows sort out of the data set
|
|
185
|
-
// or after a sort they float above the real rows
|
|
183
|
+
// Empty/sentinel rows sort out of the data set and must re-append last,
|
|
184
|
+
// or after a sort they float above the real rows.
|
|
186
185
|
const emptyRows = [...tbody.rows].filter((r) => r.classList.contains('ui-table__empty'));
|
|
187
186
|
const rows = [...tbody.rows].filter((r) => !r.classList.contains('ui-table__empty'));
|
|
188
187
|
rows.sort((a, b) => {
|
|
@@ -222,8 +221,8 @@ export function initTableSort({ root } = {}) {
|
|
|
222
221
|
const onClick = (e) => {
|
|
223
222
|
// Only the focusable `.ui-table__sort` button is a sort trigger — it is
|
|
224
223
|
// keyboard-operable and carries the `::after` sort glyph. The bare
|
|
225
|
-
// `th[data-sort]` path was mouse-only with no affordance, so it is gone
|
|
226
|
-
//
|
|
224
|
+
// `th[data-sort]` path was mouse-only with no affordance, so it is gone;
|
|
225
|
+
// `data-sort="num"` is still read from the button or its th.
|
|
227
226
|
const sorter = closestSafe(e.target, '.ui-table__sort');
|
|
228
227
|
if (sorter && table.contains(sorter)) {
|
|
229
228
|
rememberSorterState(sorter);
|
package/behaviors/tabs.js
CHANGED
|
@@ -103,14 +103,14 @@ export function initTabs({ root } = {}) {
|
|
|
103
103
|
t.tabIndex = on ? 0 : -1;
|
|
104
104
|
}
|
|
105
105
|
// Only retarget panels when this tab actually controls one. A panel-less
|
|
106
|
-
// tab must
|
|
106
|
+
// tab must not hide every panel; leave the prior panel visible.
|
|
107
107
|
if (!panels.some((p) => p.dataset.panel === tab.dataset.tab)) return;
|
|
108
108
|
for (const p of panels) {
|
|
109
109
|
p.setAttribute('role', 'tabpanel');
|
|
110
110
|
const shown = p.dataset.panel === tab.dataset.tab;
|
|
111
111
|
p.hidden = !shown;
|
|
112
112
|
// APG: a tabpanel is focusable so keyboard users can reach a text-only
|
|
113
|
-
// panel; hidden panels drop out of the tab order
|
|
113
|
+
// panel; hidden panels drop out of the tab order.
|
|
114
114
|
if (shown) p.tabIndex = 0;
|
|
115
115
|
else p.removeAttribute('tabindex');
|
|
116
116
|
}
|
package/behaviors/toast.js
CHANGED
|
@@ -3,7 +3,7 @@ import { hasDom, noop } from './internal.js';
|
|
|
3
3
|
// The tones that have a `.ui-toast--*` rule. The TS type already unions these,
|
|
4
4
|
// but a plain-JS / LLM caller can pass any string — and an unknown tone built a
|
|
5
5
|
// `.ui-toast--error` class that matches no CSS, yielding a silent neutral toast.
|
|
6
|
-
// Validate so an unknown tone degrades to neutral *and warns*, never lies.
|
|
6
|
+
// Validate so an unknown tone degrades to neutral *and warns*, never lies.
|
|
7
7
|
const TOAST_TONES = new Set(['accent', 'success', 'warning', 'danger', 'info']);
|
|
8
8
|
|
|
9
9
|
// First-toast deferral queue. The very first toast on a brand-new stack
|
|
@@ -29,7 +29,7 @@ function toastStack(isAssertive) {
|
|
|
29
29
|
stack.setAttribute('role', 'alert');
|
|
30
30
|
// The assertive region carries one error at a time and must be read whole;
|
|
31
31
|
// aria-atomic ensures the full toast (title + message) announces, not just
|
|
32
|
-
// the
|
|
32
|
+
// the changed fragment.
|
|
33
33
|
stack.setAttribute('aria-atomic', 'true');
|
|
34
34
|
}
|
|
35
35
|
document.body.appendChild(stack);
|
|
@@ -67,7 +67,7 @@ function toastElement(message, { tone, title }) {
|
|
|
67
67
|
// aria-atomic so a *titled* toast announces title + message as one unit, not
|
|
68
68
|
// disjointly — and unlike aria-atomic on the polite STACK (which would re-read
|
|
69
69
|
// every resident toast on each new one), scoping it to the toast keeps sibling
|
|
70
|
-
// toasts out of the announcement.
|
|
70
|
+
// toasts out of the announcement.
|
|
71
71
|
el.setAttribute('aria-atomic', 'true');
|
|
72
72
|
if (title) {
|
|
73
73
|
const t = document.createElement('p');
|
|
@@ -183,8 +183,8 @@ export function toast(message, { tone, title, duration = 4000, assertive, closab
|
|
|
183
183
|
// `closable`. The button carries no text node (glyph is a CSS
|
|
184
184
|
// ::before) so the toast's announced/textContent stays the message.
|
|
185
185
|
// Explicitly opting OUT of the close button on a sticky toast strands it with
|
|
186
|
-
// no in-UI dismissal
|
|
187
|
-
// dismiss
|
|
186
|
+
// no in-UI dismissal; warn that the caller must retain and call the returned
|
|
187
|
+
// dismiss function.
|
|
188
188
|
if (duration === 0 && closable === false && typeof console !== 'undefined') {
|
|
189
189
|
console.warn(
|
|
190
190
|
'[bronto] toast(): duration:0 + closable:false has no in-UI dismissal — keep the returned dismiss() and call it yourself, or set closable:true.',
|
package/classes/classes.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "@ponchia/ui class vocabulary as language-neutral data — validate emitted markup without executing the ESM cls map or parsing the .d.ts. Generated from classes/index.js — do not edit by hand; run `npm run classes:json:build`. Drift-checked in CI. `groups[].base` is null for a parts-only namespace (no standalone base class — do NOT emit it). A modifier whose name contains `__` (e.g. `ui-spark__bar--pos`) attaches to THAT part, not the base host. `states` is the author-applied `is-*` hooks (runtime/behavior-managed hooks are excluded); `behaviorAttributes` are the `data-bronto-*` wiring hooks the optional behaviors delegate on; `requiredAria` is the role/aria a generator must emit per component. `states` + `customProperties` are documented in docs/reference.md and ship outside `cls` by design.",
|
|
3
3
|
"counts": {
|
|
4
|
-
"classes":
|
|
5
|
-
"groups":
|
|
4
|
+
"classes": 644,
|
|
5
|
+
"groups": 179
|
|
6
6
|
},
|
|
7
7
|
"groups": {
|
|
8
8
|
"ui-accordion": {
|
|
@@ -1081,6 +1081,13 @@
|
|
|
1081
1081
|
"ui-segmented__option"
|
|
1082
1082
|
]
|
|
1083
1083
|
},
|
|
1084
|
+
"ui-segmented-buttons": {
|
|
1085
|
+
"base": "ui-segmented-buttons",
|
|
1086
|
+
"modifiers": [],
|
|
1087
|
+
"parts": [
|
|
1088
|
+
"ui-segmented-buttons__button"
|
|
1089
|
+
]
|
|
1090
|
+
},
|
|
1084
1091
|
"ui-sel": {
|
|
1085
1092
|
"base": "ui-sel",
|
|
1086
1093
|
"modifiers": [
|
|
@@ -1437,6 +1444,20 @@
|
|
|
1437
1444
|
"modifiers": [],
|
|
1438
1445
|
"parts": []
|
|
1439
1446
|
},
|
|
1447
|
+
"ui-toolstrip": {
|
|
1448
|
+
"base": "ui-toolstrip",
|
|
1449
|
+
"modifiers": [
|
|
1450
|
+
"ui-toolstrip--compact",
|
|
1451
|
+
"ui-toolstrip--floating"
|
|
1452
|
+
],
|
|
1453
|
+
"parts": [
|
|
1454
|
+
"ui-toolstrip__actions",
|
|
1455
|
+
"ui-toolstrip__brand",
|
|
1456
|
+
"ui-toolstrip__context",
|
|
1457
|
+
"ui-toolstrip__group",
|
|
1458
|
+
"ui-toolstrip__search"
|
|
1459
|
+
]
|
|
1460
|
+
},
|
|
1440
1461
|
"ui-tooltip": {
|
|
1441
1462
|
"base": "ui-tooltip",
|
|
1442
1463
|
"modifiers": [],
|
|
@@ -1964,6 +1985,8 @@
|
|
|
1964
1985
|
"ui-scroll-reveal",
|
|
1965
1986
|
"ui-search",
|
|
1966
1987
|
"ui-segmented",
|
|
1988
|
+
"ui-segmented-buttons",
|
|
1989
|
+
"ui-segmented-buttons__button",
|
|
1967
1990
|
"ui-segmented__option",
|
|
1968
1991
|
"ui-sel",
|
|
1969
1992
|
"ui-sel--maybe",
|
|
@@ -2100,6 +2123,14 @@
|
|
|
2100
2123
|
"ui-tool-call__name",
|
|
2101
2124
|
"ui-tool-call__status",
|
|
2102
2125
|
"ui-tool-log",
|
|
2126
|
+
"ui-toolstrip",
|
|
2127
|
+
"ui-toolstrip--compact",
|
|
2128
|
+
"ui-toolstrip--floating",
|
|
2129
|
+
"ui-toolstrip__actions",
|
|
2130
|
+
"ui-toolstrip__brand",
|
|
2131
|
+
"ui-toolstrip__context",
|
|
2132
|
+
"ui-toolstrip__group",
|
|
2133
|
+
"ui-toolstrip__search",
|
|
2103
2134
|
"ui-tooltip",
|
|
2104
2135
|
"ui-tooltip__bubble",
|
|
2105
2136
|
"ui-tour-note",
|
package/classes/index.d.ts
CHANGED
|
@@ -574,6 +574,16 @@ export declare const cls: {
|
|
|
574
574
|
readonly inspector: 'ui-inspector';
|
|
575
575
|
readonly inspectorHead: 'ui-inspector__head';
|
|
576
576
|
readonly inspectorBody: 'ui-inspector__body';
|
|
577
|
+
readonly toolstrip: 'ui-toolstrip';
|
|
578
|
+
readonly toolstripFloating: 'ui-toolstrip--floating';
|
|
579
|
+
readonly toolstripCompact: 'ui-toolstrip--compact';
|
|
580
|
+
readonly toolstripBrand: 'ui-toolstrip__brand';
|
|
581
|
+
readonly toolstripContext: 'ui-toolstrip__context';
|
|
582
|
+
readonly toolstripGroup: 'ui-toolstrip__group';
|
|
583
|
+
readonly toolstripActions: 'ui-toolstrip__actions';
|
|
584
|
+
readonly toolstripSearch: 'ui-toolstrip__search';
|
|
585
|
+
readonly segmentedButtons: 'ui-segmented-buttons';
|
|
586
|
+
readonly segmentedButtonsButton: 'ui-segmented-buttons__button';
|
|
577
587
|
readonly property: 'ui-property';
|
|
578
588
|
readonly propertyLabel: 'ui-property__label';
|
|
579
589
|
readonly propertyValue: 'ui-property__value';
|