@threadlabs/looma 0.17.3 → 0.18.0
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 +8 -4
- package/components/ui-badge/ui-badge.html +28 -34
- package/components/ui-button/ui-button.html +5 -0
- package/components/ui-checkbox/ui-checkbox.html +5 -5
- package/components/ui-combobox/ui-combobox.html +4 -48
- package/components/ui-icon-button/ui-icon-button.html +7 -7
- package/components/ui-input/ui-input.html +1 -1
- package/components/ui-input-group/ui-input-group.html +2 -2
- package/components/ui-listbox/ui-listbox.html +88 -12
- package/components/ui-listbox/ui-listbox.js +162 -18
- package/components/ui-search-result-row/ui-search-result-row.html +2 -6
- package/components/ui-search-shell/ui-search-shell.html +2 -11
- package/components/ui-select/ui-select.html +1 -1
- package/components/ui-spinner/ui-spinner.html +45 -18
- package/components/ui-textarea/ui-textarea.html +1 -1
- package/components/ui-tooltip/ui-tooltip.html +2 -2
- package/dist/index.js +1 -1
- package/editor/chunks/extensions.js +5 -0
- package/package.json +1 -1
- package/styles/ui-avatar.css +6 -2
- package/styles/ui-badge.css +30 -40
- package/styles/ui-button.css +7 -0
- package/styles/ui-checkbox.css +6 -6
- package/styles/ui-combobox.css +0 -52
- package/styles/ui-icon-button.css +7 -7
- package/styles/ui-input-group.css +2 -2
- package/styles/ui-input.css +1 -1
- package/styles/ui-listbox.css +33 -8
- package/styles/ui-search-result-row.css +2 -6
- package/styles/ui-search-shell.css +2 -13
- package/styles/ui-select.css +1 -1
- package/styles/ui-spinner.css +38 -19
- package/styles/ui-textarea.css +1 -1
- package/styles/ui-tooltip.css +2 -2
- package/tokens.css +67 -2
- package/vanilla/UiCombobox.js +9 -1
- package/vanilla/UiListbox.d.ts +3 -1
- package/vanilla/UiListbox.js +65 -18
- package/vanilla/UiSpinner.js +19 -1
- package/vue/UiAvatar.vue +11 -2
- package/vue/UiBadge.vue +30 -36
- package/vue/UiButton.vue +5 -0
- package/vue/UiCheckbox.vue +5 -5
- package/vue/UiCombobox.vue +4 -49
- package/vue/UiIconButton.vue +10 -7
- package/vue/UiInput.vue +1 -1
- package/vue/UiInputGroup.vue +2 -2
- package/vue/UiListbox.d.ts +18 -5
- package/vue/UiListbox.vue +142 -25
- package/vue/UiSearchResultRow.vue +2 -6
- package/vue/UiSearchShell.vue +2 -13
- package/vue/UiSelect.vue +1 -1
- package/vue/UiSpinner.vue +45 -18
- package/vue/UiTextarea.vue +1 -1
- package/vue/UiTooltip.vue +2 -2
- package/vue/chunks/UiAvatar.js +2 -1
- package/vue/chunks/UiBadge.js +1 -1
- package/vue/chunks/UiButton.js +1 -1
- package/vue/chunks/UiCheckbox.js +1 -1
- package/vue/chunks/UiCombobox.js +11 -2
- package/vue/chunks/UiIconButton.js +1 -1
- package/vue/chunks/UiInput.js +1 -1
- package/vue/chunks/UiInputGroup.js +1 -1
- package/vue/chunks/UiListbox.js +134 -20
- package/vue/chunks/UiSearchResultRow.js +1 -1
- package/vue/chunks/UiSearchShell.js +1 -1
- package/vue/chunks/UiSelect.js +1 -1
- package/vue/chunks/UiSpinner.js +29 -3
- package/vue/chunks/UiTextarea.js +1 -1
- package/vue/chunks/UiTooltip.js +1 -1
- package/vue/components.css +426 -440
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
<prop name="shape" type="circle | square" default="circle">Circle by default; square uses the theme's medium radius.</prop>
|
|
19
19
|
<prop name="decorative" type="boolean" default="false">Hides the avatar from assistive technology when adjacent text already identifies the person.</prop>
|
|
20
20
|
<prop name="active" type="boolean" default="false"
|
|
21
|
-
>Marks the person as active now, such as editing the same page: a
|
|
22
|
-
|
|
21
|
+
>Marks the person as active now, such as editing the same page: a fine accent ring around the avatar. The ring is not announced, so say what it means in alt
|
|
22
|
+
("Ada Lovelace, editing").</prop>
|
|
23
23
|
<state name="hasImage" :value="false"></state>
|
|
24
24
|
<state name="hasAuthoredImage" :value="false"></state>
|
|
25
25
|
<state name="initials" :value="'?'"></state>
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
inline-size: 2.5rem;
|
|
65
65
|
block-size: 2.5rem;
|
|
66
66
|
border-radius: var(--ui-avatar-radius, var(--ui-radius-round));
|
|
67
|
-
border: 1px solid var(--ui-avatar-border, var(--ui-
|
|
67
|
+
border: 1px solid var(--ui-avatar-border, color-mix(in srgb, var(--ui-accent-solid) 35%, var(--ui-surface-default)));
|
|
68
68
|
background: var(--ui-avatar-surface, var(--ui-accent-soft));
|
|
69
69
|
color: var(--ui-avatar-text, var(--ui-accent-subtle-text, var(--ui-accent-solid)));
|
|
70
70
|
font: inherit;
|
|
@@ -80,6 +80,10 @@
|
|
|
80
80
|
font-size: var(--ui-font-size-xs);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
:host-state([hasImage]) {
|
|
84
|
+
border-color: var(--ui-avatar-border, var(--ui-border-strong));
|
|
85
|
+
}
|
|
86
|
+
|
|
83
87
|
:host-state([shape="square"]) {
|
|
84
88
|
border-radius: var(--ui-avatar-radius, var(--ui-radius-md));
|
|
85
89
|
}
|
|
@@ -87,7 +91,7 @@
|
|
|
87
91
|
/* An outline, not a dot: the avatar clips its content to the circle, and an outline is drawn
|
|
88
92
|
outside the box, so the ring survives the clip and the overlap in an Avatar Group. */
|
|
89
93
|
:host-state([active]) {
|
|
90
|
-
outline:
|
|
94
|
+
outline: 1px solid var(--ui-avatar-active-ring, var(--ui-accent-solid));
|
|
91
95
|
outline-offset: 1px;
|
|
92
96
|
}
|
|
93
97
|
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
:host {
|
|
62
|
-
--_badge-surface: var(--ui-surface-subtle);
|
|
62
|
+
--_badge-surface: color-mix(in srgb, var(--ui-surface-subtle) 80%, var(--ui-surface-default));
|
|
63
63
|
--_badge-text: var(--ui-text-secondary);
|
|
64
64
|
/* Sit between the soft fill and its stronger theme ink. */
|
|
65
65
|
--_badge-border: color-mix(in srgb, var(--_badge-surface) 75%, var(--_badge-text) 25%);
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
surface, text, and border hooks. Each value is invalid, so the tone shows, while it is unset. The
|
|
68
68
|
mixes keep 4.5:1 text contrast for any colour, in a light theme or a dark one. */
|
|
69
69
|
--_ui-badge-color: var(--ui-badge-color);
|
|
70
|
-
--_badge-color-wash: color-mix(in oklab, var(--_ui-badge-color)
|
|
70
|
+
--_badge-color-wash: color-mix(in oklab, var(--_ui-badge-color) 13%, var(--ui-surface-default));
|
|
71
71
|
--_badge-color-ink: color-mix(in oklab, var(--_ui-badge-color) 35%, var(--ui-text-primary));
|
|
72
72
|
--_badge-color-surface: var(--_badge-color-wash);
|
|
73
73
|
--_badge-color-border: color-mix(in srgb, var(--_badge-color-wash) 75%, var(--_badge-color-ink) 25%);
|
|
@@ -135,32 +135,32 @@
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
:host-state([variant="subtle"][tone="accent"]) {
|
|
138
|
-
--_badge-surface: color-mix(in srgb, var(--ui-accent-solid)
|
|
139
|
-
--_badge-border: color-mix(in srgb, var(--ui-accent-solid)
|
|
138
|
+
--_badge-surface: color-mix(in srgb, var(--ui-accent-solid) 19%, var(--ui-surface-default));
|
|
139
|
+
--_badge-border: color-mix(in srgb, var(--ui-accent-solid) 25%, var(--ui-surface-default));
|
|
140
140
|
--_badge-text: var(--ui-accent-subtle-text, var(--ui-accent-solid));
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
:host-state([variant="subtle"][tone="danger"]) {
|
|
144
|
-
--_badge-surface: color-mix(in srgb, var(--ui-danger-solid)
|
|
145
|
-
--_badge-border: color-mix(in srgb, var(--ui-danger-solid)
|
|
144
|
+
--_badge-surface: color-mix(in srgb, var(--ui-danger-solid) 19%, var(--ui-surface-default));
|
|
145
|
+
--_badge-border: color-mix(in srgb, var(--ui-danger-solid) 25%, var(--ui-surface-default));
|
|
146
146
|
--_badge-text: var(--ui-danger-subtle-text, var(--ui-danger-solid));
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
:host-state([variant="subtle"][tone="success"]) {
|
|
150
|
-
--_badge-surface: color-mix(in srgb, var(--ui-success-solid)
|
|
151
|
-
--_badge-border: color-mix(in srgb, var(--ui-success-solid)
|
|
150
|
+
--_badge-surface: color-mix(in srgb, var(--ui-success-solid) 19%, var(--ui-surface-default));
|
|
151
|
+
--_badge-border: color-mix(in srgb, var(--ui-success-solid) 25%, var(--ui-surface-default));
|
|
152
152
|
--_badge-text: var(--ui-success-subtle-text, var(--ui-success-solid));
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
:host-state([variant="subtle"][tone="warning"]) {
|
|
156
|
-
--_badge-surface: color-mix(in srgb, var(--ui-warning-solid)
|
|
157
|
-
--_badge-border: color-mix(in srgb, var(--ui-warning-solid)
|
|
156
|
+
--_badge-surface: color-mix(in srgb, var(--ui-warning-solid) 19%, var(--ui-surface-default));
|
|
157
|
+
--_badge-border: color-mix(in srgb, var(--ui-warning-solid) 25%, var(--ui-surface-default));
|
|
158
158
|
--_badge-text: var(--ui-warning-subtle-text, var(--ui-warning-solid));
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
:host-state([variant="subtle"][tone="info"]) {
|
|
162
|
-
--_badge-surface: color-mix(in srgb, var(--ui-info-solid)
|
|
163
|
-
--_badge-border: color-mix(in srgb, var(--ui-info-solid)
|
|
162
|
+
--_badge-surface: color-mix(in srgb, var(--ui-info-solid) 19%, var(--ui-surface-default));
|
|
163
|
+
--_badge-border: color-mix(in srgb, var(--ui-info-solid) 25%, var(--ui-surface-default));
|
|
164
164
|
--_badge-text: var(--ui-info-subtle-text, var(--ui-info-solid));
|
|
165
165
|
}
|
|
166
166
|
|
|
@@ -206,54 +206,48 @@
|
|
|
206
206
|
--_badge-text: var(--ui-info-subtle-text, var(--ui-info-solid));
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
/*
|
|
210
|
-
|
|
211
|
-
label never runs into it. */
|
|
209
|
+
/* The clipped point needs an inset surface over an outer border-colour shape: a native border
|
|
210
|
+
has no diagonal edge to follow. Keep this edge for every tag variant. */
|
|
212
211
|
:host-state([shape="tag"]) {
|
|
213
212
|
--_badge-point: var(--ui-badge-point, var(--ui-space-2));
|
|
214
213
|
|
|
214
|
+
position: relative;
|
|
215
|
+
isolation: isolate;
|
|
215
216
|
border-color: transparent;
|
|
216
217
|
border-radius: 0;
|
|
218
|
+
background: var(--ui-badge-border, var(--_badge-color-border, var(--_badge-border)));
|
|
217
219
|
padding-inline-end: calc(var(--ui-badge-padding-inline, var(--ui-space-2)) + var(--_badge-point));
|
|
218
220
|
clip-path: polygon(0 0, calc(100% - var(--_badge-point)) 0, 100% 50%, calc(100% - var(--_badge-point)) 100%, 0 100%);
|
|
219
221
|
}
|
|
220
222
|
|
|
221
|
-
:host-state([shape="tag"])
|
|
222
|
-
padding-inline-end: var(--ui-badge-padding-inline, var(--ui-space-2));
|
|
223
|
-
padding-inline-start: calc(var(--ui-badge-padding-inline, var(--ui-space-2)) + var(--_badge-point));
|
|
224
|
-
clip-path: polygon(var(--_badge-point) 0, 100% 0, 100% 100%, var(--_badge-point) 100%, 0 50%);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/* The tag clips its point, including the native border. Draw the outer shape in the border
|
|
228
|
-
colour and an inset inner shape in the surface colour to keep a visible diagonal edge. */
|
|
229
|
-
:host-state([variant="outline"][shape="tag"]) {
|
|
230
|
-
position: relative;
|
|
231
|
-
isolation: isolate;
|
|
232
|
-
background: var(--ui-badge-border, var(--_badge-color-border, var(--_badge-border)));
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
:host-state([variant="outline"][shape="tag"])::before {
|
|
223
|
+
:host-state([shape="tag"])::before {
|
|
236
224
|
content: "";
|
|
237
225
|
position: absolute;
|
|
238
|
-
inset:
|
|
226
|
+
inset: 0;
|
|
239
227
|
z-index: -1;
|
|
240
228
|
background: var(--_badge-outline-surface);
|
|
241
229
|
clip-path: polygon(0 0, calc(100% - var(--_badge-point)) 0, 100% 50%, calc(100% - var(--_badge-point)) 100%, 0 100%);
|
|
242
230
|
pointer-events: none;
|
|
243
231
|
}
|
|
244
232
|
|
|
245
|
-
:host-state([
|
|
233
|
+
:host-state([shape="tag"]):dir(rtl) {
|
|
234
|
+
padding-inline-end: var(--ui-badge-padding-inline, var(--ui-space-2));
|
|
235
|
+
padding-inline-start: calc(var(--ui-badge-padding-inline, var(--ui-space-2)) + var(--_badge-point));
|
|
236
|
+
clip-path: polygon(var(--_badge-point) 0, 100% 0, 100% 100%, var(--_badge-point) 100%, 0 50%);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
:host-state([shape="tag"]):dir(rtl)::before {
|
|
246
240
|
clip-path: polygon(var(--_badge-point) 0, 100% 0, 100% 100%, var(--_badge-point) 100%, 0 50%);
|
|
247
241
|
}
|
|
248
242
|
|
|
249
243
|
@media (forced-colors: active) {
|
|
250
|
-
:host-state([
|
|
244
|
+
:host-state([shape="tag"]) {
|
|
251
245
|
forced-color-adjust: none;
|
|
252
246
|
background: CanvasText;
|
|
253
247
|
color: CanvasText;
|
|
254
248
|
}
|
|
255
249
|
|
|
256
|
-
:host-state([
|
|
250
|
+
:host-state([shape="tag"])::before {
|
|
257
251
|
background: Canvas;
|
|
258
252
|
}
|
|
259
253
|
}
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
box-shadow: var(--ui-control-inset);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
/* Centre the tick's box
|
|
126
|
+
/* Centre the tick's box and lift it slightly for the rotated check shape. */
|
|
127
127
|
input:checked::after {
|
|
128
128
|
inset: 0;
|
|
129
129
|
margin: auto;
|
|
@@ -135,7 +135,7 @@
|
|
|
135
135
|
border-color: var(--ui-text-on-accent);
|
|
136
136
|
border-width: 0 0.125rem 0.125rem 0;
|
|
137
137
|
opacity: 1;
|
|
138
|
-
transform:
|
|
138
|
+
transform: translateY(-0.09rem) rotate(45deg);
|
|
139
139
|
animation: ui-checkbox-check 180ms var(--ui-motion-ease-out, cubic-bezier(0.16, 1, 0.3, 1)) both;
|
|
140
140
|
}
|
|
141
141
|
|
|
@@ -151,15 +151,15 @@
|
|
|
151
151
|
@keyframes ui-checkbox-check {
|
|
152
152
|
from {
|
|
153
153
|
opacity: 0;
|
|
154
|
-
transform:
|
|
154
|
+
transform: translateY(-0.09rem) rotate(45deg) scale(0.55);
|
|
155
155
|
}
|
|
156
156
|
65% {
|
|
157
157
|
opacity: 1;
|
|
158
|
-
transform:
|
|
158
|
+
transform: translateY(-0.09rem) rotate(45deg) scale(1.12);
|
|
159
159
|
}
|
|
160
160
|
to {
|
|
161
161
|
opacity: 1;
|
|
162
|
-
transform:
|
|
162
|
+
transform: translateY(-0.09rem) rotate(45deg) scale(1);
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
|
|
@@ -244,6 +244,8 @@
|
|
|
244
244
|
class="option"
|
|
245
245
|
class:active="row.index = active"
|
|
246
246
|
role="option"
|
|
247
|
+
data-ui-choice-option
|
|
248
|
+
:data-multiple="multiple"
|
|
247
249
|
:id="format('%s-option-%s', uid, row.index)"
|
|
248
250
|
:aria-selected="row.selected"
|
|
249
251
|
:aria-disabled="row.disabled"
|
|
@@ -271,6 +273,8 @@
|
|
|
271
273
|
class="option"
|
|
272
274
|
class:active="createIndex = active"
|
|
273
275
|
role="option"
|
|
276
|
+
data-ui-choice-option
|
|
277
|
+
:data-multiple="multiple"
|
|
274
278
|
aria-selected="false"
|
|
275
279
|
:id="format('%s-option-%s', uid, createIndex)"
|
|
276
280
|
:data-index="createIndex"
|
|
@@ -543,21 +547,6 @@
|
|
|
543
547
|
[hidden] {
|
|
544
548
|
display: none;
|
|
545
549
|
}
|
|
546
|
-
.option {
|
|
547
|
-
padding: var(--ui-space-2) var(--ui-space-3);
|
|
548
|
-
border-radius: var(--ui-radius-sm);
|
|
549
|
-
cursor: pointer;
|
|
550
|
-
overflow-wrap: anywhere;
|
|
551
|
-
}
|
|
552
|
-
.option:hover, .option.active {
|
|
553
|
-
background: var(--ui-surface-muted);
|
|
554
|
-
outline: 1px solid var(--ui-focus-ring);
|
|
555
|
-
outline-offset: -1px;
|
|
556
|
-
}
|
|
557
|
-
.option[aria-disabled="true"] {
|
|
558
|
-
color: var(--ui-disabled-text);
|
|
559
|
-
cursor: not-allowed;
|
|
560
|
-
}
|
|
561
550
|
.option[aria-disabled="true"] .primary,
|
|
562
551
|
.option[aria-disabled="true"] .secondary {
|
|
563
552
|
color: var(--ui-disabled-text);
|
|
@@ -625,39 +614,6 @@
|
|
|
625
614
|
}
|
|
626
615
|
}
|
|
627
616
|
|
|
628
|
-
/* Multiple mode: every option carries a checkbox, so a list that takes several answers says so
|
|
629
|
-
before anything is picked. aria-selected is the state; this draws it.
|
|
630
|
-
Longhands, not shorthands: lowering drops a shorthand that omits a component. */
|
|
631
|
-
:host-state([multiple]) .option {
|
|
632
|
-
display: flex;
|
|
633
|
-
gap: var(--ui-space-2);
|
|
634
|
-
align-items: center;
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
:host-state([multiple]) .option::before {
|
|
638
|
-
content: "";
|
|
639
|
-
flex: none;
|
|
640
|
-
inline-size: 1.125rem;
|
|
641
|
-
block-size: 1.125rem;
|
|
642
|
-
border-style: solid;
|
|
643
|
-
border-width: 1px;
|
|
644
|
-
border-color: var(--ui-control-border-hover, var(--ui-border-strong));
|
|
645
|
-
border-radius: var(--ui-radius-sm);
|
|
646
|
-
background-color: var(--ui-control-surface, var(--ui-surface-default));
|
|
647
|
-
background-repeat: no-repeat;
|
|
648
|
-
background-position: center;
|
|
649
|
-
background-size: 0.7rem;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
:host-state([multiple]) .option[aria-selected="true"]::before {
|
|
653
|
-
border-color: var(--ui-accent-solid);
|
|
654
|
-
background-color: var(--ui-accent-solid);
|
|
655
|
-
background-image: var(
|
|
656
|
-
--_ui-combobox-option-check,
|
|
657
|
-
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m5 12 5 5 9-9'/%3E%3C/svg%3E")
|
|
658
|
-
);
|
|
659
|
-
}
|
|
660
|
-
|
|
661
617
|
/* Multiple mode keeps one control height. Chips and the input scroll within the entry,
|
|
662
618
|
while the clear and disclosure controls remain fixed at the end of the field. */
|
|
663
619
|
:host-state([multiple]) .field {
|
|
@@ -147,7 +147,7 @@
|
|
|
147
147
|
@media (hover: hover) and (pointer: fine) {
|
|
148
148
|
:host-state([anticipatory]):not(:host-state([variant="solid"]), :host-state([loading]), :host-state([pending]), :disabled, [data-ui-proximity="near"], :hover, :focus-visible) {
|
|
149
149
|
border-color: transparent;
|
|
150
|
-
background: transparent;
|
|
150
|
+
background-color: transparent;
|
|
151
151
|
box-shadow: none;
|
|
152
152
|
}
|
|
153
153
|
|
|
@@ -175,7 +175,7 @@
|
|
|
175
175
|
margin: 0;
|
|
176
176
|
border: 1px solid transparent;
|
|
177
177
|
border-radius: var(--ui-icon-button-radius, var(--ui-action-radius, var(--ui-radius-md)));
|
|
178
|
-
background: var(--ui-icon-button-surface, transparent);
|
|
178
|
+
background-color: var(--ui-icon-button-surface, transparent);
|
|
179
179
|
color: inherit;
|
|
180
180
|
display: inline-flex;
|
|
181
181
|
align-items: center;
|
|
@@ -247,14 +247,14 @@
|
|
|
247
247
|
|
|
248
248
|
:host-state([variant="outline"]) {
|
|
249
249
|
border-color: var(--ui-icon-button-border, var(--ui-control-border, var(--ui-border-strong)));
|
|
250
|
-
background: var(--ui-icon-button-outline-surface, var(--ui-surface-default));
|
|
250
|
+
background-color: var(--ui-icon-button-outline-surface, var(--ui-surface-default));
|
|
251
251
|
background-image: var(--ui-raised-highlight);
|
|
252
252
|
color: var(--ui-icon-button-outline-text, var(--ui-text-primary));
|
|
253
253
|
box-shadow: var(--ui-raised);
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
:host-state([variant="solid"]) {
|
|
257
|
-
background: var(--ui-icon-button-solid-surface, var(--ui-accent-solid));
|
|
257
|
+
background-color: var(--ui-icon-button-solid-surface, var(--ui-accent-solid));
|
|
258
258
|
background-image: var(--ui-raised-highlight);
|
|
259
259
|
color: var(--ui-icon-button-solid-text, var(--ui-text-on-accent));
|
|
260
260
|
box-shadow: var(
|
|
@@ -265,7 +265,7 @@
|
|
|
265
265
|
|
|
266
266
|
:host:hover:enabled:not([aria-disabled="true"]) {
|
|
267
267
|
/* Derived from the text colour so the ghost hover shows on any background. */
|
|
268
|
-
background: var(--ui-icon-button-hover-surface, color-mix(in srgb, currentColor 8%, transparent));
|
|
268
|
+
background-color: var(--ui-icon-button-hover-surface, color-mix(in srgb, currentColor 8%, transparent));
|
|
269
269
|
color: var(--ui-icon-button-hover-text, var(--ui-text-primary));
|
|
270
270
|
}
|
|
271
271
|
|
|
@@ -274,7 +274,7 @@
|
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
:host-state([variant="solid"]):hover:enabled:not([aria-disabled="true"]) {
|
|
277
|
-
background: var(--ui-icon-button-solid-hover-surface, var(--ui-accent-hover));
|
|
277
|
+
background-color: var(--ui-icon-button-solid-hover-surface, var(--ui-accent-hover));
|
|
278
278
|
}
|
|
279
279
|
|
|
280
280
|
:host:active:enabled:not([aria-disabled="true"]) {
|
|
@@ -309,7 +309,7 @@
|
|
|
309
309
|
/* Disabled is a flat neutral control, independent of the active variant's tone. */
|
|
310
310
|
:host:disabled {
|
|
311
311
|
border-color: var(--ui-disabled-border);
|
|
312
|
-
background: var(--ui-disabled-surface);
|
|
312
|
+
background-color: var(--ui-disabled-surface);
|
|
313
313
|
color: var(--ui-disabled-text);
|
|
314
314
|
filter: var(--ui-icon-button-disabled-filter, var(--ui-disabled-filter));
|
|
315
315
|
opacity: var(--ui-icon-button-disabled-opacity, 1);
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
opacity: 1;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
:host:hover:not(:focus):not(:disabled) {
|
|
130
|
+
:host:hover:not(:focus):not(:disabled):not(:read-only):not([aria-invalid="true"]):not(:user-invalid) {
|
|
131
131
|
border-color: var(--ui-input-border-hover, var(--_ui-default-input-border-hover, var(--ui-control-border-hover, var(--ui-text-secondary))));
|
|
132
132
|
}
|
|
133
133
|
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
:host:hover:not(:has(input:focus, input:disabled)) {
|
|
80
|
+
:host:hover:not(:has(input:focus, input:disabled, input:read-only, [aria-invalid="true"], :user-invalid)) {
|
|
81
81
|
border-color: var(--ui-control-border-hover, var(--ui-text-secondary));
|
|
82
82
|
}
|
|
83
83
|
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
flex: none;
|
|
129
129
|
align-items: center;
|
|
130
130
|
padding-inline: var(--ui-space-2);
|
|
131
|
-
color: var(--ui-text-
|
|
131
|
+
color: var(--ui-text-muted);
|
|
132
132
|
font-family: var(--ui-font-family-mono);
|
|
133
133
|
font-size: var(--ui-font-size-sm);
|
|
134
134
|
white-space: nowrap;
|
|
@@ -1,14 +1,65 @@
|
|
|
1
|
+
<link rel="component" href="../ui-scroll-area/ui-scroll-area.html">
|
|
1
2
|
<template component="ui-listbox" controller="./ui-listbox.js">
|
|
2
3
|
<defs>
|
|
3
|
-
<prop name="rows" type="number" default="5">The number of
|
|
4
|
-
<prop name="multiple" type="boolean" default="false">Allows several
|
|
4
|
+
<prop name="rows" type="number" default="5">The maximum number of choices visible before the list scrolls. Use at least two.</prop>
|
|
5
|
+
<prop name="multiple" type="boolean" default="false">Allows several choices, each shown with a checkmark when selected.</prop>
|
|
6
|
+
<prop name="name" type="string" default="">The form field name used for each selected value.</prop>
|
|
5
7
|
<prop name="value" type="string">The initial or externally set single selection. Authored selected options work when it is omitted.</prop>
|
|
6
8
|
<prop name="values" type="list(string)">The initial or externally set selections in multiple mode. Authored selected options work when it is omitted.</prop>
|
|
7
9
|
<prop name="disabled" type="boolean" default="false">Prevents selection and form submission.</prop>
|
|
8
|
-
<prop name="required" type="boolean" default="false">Requires at least one
|
|
10
|
+
<prop name="required" type="boolean" default="false">Requires at least one choice before form submission.</prop>
|
|
9
11
|
<prop name="invalid" type="boolean" default="false">Exposes invalid state to assistive technology and styling.</prop>
|
|
12
|
+
<state name="choices" type="list(object({ value: string, label: string, disabled: boolean, selected: boolean, index: integer }))" :value="[]"></state>
|
|
13
|
+
<state name="selection" type="list(string)" :value="[]"></state>
|
|
14
|
+
<state name="hasSelection" type="boolean" :value="false"></state>
|
|
15
|
+
<state name="active" type="integer" :value="-1"></state>
|
|
16
|
+
<state name="uid" type="string" :value="''"></state>
|
|
17
|
+
<state name="validationError" type="boolean" :value="false"></state>
|
|
18
|
+
<event name="change" type="object({ value: string | null, values: list(string), trigger: keyboard | pointer })"></event>
|
|
10
19
|
</defs>
|
|
11
|
-
<
|
|
20
|
+
<div
|
|
21
|
+
role="listbox"
|
|
22
|
+
:tabindex="{ true: '-1', false: '0' }[format('%s', disabled)]"
|
|
23
|
+
:aria-multiselectable="multiple"
|
|
24
|
+
:aria-disabled="disabled"
|
|
25
|
+
:aria-required="required"
|
|
26
|
+
:aria-invalid="invalid or validationError"
|
|
27
|
+
style:--_rows="rows"
|
|
28
|
+
>
|
|
29
|
+
<ui-scroll-area class="choices" $ref="choices">
|
|
30
|
+
<div
|
|
31
|
+
$each="choice of choices"
|
|
32
|
+
$key="choice.index"
|
|
33
|
+
class="option"
|
|
34
|
+
class:active="choice.index = active"
|
|
35
|
+
role="option"
|
|
36
|
+
data-ui-choice-option
|
|
37
|
+
:data-multiple="multiple"
|
|
38
|
+
:id="format('%s-option-%s', uid, choice.index)"
|
|
39
|
+
:data-index="choice.index"
|
|
40
|
+
:aria-selected="choice.selected"
|
|
41
|
+
:aria-disabled="choice.disabled"
|
|
42
|
+
>
|
|
43
|
+
<span $value="choice.label"></span>
|
|
44
|
+
</div>
|
|
45
|
+
</ui-scroll-area>
|
|
46
|
+
<select class="fallback" $ref="options" :size="rows" :multiple="multiple" :disabled="disabled" :required="required" :name="name" aria-label="Choices">
|
|
47
|
+
<slot></slot>
|
|
48
|
+
</select>
|
|
49
|
+
<template $if="name and not disabled">
|
|
50
|
+
<input $each="selected of selection" type="hidden" :name="name" :value="selected">
|
|
51
|
+
</template>
|
|
52
|
+
<input
|
|
53
|
+
class="validity"
|
|
54
|
+
$ref="validity"
|
|
55
|
+
type="checkbox"
|
|
56
|
+
:required="required and not hasSelection and not disabled"
|
|
57
|
+
:checked="hasSelection"
|
|
58
|
+
:disabled="disabled"
|
|
59
|
+
aria-hidden="true"
|
|
60
|
+
tabindex="-1"
|
|
61
|
+
>
|
|
62
|
+
</div>
|
|
12
63
|
<style>
|
|
13
64
|
@property --ui-listbox-radius {
|
|
14
65
|
syntax: "*";
|
|
@@ -32,29 +83,55 @@
|
|
|
32
83
|
}
|
|
33
84
|
|
|
34
85
|
:host {
|
|
86
|
+
position: relative;
|
|
35
87
|
box-sizing: border-box;
|
|
36
88
|
display: block;
|
|
37
89
|
inline-size: 100%;
|
|
38
90
|
min-inline-size: 0;
|
|
39
|
-
min-block-size: var(--ui-control-min-block-size);
|
|
40
91
|
margin: 0;
|
|
41
92
|
padding: var(--ui-space-1);
|
|
42
93
|
border: 1px solid var(--ui-listbox-border, var(--ui-control-border));
|
|
43
94
|
border-radius: var(--ui-listbox-radius, var(--ui-field-radius, var(--ui-radius-md)));
|
|
44
95
|
background: var(--ui-listbox-surface, var(--ui-control-surface));
|
|
45
96
|
color: var(--ui-text-primary);
|
|
46
|
-
color-scheme: light dark;
|
|
47
97
|
font: inherit;
|
|
48
98
|
line-height: var(--ui-line-height-md);
|
|
49
99
|
box-shadow: var(--ui-control-inset);
|
|
100
|
+
outline: none;
|
|
50
101
|
}
|
|
51
102
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
103
|
+
.choices {
|
|
104
|
+
max-block-size: calc(max(2, var(--_rows, 5)) * var(--ui-control-size-md));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
:host:not([data-enhanced]) {
|
|
108
|
+
padding: 0;
|
|
109
|
+
border: 0;
|
|
110
|
+
background: transparent;
|
|
111
|
+
box-shadow: none;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
:host:not([data-enhanced]) .choices,
|
|
115
|
+
:host[data-enhanced] .fallback {
|
|
116
|
+
display: none;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.fallback {
|
|
120
|
+
box-sizing: border-box;
|
|
121
|
+
inline-size: 100%;
|
|
55
122
|
font: inherit;
|
|
56
123
|
}
|
|
57
124
|
|
|
125
|
+
.validity {
|
|
126
|
+
position: absolute;
|
|
127
|
+
inline-size: 1px;
|
|
128
|
+
block-size: 1px;
|
|
129
|
+
overflow: hidden;
|
|
130
|
+
clip-path: inset(50%);
|
|
131
|
+
opacity: 0;
|
|
132
|
+
pointer-events: none;
|
|
133
|
+
}
|
|
134
|
+
|
|
58
135
|
:host:focus-visible {
|
|
59
136
|
border-color: var(--ui-control-focus);
|
|
60
137
|
outline: 2px solid var(--ui-focus-ring);
|
|
@@ -62,12 +139,11 @@
|
|
|
62
139
|
box-shadow: var(--ui-listbox-focus-shadow, var(--ui-focus-halo));
|
|
63
140
|
}
|
|
64
141
|
|
|
65
|
-
:host[aria-invalid="true"]
|
|
66
|
-
:host:user-invalid {
|
|
142
|
+
:host[aria-invalid="true"] {
|
|
67
143
|
border-color: var(--ui-listbox-invalid-border, var(--ui-field-danger, var(--ui-danger)));
|
|
68
144
|
}
|
|
69
145
|
|
|
70
|
-
:host
|
|
146
|
+
:host[aria-disabled="true"] {
|
|
71
147
|
background: var(--ui-control-surface-disabled);
|
|
72
148
|
color: var(--ui-disabled-text);
|
|
73
149
|
box-shadow: none;
|