@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.
Files changed (60) hide show
  1. package/CHANGELOG.md +79 -4
  2. package/README.md +2 -2
  3. package/annotations/index.d.ts.map +1 -1
  4. package/annotations/index.js +5 -6
  5. package/behaviors/carousel.d.ts.map +1 -1
  6. package/behaviors/carousel.js +100 -60
  7. package/behaviors/combobox.d.ts.map +1 -1
  8. package/behaviors/combobox.js +167 -113
  9. package/behaviors/connectors.d.ts.map +1 -1
  10. package/behaviors/connectors.js +39 -23
  11. package/behaviors/forms.d.ts.map +1 -1
  12. package/behaviors/forms.js +211 -207
  13. package/behaviors/glyph.d.ts.map +1 -1
  14. package/behaviors/glyph.js +157 -132
  15. package/behaviors/inert.d.ts +1 -1
  16. package/behaviors/inert.d.ts.map +1 -1
  17. package/behaviors/inert.js +1 -1
  18. package/behaviors/internal.js +2 -2
  19. package/behaviors/modal.js +1 -1
  20. package/behaviors/popover.js +5 -5
  21. package/behaviors/table.d.ts +1 -1
  22. package/behaviors/table.d.ts.map +1 -1
  23. package/behaviors/table.js +7 -8
  24. package/behaviors/tabs.js +2 -2
  25. package/behaviors/toast.js +5 -5
  26. package/classes/classes.json +33 -2
  27. package/classes/index.d.ts +10 -0
  28. package/classes/index.js +59 -35
  29. package/connectors/index.d.ts +2 -2
  30. package/connectors/index.d.ts.map +1 -1
  31. package/connectors/index.js +7 -10
  32. package/css/app.css +3 -4
  33. package/css/base.css +1 -1
  34. package/css/content.css +3 -3
  35. package/css/disclosure.css +3 -3
  36. package/css/dots.css +4 -4
  37. package/css/feedback.css +6 -7
  38. package/css/forms.css +9 -12
  39. package/css/legend.css +1 -1
  40. package/css/marks.css +1 -1
  41. package/css/motion.css +6 -6
  42. package/css/overlay.css +5 -7
  43. package/css/primitives.css +14 -16
  44. package/css/sidenote.css +2 -2
  45. package/css/table.css +2 -2
  46. package/css/workbench.css +128 -0
  47. package/dist/css/workbench.css +1 -1
  48. package/docs/annotations.md +36 -0
  49. package/docs/architecture.md +28 -0
  50. package/docs/interop/react-flow.md +89 -0
  51. package/docs/package-contract.md +2 -0
  52. package/docs/reference.md +21 -1
  53. package/docs/reporting.md +8 -8
  54. package/docs/stability.md +67 -7
  55. package/docs/workbench.md +56 -9
  56. package/glyphs/glyphs.js +43 -33
  57. package/llms.txt +10 -4
  58. package/package.json +5 -2
  59. package/schemas/report-claims.v1.schema.json +1 -1
  60. package/tokens/index.js +2 -2
@@ -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
- if (el.classList.contains('ui-icon') && el.style.getPropertyValue('--icon-mask')) continue;
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
- // Scope to DIRECT-child cells (the ones we append) — so a placeholder that
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());
@@ -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. (component audit C4.)
14
+ * sees it.
15
15
  *
16
16
  * @param {import('./internal.js').DelegateOpts} [opts]
17
17
  * @returns {import('./internal.js').Cleanup}
@@ -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,CAyB3C"}
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"}
@@ -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. (component audit C4.)
18
+ * sees it.
19
19
  *
20
20
  * @param {import('./internal.js').DelegateOpts} [opts]
21
21
  * @returns {import('./internal.js').Cleanup}
@@ -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. (code-quality audit Q4.)
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. (code-quality audit Q12.)
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;
@@ -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. (component audit C13.)
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 =
@@ -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 BE a
98
- // dialog: a role, an accessible name, and focus moved into it (C6) — see the
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 (C6).
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 (C6).
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') ||
@@ -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. (component audit C3/C5.)
30
+ * grid.
31
31
  *
32
32
  * @param {import('./internal.js').DelegateOpts} [opts]
33
33
  * @returns {import('./internal.js').Cleanup}
@@ -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,CAyO3C"}
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"}
@@ -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. (component audit C3/C5.)
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 (it was unset until the
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. (component audit C3.)
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 AND must re-append LAST,
185
- // or after a sort they float above the real rows (C29).
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
- // (C10); `data-sort="num"` is still read from the button or its th.
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 NOT hide every panel leave the prior panel visible (C30).
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 (C30).
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
  }
@@ -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. (C16)
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 diff. (component audit C38.)
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. (component audit C23.)
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 warn that the caller must retain and call the returned
187
- // dismiss fn. (component audit C37.)
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.',
@@ -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": 634,
5
- "groups": 177
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",
@@ -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';