@threadlabs/looma 0.26.2 → 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.
- package/components/ui-avatar/ui-avatar.html +6 -1
- package/components/ui-badge/ui-badge.html +62 -12
- package/components/ui-list-item/ui-list-item.html +13 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/styles/ui-avatar.css +4 -0
- package/styles/ui-badge.css +67 -8
- package/styles/ui-list-item.css +12 -0
- package/vanilla/UiBadge.d.ts +2 -2
- package/vanilla/UiBadge.js +1 -1
- package/vanilla/UiListItem.d.ts +1 -0
- package/vanilla/UiListItem.js +6 -6
- package/vue/UiAvatar.vue +6 -1
- package/vue/UiBadge.d.ts +6 -6
- package/vue/UiBadge.vue +81 -17
- package/vue/UiCard.d.ts +1 -1
- package/vue/UiListItem.d.ts +9 -0
- package/vue/UiListItem.vue +31 -0
- package/vue/UiPopover.d.ts +1 -1
- package/vue/UiScrollArea.d.ts +1 -1
- package/vue/UiTooltip.d.ts +1 -1
- package/vue/chunks/UiAvatar.js +2 -2
- package/vue/chunks/UiBadge.js +11 -6
- package/vue/chunks/UiListItem.js +26 -2
- package/vue/components.css +167 -95
package/package.json
CHANGED
package/styles/ui-avatar.css
CHANGED
|
@@ -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
|
}
|
package/styles/ui-badge.css
CHANGED
|
@@ -124,25 +124,26 @@
|
|
|
124
124
|
text-box-trim: none;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
/*
|
|
128
|
-
|
|
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. */
|
|
129
129
|
@supports (text-box-trim: trim-both) {
|
|
130
130
|
:scope {
|
|
131
131
|
display: inline-block;
|
|
132
132
|
min-block-size: var(--ui-badge-min-block-size, 1.5rem);
|
|
133
133
|
text-align: center;
|
|
134
134
|
align-content: center;
|
|
135
|
-
text-box-trim:
|
|
136
|
-
text-box-edge: text;
|
|
135
|
+
text-box-trim: none;
|
|
137
136
|
}
|
|
138
137
|
:scope:has(.icon:not(:empty)),
|
|
139
138
|
:scope:has(.label svg) {
|
|
140
139
|
display: inline-flex;
|
|
141
140
|
text-box-trim: none;
|
|
142
141
|
}
|
|
143
|
-
|
|
142
|
+
.label:not(:has(svg)) {
|
|
144
143
|
text-box-trim: trim-both;
|
|
145
|
-
text-box-edge:
|
|
144
|
+
text-box-edge: cap alphabetic;
|
|
145
|
+
overflow-x: clip;
|
|
146
|
+
overflow-y: visible;
|
|
146
147
|
}
|
|
147
148
|
}
|
|
148
149
|
|
|
@@ -223,6 +224,29 @@
|
|
|
223
224
|
--_badge-color-text: color-mix(in oklab, var(--_ui-badge-color) 0%, var(--ui-surface-default));
|
|
224
225
|
}
|
|
225
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
|
+
|
|
226
250
|
:scope[data-ui-badge-state~="variant=outline"] {
|
|
227
251
|
--_badge-surface: var(--ui-surface-default);
|
|
228
252
|
--_badge-border: var(--ui-border-strong);
|
|
@@ -276,6 +300,10 @@
|
|
|
276
300
|
--_badge-square-size: var(--ui-badge-square-size, calc(var(--ui-space-3) * 2));
|
|
277
301
|
}
|
|
278
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
|
+
|
|
279
307
|
:scope[data-ui-badge-state~="shape=square"] .label {
|
|
280
308
|
display: inline-flex;
|
|
281
309
|
align-items: center;
|
|
@@ -304,6 +332,10 @@
|
|
|
304
332
|
--_badge-circle-size: var(--ui-badge-circle-size, var(--ui-space-4));
|
|
305
333
|
}
|
|
306
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
|
+
|
|
307
339
|
:scope[data-ui-badge-state~="shape=circle"] .label {
|
|
308
340
|
display: inline-flex;
|
|
309
341
|
align-items: center;
|
|
@@ -316,6 +348,18 @@
|
|
|
316
348
|
display: none;
|
|
317
349
|
}
|
|
318
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
|
+
|
|
319
363
|
/* A dot uses the badge's tone recipe; only its silhouette and visible label change. */
|
|
320
364
|
:scope[data-ui-badge-state~="shape=dot"] {
|
|
321
365
|
position: relative;
|
|
@@ -363,6 +407,10 @@
|
|
|
363
407
|
pointer-events: none;
|
|
364
408
|
}
|
|
365
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
|
+
|
|
366
414
|
:scope[data-ui-badge-state~="shape=tag"]:dir(rtl) {
|
|
367
415
|
padding-inline-end: var(--ui-badge-padding-inline, var(--ui-space-2));
|
|
368
416
|
padding-inline-start: calc(var(--ui-badge-padding-inline, var(--ui-space-2)) + var(--_badge-point));
|
|
@@ -419,14 +467,20 @@
|
|
|
419
467
|
margin-inline-end: var(--ui-space-1);
|
|
420
468
|
}
|
|
421
469
|
|
|
422
|
-
/*
|
|
423
|
-
|
|
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. */
|
|
424
472
|
@supports (text-box-trim: trim-both) {
|
|
425
473
|
}
|
|
426
474
|
|
|
427
475
|
/* A solid colour fills with the ink. Its text is the page surface, mixed at 0% only so that it too is
|
|
428
476
|
invalid while the hook is unset. */
|
|
429
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
|
+
|
|
430
484
|
/* An icon mark keeps the existing tone recipe and follows the global spacing and radius. */
|
|
431
485
|
|
|
432
486
|
:scope[data-ui-badge-state~="shape=square"] .label :where([data-slotted], [data-slotted] *):is(svg) {
|
|
@@ -439,6 +493,11 @@
|
|
|
439
493
|
margin-inline-end: 0;
|
|
440
494
|
}
|
|
441
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
|
+
|
|
442
501
|
/* A dot uses the badge's tone recipe; only its silhouette and visible label change. */
|
|
443
502
|
|
|
444
503
|
/* The clipped point needs an inset surface over an outer border-colour shape: a native border
|
package/styles/ui-list-item.css
CHANGED
|
@@ -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;
|
package/vanilla/UiBadge.d.ts
CHANGED
|
@@ -7,9 +7,9 @@ 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" | 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)[]>>;
|
package/vanilla/UiBadge.js
CHANGED
|
@@ -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"],"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;
|
package/vanilla/UiListItem.d.ts
CHANGED
|
@@ -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;
|
package/vanilla/UiListItem.js
CHANGED
|
@@ -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 &
|
|
59
|
-
const x0 = v[
|
|
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 &
|
|
64
|
-
const x1 = hostState(["variant","highlighted","current","leadingInteractive","wrap"], [v[
|
|
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:
|
|
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
|
@@ -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" | null>;
|
|
14
|
+
type: PropType<"xs" | "md" | "lg" | null>;
|
|
15
15
|
default: string;
|
|
16
16
|
};
|
|
17
17
|
tone: {
|
|
@@ -19,7 +19,7 @@ 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<{
|
|
@@ -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" | 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
43
|
shape: "circle" | "dot" | "tag" | "square" | "pill" | null;
|
|
44
|
-
size: "xs" | "md" | 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;
|
package/vue/UiBadge.vue
CHANGED
|
@@ -13,7 +13,7 @@ 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. circle frames a visible
|
|
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 glyph, icon or short bounded count in a fixed circle, centered in both\n directions.',
|
|
17
17
|
values: ['pill', 'tag', 'dot', 'square', 'circle'],
|
|
18
18
|
default: 'pill',
|
|
19
19
|
},
|
|
@@ -22,8 +22,8 @@ const propValidityContract = {
|
|
|
22
22
|
required: false,
|
|
23
23
|
target: { attribute: 'size' },
|
|
24
24
|
description:
|
|
25
|
-
'Scale. xs is
|
|
26
|
-
values: ['xs', 'md'],
|
|
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'],
|
|
27
27
|
default: 'md',
|
|
28
28
|
},
|
|
29
29
|
tone: {
|
|
@@ -40,8 +40,8 @@ const propValidityContract = {
|
|
|
40
40
|
required: false,
|
|
41
41
|
target: { attribute: 'variant' },
|
|
42
42
|
description:
|
|
43
|
-
'Emphasis. subtle sets toned text over a soft wash; solid fills with the tone and uses contrasting text; outline keeps the surface with a toned edge.\n Neutral looks the same in solid and
|
|
44
|
-
values: ['solid', 'subtle', 'outline'],
|
|
43
|
+
'Emphasis. subtle sets toned text over a soft wash; solid fills with the tone and uses contrasting text; outline keeps the surface with a toned edge.\n inverse uses a translucent contrasting surface and toned ink for a count or label on a filled control of that tone. Neutral looks the same in solid and\n subtle. When space is constrained, the label truncates with an ellipsis while its full text remains available.',
|
|
44
|
+
values: ['solid', 'subtle', 'outline', 'inverse'],
|
|
45
45
|
default: 'subtle',
|
|
46
46
|
},
|
|
47
47
|
},
|
|
@@ -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' | null>, default: 'md' },
|
|
54
|
+
size: { type: null as unknown as PropType<'xs' | '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
|
|
@@ -59,7 +59,7 @@ const props = defineProps({
|
|
|
59
59
|
default: 'neutral',
|
|
60
60
|
},
|
|
61
61
|
variant: {
|
|
62
|
-
type: null as unknown as PropType<'solid' | 'subtle' | 'outline' | null>,
|
|
62
|
+
type: null as unknown as PropType<'solid' | 'subtle' | 'outline' | 'inverse' | null>,
|
|
63
63
|
default: 'subtle',
|
|
64
64
|
},
|
|
65
65
|
})
|
|
@@ -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'>(
|
|
80
|
+
size: checkedProp<'xs' | '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',
|
|
86
|
+
) as 'xs' | 'md' | 'lg',
|
|
87
87
|
tone: checkedProp<'neutral' | 'accent' | 'info' | 'success' | 'warning' | 'danger'>(
|
|
88
88
|
props['tone'],
|
|
89
89
|
{ kind: 'terminal', name: 'keyword' },
|
|
@@ -91,13 +91,13 @@ const checkedProps = computed(() => ({
|
|
|
91
91
|
'tone',
|
|
92
92
|
acceptedProps,
|
|
93
93
|
) as 'neutral' | 'accent' | 'info' | 'success' | 'warning' | 'danger',
|
|
94
|
-
variant: checkedProp<'solid' | 'subtle' | 'outline'>(
|
|
94
|
+
variant: checkedProp<'solid' | 'subtle' | 'outline' | 'inverse'>(
|
|
95
95
|
props['variant'],
|
|
96
96
|
{ kind: 'terminal', name: 'keyword' },
|
|
97
97
|
false,
|
|
98
98
|
'variant',
|
|
99
99
|
acceptedProps,
|
|
100
|
-
) as 'solid' | 'subtle' | 'outline',
|
|
100
|
+
) as 'solid' | 'subtle' | 'outline' | 'inverse',
|
|
101
101
|
}))
|
|
102
102
|
void checkedProps.value
|
|
103
103
|
watchSyncEffect(() => {
|
|
@@ -328,25 +328,26 @@ const hostState = computed(() =>
|
|
|
328
328
|
text-box-trim: none;
|
|
329
329
|
}
|
|
330
330
|
|
|
331
|
-
/*
|
|
332
|
-
|
|
331
|
+
/* Center the capital/baseline text box, while clipping only inline overflow for ellipsis.
|
|
332
|
+
Vertical ink remains visible so trimming never cuts off ascenders or descenders. */
|
|
333
333
|
@supports (text-box-trim: trim-both) {
|
|
334
334
|
[data-component~="ui-badge"] {
|
|
335
335
|
display: inline-block;
|
|
336
336
|
min-block-size: var(--ui-badge-min-block-size, 1.5rem);
|
|
337
337
|
text-align: center;
|
|
338
338
|
align-content: center;
|
|
339
|
-
text-box-trim:
|
|
340
|
-
text-box-edge: text;
|
|
339
|
+
text-box-trim: none;
|
|
341
340
|
}
|
|
342
341
|
[data-component~="ui-badge"]:has(.icon:not(:empty)),
|
|
343
342
|
[data-component~="ui-badge"]:has(.label svg) {
|
|
344
343
|
display: inline-flex;
|
|
345
344
|
text-box-trim: none;
|
|
346
345
|
}
|
|
347
|
-
|
|
346
|
+
.label:not(:has(svg)) {
|
|
348
347
|
text-box-trim: trim-both;
|
|
349
|
-
text-box-edge:
|
|
348
|
+
text-box-edge: cap alphabetic;
|
|
349
|
+
overflow-x: clip;
|
|
350
|
+
overflow-y: visible;
|
|
350
351
|
}
|
|
351
352
|
}
|
|
352
353
|
|
|
@@ -427,6 +428,42 @@ const hostState = computed(() =>
|
|
|
427
428
|
--_badge-color-text: color-mix(in oklab, var(--_ui-badge-color) 0%, var(--ui-surface-default));
|
|
428
429
|
}
|
|
429
430
|
|
|
431
|
+
/* A translucent inverse surface picks up its filled control's tone. Deepen the tone ink
|
|
432
|
+
toward the theme text to retain contrast against that composed surface. */
|
|
433
|
+
[data-component~="ui-badge"][data-ui-badge-state~="variant=inverse"] {
|
|
434
|
+
--_badge-color-surface: color-mix(
|
|
435
|
+
in srgb,
|
|
436
|
+
color-mix(in oklab, var(--_ui-badge-color) 0%, var(--ui-surface-default)) 88%,
|
|
437
|
+
transparent
|
|
438
|
+
);
|
|
439
|
+
--_badge-color-border: var(--_badge-color-surface);
|
|
440
|
+
--_badge-color-text: color-mix(in srgb, var(--_badge-color-ink) 85%, var(--ui-text-primary));
|
|
441
|
+
--_badge-outline-surface: var(
|
|
442
|
+
--ui-badge-surface,
|
|
443
|
+
var(--_badge-color-surface, color-mix(in srgb, var(--_badge-text) 88%, transparent))
|
|
444
|
+
);
|
|
445
|
+
border-color: var(
|
|
446
|
+
--ui-badge-border,
|
|
447
|
+
var(--_badge-color-border, color-mix(in srgb, var(--_badge-text) 88%, transparent))
|
|
448
|
+
);
|
|
449
|
+
color: var(
|
|
450
|
+
--ui-badge-text,
|
|
451
|
+
var(--_badge-color-text, color-mix(in srgb, var(--_badge-surface) 85%, var(--ui-text-primary)))
|
|
452
|
+
);
|
|
453
|
+
/* Paint each translucent layer once, including the border, without a bright double-painted ring. */
|
|
454
|
+
background-clip: padding-box;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
[data-component~="ui-badge"][data-ui-badge-state~="variant=inverse"][data-ui-badge-state~="shape=circle"] {
|
|
458
|
+
box-shadow: var(--ui-shadow-sm);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/* Neutral reverses a subtle surface/ink pair rather than a strong tone. Keep its original
|
|
462
|
+
surface ink: moving it toward dark-theme primary text would reduce overlay contrast. */
|
|
463
|
+
[data-component~="ui-badge"][data-ui-badge-state~="variant=inverse"][data-ui-badge-state~="tone=neutral"] {
|
|
464
|
+
color: var(--ui-badge-text, var(--_badge-color-text, var(--_badge-surface)));
|
|
465
|
+
}
|
|
466
|
+
|
|
430
467
|
[data-component~="ui-badge"][data-ui-badge-state~="variant=outline"] {
|
|
431
468
|
--_badge-surface: var(--ui-surface-default);
|
|
432
469
|
--_badge-border: var(--ui-border-strong);
|
|
@@ -480,6 +517,13 @@ const hostState = computed(() =>
|
|
|
480
517
|
--_badge-square-size: var(--ui-badge-square-size, calc(var(--ui-space-3) * 2));
|
|
481
518
|
}
|
|
482
519
|
|
|
520
|
+
[data-component~="ui-badge"][data-ui-badge-state~="shape=square"][data-ui-badge-state~="size=lg"] {
|
|
521
|
+
--_badge-square-size: var(
|
|
522
|
+
--ui-badge-square-size,
|
|
523
|
+
calc(var(--ui-space-4) * 2 + var(--ui-space-2))
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
|
|
483
527
|
[data-component~="ui-badge"][data-ui-badge-state~="shape=square"] .label {
|
|
484
528
|
display: inline-flex;
|
|
485
529
|
align-items: center;
|
|
@@ -512,6 +556,10 @@ const hostState = computed(() =>
|
|
|
512
556
|
--_badge-circle-size: var(--ui-badge-circle-size, var(--ui-space-4));
|
|
513
557
|
}
|
|
514
558
|
|
|
559
|
+
[data-component~="ui-badge"][data-ui-badge-state~="shape=circle"][data-ui-badge-state~="size=lg"] {
|
|
560
|
+
--_badge-circle-size: var(--ui-badge-circle-size, calc(var(--ui-space-4) * 2));
|
|
561
|
+
}
|
|
562
|
+
|
|
515
563
|
[data-component~="ui-badge"][data-ui-badge-state~="shape=circle"] .label {
|
|
516
564
|
display: inline-flex;
|
|
517
565
|
align-items: center;
|
|
@@ -528,6 +576,18 @@ const hostState = computed(() =>
|
|
|
528
576
|
display: none;
|
|
529
577
|
}
|
|
530
578
|
|
|
579
|
+
/* Center capital and numeral ink, rather than the font's ascent/descent line box.
|
|
580
|
+
Icon labels retain flex geometry; untrimmed browsers keep that fallback too. */
|
|
581
|
+
@supports (text-box-trim: trim-both) {
|
|
582
|
+
[data-component~="ui-badge"][data-ui-badge-state~="shape=circle"] .label:not(:has(svg)),
|
|
583
|
+
[data-component~="ui-badge"][data-ui-badge-state~="shape=square"] .label:not(:has(svg)) {
|
|
584
|
+
display: block;
|
|
585
|
+
text-box-trim: trim-both;
|
|
586
|
+
text-box-edge: cap alphabetic;
|
|
587
|
+
overflow: visible;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
531
591
|
/* A dot uses the badge's tone recipe; only its silhouette and visible label change. */
|
|
532
592
|
[data-component~="ui-badge"][data-ui-badge-state~="shape=dot"] {
|
|
533
593
|
position: relative;
|
|
@@ -587,6 +647,10 @@ const hostState = computed(() =>
|
|
|
587
647
|
pointer-events: none;
|
|
588
648
|
}
|
|
589
649
|
|
|
650
|
+
[data-component~="ui-badge"][data-ui-badge-state~="shape=tag"][data-ui-badge-state~="variant=inverse"] {
|
|
651
|
+
background: var(--ui-badge-border, var(--_badge-color-border, var(--_badge-text)));
|
|
652
|
+
}
|
|
653
|
+
|
|
590
654
|
[data-component~="ui-badge"][data-ui-badge-state~="shape=tag"]:dir(rtl) {
|
|
591
655
|
padding-inline-end: var(--ui-badge-padding-inline, var(--ui-space-2));
|
|
592
656
|
padding-inline-start: calc(
|
package/vue/UiCard.d.ts
CHANGED
|
@@ -52,7 +52,7 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
|
|
|
52
52
|
default: string;
|
|
53
53
|
};
|
|
54
54
|
}>> & Readonly<{}>, {
|
|
55
|
-
size: "sm" | "md" | "
|
|
55
|
+
size: "sm" | "md" | "lg" | "content" | null;
|
|
56
56
|
tone: "info" | "neutral" | "accent" | "success" | "warning" | "danger" | null;
|
|
57
57
|
variant: "subtle" | "outlined" | "elevated" | null;
|
|
58
58
|
as: "div" | "article" | "aside" | "section" | null;
|
package/vue/UiListItem.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ type __VLS_Slots = {} & {
|
|
|
10
10
|
trailing?: (props: typeof __VLS_7) => any;
|
|
11
11
|
};
|
|
12
12
|
declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
13
|
+
align: {
|
|
14
|
+
type: PropType<"center" | "title" | null>;
|
|
15
|
+
default: string;
|
|
16
|
+
};
|
|
13
17
|
current: {
|
|
14
18
|
type: PropType<boolean | null>;
|
|
15
19
|
default: boolean;
|
|
@@ -31,6 +35,10 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
|
|
|
31
35
|
default: boolean;
|
|
32
36
|
};
|
|
33
37
|
}>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
38
|
+
align: {
|
|
39
|
+
type: PropType<"center" | "title" | null>;
|
|
40
|
+
default: string;
|
|
41
|
+
};
|
|
34
42
|
current: {
|
|
35
43
|
type: PropType<boolean | null>;
|
|
36
44
|
default: boolean;
|
|
@@ -54,6 +62,7 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
|
|
|
54
62
|
}>> & Readonly<{}>, {
|
|
55
63
|
variant: "card" | "row" | null;
|
|
56
64
|
current: boolean | null;
|
|
65
|
+
align: "title" | "center" | null;
|
|
57
66
|
wrap: boolean | null;
|
|
58
67
|
highlighted: boolean | null;
|
|
59
68
|
leadingInteractive: boolean | null;
|