@threadlabs/looma 0.26.1 → 0.26.2

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.1",
3
+ "version": "0.26.2",
4
4
  "type": "module",
5
5
  "description": "Looma components as HTML, Vue, and plain DOM, with design tokens.",
6
6
  "license": "MIT",
@@ -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;
@@ -282,6 +286,36 @@
282
286
  display: none;
283
287
  }
284
288
 
289
+ /* A visible glyph needs fixed geometry, unlike a content-sized pill or a hidden-label dot. */
290
+ :scope[data-ui-badge-state~="shape=circle"] {
291
+ --_badge-circle-size: var(--ui-badge-circle-size, calc(var(--ui-space-3) * 2));
292
+ display: inline-flex;
293
+ flex: none;
294
+ inline-size: var(--_badge-circle-size);
295
+ block-size: var(--_badge-circle-size);
296
+ min-inline-size: var(--_badge-circle-size);
297
+ min-block-size: var(--_badge-circle-size);
298
+ padding: 0;
299
+ border-radius: var(--ui-radius-round);
300
+ text-box-trim: none;
301
+ }
302
+
303
+ :scope[data-ui-badge-state~="shape=circle"][data-ui-badge-state~="size=xs"] {
304
+ --_badge-circle-size: var(--ui-badge-circle-size, var(--ui-space-4));
305
+ }
306
+
307
+ :scope[data-ui-badge-state~="shape=circle"] .label {
308
+ display: inline-flex;
309
+ align-items: center;
310
+ justify-content: center;
311
+ line-height: 1;
312
+ text-box-trim: none;
313
+ }
314
+
315
+ :scope[data-ui-badge-state~="shape=circle"] .label:empty {
316
+ display: none;
317
+ }
318
+
285
319
  /* A dot uses the badge's tone recipe; only its silhouette and visible label change. */
286
320
  :scope[data-ui-badge-state~="shape=dot"] {
287
321
  position: relative;
@@ -399,6 +433,12 @@
399
433
  margin-inline-end: 0;
400
434
  }
401
435
 
436
+ /* A visible glyph needs fixed geometry, unlike a content-sized pill or a hidden-label dot. */
437
+
438
+ :scope[data-ui-badge-state~="shape=circle"] .label :where([data-slotted], [data-slotted] *):is(svg) {
439
+ margin-inline-end: 0;
440
+ }
441
+
402
442
  /* A dot uses the badge's tone recipe; only its silhouette and visible label change. */
403
443
 
404
444
  /* 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) {
@@ -6,7 +6,7 @@ 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;
9
+ shape?: "pill" | "tag" | "dot" | "square" | "circle" | null;
10
10
  size?: "xs" | "md" | null;
11
11
  tone?: "neutral" | "accent" | "info" | "success" | "warning" | "danger" | null;
12
12
  variant?: "solid" | "subtle" | "outline" | null;
@@ -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"],"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] } } };
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
  }
package/vue/UiBadge.d.ts CHANGED
@@ -7,7 +7,7 @@ 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: {
@@ -24,7 +24,7 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
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: {
@@ -40,7 +40,7 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
40
40
  default: string;
41
41
  };
42
42
  }>> & Readonly<{}>, {
43
- shape: "dot" | "tag" | "square" | "pill" | null;
43
+ shape: "circle" | "dot" | "tag" | "square" | "pill" | null;
44
44
  size: "xs" | "md" | null;
45
45
  tone: "info" | "neutral" | "accent" | "success" | "warning" | "danger" | null;
46
46
  variant: "solid" | "subtle" | "outline" | null;
package/vue/UiBadge.vue CHANGED
@@ -13,8 +13,8 @@ const propValidityContract = {
13
13
  required: false,
14
14
  target: { attribute: 'shape' },
15
15
  description:
16
- 'Silhouette. pill is rounded at both ends; tag is a pointed label. dot is a compact circular signal with visually hidden text: include a state label, or set\n aria-hidden when the surrounding control already names that state. square is a fixed rounded square for an icon or single glyph beside a heading; hide a\n decorative icon when adjacent words name it.',
17
- values: ['pill', 'tag', 'dot', 'square'],
16
+ 'Silhouette. pill is rounded at both ends; tag is a pointed label. dot is a compact circular signal with visually hidden text: include a state label, or set\n aria-hidden when the surrounding control already names that state. square is a fixed rounded square for an icon or single glyph beside a heading; hide a\n decorative icon when adjacent words name it. circle frames a visible single glyph or icon in a fixed circle, centered in both directions.',
17
+ values: ['pill', 'tag', 'dot', 'square', 'circle'],
18
18
  default: 'pill',
19
19
  },
20
20
  size: {
@@ -22,7 +22,7 @@ const propValidityContract = {
22
22
  required: false,
23
23
  target: { attribute: 'size' },
24
24
  description:
25
- 'Scale. xs is a compact count beside small controls; md is the usual text badge. A square is 24px at xs and 32px at md with default spacing.',
25
+ 'Scale. xs is a compact count beside small controls; md is the usual text badge. A square is 24px at xs and 32px at md with default spacing. A circle is\n 16px at xs and 24px at md.',
26
26
  values: ['xs', 'md'],
27
27
  default: 'md',
28
28
  },
@@ -48,7 +48,7 @@ const propValidityContract = {
48
48
  } as const
49
49
  const props = defineProps({
50
50
  shape: {
51
- type: null as unknown as PropType<'pill' | 'tag' | 'dot' | 'square' | null>,
51
+ type: null as unknown as PropType<'pill' | 'tag' | 'dot' | 'square' | 'circle' | null>,
52
52
  default: 'pill',
53
53
  },
54
54
  size: { type: null as unknown as PropType<'xs' | 'md' | null>, default: 'md' },
@@ -70,13 +70,13 @@ const acceptedProps: Record<string, unknown> = {
70
70
  variant: 'subtle',
71
71
  }
72
72
  const checkedProps = computed(() => ({
73
- shape: checkedProp<'pill' | 'tag' | 'dot' | 'square'>(
73
+ shape: checkedProp<'pill' | 'tag' | 'dot' | 'square' | 'circle'>(
74
74
  props['shape'],
75
75
  { kind: 'terminal', name: 'keyword' },
76
76
  false,
77
77
  'shape',
78
78
  acceptedProps,
79
- ) as 'pill' | 'tag' | 'dot' | 'square',
79
+ ) as 'pill' | 'tag' | 'dot' | 'square' | 'circle',
80
80
  size: checkedProp<'xs' | 'md'>(
81
81
  props['size'],
82
82
  { kind: 'terminal', name: 'keyword' },
@@ -191,6 +191,10 @@ const hostState = computed(() =>
191
191
  syntax: "*";
192
192
  inherits: false;
193
193
  }
194
+ @property --ui-badge-circle-size {
195
+ syntax: "*";
196
+ inherits: false;
197
+ }
194
198
  @property --ui-badge-dot-size {
195
199
  syntax: "*";
196
200
  inherits: false;
@@ -490,6 +494,40 @@ const hostState = computed(() =>
490
494
  display: none;
491
495
  }
492
496
 
497
+ /* A visible glyph needs fixed geometry, unlike a content-sized pill or a hidden-label dot. */
498
+ [data-component~="ui-badge"][data-ui-badge-state~="shape=circle"] {
499
+ --_badge-circle-size: var(--ui-badge-circle-size, calc(var(--ui-space-3) * 2));
500
+ display: inline-flex;
501
+ flex: none;
502
+ inline-size: var(--_badge-circle-size);
503
+ block-size: var(--_badge-circle-size);
504
+ min-inline-size: var(--_badge-circle-size);
505
+ min-block-size: var(--_badge-circle-size);
506
+ padding: 0;
507
+ border-radius: var(--ui-radius-round);
508
+ text-box-trim: none;
509
+ }
510
+
511
+ [data-component~="ui-badge"][data-ui-badge-state~="shape=circle"][data-ui-badge-state~="size=xs"] {
512
+ --_badge-circle-size: var(--ui-badge-circle-size, var(--ui-space-4));
513
+ }
514
+
515
+ [data-component~="ui-badge"][data-ui-badge-state~="shape=circle"] .label {
516
+ display: inline-flex;
517
+ align-items: center;
518
+ justify-content: center;
519
+ line-height: 1;
520
+ text-box-trim: none;
521
+ }
522
+
523
+ [data-component~="ui-badge"][data-ui-badge-state~="shape=circle"] .label :slotted(svg) {
524
+ margin-inline-end: 0;
525
+ }
526
+
527
+ [data-component~="ui-badge"][data-ui-badge-state~="shape=circle"] .label:empty {
528
+ display: none;
529
+ }
530
+
493
531
  /* A dot uses the badge's tone recipe; only its silhouette and visible label change. */
494
532
  [data-component~="ui-badge"][data-ui-badge-state~="shape=dot"] {
495
533
  position: relative;
@@ -4,6 +4,10 @@ type __VLS_Slots = {} & {
4
4
  default?: (props: typeof __VLS_1) => any;
5
5
  };
6
6
  declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
7
+ fill: {
8
+ type: PropType<boolean | null>;
9
+ default: boolean;
10
+ };
7
11
  gutters: {
8
12
  type: PropType<"s" | "m" | "l" | null>;
9
13
  default: null;
@@ -13,6 +17,10 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
13
17
  default: null;
14
18
  };
15
19
  }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
20
+ fill: {
21
+ type: PropType<boolean | null>;
22
+ default: boolean;
23
+ };
16
24
  gutters: {
17
25
  type: PropType<"s" | "m" | "l" | null>;
18
26
  default: null;
@@ -22,6 +30,7 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
22
30
  default: null;
23
31
  };
24
32
  }>> & Readonly<{}>, {
33
+ fill: boolean | null;
25
34
  gutters: "s" | "m" | "l" | null;
26
35
  measure: "narrow" | "wide" | null;
27
36
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -8,6 +8,14 @@ defineOptions({ inheritAttrs: false })
8
8
 
9
9
  const propValidityContract = {
10
10
  props: {
11
+ fill: {
12
+ type: { kind: 'terminal', name: 'boolean' },
13
+ required: false,
14
+ target: { attribute: 'fill' },
15
+ description:
16
+ 'Keeps the readable width while filling available layout space. Uses a flex column so scrolling children can shrink inside a bounded flex or grid parent.\n Omit it to keep the usual block layout.',
17
+ default: false,
18
+ },
11
19
  gutters: {
12
20
  type: { kind: 'terminal', name: 'keyword' },
13
21
  required: false,
@@ -27,11 +35,19 @@ const propValidityContract = {
27
35
  },
28
36
  } as const
29
37
  const props = defineProps({
38
+ fill: { type: null as unknown as PropType<boolean | null>, default: false },
30
39
  gutters: { type: null as unknown as PropType<'s' | 'm' | 'l' | null>, default: null },
31
40
  measure: { type: null as unknown as PropType<'narrow' | 'wide' | null>, default: null },
32
41
  })
33
- const acceptedProps: Record<string, unknown> = { gutters: null, measure: null }
42
+ const acceptedProps: Record<string, unknown> = { fill: false, gutters: null, measure: null }
34
43
  const checkedProps = computed(() => ({
44
+ fill: checkedProp<boolean>(
45
+ props['fill'],
46
+ { kind: 'terminal', name: 'boolean' },
47
+ false,
48
+ 'fill',
49
+ acceptedProps,
50
+ ) as boolean,
35
51
  gutters: checkedProp<'s' | 'm' | 'l'>(
36
52
  props['gutters'],
37
53
  { kind: 'terminal', name: 'keyword' },
@@ -108,6 +124,7 @@ const hostState = computed(() =>
108
124
  checkedProps.value.measure && 'measure',
109
125
  checkedProps.value.measure != null
110
126
  && `measure=${encodeURIComponent(checkedProps.value.measure)}`,
127
+ checkedProps.value.fill && 'fill',
111
128
  checkedProps.value.gutters && 'gutters',
112
129
  checkedProps.value.gutters != null
113
130
  && `gutters=${encodeURIComponent(checkedProps.value.gutters)}`,
@@ -120,6 +137,7 @@ const hostState = computed(() =>
120
137
  data-component="ui-container"
121
138
  v-bind="nativeAttrs($attrs)"
122
139
  v-prop-validity="{ contract: propValidityContract, values: { ...props } }"
140
+ :data-fill="reflectedProp('fill', 'fill', checkedProps.fill, undefined, false)"
123
141
  :data-gutters="reflectedProp('gutters', 'gutters', checkedProps.gutters, undefined, false)"
124
142
  :data-measure="reflectedProp('measure', 'measure', checkedProps.measure, undefined, false)"
125
143
  :data-ui-container-state="hostState || undefined"
@@ -152,6 +170,14 @@ const hostState = computed(() =>
152
170
  [data-component~="ui-container"][data-ui-container-state~="measure=narrow"] {
153
171
  --_container-measure: 45ch;
154
172
  }
173
+ [data-component~="ui-container"][data-ui-container-state~="fill"] {
174
+ box-sizing: border-box;
175
+ display: flex;
176
+ flex-direction: column;
177
+ flex: 1;
178
+ min-inline-size: 0;
179
+ min-block-size: 0;
180
+ }
155
181
  [data-component~="ui-container"][data-ui-container-state~="measure=wide"] {
156
182
  --_container-measure: 80ch;
157
183
  }
@@ -36,6 +36,10 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
36
36
  type: PropType<"sm" | "md" | "lg" | null>;
37
37
  default: string;
38
38
  };
39
+ tone: {
40
+ type: PropType<"accent" | "neutral" | null>;
41
+ default: null;
42
+ };
39
43
  variant: {
40
44
  type: PropType<"ghost" | "outline" | "solid" | null>;
41
45
  default: string;
@@ -73,6 +77,10 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
73
77
  type: PropType<"sm" | "md" | "lg" | null>;
74
78
  default: string;
75
79
  };
80
+ tone: {
81
+ type: PropType<"accent" | "neutral" | null>;
82
+ default: null;
83
+ };
76
84
  variant: {
77
85
  type: PropType<"ghost" | "outline" | "solid" | null>;
78
86
  default: string;
@@ -81,6 +89,7 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
81
89
  label: string | null;
82
90
  round: boolean | null;
83
91
  size: "sm" | "md" | "lg" | null;
92
+ tone: "neutral" | "accent" | null;
84
93
  variant: "solid" | "outline" | "ghost" | null;
85
94
  disabled: boolean | null;
86
95
  loading: boolean | null;