@threadlabs/looma 0.26.4 → 0.26.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@threadlabs/looma",
3
- "version": "0.26.4",
3
+ "version": "0.26.5",
4
4
  "type": "module",
5
5
  "description": "Looma components as HTML, Vue, and plain DOM, with design tokens.",
6
6
  "license": "MIT",
@@ -11,6 +11,10 @@
11
11
  syntax: "*";
12
12
  inherits: false;
13
13
  }
14
+ @property --ui-badge-circle-opacity {
15
+ syntax: "*";
16
+ inherits: false;
17
+ }
14
18
  @property --ui-badge-dot-size {
15
19
  syntax: "*";
16
20
  inherits: false;
@@ -156,6 +160,11 @@
156
160
  line-height: var(--ui-badge-line-height, 1);
157
161
  }
158
162
 
163
+ :scope[data-ui-badge-state~="size=sm"] {
164
+ font-size: var(--ui-badge-font-size, var(--ui-font-size-2xs));
165
+ line-height: var(--ui-badge-line-height, 1);
166
+ }
167
+
159
168
  :scope[data-ui-badge-state~="tone=accent"] {
160
169
  --_badge-surface: var(--ui-accent-strong-surface, var(--ui-accent-solid));
161
170
  --_badge-border: var(--ui-accent-strong-surface, var(--ui-accent-solid));
@@ -237,8 +246,35 @@
237
246
  background-clip: padding-box;
238
247
  }
239
248
 
249
+ /* A circular count has a tone-tinted surface, a separate lighter rim and readable tone ink.
250
+ Dark controls retain a shaded surface and theme ink with a quieter rim. */
240
251
  :scope[data-ui-badge-state~="variant=inverse"][data-ui-badge-state~="shape=circle"] {
241
- box-shadow: var(--ui-shadow-sm);
252
+ --_badge-color-surface: light-dark(
253
+ color-mix(in srgb, color-mix(in oklab, var(--_ui-badge-color) 0%, var(--ui-surface-default)) var(--ui-badge-circle-opacity, 66%), transparent),
254
+ color-mix(in srgb, color-mix(in oklab, var(--_ui-badge-color) 0%, var(--ui-surface-default)) var(--ui-badge-circle-opacity, 56%), transparent)
255
+ );
256
+ --_badge-color-border: light-dark(
257
+ color-mix(in srgb, color-mix(in oklab, var(--_ui-badge-color) 0%, var(--ui-surface-default)) 88%, transparent),
258
+ color-mix(in srgb, color-mix(in oklab, var(--_ui-badge-color) 0%, var(--ui-surface-default)) 44%, transparent)
259
+ );
260
+ --_badge-color-text: light-dark(
261
+ color-mix(in srgb, var(--_badge-color-ink) 65%, var(--ui-text-primary)),
262
+ color-mix(in oklab, var(--_ui-badge-color) 0%, var(--ui-text-primary))
263
+ );
264
+ --_badge-count-surface: light-dark(
265
+ color-mix(in srgb, var(--_badge-text) var(--ui-badge-circle-opacity, 66%), transparent),
266
+ color-mix(in srgb, var(--_badge-text) var(--ui-badge-circle-opacity, 56%), transparent)
267
+ );
268
+ --_badge-outline-surface: var(--ui-badge-surface, var(--_badge-color-surface, var(--_badge-count-surface)));
269
+ border-color: var(
270
+ --ui-badge-border,
271
+ var(--_badge-color-border, light-dark(color-mix(in srgb, var(--_badge-text) 88%, transparent), color-mix(in srgb, var(--_badge-text) 44%, transparent)))
272
+ );
273
+ color: var(
274
+ --ui-badge-text,
275
+ var(--_badge-color-text, light-dark(color-mix(in srgb, var(--_badge-surface) 65%, var(--ui-text-primary)), var(--ui-text-primary)))
276
+ );
277
+ box-shadow: 0 1px 1px light-dark(rgb(0 0 0 / 0.08), rgb(0 0 0 / 0.12));
242
278
  }
243
279
 
244
280
  /* Neutral reverses a subtle surface/ink pair rather than a strong tone. Keep its original
@@ -478,6 +514,9 @@
478
514
  /* A translucent inverse surface picks up its filled control's tone. Deepen the tone ink
479
515
  toward the theme text to retain contrast against that composed surface. */
480
516
 
517
+ /* A circular count has a tone-tinted surface, a separate lighter rim and readable tone ink.
518
+ Dark controls retain a shaded surface and theme ink with a quieter rim. */
519
+
481
520
  /* Neutral reverses a subtle surface/ink pair rather than a strong tone. Keep its original
482
521
  surface ink: moving it toward dark-theme primary text would reduce overlay contrast. */
483
522
 
@@ -75,6 +75,16 @@
75
75
  /* This component's hooks style the element they are set on, and never a nested one. */
76
76
 
77
77
  :scope {
78
+ /* Optional inherited action-group values keep text and icon actions under the same light.
79
+ One fine top/start highlight follows the bottom/end lowlight at a joined action seam.
80
+ A single ambient lift avoids stacking a face gradient or another inset bevel. */
81
+ --_ui-action-start: 1px;
82
+ --_ui-action-edge: var(
83
+ --ui-action-edge,
84
+ inset var(--_ui-action-start) 1px 0 light-dark(rgb(255 255 255 / 0.08), rgb(255 255 255 / 0.06)),
85
+ inset calc(-1 * var(--_ui-action-start)) -1px 0 light-dark(rgb(0 0 0 / 0.08), rgb(0 0 0 / 0.08))
86
+ );
87
+ --_ui-action-shadow: var(--_ui-action-edge), 0 1px 2px light-dark(rgb(0 0 0 / 0.10), rgb(0 0 0 / 0.20));
78
88
  --_hue: var(--ui-accent);
79
89
  --_hue-hover: var(--ui-accent-hover);
80
90
  --_hue-active: var(--ui-accent-active);
@@ -95,6 +105,10 @@
95
105
  color: var(--_tone-text);
96
106
  }
97
107
 
108
+ :scope:dir(rtl) {
109
+ --_ui-action-start: -1px;
110
+ }
111
+
98
112
  :scope[data-ui-button-state~="align=start"] {
99
113
  justify-content: flex-start;
100
114
  text-align: start;
@@ -121,8 +135,8 @@
121
135
  white-space: var(--ui-button-white-space, inherit);
122
136
  line-height: var(--ui-line-height-tight);
123
137
  padding: var(--ui-space-2) var(--ui-space-3);
124
- background-image: var(--ui-raised-highlight);
125
- box-shadow: var(--ui-button-shadow, var(--ui-raised));
138
+ background-image: none;
139
+ box-shadow: var(--ui-button-shadow, var(--ui-action-shadow, var(--_ui-action-shadow)));
126
140
  cursor: pointer;
127
141
  transition:
128
142
  background-color var(--ui-motion-fast) var(--ui-motion-ease),
@@ -143,6 +157,22 @@
143
157
  vertical-align: middle;
144
158
  }
145
159
 
160
+ /* A count is an adornment, not another line of label text. Keep the standard control height
161
+ with enough inset for a 24px mark; large content can still grow the button naturally. */
162
+ :scope:not([variant="link"]):has(.badge:not(:empty)) {
163
+ padding-block: var(--ui-space-0-5);
164
+ }
165
+
166
+ .badge {
167
+ display: inline-flex;
168
+ align-items: center;
169
+ flex: none;
170
+ }
171
+
172
+ .badge:empty {
173
+ display: none;
174
+ }
175
+
146
176
  @supports (text-box-trim: trim-both) {
147
177
  :scope {
148
178
  text-box-trim: trim-both;
@@ -216,6 +246,15 @@
216
246
  background-color: var(--ui-button-hover-surface, oklch(from var(--_tone) l c h / 12%));
217
247
  }
218
248
 
249
+ /* A neutral card encloses content quietly; ordinary outlines keep their action-tone edge. */
250
+ :scope[data-ui-button-state~="variant=card"][data-ui-button-state~="tone=neutral"] {
251
+ border-color: var(--ui-button-border, var(--ui-border));
252
+ }
253
+
254
+ :scope[data-ui-button-state~="variant=card"][data-ui-button-state~="tone=neutral"]:hover:not(:disabled, [aria-disabled="true"]) {
255
+ border-color: var(--ui-button-hover-border, var(--ui-border-strong));
256
+ }
257
+
219
258
  /* Pressed reads as pressed in: the highlight goes, and the shadow moves inside. */
220
259
  :scope:active:not(:disabled, [aria-disabled="true"]) {
221
260
  background-color: var(--ui-button-active-surface, oklch(from var(--_tone) l c h / 18%));
@@ -227,7 +266,7 @@
227
266
  border-color: var(--_tone);
228
267
  background-color: var(--_tone);
229
268
  color: var(--_on-tone);
230
- box-shadow: var(--ui-button-solid-shadow, var(--ui-raised));
269
+ box-shadow: var(--ui-button-solid-shadow, var(--ui-action-shadow, var(--_ui-action-shadow)));
231
270
  }
232
271
 
233
272
  :is(:scope[data-ui-button-state~="variant=solid"], :scope[data-ui-button-state~="variant=danger"]):hover:not(:disabled, [aria-disabled="true"]) {
@@ -309,16 +348,20 @@
309
348
  :scope:focus-visible {
310
349
  outline: none;
311
350
  box-shadow:
312
- var(--ui-button-shadow, var(--ui-raised)),
351
+ var(--ui-button-shadow, var(--ui-action-shadow, var(--_ui-action-shadow))),
313
352
  var(--ui-focus-halo);
314
353
  }
315
354
 
316
355
  :scope[data-ui-button-state~="variant=solid"]:focus-visible {
317
356
  box-shadow:
318
- var(--ui-button-solid-shadow, var(--ui-raised)),
357
+ var(--ui-button-solid-shadow, var(--ui-action-shadow, var(--_ui-action-shadow))),
319
358
  var(--ui-focus-halo);
320
359
  }
321
360
 
361
+ :is(:scope[data-ui-button-state~="variant=ghost"], :scope[data-ui-button-state~="variant=link"], :scope[data-ui-button-state~="variant=card"]):focus-visible {
362
+ box-shadow: var(--ui-focus-halo);
363
+ }
364
+
322
365
  /* A loading action stays visually active; only an explicitly disabled action takes the
323
366
  neutral disabled palette. Its flat surface and muted ink make the state unambiguous. */
324
367
  :scope:is(:disabled, [aria-disabled="true"]:not([aria-busy="true"])) {
@@ -422,6 +465,9 @@
422
465
 
423
466
  /* An anchor keeps the boxed button look even when an app styles ordinary links. */
424
467
 
468
+ /* A count is an adornment, not another line of label text. Keep the standard control height
469
+ with enough inset for a 24px mark; large content can still grow the button naturally. */
470
+
425
471
  @supports (text-box-trim: trim-both) {
426
472
  }
427
473
 
@@ -431,6 +477,8 @@
431
477
  /* A link is never :enabled, so interactive states test "not unavailable" instead: for a button that
432
478
  is exactly :enabled, at the same specificity, and it also matches a link that can be followed. */
433
479
 
480
+ /* A neutral card encloses content quietly; ordinary outlines keep their action-tone edge. */
481
+
434
482
  /* Pressed reads as pressed in: the highlight goes, and the shadow moves inside. */
435
483
 
436
484
  /* Tone is the colour, variant is the volume. Every pairing works: a danger outline is a
@@ -79,6 +79,16 @@
79
79
  /* This component's hooks style the element they are set on, and never a nested one. */
80
80
 
81
81
  :scope {
82
+ /* Optional inherited action-group values keep text and icon actions under the same light.
83
+ One fine top/start highlight follows the bottom/end lowlight at a joined action seam.
84
+ A single ambient lift avoids stacking a face gradient or another inset bevel. */
85
+ --_ui-action-start: 1px;
86
+ --_ui-action-edge: var(
87
+ --ui-action-edge,
88
+ inset var(--_ui-action-start) 1px 0 light-dark(rgb(255 255 255 / 0.08), rgb(255 255 255 / 0.06)),
89
+ inset calc(-1 * var(--_ui-action-start)) -1px 0 light-dark(rgb(0 0 0 / 0.08), rgb(0 0 0 / 0.08))
90
+ );
91
+ --_ui-action-shadow: var(--_ui-action-edge), 0 1px 2px light-dark(rgb(0 0 0 / 0.10), rgb(0 0 0 / 0.20));
82
92
  /* Hooks do not inherit, so the parts below read them from the root. */
83
93
  --_ui-icon-button-icon-size: var(--ui-icon-button-icon-size);
84
94
  --_ui-icon-button-icon-stroke-width: var(--ui-icon-button-icon-stroke-width);
@@ -87,6 +97,10 @@
87
97
  color: var(--ui-text-secondary);
88
98
  }
89
99
 
100
+ :scope:dir(rtl) {
101
+ --_ui-action-start: -1px;
102
+ }
103
+
90
104
  :scope[data-ui-icon-button-state~="tone=accent"] {
91
105
  --_icon-button-border: var(--ui-accent-solid);
92
106
  --_icon-button-hover-border: var(--ui-accent-hover);
@@ -260,19 +274,16 @@
260
274
  :scope[data-ui-icon-button-state~="variant=outline"] {
261
275
  border-color: var(--ui-icon-button-border, var(--_icon-button-border, var(--ui-control-border, var(--ui-border-strong))));
262
276
  background-color: var(--ui-icon-button-outline-surface, var(--_icon-button-outline-surface, var(--ui-surface-default)));
263
- background-image: var(--ui-raised-highlight);
277
+ background-image: none;
264
278
  color: var(--ui-icon-button-outline-text, var(--_icon-button-text, var(--ui-text-primary)));
265
- box-shadow: var(--ui-raised);
279
+ box-shadow: var(--ui-action-shadow, var(--_ui-action-shadow));
266
280
  }
267
281
 
268
282
  :scope[data-ui-icon-button-state~="variant=solid"] {
269
283
  background-color: var(--ui-icon-button-solid-surface, var(--_icon-button-solid-surface, var(--ui-accent-solid)));
270
- background-image: var(--ui-raised-highlight);
284
+ background-image: none;
271
285
  color: var(--ui-icon-button-solid-text, var(--_icon-button-solid-text, var(--ui-text-on-accent)));
272
- box-shadow: var(
273
- --ui-icon-button-solid-shadow,
274
- 0 1px 2px color-mix(in srgb, var(--ui-accent-solid) 18%, transparent)
275
- );
286
+ box-shadow: var(--ui-icon-button-solid-shadow, var(--ui-action-shadow, var(--_ui-action-shadow)));
276
287
  }
277
288
 
278
289
  :scope:hover:enabled:not([aria-disabled="true"]) {
@@ -336,6 +347,23 @@
336
347
  box-shadow: var(--ui-focus-halo);
337
348
  }
338
349
 
350
+ :scope[data-ui-icon-button-state~="variant=outline"]:focus-visible {
351
+ box-shadow:
352
+ var(--ui-action-shadow, var(--_ui-action-shadow)),
353
+ var(--ui-focus-halo);
354
+ }
355
+
356
+ :scope[data-ui-icon-button-state~="variant=solid"]:focus-visible {
357
+ box-shadow:
358
+ var(--ui-icon-button-solid-shadow, var(--ui-action-shadow, var(--_ui-action-shadow))),
359
+ var(--ui-focus-halo);
360
+ }
361
+
362
+ :scope:is(:scope[data-ui-icon-button-state~="variant=outline"], :scope[data-ui-icon-button-state~="variant=solid"]):active:enabled:not([aria-disabled="true"]) {
363
+ background-image: none;
364
+ box-shadow: var(--ui-pressed);
365
+ }
366
+
339
367
  :scope[data-ui-icon-button-state~="variant=outline"][aria-pressed="true"]:enabled:focus-visible {
340
368
  box-shadow:
341
369
  var(--ui-pressed),
@@ -64,6 +64,12 @@
64
64
  transition: border-color var(--ui-motion-fast) var(--ui-motion-ease), box-shadow var(--ui-motion-fast) var(--ui-motion-ease);
65
65
  }
66
66
 
67
+ :scope[data-ui-input-group-state~="variant=subtle"] {
68
+ border-color: var(--ui-border);
69
+ background: var(--ui-surface-muted);
70
+ box-shadow: none;
71
+ }
72
+
67
73
  /* The frame takes its input's size, so a small group lines up with small buttons and fields. */
68
74
  :scope:has(.field [data-ui-input-state~="size=sm"]) {
69
75
  min-block-size: var(--ui-control-size-sm);
@@ -7,7 +7,7 @@ export interface UiBadgeElement extends HTMLSpanElement {
7
7
  }
8
8
  export interface UiBadgeProps {
9
9
  shape?: "pill" | "tag" | "dot" | "square" | "circle" | null;
10
- size?: "xs" | "md" | "lg" | null;
10
+ size?: "xs" | "sm" | "md" | "lg" | null;
11
11
  tone?: "neutral" | "accent" | "info" | "success" | "warning" | "danger" | null;
12
12
  variant?: "solid" | "subtle" | "outline" | "inverse" | null;
13
13
  attributes?: Readonly<Record<string, string | number | boolean | null | undefined>>;
@@ -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","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] } } };
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","sm","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;
@@ -15,13 +15,15 @@ const T8 = ["",[],4];
15
15
  const T9 = ["",[]];
16
16
  const T10 = ["",[]];
17
17
  const T11 = ["",[],["ui-icon",["name","chevron-right","aria-hidden","true"]]];
18
- const T12 = ["",[],1,4];
18
+ const T12 = ["",[],1,4,["span",["class","badge"],4]];
19
19
  const T13 = ["span",["class","spinner-wrap"],["ui-spinner",["size","sm"]]];
20
20
  const T14 = ["",[]];
21
- const T15 = ["",[],1,4];
22
- const T16 = ["span",["class","spinner-wrap"],["ui-spinner",["size","sm"]]];
23
- const T17 = ["",[]];
24
- let P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17;
21
+ const T15 = ["",[]];
22
+ const T16 = ["",[],1,4,["span",["class","badge"],4]];
23
+ const T17 = ["span",["class","spinner-wrap"],["ui-spinner",["size","sm"]]];
24
+ const T18 = ["",[]];
25
+ const T19 = ["",[]];
26
+ let P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19;
25
27
  const S = { n: [], t: [], f: import.meta.url, g: "ui-button" };
26
28
  const A = [["a",{},[],"",[],["data-ui-button-state","data-align","data-as","data-disabled","data-href","data-loading","data-pending","data-rel","data-shape","data-size","data-stretch","data-target","data-tone","data-type","data-variant","href","target","rel","aria-disabled","aria-busy","tabindex"]],["button",{},[],"",[],["data-ui-button-state","data-align","data-as","data-disabled","data-href","data-loading","data-pending","data-rel","data-shape","data-size","data-stretch","data-target","data-tone","data-type","data-variant","type","disabled","aria-disabled","aria-busy"]],["a",{},[],"",[],["data-ui-button-state","data-align","data-as","data-disabled","data-href","data-loading","data-pending","data-rel","data-shape","data-size","data-stretch","data-target","data-tone","data-type","data-variant","href","target","rel","aria-disabled","aria-busy","tabindex"]],["button",{},[],"",[],["data-ui-button-state","data-align","data-as","data-disabled","data-href","data-loading","data-pending","data-rel","data-shape","data-size","data-stretch","data-target","data-tone","data-type","data-variant","type","disabled","aria-disabled","aria-busy"]]];
27
29
  const Q = [[],[],[],[]];
@@ -168,13 +170,13 @@ export function createUiButton(options = {}, html) {
168
170
  };
169
171
  const p14 = (r, c, d) => {
170
172
  };
171
- const m16 = (d) => {
172
- const n = (P16 ??= buildTemplate(T16)).cloneNode(true);
173
+ const m15 = (d) => {
174
+ const n = (P15 ??= buildTemplate(T15)).cloneNode(true);
173
175
  const r = { n };
174
- p16(r, -1, d);
176
+ p15(r, -1, d);
175
177
  return r;
176
178
  };
177
- const p16 = (r, c, d) => {
179
+ const p15 = (r, c, d) => {
178
180
  };
179
181
  const m17 = (d) => {
180
182
  const n = (P17 ??= buildTemplate(T17)).cloneNode(true);
@@ -184,6 +186,22 @@ export function createUiButton(options = {}, html) {
184
186
  };
185
187
  const p17 = (r, c, d) => {
186
188
  };
189
+ const m18 = (d) => {
190
+ const n = (P18 ??= buildTemplate(T18)).cloneNode(true);
191
+ const r = { n };
192
+ p18(r, -1, d);
193
+ return r;
194
+ };
195
+ const p18 = (r, c, d) => {
196
+ };
197
+ const m19 = (d) => {
198
+ const n = (P19 ??= buildTemplate(T19)).cloneNode(true);
199
+ const r = { n };
200
+ p19(r, -1, d);
201
+ return r;
202
+ };
203
+ const p19 = (r, c, d) => {
204
+ };
187
205
  const m0 = (n, d) => {
188
206
  n.append((P0 ??= buildTemplate(T0)).cloneNode(true));
189
207
  const t0 = n.firstChild;
@@ -340,7 +358,8 @@ export function createUiButton(options = {}, html) {
340
358
  n.append((P12 ??= buildTemplate(T12)).cloneNode(true));
341
359
  const t0 = n.firstChild;
342
360
  const t1 = t0.nextSibling.nextSibling;
343
- const r = { n, v0: undefined, v1: undefined, v2: undefined, v3: undefined, v4: undefined, v5: undefined, v6: undefined, a1: t0, e0: t0.nextSibling, b0: undefined, a2: t1, e1: t1.nextSibling, b1: undefined, f1: undefined };
361
+ const t2 = t1.nextSibling.nextSibling.firstChild;
362
+ const r = { n, v0: undefined, v1: undefined, v2: undefined, v3: undefined, v4: undefined, v5: undefined, v6: undefined, a1: t0, e0: t0.nextSibling, b0: undefined, a2: t1, e1: t1.nextSibling, b1: undefined, f1: undefined, a3: t2, e2: t2.nextSibling, b2: undefined, f2: undefined };
344
363
  p12(r, -1, d);
345
364
  return r;
346
365
  };
@@ -396,19 +415,23 @@ export function createUiButton(options = {}, html) {
396
415
  }
397
416
  } else if (r.b0 !== undefined) p13(r.b0, c, d);
398
417
  } else if (r.b0 !== undefined) p13(r.b0, c, d);
418
+ if (r.f2 === undefined) { r.f2 = 1; const at = fillSlot(r.a3, r.e2, "badge", J, false, undefined, d);
419
+ if (Array.isArray(at)) (r.z ??= []).push(() => { at[0].l.delete(at[1]); dispose(at[1]); }); else if (at !== undefined) { r.b2 = m15(d); at.before(r.b2.n); } }
420
+ else if (r.b2 !== undefined) p15(r.b2, c, d);
399
421
  if (r.f1 === undefined) { r.f1 = 1; const at = fillSlot(r.a2, r.e1, "", J, false, undefined, d);
400
422
  if (Array.isArray(at)) (r.z ??= []).push(() => { at[0].l.delete(at[1]); dispose(at[1]); }); else if (at !== undefined) { r.b1 = m14(d); at.before(r.b1.n); } }
401
423
  else if (r.b1 !== undefined) p14(r.b1, c, d);
402
424
  };
403
- const m15 = (n, d) => {
404
- n.append((P15 ??= buildTemplate(T15)).cloneNode(true));
425
+ const m16 = (n, d) => {
426
+ n.append((P16 ??= buildTemplate(T16)).cloneNode(true));
405
427
  const t0 = n.firstChild;
406
428
  const t1 = t0.nextSibling.nextSibling;
407
- const r = { n, v0: undefined, v1: undefined, v2: undefined, v3: undefined, v4: undefined, a1: t0, e0: t0.nextSibling, b0: undefined, a2: t1, e1: t1.nextSibling, b1: undefined, f1: undefined };
408
- p15(r, -1, d);
429
+ const t2 = t1.nextSibling.nextSibling.firstChild;
430
+ const r = { n, v0: undefined, v1: undefined, v2: undefined, v3: undefined, v4: undefined, a1: t0, e0: t0.nextSibling, b0: undefined, a2: t1, e1: t1.nextSibling, b1: undefined, f1: undefined, a3: t2, e2: t2.nextSibling, b2: undefined, f2: undefined };
431
+ p16(r, -1, d);
409
432
  return r;
410
433
  };
411
- const p15 = (r, c, d) => {
434
+ const p16 = (r, c, d) => {
412
435
  if (c & 1073745920) {
413
436
  const x0 = v[12];
414
437
  const s0 = toAttribute(x0, "type");
@@ -442,15 +465,18 @@ export function createUiButton(options = {}, html) {
442
465
  {
443
466
  r.b0 = undefined;
444
467
  clearRegion(r.a1, r.e0);
445
- if (t0) { r.b0 = m16(d); r.e0.before(r.b0.n); }
468
+ if (t0) { r.b0 = m17(d); r.e0.before(r.b0.n); }
446
469
  }
447
- } else if (r.b0 !== undefined) p16(r.b0, c, d);
448
- } else if (r.b0 !== undefined) p16(r.b0, c, d);
470
+ } else if (r.b0 !== undefined) p17(r.b0, c, d);
471
+ } else if (r.b0 !== undefined) p17(r.b0, c, d);
472
+ if (r.f2 === undefined) { r.f2 = 1; const at = fillSlot(r.a3, r.e2, "badge", J, false, undefined, d);
473
+ if (Array.isArray(at)) (r.z ??= []).push(() => { at[0].l.delete(at[1]); dispose(at[1]); }); else if (at !== undefined) { r.b2 = m19(d); at.before(r.b2.n); } }
474
+ else if (r.b2 !== undefined) p19(r.b2, c, d);
449
475
  if (r.f1 === undefined) { r.f1 = 1; const at = fillSlot(r.a2, r.e1, "", J, false, undefined, d);
450
- if (Array.isArray(at)) (r.z ??= []).push(() => { at[0].l.delete(at[1]); dispose(at[1]); }); else if (at !== undefined) { r.b1 = m17(d); at.before(r.b1.n); } }
451
- else if (r.b1 !== undefined) p17(r.b1, c, d);
476
+ if (Array.isArray(at)) (r.z ??= []).push(() => { at[0].l.delete(at[1]); dispose(at[1]); }); else if (at !== undefined) { r.b1 = m18(d); at.before(r.b1.n); } }
477
+ else if (r.b1 !== undefined) p18(r.b1, c, d);
452
478
  };
453
- const M = [m0, m6, m12, m15], N = [p0, p6, p12, p15];
479
+ const M = [m0, m6, m12, m16], N = [p0, p6, p12, p16];
454
480
  let R;
455
481
  const p = (c, d) => {
456
482
  if (R === null) return;
@@ -10,6 +10,7 @@ export interface UiInputGroupProps {
10
10
  disabled?: boolean | null;
11
11
  multiline?: boolean | null;
12
12
  prefixKind?: "text" | "icon" | null;
13
+ variant?: "outline" | "subtle" | null;
13
14
  attributes?: Readonly<Record<string, string | number | boolean | null | undefined>>;
14
15
  children?: readonly (string | Node)[];
15
16
  slots?: Readonly<Record<string, readonly (string | Node)[]>>;
@@ -10,7 +10,7 @@ const T3 = ["",[]];
10
10
  const T4 = ["",[]];
11
11
  let P0, P1, P2, P3, P4;
12
12
  const S = { n: [], t: [], f: import.meta.url, g: "ui-input-group" };
13
- const D = { props: { "actionPosition": { "required":false,"values":["below","top-end"],"default":"below", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "disabled": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "multiline": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "prefixKind": { "required":false,"values":["text","icon"],"default":"text", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] } } };
13
+ const D = { props: { "actionPosition": { "required":false,"values":["below","top-end"],"default":"below", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "disabled": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "multiline": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "prefixKind": { "required":false,"values":["text","icon"],"default":"text", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "variant": { "required":false,"values":["outline","subtle"],"default":"outline", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] } } };
14
14
  const C = (host) => controller.default(host);
15
15
 
16
16
  export function createUiInputGroup(options = {}, html) {
@@ -22,7 +22,7 @@ export function createUiInputGroup(options = {}, html) {
22
22
  }
23
23
  element.setAttribute("data-component", "ui-input-group");
24
24
  element.append((P0 ??= buildTemplate(T0)).cloneNode(true));
25
- const v = [null, null, null, null];
25
+ const v = [null, null, null, null, null];
26
26
  const B = acceptProps(element, D, S.n, v, componentProps, [], html);
27
27
  const m1 = (d) => {
28
28
  const n = (P1 ??= buildTemplate(T1)).cloneNode(true);
@@ -57,8 +57,8 @@ export function createUiInputGroup(options = {}, html) {
57
57
  const p4 = (r, c, d) => {
58
58
  };
59
59
  const p0 = (r, c, d) => {
60
- if (c & 15) {
61
- const x0 = hostState(["disabled","prefixKind","multiline","actionPosition"], [v[1], v[3], v[2], v[0]]);
60
+ if (c & 31) {
61
+ const x0 = hostState(["variant","disabled","prefixKind","multiline","actionPosition"], [v[4], v[1], v[3], v[2], v[0]]);
62
62
  const s0 = x0;
63
63
  if (s0 !== r.v0) writeAttribute(r.n, "data-ui-input-group-state", (r.v0 = s0) === "" ? null : s0);
64
64
  }
package/vue/UiBadge.d.ts CHANGED
@@ -11,7 +11,7 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
11
11
  default: string;
12
12
  };
13
13
  size: {
14
- type: PropType<"xs" | "md" | "lg" | null>;
14
+ type: PropType<"xs" | "sm" | "md" | "lg" | null>;
15
15
  default: string;
16
16
  };
17
17
  tone: {
@@ -28,7 +28,7 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
28
28
  default: string;
29
29
  };
30
30
  size: {
31
- type: PropType<"xs" | "md" | "lg" | null>;
31
+ type: PropType<"xs" | "sm" | "md" | "lg" | null>;
32
32
  default: string;
33
33
  };
34
34
  tone: {
@@ -41,7 +41,7 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
41
41
  };
42
42
  }>> & Readonly<{}>, {
43
43
  shape: "circle" | "dot" | "tag" | "square" | "pill" | null;
44
- size: "xs" | "md" | "lg" | null;
44
+ size: "xs" | "sm" | "md" | "lg" | null;
45
45
  tone: "info" | "neutral" | "accent" | "success" | "warning" | "danger" | null;
46
46
  variant: "solid" | "subtle" | "outline" | "inverse" | null;
47
47
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
package/vue/UiBadge.vue CHANGED
@@ -22,8 +22,8 @@ const propValidityContract = {
22
22
  required: false,
23
23
  target: { attribute: 'size' },
24
24
  description:
25
- 'Scale. xs is compact; md is the usual text badge; lg adds room to fixed marks while retaining the usual text size. A square is 24px at xs, 32px at md and\n 40px at lg. A circle is 16px at xs, 24px at md and 32px at lg; use lg for short counts such as 30 or 99+.',
26
- values: ['xs', 'md', 'lg'],
25
+ 'Scale. xs is compact; sm keeps the usual badge geometry with compact text, fitting short counts such as 99+ inside a 24px circle. md is the usual text\n badge; lg adds room to fixed marks while retaining the usual text size. A square is 24px at xs, 32px at sm and md, and 40px at lg. A circle is 16px at xs,\n 24px at sm and md, and 32px at lg.',
26
+ values: ['xs', 'sm', 'md', 'lg'],
27
27
  default: 'md',
28
28
  },
29
29
  tone: {
@@ -51,7 +51,7 @@ const props = defineProps({
51
51
  type: null as unknown as PropType<'pill' | 'tag' | 'dot' | 'square' | 'circle' | null>,
52
52
  default: 'pill',
53
53
  },
54
- size: { type: null as unknown as PropType<'xs' | 'md' | 'lg' | null>, default: 'md' },
54
+ size: { type: null as unknown as PropType<'xs' | 'sm' | 'md' | 'lg' | null>, default: 'md' },
55
55
  tone: {
56
56
  type: null as unknown as PropType<
57
57
  'neutral' | 'accent' | 'info' | 'success' | 'warning' | 'danger' | null
@@ -77,13 +77,13 @@ const checkedProps = computed(() => ({
77
77
  'shape',
78
78
  acceptedProps,
79
79
  ) as 'pill' | 'tag' | 'dot' | 'square' | 'circle',
80
- size: checkedProp<'xs' | 'md' | 'lg'>(
80
+ size: checkedProp<'xs' | 'sm' | 'md' | 'lg'>(
81
81
  props['size'],
82
82
  { kind: 'terminal', name: 'keyword' },
83
83
  false,
84
84
  'size',
85
85
  acceptedProps,
86
- ) as 'xs' | 'md' | 'lg',
86
+ ) as 'xs' | 'sm' | 'md' | 'lg',
87
87
  tone: checkedProp<'neutral' | 'accent' | 'info' | 'success' | 'warning' | 'danger'>(
88
88
  props['tone'],
89
89
  { kind: 'terminal', name: 'keyword' },
@@ -195,6 +195,10 @@ const hostState = computed(() =>
195
195
  syntax: "*";
196
196
  inherits: false;
197
197
  }
198
+ @property --ui-badge-circle-opacity {
199
+ syntax: "*";
200
+ inherits: false;
201
+ }
198
202
  @property --ui-badge-dot-size {
199
203
  syntax: "*";
200
204
  inherits: false;
@@ -360,6 +364,11 @@ const hostState = computed(() =>
360
364
  line-height: var(--ui-badge-line-height, 1);
361
365
  }
362
366
 
367
+ [data-component~="ui-badge"][data-ui-badge-state~="size=sm"] {
368
+ font-size: var(--ui-badge-font-size, var(--ui-font-size-2xs));
369
+ line-height: var(--ui-badge-line-height, 1);
370
+ }
371
+
363
372
  [data-component~="ui-badge"][data-ui-badge-state~="tone=accent"] {
364
373
  --_badge-surface: var(--ui-accent-strong-surface, var(--ui-accent-solid));
365
374
  --_badge-border: var(--ui-accent-strong-surface, var(--ui-accent-solid));
@@ -454,8 +463,72 @@ const hostState = computed(() =>
454
463
  background-clip: padding-box;
455
464
  }
456
465
 
466
+ /* A circular count has a tone-tinted surface, a separate lighter rim and readable tone ink.
467
+ Dark controls retain a shaded surface and theme ink with a quieter rim. */
457
468
  [data-component~="ui-badge"][data-ui-badge-state~="variant=inverse"][data-ui-badge-state~="shape=circle"] {
458
- box-shadow: var(--ui-shadow-sm);
469
+ --_badge-color-surface: light-dark(
470
+ color-mix(
471
+ in srgb,
472
+ color-mix(in oklab, var(--_ui-badge-color) 0%, var(--ui-surface-default)) var(
473
+ --ui-badge-circle-opacity,
474
+ 66%
475
+ ),
476
+ transparent
477
+ ),
478
+ color-mix(
479
+ in srgb,
480
+ color-mix(in oklab, var(--_ui-badge-color) 0%, var(--ui-surface-default)) var(
481
+ --ui-badge-circle-opacity,
482
+ 56%
483
+ ),
484
+ transparent
485
+ )
486
+ );
487
+ --_badge-color-border: light-dark(
488
+ color-mix(
489
+ in srgb,
490
+ color-mix(in oklab, var(--_ui-badge-color) 0%, var(--ui-surface-default)) 88%,
491
+ transparent
492
+ ),
493
+ color-mix(
494
+ in srgb,
495
+ color-mix(in oklab, var(--_ui-badge-color) 0%, var(--ui-surface-default)) 44%,
496
+ transparent
497
+ )
498
+ );
499
+ --_badge-color-text: light-dark(
500
+ color-mix(in srgb, var(--_badge-color-ink) 65%, var(--ui-text-primary)),
501
+ color-mix(in oklab, var(--_ui-badge-color) 0%, var(--ui-text-primary))
502
+ );
503
+ --_badge-count-surface: light-dark(
504
+ color-mix(in srgb, var(--_badge-text) var(--ui-badge-circle-opacity, 66%), transparent),
505
+ color-mix(in srgb, var(--_badge-text) var(--ui-badge-circle-opacity, 56%), transparent)
506
+ );
507
+ --_badge-outline-surface: var(
508
+ --ui-badge-surface,
509
+ var(--_badge-color-surface, var(--_badge-count-surface))
510
+ );
511
+ border-color: var(
512
+ --ui-badge-border,
513
+ var(
514
+ --_badge-color-border,
515
+ light-dark(
516
+ color-mix(in srgb, var(--_badge-text) 88%, transparent),
517
+ color-mix(in srgb, var(--_badge-text) 44%, transparent)
518
+ )
519
+ )
520
+ );
521
+ color: var(
522
+ --ui-badge-text,
523
+ var(
524
+ --_badge-color-text,
525
+ light-dark(
526
+ color-mix(in srgb, var(--_badge-surface) 65%, var(--ui-text-primary)),
527
+ var(--ui-text-primary)
528
+ )
529
+ )
530
+ );
531
+ box-shadow: 0 1px 1px light-dark(rgb(0 0 0 / 0.08), rgb(0 0 0 / 0.12));
459
532
  }
460
533
 
461
534
  /* Neutral reverses a subtle surface/ink pair rather than a strong tone. Keep its original