@sveltia/ui 0.75.4 → 0.75.5
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/dist/components/button/button.svelte +0 -3
- package/dist/components/button/select-button-group.svelte +6 -3
- package/dist/components/calendar/calendar.svelte +8 -3
- package/dist/components/checkbox/checkbox.svelte +11 -0
- package/dist/components/listbox/listbox.svelte +3 -0
- package/dist/components/listbox/option.svelte +12 -1
- package/dist/components/menu/menu-item.svelte +0 -13
- package/dist/components/menu/menu.svelte +1 -1
- package/dist/components/radio/radio-group.svelte +3 -3
- package/dist/components/radio/radio.svelte +28 -6
- package/dist/components/select/combobox.svelte +6 -3
- package/dist/components/slider/slider.svelte +13 -0
- package/dist/components/switch/switch.svelte +13 -0
- package/dist/components/text-editor/shiki/version.d.ts +1 -1
- package/dist/components/text-editor/shiki/version.js +1 -1
- package/dist/components/text-field/number-input.svelte +6 -6
- package/dist/components/toast/toast.svelte +208 -33
- package/dist/components/toast/toast.svelte.d.ts +1 -0
- package/dist/components/tree/tree-item.svelte +14 -0
- package/dist/components/util/app-shell.svelte +13 -5
- package/dist/styles/variables.scss +10 -3
- package/package.json +1 -1
|
@@ -94,15 +94,18 @@
|
|
|
94
94
|
color: var(--sui-highlight-foreground-color);
|
|
95
95
|
background-color: var(--sui-selected-background-color);
|
|
96
96
|
}
|
|
97
|
-
.select-button-group
|
|
97
|
+
.select-button-group[aria-invalid=true] :global(button) {
|
|
98
98
|
border-color: var(--sui-error-border-color);
|
|
99
99
|
}
|
|
100
|
-
.select-button-group
|
|
100
|
+
.select-button-group[aria-disabled=false] :global(button[aria-disabled='true']) {
|
|
101
101
|
filter: grayscale(0) opacity(1);
|
|
102
102
|
}
|
|
103
|
-
.select-button-group
|
|
103
|
+
.select-button-group[aria-disabled=false] :global(button[aria-disabled='true']) :global(*) {
|
|
104
104
|
filter: grayscale(1) opacity(var(--sui-disabled-opacity, 0.35));
|
|
105
105
|
}
|
|
106
|
+
.select-button-group[aria-readonly=true] :global(button) {
|
|
107
|
+
pointer-events: none;
|
|
108
|
+
}
|
|
106
109
|
|
|
107
110
|
.inner {
|
|
108
111
|
display: contents;
|
|
@@ -259,9 +259,10 @@
|
|
|
259
259
|
.grid div.other-month {
|
|
260
260
|
color: var(--sui-tertiary-foreground-color);
|
|
261
261
|
}
|
|
262
|
-
.grid div
|
|
263
|
-
|
|
264
|
-
|
|
262
|
+
.grid div.today :global(button) {
|
|
263
|
+
border-width: 1px;
|
|
264
|
+
border-color: var(--sui-primary-accent-color);
|
|
265
|
+
color: var(--sui-primary-accent-color-text);
|
|
265
266
|
}
|
|
266
267
|
.grid div :global(button) {
|
|
267
268
|
justify-content: center;
|
|
@@ -277,6 +278,10 @@
|
|
|
277
278
|
border-width: 1px;
|
|
278
279
|
border-color: var(--sui-primary-accent-color-light);
|
|
279
280
|
}
|
|
281
|
+
.grid div :global(button[aria-selected=true]) {
|
|
282
|
+
color: var(--sui-highlight-foreground-color);
|
|
283
|
+
background-color: var(--sui-primary-accent-color);
|
|
284
|
+
}
|
|
280
285
|
|
|
281
286
|
.footer :global(button) {
|
|
282
287
|
font-size: var(--sui-font-size-small);
|
|
@@ -223,6 +223,17 @@
|
|
|
223
223
|
.checkbox label {
|
|
224
224
|
cursor: inherit;
|
|
225
225
|
}
|
|
226
|
+
.checkbox.readonly {
|
|
227
|
+
cursor: default;
|
|
228
|
+
}
|
|
229
|
+
.checkbox.readonly :global(button), .checkbox.readonly:is(:hover, :active) :global(button) {
|
|
230
|
+
background-color: var(--sui-checkbox-background-color);
|
|
231
|
+
}
|
|
232
|
+
.checkbox.readonly :global(button[aria-checked='true']:not([aria-invalid='true'])), .checkbox.readonly:is(:hover, :active) :global(button[aria-checked='true']:not([aria-invalid='true'])) {
|
|
233
|
+
border-color: var(--sui-readonly-accent-color);
|
|
234
|
+
color: var(--sui-readonly-accent-color-inverted);
|
|
235
|
+
background-color: var(--sui-readonly-accent-color);
|
|
236
|
+
}
|
|
226
237
|
|
|
227
238
|
.inner {
|
|
228
239
|
display: contents;
|
|
@@ -142,6 +142,9 @@
|
|
|
142
142
|
[role=listbox].tabs :global(.option) :global(button) :global(.icon) {
|
|
143
143
|
display: none;
|
|
144
144
|
}
|
|
145
|
+
[role=listbox].tabs[aria-readonly=true] :global(.option button[aria-selected='true']) {
|
|
146
|
+
border-color: var(--sui-readonly-accent-color);
|
|
147
|
+
}
|
|
145
148
|
[role=listbox].in-combobox:focus-visible {
|
|
146
149
|
outline-color: transparent;
|
|
147
150
|
}
|
|
@@ -173,7 +173,7 @@
|
|
|
173
173
|
.option:focus-visible {
|
|
174
174
|
outline-width: 0 !important;
|
|
175
175
|
}
|
|
176
|
-
.option
|
|
176
|
+
.option.wrap :global(button) {
|
|
177
177
|
white-space: normal;
|
|
178
178
|
}
|
|
179
179
|
.option :global(button) {
|
|
@@ -210,4 +210,15 @@
|
|
|
210
210
|
:global([role='listbox']:focus-visible) .option :global(.focused) {
|
|
211
211
|
outline-color: var(--sui-focus-ring-color);
|
|
212
212
|
outline-offset: calc(var(--sui-focus-ring-width) * -1);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
:global([role='listbox'][aria-readonly='true']) .option :global(button) {
|
|
216
|
+
cursor: default;
|
|
217
|
+
}
|
|
218
|
+
:global([role='listbox'][aria-readonly='true']) .option :global(button:not(.focused):is(:hover, :active)) {
|
|
219
|
+
color: var(--sui-control-foreground-color);
|
|
220
|
+
background-color: transparent;
|
|
221
|
+
}
|
|
222
|
+
:global([role='listbox'][aria-readonly='true']) .option :global(button[aria-selected=true]) :global(.icon.check) {
|
|
223
|
+
color: var(--sui-readonly-accent-color);
|
|
213
224
|
}</style>
|
|
@@ -184,19 +184,6 @@
|
|
|
184
184
|
.menuitem :global(button:active) {
|
|
185
185
|
background-color: var(--sui-active-background-color);
|
|
186
186
|
}
|
|
187
|
-
.menuitem :global(:hover) > :global([role=menu]) {
|
|
188
|
-
opacity: 1;
|
|
189
|
-
}
|
|
190
|
-
.menuitem > :global([role=menu]) {
|
|
191
|
-
position: absolute;
|
|
192
|
-
inset-block-start: 2px;
|
|
193
|
-
inset-block-end: auto;
|
|
194
|
-
inset-inline-start: calc(100% + 4px);
|
|
195
|
-
inset-inline-end: auto;
|
|
196
|
-
}
|
|
197
|
-
.menuitem > :global([role=menu]:hover) {
|
|
198
|
-
opacity: 1;
|
|
199
|
-
}
|
|
200
187
|
|
|
201
188
|
.content {
|
|
202
189
|
flex: auto;
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
margin: 0;
|
|
62
62
|
border-width: var(--sui-menu-border-width, 1px);
|
|
63
63
|
border-style: var(--sui-menu-border-style, solid);
|
|
64
|
-
border-color: var(--sui-menu-border-
|
|
64
|
+
border-color: var(--sui-menu-border-color, var(--sui-secondary-border-color));
|
|
65
65
|
border-radius: var(--sui-menu-border-radius, 4px);
|
|
66
66
|
padding: var(--sui-menu-padding, 4px);
|
|
67
67
|
}
|
|
@@ -93,13 +93,13 @@
|
|
|
93
93
|
gap: 8px;
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
|
-
.radio-group
|
|
96
|
+
.radio-group[aria-invalid=true] :global(button) {
|
|
97
97
|
border-color: var(--sui-error-border-color);
|
|
98
98
|
}
|
|
99
|
-
.radio-group
|
|
99
|
+
.radio-group[aria-invalid=true] :global(button[aria-checked=true]) {
|
|
100
100
|
border-color: var(--sui-error-border-color);
|
|
101
101
|
}
|
|
102
|
-
.radio-group
|
|
102
|
+
.radio-group[aria-invalid=true] :global(button[aria-checked=true]::before) {
|
|
103
103
|
background-color: var(--sui-error-border-color);
|
|
104
104
|
}
|
|
105
105
|
|
|
@@ -74,6 +74,13 @@
|
|
|
74
74
|
const accessibleLabel = $derived(ariaLabel ?? restProps['aria-label']);
|
|
75
75
|
const hasVisibleLabel = $derived(!!(children || label));
|
|
76
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Check if the radio button belongs to a read-only `<RadioGroup>`.
|
|
79
|
+
* @returns {boolean} Result.
|
|
80
|
+
*/
|
|
81
|
+
const isGroupReadOnly = () =>
|
|
82
|
+
!!buttonElement?.closest('[role="radiogroup"]')?.matches('[aria-readonly="true"]');
|
|
83
|
+
|
|
77
84
|
// Sync `checked` with `group` and `value`
|
|
78
85
|
$effect(() => {
|
|
79
86
|
if (typeof group === 'string') {
|
|
@@ -119,7 +126,9 @@
|
|
|
119
126
|
onclick={(event) => {
|
|
120
127
|
event.preventDefault();
|
|
121
128
|
|
|
122
|
-
|
|
129
|
+
// A read-only group turns the selection down too, but only once this handler has run, so it
|
|
130
|
+
// has to be checked here as well
|
|
131
|
+
if (disabled || checked || isGroupReadOnly()) {
|
|
123
132
|
return;
|
|
124
133
|
}
|
|
125
134
|
|
|
@@ -158,20 +167,20 @@
|
|
|
158
167
|
-webkit-user-select: none;
|
|
159
168
|
user-select: none;
|
|
160
169
|
}
|
|
161
|
-
.radio
|
|
170
|
+
.radio:hover :global(button) {
|
|
162
171
|
background-color: var(--sui-hover-background-color);
|
|
163
172
|
}
|
|
164
|
-
.radio
|
|
173
|
+
.radio:hover :global(button[aria-checked=true]) {
|
|
165
174
|
border-color: var(--sui-primary-accent-color-light);
|
|
166
175
|
color: var(--sui-primary-accent-color-text);
|
|
167
176
|
}
|
|
168
|
-
.radio
|
|
177
|
+
.radio:hover :global(button[aria-checked=true]::before) {
|
|
169
178
|
background-color: var(--sui-primary-accent-color-light);
|
|
170
179
|
}
|
|
171
|
-
.radio
|
|
180
|
+
.radio:active :global(button) {
|
|
172
181
|
background-color: var(--sui-active-background-color);
|
|
173
182
|
}
|
|
174
|
-
.radio
|
|
183
|
+
.radio:active :global(button[aria-checked=true]) {
|
|
175
184
|
border-color: var(--sui-primary-accent-color-dark);
|
|
176
185
|
color: var(--sui-primary-accent-color-dark);
|
|
177
186
|
}
|
|
@@ -207,4 +216,17 @@
|
|
|
207
216
|
}
|
|
208
217
|
.radio label {
|
|
209
218
|
cursor: inherit;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
:global([role='radiogroup'][aria-readonly='true']) .radio {
|
|
222
|
+
cursor: default;
|
|
223
|
+
}
|
|
224
|
+
:global([role='radiogroup'][aria-readonly='true']) .radio :global(button), :global([role='radiogroup'][aria-readonly='true']) .radio:is(:hover, :active) :global(button) {
|
|
225
|
+
background-color: var(--sui-checkbox-background-color);
|
|
226
|
+
}
|
|
227
|
+
:global([role='radiogroup'][aria-readonly='true']) .radio :global(button[aria-checked='true']), :global([role='radiogroup'][aria-readonly='true']) .radio:is(:hover, :active) :global(button[aria-checked='true']) {
|
|
228
|
+
border-color: var(--sui-readonly-accent-color);
|
|
229
|
+
}
|
|
230
|
+
:global([role='radiogroup'][aria-readonly='true']) .radio :global(button[aria-checked='true']::before), :global([role='radiogroup'][aria-readonly='true']) .radio:is(:hover, :active) :global(button[aria-checked='true']::before) {
|
|
231
|
+
background-color: var(--sui-readonly-accent-color);
|
|
210
232
|
}</style>
|
|
@@ -477,6 +477,12 @@
|
|
|
477
477
|
.combobox div[role=combobox]:is(:hover, :focus) {
|
|
478
478
|
background-color: var(--sui-hover-background-color);
|
|
479
479
|
}
|
|
480
|
+
.combobox div[role=combobox][aria-readonly=true] {
|
|
481
|
+
cursor: default;
|
|
482
|
+
}
|
|
483
|
+
.combobox div[role=combobox][aria-readonly=true]:is(:hover, :focus):not(:focus-visible) {
|
|
484
|
+
background-color: var(--sui-disabled-background-color);
|
|
485
|
+
}
|
|
480
486
|
.combobox div[role=combobox][aria-invalid=true] {
|
|
481
487
|
border-color: var(--sui-error-border-color);
|
|
482
488
|
}
|
|
@@ -506,9 +512,6 @@
|
|
|
506
512
|
.combobox > :global(button[tabindex="-1"]) {
|
|
507
513
|
pointer-events: none;
|
|
508
514
|
}
|
|
509
|
-
.combobox :global(:not(.editable)) > :global(button) {
|
|
510
|
-
background-color: transparent !important;
|
|
511
|
-
}
|
|
512
515
|
.combobox :global(.text-input) {
|
|
513
516
|
margin: 0 !important;
|
|
514
517
|
width: 100% !important;
|
|
@@ -407,6 +407,19 @@
|
|
|
407
407
|
.slider:active .base-bar {
|
|
408
408
|
background-color: var(--sui-active-background-color);
|
|
409
409
|
}
|
|
410
|
+
.slider.readonly .base-bar, .slider.readonly:is(:hover, :active) .base-bar {
|
|
411
|
+
background-color: var(--sui-slider-background-color, var(--sui-secondary-background-color));
|
|
412
|
+
}
|
|
413
|
+
.slider.readonly .base,
|
|
414
|
+
.slider.readonly [role=slider] {
|
|
415
|
+
cursor: default;
|
|
416
|
+
}
|
|
417
|
+
.slider.readonly:not(.invalid) .slider-bar {
|
|
418
|
+
background-color: var(--sui-readonly-accent-color);
|
|
419
|
+
}
|
|
420
|
+
.slider.readonly:not(.invalid) [role=slider] {
|
|
421
|
+
border-color: var(--sui-readonly-accent-color);
|
|
422
|
+
}
|
|
410
423
|
|
|
411
424
|
.base {
|
|
412
425
|
position: relative;
|
|
@@ -134,6 +134,19 @@ button[aria-checked=true] span:dir(ltr)::before {
|
|
|
134
134
|
button[aria-checked=true] span:dir(rtl)::before {
|
|
135
135
|
transform: translateX(calc((var(--translateX)) * -1));
|
|
136
136
|
}
|
|
137
|
+
button[aria-readonly=true] {
|
|
138
|
+
cursor: default;
|
|
139
|
+
}
|
|
140
|
+
button[aria-readonly=true][aria-checked=false] span, button[aria-readonly=true]:is(:hover, :active)[aria-checked=false] span {
|
|
141
|
+
background-color: var(--sui-control-background-color);
|
|
142
|
+
}
|
|
143
|
+
button[aria-readonly=true][aria-checked=true] span, button[aria-readonly=true]:is(:hover, :active)[aria-checked=true] span {
|
|
144
|
+
background-color: var(--sui-readonly-accent-color);
|
|
145
|
+
}
|
|
146
|
+
button[aria-readonly=true][aria-checked=true] span::before, button[aria-readonly=true]:is(:hover, :active)[aria-checked=true] span::before {
|
|
147
|
+
border-color: var(--sui-readonly-accent-color);
|
|
148
|
+
background-color: var(--sui-readonly-accent-color-inverted);
|
|
149
|
+
}
|
|
137
150
|
|
|
138
151
|
span {
|
|
139
152
|
position: relative;
|
|
@@ -216,6 +216,12 @@
|
|
|
216
216
|
width: stretch;
|
|
217
217
|
min-width: 0;
|
|
218
218
|
}
|
|
219
|
+
.number-input:not(.disabled) :global(button[aria-disabled='true']) {
|
|
220
|
+
filter: grayscale(0) opacity(1);
|
|
221
|
+
}
|
|
222
|
+
.number-input:not(.disabled) :global(button[aria-disabled='true']) :global(*) {
|
|
223
|
+
filter: grayscale(1) opacity(var(--sui-disabled-opacity, 0.35));
|
|
224
|
+
}
|
|
219
225
|
.number-input :global(:not(:first-child)) :global(input) {
|
|
220
226
|
border-start-start-radius: 0;
|
|
221
227
|
border-end-start-radius: 0;
|
|
@@ -224,12 +230,6 @@
|
|
|
224
230
|
border-start-end-radius: 0;
|
|
225
231
|
border-end-end-radius: 0;
|
|
226
232
|
}
|
|
227
|
-
.number-input :global(:not(.disabled)) :global(button[aria-disabled=true]) {
|
|
228
|
-
filter: grayscale(0) opacity(1);
|
|
229
|
-
}
|
|
230
|
-
.number-input :global(:not(.disabled)) :global(button[aria-disabled=true]) :global(*) {
|
|
231
|
-
filter: grayscale(1) opacity(var(--sui-disabled-opacity, 0.35));
|
|
232
|
-
}
|
|
233
233
|
.number-input :global(.text-input) {
|
|
234
234
|
flex: auto;
|
|
235
235
|
margin: 0 !important;
|
|
@@ -3,7 +3,181 @@
|
|
|
3
3
|
Toast/snackbar notification. Use the Popover API if possible to acquire a non-modal top layer.
|
|
4
4
|
@see https://w3c.github.io/aria/#alert
|
|
5
5
|
@see https://developer.chrome.com/blog/introducing-popover-api/
|
|
6
|
+
@see https://github.com/whatwg/html/issues/9936
|
|
6
7
|
-->
|
|
8
|
+
<script module>
|
|
9
|
+
/**
|
|
10
|
+
* Modal dialogs currently open, the topmost last. While a modal dialog is open, the rest of the
|
|
11
|
+
* document is inert, including a popover stacked on top of the dialog, so the toast base has to
|
|
12
|
+
* live inside the topmost dialog to stay interactive and be announced by screen readers.
|
|
13
|
+
* @type {HTMLDialogElement[]}
|
|
14
|
+
*/
|
|
15
|
+
const modals = [];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get the element the toast base should be placed in.
|
|
19
|
+
* @returns {HTMLElement} Topmost modal dialog, the app shell, or the `<body>`.
|
|
20
|
+
*/
|
|
21
|
+
const getHost = () => {
|
|
22
|
+
// Drop the dialogs that have been closed or removed without us noticing
|
|
23
|
+
modals.splice(
|
|
24
|
+
0,
|
|
25
|
+
modals.length,
|
|
26
|
+
...modals.filter((dialog) => dialog.isConnected && dialog.matches(':modal')),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
return modals.at(-1) ?? document.querySelector('.sui.app-shell') ?? document.body;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Move the toast base to the top of the top layer if it has any toast, or close it otherwise.
|
|
34
|
+
* It has to be reopened, because a modal dialog opened after the base is stacked on top of it.
|
|
35
|
+
* @param {HTMLElement} base Toast base.
|
|
36
|
+
*/
|
|
37
|
+
const raiseBase = (base) => {
|
|
38
|
+
// Skip if the browser doesn’t support the Popover API
|
|
39
|
+
/* v8 ignore next */
|
|
40
|
+
if (!base.showPopover) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (base.matches(':popover-open')) {
|
|
45
|
+
base.hidePopover();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (base.querySelector('.toast')) {
|
|
49
|
+
base.showPopover();
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Move the toast base to the host returned by {@link getHost}, if it’s not there yet. Use
|
|
55
|
+
* `moveBefore()` where supported, so the base stays open and the focus stays in a toast.
|
|
56
|
+
* @param {HTMLElement} base Toast base.
|
|
57
|
+
* @returns {boolean} Whether the base has been moved.
|
|
58
|
+
*/
|
|
59
|
+
const moveBase = (base) => {
|
|
60
|
+
const host = getHost();
|
|
61
|
+
|
|
62
|
+
if (base.parentElement === host) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const { moveBefore } = /** @type {any} */ (host);
|
|
67
|
+
|
|
68
|
+
if (typeof moveBefore === 'function' && base.isConnected) {
|
|
69
|
+
moveBefore.call(host, base, null);
|
|
70
|
+
} else {
|
|
71
|
+
host.appendChild(base);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return true;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Toast base shared by all the toasts. It’s created by the first toast mounted and removed with
|
|
79
|
+
* the last one unmounted, rather than rendered by a component, so it doesn’t go away with the
|
|
80
|
+
* toast that happened to create it.
|
|
81
|
+
* @type {HTMLElement | undefined}
|
|
82
|
+
*/
|
|
83
|
+
let base;
|
|
84
|
+
/**
|
|
85
|
+
* Number of the toasts currently mounted.
|
|
86
|
+
*/
|
|
87
|
+
let mountCount = 0;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Move the base into a modal dialog that has just been opened.
|
|
91
|
+
* @param {Event} event `toggle` event.
|
|
92
|
+
*/
|
|
93
|
+
const onToggle = (event) => {
|
|
94
|
+
const { target, newState } = /** @type {ToggleEvent} */ (event);
|
|
95
|
+
|
|
96
|
+
if (
|
|
97
|
+
base &&
|
|
98
|
+
target instanceof HTMLDialogElement &&
|
|
99
|
+
newState === 'open' &&
|
|
100
|
+
target.matches(':modal') &&
|
|
101
|
+
!modals.includes(target)
|
|
102
|
+
) {
|
|
103
|
+
modals.push(target);
|
|
104
|
+
moveBase(base);
|
|
105
|
+
raiseBase(base);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Move the base out of a modal dialog that is being closed, before the dialog is possibly
|
|
111
|
+
* removed from the DOM tree along with the base. `beforetoggle` is fired synchronously when the
|
|
112
|
+
* dialog is closed; `close` is a fallback for browsers that don’t fire it on a dialog.
|
|
113
|
+
* @param {Event} event `beforetoggle` or `close` event.
|
|
114
|
+
*/
|
|
115
|
+
const onClose = (event) => {
|
|
116
|
+
const { target } = event;
|
|
117
|
+
|
|
118
|
+
if (
|
|
119
|
+
!base ||
|
|
120
|
+
!(target instanceof HTMLDialogElement) ||
|
|
121
|
+
(event.type === 'beforetoggle' && /** @type {ToggleEvent} */ (event).newState !== 'closed')
|
|
122
|
+
) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const index = modals.indexOf(target);
|
|
127
|
+
|
|
128
|
+
if (index > -1) {
|
|
129
|
+
modals.splice(index, 1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (target.contains(base) && moveBase(base) && !base.matches(':popover-open')) {
|
|
133
|
+
raiseBase(base);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Get the toast base, creating it if needed, and start keeping it in the topmost modal dialog.
|
|
139
|
+
* Call {@link releaseBase} once done.
|
|
140
|
+
* @returns {HTMLElement} Toast base.
|
|
141
|
+
*/
|
|
142
|
+
const acquireBase = () => {
|
|
143
|
+
mountCount += 1;
|
|
144
|
+
|
|
145
|
+
if (!base) {
|
|
146
|
+
base = document.createElement('div');
|
|
147
|
+
base.setAttribute('role', 'none');
|
|
148
|
+
base.className = 'sui toast-base enabled';
|
|
149
|
+
base.popover = 'manual';
|
|
150
|
+
modals.push(...document.querySelectorAll(/** @type {'dialog'} */ ('dialog:modal')));
|
|
151
|
+
// These events don’t bubble, so listen in the capture phase
|
|
152
|
+
document.addEventListener('toggle', onToggle, true);
|
|
153
|
+
document.addEventListener('beforetoggle', onClose, true);
|
|
154
|
+
document.addEventListener('close', onClose, true);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
moveBase(base);
|
|
158
|
+
|
|
159
|
+
return base;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Release the toast base, removing it once no toast uses it.
|
|
164
|
+
*/
|
|
165
|
+
const releaseBase = () => {
|
|
166
|
+
mountCount -= 1;
|
|
167
|
+
|
|
168
|
+
if (mountCount > 0 || !base) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
document.removeEventListener('toggle', onToggle, true);
|
|
173
|
+
document.removeEventListener('beforetoggle', onClose, true);
|
|
174
|
+
document.removeEventListener('close', onClose, true);
|
|
175
|
+
base.remove();
|
|
176
|
+
base = undefined;
|
|
177
|
+
modals.length = 0;
|
|
178
|
+
};
|
|
179
|
+
</script>
|
|
180
|
+
|
|
7
181
|
<script>
|
|
8
182
|
import { onMount, untrack } from 'svelte';
|
|
9
183
|
|
|
@@ -38,10 +212,6 @@
|
|
|
38
212
|
/* eslint-enable prefer-const */
|
|
39
213
|
} = $props();
|
|
40
214
|
|
|
41
|
-
/**
|
|
42
|
-
* @type {HTMLElement | undefined}
|
|
43
|
-
*/
|
|
44
|
-
let popoverBase = $state();
|
|
45
215
|
/**
|
|
46
216
|
* @type {HTMLElement | undefined}
|
|
47
217
|
*/
|
|
@@ -70,33 +240,12 @@
|
|
|
70
240
|
let rendered = $state(false);
|
|
71
241
|
|
|
72
242
|
onMount(() => {
|
|
73
|
-
popover =
|
|
74
|
-
/** @type {HTMLElement} */ (document.querySelector('.sui.toast-base.enabled')) ?? undefined;
|
|
75
|
-
|
|
76
|
-
if (popover) {
|
|
77
|
-
// eslint-disable-next-line svelte/no-dom-manipulating
|
|
78
|
-
popoverBase?.remove();
|
|
79
|
-
} else {
|
|
80
|
-
popover = popoverBase;
|
|
81
|
-
|
|
82
|
-
// The base is bound by the time the component is mounted
|
|
83
|
-
/* v8 ignore else */
|
|
84
|
-
if (popover) {
|
|
85
|
-
popover.classList.add('enabled');
|
|
86
|
-
(document.querySelector('.sui.app-shell') ?? document.body).appendChild(popover);
|
|
87
|
-
|
|
88
|
-
// Move the element to top layer, unless the browser doesn’t support the Popover API
|
|
89
|
-
/* v8 ignore else */
|
|
90
|
-
if (popover.showPopover) {
|
|
91
|
-
popover.popover = 'manual';
|
|
92
|
-
popover.showPopover();
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
243
|
+
popover = acquireBase();
|
|
96
244
|
|
|
97
245
|
return () => {
|
|
98
246
|
// eslint-disable-next-line svelte/no-dom-manipulating
|
|
99
247
|
toast?.remove();
|
|
248
|
+
releaseBase();
|
|
100
249
|
};
|
|
101
250
|
});
|
|
102
251
|
|
|
@@ -124,10 +273,31 @@
|
|
|
124
273
|
|
|
125
274
|
$effect(() => {
|
|
126
275
|
// Both are in place by the time the effect first runs; see `onMount()` above
|
|
127
|
-
/* v8 ignore
|
|
128
|
-
if (popover
|
|
129
|
-
|
|
276
|
+
/* v8 ignore next */
|
|
277
|
+
if (!popover || !toast) {
|
|
278
|
+
return;
|
|
130
279
|
}
|
|
280
|
+
|
|
281
|
+
// Keep the toast out of the base while it’s hidden, so faded-out toasts don’t pile up there
|
|
282
|
+
if (!rendered) {
|
|
283
|
+
// eslint-disable-next-line svelte/no-dom-manipulating
|
|
284
|
+
toast.remove();
|
|
285
|
+
// A detached element gets no `pointerleave` or `focusout`, so release the hold here, or the
|
|
286
|
+
// countdown would never start the next time the toast is shown
|
|
287
|
+
held = false;
|
|
288
|
+
|
|
289
|
+
// Close the base once the last toast is gone
|
|
290
|
+
if (!popover.querySelector('.toast') && popover.matches(':popover-open')) {
|
|
291
|
+
popover.hidePopover();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Make sure the base is in the topmost modal dialog, in case a dialog event has been missed
|
|
298
|
+
moveBase(popover);
|
|
299
|
+
popover.appendChild(toast);
|
|
300
|
+
raiseBase(popover);
|
|
131
301
|
});
|
|
132
302
|
|
|
133
303
|
$effect(() => {
|
|
@@ -169,8 +339,6 @@
|
|
|
169
339
|
});
|
|
170
340
|
</script>
|
|
171
341
|
|
|
172
|
-
<div bind:this={popoverBase} role="none" class="sui toast-base"></div>
|
|
173
|
-
|
|
174
342
|
<div
|
|
175
343
|
{...restProps}
|
|
176
344
|
bind:this={toast}
|
|
@@ -194,7 +362,7 @@
|
|
|
194
362
|
{/if}
|
|
195
363
|
</div>
|
|
196
364
|
|
|
197
|
-
<style
|
|
365
|
+
<style>:global(.sui.toast-base) {
|
|
198
366
|
position: fixed;
|
|
199
367
|
inset: 16px;
|
|
200
368
|
z-index: 99999;
|
|
@@ -230,6 +398,13 @@
|
|
|
230
398
|
opacity: 1;
|
|
231
399
|
transition-duration: 250ms;
|
|
232
400
|
will-change: opacity;
|
|
401
|
+
}
|
|
402
|
+
@starting-style {
|
|
403
|
+
.toast {
|
|
404
|
+
opacity: 0;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
.toast {
|
|
233
408
|
pointer-events: auto;
|
|
234
409
|
}
|
|
235
410
|
.toast[aria-hidden=true] {
|
|
@@ -7,6 +7,7 @@ type Toast = {
|
|
|
7
7
|
* Toast/snackbar notification. Use the Popover API if possible to acquire a non-modal top layer.
|
|
8
8
|
* @see https://w3c.github.io/aria/#alert
|
|
9
9
|
* @see https://developer.chrome.com/blog/introducing-popover-api/
|
|
10
|
+
* @see https://github.com/whatwg/html/issues/9936
|
|
10
11
|
*/
|
|
11
12
|
declare const Toast: import("svelte").Component<{
|
|
12
13
|
/**
|
|
@@ -200,6 +200,20 @@
|
|
|
200
200
|
[role=treeitem] > .row:active {
|
|
201
201
|
background-color: var(--sui-active-background-color);
|
|
202
202
|
}
|
|
203
|
+
:global([role='tree'][aria-readonly='true']) [role=treeitem] > .row {
|
|
204
|
+
cursor: default;
|
|
205
|
+
}
|
|
206
|
+
:global([role='tree'][aria-readonly='true']) [role=treeitem] > .row:is(:hover, :active) {
|
|
207
|
+
color: var(--sui-tree-foreground-color);
|
|
208
|
+
background-color: transparent;
|
|
209
|
+
}
|
|
210
|
+
:global([role='tree'][aria-readonly='true']) [role=treeitem] > .row > .chevron:not(.placeholder) {
|
|
211
|
+
cursor: pointer;
|
|
212
|
+
}
|
|
213
|
+
:global([role='tree'][aria-readonly='true']) [role=treeitem][aria-selected=true] > .row:is(:hover, :active) {
|
|
214
|
+
color: var(--sui-highlight-foreground-color);
|
|
215
|
+
background-color: var(--sui-selected-background-color);
|
|
216
|
+
}
|
|
203
217
|
[role=treeitem][aria-expanded=true] > .row > .chevron:dir(ltr) {
|
|
204
218
|
transform: rotate(90deg);
|
|
205
219
|
}
|
|
@@ -161,7 +161,7 @@
|
|
|
161
161
|
--sui-secondary-border-color: hsl(var(--sui-border-color-3-hsl));
|
|
162
162
|
--sui-error-border-color: hsl(
|
|
163
163
|
var(--sui-error-color-hue) var(--sui-alert-border-color-saturation)
|
|
164
|
-
var(--sui-alert-border-color-lightness)
|
|
164
|
+
var(--sui-error-border-color-lightness, var(--sui-alert-border-color-lightness))
|
|
165
165
|
);
|
|
166
166
|
--sui-warning-border-color: hsl(
|
|
167
167
|
var(--sui-warning-color-hue) var(--sui-alert-border-color-saturation)
|
|
@@ -231,6 +231,8 @@
|
|
|
231
231
|
--sui-checkbox-height: 20px;
|
|
232
232
|
--sui-checkbox-border-color: hsl(var(--sui-border-color-1-hsl));
|
|
233
233
|
--sui-checkbox-background-color: var(--sui-control-background-color);
|
|
234
|
+
--sui-readonly-accent-color: var(--sui-tertiary-foreground-color);
|
|
235
|
+
--sui-readonly-accent-color-inverted: var(--sui-primary-background-color);
|
|
234
236
|
--sui-option-border-radius: var(--sui-control-medium-border-radius);
|
|
235
237
|
--sui-option-padding: calc((var(--sui-control-medium-height) / 6))
|
|
236
238
|
calc((var(--sui-control-medium-height) / 5));
|
|
@@ -302,8 +304,8 @@
|
|
|
302
304
|
--sui-background-color-4-hsl: var(--sui-base-hue) 5% 92%;
|
|
303
305
|
--sui-background-color-5-hsl: var(--sui-base-hue) 5% 84%;
|
|
304
306
|
--sui-border-color-1-hsl: var(--sui-base-hue) 5% 60%;
|
|
305
|
-
--sui-border-color-2-hsl: var(--sui-base-hue) 5%
|
|
306
|
-
--sui-border-color-3-hsl: var(--sui-base-hue) 5%
|
|
307
|
+
--sui-border-color-2-hsl: var(--sui-base-hue) 5% 72%;
|
|
308
|
+
--sui-border-color-3-hsl: var(--sui-base-hue) 5% 80%;
|
|
307
309
|
--sui-shadow-color: var(--sui-base-hue) 10% 0%;
|
|
308
310
|
--sui-primary-accent-color-text: hsl(var(--sui-base-hue) 80% 40%);
|
|
309
311
|
--sui-primary-accent-color-light: hsl(var(--sui-base-hue) 80% 45%);
|
|
@@ -322,6 +324,7 @@
|
|
|
322
324
|
--sui-alert-background-color-lightness: 90%;
|
|
323
325
|
--sui-alert-border-color-saturation: 48%;
|
|
324
326
|
--sui-alert-border-color-lightness: 68%;
|
|
327
|
+
--sui-error-border-color-lightness: 60%;
|
|
325
328
|
}
|
|
326
329
|
:global(:root[data-theme=dark]),
|
|
327
330
|
:global(:host[data-theme=dark]) {
|
|
@@ -373,8 +376,8 @@
|
|
|
373
376
|
--sui-background-color-4-hsl: var(--sui-base-hue) 5% 92%;
|
|
374
377
|
--sui-background-color-5-hsl: var(--sui-base-hue) 5% 84%;
|
|
375
378
|
--sui-border-color-1-hsl: var(--sui-base-hue) 5% 60%;
|
|
376
|
-
--sui-border-color-2-hsl: var(--sui-base-hue) 5%
|
|
377
|
-
--sui-border-color-3-hsl: var(--sui-base-hue) 5%
|
|
379
|
+
--sui-border-color-2-hsl: var(--sui-base-hue) 5% 72%;
|
|
380
|
+
--sui-border-color-3-hsl: var(--sui-base-hue) 5% 80%;
|
|
378
381
|
--sui-shadow-color: var(--sui-base-hue) 10% 0%;
|
|
379
382
|
--sui-primary-accent-color-text: hsl(var(--sui-base-hue) 80% 40%);
|
|
380
383
|
--sui-primary-accent-color-light: hsl(var(--sui-base-hue) 80% 45%);
|
|
@@ -393,6 +396,7 @@
|
|
|
393
396
|
--sui-alert-background-color-lightness: 90%;
|
|
394
397
|
--sui-alert-border-color-saturation: 48%;
|
|
395
398
|
--sui-alert-border-color-lightness: 68%;
|
|
399
|
+
--sui-error-border-color-lightness: 60%;
|
|
396
400
|
}
|
|
397
401
|
}
|
|
398
402
|
@media (prefers-color-scheme: dark) {
|
|
@@ -451,6 +455,7 @@
|
|
|
451
455
|
--sui-alert-foreground-color-lightness: 18%;
|
|
452
456
|
--sui-alert-border-color-saturation: 55%;
|
|
453
457
|
--sui-alert-border-color-lightness: 40%;
|
|
458
|
+
--sui-error-border-color-lightness: 40%;
|
|
454
459
|
--sui-hover-background-color: hsl(var(--sui-background-color-5-hsl) / 60%);
|
|
455
460
|
--sui-selected-background-color: hsl(var(--sui-background-color-5-hsl));
|
|
456
461
|
--sui-active-background-color: hsl(var(--sui-background-color-5-hsl));
|
|
@@ -518,6 +523,7 @@
|
|
|
518
523
|
--sui-alert-foreground-color-lightness: 18%;
|
|
519
524
|
--sui-alert-border-color-saturation: 55%;
|
|
520
525
|
--sui-alert-border-color-lightness: 40%;
|
|
526
|
+
--sui-error-border-color-lightness: 40%;
|
|
521
527
|
--sui-hover-background-color: hsl(var(--sui-background-color-5-hsl) / 60%);
|
|
522
528
|
--sui-selected-background-color: hsl(var(--sui-background-color-5-hsl));
|
|
523
529
|
--sui-active-background-color: hsl(var(--sui-background-color-5-hsl));
|
|
@@ -588,6 +594,7 @@
|
|
|
588
594
|
--sui-alert-foreground-color-lightness: 18%;
|
|
589
595
|
--sui-alert-border-color-saturation: 55%;
|
|
590
596
|
--sui-alert-border-color-lightness: 40%;
|
|
597
|
+
--sui-error-border-color-lightness: 40%;
|
|
591
598
|
--sui-hover-background-color: hsl(var(--sui-background-color-5-hsl) / 60%);
|
|
592
599
|
--sui-selected-background-color: hsl(var(--sui-background-color-5-hsl));
|
|
593
600
|
--sui-active-background-color: hsl(var(--sui-background-color-5-hsl));
|
|
@@ -656,6 +663,7 @@
|
|
|
656
663
|
--sui-alert-foreground-color-lightness: 18%;
|
|
657
664
|
--sui-alert-border-color-saturation: 55%;
|
|
658
665
|
--sui-alert-border-color-lightness: 40%;
|
|
666
|
+
--sui-error-border-color-lightness: 40%;
|
|
659
667
|
--sui-hover-background-color: hsl(var(--sui-background-color-5-hsl) / 60%);
|
|
660
668
|
--sui-selected-background-color: hsl(var(--sui-background-color-5-hsl));
|
|
661
669
|
--sui-active-background-color: hsl(var(--sui-background-color-5-hsl));
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
--sui-background-color-4-hsl: var(--sui-base-hue) 5% 92%; // tertiary/disabled
|
|
12
12
|
--sui-background-color-5-hsl: var(--sui-base-hue) 5% 84%; // highlight
|
|
13
13
|
--sui-border-color-1-hsl: var(--sui-base-hue) 5% 60%; // control
|
|
14
|
-
--sui-border-color-2-hsl: var(--sui-base-hue) 5%
|
|
15
|
-
--sui-border-color-3-hsl: var(--sui-base-hue) 5%
|
|
14
|
+
--sui-border-color-2-hsl: var(--sui-base-hue) 5% 72%; // primary
|
|
15
|
+
--sui-border-color-3-hsl: var(--sui-base-hue) 5% 80%; // secondary
|
|
16
16
|
--sui-shadow-color: var(--sui-base-hue) 10% 0%;
|
|
17
17
|
// Accents
|
|
18
18
|
--sui-primary-accent-color-text: hsl(var(--sui-base-hue) 80% 40%);
|
|
@@ -33,6 +33,8 @@
|
|
|
33
33
|
--sui-alert-background-color-lightness: 90%;
|
|
34
34
|
--sui-alert-border-color-saturation: 48%;
|
|
35
35
|
--sui-alert-border-color-lightness: 68%;
|
|
36
|
+
// Darker than the other alert borders, as it also marks invalid controls (3:1, WCAG 1.4.11)
|
|
37
|
+
--sui-error-border-color-lightness: 60%;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
@mixin dark-theme {
|
|
@@ -97,6 +99,7 @@
|
|
|
97
99
|
--sui-alert-foreground-color-lightness: 18%;
|
|
98
100
|
--sui-alert-border-color-saturation: 55%;
|
|
99
101
|
--sui-alert-border-color-lightness: 40%;
|
|
102
|
+
--sui-error-border-color-lightness: 40%;
|
|
100
103
|
@include high-contrast-common;
|
|
101
104
|
}
|
|
102
105
|
|
|
@@ -239,7 +242,7 @@
|
|
|
239
242
|
--sui-secondary-border-color: hsl(var(--sui-border-color-3-hsl));
|
|
240
243
|
--sui-error-border-color: hsl(
|
|
241
244
|
var(--sui-error-color-hue) var(--sui-alert-border-color-saturation)
|
|
242
|
-
var(--sui-alert-border-color-lightness)
|
|
245
|
+
var(--sui-error-border-color-lightness, var(--sui-alert-border-color-lightness))
|
|
243
246
|
);
|
|
244
247
|
--sui-warning-border-color: hsl(
|
|
245
248
|
var(--sui-warning-color-hue) var(--sui-alert-border-color-saturation)
|
|
@@ -314,6 +317,10 @@
|
|
|
314
317
|
--sui-checkbox-height: 20px;
|
|
315
318
|
--sui-checkbox-border-color: hsl(var(--sui-border-color-1-hsl));
|
|
316
319
|
--sui-checkbox-background-color: var(--sui-control-background-color);
|
|
320
|
+
// Read-only choice controls draw their checked or selected state in a neutral colour instead
|
|
321
|
+
// of the accent, so they don’t look editable while keeping full contrast
|
|
322
|
+
--sui-readonly-accent-color: var(--sui-tertiary-foreground-color);
|
|
323
|
+
--sui-readonly-accent-color-inverted: var(--sui-primary-background-color);
|
|
317
324
|
// Option & menu item
|
|
318
325
|
--sui-option-border-radius: var(--sui-control-medium-border-radius);
|
|
319
326
|
--sui-option-padding: calc((var(--sui-control-medium-height) / 6))
|