@sveltia/ui 0.78.4 → 0.79.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.
Files changed (181) hide show
  1. package/dist/components/alert/alert-content.svelte +51 -0
  2. package/dist/components/alert/alert-content.svelte.d.ts +39 -0
  3. package/dist/components/alert/alert.d.ts +3 -0
  4. package/dist/components/alert/alert.js +21 -0
  5. package/dist/components/alert/alert.svelte +6 -30
  6. package/dist/components/alert/infobar.svelte +7 -21
  7. package/dist/components/alert/infobar.svelte.d.ts +2 -2
  8. package/dist/components/button/button.svelte +11 -11
  9. package/dist/components/button/button.svelte.d.ts +2 -2
  10. package/dist/components/button/select-button.svelte +1 -1
  11. package/dist/components/button/select-button.svelte.d.ts +2 -2
  12. package/dist/components/button/split-button.svelte +1 -1
  13. package/dist/components/button/split-button.svelte.d.ts +3 -3
  14. package/dist/components/calendar/calendar.d.ts +2 -0
  15. package/dist/components/calendar/calendar.js +34 -0
  16. package/dist/components/calendar/calendar.svelte +49 -5
  17. package/dist/components/checkbox/checkbox.svelte +11 -4
  18. package/dist/components/checkbox/checkbox.svelte.d.ts +2 -2
  19. package/dist/components/dialog/alert-dialog.svelte +1 -1
  20. package/dist/components/dialog/alert-dialog.svelte.d.ts +2 -2
  21. package/dist/components/dialog/confirmation-dialog.svelte +1 -1
  22. package/dist/components/dialog/confirmation-dialog.svelte.d.ts +2 -2
  23. package/dist/components/dialog/dialog.svelte +8 -18
  24. package/dist/components/dialog/dialog.svelte.d.ts +2 -2
  25. package/dist/components/dialog/modal-close-button.svelte +50 -0
  26. package/dist/components/dialog/modal-close-button.svelte.d.ts +43 -0
  27. package/dist/components/dialog/prompt-dialog.svelte +1 -1
  28. package/dist/components/dialog/prompt-dialog.svelte.d.ts +4 -4
  29. package/dist/components/disclosure/disclosure.svelte +1 -1
  30. package/dist/components/drawer/drawer.svelte +12 -34
  31. package/dist/components/drawer/drawer.svelte.d.ts +1 -1
  32. package/dist/components/emoji/caret.d.ts +1 -1
  33. package/dist/components/emoji/caret.js +1 -1
  34. package/dist/components/emoji/emoji-suggestions.svelte +1 -1
  35. package/dist/components/emoji/emoji-suggestions.svelte.d.ts +3 -3
  36. package/dist/components/emoji/emoji.d.ts +2 -2
  37. package/dist/components/emoji/emoji.js +1 -1
  38. package/dist/components/emoji/position.d.ts +2 -2
  39. package/dist/components/emoji/position.js +1 -1
  40. package/dist/components/grid/grid-body.svelte +6 -2
  41. package/dist/components/grid/grid-cell.svelte +3 -9
  42. package/dist/components/grid/grid-col-header.svelte +3 -9
  43. package/dist/components/grid/grid-foot.svelte +4 -11
  44. package/dist/components/grid/grid-head.svelte +4 -11
  45. package/dist/components/grid/grid-row-header.svelte +3 -10
  46. package/dist/components/grid/grid-row.svelte +3 -16
  47. package/dist/components/listbox/listbox.svelte +1 -1
  48. package/dist/components/listbox/listbox.svelte.d.ts +1 -1
  49. package/dist/components/listbox/option.svelte +1 -1
  50. package/dist/components/listbox/option.svelte.d.ts +2 -2
  51. package/dist/components/menu/menu-button.svelte +2 -2
  52. package/dist/components/menu/menu-button.svelte.d.ts +4 -4
  53. package/dist/components/menu/menu-item-checkable.svelte +71 -0
  54. package/dist/components/menu/menu-item-checkable.svelte.d.ts +25 -0
  55. package/dist/components/menu/menu-item-checkbox.svelte +3 -36
  56. package/dist/components/menu/menu-item-checkbox.svelte.d.ts +3 -3
  57. package/dist/components/menu/menu-item-group.svelte +8 -5
  58. package/dist/components/menu/menu-item-group.svelte.d.ts +9 -1
  59. package/dist/components/menu/menu-item-radio.svelte +3 -36
  60. package/dist/components/menu/menu-item-radio.svelte.d.ts +3 -3
  61. package/dist/components/menu/menu-item.svelte +22 -6
  62. package/dist/components/menu/menu-item.svelte.d.ts +4 -4
  63. package/dist/components/radio/radio.svelte +12 -1
  64. package/dist/components/resizable-pane/resizable-handle.svelte +14 -18
  65. package/dist/components/resizable-pane/resizable-pane-group.svelte +9 -6
  66. package/dist/components/resizable-pane/resizable-pane.svelte +4 -4
  67. package/dist/components/resizable-pane/resizable-pane.svelte.d.ts +7 -6
  68. package/dist/components/resizable-pane/sizing.d.ts +5 -4
  69. package/dist/components/resizable-pane/sizing.js +84 -25
  70. package/dist/components/scroll/infinite-scroll.svelte +16 -21
  71. package/dist/components/select/combobox.svelte +43 -52
  72. package/dist/components/select/combobox.svelte.d.ts +2 -2
  73. package/dist/components/select/select-tags.svelte +7 -1
  74. package/dist/components/select/select.svelte +2 -22
  75. package/dist/components/select/select.svelte.d.ts +1 -1
  76. package/dist/components/slider/slider.d.ts +6 -0
  77. package/dist/components/slider/slider.js +55 -2
  78. package/dist/components/slider/slider.svelte +63 -32
  79. package/dist/components/table/table-cell.svelte +3 -9
  80. package/dist/components/table/table-col-header.svelte +3 -9
  81. package/dist/components/table/table-foot.svelte +4 -11
  82. package/dist/components/table/table-head.svelte +4 -11
  83. package/dist/components/table/table-part.svelte +69 -0
  84. package/dist/components/table/table-part.svelte.d.ts +48 -0
  85. package/dist/components/table/table-row-header.svelte +3 -10
  86. package/dist/components/table/table-row.svelte +3 -10
  87. package/dist/components/tabs/tab-list.svelte +12 -0
  88. package/dist/components/tabs/tab.svelte +1 -1
  89. package/dist/components/tabs/tab.svelte.d.ts +2 -2
  90. package/dist/components/text-editor/constants.d.ts +5 -5
  91. package/dist/components/text-editor/constants.js +1 -1
  92. package/dist/components/text-editor/core.d.ts +4 -5
  93. package/dist/components/text-editor/core.js +372 -285
  94. package/dist/components/text-editor/emoji-autocomplete.svelte +1 -1
  95. package/dist/components/text-editor/lexical-root.svelte +1 -1
  96. package/dist/components/text-editor/raw-markdown.d.ts +6 -33
  97. package/dist/components/text-editor/raw-markdown.js +63 -8
  98. package/dist/components/text-editor/shiki/facade.d.ts +1 -1
  99. package/dist/components/text-editor/shiki/facade.js +63 -48
  100. package/dist/components/text-editor/shiki/generated.d.ts +1 -1
  101. package/dist/components/text-editor/shiki/generated.js +1 -1
  102. package/dist/components/text-editor/shiki/highlighter.d.ts +6 -1
  103. package/dist/components/text-editor/shiki/highlighter.js +3 -4
  104. package/dist/components/text-editor/shiki/loader.d.ts +1 -1
  105. package/dist/components/text-editor/shiki/loader.js +1 -1
  106. package/dist/components/text-editor/shiki/version.d.ts +1 -1
  107. package/dist/components/text-editor/shiki/version.js +1 -1
  108. package/dist/components/text-editor/store.svelte.d.ts +1 -1
  109. package/dist/components/text-editor/store.svelte.js +5 -2
  110. package/dist/components/text-editor/text-editor.svelte +2 -2
  111. package/dist/components/text-editor/text-editor.svelte.d.ts +3 -3
  112. package/dist/components/text-editor/toolbar/code-language-switcher.svelte +24 -16
  113. package/dist/components/text-editor/toolbar/format-text-button.svelte +7 -31
  114. package/dist/components/text-editor/toolbar/format-text-button.svelte.d.ts +1 -1
  115. package/dist/components/text-editor/toolbar/insert-component.d.ts +3 -0
  116. package/dist/components/text-editor/toolbar/insert-component.js +34 -0
  117. package/dist/components/text-editor/toolbar/insert-item-button.svelte +4 -24
  118. package/dist/components/text-editor/toolbar/insert-item-button.svelte.d.ts +1 -1
  119. package/dist/components/text-editor/toolbar/insert-link-button.svelte +9 -25
  120. package/dist/components/text-editor/toolbar/insert-menu-button.svelte +4 -24
  121. package/dist/components/text-editor/toolbar/insert-menu-button.svelte.d.ts +1 -1
  122. package/dist/components/text-editor/toolbar/text-editor-toolbar.svelte +1 -1
  123. package/dist/components/text-editor/toolbar/toggle-block-menu-item.svelte +31 -35
  124. package/dist/components/text-editor/toolbar/toggle-block-menu-item.svelte.d.ts +1 -1
  125. package/dist/components/text-editor/toolbar/toolbar-wrapper.svelte +0 -10
  126. package/dist/components/text-editor/toolbar/toolbar-wrapper.svelte.d.ts +1 -11
  127. package/dist/components/text-editor/transformers/table.d.ts +0 -1
  128. package/dist/components/text-editor/transformers/table.js +1 -2
  129. package/dist/components/text-field/emoji-autocomplete.svelte +1 -1
  130. package/dist/components/text-field/masked-input.svelte +179 -0
  131. package/dist/components/text-field/masked-input.svelte.d.ts +42 -0
  132. package/dist/components/text-field/number-input.js +7 -1
  133. package/dist/components/text-field/number-input.svelte +13 -5
  134. package/dist/components/text-field/number-input.svelte.d.ts +3 -3
  135. package/dist/components/text-field/password-input.svelte +3 -116
  136. package/dist/components/text-field/password-input.svelte.d.ts +3 -3
  137. package/dist/components/text-field/search-bar.svelte +1 -1
  138. package/dist/components/text-field/search-bar.svelte.d.ts +3 -3
  139. package/dist/components/text-field/secret-input.svelte +3 -119
  140. package/dist/components/text-field/secret-input.svelte.d.ts +3 -3
  141. package/dist/components/text-field/text-area.svelte +1 -1
  142. package/dist/components/text-field/text-area.svelte.d.ts +3 -3
  143. package/dist/components/text-field/text-input.svelte +1 -1
  144. package/dist/components/text-field/text-input.svelte.d.ts +3 -3
  145. package/dist/components/toast/toast.svelte +11 -20
  146. package/dist/components/toast/toast.svelte.d.ts +1 -1
  147. package/dist/components/tree/tree-item.svelte +1 -1
  148. package/dist/components/tree/tree-item.svelte.d.ts +1 -1
  149. package/dist/components/tree/tree.svelte +1 -1
  150. package/dist/components/tree/tree.svelte.d.ts +1 -1
  151. package/dist/components/util/font-links.svelte +1 -0
  152. package/dist/components/util/modal.svelte +37 -1
  153. package/dist/components/util/modal.svelte.d.ts +1 -1
  154. package/dist/components/util/popup.svelte +2 -5
  155. package/dist/components/util/popup.svelte.d.ts +2 -2
  156. package/dist/components/util/visibility-observer.svelte +1 -0
  157. package/dist/locales/ar.yaml +1 -1
  158. package/dist/locales/sk.yaml +136 -0
  159. package/dist/services/group.svelte.d.ts +10 -1
  160. package/dist/services/group.svelte.js +361 -251
  161. package/dist/services/navigation.d.ts +19 -0
  162. package/dist/services/navigation.js +84 -0
  163. package/dist/services/popup-position.d.ts +90 -0
  164. package/dist/services/popup-position.js +275 -0
  165. package/dist/services/popup.svelte.d.ts +16 -17
  166. package/dist/services/popup.svelte.js +25 -169
  167. package/dist/services/scroll.d.ts +1 -0
  168. package/dist/services/scroll.js +17 -0
  169. package/dist/services/select.svelte.d.ts +2 -2
  170. package/dist/services/select.svelte.js +1 -1
  171. package/dist/services/text.d.ts +2 -0
  172. package/dist/services/text.js +28 -0
  173. package/dist/services/tree.svelte.d.ts +3 -11
  174. package/dist/services/tree.svelte.js +35 -77
  175. package/dist/services/type-ahead.d.ts +0 -4
  176. package/dist/services/type-ahead.js +3 -1
  177. package/dist/services/widget.d.ts +8 -0
  178. package/dist/services/widget.js +39 -0
  179. package/dist/shiki-engine.js +15 -15
  180. package/dist/styles/mixins.scss +126 -0
  181. package/package.json +24 -20
@@ -0,0 +1,51 @@
1
+ <!--
2
+ @component
3
+ The content shared by `<Alert>` and `<Infobar>`: the status icon, the status spelled out off
4
+ screen, and the message. Internal; not exported.
5
+ -->
6
+ <script>
7
+ import { _ } from '@sveltia/i18n';
8
+ import Icon from '../icon/icon.svelte';
9
+
10
+ /**
11
+ * @import { Snippet } from 'svelte';
12
+ * @import { AlertStatus } from './alert.js';
13
+ */
14
+
15
+ /**
16
+ * @typedef {object} Props
17
+ * @property {AlertStatus} status Alert status.
18
+ * @property {Snippet} [children] Primary slot content.
19
+ * @property {Snippet} [icon] Icon slot content.
20
+ */
21
+
22
+ /** @type {Props} */
23
+ let {
24
+ /* eslint-disable prefer-const */
25
+ status,
26
+ children = undefined,
27
+ icon = undefined,
28
+ /* eslint-enable prefer-const */
29
+ } = $props();
30
+ </script>
31
+
32
+ <!--
33
+ The colour and icon tell sighted users how serious the message is; the icon is hidden from
34
+ assistive technology, so the status is also spelled out for screen readers, off screen.
35
+ -->
36
+ {#if icon}
37
+ {@render icon()}
38
+ {:else}
39
+ <Icon name={status === 'success' ? 'check_circle' : status} />
40
+ {/if}
41
+ <span class="status-label">{_(`_sui.alert.${status}`)}</span>
42
+ {@render children?.()}
43
+
44
+ <style>.status-label {
45
+ position: absolute;
46
+ overflow: hidden;
47
+ clip-path: inset(50%);
48
+ width: 1px;
49
+ height: 1px;
50
+ white-space: nowrap;
51
+ }</style>
@@ -0,0 +1,39 @@
1
+ export default AlertContent;
2
+ type AlertContent = {
3
+ $on?(type: string, callback: (e: any) => void): () => void;
4
+ $set?(props: Partial<Props>): void;
5
+ };
6
+ /**
7
+ * The content shared by `<Alert>` and `<Infobar>`: the status icon, the status spelled out off
8
+ * screen, and the message. Internal; not exported.
9
+ */
10
+ declare const AlertContent: import("svelte").Component<{
11
+ /**
12
+ * Alert status.
13
+ */
14
+ status: AlertStatus;
15
+ /**
16
+ * Primary slot content.
17
+ */
18
+ children?: Snippet<[]> | undefined;
19
+ /**
20
+ * Icon slot content.
21
+ */
22
+ icon?: Snippet<[]> | undefined;
23
+ }, {}, "">;
24
+ type Props = {
25
+ /**
26
+ * Alert status.
27
+ */
28
+ status: AlertStatus;
29
+ /**
30
+ * Primary slot content.
31
+ */
32
+ children?: Snippet<[]> | undefined;
33
+ /**
34
+ * Icon slot content.
35
+ */
36
+ icon?: Snippet<[]> | undefined;
37
+ };
38
+ import type { AlertStatus } from './alert.js';
39
+ import type { Snippet } from 'svelte';
@@ -0,0 +1,3 @@
1
+ export function getAlertRole(status: AlertStatus): "alert" | "status";
2
+ export function getAlertAriaLive(role: "alert" | "status", ariaLive?: "off" | "polite" | "assertive"): "off" | "polite" | "assertive";
3
+ export type AlertStatus = "error" | "warning" | "info" | "success";
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @typedef {'error' | 'warning' | 'info' | 'success'} AlertStatus
3
+ */
4
+
5
+ /**
6
+ * Get the ARIA role for an alert status. Errors and warnings are `alert` and interrupt; information
7
+ * and success are `status` and wait.
8
+ * @param {AlertStatus} status Alert status.
9
+ * @returns {'alert' | 'status'} Role.
10
+ */
11
+ export const getAlertRole = (status) =>
12
+ status === 'error' || status === 'warning' ? 'alert' : 'status';
13
+
14
+ /**
15
+ * Get the ARIA live region politeness for an alert role, unless one is given explicitly.
16
+ * @param {'alert' | 'status'} role Role from {@link getAlertRole}.
17
+ * @param {'off' | 'polite' | 'assertive'} [ariaLive] Explicit politeness, if any.
18
+ * @returns {'off' | 'polite' | 'assertive'} Politeness.
19
+ */
20
+ export const getAlertAriaLive = (role, ariaLive) =>
21
+ ariaLive ?? (role === 'alert' ? 'assertive' : 'polite');
@@ -5,8 +5,8 @@
5
5
  @see https://www.w3.org/WAI/ARIA/apg/patterns/alert/
6
6
  -->
7
7
  <script>
8
- import { _ } from '@sveltia/i18n';
9
- import Icon from '../icon/icon.svelte';
8
+ import AlertContent from './alert-content.svelte';
9
+ import { getAlertAriaLive, getAlertRole } from './alert.js';
10
10
 
11
11
  /**
12
12
  * @import { Snippet } from 'svelte';
@@ -35,38 +35,14 @@
35
35
  /* eslint-enable prefer-const */
36
36
  } = $props();
37
37
 
38
- const role = $derived(status === 'error' || status === 'warning' ? 'alert' : 'status');
38
+ const role = $derived(getAlertRole(status));
39
39
  </script>
40
40
 
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
- >
51
- {#if icon}
52
- {@render icon()}
53
- {:else}
54
- <Icon name={status === 'success' ? 'check_circle' : status} />
55
- {/if}
56
- <span class="status-label">{_(`_sui.alert.${status}`)}</span>
57
- {@render children?.()}
41
+ <div {...restProps} {role} class="sui alert {status}" aria-live={getAlertAriaLive(role, ariaLive)}>
42
+ <AlertContent {status} {children} {icon} />
58
43
  </div>
59
44
 
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 {
45
+ <style>.alert {
70
46
  display: flex;
71
47
  align-items: center;
72
48
  gap: var(--gap, 8px);
@@ -2,6 +2,8 @@
2
2
  import { _ } from '@sveltia/i18n';
3
3
  import Button from '../button/button.svelte';
4
4
  import Icon from '../icon/icon.svelte';
5
+ import AlertContent from './alert-content.svelte';
6
+ import { getAlertAriaLive, getAlertRole } from './alert.js';
5
7
 
6
8
  /**
7
9
  * @import { Snippet } from 'svelte';
@@ -9,7 +11,7 @@
9
11
 
10
12
  /**
11
13
  * @typedef {object} Props
12
- * @property {boolean} [show] Whether to show the toast.
14
+ * @property {boolean} [show] Whether to show the infobar.
13
15
  * @property {boolean} [dismissible] Whether to show the close button.
14
16
  * @property {'error' | 'warning' | 'info' | 'success'} [status] Information status. Errors and
15
17
  * warnings are `role="alert"` and interrupt; information and success are `role="status"` and
@@ -34,20 +36,13 @@
34
36
  /* eslint-enable prefer-const */
35
37
  } = $props();
36
38
 
37
- const role = $derived(status === 'error' || status === 'warning' ? 'alert' : 'status');
39
+ const role = $derived(getAlertRole(status));
38
40
  </script>
39
41
 
40
42
  {#if show}
41
43
  <div role="none" class={['infobar', status]}>
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')}>
44
- {#if icon}
45
- {@render icon()}
46
- {:else}
47
- <Icon name={status === 'success' ? 'check_circle' : status} />
48
- {/if}
49
- <span class="status-label">{_(`_sui.alert.${status}`)}</span>
50
- {@render children?.()}
44
+ <div {role} class="message" aria-live={getAlertAriaLive(role, ariaLive)}>
45
+ <AlertContent {status} {children} {icon} />
51
46
  </div>
52
47
  {#if dismissible}
53
48
  <div role="none">
@@ -68,16 +63,7 @@
68
63
  </div>
69
64
  {/if}
70
65
 
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 {
66
+ <style>.infobar {
81
67
  flex: none;
82
68
  display: flex;
83
69
  align-items: center;
@@ -5,7 +5,7 @@ type Infobar = {
5
5
  };
6
6
  declare const Infobar: import("svelte").Component<{
7
7
  /**
8
- * Whether to show the toast.
8
+ * Whether to show the infobar.
9
9
  */
10
10
  show?: boolean | undefined;
11
11
  /**
@@ -38,7 +38,7 @@ declare const Infobar: import("svelte").Component<{
38
38
  }, {}, "show">;
39
39
  type Props = {
40
40
  /**
41
- * Whether to show the toast.
41
+ * Whether to show the infobar.
42
42
  */
43
43
  show?: boolean | undefined;
44
44
  /**
@@ -11,7 +11,7 @@
11
11
  import Popup from '../util/popup.svelte';
12
12
 
13
13
  /**
14
- * @import { ButtonProps, CommonEventHandlers } from '../../typedefs';
14
+ * @import { ButtonProps, CommonEventHandlers } from '../../typedefs.js';
15
15
  */
16
16
 
17
17
  /**
@@ -51,6 +51,14 @@
51
51
  } = $props();
52
52
  </script>
53
53
 
54
+ {#snippet labelText()}
55
+ <span role="none" class="label" dir={labelDir}>
56
+ <TruncatedText {lines}>
57
+ {label}
58
+ </TruncatedText>
59
+ </span>
60
+ {/snippet}
61
+
54
62
  <!--
55
63
  `aria-readonly` is only rendered when it applies: the attribute isn’t supported on the `button`
56
64
  role (or `option`, `tab` and `menuitem`, which also wrap this component), and Svelte would
@@ -86,11 +94,7 @@
86
94
  {@render startIcon?.()}
87
95
  {#if variant === 'link'}
88
96
  {#if label}
89
- <span role="none" class="label" dir={labelDir}>
90
- <TruncatedText {lines}>
91
- {label}
92
- </TruncatedText>
93
- </span>
97
+ {@render labelText()}
94
98
  {:else}
95
99
  <span role="none" class="label">
96
100
  {@render children?.()}
@@ -98,11 +102,7 @@
98
102
  {/if}
99
103
  {:else}
100
104
  {#if label}
101
- <span role="none" class="label" dir={labelDir}>
102
- <TruncatedText {lines}>
103
- {label}
104
- </TruncatedText>
105
- </span>
105
+ {@render labelText()}
106
106
  {/if}
107
107
  {@render children?.()}
108
108
  {/if}
@@ -8,5 +8,5 @@ type Button = {
8
8
  * @see https://w3c.github.io/aria/#button
9
9
  * @see https://www.w3.org/WAI/ARIA/apg/patterns/button/
10
10
  */
11
- declare const Button: import("svelte").Component<ButtonProps & import("../../typedefs").KeyboardEventHandlers & import("../../typedefs").MouseEventHandlers & import("../../typedefs").PointerEventHandlers & import("../../typedefs").FocusEventHandlers & import("../../typedefs").DragEventHandlers & Record<string, any>, {}, "element">;
12
- import type { ButtonProps } from '../../typedefs';
11
+ declare const Button: import("svelte").Component<ButtonProps & import("../../typedefs.js").KeyboardEventHandlers & import("../../typedefs.js").MouseEventHandlers & import("../../typedefs.js").PointerEventHandlers & import("../../typedefs.js").FocusEventHandlers & import("../../typedefs.js").DragEventHandlers & Record<string, any>, {}, "element">;
12
+ import type { ButtonProps } from '../../typedefs.js';
@@ -8,7 +8,7 @@
8
8
  import Button from './button.svelte';
9
9
 
10
10
  /**
11
- * @import { ButtonProps, CommonEventHandlers } from '../../typedefs';
11
+ * @import { ButtonProps, CommonEventHandlers } from '../../typedefs.js';
12
12
  */
13
13
 
14
14
  /**
@@ -8,7 +8,7 @@ type SelectButton = {
8
8
  * @see https://w3c.github.io/aria/#radio
9
9
  * @see https://www.w3.org/WAI/ARIA/apg/patterns/radio/
10
10
  */
11
- declare const SelectButton: import("svelte").Component<ButtonProps & import("../../typedefs").KeyboardEventHandlers & import("../../typedefs").MouseEventHandlers & import("../../typedefs").PointerEventHandlers & import("../../typedefs").FocusEventHandlers & import("../../typedefs").DragEventHandlers & {
11
+ declare const SelectButton: import("svelte").Component<ButtonProps & import("../../typedefs.js").KeyboardEventHandlers & import("../../typedefs.js").MouseEventHandlers & import("../../typedefs.js").PointerEventHandlers & import("../../typedefs.js").FocusEventHandlers & import("../../typedefs.js").DragEventHandlers & {
12
12
  /**
13
13
  * Whether to select the widget. An alias of the `aria-checked`
14
14
  * attribute. Because the UI is a button, we expose `selected` instead of `checked`.
@@ -22,4 +22,4 @@ type Props = {
22
22
  */
23
23
  selected?: boolean | undefined;
24
24
  };
25
- import type { ButtonProps } from '../../typedefs';
25
+ import type { ButtonProps } from '../../typedefs.js';
@@ -13,7 +13,7 @@
13
13
 
14
14
  /**
15
15
  * @import { Snippet } from 'svelte';
16
- * @import { ButtonProps, CommonEventHandlers, PopupPosition } from '../../typedefs';
16
+ * @import { ButtonProps, CommonEventHandlers, PopupPosition } from '../../typedefs.js';
17
17
  */
18
18
 
19
19
  /**
@@ -9,7 +9,7 @@ type SplitButton = {
9
9
  * Match Formatting and Keep Text Only options.
10
10
  * @see https://www.google.com/search?q=split+button&tbm=isch
11
11
  */
12
- declare const SplitButton: import("svelte").Component<ButtonProps & import("../../typedefs").KeyboardEventHandlers & import("../../typedefs").MouseEventHandlers & import("../../typedefs").PointerEventHandlers & import("../../typedefs").FocusEventHandlers & import("../../typedefs").DragEventHandlers & {
12
+ declare const SplitButton: import("svelte").Component<ButtonProps & import("../../typedefs.js").KeyboardEventHandlers & import("../../typedefs.js").MouseEventHandlers & import("../../typedefs.js").PointerEventHandlers & import("../../typedefs.js").FocusEventHandlers & import("../../typedefs.js").DragEventHandlers & {
13
13
  /**
14
14
  * Where to show the dropdown menu.
15
15
  */
@@ -45,6 +45,6 @@ type Props = {
45
45
  */
46
46
  popup?: Snippet<[]> | undefined;
47
47
  };
48
- import type { ButtonProps } from '../../typedefs';
49
- import type { PopupPosition } from '../../typedefs';
48
+ import type { ButtonProps } from '../../typedefs.js';
49
+ import type { PopupPosition } from '../../typedefs.js';
50
50
  import type { Snippet } from 'svelte';
@@ -12,6 +12,8 @@ export const DAY_GRID_SIZE: 42;
12
12
  export const MONTH_NAMES: string[];
13
13
  export function getFirstDayOfMonth(date: Date): Date;
14
14
  export function addMonths(firstDay: Date, delta: number): Date;
15
+ export function addDays(date: Date, delta: number): Date;
16
+ export function getArrowKeyDayOffset(key: string, rtl?: boolean): number | undefined;
15
17
  export function getCalendarDays(firstDay: Date): Date[];
16
18
  export function toDateString(date: Date): string;
17
19
  export function getToday(now?: Date): Date;
@@ -1,5 +1,7 @@
1
1
  import { date as formatLocaleDate } from '@sveltia/i18n';
2
2
 
3
+ import { getArrowKeys } from '../../services/navigation.js';
4
+
3
5
  /**
4
6
  * Date helpers behind `<Calendar>`, kept apart from the component so the grid can be tested without
5
7
  * rendering anything.
@@ -59,6 +61,38 @@ export const addMonths = (firstDay, delta) => {
59
61
  return date;
60
62
  };
61
63
 
64
+ /**
65
+ * Get another day, relative to the given one.
66
+ * @param {Date} date Any date.
67
+ * @param {number} delta Number of days to move by; negative to go back.
68
+ * @returns {Date} A new date, `delta` days away.
69
+ */
70
+ export const addDays = (date, delta) => {
71
+ const day = new Date(date);
72
+
73
+ day.setUTCDate(day.getUTCDate() + delta);
74
+
75
+ return day;
76
+ };
77
+
78
+ /**
79
+ * Get how many days an arrow key moves the cursor on the day grid: a day sideways, a week up or
80
+ * down. The inline arrows are mirrored for RTL.
81
+ * @param {string} key `KeyboardEvent.key` value.
82
+ * @param {boolean} [rtl] Whether the layout is right-to-left.
83
+ * @returns {number | undefined} Number of days, or `undefined` if the key is not an arrow key.
84
+ */
85
+ export const getArrowKeyDayOffset = (key, rtl = false) => {
86
+ const { prevKey, nextKey } = getArrowKeys('horizontal', rtl);
87
+
88
+ return /** @type {Record<string, number>} */ ({
89
+ ArrowUp: -7,
90
+ ArrowDown: 7,
91
+ [prevKey]: -1,
92
+ [nextKey]: 1,
93
+ })[key];
94
+ };
95
+
62
96
  /**
63
97
  * Get the days to be laid out in the calendar grid.
64
98
  * @param {Date} firstDay First day of the month being displayed.
@@ -11,8 +11,10 @@
11
11
  import Spacer from '../divider/spacer.svelte';
12
12
  import Icon from '../icon/icon.svelte';
13
13
  import {
14
+ addDays,
14
15
  addMonths,
15
16
  formatDate,
17
+ getArrowKeyDayOffset,
16
18
  getCalendarDays,
17
19
  getFirstDayOfMonth,
18
20
  getToday,
@@ -53,6 +55,46 @@
53
55
  /** @type {HTMLElement | undefined} */
54
56
  let grid = $state();
55
57
 
58
+ /**
59
+ * Move the keyboard cursor, and with it the selection, by date rather than by cell: the group
60
+ * service would step through the 42 cells on the grid and stop at its edges, whereas a week up
61
+ * from the first of the month is a day in the previous month, which may not be on the grid at
62
+ * all. The month displayed follows the new {@link value}.
63
+ * @param {KeyboardEvent} event `keydown` event.
64
+ */
65
+ const onGridKeyDown = (event) => {
66
+ const { key, shiftKey, altKey, ctrlKey, metaKey } = event;
67
+ const offset = getArrowKeyDayOffset(key, isRTL());
68
+
69
+ if (offset === undefined || shiftKey || altKey || ctrlKey || metaKey) {
70
+ return;
71
+ }
72
+
73
+ // Keep the group service, which listens on the same element, from moving the cursor as well
74
+ event.preventDefault();
75
+ event.stopImmediatePropagation();
76
+
77
+ const focused = /** @type {HTMLButtonElement | null | undefined} */ (
78
+ grid?.querySelector('[role="option"].focused')
79
+ );
80
+
81
+ // Without a cursor, start from the selected day only if it’s on the grid. After browsing to
82
+ // another month, it isn’t, and moving from it would bring that month back. Land on today if
83
+ // it’s in the month displayed, or on the first of the month otherwise.
84
+ const cursor =
85
+ focused?.value ||
86
+ (value && dayList.some(({ day }) => toDateString(day) === value) ? value : undefined);
87
+
88
+ if (cursor) {
89
+ value = toDateString(addDays(new Date(cursor), offset));
90
+ } else {
91
+ value =
92
+ getFirstDayOfMonth(today).getTime() === firstDay.getTime()
93
+ ? todayString
94
+ : toDateString(firstDay);
95
+ }
96
+ };
97
+
56
98
  // Keep the keyboard cursor on the selected day. The group service tracks the cursor with the
57
99
  // `focused` class and `aria-activedescendant`, and moves both along with the selection it makes
58
100
  // itself; this covers the starting point and a `value` set from outside, which may land in
@@ -107,7 +149,7 @@
107
149
  //
108
150
  }}
109
151
  >
110
- <Icon name="chevron_left" />
152
+ <Icon name={isRTL() ? 'chevron_right' : 'chevron_left'} />
111
153
  </Button>
112
154
  <Button
113
155
  aria-label={_('_sui.calendar.next_decade')}
@@ -145,7 +187,7 @@
145
187
  firstDay = addMonths(firstDay, -1);
146
188
  }}
147
189
  >
148
- <Icon name="chevron_left" />
190
+ <Icon name={isRTL() ? 'chevron_right' : 'chevron_left'} />
149
191
  </Button>
150
192
  <Button
151
193
  aria-label={_('_sui.calendar.next_month')}
@@ -157,9 +199,10 @@
157
199
  </Button>
158
200
  </div>
159
201
  <!--
160
- The day grid is a listbox laid out as a grid, so the group service moves through it with all
161
- four arrow keys. Each day is named by its full date: the digit alone means nothing out of
162
- context. The days are UTC-based, like the `value`, so the names are formatted the same way.
202
+ The day grid is a listbox laid out as a grid. The arrow keys move through it by date, handled
203
+ above; the group service still takes care of the rest, such as Home and End. Each day is named
204
+ by its full date: the digit alone means nothing out of context. The days are UTC-based, like
205
+ the `value`, so the names are formatted the same way.
163
206
  The cells are keyed by date string rather than by `Date` object, which is recreated on every
164
207
  update: a day that stays on the grid when the month changes — the last days of the previous
165
208
  month, say — keeps its element, and with it the keyboard cursor.
@@ -169,6 +212,7 @@
169
212
  role="listbox"
170
213
  class="grid"
171
214
  aria-label={monthLabel}
215
+ onkeydowncapture={onGridKeyDown}
172
216
  onChange={(/** @type {CustomEvent} */ event) => {
173
217
  value = event.detail.value;
174
218
  }}
@@ -11,7 +11,7 @@
11
11
 
12
12
  /**
13
13
  * @import { Snippet } from 'svelte';
14
- * @import { ButtonProps, CommonEventHandlers } from '../../typedefs';
14
+ * @import { ButtonProps, CommonEventHandlers } from '../../typedefs.js';
15
15
  */
16
16
 
17
17
  /**
@@ -63,6 +63,13 @@
63
63
  */
64
64
  let buttonElement = $state();
65
65
 
66
+ /**
67
+ * Accessible name given as a prop or as a plain `aria-label` attribute. It takes the place of the
68
+ * visible label, which the button otherwise points at — but only when there is one to point at.
69
+ * @type {string | undefined}
70
+ */
71
+ const accessibleLabel = $derived(ariaLabel ?? restProps['aria-label']);
72
+ const hasVisibleLabel = $derived(!!(children || label));
66
73
  const indeterminate = $derived(checked === 'mixed');
67
74
 
68
75
  // Sync `checked` with `group` and `value`
@@ -112,8 +119,8 @@
112
119
  aria-required={required}
113
120
  aria-invalid={invalid}
114
121
  aria-checked={checked}
115
- aria-label={ariaLabel || undefined}
116
- aria-labelledby={ariaLabel ? undefined : `${id}-label`}
122
+ aria-label={accessibleLabel || undefined}
123
+ aria-labelledby={!accessibleLabel && hasVisibleLabel ? `${id}-label` : undefined}
117
124
  onclick={(event) => {
118
125
  event.preventDefault();
119
126
  event.stopPropagation();
@@ -150,7 +157,7 @@
150
157
  {/if}
151
158
  {/snippet}
152
159
  </Button>
153
- {#if children || label}
160
+ {#if hasVisibleLabel}
154
161
  <label id="{id}-label">
155
162
  {#if children}
156
163
  {@render children()}
@@ -9,7 +9,7 @@ type Checkbox = {
9
9
  * @see https://w3c.github.io/aria/#checkbox
10
10
  * @see https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/
11
11
  */
12
- declare const Checkbox: import("svelte").Component<ButtonProps & import("../../typedefs").KeyboardEventHandlers & import("../../typedefs").MouseEventHandlers & import("../../typedefs").PointerEventHandlers & import("../../typedefs").FocusEventHandlers & import("../../typedefs").DragEventHandlers & {
12
+ declare const Checkbox: import("svelte").Component<ButtonProps & import("../../typedefs.js").KeyboardEventHandlers & import("../../typedefs.js").MouseEventHandlers & import("../../typedefs.js").PointerEventHandlers & import("../../typedefs.js").FocusEventHandlers & import("../../typedefs.js").DragEventHandlers & {
13
13
  /**
14
14
  * The `class` attribute on the wrapper element.
15
15
  */
@@ -87,5 +87,5 @@ type Props = {
87
87
  */
88
88
  group?: string[] | undefined;
89
89
  };
90
- import type { ButtonProps } from '../../typedefs';
90
+ import type { ButtonProps } from '../../typedefs.js';
91
91
  import type { Snippet } from 'svelte';
@@ -9,7 +9,7 @@
9
9
  import Dialog from './dialog.svelte';
10
10
 
11
11
  /**
12
- * @import { DialogProps, ModalProps } from '../../typedefs';
12
+ * @import { DialogProps, ModalProps } from '../../typedefs.js';
13
13
  */
14
14
 
15
15
  /**
@@ -10,5 +10,5 @@ type AlertDialog = {
10
10
  * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/alert
11
11
  */
12
12
  declare const AlertDialog: import("svelte").Component<ModalProps & DialogProps & Record<string, any>, {}, "open">;
13
- import type { ModalProps } from '../../typedefs';
14
- import type { DialogProps } from '../../typedefs';
13
+ import type { ModalProps } from '../../typedefs.js';
14
+ import type { DialogProps } from '../../typedefs.js';
@@ -9,7 +9,7 @@
9
9
  import Dialog from './dialog.svelte';
10
10
 
11
11
  /**
12
- * @import { DialogProps, ModalProps } from '../../typedefs';
12
+ * @import { DialogProps, ModalProps } from '../../typedefs.js';
13
13
  */
14
14
 
15
15
  /**
@@ -10,5 +10,5 @@ type ConfirmationDialog = {
10
10
  * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm
11
11
  */
12
12
  declare const ConfirmationDialog: import("svelte").Component<ModalProps & DialogProps & Record<string, any>, {}, "open">;
13
- import type { ModalProps } from '../../typedefs';
14
- import type { DialogProps } from '../../typedefs';
13
+ import type { ModalProps } from '../../typedefs.js';
14
+ import type { DialogProps } from '../../typedefs.js';