@threadlabs/looma 0.26.1 → 0.26.3

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 (39) hide show
  1. package/components/ui-avatar/ui-avatar.html +6 -1
  2. package/components/ui-badge/ui-badge.html +101 -12
  3. package/components/ui-container/ui-container.html +11 -0
  4. package/components/ui-icon-button/ui-icon-button.html +58 -12
  5. package/components/ui-list-item/ui-list-item.html +13 -0
  6. package/dist/index.js +1 -1
  7. package/package.json +1 -1
  8. package/styles/ui-avatar.css +4 -0
  9. package/styles/ui-badge.css +107 -8
  10. package/styles/ui-container.css +8 -0
  11. package/styles/ui-icon-button.css +62 -12
  12. package/styles/ui-list-item.css +12 -0
  13. package/vanilla/UiBadge.d.ts +3 -3
  14. package/vanilla/UiBadge.js +1 -1
  15. package/vanilla/UiContainer.d.ts +1 -0
  16. package/vanilla/UiContainer.js +5 -5
  17. package/vanilla/UiIconButton.d.ts +1 -0
  18. package/vanilla/UiIconButton.js +4 -4
  19. package/vanilla/UiListItem.d.ts +1 -0
  20. package/vanilla/UiListItem.js +6 -6
  21. package/vue/UiAvatar.vue +6 -1
  22. package/vue/UiBadge.d.ts +9 -9
  23. package/vue/UiBadge.vue +123 -21
  24. package/vue/UiCard.d.ts +1 -1
  25. package/vue/UiContainer.d.ts +9 -0
  26. package/vue/UiContainer.vue +27 -1
  27. package/vue/UiIconButton.d.ts +9 -0
  28. package/vue/UiIconButton.vue +95 -12
  29. package/vue/UiListItem.d.ts +9 -0
  30. package/vue/UiListItem.vue +31 -0
  31. package/vue/UiPopover.d.ts +1 -1
  32. package/vue/UiScrollArea.d.ts +1 -1
  33. package/vue/UiTooltip.d.ts +1 -1
  34. package/vue/chunks/UiAvatar.js +2 -2
  35. package/vue/chunks/UiBadge.js +13 -7
  36. package/vue/chunks/UiContainer.js +23 -1
  37. package/vue/chunks/UiIconButton.js +25 -1
  38. package/vue/chunks/UiListItem.js +26 -2
  39. package/vue/components.css +319 -147
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@threadlabs/looma",
3
- "version": "0.26.1",
3
+ "version": "0.26.3",
4
4
  "type": "module",
5
5
  "description": "Looma components as HTML, Vue, and plain DOM, with design tokens.",
6
6
  "license": "MIT",
@@ -61,6 +61,10 @@
61
61
  border-color: var(--ui-avatar-border, var(--ui-border-strong));
62
62
  }
63
63
 
64
+ :scope[data-ui-avatar-state~="shape=circle"] {
65
+ box-shadow: var(--ui-shadow-sm);
66
+ }
67
+
64
68
  :scope[data-ui-avatar-state~="shape=square"] {
65
69
  border-radius: var(--ui-avatar-radius, var(--ui-radius-md));
66
70
  }
@@ -7,6 +7,10 @@
7
7
  syntax: "*";
8
8
  inherits: false;
9
9
  }
10
+ @property --ui-badge-circle-size {
11
+ syntax: "*";
12
+ inherits: false;
13
+ }
10
14
  @property --ui-badge-dot-size {
11
15
  syntax: "*";
12
16
  inherits: false;
@@ -120,25 +124,26 @@
120
124
  text-box-trim: none;
121
125
  }
122
126
 
123
- /* Text-only badges retain their trimmed inline baseline. Icon content needs a flex row;
124
- trimming its label separately keeps the glyph centered without changing adjacent lines. */
127
+ /* Center the capital/baseline text box, while clipping only inline overflow for ellipsis.
128
+ Vertical ink remains visible so trimming never cuts off ascenders or descenders. */
125
129
  @supports (text-box-trim: trim-both) {
126
130
  :scope {
127
131
  display: inline-block;
128
132
  min-block-size: var(--ui-badge-min-block-size, 1.5rem);
129
133
  text-align: center;
130
134
  align-content: center;
131
- text-box-trim: trim-both;
132
- text-box-edge: text;
135
+ text-box-trim: none;
133
136
  }
134
137
  :scope:has(.icon:not(:empty)),
135
138
  :scope:has(.label svg) {
136
139
  display: inline-flex;
137
140
  text-box-trim: none;
138
141
  }
139
- :scope:has(.icon:not(:empty)) .label {
142
+ .label:not(:has(svg)) {
140
143
  text-box-trim: trim-both;
141
- text-box-edge: text;
144
+ text-box-edge: cap alphabetic;
145
+ overflow-x: clip;
146
+ overflow-y: visible;
142
147
  }
143
148
  }
144
149
 
@@ -219,6 +224,29 @@
219
224
  --_badge-color-text: color-mix(in oklab, var(--_ui-badge-color) 0%, var(--ui-surface-default));
220
225
  }
221
226
 
227
+ /* A translucent inverse surface picks up its filled control's tone. Deepen the tone ink
228
+ toward the theme text to retain contrast against that composed surface. */
229
+ :scope[data-ui-badge-state~="variant=inverse"] {
230
+ --_badge-color-surface: color-mix(in srgb, color-mix(in oklab, var(--_ui-badge-color) 0%, var(--ui-surface-default)) 88%, transparent);
231
+ --_badge-color-border: var(--_badge-color-surface);
232
+ --_badge-color-text: color-mix(in srgb, var(--_badge-color-ink) 85%, var(--ui-text-primary));
233
+ --_badge-outline-surface: var(--ui-badge-surface, var(--_badge-color-surface, color-mix(in srgb, var(--_badge-text) 88%, transparent)));
234
+ border-color: var(--ui-badge-border, var(--_badge-color-border, color-mix(in srgb, var(--_badge-text) 88%, transparent)));
235
+ color: var(--ui-badge-text, var(--_badge-color-text, color-mix(in srgb, var(--_badge-surface) 85%, var(--ui-text-primary))));
236
+ /* Paint each translucent layer once, including the border, without a bright double-painted ring. */
237
+ background-clip: padding-box;
238
+ }
239
+
240
+ :scope[data-ui-badge-state~="variant=inverse"][data-ui-badge-state~="shape=circle"] {
241
+ box-shadow: var(--ui-shadow-sm);
242
+ }
243
+
244
+ /* Neutral reverses a subtle surface/ink pair rather than a strong tone. Keep its original
245
+ surface ink: moving it toward dark-theme primary text would reduce overlay contrast. */
246
+ :scope[data-ui-badge-state~="variant=inverse"][data-ui-badge-state~="tone=neutral"] {
247
+ color: var(--ui-badge-text, var(--_badge-color-text, var(--_badge-surface)));
248
+ }
249
+
222
250
  :scope[data-ui-badge-state~="variant=outline"] {
223
251
  --_badge-surface: var(--ui-surface-default);
224
252
  --_badge-border: var(--ui-border-strong);
@@ -272,6 +300,10 @@
272
300
  --_badge-square-size: var(--ui-badge-square-size, calc(var(--ui-space-3) * 2));
273
301
  }
274
302
 
303
+ :scope[data-ui-badge-state~="shape=square"][data-ui-badge-state~="size=lg"] {
304
+ --_badge-square-size: var(--ui-badge-square-size, calc(var(--ui-space-4) * 2 + var(--ui-space-2)));
305
+ }
306
+
275
307
  :scope[data-ui-badge-state~="shape=square"] .label {
276
308
  display: inline-flex;
277
309
  align-items: center;
@@ -282,6 +314,52 @@
282
314
  display: none;
283
315
  }
284
316
 
317
+ /* A visible glyph needs fixed geometry, unlike a content-sized pill or a hidden-label dot. */
318
+ :scope[data-ui-badge-state~="shape=circle"] {
319
+ --_badge-circle-size: var(--ui-badge-circle-size, calc(var(--ui-space-3) * 2));
320
+ display: inline-flex;
321
+ flex: none;
322
+ inline-size: var(--_badge-circle-size);
323
+ block-size: var(--_badge-circle-size);
324
+ min-inline-size: var(--_badge-circle-size);
325
+ min-block-size: var(--_badge-circle-size);
326
+ padding: 0;
327
+ border-radius: var(--ui-radius-round);
328
+ text-box-trim: none;
329
+ }
330
+
331
+ :scope[data-ui-badge-state~="shape=circle"][data-ui-badge-state~="size=xs"] {
332
+ --_badge-circle-size: var(--ui-badge-circle-size, var(--ui-space-4));
333
+ }
334
+
335
+ :scope[data-ui-badge-state~="shape=circle"][data-ui-badge-state~="size=lg"] {
336
+ --_badge-circle-size: var(--ui-badge-circle-size, calc(var(--ui-space-4) * 2));
337
+ }
338
+
339
+ :scope[data-ui-badge-state~="shape=circle"] .label {
340
+ display: inline-flex;
341
+ align-items: center;
342
+ justify-content: center;
343
+ line-height: 1;
344
+ text-box-trim: none;
345
+ }
346
+
347
+ :scope[data-ui-badge-state~="shape=circle"] .label:empty {
348
+ display: none;
349
+ }
350
+
351
+ /* Center capital and numeral ink, rather than the font's ascent/descent line box.
352
+ Icon labels retain flex geometry; untrimmed browsers keep that fallback too. */
353
+ @supports (text-box-trim: trim-both) {
354
+ :scope[data-ui-badge-state~="shape=circle"] .label:not(:has(svg)),
355
+ :scope[data-ui-badge-state~="shape=square"] .label:not(:has(svg)) {
356
+ display: block;
357
+ text-box-trim: trim-both;
358
+ text-box-edge: cap alphabetic;
359
+ overflow: visible;
360
+ }
361
+ }
362
+
285
363
  /* A dot uses the badge's tone recipe; only its silhouette and visible label change. */
286
364
  :scope[data-ui-badge-state~="shape=dot"] {
287
365
  position: relative;
@@ -329,6 +407,10 @@
329
407
  pointer-events: none;
330
408
  }
331
409
 
410
+ :scope[data-ui-badge-state~="shape=tag"][data-ui-badge-state~="variant=inverse"] {
411
+ background: var(--ui-badge-border, var(--_badge-color-border, var(--_badge-text)));
412
+ }
413
+
332
414
  :scope[data-ui-badge-state~="shape=tag"]:dir(rtl) {
333
415
  padding-inline-end: var(--ui-badge-padding-inline, var(--ui-space-2));
334
416
  padding-inline-start: calc(var(--ui-badge-padding-inline, var(--ui-space-2)) + var(--_badge-point));
@@ -385,20 +467,37 @@
385
467
  margin-inline-end: var(--ui-space-1);
386
468
  }
387
469
 
388
- /* Text-only badges retain their trimmed inline baseline. Icon content needs a flex row;
389
- trimming its label separately keeps the glyph centered without changing adjacent lines. */
470
+ /* Center the capital/baseline text box, while clipping only inline overflow for ellipsis.
471
+ Vertical ink remains visible so trimming never cuts off ascenders or descenders. */
390
472
  @supports (text-box-trim: trim-both) {
391
473
  }
392
474
 
393
475
  /* A solid colour fills with the ink. Its text is the page surface, mixed at 0% only so that it too is
394
476
  invalid while the hook is unset. */
395
477
 
478
+ /* A translucent inverse surface picks up its filled control's tone. Deepen the tone ink
479
+ toward the theme text to retain contrast against that composed surface. */
480
+
481
+ /* Neutral reverses a subtle surface/ink pair rather than a strong tone. Keep its original
482
+ surface ink: moving it toward dark-theme primary text would reduce overlay contrast. */
483
+
396
484
  /* An icon mark keeps the existing tone recipe and follows the global spacing and radius. */
397
485
 
398
486
  :scope[data-ui-badge-state~="shape=square"] .label :where([data-slotted], [data-slotted] *):is(svg) {
399
487
  margin-inline-end: 0;
400
488
  }
401
489
 
490
+ /* A visible glyph needs fixed geometry, unlike a content-sized pill or a hidden-label dot. */
491
+
492
+ :scope[data-ui-badge-state~="shape=circle"] .label :where([data-slotted], [data-slotted] *):is(svg) {
493
+ margin-inline-end: 0;
494
+ }
495
+
496
+ /* Center capital and numeral ink, rather than the font's ascent/descent line box.
497
+ Icon labels retain flex geometry; untrimmed browsers keep that fallback too. */
498
+ @supports (text-box-trim: trim-both) {
499
+ }
500
+
402
501
  /* A dot uses the badge's tone recipe; only its silhouette and visible label change. */
403
502
 
404
503
  /* The clipped point needs an inset surface over an outer border-colour shape: a native border
@@ -23,6 +23,14 @@
23
23
  :scope[data-ui-container-state~="measure=narrow"] {
24
24
  --_container-measure: 45ch;
25
25
  }
26
+ :scope[data-ui-container-state~="fill"] {
27
+ box-sizing: border-box;
28
+ display: flex;
29
+ flex-direction: column;
30
+ flex: 1;
31
+ min-inline-size: 0;
32
+ min-block-size: 0;
33
+ }
26
34
  :scope[data-ui-container-state~="measure=wide"] {
27
35
  --_container-measure: 80ch;
28
36
  }
@@ -87,6 +87,21 @@
87
87
  color: var(--ui-text-secondary);
88
88
  }
89
89
 
90
+ :scope[data-ui-icon-button-state~="tone=accent"] {
91
+ --_icon-button-border: var(--ui-accent-solid);
92
+ --_icon-button-hover-border: var(--ui-accent-hover);
93
+ --_icon-button-text: var(--ui-accent-subtle-text, var(--ui-accent-solid));
94
+ --_icon-button-outline-surface: color-mix(in srgb, var(--ui-accent-solid) 5%, var(--ui-surface-default));
95
+ color: var(--_icon-button-text);
96
+ }
97
+
98
+ /* Omitted tone retains the existing accent solid; explicit neutral is additive. */
99
+ :scope[data-ui-icon-button-state~="tone=neutral"] {
100
+ --_icon-button-solid-surface: var(--ui-text-primary);
101
+ --_icon-button-solid-hover-surface: var(--ui-text-primary);
102
+ --_icon-button-solid-text: var(--ui-surface-default);
103
+ }
104
+
90
105
  :scope[data-ui-icon-button-state~="anticipatory"] {
91
106
  position: relative;
92
107
  }
@@ -119,13 +134,13 @@
119
134
  }
120
135
 
121
136
  @media (hover: hover) and (pointer: fine) {
122
- :scope[data-ui-icon-button-state~="anticipatory"]:not(:scope[data-ui-icon-button-state~="variant=solid"], :scope[data-ui-icon-button-state~="loading"], :scope[data-ui-icon-button-state~="pending"], :disabled, [data-ui-proximity="near"], :hover, :focus-visible) {
137
+ :scope[data-ui-icon-button-state~="anticipatory"]:not(:scope[data-ui-icon-button-state~="variant=solid"], :scope[data-ui-icon-button-state~="loading"], :scope[data-ui-icon-button-state~="pending"], :disabled, [aria-pressed="true"], [data-ui-proximity="near"], :hover, :focus-visible) {
123
138
  border-color: transparent;
124
139
  background-color: transparent;
125
140
  box-shadow: none;
126
141
  }
127
142
 
128
- :scope[data-ui-icon-button-state~="anticipatory"]:not(:scope[data-ui-icon-button-state~="variant=solid"], :scope[data-ui-icon-button-state~="loading"], :scope[data-ui-icon-button-state~="pending"], :disabled, [data-ui-proximity="near"], :hover, :focus-visible)
143
+ :scope[data-ui-icon-button-state~="anticipatory"]:not(:scope[data-ui-icon-button-state~="variant=solid"], :scope[data-ui-icon-button-state~="loading"], :scope[data-ui-icon-button-state~="pending"], :disabled, [aria-pressed="true"], [data-ui-proximity="near"], :hover, :focus-visible)
129
144
  .content {
130
145
  opacity: 0;
131
146
  transform: scale(0.65);
@@ -133,7 +148,7 @@
133
148
  }
134
149
  }
135
150
 
136
- :scope[data-ui-icon-button-state~="anticipatory"]:is(:scope[data-ui-icon-button-state~="variant=solid"], [data-ui-proximity="near"], :hover, :focus-visible)::before {
151
+ :scope[data-ui-icon-button-state~="anticipatory"]:is(:scope[data-ui-icon-button-state~="variant=solid"], [aria-pressed="true"], [data-ui-proximity="near"], :hover, :focus-visible)::before {
137
152
  opacity: 0;
138
153
  }
139
154
 
@@ -243,17 +258,17 @@
243
258
  }
244
259
 
245
260
  :scope[data-ui-icon-button-state~="variant=outline"] {
246
- border-color: var(--ui-icon-button-border, var(--ui-control-border, var(--ui-border-strong)));
247
- background-color: var(--ui-icon-button-outline-surface, var(--ui-surface-default));
261
+ border-color: var(--ui-icon-button-border, var(--_icon-button-border, var(--ui-control-border, var(--ui-border-strong))));
262
+ background-color: var(--ui-icon-button-outline-surface, var(--_icon-button-outline-surface, var(--ui-surface-default)));
248
263
  background-image: var(--ui-raised-highlight);
249
- color: var(--ui-icon-button-outline-text, var(--ui-text-primary));
264
+ color: var(--ui-icon-button-outline-text, var(--_icon-button-text, var(--ui-text-primary)));
250
265
  box-shadow: var(--ui-raised);
251
266
  }
252
267
 
253
268
  :scope[data-ui-icon-button-state~="variant=solid"] {
254
- background-color: var(--ui-icon-button-solid-surface, var(--ui-accent-solid));
269
+ background-color: var(--ui-icon-button-solid-surface, var(--_icon-button-solid-surface, var(--ui-accent-solid)));
255
270
  background-image: var(--ui-raised-highlight);
256
- color: var(--ui-icon-button-solid-text, var(--ui-text-on-accent));
271
+ color: var(--ui-icon-button-solid-text, var(--_icon-button-solid-text, var(--ui-text-on-accent)));
257
272
  box-shadow: var(
258
273
  --ui-icon-button-solid-shadow,
259
274
  0 1px 2px color-mix(in srgb, var(--ui-accent-solid) 18%, transparent)
@@ -263,20 +278,32 @@
263
278
  :scope:hover:enabled:not([aria-disabled="true"]) {
264
279
  /* Derived from the text colour so the ghost hover shows on any background. */
265
280
  background-color: var(--ui-icon-button-hover-surface, color-mix(in srgb, currentColor 8%, transparent));
266
- color: var(--ui-icon-button-hover-text, var(--ui-text-primary));
281
+ color: var(--ui-icon-button-hover-text, var(--_icon-button-text, var(--ui-text-primary)));
267
282
  }
268
283
 
269
284
  :scope[data-ui-icon-button-state~="variant=outline"]:hover:enabled:not([aria-disabled="true"]) {
270
- border-color: var(--ui-icon-button-hover-border, var(--ui-control-border-hover, var(--ui-control-border)));
285
+ border-color: var(--ui-icon-button-hover-border, var(--_icon-button-hover-border, var(--ui-control-border-hover, var(--ui-control-border))));
271
286
  }
272
287
 
273
288
  :scope[data-ui-icon-button-state~="variant=solid"]:hover:enabled:not([aria-disabled="true"]) {
274
- background-color: var(--ui-icon-button-solid-hover-surface, var(--ui-accent-hover));
289
+ background-color: var(--ui-icon-button-solid-hover-surface, var(--_icon-button-solid-hover-surface, var(--ui-accent-hover)));
290
+ }
291
+
292
+ :scope[data-ui-icon-button-state~="variant=solid"][data-ui-icon-button-state~="tone=accent"]:hover:enabled:not([aria-disabled="true"]),
293
+ :scope[data-ui-icon-button-state~="variant=solid"][data-ui-icon-button-state~="tone=neutral"]:hover:enabled:not([aria-disabled="true"]) {
294
+ color: var(--ui-icon-button-solid-text, var(--_icon-button-solid-text, var(--ui-text-on-accent)));
275
295
  }
276
296
 
277
297
  :scope[data-ui-icon-button-state~="variant=ghost"][aria-expanded="true"]:enabled:not([aria-disabled="true"]) {
278
298
  background-color: var(--ui-icon-button-active-surface, color-mix(in srgb, currentColor 16%, transparent));
279
- color: var(--ui-icon-button-hover-text, var(--ui-text-primary));
299
+ color: var(--ui-icon-button-hover-text, var(--_icon-button-text, var(--ui-text-primary)));
300
+ box-shadow: var(--ui-pressed);
301
+ }
302
+
303
+ /* Native toggle state persists after pointer release, including during loading. */
304
+ :scope[data-ui-icon-button-state~="variant=outline"][aria-pressed="true"]:enabled {
305
+ background-color: var(--ui-icon-button-active-surface, color-mix(in srgb, currentColor 12%, var(--ui-surface-default)));
306
+ background-image: none;
280
307
  box-shadow: var(--ui-pressed);
281
308
  }
282
309
 
@@ -309,6 +336,22 @@
309
336
  box-shadow: var(--ui-focus-halo);
310
337
  }
311
338
 
339
+ :scope[data-ui-icon-button-state~="variant=outline"][aria-pressed="true"]:enabled:focus-visible {
340
+ box-shadow:
341
+ var(--ui-pressed),
342
+ var(--ui-focus-halo);
343
+ }
344
+
345
+ @media (forced-colors: active) {
346
+ :scope[data-ui-icon-button-state~="variant=outline"][aria-pressed="true"]:enabled {
347
+ outline: var(--ui-focus-width) solid Highlight;
348
+ outline-offset: calc(-2 * var(--ui-focus-width));
349
+ }
350
+ :scope[data-ui-icon-button-state~="variant=outline"][aria-pressed="true"]:enabled:focus-visible {
351
+ outline-offset: var(--ui-focus-width);
352
+ }
353
+ }
354
+
312
355
  /* Disabled is a flat neutral control, independent of the active variant's tone. */
313
356
  :scope:disabled {
314
357
  border-color: var(--ui-disabled-border);
@@ -349,6 +392,8 @@
349
392
  @scope ([data-component~="ui-icon-button"]) to ([data-component]) {
350
393
  /* This component's hooks style the element they are set on, and never a nested one. */
351
394
 
395
+ /* Omitted tone retains the existing accent solid; explicit neutral is additive. */
396
+
352
397
  /* At rest an anticipatory button shows only its guide dot. The icon grows into place when
353
398
  proximity, hover, or focus reveals it; the button's hit area and guide remain stable. */
354
399
 
@@ -368,9 +413,14 @@
368
413
  @media (pointer: coarse) {
369
414
  }
370
415
 
416
+ /* Native toggle state persists after pointer release, including during loading. */
417
+
371
418
  @media (prefers-reduced-motion: reduce) {
372
419
  }
373
420
 
421
+ @media (forced-colors: active) {
422
+ }
423
+
374
424
  /* Disabled is a flat neutral control, independent of the active variant's tone. */
375
425
 
376
426
  :where([data-slotted], [data-slotted] *):is(svg) {
@@ -134,6 +134,16 @@
134
134
  line-height: var(--ui-line-height-tight);
135
135
  }
136
136
 
137
+ /* Keep identity and disclosure marks beside the first line, even with a long description. */
138
+ :scope[data-ui-list-item-state~="align=title"] {
139
+ align-items: flex-start;
140
+ }
141
+
142
+ :scope[data-ui-list-item-state~="align=title"] .leading,
143
+ :scope[data-ui-list-item-state~="align=title"] .trailing {
144
+ min-block-size: calc(var(--ui-font-size-md) * var(--ui-line-height-tight));
145
+ }
146
+
137
147
  .description {
138
148
  color: var(--ui-text-muted);
139
149
  font-size: var(--ui-font-size-sm);
@@ -180,6 +190,8 @@
180
190
 
181
191
  /* Trailing controls take their own clicks, above the title link's stretched hit area. */
182
192
 
193
+ /* Keep identity and disclosure marks beside the first line, even with a long description. */
194
+
183
195
  .title > :where([data-slotted], [data-slotted] *):is(a) {
184
196
  color: inherit;
185
197
  text-decoration: none;
@@ -6,10 +6,10 @@ export interface UiBadgeElement extends HTMLSpanElement {
6
6
  removeEventListener<K extends keyof UiBadgeEventMap>(type: K, listener: (this: UiBadgeElement, event: UiBadgeEventMap[K]) => unknown, options?: boolean | EventListenerOptions): void;
7
7
  }
8
8
  export interface UiBadgeProps {
9
- shape?: "pill" | "tag" | "dot" | "square" | null;
10
- size?: "xs" | "md" | null;
9
+ shape?: "pill" | "tag" | "dot" | "square" | "circle" | null;
10
+ size?: "xs" | "md" | "lg" | null;
11
11
  tone?: "neutral" | "accent" | "info" | "success" | "warning" | "danger" | null;
12
- variant?: "solid" | "subtle" | "outline" | null;
12
+ variant?: "solid" | "subtle" | "outline" | "inverse" | null;
13
13
  attributes?: Readonly<Record<string, string | number | boolean | null | undefined>>;
14
14
  children?: readonly (string | Node)[];
15
15
  slots?: Readonly<Record<string, readonly (string | Node)[]>>;
@@ -7,7 +7,7 @@ const T1 = ["",[]];
7
7
  const T2 = ["",[]];
8
8
  let P0, P1, P2;
9
9
  const S = { n: [], t: [], f: import.meta.url, g: "ui-badge" };
10
- const D = { props: { "shape": { "required":false,"values":["pill","tag","dot","square"],"default":"pill", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "size": { "required":false,"values":["xs","md"],"default":"md", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "tone": { "required":false,"values":["neutral","accent","info","success","warning","danger"],"default":"neutral", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "variant": { "required":false,"values":["solid","subtle","outline"],"default":"subtle", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] } } };
10
+ const D = { props: { "shape": { "required":false,"values":["pill","tag","dot","square","circle"],"default":"pill", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "size": { "required":false,"values":["xs","md","lg"],"default":"md", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "tone": { "required":false,"values":["neutral","accent","info","success","warning","danger"],"default":"neutral", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "variant": { "required":false,"values":["solid","subtle","outline","inverse"],"default":"subtle", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] } } };
11
11
 
12
12
  export function createUiBadge(options = {}, html) {
13
13
  const { attributes = {}, children = [], slots = {}, ...componentProps } = options;
@@ -6,6 +6,7 @@ export interface UiContainerElement extends HTMLDivElement {
6
6
  removeEventListener<K extends keyof UiContainerEventMap>(type: K, listener: (this: UiContainerElement, event: UiContainerEventMap[K]) => unknown, options?: boolean | EventListenerOptions): void;
7
7
  }
8
8
  export interface UiContainerProps {
9
+ fill?: boolean | null;
9
10
  gutters?: "s" | "m" | "l" | null;
10
11
  measure?: "narrow" | "wide" | null;
11
12
  attributes?: Readonly<Record<string, string | number | boolean | null | undefined>>;
@@ -1,12 +1,12 @@
1
1
  // Generated by HTML Next 1.0.0-alpha.35 for Vanilla DOM. Do not edit.
2
- import { attachGeneratedController, buildTemplate, writeAttribute, dispose, formatOf, keywordFormat, hostState, acceptProps, manageProps, project, fillSlot, checkFormat } from "@nextwebwg/html-next/generated-runtime";
2
+ import { attachGeneratedController, buildTemplate, writeAttribute, dispose, formatOf, keywordFormat, hostState, acceptProps, manageProps, project, fillSlot, checkBoolean, checkFormat } from "@nextwebwg/html-next/generated-runtime";
3
3
  import "../styles/ui-container.css";
4
4
 
5
5
  const T0 = ["",[],4];
6
6
  const T1 = ["",[]];
7
7
  let P0, P1;
8
8
  const S = { n: [], t: [], f: import.meta.url, g: "ui-container" };
9
- const D = { props: { "gutters": { "required":false,"values":["s","m","l"], c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "measure": { "required":false,"values":["narrow","wide"], c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] } } };
9
+ const D = { props: { "fill": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "gutters": { "required":false,"values":["s","m","l"], c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "measure": { "required":false,"values":["narrow","wide"], c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] } } };
10
10
 
11
11
  export function createUiContainer(options = {}, html) {
12
12
  const { attributes = {}, children = [], slots = {}, ...componentProps } = options;
@@ -17,7 +17,7 @@ export function createUiContainer(options = {}, html) {
17
17
  }
18
18
  element.setAttribute("data-component", "ui-container");
19
19
  element.append((P0 ??= buildTemplate(T0)).cloneNode(true));
20
- const v = [null, null];
20
+ const v = [null, null, null];
21
21
  const B = acceptProps(element, D, S.n, v, componentProps, [], html);
22
22
  const m1 = (d) => {
23
23
  const n = (P1 ??= buildTemplate(T1)).cloneNode(true);
@@ -28,8 +28,8 @@ export function createUiContainer(options = {}, html) {
28
28
  const p1 = (r, c, d) => {
29
29
  };
30
30
  const p0 = (r, c, d) => {
31
- if (c & 3) {
32
- const x0 = hostState(["measure","gutters"], [v[1], v[0]]);
31
+ if (c & 7) {
32
+ const x0 = hostState(["measure","fill","gutters"], [v[2], v[0], v[1]]);
33
33
  const s0 = x0;
34
34
  if (s0 !== r.v0) writeAttribute(r.n, "data-ui-container-state", (r.v0 = s0) === "" ? null : s0);
35
35
  }
@@ -14,6 +14,7 @@ export interface UiIconButtonProps {
14
14
  pending?: boolean | null;
15
15
  round?: boolean | null;
16
16
  size?: "sm" | "md" | "lg" | null;
17
+ tone?: "accent" | "neutral" | null;
17
18
  variant?: "ghost" | "outline" | "solid" | null;
18
19
  attributes?: Readonly<Record<string, string | number | boolean | null | undefined>>;
19
20
  children?: readonly (string | Node)[];
@@ -8,7 +8,7 @@ const T1 = ["ui-spinner",["size","sm"]];
8
8
  const T2 = ["",[]];
9
9
  let P0, P1, P2;
10
10
  const S = { n: [], t: [], f: import.meta.url, g: "ui-icon-button" };
11
- const D = { props: { "anticipatory": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "disabled": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "label": { "required":false, c: checkString, t: "string", j: 0, m: "s" }, "loading": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "matchButton": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "pending": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "round": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "size": { "required":false,"values":["sm","md","lg"],"default":"md", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "variant": { "required":false,"values":["ghost","outline","solid"],"default":"ghost", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] } } };
11
+ const D = { props: { "anticipatory": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "disabled": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "label": { "required":false, c: checkString, t: "string", j: 0, m: "s" }, "loading": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "matchButton": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "pending": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "round": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "size": { "required":false,"values":["sm","md","lg"],"default":"md", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "tone": { "required":false,"values":["accent","neutral"], c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "variant": { "required":false,"values":["ghost","outline","solid"],"default":"ghost", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] } } };
12
12
  const C = (host) => controller.default(host);
13
13
 
14
14
  export function createUiIconButton(options = {}, html) {
@@ -21,7 +21,7 @@ export function createUiIconButton(options = {}, html) {
21
21
  if (!element.hasAttribute("type")) element.setAttribute("type", "button");
22
22
  element.setAttribute("data-component", "ui-icon-button");
23
23
  element.append((P0 ??= buildTemplate(T0)).cloneNode(true));
24
- const v = [null, null, null, null, null, null, null, null, null];
24
+ const v = [null, null, null, null, null, null, null, null, null, null];
25
25
  const B = acceptProps(element, D, S.n, v, componentProps, ["matchButton"], html);
26
26
  const m1 = (d) => {
27
27
  const n = (P1 ??= buildTemplate(T1)).cloneNode(true);
@@ -67,8 +67,8 @@ export function createUiIconButton(options = {}, html) {
67
67
  const s4 = toAttribute(x4, "aria-busy");
68
68
  if (s4 !== r.v4) writeAttribute(r.n, "aria-busy", r.v4 = s4);
69
69
  }
70
- if (c & 505) {
71
- const x5 = hostState(["anticipatory","variant","loading","pending","size","matchButton","round"], [v[0], v[8], v[3], v[5], v[7], v[4], v[6]]);
70
+ if (c & 1017) {
71
+ const x5 = hostState(["tone","anticipatory","variant","loading","pending","size","matchButton","round"], [v[8], v[0], v[9], v[3], v[5], v[7], v[4], v[6]]);
72
72
  const s5 = x5;
73
73
  if (s5 !== r.v5) writeAttribute(r.n, "data-ui-icon-button-state", (r.v5 = s5) === "" ? null : s5);
74
74
  }
@@ -6,6 +6,7 @@ export interface UiListItemElement extends HTMLLIElement {
6
6
  removeEventListener<K extends keyof UiListItemEventMap>(type: K, listener: (this: UiListItemElement, event: UiListItemEventMap[K]) => unknown, options?: boolean | EventListenerOptions): void;
7
7
  }
8
8
  export interface UiListItemProps {
9
+ align?: "center" | "title" | null;
9
10
  current?: boolean | null;
10
11
  highlighted?: boolean | null;
11
12
  leadingInteractive?: boolean | null;
@@ -9,7 +9,7 @@ const T3 = ["",[]];
9
9
  const T4 = ["",[]];
10
10
  let P0, P1, P2, P3, P4;
11
11
  const S = { n: [], t: [], f: import.meta.url, g: "ui-list-item" };
12
- const D = { props: { "current": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "highlighted": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "leadingInteractive": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "variant": { "required":false,"values":["row","card"],"default":"row", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "wrap": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" } } };
12
+ const D = { props: { "align": { "required":false,"values":["center","title"],"default":"center", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "current": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "highlighted": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "leadingInteractive": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "variant": { "required":false,"values":["row","card"],"default":"row", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "wrap": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" } } };
13
13
 
14
14
  export function createUiListItem(options = {}, html) {
15
15
  const { attributes = {}, children = [], slots = {}, ...componentProps } = options;
@@ -20,7 +20,7 @@ export function createUiListItem(options = {}, html) {
20
20
  }
21
21
  element.setAttribute("data-component", "ui-list-item");
22
22
  element.append((P0 ??= buildTemplate(T0)).cloneNode(true));
23
- const v = [null, null, null, null, null];
23
+ const v = [null, null, null, null, null, null];
24
24
  const B = acceptProps(element, D, S.n, v, componentProps, [], html);
25
25
  const m1 = (d) => {
26
26
  const n = (P1 ??= buildTemplate(T1)).cloneNode(true);
@@ -55,13 +55,13 @@ export function createUiListItem(options = {}, html) {
55
55
  const p4 = (r, c, d) => {
56
56
  };
57
57
  const p0 = (r, c, d) => {
58
- if (c & 1) {
59
- const x0 = v[0];
58
+ if (c & 2) {
59
+ const x0 = v[1];
60
60
  const s0 = toAttribute(x0, "aria-current");
61
61
  if (s0 !== r.v0) writeAttribute(r.n, "aria-current", r.v0 = s0);
62
62
  }
63
- if (c & 31) {
64
- const x1 = hostState(["variant","highlighted","current","leadingInteractive","wrap"], [v[3], v[1], v[0], v[2], v[4]]);
63
+ if (c & 63) {
64
+ const x1 = hostState(["variant","highlighted","current","leadingInteractive","wrap","align"], [v[4], v[2], v[1], v[3], v[5], v[0]]);
65
65
  const s1 = x1;
66
66
  if (s1 !== r.v1) writeAttribute(r.n, "data-ui-list-item-state", (r.v1 = s1) === "" ? null : s1);
67
67
  }
package/vue/UiAvatar.vue CHANGED
@@ -66,7 +66,8 @@ const propValidityContract = {
66
66
  type: { kind: 'terminal', name: 'keyword' },
67
67
  required: false,
68
68
  target: { attribute: 'shape' },
69
- description: 'Circle by default; square uses the theme\'s medium radius.',
69
+ description:
70
+ 'Circle by default, with a shallow shadow; square uses the theme\'s medium radius.',
70
71
  values: ['circle', 'square'],
71
72
  default: 'circle',
72
73
  },
@@ -443,6 +444,10 @@ useComponentHost(() => import('../components/ui-avatar/ui-avatar.js'), {
443
444
  border-color: var(--ui-avatar-border, var(--ui-border-strong));
444
445
  }
445
446
 
447
+ [data-component~="ui-avatar"][data-ui-avatar-state~="shape=circle"] {
448
+ box-shadow: var(--ui-shadow-sm);
449
+ }
450
+
446
451
  [data-component~="ui-avatar"][data-ui-avatar-state~="shape=square"] {
447
452
  border-radius: var(--ui-avatar-radius, var(--ui-radius-md));
448
453
  }
package/vue/UiBadge.d.ts CHANGED
@@ -7,11 +7,11 @@ type __VLS_Slots = {} & {
7
7
  };
8
8
  declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
9
9
  shape: {
10
- type: PropType<"pill" | "tag" | "dot" | "square" | null>;
10
+ type: PropType<"pill" | "tag" | "dot" | "square" | "circle" | null>;
11
11
  default: string;
12
12
  };
13
13
  size: {
14
- type: PropType<"xs" | "md" | null>;
14
+ type: PropType<"xs" | "md" | "lg" | null>;
15
15
  default: string;
16
16
  };
17
17
  tone: {
@@ -19,16 +19,16 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
19
19
  default: string;
20
20
  };
21
21
  variant: {
22
- type: PropType<"solid" | "subtle" | "outline" | null>;
22
+ type: PropType<"solid" | "subtle" | "outline" | "inverse" | null>;
23
23
  default: string;
24
24
  };
25
25
  }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
26
26
  shape: {
27
- type: PropType<"pill" | "tag" | "dot" | "square" | null>;
27
+ type: PropType<"pill" | "tag" | "dot" | "square" | "circle" | null>;
28
28
  default: string;
29
29
  };
30
30
  size: {
31
- type: PropType<"xs" | "md" | null>;
31
+ type: PropType<"xs" | "md" | "lg" | null>;
32
32
  default: string;
33
33
  };
34
34
  tone: {
@@ -36,14 +36,14 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
36
36
  default: string;
37
37
  };
38
38
  variant: {
39
- type: PropType<"solid" | "subtle" | "outline" | null>;
39
+ type: PropType<"solid" | "subtle" | "outline" | "inverse" | null>;
40
40
  default: string;
41
41
  };
42
42
  }>> & Readonly<{}>, {
43
- shape: "dot" | "tag" | "square" | "pill" | null;
44
- size: "xs" | "md" | null;
43
+ shape: "circle" | "dot" | "tag" | "square" | "pill" | null;
44
+ size: "xs" | "md" | "lg" | null;
45
45
  tone: "info" | "neutral" | "accent" | "success" | "warning" | "danger" | null;
46
- variant: "solid" | "subtle" | "outline" | null;
46
+ variant: "solid" | "subtle" | "outline" | "inverse" | null;
47
47
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
48
48
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
49
49
  declare const _default: typeof __VLS_export;