@threadlabs/looma 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/ui-avatar/ui-avatar.html +13 -0
- package/components/ui-avatar-group/ui-avatar-group.html +7 -2
- package/components/ui-avatar-group/ui-avatar-group.js +3 -3
- package/components/ui-badge/ui-badge.html +14 -0
- package/components/ui-button/ui-button.html +2 -2
- package/components/ui-cluster/ui-cluster.html +1 -1
- package/components/ui-combobox/ui-combobox.html +112 -46
- package/components/ui-combobox/ui-combobox.js +14 -2
- package/components/ui-editor-insert-table-grid/ui-editor-insert-table-grid.html +3 -3
- package/components/ui-editor-table-context-menu/ui-editor-table-context-menu.html +2 -2
- package/components/ui-editor-table-toolbar/ui-editor-table-toolbar.html +2 -2
- package/components/ui-editor-toolbar/ui-editor-toolbar.html +5 -3
- package/components/ui-menu-item/ui-menu-item.html +13 -0
- package/components/ui-stack/ui-stack.html +1 -1
- package/components/ui-tabs/ui-tabs.html +13 -0
- package/components/ui-toast-region/ui-toast-region.html +2 -2
- package/components/ui-tooltip/ui-tooltip.html +3 -0
- package/components/ui-tooltip/ui-tooltip.js +24 -3
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/styles/ui-avatar-group.css +7 -2
- package/styles/ui-avatar.css +19 -0
- package/styles/ui-badge.css +21 -1
- package/styles/ui-cluster.css +1 -1
- package/styles/ui-combobox.css +52 -1
- package/styles/ui-editor-insert-table-grid.css +2 -2
- package/styles/ui-editor-table-context-menu.css +1 -1
- package/styles/ui-editor-table-toolbar.css +1 -1
- package/styles/ui-editor-toolbar.css +5 -3
- package/styles/ui-menu-item.css +20 -1
- package/styles/ui-stack.css +1 -1
- package/styles/ui-tabs.css +19 -0
- package/styles/ui-toast-region.css +2 -2
- package/tokens.css +4 -0
- package/vanilla/UiCombobox.js +141 -119
- package/vanilla/UiEditorInsertTableGrid.js +1 -1
- package/vanilla/UiEditorTableContextMenu.js +1 -1
- package/vanilla/UiEditorTableToolbar.js +1 -1
- package/vanilla/UiTooltip.d.ts +1 -0
- package/vanilla/UiTooltip.js +1 -1
- package/vue/UiAvatar.vue +13 -0
- package/vue/UiAvatarGroup.vue +7 -2
- package/vue/UiBadge.vue +14 -0
- package/vue/UiCluster.vue +1 -1
- package/vue/UiCombobox.vue +135 -53
- package/vue/UiEditorInsertTableGrid.vue +3 -3
- package/vue/UiEditorTableContextMenu.vue +2 -2
- package/vue/UiEditorTableToolbar.vue +2 -2
- package/vue/UiEditorToolbar.vue +5 -3
- package/vue/UiMenuItem.vue +13 -0
- package/vue/UiStack.vue +1 -1
- package/vue/UiTabs.vue +13 -0
- package/vue/UiToastRegion.vue +2 -2
- package/vue/UiTooltip.d.ts +4 -0
- package/vue/UiTooltip.vue +6 -0
- package/vue/chunks/UiAvatar.js +1 -1
- package/vue/chunks/UiAvatarGroup.js +1 -1
- package/vue/chunks/UiBadge.js +1 -1
- package/vue/chunks/UiCluster.js +1 -1
- package/vue/chunks/UiCombobox.js +66 -41
- package/vue/chunks/UiEditorInsertTableGrid.js +3 -3
- package/vue/chunks/UiEditorTableContextMenu.js +2 -2
- package/vue/chunks/UiEditorTableToolbar.js +2 -2
- package/vue/chunks/UiEditorToolbar.js +1 -1
- package/vue/chunks/UiMenuItem.js +1 -1
- package/vue/chunks/UiStack.js +1 -1
- package/vue/chunks/UiTabs.js +1 -1
- package/vue/chunks/UiToastRegion.js +1 -1
- package/vue/chunks/UiTooltip.js +5 -2
- package/vue/components.css +322 -211
- package/vue/editor/index.js +2 -1
|
@@ -47,6 +47,19 @@
|
|
|
47
47
|
block-size: 100%;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/* Centre the glyphs, not the line box: a label without descenders otherwise reads high. */
|
|
51
|
+
/* Centre the glyphs, not the line box: a label without descenders otherwise reads high.
|
|
52
|
+
* Trimming applies to block containers, so the box is a block with centred content. */
|
|
53
|
+
@supports (text-box-trim: trim-both) {
|
|
54
|
+
.fallback {
|
|
55
|
+
display: block;
|
|
56
|
+
text-align: center;
|
|
57
|
+
align-content: center;
|
|
58
|
+
text-box-trim: trim-both;
|
|
59
|
+
text-box-edge: cap alphabetic;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
50
63
|
[hidden] {
|
|
51
64
|
display: none;
|
|
52
65
|
}
|
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
</div>
|
|
13
13
|
<style>
|
|
14
14
|
:host {
|
|
15
|
+
/* Avatars separate with a tight, crisp edge, matching Looma's 1px borders rather than the
|
|
16
|
+
* diffuse elevation overlays use. */
|
|
17
|
+
--_overlap-shadow: -2px 0 2px -1px rgba(0, 0, 0, 0.22);
|
|
18
|
+
--_edge-ring: 0 0 0 1px rgba(0, 0, 0, 0.08);
|
|
19
|
+
|
|
15
20
|
display: flex;
|
|
16
21
|
align-items: center;
|
|
17
22
|
flex-direction: row;
|
|
@@ -42,8 +47,8 @@
|
|
|
42
47
|
background: var(--ui-surface-elevated);
|
|
43
48
|
color: var(--ui-text-primary);
|
|
44
49
|
box-shadow:
|
|
45
|
-
var(--ui-avatar-group-overlap-shadow, -
|
|
46
|
-
|
|
50
|
+
var(--ui-avatar-group-overlap-shadow, var(--_overlap-shadow)),
|
|
51
|
+
var(--ui-avatar-group-edge-ring, var(--_edge-ring));
|
|
47
52
|
font: inherit;
|
|
48
53
|
font-size: 0.75rem;
|
|
49
54
|
font-weight: 600;
|
|
@@ -10,10 +10,10 @@ export default function controller(host) {
|
|
|
10
10
|
avatar.style.display = index >= visible ? "none" : "";
|
|
11
11
|
avatar.style.marginInlineStart = index === 0 ? "0px" : "-0.625rem";
|
|
12
12
|
avatar.style.borderRadius = "999px";
|
|
13
|
-
// Later avatars cast a
|
|
13
|
+
// Later avatars cast a crisp shadow back onto the one they overlap; the first overlaps nothing.
|
|
14
14
|
avatar.style.boxShadow = index === 0
|
|
15
|
-
? "
|
|
16
|
-
: "var(--ui-avatar-group-overlap-shadow, -
|
|
15
|
+
? "var(--ui-avatar-group-edge-ring, var(--_edge-ring))"
|
|
16
|
+
: "var(--ui-avatar-group-overlap-shadow, var(--_overlap-shadow)), var(--ui-avatar-group-edge-ring, var(--_edge-ring))";
|
|
17
17
|
});
|
|
18
18
|
host.state.overflowCount = Math.max(0, avatars.length - visible);
|
|
19
19
|
};
|
|
@@ -26,6 +26,20 @@
|
|
|
26
26
|
white-space: nowrap;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/* Centre the glyphs, not the line box: a label without descenders otherwise reads high. */
|
|
30
|
+
/* Centre the glyphs, not the line box: a label without descenders otherwise reads high.
|
|
31
|
+
* Trimming applies to block containers, so the box is a block with centred content. */
|
|
32
|
+
@supports (text-box-trim: trim-both) {
|
|
33
|
+
:host {
|
|
34
|
+
display: block;
|
|
35
|
+
min-block-size: var(--ui-badge-min-block-size, 1.5rem);
|
|
36
|
+
text-align: center;
|
|
37
|
+
align-content: center;
|
|
38
|
+
text-box-trim: trim-both;
|
|
39
|
+
text-box-edge: cap alphabetic;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
29
43
|
:host-state([tone="accent"]) {
|
|
30
44
|
--_badge-surface: var(--ui-accent-strong-surface, var(--ui-accent-solid));
|
|
31
45
|
--_badge-border: var(--ui-accent-strong-surface, var(--ui-accent-solid));
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<prop name="size" type="sm | md | lg" default="md">Control size.</prop>
|
|
6
6
|
<prop name="tone" type="neutral | accent" default="neutral"
|
|
7
7
|
>Intent of an outline, ghost, or link button. accent tints it with the accent colour, for a secondary action that still reads as the primary path. For a
|
|
8
|
-
product whose buttons are accent-tinted by default,
|
|
9
|
-
annotating every call site.</prop>
|
|
8
|
+
product whose buttons are accent-tinted by default, point --ui-button-surface, --ui-button-border, and --ui-button-text at the accent values in your theme
|
|
9
|
+
rather than annotating every call site, and keep them as var() references so they follow a later change of accent.</prop>
|
|
10
10
|
<prop name="disabled" type="boolean" default="false">disabled token.</prop>
|
|
11
11
|
<prop name="align" type="center | start" default="center"
|
|
12
12
|
>Content alignment. start lays the content out like a list option: from the start edge, with start-aligned text.</prop>
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
<prop name="disclosure" type="boolean" default="false">disclosure token.</prop>
|
|
22
22
|
<prop name="clearable" type="boolean" default="false">clearable token.</prop>
|
|
23
23
|
<prop name="help" type="string" default="">help token.</prop>
|
|
24
|
+
<state name="internalItems" type="list(object({ id: string, value: string, label: string, group?: string, disabled?: boolean }))" :value="[]"></state>
|
|
24
25
|
<state name="raw" :value="''"></state>
|
|
25
26
|
<state name="display" :value="''"></state>
|
|
26
27
|
<state name="selected" type="string | null" :value="null"></state>
|
|
@@ -79,68 +80,85 @@
|
|
|
79
80
|
<div>
|
|
80
81
|
<label :for="format('%s-input', uid)" class:sr-only="labelVisibility = 'sr-only'"><template $value="label"></template><template $if="required">
|
|
81
82
|
*</template></label>
|
|
82
|
-
<div class="
|
|
83
|
-
<
|
|
84
|
-
|
|
83
|
+
<div class="control">
|
|
84
|
+
<div class="field" $ref="field">
|
|
85
|
+
<slot name="start"></slot>
|
|
86
|
+
<template $if="multiple">
|
|
87
|
+
<button
|
|
88
|
+
$each="item, index of internalItems"
|
|
89
|
+
$key="item.id"
|
|
90
|
+
class="item"
|
|
91
|
+
type="button"
|
|
92
|
+
tabindex="-1"
|
|
93
|
+
:data-index="index"
|
|
94
|
+
:disabled="disabled or readonly or item.disabled"
|
|
95
|
+
:aria-label="format('%s, press Delete or Backspace to remove', item.label)"
|
|
96
|
+
>
|
|
97
|
+
<slot :name="format('item-%s', item.id)"><ui-badge $value="item.label"></ui-badge></slot>
|
|
98
|
+
</button>
|
|
99
|
+
</template>
|
|
100
|
+
<input
|
|
101
|
+
$ref="input"
|
|
102
|
+
:id="format('%s-input', uid)"
|
|
103
|
+
role="combobox"
|
|
104
|
+
aria-autocomplete="list"
|
|
105
|
+
autocomplete="off"
|
|
106
|
+
:aria-controls="format('%s-listbox', uid)"
|
|
107
|
+
:aria-expanded="expanded"
|
|
108
|
+
:aria-invalid="validationStatus = 'error'"
|
|
109
|
+
:aria-busy="validationStatus = 'pending'"
|
|
110
|
+
.value="display"
|
|
111
|
+
:placeholder="placeholder"
|
|
112
|
+
:name="name"
|
|
113
|
+
:disabled="disabled"
|
|
114
|
+
:readonly="readonly"
|
|
115
|
+
:required="required"
|
|
116
|
+
>
|
|
117
|
+
<button $if="clearable" type="button" data-combobox-action="clear" aria-label="Clear">
|
|
118
|
+
<svg class="affordance-icon" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
119
|
+
<path d="M7 7l10 10M17 7 7 17"></path>
|
|
120
|
+
</svg>
|
|
121
|
+
</button>
|
|
85
122
|
<button
|
|
86
|
-
$
|
|
87
|
-
$key="item.id"
|
|
88
|
-
class="item"
|
|
123
|
+
$if="disclosure"
|
|
89
124
|
type="button"
|
|
125
|
+
data-combobox-action="disclosure"
|
|
90
126
|
tabindex="-1"
|
|
91
|
-
|
|
92
|
-
:
|
|
93
|
-
:aria-label="format('%s, press Delete or Backspace to remove', item.label)"
|
|
127
|
+
aria-label="Show suggestions"
|
|
128
|
+
:aria-controls="format('%s-listbox', uid)"
|
|
94
129
|
>
|
|
95
|
-
<
|
|
130
|
+
<svg
|
|
131
|
+
class="affordance-icon"
|
|
132
|
+
aria-hidden="true"
|
|
133
|
+
viewBox="0 0 24 24"
|
|
134
|
+
fill="none"
|
|
135
|
+
stroke="currentColor"
|
|
136
|
+
stroke-width="2.25"
|
|
137
|
+
stroke-linecap="round"
|
|
138
|
+
stroke-linejoin="round"
|
|
139
|
+
>
|
|
140
|
+
<path d="m6 9 6 6 6-6"></path>
|
|
141
|
+
</svg>
|
|
96
142
|
</button>
|
|
97
|
-
</
|
|
98
|
-
<
|
|
99
|
-
$ref="input"
|
|
100
|
-
:id="format('%s-input', uid)"
|
|
101
|
-
role="combobox"
|
|
102
|
-
aria-autocomplete="list"
|
|
103
|
-
autocomplete="off"
|
|
104
|
-
:aria-controls="format('%s-listbox', uid)"
|
|
105
|
-
:aria-expanded="expanded"
|
|
106
|
-
:aria-invalid="validationStatus = 'error'"
|
|
107
|
-
:aria-busy="validationStatus = 'pending'"
|
|
108
|
-
.value="display"
|
|
109
|
-
:placeholder="placeholder"
|
|
110
|
-
:name="name"
|
|
111
|
-
:disabled="disabled"
|
|
112
|
-
:readonly="readonly"
|
|
113
|
-
:required="required"
|
|
114
|
-
>
|
|
115
|
-
<button $if="clearable" type="button" data-combobox-action="clear" aria-label="Clear">
|
|
116
|
-
<svg class="affordance-icon" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
117
|
-
<path d="M7 7l10 10M17 7 7 17"></path>
|
|
118
|
-
</svg>
|
|
119
|
-
</button>
|
|
120
|
-
<button $if="help" :id="format('%s-help', uid)" type="button" aria-label="Help" :disabled="disabled"><span aria-hidden="true">?</span></button>
|
|
121
|
-
<button
|
|
122
|
-
$if="disclosure"
|
|
123
|
-
type="button"
|
|
124
|
-
data-combobox-action="disclosure"
|
|
125
|
-
tabindex="-1"
|
|
126
|
-
aria-label="Show suggestions"
|
|
127
|
-
:aria-controls="format('%s-listbox', uid)"
|
|
128
|
-
>
|
|
143
|
+
</div>
|
|
144
|
+
<button $if="help" class="help" :id="format('%s-help', uid)" type="button" aria-label="Help" :disabled="disabled">
|
|
129
145
|
<svg
|
|
130
|
-
class="
|
|
146
|
+
class="help-icon"
|
|
131
147
|
aria-hidden="true"
|
|
132
148
|
viewBox="0 0 24 24"
|
|
133
149
|
fill="none"
|
|
134
150
|
stroke="currentColor"
|
|
135
|
-
stroke-width="2
|
|
151
|
+
stroke-width="2"
|
|
136
152
|
stroke-linecap="round"
|
|
137
153
|
stroke-linejoin="round"
|
|
138
154
|
>
|
|
139
|
-
<
|
|
155
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
156
|
+
<path d="M9.1 9a3 3 0 0 1 5.8 1c0 2-3 3-3 3"></path>
|
|
157
|
+
<path d="M12 17h.01"></path>
|
|
140
158
|
</svg>
|
|
141
159
|
</button>
|
|
142
160
|
</div>
|
|
143
|
-
<ui-tooltip $if="help" :id="format('%s-help-text', uid)" :for="format('%s-help', uid)"><template $value="help"></template></ui-tooltip>
|
|
161
|
+
<ui-tooltip $if="help" trigger="click" :id="format('%s-help-text', uid)" :for="format('%s-help', uid)"><template $value="help"></template></ui-tooltip>
|
|
144
162
|
<div class="popup" $ref="popup" :hidden="not expanded">
|
|
145
163
|
<div role="listbox" $ref="listbox" :id="format('%s-listbox', uid)" :aria-label="format('%s suggestions', label)" :aria-busy="loading">
|
|
146
164
|
<div $each="group of groups" $key="group.name" :role="group.role" :aria-label="group.label">
|
|
@@ -193,7 +211,17 @@
|
|
|
193
211
|
font-size: var(--ui-combobox-label-font-size, 0.875rem);
|
|
194
212
|
font-weight: 500;
|
|
195
213
|
}
|
|
214
|
+
/* The field and anything that annotates it sit on one row; the help button is beside the box,
|
|
215
|
+
not inside it, because a control's box holds its value. */
|
|
216
|
+
.control {
|
|
217
|
+
display: flex;
|
|
218
|
+
align-items: center;
|
|
219
|
+
gap: var(--ui-space-2);
|
|
220
|
+
min-inline-size: 0;
|
|
221
|
+
}
|
|
222
|
+
|
|
196
223
|
.field {
|
|
224
|
+
flex: 1 1 auto;
|
|
197
225
|
display: flex;
|
|
198
226
|
min-inline-size: 0;
|
|
199
227
|
border: 1px solid var(--ui-control-border, var(--ui-border-default));
|
|
@@ -208,6 +236,44 @@
|
|
|
208
236
|
var(--ui-control-inset),
|
|
209
237
|
var(--ui-control-focus-halo-shadow);
|
|
210
238
|
}
|
|
239
|
+
/* A ghost icon button: the affordance is the mark, not a box around it. */
|
|
240
|
+
.help {
|
|
241
|
+
flex: none;
|
|
242
|
+
display: inline-flex;
|
|
243
|
+
align-items: center;
|
|
244
|
+
justify-content: center;
|
|
245
|
+
inline-size: var(--ui-control-size-sm);
|
|
246
|
+
block-size: var(--ui-control-size-sm);
|
|
247
|
+
padding: 0;
|
|
248
|
+
border: 0;
|
|
249
|
+
border-radius: var(--ui-radius-round);
|
|
250
|
+
background: transparent;
|
|
251
|
+
color: var(--ui-text-secondary);
|
|
252
|
+
cursor: pointer;
|
|
253
|
+
transition: background-color var(--ui-motion-fast) var(--ui-motion-ease), color var(--ui-motion-fast) var(--ui-motion-ease);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.help:hover:enabled,
|
|
257
|
+
.help[aria-expanded="true"] {
|
|
258
|
+
background: color-mix(in srgb, currentColor 8%, transparent);
|
|
259
|
+
color: var(--ui-text-primary);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.help:focus-visible {
|
|
263
|
+
outline: none;
|
|
264
|
+
box-shadow: var(--ui-focus-halo);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.help:disabled {
|
|
268
|
+
color: var(--ui-disabled-text);
|
|
269
|
+
cursor: not-allowed;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.help-icon {
|
|
273
|
+
inline-size: var(--ui-icon-size-sm);
|
|
274
|
+
block-size: var(--ui-icon-size-sm);
|
|
275
|
+
}
|
|
276
|
+
|
|
211
277
|
input::selection {
|
|
212
278
|
background: color-mix(in srgb, var(--ui-accent-solid) 20%, transparent);
|
|
213
279
|
color: inherit;
|
|
@@ -48,7 +48,9 @@ export default function controller(host) {
|
|
|
48
48
|
allowFreeText: Boolean(host.state.allowFreeText),
|
|
49
49
|
allowCreate: Boolean(host.state.allowCreate),
|
|
50
50
|
});
|
|
51
|
-
|
|
51
|
+
// Selected items work uncontrolled: the component keeps them and reports every change. A consumer
|
|
52
|
+
// that owns `items` stays in charge, because the prop resyncs whatever it sets.
|
|
53
|
+
const items = () => host.state.multiple && Array.isArray(host.state.internalItems) ? host.state.internalItems : [];
|
|
52
54
|
const selectedValues = () => new Set(items().map((item) => item.value));
|
|
53
55
|
const setValidation = (result, touched = host.state.validation?.touched ?? false) => {
|
|
54
56
|
const issues = result.issues ?? [];
|
|
@@ -168,7 +170,10 @@ export default function controller(host) {
|
|
|
168
170
|
proposedChange = undefined;
|
|
169
171
|
});
|
|
170
172
|
};
|
|
171
|
-
const emitItems = (next) =>
|
|
173
|
+
const emitItems = (next) => {
|
|
174
|
+
host.state.internalItems = next;
|
|
175
|
+
host.dispatch("value-change", next);
|
|
176
|
+
};
|
|
172
177
|
const setMultiQuery = (query, trigger) => {
|
|
173
178
|
if (host.state.query === undefined) {
|
|
174
179
|
host.state.raw = query;
|
|
@@ -412,7 +417,14 @@ export default function controller(host) {
|
|
|
412
417
|
};
|
|
413
418
|
const observer = new MutationObserver(relabel);
|
|
414
419
|
observer.observe(authored, { childList: true, subtree: true, characterData: true, attributes: true });
|
|
420
|
+
let lastItems = host.state.items;
|
|
421
|
+
if (Array.isArray(lastItems)) host.state.internalItems = lastItems;
|
|
415
422
|
const stop = host.effect(() => {
|
|
423
|
+
// A consumer that sets `items` owns them; otherwise the component keeps its own.
|
|
424
|
+
if (host.state.items !== lastItems) {
|
|
425
|
+
lastItems = host.state.items;
|
|
426
|
+
if (Array.isArray(lastItems)) host.state.internalItems = lastItems;
|
|
427
|
+
}
|
|
416
428
|
if (host.state.value !== lastValue) { lastValue = host.state.value; syncValue(); }
|
|
417
429
|
if (host.state.query !== lastQuery) { lastQuery = host.state.query; syncQuery(); }
|
|
418
430
|
if (host.state.disabled || host.state.readonly) close();
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
</defs>
|
|
14
14
|
<div :hidden="not open">
|
|
15
15
|
<p class="hint" $value="hint"></p>
|
|
16
|
-
<div class="grid" role="group" aria-label="Table dimensions" style:--
|
|
16
|
+
<div class="grid" role="group" aria-label="Table dimensions" style:--_rows="rows" style:--_cols="cols">
|
|
17
17
|
<button
|
|
18
18
|
$each="cell of cells"
|
|
19
19
|
$key="cell.key"
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
|
|
48
48
|
.grid {
|
|
49
49
|
display: grid;
|
|
50
|
-
grid-template-columns: repeat(var(--
|
|
51
|
-
grid-template-rows: repeat(var(--
|
|
50
|
+
grid-template-columns: repeat(var(--_cols, 8), 1.25rem);
|
|
51
|
+
grid-template-rows: repeat(var(--_rows, 8), 1.25rem);
|
|
52
52
|
gap: 2px;
|
|
53
53
|
margin-bottom: var(--ui-space-2);
|
|
54
54
|
}
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
:aria-checked="swatch.selected"
|
|
33
33
|
:data-action="swatch.action"
|
|
34
34
|
>
|
|
35
|
-
<span class="swatch" class:default="not swatch.color" style:--
|
|
35
|
+
<span class="swatch" class:default="not swatch.color" style:--_swatch="swatch.color"></span>
|
|
36
36
|
<span class="swatch-label" $value="swatch.label"></span>
|
|
37
37
|
</button>
|
|
38
38
|
</div>
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
height: 1rem;
|
|
124
124
|
border-radius: 999px;
|
|
125
125
|
border: 1px solid var(--ui-border);
|
|
126
|
-
background: var(--
|
|
126
|
+
background: var(--_swatch, #fff);
|
|
127
127
|
box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.35);
|
|
128
128
|
flex-shrink: 0;
|
|
129
129
|
}
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
:aria-checked="swatch.selected"
|
|
68
68
|
:data-action="swatch.action"
|
|
69
69
|
>
|
|
70
|
-
<span class="swatch" class:default="not swatch.color" style:--
|
|
70
|
+
<span class="swatch" class:default="not swatch.color" style:--_swatch="swatch.color"></span>
|
|
71
71
|
<span $value="swatch.label"></span>
|
|
72
72
|
</button>
|
|
73
73
|
</div>
|
|
@@ -255,7 +255,7 @@
|
|
|
255
255
|
flex: 0 0 14px;
|
|
256
256
|
border: 1px solid color-mix(in srgb, var(--ui-border) 80%, #000);
|
|
257
257
|
border-radius: 3px;
|
|
258
|
-
background: var(--
|
|
258
|
+
background: var(--_swatch);
|
|
259
259
|
}
|
|
260
260
|
|
|
261
261
|
.swatch.default {
|
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
gap: var(--ui-editor-toolbar-gap, 2px);
|
|
15
15
|
padding: var(--ui-editor-toolbar-padding-block, 6px) var(--ui-editor-toolbar-padding-inline, 12px);
|
|
16
16
|
min-height: var(--ui-editor-toolbar-min-height, 48px);
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
/* More controls than the column is wide is normal at a readable measure, so the row wraps.
|
|
18
|
+
Scrolling hid the tail behind an invisible scrollbar. */
|
|
19
|
+
flex-wrap: wrap;
|
|
20
|
+
row-gap: var(--ui-editor-toolbar-row-gap, var(--ui-editor-toolbar-gap, 2px));
|
|
20
21
|
background: var(--ui-editor-toolbar-surface, var(--ui-surface));
|
|
21
22
|
border-bottom: 1px solid var(--ui-editor-toolbar-border, var(--ui-border));
|
|
22
23
|
}
|
|
@@ -38,6 +39,7 @@
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
:host-state([floating]) {
|
|
42
|
+
flex-wrap: nowrap;
|
|
41
43
|
position: static;
|
|
42
44
|
top: auto;
|
|
43
45
|
width: max-content;
|
|
@@ -20,6 +20,19 @@
|
|
|
20
20
|
transition: background-color var(--ui-motion-fast) var(--ui-motion-ease), color var(--ui-motion-fast) var(--ui-motion-ease);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/* Centre the glyphs, not the line box: a label without descenders otherwise reads high. */
|
|
24
|
+
/* Centre the glyphs, not the line box: a label without descenders otherwise reads high.
|
|
25
|
+
* Trimming applies to block containers, so the box is a block with centred content. */
|
|
26
|
+
@supports (text-box-trim: trim-both) {
|
|
27
|
+
:host {
|
|
28
|
+
display: block;
|
|
29
|
+
min-block-size: var(--ui-menu-item-min-block-size, var(--ui-control-size-md, 2.5rem));
|
|
30
|
+
align-content: center;
|
|
31
|
+
text-box-trim: trim-both;
|
|
32
|
+
text-box-edge: cap alphabetic;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
23
36
|
:host:is(:hover, :focus-visible):not([aria-disabled="true"]) {
|
|
24
37
|
background: var(--ui-menu-item-hover-surface, var(--ui-surface-subtle));
|
|
25
38
|
}
|
|
@@ -81,6 +81,19 @@
|
|
|
81
81
|
border-color var(--ui-motion-fast) var(--ui-motion-ease);
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
/* Centre the glyphs, not the line box: a label without descenders otherwise reads high. */
|
|
85
|
+
/* Centre the glyphs, not the line box: a label without descenders otherwise reads high.
|
|
86
|
+
* Trimming applies to block containers, so the box is a block with centred content. */
|
|
87
|
+
@supports (text-box-trim: trim-both) {
|
|
88
|
+
.list button {
|
|
89
|
+
display: block;
|
|
90
|
+
text-align: center;
|
|
91
|
+
align-content: center;
|
|
92
|
+
text-box-trim: trim-both;
|
|
93
|
+
text-box-edge: cap alphabetic;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
84
97
|
:host-state([stretch]) .list {
|
|
85
98
|
grid-auto-columns: minmax(max-content, 1fr);
|
|
86
99
|
}
|
|
@@ -72,12 +72,12 @@
|
|
|
72
72
|
background: var(--ui-surface-elevated);
|
|
73
73
|
color: var(--ui-text-primary);
|
|
74
74
|
box-shadow: var(--ui-elevation-overlay, var(--ui-shadow-lg));
|
|
75
|
-
animation: ui-toast-enter var(--ui-toast-enter-duration, 0.32s) var(--ui-motion-ease-out, cubic-bezier(0.16, 1, 0.3, 1)) both;
|
|
75
|
+
animation: ui-toast-enter var(--ui-toast-region-enter-duration, 0.32s) var(--ui-motion-ease-out, cubic-bezier(0.16, 1, 0.3, 1)) both;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
/* Leaving slides on down and fades; the controller removes the toast when this ends. */
|
|
79
79
|
.toast.closing {
|
|
80
|
-
animation: ui-toast-exit var(--ui-toast-exit-duration, 0.22s) var(--ui-motion-ease) both;
|
|
80
|
+
animation: ui-toast-exit var(--ui-toast-region-exit-duration, 0.22s) var(--ui-motion-ease) both;
|
|
81
81
|
pointer-events: none;
|
|
82
82
|
}
|
|
83
83
|
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
<prop name="for" type="string" default="">ID of the control described and visually anchored by the tooltip.</prop>
|
|
4
4
|
<prop name="open" type="boolean" default="false">Whether the tooltip is open initially or when set externally.</prop>
|
|
5
5
|
<prop name="placement" type="string" default="top-start">Positions the description relative to its trigger.</prop>
|
|
6
|
+
<prop name="trigger" type="hover | click | focus" default="hover"
|
|
7
|
+
>How the trigger opens it. hover also opens on keyboard focus; click toggles it and suits a help button, where hovering a question mark says nothing; focus
|
|
8
|
+
opens only while the trigger holds focus.</prop>
|
|
6
9
|
<prop name="showDelay" type="number" default="500">Waits this many milliseconds before showing after pointer entry.</prop>
|
|
7
10
|
<prop name="hideDelay" type="number" default="100">Waits this many milliseconds before hiding after pointer exit.</prop>
|
|
8
11
|
<prop name="inverse" type="boolean" default="false">Use the high-contrast inverse surface (dark on light themes, light on dark themes).</prop>
|
|
@@ -21,9 +21,15 @@ export default function controller(host) {
|
|
|
21
21
|
const setOpen = (open, input) => {
|
|
22
22
|
if (Boolean(host.state.internalOpen) === open) return;
|
|
23
23
|
host.state.internalOpen = open;
|
|
24
|
+
if (trigger && host.state.trigger === "click") trigger.setAttribute("aria-expanded", String(open));
|
|
24
25
|
host.dispatch(open ? "open" : "close", { open, reason: "action", trigger: input });
|
|
25
26
|
};
|
|
26
|
-
const onKeydown = (event) => {
|
|
27
|
+
const onKeydown = (event) => {
|
|
28
|
+
if (event.key !== "Escape") return;
|
|
29
|
+
clearTimers();
|
|
30
|
+
// A tooltip the reader opened is theirs to close.
|
|
31
|
+
if (host.state.trigger === "click" && host.state.internalOpen) setOpen(false, "keyboard");
|
|
32
|
+
};
|
|
27
33
|
const onEnter = (event) => {
|
|
28
34
|
if (event.pointerType === "touch") return;
|
|
29
35
|
clearHide();
|
|
@@ -58,19 +64,34 @@ export default function controller(host) {
|
|
|
58
64
|
if (ids.length) trigger.setAttribute("aria-describedby", ids.join(" "));
|
|
59
65
|
else trigger.removeAttribute("aria-describedby");
|
|
60
66
|
trigger.removeEventListener("keydown", onKeydown);
|
|
67
|
+
trigger.removeEventListener("click", onClick);
|
|
68
|
+
trigger.removeAttribute("aria-expanded");
|
|
61
69
|
trigger.removeEventListener("pointerenter", onEnter);
|
|
62
70
|
trigger.removeEventListener("pointerleave", onLeave);
|
|
63
71
|
trigger.removeEventListener("focusin", onFocusin);
|
|
64
72
|
trigger.removeEventListener("focusout", onFocusout);
|
|
65
73
|
};
|
|
74
|
+
const onClick = () => {
|
|
75
|
+
clearTimers();
|
|
76
|
+
setOpen(!host.state.internalOpen, "pointer");
|
|
77
|
+
};
|
|
66
78
|
const attach = () => {
|
|
67
79
|
if (!trigger) return;
|
|
68
80
|
const ids = new Set((trigger.getAttribute("aria-describedby") ?? "").split(/\s+/).filter(Boolean));
|
|
69
81
|
ids.add(element.id);
|
|
70
82
|
trigger.setAttribute("aria-describedby", [...ids].join(" "));
|
|
71
83
|
trigger.addEventListener("keydown", onKeydown);
|
|
72
|
-
trigger
|
|
73
|
-
|
|
84
|
+
const how = host.state.trigger ?? "hover";
|
|
85
|
+
if (how === "click") {
|
|
86
|
+
// A question-mark button says nothing on hover: it opens when pressed, and closes the same way.
|
|
87
|
+
trigger.addEventListener("click", onClick);
|
|
88
|
+
trigger.setAttribute("aria-expanded", String(Boolean(host.state.internalOpen)));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (how === "hover") {
|
|
92
|
+
trigger.addEventListener("pointerenter", onEnter);
|
|
93
|
+
trigger.addEventListener("pointerleave", onLeave);
|
|
94
|
+
}
|
|
74
95
|
trigger.addEventListener("focusin", onFocusin);
|
|
75
96
|
trigger.addEventListener("focusout", onFocusout);
|
|
76
97
|
};
|