@sveltia/ui 0.72.2 → 0.73.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/dist/components/alert/alert.svelte +29 -5
- package/dist/components/alert/alert.svelte.d.ts +8 -4
- package/dist/components/alert/infobar.svelte +21 -5
- package/dist/components/alert/infobar.svelte.d.ts +10 -4
- package/dist/components/button/button.svelte +5 -1
- package/dist/components/button/select-button-group.svelte +1 -1
- package/dist/components/calendar/calendar.d.ts +1 -4
- package/dist/components/calendar/calendar.js +17 -0
- package/dist/components/calendar/calendar.svelte +67 -11
- package/dist/components/checkbox/checkbox-group.svelte +3 -1
- package/dist/components/checkbox/checkbox.svelte +3 -1
- package/dist/components/dialog/dialog.svelte +14 -2
- package/dist/components/dialog/prompt-dialog.svelte +9 -2
- package/dist/components/dialog/prompt-dialog.svelte.d.ts +4 -2
- package/dist/components/disclosure/disclosure.svelte +2 -1
- package/dist/components/drawer/drawer.svelte +28 -3
- package/dist/components/drawer/drawer.svelte.d.ts +16 -2
- package/dist/components/grid/grid-body.svelte +3 -1
- package/dist/components/grid/grid-foot.svelte +3 -1
- package/dist/components/grid/grid-head.svelte +3 -1
- package/dist/components/listbox/option-group.svelte +2 -1
- package/dist/components/listbox/option.svelte +4 -0
- package/dist/components/menu/menu-item-group.svelte +3 -1
- package/dist/components/radio/radio.svelte +21 -3
- package/dist/components/radio/radio.svelte.d.ts +12 -2
- package/dist/components/resizable-pane/resizable-handle.svelte +12 -0
- package/dist/components/select/combobox.svelte +69 -1
- package/dist/components/select/select-tags.svelte +34 -23
- package/dist/components/slider/slider.d.ts +6 -0
- package/dist/components/slider/slider.js +40 -0
- package/dist/components/slider/slider.svelte +45 -11
- package/dist/components/slider/slider.svelte.d.ts +14 -2
- package/dist/components/switch/switch.svelte +1 -1
- package/dist/components/table/table-body.svelte +3 -1
- package/dist/components/table/table-foot.svelte +3 -1
- package/dist/components/table/table-head.svelte +3 -1
- package/dist/components/text-editor/code-editor.svelte +22 -1
- package/dist/components/text-editor/code-editor.svelte.d.ts +28 -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-editor/text-editor.svelte +23 -0
- package/dist/components/text-editor/text-editor.svelte.d.ts +28 -0
- package/dist/components/text-field/number-input.svelte +7 -17
- package/dist/components/text-field/search-bar.svelte +1 -0
- package/dist/components/text-field/text-input.svelte +1 -1
- package/dist/components/toast/toast.svelte +58 -4
- package/dist/components/toast/toast.svelte.d.ts +4 -2
- package/dist/components/util/app-shell.svelte +289 -5
- package/dist/components/util/popup.svelte +1 -1
- package/dist/locales/ar.yaml +20 -0
- package/dist/locales/bg.yaml +20 -0
- package/dist/locales/ca.yaml +20 -0
- package/dist/locales/cs.yaml +20 -0
- package/dist/locales/da.yaml +20 -0
- package/dist/locales/de.yaml +20 -0
- package/dist/locales/el.yaml +20 -0
- package/dist/locales/en-CA.yaml +20 -0
- package/dist/locales/en-GB.yaml +20 -0
- package/dist/locales/en-US.yaml +20 -0
- package/dist/locales/es-CO.yaml +20 -0
- package/dist/locales/fa.yaml +20 -0
- package/dist/locales/fi.yaml +20 -0
- package/dist/locales/fr.yaml +20 -0
- package/dist/locales/hr.yaml +20 -0
- package/dist/locales/it.yaml +20 -0
- package/dist/locales/ja.yaml +20 -0
- package/dist/locales/ko.yaml +20 -0
- package/dist/locales/nl.yaml +20 -0
- package/dist/locales/pl.yaml +20 -0
- package/dist/locales/pt-BR.yaml +20 -0
- package/dist/locales/pt-PT.yaml +20 -0
- package/dist/locales/ru.yaml +20 -0
- package/dist/locales/sv.yaml +20 -0
- package/dist/locales/tr.yaml +20 -0
- package/dist/locales/uk.yaml +20 -0
- package/dist/locales/vi.yaml +20 -0
- package/dist/locales/zh-CN.yaml +20 -0
- package/dist/locales/zh-TW.yaml +20 -0
- package/dist/services/group.svelte.js +60 -0
- package/dist/services/popup.svelte.js +24 -1
- package/dist/services/select.svelte.d.ts +5 -0
- package/dist/services/select.svelte.js +8 -0
- package/dist/services/type-ahead.d.ts +26 -0
- package/dist/services/type-ahead.js +81 -0
- package/dist/styles/core.scss +14 -3
- package/dist/styles/variables.scss +118 -1
- package/dist/typedefs.d.ts +7 -1
- package/dist/typedefs.js +4 -1
- package/package.json +3 -3
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
@see https://www.w3.org/WAI/ARIA/apg/patterns/alert/
|
|
6
6
|
-->
|
|
7
7
|
<script>
|
|
8
|
+
import { _ } from '@sveltia/i18n';
|
|
8
9
|
import Icon from '../icon/icon.svelte';
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -13,8 +14,10 @@
|
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* @typedef {object} Props
|
|
16
|
-
* @property {'error' | 'warning' | 'info' | 'success'} status Alert status.
|
|
17
|
-
*
|
|
17
|
+
* @property {'error' | 'warning' | 'info' | 'success'} status Alert status. Errors and warnings
|
|
18
|
+
* are `role="alert"` and interrupt; information and success are `role="status"` and wait.
|
|
19
|
+
* @property {'off' | 'polite' | 'assertive'} [ariaLive] ARIA live region politeness. Defaults to
|
|
20
|
+
* `assertive` for an alert and `polite` for a status.
|
|
18
21
|
* @property {Snippet} [children] Primary slot content.
|
|
19
22
|
* @property {Snippet} [icon] Icon slot content.
|
|
20
23
|
*/
|
|
@@ -25,24 +28,45 @@
|
|
|
25
28
|
let {
|
|
26
29
|
/* eslint-disable prefer-const */
|
|
27
30
|
status,
|
|
28
|
-
ariaLive =
|
|
31
|
+
ariaLive = undefined,
|
|
29
32
|
children,
|
|
30
33
|
icon,
|
|
31
34
|
...restProps
|
|
32
35
|
/* eslint-enable prefer-const */
|
|
33
36
|
} = $props();
|
|
37
|
+
|
|
38
|
+
const role = $derived(status === 'error' || status === 'warning' ? 'alert' : 'status');
|
|
34
39
|
</script>
|
|
35
40
|
|
|
36
|
-
|
|
41
|
+
<!--
|
|
42
|
+
The colour and icon tell sighted users how serious the message is; the icon is hidden from
|
|
43
|
+
assistive technology, so the status is also spelled out for screen readers, off screen.
|
|
44
|
+
-->
|
|
45
|
+
<div
|
|
46
|
+
{...restProps}
|
|
47
|
+
{role}
|
|
48
|
+
class="sui alert {status}"
|
|
49
|
+
aria-live={ariaLive ?? (role === 'alert' ? 'assertive' : 'polite')}
|
|
50
|
+
>
|
|
37
51
|
{#if icon}
|
|
38
52
|
{@render icon()}
|
|
39
53
|
{:else}
|
|
40
54
|
<Icon name={status === 'success' ? 'check_circle' : status} />
|
|
41
55
|
{/if}
|
|
56
|
+
<span class="status-label">{_(`_sui.alert.${status}`)}</span>
|
|
42
57
|
{@render children?.()}
|
|
43
58
|
</div>
|
|
44
59
|
|
|
45
|
-
<style>.
|
|
60
|
+
<style>.status-label {
|
|
61
|
+
position: absolute;
|
|
62
|
+
overflow: hidden;
|
|
63
|
+
clip-path: inset(50%);
|
|
64
|
+
width: 1px;
|
|
65
|
+
height: 1px;
|
|
66
|
+
white-space: nowrap;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.alert {
|
|
46
70
|
display: flex;
|
|
47
71
|
align-items: center;
|
|
48
72
|
gap: var(--gap, 8px);
|
|
@@ -10,11 +10,13 @@ type Alert = {
|
|
|
10
10
|
*/
|
|
11
11
|
declare const Alert: import("svelte").Component<{
|
|
12
12
|
/**
|
|
13
|
-
* Alert status.
|
|
13
|
+
* Alert status. Errors and warnings
|
|
14
|
+
* are `role="alert"` and interrupt; information and success are `role="status"` and wait.
|
|
14
15
|
*/
|
|
15
16
|
status: "error" | "warning" | "info" | "success";
|
|
16
17
|
/**
|
|
17
|
-
* ARIA live region politeness.
|
|
18
|
+
* ARIA live region politeness. Defaults to
|
|
19
|
+
* `assertive` for an alert and `polite` for a status.
|
|
18
20
|
*/
|
|
19
21
|
ariaLive?: "off" | "polite" | "assertive" | undefined;
|
|
20
22
|
/**
|
|
@@ -28,11 +30,13 @@ declare const Alert: import("svelte").Component<{
|
|
|
28
30
|
} & Record<string, any>, {}, "">;
|
|
29
31
|
type Props = {
|
|
30
32
|
/**
|
|
31
|
-
* Alert status.
|
|
33
|
+
* Alert status. Errors and warnings
|
|
34
|
+
* are `role="alert"` and interrupt; information and success are `role="status"` and wait.
|
|
32
35
|
*/
|
|
33
36
|
status: "error" | "warning" | "info" | "success";
|
|
34
37
|
/**
|
|
35
|
-
* ARIA live region politeness.
|
|
38
|
+
* ARIA live region politeness. Defaults to
|
|
39
|
+
* `assertive` for an alert and `polite` for a status.
|
|
36
40
|
*/
|
|
37
41
|
ariaLive?: "off" | "polite" | "assertive" | undefined;
|
|
38
42
|
/**
|
|
@@ -11,8 +11,11 @@
|
|
|
11
11
|
* @typedef {object} Props
|
|
12
12
|
* @property {boolean} [show] Whether to show the toast.
|
|
13
13
|
* @property {boolean} [dismissible] Whether to show the close button.
|
|
14
|
-
* @property {'error' | 'warning' | 'info' | 'success'} [status] Information status.
|
|
15
|
-
*
|
|
14
|
+
* @property {'error' | 'warning' | 'info' | 'success'} [status] Information status. Errors and
|
|
15
|
+
* warnings are `role="alert"` and interrupt; information and success are `role="status"` and
|
|
16
|
+
* wait.
|
|
17
|
+
* @property {'off' | 'polite' | 'assertive'} [ariaLive] ARIA live region politeness. Defaults to
|
|
18
|
+
* `assertive` for an alert and `polite` for a status.
|
|
16
19
|
* @property {Snippet} [children] Primary slot content.
|
|
17
20
|
* @property {Snippet} [icon] Icon slot content.
|
|
18
21
|
* @property {() => void} [onDismiss] Callback invoked when the close button is clicked.
|
|
@@ -24,22 +27,26 @@
|
|
|
24
27
|
show = $bindable(true),
|
|
25
28
|
dismissible = true,
|
|
26
29
|
status = 'info',
|
|
27
|
-
ariaLive =
|
|
30
|
+
ariaLive = undefined,
|
|
28
31
|
children = undefined,
|
|
29
32
|
icon = undefined,
|
|
30
33
|
onDismiss,
|
|
31
34
|
/* eslint-enable prefer-const */
|
|
32
35
|
} = $props();
|
|
36
|
+
|
|
37
|
+
const role = $derived(status === 'error' || status === 'warning' ? 'alert' : 'status');
|
|
33
38
|
</script>
|
|
34
39
|
|
|
35
40
|
{#if show}
|
|
36
41
|
<div role="none" class={['infobar', status]}>
|
|
37
|
-
|
|
42
|
+
<!-- The status is spelled out off screen for screen readers; see `<Alert>` -->
|
|
43
|
+
<div {role} class="message" aria-live={ariaLive ?? (role === 'alert' ? 'assertive' : 'polite')}>
|
|
38
44
|
{#if icon}
|
|
39
45
|
{@render icon()}
|
|
40
46
|
{:else}
|
|
41
47
|
<Icon name={status === 'success' ? 'check_circle' : status} />
|
|
42
48
|
{/if}
|
|
49
|
+
<span class="status-label">{_(`_sui.alert.${status}`)}</span>
|
|
43
50
|
{@render children?.()}
|
|
44
51
|
</div>
|
|
45
52
|
{#if dismissible}
|
|
@@ -61,7 +68,16 @@
|
|
|
61
68
|
</div>
|
|
62
69
|
{/if}
|
|
63
70
|
|
|
64
|
-
<style>.
|
|
71
|
+
<style>.status-label {
|
|
72
|
+
position: absolute;
|
|
73
|
+
overflow: hidden;
|
|
74
|
+
clip-path: inset(50%);
|
|
75
|
+
width: 1px;
|
|
76
|
+
height: 1px;
|
|
77
|
+
white-space: nowrap;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.infobar {
|
|
65
81
|
flex: none;
|
|
66
82
|
display: flex;
|
|
67
83
|
align-items: center;
|
|
@@ -13,11 +13,14 @@ declare const Infobar: import("svelte").Component<{
|
|
|
13
13
|
*/
|
|
14
14
|
dismissible?: boolean | undefined;
|
|
15
15
|
/**
|
|
16
|
-
* Information status.
|
|
16
|
+
* Information status. Errors and
|
|
17
|
+
* warnings are `role="alert"` and interrupt; information and success are `role="status"` and
|
|
18
|
+
* wait.
|
|
17
19
|
*/
|
|
18
20
|
status?: "error" | "warning" | "info" | "success" | undefined;
|
|
19
21
|
/**
|
|
20
|
-
* ARIA live region politeness.
|
|
22
|
+
* ARIA live region politeness. Defaults to
|
|
23
|
+
* `assertive` for an alert and `polite` for a status.
|
|
21
24
|
*/
|
|
22
25
|
ariaLive?: "off" | "polite" | "assertive" | undefined;
|
|
23
26
|
/**
|
|
@@ -43,11 +46,14 @@ type Props = {
|
|
|
43
46
|
*/
|
|
44
47
|
dismissible?: boolean | undefined;
|
|
45
48
|
/**
|
|
46
|
-
* Information status.
|
|
49
|
+
* Information status. Errors and
|
|
50
|
+
* warnings are `role="alert"` and interrupt; information and success are `role="status"` and
|
|
51
|
+
* wait.
|
|
47
52
|
*/
|
|
48
53
|
status?: "error" | "warning" | "info" | "success" | undefined;
|
|
49
54
|
/**
|
|
50
|
-
* ARIA live region politeness.
|
|
55
|
+
* ARIA live region politeness. Defaults to
|
|
56
|
+
* `assertive` for an alert and `polite` for a status.
|
|
51
57
|
*/
|
|
52
58
|
ariaLive?: "off" | "polite" | "assertive" | undefined;
|
|
53
59
|
/**
|
|
@@ -51,6 +51,10 @@
|
|
|
51
51
|
</script>
|
|
52
52
|
|
|
53
53
|
<!--
|
|
54
|
+
`aria-readonly` is only rendered when it applies: the attribute isn’t supported on the `button`
|
|
55
|
+
role (or `option`, `tab` and `menuitem`, which also wrap this component), and Svelte would
|
|
56
|
+
otherwise serialize `false` as the literal string "false".
|
|
57
|
+
|
|
54
58
|
The key shortcut handler is attached only when there are shortcuts to bind. A falsy value counts
|
|
55
59
|
as no attachment at all, which saves an effect per button — and both `<Option>` and `<MenuItem>`
|
|
56
60
|
wrap one, so a long list would otherwise pay for an effect per item that has nothing to do.
|
|
@@ -70,7 +74,7 @@
|
|
|
70
74
|
{role}
|
|
71
75
|
aria-hidden={hidden}
|
|
72
76
|
aria-disabled={disabled}
|
|
73
|
-
aria-readonly={readonly}
|
|
77
|
+
aria-readonly={readonly || undefined}
|
|
74
78
|
aria-pressed={pressed}
|
|
75
79
|
data-type={valueType}
|
|
76
80
|
data-name={name}
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
filter: grayscale(0) opacity(1);
|
|
102
102
|
}
|
|
103
103
|
.select-button-group :global([aria-disabled=false]) :global(button[aria-disabled=true]) :global(*) {
|
|
104
|
-
filter: grayscale(1) opacity(0.35);
|
|
104
|
+
filter: grayscale(1) opacity(var(--sui-disabled-opacity, 0.35));
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
.inner {
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Date helpers behind `<Calendar>`, kept apart from the component so the grid can be tested without
|
|
3
|
-
* rendering anything.
|
|
4
|
-
*/
|
|
1
|
+
export function formatDate(value: Date, options: Intl.DateTimeFormatOptions): string;
|
|
5
2
|
/**
|
|
6
3
|
* Number of cells in the day grid: six weeks, enough to show any month in full from the Sunday on
|
|
7
4
|
* or before its first day.
|
|
@@ -1,8 +1,25 @@
|
|
|
1
|
+
import { date as formatLocaleDate } from '@sveltia/i18n';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Date helpers behind `<Calendar>`, kept apart from the component so the grid can be tested without
|
|
3
5
|
* rendering anything.
|
|
4
6
|
*/
|
|
5
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Format a date in the current locale. Falls back to the browser’s locale if `@sveltia/i18n` has
|
|
10
|
+
* not been initialized yet, in which case its formatter has no locale to work with and throws.
|
|
11
|
+
* @param {Date} value Date to format.
|
|
12
|
+
* @param {Intl.DateTimeFormatOptions} options Formatting options.
|
|
13
|
+
* @returns {string} Formatted date.
|
|
14
|
+
*/
|
|
15
|
+
export const formatDate = (value, options) => {
|
|
16
|
+
try {
|
|
17
|
+
return formatLocaleDate(value, options);
|
|
18
|
+
} catch {
|
|
19
|
+
return value.toLocaleDateString(undefined, options);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
6
23
|
/**
|
|
7
24
|
* Number of cells in the day grid: six weeks, enough to show any month in full from the Sunday on
|
|
8
25
|
* or before its first day.
|
|
@@ -4,12 +4,15 @@
|
|
|
4
4
|
-->
|
|
5
5
|
<script>
|
|
6
6
|
import { _, isRTL } from '@sveltia/i18n';
|
|
7
|
+
import { tick } from 'svelte';
|
|
8
|
+
import { activateGroup } from '../../services/group.svelte.js';
|
|
7
9
|
import Button from '../button/button.svelte';
|
|
8
10
|
import Divider from '../divider/divider.svelte';
|
|
9
11
|
import Spacer from '../divider/spacer.svelte';
|
|
10
12
|
import Icon from '../icon/icon.svelte';
|
|
11
13
|
import {
|
|
12
14
|
addMonths,
|
|
15
|
+
formatDate,
|
|
13
16
|
getCalendarDays,
|
|
14
17
|
getFirstDayOfMonth,
|
|
15
18
|
isSameDay,
|
|
@@ -37,16 +40,48 @@
|
|
|
37
40
|
*/
|
|
38
41
|
let firstDay = $derived(getFirstDayOfMonth(date));
|
|
39
42
|
const dayList = $derived(getCalendarDays(firstDay).map((day) => ({ day })));
|
|
43
|
+
/**
|
|
44
|
+
* Month and year being displayed, in the current locale. It’s the caption of the day grid as
|
|
45
|
+
* well as the label of the month switcher.
|
|
46
|
+
*/
|
|
47
|
+
const monthLabel = $derived(formatDate(firstDay, { year: 'numeric', month: 'short' }));
|
|
48
|
+
const id = $props.id();
|
|
49
|
+
/** @type {HTMLElement | undefined} */
|
|
50
|
+
let grid = $state();
|
|
51
|
+
|
|
52
|
+
// Start the keyboard cursor on the selected day. The group service tracks the cursor with the
|
|
53
|
+
// `focused` class and `aria-activedescendant`, and keeps it as the selection moves; it only
|
|
54
|
+
// needs a starting point here, where nothing has been focused yet.
|
|
55
|
+
$effect(() => {
|
|
56
|
+
void value;
|
|
57
|
+
|
|
58
|
+
// The grid is bound before any effect runs
|
|
59
|
+
/* v8 ignore next */
|
|
60
|
+
if (!grid) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const listbox = grid;
|
|
65
|
+
|
|
66
|
+
tick().then(() => {
|
|
67
|
+
if (listbox.querySelector('.focused')) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const option = listbox.querySelector('[role="option"][aria-selected="true"]');
|
|
72
|
+
|
|
73
|
+
if (option) {
|
|
74
|
+
option.classList.add('focused');
|
|
75
|
+
listbox.setAttribute('aria-activedescendant', option.id);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
});
|
|
40
79
|
</script>
|
|
41
80
|
|
|
42
81
|
<div role="group">
|
|
43
82
|
<input type="hidden" bind:value />
|
|
44
83
|
<div role="none" class="header">
|
|
45
|
-
<Button
|
|
46
|
-
variant="ghost"
|
|
47
|
-
label={firstDay.toLocaleDateString('en', { year: 'numeric', month: 'short' })}
|
|
48
|
-
aria-haspopup="dialog"
|
|
49
|
-
>
|
|
84
|
+
<Button variant="ghost" label={monthLabel} aria-haspopup="dialog">
|
|
50
85
|
{#snippet endIcon()}
|
|
51
86
|
<Icon name="arrow_drop_down" class="small-arrow" />
|
|
52
87
|
{/snippet}
|
|
@@ -115,13 +150,31 @@
|
|
|
115
150
|
<Icon name={isRTL() ? 'chevron_left' : 'chevron_right'} />
|
|
116
151
|
</Button>
|
|
117
152
|
</div>
|
|
118
|
-
|
|
119
|
-
|
|
153
|
+
<!--
|
|
154
|
+
The day grid is a listbox laid out as a grid, so the group service moves through it with all
|
|
155
|
+
four arrow keys. Each day is named by its full date: the digit alone means nothing out of
|
|
156
|
+
context. The days are UTC-based, like the `value`, so the names are formatted the same way.
|
|
157
|
+
The cells are keyed by date string rather than by `Date` object, which is recreated on every
|
|
158
|
+
update: a day that stays on the grid when the month changes — the last days of the previous
|
|
159
|
+
month, say — keeps its element, and with it the keyboard cursor.
|
|
160
|
+
-->
|
|
161
|
+
<div
|
|
162
|
+
bind:this={grid}
|
|
163
|
+
role="listbox"
|
|
164
|
+
class="grid"
|
|
165
|
+
aria-label={monthLabel}
|
|
166
|
+
onChange={(/** @type {CustomEvent} */ event) => {
|
|
167
|
+
value = event.detail.value;
|
|
168
|
+
}}
|
|
169
|
+
{@attach activateGroup()}
|
|
170
|
+
>
|
|
171
|
+
{#each dayList.slice(0, 7) as { day } (day.getUTCDay())}
|
|
120
172
|
<div role="none" class="weekday">
|
|
121
|
-
{day
|
|
173
|
+
{formatDate(day, { weekday: 'narrow' })}
|
|
122
174
|
</div>
|
|
123
175
|
{/each}
|
|
124
|
-
{#each dayList as { day } (day)}
|
|
176
|
+
{#each dayList as { day } (toDateString(day))}
|
|
177
|
+
{@const dateString = toDateString(day)}
|
|
125
178
|
<div
|
|
126
179
|
role="none"
|
|
127
180
|
class:other-month={day.getUTCMonth() !== firstDay.getUTCMonth()}
|
|
@@ -129,9 +182,12 @@
|
|
|
129
182
|
>
|
|
130
183
|
<Button
|
|
131
184
|
role="option"
|
|
132
|
-
|
|
185
|
+
id="{id}-{dateString}"
|
|
186
|
+
value={dateString}
|
|
187
|
+
aria-label={formatDate(day, { dateStyle: 'full', timeZone: 'UTC' })}
|
|
188
|
+
aria-selected={dateString === value}
|
|
133
189
|
onclick={() => {
|
|
134
|
-
value =
|
|
190
|
+
value = dateString;
|
|
135
191
|
}}
|
|
136
192
|
>
|
|
137
193
|
{day.getUTCDate()}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
The layout container of `<Checkbox>`es.
|
|
4
4
|
-->
|
|
5
5
|
<script>
|
|
6
|
+
import { _ } from '@sveltia/i18n';
|
|
7
|
+
|
|
6
8
|
/**
|
|
7
9
|
* @import { Snippet } from 'svelte';
|
|
8
10
|
*/
|
|
@@ -42,7 +44,7 @@
|
|
|
42
44
|
{hidden}
|
|
43
45
|
aria-hidden={hidden}
|
|
44
46
|
aria-disabled={disabled}
|
|
45
|
-
aria-roledescription=
|
|
47
|
+
aria-roledescription={_('_sui.role_descriptions.checkbox_group')}
|
|
46
48
|
aria-label={ariaLabel}
|
|
47
49
|
>
|
|
48
50
|
<div role="none" class="inner" inert={disabled}>
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
*/
|
|
37
37
|
let {
|
|
38
38
|
/* eslint-disable prefer-const */
|
|
39
|
-
checked = $bindable(),
|
|
39
|
+
checked = $bindable(false),
|
|
40
40
|
class: className,
|
|
41
41
|
name = undefined,
|
|
42
42
|
value = undefined,
|
|
@@ -167,6 +167,8 @@
|
|
|
167
167
|
align-items: center;
|
|
168
168
|
gap: 8px;
|
|
169
169
|
margin: var(--sui-focus-ring-width);
|
|
170
|
+
min-width: 24px;
|
|
171
|
+
min-height: 24px;
|
|
170
172
|
color: var(--sui-control-foreground-color);
|
|
171
173
|
font-family: var(--sui-control-font-family);
|
|
172
174
|
font-size: var(--sui-control-font-size);
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
open = $bindable(false),
|
|
25
25
|
value = $bindable(''),
|
|
26
26
|
title,
|
|
27
|
+
ariaLabelledby = undefined,
|
|
27
28
|
role = 'dialog',
|
|
28
29
|
size = 'medium',
|
|
29
30
|
class: className,
|
|
@@ -53,6 +54,14 @@
|
|
|
53
54
|
* @type {string}
|
|
54
55
|
*/
|
|
55
56
|
const id = $props.id();
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Accessible name for the `<dialog>`. The built-in header renders the title in an element the
|
|
60
|
+
* dialog can point at; with a custom header the title is only text, so it becomes `aria-label`
|
|
61
|
+
* unless the consumer names the labelling element.
|
|
62
|
+
*/
|
|
63
|
+
const labelledby = $derived(ariaLabelledby ?? (!header && title ? `${id}-title` : undefined));
|
|
64
|
+
const label = $derived(labelledby ? undefined : title || undefined);
|
|
56
65
|
/**
|
|
57
66
|
* A reference to the modal component.
|
|
58
67
|
* @type {Modal | undefined}
|
|
@@ -109,8 +118,8 @@
|
|
|
109
118
|
{role}
|
|
110
119
|
{id}
|
|
111
120
|
class="dialog"
|
|
112
|
-
aria-label={
|
|
113
|
-
aria-labelledby={
|
|
121
|
+
aria-label={label}
|
|
122
|
+
aria-labelledby={labelledby}
|
|
114
123
|
aria-describedby="{id}-body"
|
|
115
124
|
bind:open
|
|
116
125
|
showBackdrop
|
|
@@ -191,6 +200,9 @@
|
|
|
191
200
|
display: flex;
|
|
192
201
|
flex-direction: column;
|
|
193
202
|
overflow: hidden;
|
|
203
|
+
border-width: var(--sui-dialog-content-border-width, var(--sui-modal-border-width, 0));
|
|
204
|
+
border-style: solid;
|
|
205
|
+
border-color: var(--sui-dialog-content-border-color, var(--sui-primary-border-color));
|
|
194
206
|
border-radius: var(--sui-dialog-content-border-radius, 4px);
|
|
195
207
|
max-width: calc(100dvw - var(--sui-dialog-content-margin, 16px) * 2);
|
|
196
208
|
background-color: var(--sui-dialog-content-background-color, var(--sui-secondary-background-color-translucent));
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
/**
|
|
24
24
|
* @typedef {object} Props
|
|
25
25
|
* @property {string} [value] Value entered on the textbox.
|
|
26
|
-
* @property {object} [textboxAttrs] Extra attributes for the `<TextInput>`.
|
|
26
|
+
* @property {object} [textboxAttrs] Extra attributes for the `<TextInput>`. The text box is
|
|
27
|
+
* labelled by the dialog `title` unless an `aria-label` or `aria-labelledby` is given here.
|
|
27
28
|
* @property {Snippet} [children] Primary slot content.
|
|
28
29
|
* @property {Snippet} [input] Input slot content.
|
|
29
30
|
*/
|
|
@@ -36,6 +37,7 @@
|
|
|
36
37
|
/* eslint-disable prefer-const */
|
|
37
38
|
open = $bindable(false),
|
|
38
39
|
value = $bindable(''),
|
|
40
|
+
title,
|
|
39
41
|
textboxAttrs = {},
|
|
40
42
|
children,
|
|
41
43
|
input,
|
|
@@ -48,7 +50,11 @@
|
|
|
48
50
|
} = $props();
|
|
49
51
|
</script>
|
|
50
52
|
|
|
51
|
-
|
|
53
|
+
<!--
|
|
54
|
+
The built-in text box is the point of the dialog, so it takes the dialog title as its name unless
|
|
55
|
+
the consumer labels it; the dialog itself is still named by the title as usual.
|
|
56
|
+
-->
|
|
57
|
+
<Dialog {...restProps} {title} bind:open role="alertdialog">
|
|
52
58
|
{@render children?.()}
|
|
53
59
|
<div class="input-outer">
|
|
54
60
|
{#if input}
|
|
@@ -59,6 +65,7 @@
|
|
|
59
65
|
bind:value
|
|
60
66
|
flex
|
|
61
67
|
autofocus
|
|
68
|
+
aria-label={'aria-labelledby' in textboxAttrs ? undefined : title}
|
|
62
69
|
{...textboxAttrs}
|
|
63
70
|
{onkeydown}
|
|
64
71
|
{onkeyup}
|
|
@@ -16,7 +16,8 @@ declare const PromptDialog: import("svelte").Component<ModalProps & DialogProps
|
|
|
16
16
|
*/
|
|
17
17
|
value?: string | undefined;
|
|
18
18
|
/**
|
|
19
|
-
* Extra attributes for the `<TextInput>`.
|
|
19
|
+
* Extra attributes for the `<TextInput>`. The text box is
|
|
20
|
+
* labelled by the dialog `title` unless an `aria-label` or `aria-labelledby` is given here.
|
|
20
21
|
*/
|
|
21
22
|
textboxAttrs?: object | undefined;
|
|
22
23
|
/**
|
|
@@ -34,7 +35,8 @@ type Props = {
|
|
|
34
35
|
*/
|
|
35
36
|
value?: string | undefined;
|
|
36
37
|
/**
|
|
37
|
-
* Extra attributes for the `<TextInput>`.
|
|
38
|
+
* Extra attributes for the `<TextInput>`. The text box is
|
|
39
|
+
* labelled by the dialog `title` unless an `aria-label` or `aria-labelledby` is given here.
|
|
38
40
|
*/
|
|
39
41
|
textboxAttrs?: object | undefined;
|
|
40
42
|
/**
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
@see https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/
|
|
6
6
|
-->
|
|
7
7
|
<script>
|
|
8
|
+
import { _ } from '@sveltia/i18n';
|
|
8
9
|
import Button from '../button/button.svelte';
|
|
9
10
|
import Icon from '../icon/icon.svelte';
|
|
10
11
|
|
|
@@ -56,7 +57,7 @@
|
|
|
56
57
|
aria-hidden={hidden}
|
|
57
58
|
aria-disabled={disabled}
|
|
58
59
|
aria-labelledby="{id}-header"
|
|
59
|
-
aria-roledescription=
|
|
60
|
+
aria-roledescription={_('_sui.role_descriptions.disclosure')}
|
|
60
61
|
>
|
|
61
62
|
<div role="none" class="inner" inert={disabled}>
|
|
62
63
|
<Button
|
|
@@ -20,7 +20,11 @@
|
|
|
20
20
|
* @typedef {object} Props
|
|
21
21
|
* @property {string} [class] The `class` attribute on the content element.
|
|
22
22
|
* @property {boolean} [open] Whether to open the drawer.
|
|
23
|
-
* @property {string} [title] Title text displayed on the header.
|
|
23
|
+
* @property {string} [title] Title text displayed on the header. With a custom `header` snippet,
|
|
24
|
+
* it’s used as the drawer’s `aria-label` instead.
|
|
25
|
+
* @property {string} [ariaLabelledby] ID of the element that labels the drawer, typically a
|
|
26
|
+
* heading inside a custom `header` snippet. Takes precedence over `title` for the accessible
|
|
27
|
+
* name.
|
|
24
28
|
* @property {'top' | 'right' | 'bottom' | 'left'} [position] Position of the drawer.
|
|
25
29
|
* @property {'small' | 'medium' | 'large' | 'x-large' | 'full'} [size] Width or height of the
|
|
26
30
|
* drawer.
|
|
@@ -41,6 +45,7 @@
|
|
|
41
45
|
open = $bindable(false),
|
|
42
46
|
class: className,
|
|
43
47
|
title = '',
|
|
48
|
+
ariaLabelledby = undefined,
|
|
44
49
|
position = 'right',
|
|
45
50
|
size = 'small',
|
|
46
51
|
showClose = 'outside',
|
|
@@ -68,9 +73,26 @@
|
|
|
68
73
|
const orientation = $derived(
|
|
69
74
|
position === 'right' || position === 'left' ? 'vertical' : 'horizontal',
|
|
70
75
|
);
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Accessible name for the `<dialog>`, resolved the same way as `<Dialog>`: the built-in header
|
|
79
|
+
* renders the title in an element the dialog can point at; with a custom header the title is
|
|
80
|
+
* only text, so it becomes `aria-label` unless the consumer names the labelling element.
|
|
81
|
+
*/
|
|
82
|
+
const labelledby = $derived(ariaLabelledby ?? (!header && title ? `${id}-title` : undefined));
|
|
83
|
+
const label = $derived(labelledby ? undefined : title || undefined);
|
|
71
84
|
</script>
|
|
72
85
|
|
|
73
|
-
<Modal
|
|
86
|
+
<Modal
|
|
87
|
+
bind:this={modal}
|
|
88
|
+
{...restProps}
|
|
89
|
+
bind:open
|
|
90
|
+
{id}
|
|
91
|
+
class="drawer"
|
|
92
|
+
aria-label={label}
|
|
93
|
+
aria-labelledby={labelledby}
|
|
94
|
+
showBackdrop
|
|
95
|
+
>
|
|
74
96
|
<div role="none" class={['content', className, size, position, orientation]}>
|
|
75
97
|
<div role="none" class="extra-control">
|
|
76
98
|
{#if showClose === 'outside'}
|
|
@@ -99,7 +121,7 @@
|
|
|
99
121
|
{@render header()}
|
|
100
122
|
{:else}
|
|
101
123
|
<div role="none" class="header">
|
|
102
|
-
<div role="none" class="title">
|
|
124
|
+
<div role="none" id="{id}-title" class="title">
|
|
103
125
|
{title}
|
|
104
126
|
</div>
|
|
105
127
|
<Spacer flex={true} />
|
|
@@ -144,6 +166,9 @@
|
|
|
144
166
|
flex-direction: column;
|
|
145
167
|
max-width: 100dvw;
|
|
146
168
|
max-height: 100dvh;
|
|
169
|
+
border-width: var(--sui-drawer-content-border-width, var(--sui-modal-border-width, 0));
|
|
170
|
+
border-style: solid;
|
|
171
|
+
border-color: var(--sui-drawer-content-border-color, var(--sui-primary-border-color));
|
|
147
172
|
background-color: var(--sui-secondary-background-color-translucent);
|
|
148
173
|
box-shadow: 0 8px 16px var(--sui-popup-shadow-color);
|
|
149
174
|
-webkit-backdrop-filter: blur(16px);
|
|
@@ -18,9 +18,16 @@ declare const Drawer: import("svelte").Component<ModalProps & {
|
|
|
18
18
|
*/
|
|
19
19
|
open?: boolean | undefined;
|
|
20
20
|
/**
|
|
21
|
-
* Title text displayed on the header.
|
|
21
|
+
* Title text displayed on the header. With a custom `header` snippet,
|
|
22
|
+
* it’s used as the drawer’s `aria-label` instead.
|
|
22
23
|
*/
|
|
23
24
|
title?: string | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* ID of the element that labels the drawer, typically a
|
|
27
|
+
* heading inside a custom `header` snippet. Takes precedence over `title` for the accessible
|
|
28
|
+
* name.
|
|
29
|
+
*/
|
|
30
|
+
ariaLabelledby?: string | undefined;
|
|
24
31
|
/**
|
|
25
32
|
* Position of the drawer.
|
|
26
33
|
*/
|
|
@@ -69,9 +76,16 @@ type Props = {
|
|
|
69
76
|
*/
|
|
70
77
|
open?: boolean | undefined;
|
|
71
78
|
/**
|
|
72
|
-
* Title text displayed on the header.
|
|
79
|
+
* Title text displayed on the header. With a custom `header` snippet,
|
|
80
|
+
* it’s used as the drawer’s `aria-label` instead.
|
|
73
81
|
*/
|
|
74
82
|
title?: string | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* ID of the element that labels the drawer, typically a
|
|
85
|
+
* heading inside a custom `header` snippet. Takes precedence over `title` for the accessible
|
|
86
|
+
* name.
|
|
87
|
+
*/
|
|
88
|
+
ariaLabelledby?: string | undefined;
|
|
75
89
|
/**
|
|
76
90
|
* Position of the drawer.
|
|
77
91
|
*/
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
@see https://w3c.github.io/aria/#rowgroup
|
|
6
6
|
-->
|
|
7
7
|
<script>
|
|
8
|
+
import { _ } from '@sveltia/i18n';
|
|
9
|
+
|
|
8
10
|
/**
|
|
9
11
|
* @import { Snippet } from 'svelte';
|
|
10
12
|
*/
|
|
@@ -36,7 +38,7 @@
|
|
|
36
38
|
role="rowgroup"
|
|
37
39
|
class="sui grid-body row-group {className}"
|
|
38
40
|
aria-labelledby={label ? `${id}-label` : undefined}
|
|
39
|
-
aria-roledescription=
|
|
41
|
+
aria-roledescription={_('_sui.role_descriptions.grid_body')}
|
|
40
42
|
>
|
|
41
43
|
{#if label}
|
|
42
44
|
<div role="row" class="row-group-caption">
|