@sveltia/ui 0.72.2 → 0.73.1

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.
Files changed (90) hide show
  1. package/dist/components/alert/alert.svelte +29 -5
  2. package/dist/components/alert/alert.svelte.d.ts +8 -4
  3. package/dist/components/alert/infobar.svelte +21 -5
  4. package/dist/components/alert/infobar.svelte.d.ts +10 -4
  5. package/dist/components/button/button.svelte +5 -1
  6. package/dist/components/button/select-button-group.svelte +1 -1
  7. package/dist/components/calendar/calendar.d.ts +1 -4
  8. package/dist/components/calendar/calendar.js +17 -0
  9. package/dist/components/calendar/calendar.svelte +69 -11
  10. package/dist/components/checkbox/checkbox-group.svelte +3 -1
  11. package/dist/components/checkbox/checkbox.svelte +3 -1
  12. package/dist/components/dialog/dialog.svelte +14 -2
  13. package/dist/components/dialog/prompt-dialog.svelte +9 -2
  14. package/dist/components/dialog/prompt-dialog.svelte.d.ts +4 -2
  15. package/dist/components/disclosure/disclosure.svelte +2 -1
  16. package/dist/components/drawer/drawer.svelte +28 -3
  17. package/dist/components/drawer/drawer.svelte.d.ts +16 -2
  18. package/dist/components/grid/grid-body.svelte +3 -1
  19. package/dist/components/grid/grid-foot.svelte +3 -1
  20. package/dist/components/grid/grid-head.svelte +3 -1
  21. package/dist/components/listbox/option-group.svelte +2 -1
  22. package/dist/components/listbox/option.svelte +4 -0
  23. package/dist/components/menu/menu-item-group.svelte +3 -1
  24. package/dist/components/radio/radio.svelte +21 -3
  25. package/dist/components/radio/radio.svelte.d.ts +12 -2
  26. package/dist/components/resizable-pane/resizable-handle.svelte +12 -0
  27. package/dist/components/select/combobox.svelte +69 -1
  28. package/dist/components/select/select-tags.svelte +34 -23
  29. package/dist/components/slider/slider.d.ts +6 -0
  30. package/dist/components/slider/slider.js +40 -0
  31. package/dist/components/slider/slider.svelte +45 -11
  32. package/dist/components/slider/slider.svelte.d.ts +14 -2
  33. package/dist/components/switch/switch.svelte +1 -1
  34. package/dist/components/table/table-body.svelte +3 -1
  35. package/dist/components/table/table-foot.svelte +3 -1
  36. package/dist/components/table/table-head.svelte +3 -1
  37. package/dist/components/text-editor/code-editor.svelte +22 -1
  38. package/dist/components/text-editor/code-editor.svelte.d.ts +28 -0
  39. package/dist/components/text-editor/shiki/version.d.ts +1 -1
  40. package/dist/components/text-editor/shiki/version.js +1 -1
  41. package/dist/components/text-editor/text-editor.svelte +23 -0
  42. package/dist/components/text-editor/text-editor.svelte.d.ts +28 -0
  43. package/dist/components/text-field/number-input.svelte +7 -17
  44. package/dist/components/text-field/search-bar.svelte +1 -0
  45. package/dist/components/text-field/text-input.svelte +1 -1
  46. package/dist/components/toast/toast.svelte +58 -4
  47. package/dist/components/toast/toast.svelte.d.ts +4 -2
  48. package/dist/components/util/app-shell.svelte +289 -5
  49. package/dist/components/util/popup.svelte +1 -1
  50. package/dist/locales/ar.yaml +20 -0
  51. package/dist/locales/bg.yaml +20 -0
  52. package/dist/locales/ca.yaml +20 -0
  53. package/dist/locales/cs.yaml +20 -0
  54. package/dist/locales/da.yaml +20 -0
  55. package/dist/locales/de.yaml +20 -0
  56. package/dist/locales/el.yaml +20 -0
  57. package/dist/locales/en-CA.yaml +20 -0
  58. package/dist/locales/en-GB.yaml +20 -0
  59. package/dist/locales/en-US.yaml +20 -0
  60. package/dist/locales/es-CO.yaml +20 -0
  61. package/dist/locales/fa.yaml +20 -0
  62. package/dist/locales/fi.yaml +20 -0
  63. package/dist/locales/fr.yaml +20 -0
  64. package/dist/locales/hr.yaml +20 -0
  65. package/dist/locales/it.yaml +20 -0
  66. package/dist/locales/ja.yaml +20 -0
  67. package/dist/locales/ko.yaml +20 -0
  68. package/dist/locales/nl.yaml +20 -0
  69. package/dist/locales/pl.yaml +20 -0
  70. package/dist/locales/pt-BR.yaml +20 -0
  71. package/dist/locales/pt-PT.yaml +20 -0
  72. package/dist/locales/ru.yaml +20 -0
  73. package/dist/locales/sv.yaml +20 -0
  74. package/dist/locales/tr.yaml +20 -0
  75. package/dist/locales/uk.yaml +20 -0
  76. package/dist/locales/vi.yaml +20 -0
  77. package/dist/locales/zh-CN.yaml +20 -0
  78. package/dist/locales/zh-TW.yaml +20 -0
  79. package/dist/services/group.svelte.d.ts +12 -0
  80. package/dist/services/group.svelte.js +177 -28
  81. package/dist/services/popup.svelte.js +24 -1
  82. package/dist/services/select.svelte.d.ts +5 -0
  83. package/dist/services/select.svelte.js +8 -0
  84. package/dist/services/type-ahead.d.ts +26 -0
  85. package/dist/services/type-ahead.js +81 -0
  86. package/dist/styles/core.scss +14 -3
  87. package/dist/styles/variables.scss +118 -1
  88. package/dist/typedefs.d.ts +7 -1
  89. package/dist/typedefs.js +4 -1
  90. package/package.json +3 -3
@@ -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
  */
@@ -31,7 +33,7 @@
31
33
  {...restProps}
32
34
  role="rowgroup"
33
35
  class="sui table-foot {className}"
34
- aria-roledescription="table foot"
36
+ aria-roledescription={_('_sui.role_descriptions.table_foot')}
35
37
  >
36
38
  {@render children?.()}
37
39
  </div>
@@ -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
  */
@@ -31,7 +33,7 @@
31
33
  {...restProps}
32
34
  role="rowgroup"
33
35
  class="sui table-head {className}"
34
- aria-roledescription="table head"
36
+ aria-roledescription={_('_sui.role_descriptions.table_head')}
35
37
  >
36
38
  {@render children?.()}
37
39
  </div>
@@ -32,6 +32,11 @@
32
32
  * `aria-required` attribute.
33
33
  * @property {boolean} [invalid] Whether to mark the widget invalid. An alias of the
34
34
  * `aria-invalid` attribute.
35
+ * @property {string} [ariaLabel] The `aria-label` attribute on the editable text box. Either
36
+ * this or `ariaLabelledby` is required for the editor to have an accessible name.
37
+ * @property {string} [ariaLabelledby] The `aria-labelledby` attribute on the editable text box.
38
+ * @property {string} [ariaDescribedby] The `aria-describedby` attribute on the editable text
39
+ * box.
35
40
  * @property {Snippet} [children] Primary slot content.
36
41
  */
37
42
 
@@ -49,11 +54,27 @@
49
54
  readonly = false,
50
55
  required = false,
51
56
  invalid = false,
57
+ ariaLabel = undefined,
58
+ ariaLabelledby = undefined,
59
+ ariaDescribedby = undefined,
60
+ 'aria-label': ariaLabelAttr = undefined,
61
+ 'aria-labelledby': ariaLabelledbyAttr = undefined,
62
+ 'aria-describedby': ariaDescribedbyAttr = undefined,
52
63
  children,
53
64
  ...restProps
54
65
  /* eslint-enable prefer-const */
55
66
  } = $props();
56
67
 
68
+ /**
69
+ * Labelling attributes for the editable text box. They’re pulled out of `restProps` because the
70
+ * rest goes on the presentational wrapper, where ARIA would ignore them.
71
+ */
72
+ const labelAttrs = $derived({
73
+ 'aria-label': ariaLabel ?? ariaLabelAttr,
74
+ 'aria-labelledby': ariaLabelledby ?? ariaLabelledbyAttr,
75
+ 'aria-describedby': ariaDescribedby ?? ariaDescribedbyAttr,
76
+ });
77
+
57
78
  const editorStore = createEditorStore();
58
79
 
59
80
  editorStore.config = {
@@ -107,7 +128,7 @@
107
128
  {#if showLanguageSwitcher}
108
129
  <CodeEditorToolbar {disabled} {readonly} />
109
130
  {/if}
110
- <LexicalRoot {hidden} {disabled} {readonly} {required} {invalid} />
131
+ <LexicalRoot {...labelAttrs} {hidden} {disabled} {readonly} {required} {invalid} />
111
132
  </div>
112
133
 
113
134
  {#if editorStore.showConverterError}
@@ -49,6 +49,20 @@ declare const CodeEditor: import("svelte").Component<{
49
49
  * `aria-invalid` attribute.
50
50
  */
51
51
  invalid?: boolean | undefined;
52
+ /**
53
+ * The `aria-label` attribute on the editable text box. Either
54
+ * this or `ariaLabelledby` is required for the editor to have an accessible name.
55
+ */
56
+ ariaLabel?: string | undefined;
57
+ /**
58
+ * The `aria-labelledby` attribute on the editable text box.
59
+ */
60
+ ariaLabelledby?: string | undefined;
61
+ /**
62
+ * The `aria-describedby` attribute on the editable text
63
+ * box.
64
+ */
65
+ ariaDescribedby?: string | undefined;
52
66
  /**
53
67
  * Primary slot content.
54
68
  */
@@ -99,6 +113,20 @@ type Props = {
99
113
  * `aria-invalid` attribute.
100
114
  */
101
115
  invalid?: boolean | undefined;
116
+ /**
117
+ * The `aria-label` attribute on the editable text box. Either
118
+ * this or `ariaLabelledby` is required for the editor to have an accessible name.
119
+ */
120
+ ariaLabel?: string | undefined;
121
+ /**
122
+ * The `aria-labelledby` attribute on the editable text box.
123
+ */
124
+ ariaLabelledby?: string | undefined;
125
+ /**
126
+ * The `aria-describedby` attribute on the editable text
127
+ * box.
128
+ */
129
+ ariaDescribedby?: string | undefined;
102
130
  /**
103
131
  * Primary slot content.
104
132
  */
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * Version of this package, used to resolve the prebuilt Shiki engine chunk from a CDN.
3
3
  */
4
- export const UI_VERSION: "0.72.2";
4
+ export const UI_VERSION: "0.73.1";
@@ -3,4 +3,4 @@
3
3
  /**
4
4
  * Version of this package, used to resolve the prebuilt Shiki engine chunk from a CDN.
5
5
  */
6
- export const UI_VERSION = '0.72.2';
6
+ export const UI_VERSION = '0.73.1';
@@ -41,6 +41,11 @@
41
41
  * `aria-required` attribute.
42
42
  * @property {boolean} [invalid] Whether to mark the widget invalid. An alias of the
43
43
  * `aria-invalid` attribute.
44
+ * @property {string} [ariaLabel] The `aria-label` attribute on the editable text box. Either
45
+ * this or `ariaLabelledby` is required for the editor to have an accessible name.
46
+ * @property {string} [ariaLabelledby] The `aria-labelledby` attribute on the editable text box.
47
+ * @property {string} [ariaDescribedby] The `aria-describedby` attribute on the editable text
48
+ * box.
44
49
  * @property {Snippet} [children] Primary slot content.
45
50
  */
46
51
 
@@ -62,11 +67,27 @@
62
67
  readonly = false,
63
68
  required = false,
64
69
  invalid = false,
70
+ ariaLabel = undefined,
71
+ ariaLabelledby = undefined,
72
+ ariaDescribedby = undefined,
73
+ 'aria-label': ariaLabelAttr = undefined,
74
+ 'aria-labelledby': ariaLabelledbyAttr = undefined,
75
+ 'aria-describedby': ariaDescribedbyAttr = undefined,
65
76
  children,
66
77
  ...restProps
67
78
  /* eslint-enable prefer-const */
68
79
  } = $props();
69
80
 
81
+ /**
82
+ * Labelling attributes for the editable text box. They’re pulled out of `restProps` because the
83
+ * rest goes on the presentational wrapper, where ARIA would ignore them.
84
+ */
85
+ const labelAttrs = $derived({
86
+ 'aria-label': ariaLabel ?? ariaLabelAttr,
87
+ 'aria-labelledby': ariaLabelledby ?? ariaLabelledbyAttr,
88
+ 'aria-describedby': ariaDescribedby ?? ariaDescribedbyAttr,
89
+ });
90
+
70
91
  const editorStore = createEditorStore();
71
92
 
72
93
  // svelte-ignore state_referenced_locally
@@ -115,6 +136,7 @@
115
136
  <div {...restProps} role="none" class="sui text-editor" class:flex {hidden}>
116
137
  <TextEditorToolbar {disabled} {readonly} />
117
138
  <LexicalRoot
139
+ {...labelAttrs}
118
140
  hidden={!editorStore.useRichText || hidden}
119
141
  {disabled}
120
142
  {readonly}
@@ -122,6 +144,7 @@
122
144
  {invalid}
123
145
  />
124
146
  <TextArea
147
+ {...labelAttrs}
125
148
  autoResize={true}
126
149
  bind:value={editorStore.inputValue}
127
150
  {useEmojiAutocomplete}
@@ -67,6 +67,20 @@ declare const TextEditor: import("svelte").Component<{
67
67
  * `aria-invalid` attribute.
68
68
  */
69
69
  invalid?: boolean | undefined;
70
+ /**
71
+ * The `aria-label` attribute on the editable text box. Either
72
+ * this or `ariaLabelledby` is required for the editor to have an accessible name.
73
+ */
74
+ ariaLabel?: string | undefined;
75
+ /**
76
+ * The `aria-labelledby` attribute on the editable text box.
77
+ */
78
+ ariaLabelledby?: string | undefined;
79
+ /**
80
+ * The `aria-describedby` attribute on the editable text
81
+ * box.
82
+ */
83
+ ariaDescribedby?: string | undefined;
70
84
  /**
71
85
  * Primary slot content.
72
86
  */
@@ -135,6 +149,20 @@ type Props = {
135
149
  * `aria-invalid` attribute.
136
150
  */
137
151
  invalid?: boolean | undefined;
152
+ /**
153
+ * The `aria-label` attribute on the editable text box. Either
154
+ * this or `ariaLabelledby` is required for the editor to have an accessible name.
155
+ */
156
+ ariaLabel?: string | undefined;
157
+ /**
158
+ * The `aria-labelledby` attribute on the editable text box.
159
+ */
160
+ ariaLabelledby?: string | undefined;
161
+ /**
162
+ * The `aria-describedby` attribute on the editable text
163
+ * box.
164
+ */
165
+ ariaDescribedby?: string | undefined;
138
166
  /**
139
167
  * Primary slot content.
140
168
  */
@@ -228,7 +228,7 @@
228
228
  filter: grayscale(0) opacity(1);
229
229
  }
230
230
  .number-input :global(:not(.disabled)) :global(button[aria-disabled=true]) :global(*) {
231
- filter: grayscale(1) opacity(0.35);
231
+ filter: grayscale(1) opacity(var(--sui-disabled-opacity, 0.35));
232
232
  }
233
233
  .number-input :global(.text-input) {
234
234
  flex: auto;
@@ -239,8 +239,8 @@
239
239
 
240
240
  .buttons {
241
241
  display: flex;
242
- flex-direction: column;
243
- width: 24px;
242
+ flex-direction: row;
243
+ width: 48px;
244
244
  height: var(--sui-textbox-height);
245
245
  }
246
246
  .buttons :global(button) {
@@ -248,26 +248,16 @@
248
248
  margin: 0 !important;
249
249
  border-width: 1px;
250
250
  border-color: var(--sui-textbox-border-color);
251
- width: 100%;
252
- height: 50%;
251
+ width: 50%;
252
+ height: 100%;
253
+ border-inline-end-width: 0;
253
254
  }
254
255
  .buttons :global(button:first-of-type) {
255
- border-block-start-width: 1px;
256
- border-block-end-width: 0;
257
- border-inline-end-width: 0;
258
- border-inline-start-width: 1px;
259
256
  border-start-end-radius: 0;
260
257
  border-end-end-radius: 0;
261
- border-end-start-radius: 0;
262
258
  }
263
259
  .buttons :global(button:last-of-type) {
264
- border-block-start-width: 0;
265
- border-block-end-width: 1px;
266
- border-inline-end-width: 0;
267
- border-inline-start-width: 1px;
268
- border-start-start-radius: 0;
269
- border-start-end-radius: 0;
270
- border-end-end-radius: 0;
260
+ border-radius: 0;
271
261
  }
272
262
  .buttons :global(button) :global(.icon) {
273
263
  font-size: 20px;
@@ -96,6 +96,7 @@
96
96
  {#if value}
97
97
  <Button
98
98
  iconic
99
+ disabled={disabled || readonly}
99
100
  aria-label={_('_sui.clear')}
100
101
  aria-controls={id}
101
102
  onclick={() => {
@@ -189,7 +189,7 @@ input:focus {
189
189
  background-color: var(--sui-textbox-background-color-focus, var(--sui-textbox-background-color));
190
190
  }
191
191
  input:read-only {
192
- color: var(--sui-tertiary-foreground-color);
192
+ color: var(--sui-secondary-foreground-color);
193
193
  border-color: var(--sui-textbox-border-color) !important;
194
194
  }
195
195
  input:is(:disabled, :read-only) {
@@ -18,7 +18,8 @@
18
18
  * the toast will be reset, meaning the same toast can be displayed for a longer period of time.
19
19
  * @property {boolean} [show] Whether to show the toast.
20
20
  * @property {number} [duration] Duration to automatically hide the toast. Use `0` to hide it
21
- * manually from the consumer.
21
+ * manually from the consumer. The countdown is held while the pointer is over the toast or the
22
+ * focus is inside it, and starts over once it leaves.
22
23
  * @property {ToastPosition} [position] Position of the toast.
23
24
  * @property {Snippet} [children] Primary slot content.
24
25
  */
@@ -53,6 +54,20 @@
53
54
  * @type {number}
54
55
  */
55
56
  let timerId = $state(0);
57
+ /**
58
+ * Whether the pointer is over the toast or the focus is inside it. The auto-hide countdown is
59
+ * held while it is, so a toast with a control in it can’t vanish from under the user (WCAG
60
+ * 2.2.1).
61
+ * @type {boolean}
62
+ */
63
+ let held = $state(false);
64
+ /**
65
+ * Whether the content is in the DOM tree. It lags behind {@link show} on the way out, so the
66
+ * toast can fade before the content goes. On the way in, the content is inserted rather than
67
+ * unhidden: a live region is only announced when its content arrives.
68
+ * @type {boolean}
69
+ */
70
+ let rendered = $state(false);
56
71
 
57
72
  onMount(() => {
58
73
  popover =
@@ -115,16 +130,34 @@
115
130
  }
116
131
  });
117
132
 
133
+ $effect(() => {
134
+ if (show) {
135
+ rendered = true;
136
+
137
+ return undefined;
138
+ }
139
+
140
+ // Matches the opacity transition below
141
+ const timer = globalThis.setTimeout(() => {
142
+ rendered = false;
143
+ }, 250);
144
+
145
+ return () => {
146
+ globalThis.clearTimeout(timer);
147
+ };
148
+ });
149
+
118
150
  $effect(() => {
119
151
  void id;
120
152
  void show;
121
153
  void duration;
154
+ void held;
122
155
 
123
156
  untrack(() => {
124
157
  globalThis.clearTimeout(timerId);
125
158
  });
126
159
 
127
- if (show && duration) {
160
+ if (show && duration && !held) {
128
161
  timerId = /** @type {number} */ (
129
162
  /** @type {unknown} */ (
130
163
  globalThis.setTimeout(() => {
@@ -138,8 +171,27 @@
138
171
 
139
172
  <div bind:this={popoverBase} role="none" class="sui toast-base"></div>
140
173
 
141
- <div {...restProps} bind:this={toast} class={['sui', 'toast', position]} aria-hidden={!show}>
142
- {@render children?.()}
174
+ <div
175
+ {...restProps}
176
+ bind:this={toast}
177
+ class={['sui', 'toast', position]}
178
+ aria-hidden={!show}
179
+ onpointerenter={() => {
180
+ held = true;
181
+ }}
182
+ onpointerleave={() => {
183
+ held = false;
184
+ }}
185
+ onfocusin={() => {
186
+ held = true;
187
+ }}
188
+ onfocusout={(event) => {
189
+ held = !!event.relatedTarget && !!toast?.contains(/** @type {Node} */ (event.relatedTarget));
190
+ }}
191
+ >
192
+ {#if rendered}
193
+ {@render children?.()}
194
+ {/if}
143
195
  </div>
144
196
 
145
197
  <style>.toast-base {
@@ -178,10 +230,12 @@
178
230
  opacity: 1;
179
231
  transition-duration: 250ms;
180
232
  will-change: opacity;
233
+ pointer-events: auto;
181
234
  }
182
235
  .toast[aria-hidden=true] {
183
236
  display: block;
184
237
  opacity: 0;
238
+ pointer-events: none;
185
239
  }
186
240
  .toast.top-left {
187
241
  inset-block-start: 0;
@@ -20,7 +20,8 @@ declare const Toast: import("svelte").Component<{
20
20
  show?: boolean | undefined;
21
21
  /**
22
22
  * Duration to automatically hide the toast. Use `0` to hide it
23
- * manually from the consumer.
23
+ * manually from the consumer. The countdown is held while the pointer is over the toast or the
24
+ * focus is inside it, and starts over once it leaves.
24
25
  */
25
26
  duration?: number | undefined;
26
27
  /**
@@ -44,7 +45,8 @@ type Props = {
44
45
  show?: boolean | undefined;
45
46
  /**
46
47
  * Duration to automatically hide the toast. Use `0` to hide it
47
- * manually from the consumer.
48
+ * manually from the consumer. The countdown is held while the pointer is over the toast or the
49
+ * focus is inside it, and starts over once it leaves.
48
50
  */
49
51
  duration?: number | undefined;
50
52
  /**