@threadlabs/looma 0.26.1 → 0.26.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/ui-avatar/ui-avatar.html +6 -1
- package/components/ui-badge/ui-badge.html +101 -12
- package/components/ui-container/ui-container.html +11 -0
- package/components/ui-icon-button/ui-icon-button.html +58 -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 +107 -8
- package/styles/ui-container.css +8 -0
- package/styles/ui-icon-button.css +62 -12
- package/styles/ui-list-item.css +12 -0
- package/vanilla/UiBadge.d.ts +3 -3
- package/vanilla/UiBadge.js +1 -1
- package/vanilla/UiContainer.d.ts +1 -0
- package/vanilla/UiContainer.js +5 -5
- package/vanilla/UiIconButton.d.ts +1 -0
- package/vanilla/UiIconButton.js +4 -4
- package/vanilla/UiListItem.d.ts +1 -0
- package/vanilla/UiListItem.js +6 -6
- package/vue/UiAvatar.vue +6 -1
- package/vue/UiBadge.d.ts +9 -9
- package/vue/UiBadge.vue +123 -21
- package/vue/UiCard.d.ts +1 -1
- package/vue/UiContainer.d.ts +9 -0
- package/vue/UiContainer.vue +27 -1
- package/vue/UiIconButton.d.ts +9 -0
- package/vue/UiIconButton.vue +95 -12
- 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 +13 -7
- package/vue/chunks/UiContainer.js +23 -1
- package/vue/chunks/UiIconButton.js +25 -1
- package/vue/chunks/UiListItem.js +26 -2
- package/vue/components.css +319 -147
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 glyph, icon or short bounded count in a fixed circle, centered in both\n directions.',
|
|
17
|
+
values: ['pill', 'tag', 'dot', 'square', 'circle'],
|
|
18
18
|
default: 'pill',
|
|
19
19
|
},
|
|
20
20
|
size: {
|
|
@@ -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,18 +40,18 @@ 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
|
},
|
|
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
|
-
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
|
})
|
|
@@ -70,20 +70,20 @@ 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',
|
|
80
|
-
size: checkedProp<'xs' | 'md'>(
|
|
79
|
+
) as 'pill' | 'tag' | 'dot' | 'square' | 'circle',
|
|
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(() => {
|
|
@@ -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;
|
|
@@ -324,25 +328,26 @@ const hostState = computed(() =>
|
|
|
324
328
|
text-box-trim: none;
|
|
325
329
|
}
|
|
326
330
|
|
|
327
|
-
/*
|
|
328
|
-
|
|
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. */
|
|
329
333
|
@supports (text-box-trim: trim-both) {
|
|
330
334
|
[data-component~="ui-badge"] {
|
|
331
335
|
display: inline-block;
|
|
332
336
|
min-block-size: var(--ui-badge-min-block-size, 1.5rem);
|
|
333
337
|
text-align: center;
|
|
334
338
|
align-content: center;
|
|
335
|
-
text-box-trim:
|
|
336
|
-
text-box-edge: text;
|
|
339
|
+
text-box-trim: none;
|
|
337
340
|
}
|
|
338
341
|
[data-component~="ui-badge"]:has(.icon:not(:empty)),
|
|
339
342
|
[data-component~="ui-badge"]:has(.label svg) {
|
|
340
343
|
display: inline-flex;
|
|
341
344
|
text-box-trim: none;
|
|
342
345
|
}
|
|
343
|
-
|
|
346
|
+
.label:not(:has(svg)) {
|
|
344
347
|
text-box-trim: trim-both;
|
|
345
|
-
text-box-edge:
|
|
348
|
+
text-box-edge: cap alphabetic;
|
|
349
|
+
overflow-x: clip;
|
|
350
|
+
overflow-y: visible;
|
|
346
351
|
}
|
|
347
352
|
}
|
|
348
353
|
|
|
@@ -423,6 +428,42 @@ const hostState = computed(() =>
|
|
|
423
428
|
--_badge-color-text: color-mix(in oklab, var(--_ui-badge-color) 0%, var(--ui-surface-default));
|
|
424
429
|
}
|
|
425
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
|
+
|
|
426
467
|
[data-component~="ui-badge"][data-ui-badge-state~="variant=outline"] {
|
|
427
468
|
--_badge-surface: var(--ui-surface-default);
|
|
428
469
|
--_badge-border: var(--ui-border-strong);
|
|
@@ -476,6 +517,13 @@ const hostState = computed(() =>
|
|
|
476
517
|
--_badge-square-size: var(--ui-badge-square-size, calc(var(--ui-space-3) * 2));
|
|
477
518
|
}
|
|
478
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
|
+
|
|
479
527
|
[data-component~="ui-badge"][data-ui-badge-state~="shape=square"] .label {
|
|
480
528
|
display: inline-flex;
|
|
481
529
|
align-items: center;
|
|
@@ -490,6 +538,56 @@ const hostState = computed(() =>
|
|
|
490
538
|
display: none;
|
|
491
539
|
}
|
|
492
540
|
|
|
541
|
+
/* A visible glyph needs fixed geometry, unlike a content-sized pill or a hidden-label dot. */
|
|
542
|
+
[data-component~="ui-badge"][data-ui-badge-state~="shape=circle"] {
|
|
543
|
+
--_badge-circle-size: var(--ui-badge-circle-size, calc(var(--ui-space-3) * 2));
|
|
544
|
+
display: inline-flex;
|
|
545
|
+
flex: none;
|
|
546
|
+
inline-size: var(--_badge-circle-size);
|
|
547
|
+
block-size: var(--_badge-circle-size);
|
|
548
|
+
min-inline-size: var(--_badge-circle-size);
|
|
549
|
+
min-block-size: var(--_badge-circle-size);
|
|
550
|
+
padding: 0;
|
|
551
|
+
border-radius: var(--ui-radius-round);
|
|
552
|
+
text-box-trim: none;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
[data-component~="ui-badge"][data-ui-badge-state~="shape=circle"][data-ui-badge-state~="size=xs"] {
|
|
556
|
+
--_badge-circle-size: var(--ui-badge-circle-size, var(--ui-space-4));
|
|
557
|
+
}
|
|
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
|
+
|
|
563
|
+
[data-component~="ui-badge"][data-ui-badge-state~="shape=circle"] .label {
|
|
564
|
+
display: inline-flex;
|
|
565
|
+
align-items: center;
|
|
566
|
+
justify-content: center;
|
|
567
|
+
line-height: 1;
|
|
568
|
+
text-box-trim: none;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
[data-component~="ui-badge"][data-ui-badge-state~="shape=circle"] .label :slotted(svg) {
|
|
572
|
+
margin-inline-end: 0;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
[data-component~="ui-badge"][data-ui-badge-state~="shape=circle"] .label:empty {
|
|
576
|
+
display: none;
|
|
577
|
+
}
|
|
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
|
+
|
|
493
591
|
/* A dot uses the badge's tone recipe; only its silhouette and visible label change. */
|
|
494
592
|
[data-component~="ui-badge"][data-ui-badge-state~="shape=dot"] {
|
|
495
593
|
position: relative;
|
|
@@ -549,6 +647,10 @@ const hostState = computed(() =>
|
|
|
549
647
|
pointer-events: none;
|
|
550
648
|
}
|
|
551
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
|
+
|
|
552
654
|
[data-component~="ui-badge"][data-ui-badge-state~="shape=tag"]:dir(rtl) {
|
|
553
655
|
padding-inline-end: var(--ui-badge-padding-inline, var(--ui-space-2));
|
|
554
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/UiContainer.d.ts
CHANGED
|
@@ -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>;
|
package/vue/UiContainer.vue
CHANGED
|
@@ -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
|
}
|
package/vue/UiIconButton.d.ts
CHANGED
|
@@ -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;
|
package/vue/UiIconButton.vue
CHANGED
|
@@ -71,6 +71,14 @@ const propValidityContract = {
|
|
|
71
71
|
values: ['sm', 'md', 'lg'],
|
|
72
72
|
default: 'md',
|
|
73
73
|
},
|
|
74
|
+
tone: {
|
|
75
|
+
type: { kind: 'terminal', name: 'keyword' },
|
|
76
|
+
required: false,
|
|
77
|
+
target: { attribute: 'tone' },
|
|
78
|
+
description:
|
|
79
|
+
'Colour for every variant. When omitted, ghost and outline keep their neutral appearance and solid keeps its accent fill. Set accent for a coloured action\n or neutral for a quiet one. Native aria-pressed="true" keeps an outline toggle visibly pressed; the consumer owns that state.',
|
|
80
|
+
values: ['accent', 'neutral'],
|
|
81
|
+
},
|
|
74
82
|
variant: {
|
|
75
83
|
type: { kind: 'terminal', name: 'keyword' },
|
|
76
84
|
required: false,
|
|
@@ -91,6 +99,7 @@ const props = defineProps({
|
|
|
91
99
|
pending: { type: null as unknown as PropType<boolean | null>, default: false },
|
|
92
100
|
round: { type: null as unknown as PropType<boolean | null>, default: false },
|
|
93
101
|
size: { type: null as unknown as PropType<'sm' | 'md' | 'lg' | null>, default: 'md' },
|
|
102
|
+
tone: { type: null as unknown as PropType<'accent' | 'neutral' | null>, default: null },
|
|
94
103
|
variant: {
|
|
95
104
|
type: null as unknown as PropType<'ghost' | 'outline' | 'solid' | null>,
|
|
96
105
|
default: 'ghost',
|
|
@@ -105,6 +114,7 @@ const acceptedProps: Record<string, unknown> = {
|
|
|
105
114
|
pending: false,
|
|
106
115
|
round: false,
|
|
107
116
|
size: 'md',
|
|
117
|
+
tone: null,
|
|
108
118
|
variant: 'ghost',
|
|
109
119
|
}
|
|
110
120
|
const checkedProps = computed(() => ({
|
|
@@ -164,6 +174,13 @@ const checkedProps = computed(() => ({
|
|
|
164
174
|
'size',
|
|
165
175
|
acceptedProps,
|
|
166
176
|
) as 'sm' | 'md' | 'lg',
|
|
177
|
+
tone: checkedProp<'accent' | 'neutral'>(
|
|
178
|
+
props['tone'],
|
|
179
|
+
{ kind: 'terminal', name: 'keyword' },
|
|
180
|
+
false,
|
|
181
|
+
'tone',
|
|
182
|
+
acceptedProps,
|
|
183
|
+
),
|
|
167
184
|
variant: checkedProp<'ghost' | 'outline' | 'solid'>(
|
|
168
185
|
props['variant'],
|
|
169
186
|
{ kind: 'terminal', name: 'keyword' },
|
|
@@ -269,6 +286,8 @@ if (
|
|
|
269
286
|
/** The values the styles' :host-state() rules test. */
|
|
270
287
|
const hostState = computed(() =>
|
|
271
288
|
[
|
|
289
|
+
checkedProps.value.tone && 'tone',
|
|
290
|
+
checkedProps.value.tone != null && `tone=${encodeURIComponent(checkedProps.value.tone)}`,
|
|
272
291
|
checkedProps.value.anticipatory && 'anticipatory',
|
|
273
292
|
checkedProps.value.variant && 'variant',
|
|
274
293
|
checkedProps.value.variant != null
|
|
@@ -308,6 +327,7 @@ useComponentHost(() => import('../components/ui-icon-button/ui-icon-button.js'),
|
|
|
308
327
|
'pending',
|
|
309
328
|
'round',
|
|
310
329
|
'size',
|
|
330
|
+
'tone',
|
|
311
331
|
'variant',
|
|
312
332
|
],
|
|
313
333
|
propValidity: (name: string) =>
|
|
@@ -364,6 +384,7 @@ function concat<T extends unknown[]>(...values: T): ConcatResult<T> {
|
|
|
364
384
|
:data-pending="reflectedProp('pending', 'pending', checkedProps.pending, undefined, false)"
|
|
365
385
|
:data-round="reflectedProp('round', 'round', checkedProps.round, undefined, false)"
|
|
366
386
|
:data-size="reflectedProp('size', 'size', checkedProps.size, undefined, false)"
|
|
387
|
+
:data-tone="reflectedProp('tone', 'tone', checkedProps.tone, undefined, false)"
|
|
367
388
|
:data-variant="reflectedProp('variant', 'variant', checkedProps.variant, undefined, false)"
|
|
368
389
|
:data-ui-icon-button-state="hostState || undefined"
|
|
369
390
|
ref="root"
|
|
@@ -461,6 +482,25 @@ function concat<T extends unknown[]>(...values: T): ConcatResult<T> {
|
|
|
461
482
|
color: var(--ui-text-secondary);
|
|
462
483
|
}
|
|
463
484
|
|
|
485
|
+
[data-component~="ui-icon-button"][data-ui-icon-button-state~="tone=accent"] {
|
|
486
|
+
--_icon-button-border: var(--ui-accent-solid);
|
|
487
|
+
--_icon-button-hover-border: var(--ui-accent-hover);
|
|
488
|
+
--_icon-button-text: var(--ui-accent-subtle-text, var(--ui-accent-solid));
|
|
489
|
+
--_icon-button-outline-surface: color-mix(
|
|
490
|
+
in srgb,
|
|
491
|
+
var(--ui-accent-solid) 5%,
|
|
492
|
+
var(--ui-surface-default)
|
|
493
|
+
);
|
|
494
|
+
color: var(--_icon-button-text);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/* Omitted tone retains the existing accent solid; explicit neutral is additive. */
|
|
498
|
+
[data-component~="ui-icon-button"][data-ui-icon-button-state~="tone=neutral"] {
|
|
499
|
+
--_icon-button-solid-surface: var(--ui-text-primary);
|
|
500
|
+
--_icon-button-solid-hover-surface: var(--ui-text-primary);
|
|
501
|
+
--_icon-button-solid-text: var(--ui-surface-default);
|
|
502
|
+
}
|
|
503
|
+
|
|
464
504
|
[data-component~="ui-icon-button"][data-ui-icon-button-state~="anticipatory"] {
|
|
465
505
|
position: relative;
|
|
466
506
|
}
|
|
@@ -495,13 +535,13 @@ function concat<T extends unknown[]>(...values: T): ConcatResult<T> {
|
|
|
495
535
|
}
|
|
496
536
|
|
|
497
537
|
@media (hover: hover) and (pointer: fine) {
|
|
498
|
-
[data-component~="ui-icon-button"][data-ui-icon-button-state~="anticipatory"]:not([data-component~="ui-icon-button"][data-ui-icon-button-state~="variant=solid"], [data-component~="ui-icon-button"][data-ui-icon-button-state~="loading"], [data-component~="ui-icon-button"][data-ui-icon-button-state~="pending"], :disabled, [data-ui-proximity="near"], :hover, :focus-visible) {
|
|
538
|
+
[data-component~="ui-icon-button"][data-ui-icon-button-state~="anticipatory"]:not([data-component~="ui-icon-button"][data-ui-icon-button-state~="variant=solid"], [data-component~="ui-icon-button"][data-ui-icon-button-state~="loading"], [data-component~="ui-icon-button"][data-ui-icon-button-state~="pending"], :disabled, [aria-pressed="true"], [data-ui-proximity="near"], :hover, :focus-visible) {
|
|
499
539
|
border-color: transparent;
|
|
500
540
|
background-color: transparent;
|
|
501
541
|
box-shadow: none;
|
|
502
542
|
}
|
|
503
543
|
|
|
504
|
-
[data-component~="ui-icon-button"][data-ui-icon-button-state~="anticipatory"]:not([data-component~="ui-icon-button"][data-ui-icon-button-state~="variant=solid"], [data-component~="ui-icon-button"][data-ui-icon-button-state~="loading"], [data-component~="ui-icon-button"][data-ui-icon-button-state~="pending"], :disabled, [data-ui-proximity="near"], :hover, :focus-visible)
|
|
544
|
+
[data-component~="ui-icon-button"][data-ui-icon-button-state~="anticipatory"]:not([data-component~="ui-icon-button"][data-ui-icon-button-state~="variant=solid"], [data-component~="ui-icon-button"][data-ui-icon-button-state~="loading"], [data-component~="ui-icon-button"][data-ui-icon-button-state~="pending"], :disabled, [aria-pressed="true"], [data-ui-proximity="near"], :hover, :focus-visible)
|
|
505
545
|
.content {
|
|
506
546
|
opacity: 0;
|
|
507
547
|
transform: scale(0.65);
|
|
@@ -509,7 +549,7 @@ function concat<T extends unknown[]>(...values: T): ConcatResult<T> {
|
|
|
509
549
|
}
|
|
510
550
|
}
|
|
511
551
|
|
|
512
|
-
[data-component~="ui-icon-button"][data-ui-icon-button-state~="anticipatory"]:is([data-component~="ui-icon-button"][data-ui-icon-button-state~="variant=solid"], [data-ui-proximity="near"], :hover, :focus-visible)::before {
|
|
552
|
+
[data-component~="ui-icon-button"][data-ui-icon-button-state~="anticipatory"]:is([data-component~="ui-icon-button"][data-ui-icon-button-state~="variant=solid"], [aria-pressed="true"], [data-ui-proximity="near"], :hover, :focus-visible)::before {
|
|
513
553
|
opacity: 0;
|
|
514
554
|
}
|
|
515
555
|
|
|
@@ -622,17 +662,26 @@ html[data-ui-input-modality="touch"] [data-component~="ui-icon-button"]::after {
|
|
|
622
662
|
}
|
|
623
663
|
|
|
624
664
|
[data-component~="ui-icon-button"][data-ui-icon-button-state~="variant=outline"] {
|
|
625
|
-
border-color: var(
|
|
626
|
-
|
|
665
|
+
border-color: var(
|
|
666
|
+
--ui-icon-button-border,
|
|
667
|
+
var(--_icon-button-border, var(--ui-control-border, var(--ui-border-strong)))
|
|
668
|
+
);
|
|
669
|
+
background-color: var(
|
|
670
|
+
--ui-icon-button-outline-surface,
|
|
671
|
+
var(--_icon-button-outline-surface, var(--ui-surface-default))
|
|
672
|
+
);
|
|
627
673
|
background-image: var(--ui-raised-highlight);
|
|
628
|
-
color: var(--ui-icon-button-outline-text, var(--ui-text-primary));
|
|
674
|
+
color: var(--ui-icon-button-outline-text, var(--_icon-button-text, var(--ui-text-primary)));
|
|
629
675
|
box-shadow: var(--ui-raised);
|
|
630
676
|
}
|
|
631
677
|
|
|
632
678
|
[data-component~="ui-icon-button"][data-ui-icon-button-state~="variant=solid"] {
|
|
633
|
-
background-color: var(
|
|
679
|
+
background-color: var(
|
|
680
|
+
--ui-icon-button-solid-surface,
|
|
681
|
+
var(--_icon-button-solid-surface, var(--ui-accent-solid))
|
|
682
|
+
);
|
|
634
683
|
background-image: var(--ui-raised-highlight);
|
|
635
|
-
color: var(--ui-icon-button-solid-text, var(--ui-text-on-accent));
|
|
684
|
+
color: var(--ui-icon-button-solid-text, var(--_icon-button-solid-text, var(--ui-text-on-accent)));
|
|
636
685
|
box-shadow: var(
|
|
637
686
|
--ui-icon-button-solid-shadow,
|
|
638
687
|
0 1px 2px color-mix(in srgb, var(--ui-accent-solid) 18%, transparent)
|
|
@@ -645,18 +694,26 @@ html[data-ui-input-modality="touch"] [data-component~="ui-icon-button"]::after {
|
|
|
645
694
|
--ui-icon-button-hover-surface,
|
|
646
695
|
color-mix(in srgb, currentColor 8%, transparent)
|
|
647
696
|
);
|
|
648
|
-
color: var(--ui-icon-button-hover-text, var(--ui-text-primary));
|
|
697
|
+
color: var(--ui-icon-button-hover-text, var(--_icon-button-text, var(--ui-text-primary)));
|
|
649
698
|
}
|
|
650
699
|
|
|
651
700
|
[data-component~="ui-icon-button"][data-ui-icon-button-state~="variant=outline"]:hover:enabled:not([aria-disabled="true"]) {
|
|
652
701
|
border-color: var(
|
|
653
702
|
--ui-icon-button-hover-border,
|
|
654
|
-
var(--ui-control-border-hover, var(--ui-control-border))
|
|
703
|
+
var(--_icon-button-hover-border, var(--ui-control-border-hover, var(--ui-control-border)))
|
|
655
704
|
);
|
|
656
705
|
}
|
|
657
706
|
|
|
658
707
|
[data-component~="ui-icon-button"][data-ui-icon-button-state~="variant=solid"]:hover:enabled:not([aria-disabled="true"]) {
|
|
659
|
-
background-color: var(
|
|
708
|
+
background-color: var(
|
|
709
|
+
--ui-icon-button-solid-hover-surface,
|
|
710
|
+
var(--_icon-button-solid-hover-surface, var(--ui-accent-hover))
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
[data-component~="ui-icon-button"][data-ui-icon-button-state~="variant=solid"][data-ui-icon-button-state~="tone=accent"]:hover:enabled:not([aria-disabled="true"]),
|
|
715
|
+
[data-component~="ui-icon-button"][data-ui-icon-button-state~="variant=solid"][data-ui-icon-button-state~="tone=neutral"]:hover:enabled:not([aria-disabled="true"]) {
|
|
716
|
+
color: var(--ui-icon-button-solid-text, var(--_icon-button-solid-text, var(--ui-text-on-accent)));
|
|
660
717
|
}
|
|
661
718
|
|
|
662
719
|
[data-component~="ui-icon-button"][data-ui-icon-button-state~="variant=ghost"][aria-expanded="true"]:enabled:not([aria-disabled="true"]) {
|
|
@@ -664,7 +721,17 @@ html[data-ui-input-modality="touch"] [data-component~="ui-icon-button"]::after {
|
|
|
664
721
|
--ui-icon-button-active-surface,
|
|
665
722
|
color-mix(in srgb, currentColor 16%, transparent)
|
|
666
723
|
);
|
|
667
|
-
color: var(--ui-icon-button-hover-text, var(--ui-text-primary));
|
|
724
|
+
color: var(--ui-icon-button-hover-text, var(--_icon-button-text, var(--ui-text-primary)));
|
|
725
|
+
box-shadow: var(--ui-pressed);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/* Native toggle state persists after pointer release, including during loading. */
|
|
729
|
+
[data-component~="ui-icon-button"][data-ui-icon-button-state~="variant=outline"][aria-pressed="true"]:enabled {
|
|
730
|
+
background-color: var(
|
|
731
|
+
--ui-icon-button-active-surface,
|
|
732
|
+
color-mix(in srgb, currentColor 12%, var(--ui-surface-default))
|
|
733
|
+
);
|
|
734
|
+
background-image: none;
|
|
668
735
|
box-shadow: var(--ui-pressed);
|
|
669
736
|
}
|
|
670
737
|
|
|
@@ -698,6 +765,22 @@ html[data-ui-input-modality="touch"] [data-component~="ui-icon-button"]::after {
|
|
|
698
765
|
box-shadow: var(--ui-focus-halo);
|
|
699
766
|
}
|
|
700
767
|
|
|
768
|
+
[data-component~="ui-icon-button"][data-ui-icon-button-state~="variant=outline"][aria-pressed="true"]:enabled:focus-visible {
|
|
769
|
+
box-shadow:
|
|
770
|
+
var(--ui-pressed),
|
|
771
|
+
var(--ui-focus-halo);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
@media (forced-colors: active) {
|
|
775
|
+
[data-component~="ui-icon-button"][data-ui-icon-button-state~="variant=outline"][aria-pressed="true"]:enabled {
|
|
776
|
+
outline: var(--ui-focus-width) solid Highlight;
|
|
777
|
+
outline-offset: calc(-2 * var(--ui-focus-width));
|
|
778
|
+
}
|
|
779
|
+
[data-component~="ui-icon-button"][data-ui-icon-button-state~="variant=outline"][aria-pressed="true"]:enabled:focus-visible {
|
|
780
|
+
outline-offset: var(--ui-focus-width);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
701
784
|
/* Disabled is a flat neutral control, independent of the active variant's tone. */
|
|
702
785
|
[data-component~="ui-icon-button"]:disabled {
|
|
703
786
|
border-color: var(--ui-disabled-border);
|
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;
|