@ponchia/ui 0.3.4 → 0.3.6

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.
@@ -14,6 +14,8 @@
14
14
  * const stop = initThemeToggle(); // wire [data-bronto-theme-toggle]
15
15
  */
16
16
 
17
+ import { GLYPH_SIZE, glyphCells } from '../glyphs/glyphs.js';
18
+
17
19
  const THEMES = ['light', 'dark'];
18
20
  const noop = () => {};
19
21
  const hasDom = () => typeof document !== 'undefined';
@@ -1044,3 +1046,306 @@ export function initTableSort({ root } = {}) {
1044
1046
 
1045
1047
  return () => cleanups.forEach((fn) => fn());
1046
1048
  }
1049
+
1050
+ /**
1051
+ * Image carousel / gallery, built on CSS scroll-snap so touch + trackpad
1052
+ * swipe (and momentum) are the browser's, not hand-rolled. This wires the
1053
+ * parts scroll-snap can't do alone: prev/next buttons, keyboard nav, a
1054
+ * thumbnail strip, the position counter, and ARIA — keeping a JS index in
1055
+ * sync with the scroll position both ways.
1056
+ *
1057
+ * Markup: `[data-bronto-carousel]` containing a `.ui-carousel__viewport`
1058
+ * of `.ui-carousel__slide` children; optionally
1059
+ * `[data-bronto-carousel-prev]` / `[data-bronto-carousel-next]` controls,
1060
+ * a `.ui-carousel__thumbs` list of `.ui-carousel__thumb` buttons, and a
1061
+ * `.ui-carousel__status` counter slot. Add `data-bronto-carousel-loop` to
1062
+ * wrap at the ends, `data-bronto-carousel-label` to name the region.
1063
+ *
1064
+ * A full-screen **lightbox** is the same markup inside a native
1065
+ * `<dialog class="ui-lightbox">` opened by {@link initDialog}: the
1066
+ * `<dialog>` provides the top layer, focus-trap, Escape and focus-return,
1067
+ * so this behavior never touches focus management.
1068
+ *
1069
+ * Emits `bronto:change` ({ detail: { index } }) on every index change
1070
+ * (button, key, thumbnail, or swipe). SSR-safe, idempotent per carousel;
1071
+ * returns a cleanup function.
1072
+ */
1073
+ export function initCarousel({ root } = {}) {
1074
+ if (!hasDom()) return noop;
1075
+ const host = root || document;
1076
+ const boxes = [];
1077
+ if (host !== document && host.matches?.('[data-bronto-carousel]')) boxes.push(host);
1078
+ boxes.push(...(host.querySelectorAll?.('[data-bronto-carousel]') ?? []));
1079
+ const cleanups = [];
1080
+
1081
+ for (const box of boxes) {
1082
+ const viewport = box.querySelector('.ui-carousel__viewport');
1083
+ if (!viewport) continue;
1084
+ const slides = [...viewport.children].filter((el) =>
1085
+ el.classList.contains('ui-carousel__slide'),
1086
+ );
1087
+ if (!slides.length) continue;
1088
+ const n = slides.length;
1089
+ const thumbs = [...box.querySelectorAll('.ui-carousel__thumb')];
1090
+ const status = box.querySelector('.ui-carousel__status');
1091
+ const prevBtn = box.querySelector('[data-bronto-carousel-prev]');
1092
+ const nextBtn = box.querySelector('[data-bronto-carousel-next]');
1093
+ const loop = box.hasAttribute('data-bronto-carousel-loop');
1094
+
1095
+ // ARIA scaffolding — pragmatic carousel semantics (not the full APG
1096
+ // tablist), the same restraint initMenu takes.
1097
+ viewport.setAttribute('role', 'group');
1098
+ viewport.setAttribute('aria-roledescription', 'carousel');
1099
+ if (!viewport.hasAttribute('aria-label'))
1100
+ viewport.setAttribute(
1101
+ 'aria-label',
1102
+ box.getAttribute('data-bronto-carousel-label') || 'Carousel',
1103
+ );
1104
+ if (!viewport.hasAttribute('tabindex')) viewport.tabIndex = 0;
1105
+ slides.forEach((s, i) => {
1106
+ s.setAttribute('role', 'group');
1107
+ s.setAttribute('aria-roledescription', 'slide');
1108
+ if (!s.hasAttribute('aria-label')) s.setAttribute('aria-label', `${i + 1} of ${n}`);
1109
+ });
1110
+ if (status) status.setAttribute('aria-live', 'polite');
1111
+ for (const b of [prevBtn, nextBtn]) {
1112
+ if (!b) continue;
1113
+ if (b.tagName === 'BUTTON' && !b.hasAttribute('type')) b.type = 'button';
1114
+ }
1115
+ if (prevBtn && !prevBtn.hasAttribute('aria-label'))
1116
+ prevBtn.setAttribute('aria-label', 'Previous');
1117
+ if (nextBtn && !nextBtn.hasAttribute('aria-label')) nextBtn.setAttribute('aria-label', 'Next');
1118
+
1119
+ let index = Math.max(
1120
+ 0,
1121
+ slides.findIndex((s) => s.hasAttribute('data-bronto-carousel-current')),
1122
+ );
1123
+
1124
+ // While a button/keyboard nav is smooth-scrolling, the IntersectionObserver
1125
+ // would observe the intermediate slides crossing its threshold and re-fire
1126
+ // `bronto:change` for each — a feedback burst on a single Home→End jump.
1127
+ // This flag makes the IO drive the index on *user* swipes only; a timeout
1128
+ // (not the patchy `scrollend` event) releases it once the scroll settles.
1129
+ let programmatic = false;
1130
+ let progTimer = null;
1131
+ const holdProgrammatic = () => {
1132
+ programmatic = true;
1133
+ if (progTimer) clearTimeout(progTimer);
1134
+ progTimer = setTimeout(() => {
1135
+ programmatic = false;
1136
+ }, 500);
1137
+ progTimer?.unref?.(); // don't keep a Node test process alive
1138
+ };
1139
+
1140
+ const render = () => {
1141
+ if (status) status.textContent = `${index + 1} / ${n}`;
1142
+ thumbs.forEach((t, i) => {
1143
+ if (i === index) t.setAttribute('aria-current', 'true');
1144
+ else t.removeAttribute('aria-current');
1145
+ });
1146
+ if (prevBtn && !loop) prevBtn.disabled = index === 0;
1147
+ if (nextBtn && !loop) nextBtn.disabled = index === n - 1;
1148
+ };
1149
+
1150
+ const emit = () =>
1151
+ box.dispatchEvent(new CustomEvent('bronto:change', { detail: { index }, bubbles: true }));
1152
+
1153
+ // jsdom (and any layout-less env) has no scrollIntoView; it's a pure
1154
+ // affordance, so never let it break index/aria sync — same guard as
1155
+ // initCombobox.
1156
+ const reveal = (el) => {
1157
+ try {
1158
+ el?.scrollIntoView({ block: 'nearest', inline: 'center' });
1159
+ } catch {
1160
+ /* no layout — ignore */
1161
+ }
1162
+ };
1163
+
1164
+ const goTo = (i, { emitChange = true } = {}) => {
1165
+ const next = loop ? (i + n) % n : Math.max(0, Math.min(n - 1, i));
1166
+ const changed = next !== index;
1167
+ index = next;
1168
+ holdProgrammatic(); // suppress IO echo from the smooth-scroll this triggers
1169
+ reveal(slides[index]);
1170
+ reveal(thumbs[index]);
1171
+ render();
1172
+ if (changed && emitChange) emit();
1173
+ };
1174
+
1175
+ const onKey = (e) => {
1176
+ let target = null;
1177
+ if (e.key === 'ArrowRight') target = index + 1;
1178
+ else if (e.key === 'ArrowLeft') target = index - 1;
1179
+ else if (e.key === 'Home') target = 0;
1180
+ else if (e.key === 'End') target = n - 1;
1181
+ else return;
1182
+ e.preventDefault();
1183
+ goTo(target);
1184
+ };
1185
+ const onClick = (e) => {
1186
+ if (prevBtn && e.target.closest('[data-bronto-carousel-prev]')) {
1187
+ goTo(index - 1);
1188
+ return;
1189
+ }
1190
+ if (nextBtn && e.target.closest('[data-bronto-carousel-next]')) {
1191
+ goTo(index + 1);
1192
+ return;
1193
+ }
1194
+ const thumb = e.target.closest('.ui-carousel__thumb');
1195
+ if (thumb) {
1196
+ const i = thumbs.indexOf(thumb);
1197
+ if (i >= 0) goTo(i);
1198
+ }
1199
+ };
1200
+
1201
+ // Swipe sync (enhancement): when the user scrolls the viewport, snap
1202
+ // the JS index to the slide that's settled into view. Feature-detected
1203
+ // so the buttons/keyboard still work where IntersectionObserver is
1204
+ // absent (jsdom, older engines).
1205
+ let io = null;
1206
+ if (typeof IntersectionObserver === 'function') {
1207
+ io = new IntersectionObserver(
1208
+ (entries) => {
1209
+ if (programmatic) return; // ignore the echo of a button/key-driven scroll
1210
+ let best = null;
1211
+ for (const ent of entries) {
1212
+ if (ent.isIntersecting && (!best || ent.intersectionRatio > best.intersectionRatio))
1213
+ best = ent;
1214
+ }
1215
+ if (!best) return;
1216
+ const i = slides.indexOf(best.target);
1217
+ if (i >= 0 && i !== index) {
1218
+ index = i;
1219
+ render();
1220
+ reveal(thumbs[index]);
1221
+ emit();
1222
+ }
1223
+ },
1224
+ { root: viewport, threshold: 0.6 },
1225
+ );
1226
+ slides.forEach((s) => io.observe(s));
1227
+ }
1228
+
1229
+ render();
1230
+ const bound = bindOnce(box, 'carousel', () => {
1231
+ viewport.addEventListener('keydown', onKey);
1232
+ box.addEventListener('click', onClick);
1233
+ return () => {
1234
+ viewport.removeEventListener('keydown', onKey);
1235
+ box.removeEventListener('click', onClick);
1236
+ io?.disconnect();
1237
+ if (progTimer) clearTimeout(progTimer);
1238
+ };
1239
+ });
1240
+ cleanups.push(bound);
1241
+ }
1242
+
1243
+ return () => cleanups.forEach((fn) => fn());
1244
+ }
1245
+
1246
+ function restoreAttr(el, name, prev) {
1247
+ if (prev === null) el.removeAttribute(name);
1248
+ else el.setAttribute(name, prev);
1249
+ }
1250
+
1251
+ /**
1252
+ * Expand `[data-bronto-glyph="name"]` placeholders into a `.ui-dotmatrix`
1253
+ * grid of GLYPH_SIZE² cells — the DOM counterpart to renderGlyph() from
1254
+ * `@ponchia/ui/glyphs`, for when you'd rather drop a placeholder than inline
1255
+ * the markup. Decorative by default (`aria-hidden`); add
1256
+ * `data-bronto-glyph-label` to expose it as `role="img"`. An unknown glyph
1257
+ * name is left untouched. Idempotent (skips an already-expanded host); the
1258
+ * returned cleanup removes the cells and restores the original attributes.
1259
+ */
1260
+ export function initDotGlyph({ root } = {}) {
1261
+ if (!hasDom()) return noop;
1262
+ const host = root || document;
1263
+ const els = [];
1264
+ if (host !== document && host.matches?.('[data-bronto-glyph]')) els.push(host);
1265
+ els.push(...(host.querySelectorAll?.('[data-bronto-glyph]') ?? []));
1266
+ const cleanups = [];
1267
+
1268
+ for (const el of els) {
1269
+ // Scope to DIRECT-child cells (the ones we append) — so a placeholder that
1270
+ // legitimately nests its own .ui-dotmatrix is neither mis-read as already
1271
+ // expanded here nor have its inner cells removed by cleanup below.
1272
+ if (el.querySelector(':scope > .ui-dotmatrix__cell')) continue; // already expanded
1273
+ const cells = glyphCells(el.getAttribute('data-bronto-glyph'));
1274
+ if (!cells.length) continue; // unknown glyph — leave the placeholder as-is
1275
+
1276
+ const label = el.getAttribute('data-bronto-glyph-label');
1277
+ // `data-bronto-glyph-solid` → square, gapless pixel glyph (legible small),
1278
+ // the DOM counterpart to renderGlyph's `solid` option. Implies glyph-only.
1279
+ const solid = el.hasAttribute('data-bronto-glyph-solid');
1280
+ // `data-bronto-glyph-anim="reveal|pulse"` → decorative animation (the DOM
1281
+ // counterpart to renderGlyph's `anim`; reduced-motion-safe via CSS).
1282
+ const animAttr = el.getAttribute('data-bronto-glyph-anim');
1283
+ const animClass =
1284
+ animAttr === 'reveal'
1285
+ ? 'ui-dotmatrix--reveal'
1286
+ : animAttr === 'pulse'
1287
+ ? 'ui-dotmatrix--pulse'
1288
+ : null;
1289
+ const hadAnimClass = animClass ? el.classList.contains(animClass) : false;
1290
+ const hadMatrix = el.classList.contains('ui-dotmatrix');
1291
+ const hadCols = el.style.getPropertyValue('--dotmatrix-cols');
1292
+ const hadRadius = el.style.getPropertyValue('--dotmatrix-dot-radius');
1293
+ const hadGap = el.style.getPropertyValue('--dotmatrix-gap');
1294
+ const hadAriaHidden = el.getAttribute('aria-hidden');
1295
+ const hadRole = el.getAttribute('role');
1296
+ const hadAriaLabel = el.getAttribute('aria-label');
1297
+
1298
+ el.classList.add('ui-dotmatrix');
1299
+ if (animClass) el.classList.add(animClass);
1300
+ el.style.setProperty('--dotmatrix-cols', String(GLYPH_SIZE));
1301
+ if (solid) {
1302
+ el.style.setProperty('--dotmatrix-dot-radius', '0');
1303
+ el.style.setProperty('--dotmatrix-gap', '0');
1304
+ }
1305
+ if (label) {
1306
+ el.setAttribute('role', 'img');
1307
+ el.setAttribute('aria-label', label);
1308
+ el.removeAttribute('aria-hidden'); // a labelled img must not also be hidden
1309
+ } else {
1310
+ el.setAttribute('aria-hidden', 'true');
1311
+ }
1312
+
1313
+ const frag = document.createDocumentFragment();
1314
+ cells.forEach((c, i) => {
1315
+ const span = document.createElement('span');
1316
+ span.className = !c.on
1317
+ ? 'ui-dotmatrix__cell'
1318
+ : c.tone === 'hot'
1319
+ ? 'ui-dotmatrix__cell ui-dotmatrix__cell--hot'
1320
+ : c.tone === 'accent'
1321
+ ? 'ui-dotmatrix__cell ui-dotmatrix__cell--accent'
1322
+ : 'ui-dotmatrix__cell';
1323
+ if (!c.on && solid) span.style.background = 'transparent'; // glyph-only
1324
+ if (animAttr === 'reveal') span.style.setProperty('--i', String(i)); // scan stagger
1325
+ frag.appendChild(span);
1326
+ });
1327
+ el.appendChild(frag);
1328
+
1329
+ cleanups.push(() => {
1330
+ el.querySelectorAll(':scope > .ui-dotmatrix__cell').forEach((n) => n.remove());
1331
+ if (!hadMatrix) el.classList.remove('ui-dotmatrix');
1332
+ if (animClass && !hadAnimClass) el.classList.remove(animClass);
1333
+ if (solid) {
1334
+ if (hadRadius) el.style.setProperty('--dotmatrix-dot-radius', hadRadius);
1335
+ else el.style.removeProperty('--dotmatrix-dot-radius');
1336
+ if (hadGap) el.style.setProperty('--dotmatrix-gap', hadGap);
1337
+ else el.style.removeProperty('--dotmatrix-gap');
1338
+ }
1339
+ if (hadCols) el.style.setProperty('--dotmatrix-cols', hadCols);
1340
+ else el.style.removeProperty('--dotmatrix-cols');
1341
+ restoreAttr(el, 'aria-hidden', hadAriaHidden);
1342
+ restoreAttr(el, 'role', hadRole);
1343
+ restoreAttr(el, 'aria-label', hadAriaLabel);
1344
+ // Don't leave behind empty class=""/style="" we ourselves created.
1345
+ if (el.getAttribute('class') === '') el.removeAttribute('class');
1346
+ if (el.getAttribute('style') === '') el.removeAttribute('style');
1347
+ });
1348
+ }
1349
+
1350
+ return () => cleanups.forEach((fn) => fn());
1351
+ }
@@ -63,6 +63,8 @@ export declare const cls: {
63
63
  readonly dotmatrixCell: 'ui-dotmatrix__cell';
64
64
  readonly dotmatrixCellHot: 'ui-dotmatrix__cell--hot';
65
65
  readonly dotmatrixCellAccent: 'ui-dotmatrix__cell--accent';
66
+ readonly dotmatrixReveal: 'ui-dotmatrix--reveal';
67
+ readonly dotmatrixPulse: 'ui-dotmatrix--pulse';
66
68
  readonly dotfield: 'ui-dotfield';
67
69
  readonly dotrule: 'ui-dotrule';
68
70
  readonly dotbar: 'ui-dotbar';
@@ -85,6 +87,9 @@ export declare const cls: {
85
87
  readonly hintError: 'ui-hint--error';
86
88
  readonly inputGroup: 'ui-input-group';
87
89
  readonly inputGroupAddon: 'ui-input-group__addon';
90
+ readonly inputIcon: 'ui-input-icon';
91
+ readonly inputIconSlot: 'ui-input-icon__icon';
92
+ readonly inputIconEnd: 'ui-input-icon--end';
88
93
  readonly file: 'ui-file';
89
94
  readonly range: 'ui-range';
90
95
  readonly errorSummary: 'ui-error-summary';
@@ -115,6 +120,15 @@ export declare const cls: {
115
120
  readonly progress: 'ui-progress';
116
121
  readonly progressBar: 'ui-progress__bar';
117
122
  readonly progressIndeterminate: 'ui-progress--indeterminate';
123
+ readonly meter: 'ui-meter';
124
+ readonly meterFill: 'ui-meter__fill';
125
+ readonly meterAccent: 'ui-meter--accent';
126
+ readonly meterSuccess: 'ui-meter--success';
127
+ readonly meterWarning: 'ui-meter--warning';
128
+ readonly meterDanger: 'ui-meter--danger';
129
+ readonly steps: 'ui-steps';
130
+ readonly stepsItem: 'ui-steps__item';
131
+ readonly stepsItemDone: 'ui-steps__item--done';
118
132
  readonly modal: 'ui-modal';
119
133
  readonly modalHead: 'ui-modal__head';
120
134
  readonly modalTitle: 'ui-modal__title';
@@ -132,6 +146,8 @@ export declare const cls: {
132
146
  readonly comboboxList: 'ui-combobox__list';
133
147
  readonly comboboxOption: 'ui-combobox__option';
134
148
  readonly comboboxEmpty: 'ui-combobox__empty';
149
+ readonly lightbox: 'ui-lightbox';
150
+ readonly lightboxClose: 'ui-lightbox__close';
135
151
  readonly tabs: 'ui-tabs';
136
152
  readonly tabsList: 'ui-tabs__list';
137
153
  readonly tab: 'ui-tab';
@@ -150,6 +166,15 @@ export declare const cls: {
150
166
  readonly avatarSm: 'ui-avatar--sm';
151
167
  readonly avatarLg: 'ui-avatar--lg';
152
168
  readonly avatarGroup: 'ui-avatar-group';
169
+ readonly carousel: 'ui-carousel';
170
+ readonly carouselStage: 'ui-carousel__stage';
171
+ readonly carouselViewport: 'ui-carousel__viewport';
172
+ readonly carouselSlide: 'ui-carousel__slide';
173
+ readonly carouselPrev: 'ui-carousel__prev';
174
+ readonly carouselNext: 'ui-carousel__next';
175
+ readonly carouselStatus: 'ui-carousel__status';
176
+ readonly carouselThumbs: 'ui-carousel__thumbs';
177
+ readonly carouselThumb: 'ui-carousel__thumb';
153
178
  readonly table: 'ui-table';
154
179
  readonly tableDense: 'ui-table--dense';
155
180
  readonly tableComfortable: 'ui-table--comfortable';
@@ -190,6 +215,9 @@ export declare const cls: {
190
215
  readonly siteheaderSticky: 'ui-siteheader--sticky';
191
216
  readonly siteheaderBrand: 'ui-siteheader__brand';
192
217
  readonly siteheaderActions: 'ui-siteheader__actions';
218
+ readonly pagehead: 'ui-pagehead';
219
+ readonly pageheadTitle: 'ui-pagehead__title';
220
+ readonly pageheadActions: 'ui-pagehead__actions';
193
221
  readonly sitenav: 'ui-sitenav';
194
222
  readonly sitemenu: 'ui-sitemenu';
195
223
  readonly sitemenuPanel: 'ui-sitemenu__panel';
@@ -200,6 +228,10 @@ export declare const cls: {
200
228
  readonly tagAccent: 'ui-tag--accent';
201
229
  readonly meta: 'ui-meta';
202
230
  readonly metaItem: 'ui-meta__item';
231
+ readonly timeline: 'ui-timeline';
232
+ readonly timelineItem: 'ui-timeline__item';
233
+ readonly timelineTime: 'ui-timeline__time';
234
+ readonly kbd: 'ui-kbd';
203
235
  readonly display: 'ui-display';
204
236
  readonly mono: 'ui-mono';
205
237
  readonly muted: 'ui-muted';
@@ -307,6 +339,9 @@ export interface ToastOpts {
307
339
  export interface ProgressOpts {
308
340
  indeterminate?: boolean;
309
341
  }
342
+ export interface MeterOpts {
343
+ tone?: 'accent' | 'success' | 'warning' | 'danger';
344
+ }
310
345
  export interface DotspinnerOpts {
311
346
  size?: 'sm' | 'lg';
312
347
  }
@@ -334,6 +369,10 @@ export interface ContainerOpts {
334
369
  export interface TagOpts {
335
370
  accent?: boolean;
336
371
  }
372
+ export interface InputIconOpts {
373
+ /** Place the icon at the inline-end instead of the start. */
374
+ end?: boolean;
375
+ }
337
376
 
338
377
  export interface Ui {
339
378
  button(opts?: ButtonOpts): string;
@@ -352,6 +391,7 @@ export interface Ui {
352
391
  alert(opts?: AlertOpts): string;
353
392
  toast(opts?: ToastOpts): string;
354
393
  progress(opts?: ProgressOpts): string;
394
+ meter(opts?: MeterOpts): string;
355
395
  dotspinner(opts?: DotspinnerOpts): string;
356
396
  dotbar(opts?: DotbarOpts): string;
357
397
  modal(opts?: ModalOpts): string;
@@ -360,6 +400,7 @@ export interface Ui {
360
400
  prose(opts?: ProseOpts): string;
361
401
  container(opts?: ContainerOpts): string;
362
402
  tag(opts?: TagOpts): string;
403
+ inputIcon(opts?: InputIconOpts): string;
363
404
  }
364
405
 
365
406
  export declare const ui: Ui;
package/classes/index.js CHANGED
@@ -68,6 +68,8 @@ export const cls = Object.freeze({
68
68
  dotmatrixCell: 'ui-dotmatrix__cell',
69
69
  dotmatrixCellHot: 'ui-dotmatrix__cell--hot',
70
70
  dotmatrixCellAccent: 'ui-dotmatrix__cell--accent',
71
+ dotmatrixReveal: 'ui-dotmatrix--reveal',
72
+ dotmatrixPulse: 'ui-dotmatrix--pulse',
71
73
  dotfield: 'ui-dotfield',
72
74
  dotrule: 'ui-dotrule',
73
75
  dotbar: 'ui-dotbar',
@@ -91,6 +93,9 @@ export const cls = Object.freeze({
91
93
  hintError: 'ui-hint--error',
92
94
  inputGroup: 'ui-input-group',
93
95
  inputGroupAddon: 'ui-input-group__addon',
96
+ inputIcon: 'ui-input-icon',
97
+ inputIconSlot: 'ui-input-icon__icon',
98
+ inputIconEnd: 'ui-input-icon--end',
94
99
  file: 'ui-file',
95
100
  range: 'ui-range',
96
101
  errorSummary: 'ui-error-summary',
@@ -122,6 +127,15 @@ export const cls = Object.freeze({
122
127
  progress: 'ui-progress',
123
128
  progressBar: 'ui-progress__bar',
124
129
  progressIndeterminate: 'ui-progress--indeterminate',
130
+ meter: 'ui-meter',
131
+ meterFill: 'ui-meter__fill',
132
+ meterAccent: 'ui-meter--accent',
133
+ meterSuccess: 'ui-meter--success',
134
+ meterWarning: 'ui-meter--warning',
135
+ meterDanger: 'ui-meter--danger',
136
+ steps: 'ui-steps',
137
+ stepsItem: 'ui-steps__item',
138
+ stepsItemDone: 'ui-steps__item--done',
125
139
  // overlay
126
140
  modal: 'ui-modal',
127
141
  modalHead: 'ui-modal__head',
@@ -140,6 +154,8 @@ export const cls = Object.freeze({
140
154
  comboboxList: 'ui-combobox__list',
141
155
  comboboxOption: 'ui-combobox__option',
142
156
  comboboxEmpty: 'ui-combobox__empty',
157
+ lightbox: 'ui-lightbox',
158
+ lightboxClose: 'ui-lightbox__close',
143
159
  // disclosure
144
160
  tabs: 'ui-tabs',
145
161
  tabsList: 'ui-tabs__list',
@@ -159,6 +175,15 @@ export const cls = Object.freeze({
159
175
  avatarSm: 'ui-avatar--sm',
160
176
  avatarLg: 'ui-avatar--lg',
161
177
  avatarGroup: 'ui-avatar-group',
178
+ carousel: 'ui-carousel',
179
+ carouselStage: 'ui-carousel__stage',
180
+ carouselViewport: 'ui-carousel__viewport',
181
+ carouselSlide: 'ui-carousel__slide',
182
+ carouselPrev: 'ui-carousel__prev',
183
+ carouselNext: 'ui-carousel__next',
184
+ carouselStatus: 'ui-carousel__status',
185
+ carouselThumbs: 'ui-carousel__thumbs',
186
+ carouselThumb: 'ui-carousel__thumb',
162
187
  // table
163
188
  table: 'ui-table',
164
189
  tableDense: 'ui-table--dense',
@@ -203,6 +228,9 @@ export const cls = Object.freeze({
203
228
  siteheaderSticky: 'ui-siteheader--sticky',
204
229
  siteheaderBrand: 'ui-siteheader__brand',
205
230
  siteheaderActions: 'ui-siteheader__actions',
231
+ pagehead: 'ui-pagehead',
232
+ pageheadTitle: 'ui-pagehead__title',
233
+ pageheadActions: 'ui-pagehead__actions',
206
234
  sitenav: 'ui-sitenav',
207
235
  sitemenu: 'ui-sitemenu',
208
236
  sitemenuPanel: 'ui-sitemenu__panel',
@@ -213,6 +241,10 @@ export const cls = Object.freeze({
213
241
  tagAccent: 'ui-tag--accent',
214
242
  meta: 'ui-meta',
215
243
  metaItem: 'ui-meta__item',
244
+ timeline: 'ui-timeline',
245
+ timelineItem: 'ui-timeline__item',
246
+ timelineTime: 'ui-timeline__time',
247
+ kbd: 'ui-kbd',
216
248
  display: 'ui-display',
217
249
  mono: 'ui-mono',
218
250
  muted: 'ui-muted',
@@ -346,6 +378,14 @@ export const ui = {
346
378
  tone === 'info' && cls.toastInfo,
347
379
  ),
348
380
  progress: ({ indeterminate } = {}) => j(cls.progress, indeterminate && cls.progressIndeterminate),
381
+ meter: ({ tone } = {}) =>
382
+ j(
383
+ cls.meter,
384
+ tone === 'accent' && cls.meterAccent,
385
+ tone === 'success' && cls.meterSuccess,
386
+ tone === 'warning' && cls.meterWarning,
387
+ tone === 'danger' && cls.meterDanger,
388
+ ),
349
389
  dotspinner: ({ size } = {}) =>
350
390
  j(cls.dotspinner, size === 'sm' && cls.dotspinnerSm, size === 'lg' && cls.dotspinnerLg),
351
391
  dotbar: ({ indeterminate } = {}) => j(cls.dotbar, indeterminate && cls.dotbarIndeterminate),
@@ -357,6 +397,7 @@ export const ui = {
357
397
  container: ({ narrow, wide } = {}) =>
358
398
  j(cls.container, narrow && cls.containerNarrow, wide && cls.containerWide),
359
399
  tag: ({ accent } = {}) => j(cls.tag, accent && cls.tagAccent),
400
+ inputIcon: ({ end } = {}) => j(cls.inputIcon, end && cls.inputIconEnd),
360
401
  };
361
402
 
362
403
  export default ui;