@widgetic/design-system 0.5.14 → 0.5.16

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 (155) hide show
  1. package/README.md +167 -0
  2. package/dist/components/CopyableHeader.svelte +25 -11
  3. package/dist/components/CopyableHeader.svelte.d.ts +4 -0
  4. package/dist/components/EmptyState.svelte +1 -1
  5. package/dist/components/PasswordInput.svelte +2 -2
  6. package/dist/components/WScrollArea.svelte +2 -2
  7. package/dist/components/ic/browser/BrowserInputController.svelte +2 -2
  8. package/dist/components/index.d.ts +1 -0
  9. package/dist/components/modals/ActionModal.svelte +7 -2
  10. package/dist/components/modals/ActionModal.svelte.d.ts +2 -0
  11. package/dist/components/modals/BaseModal.svelte +4 -2
  12. package/dist/components/modals/BaseModal.svelte.d.ts +1 -0
  13. package/dist/components/modals/NewItemDialog.svelte +48 -30
  14. package/dist/components/modals/NewItemDialog.svelte.d.ts +3 -0
  15. package/dist/components/modals/Portal.svelte +9 -1
  16. package/dist/components/modals/README.md +34 -1
  17. package/dist/components/modals/RenameDialog.svelte +29 -9
  18. package/dist/components/modals/RenameDialog.svelte.d.ts +3 -0
  19. package/dist/components/modals/messages.d.ts +39 -0
  20. package/dist/components/modals/messages.js +10 -0
  21. package/dist/components/navigation/Breadcrumbs.svelte +55 -50
  22. package/dist/components/navigation/Breadcrumbs.svelte.d.ts +4 -0
  23. package/dist/components/navigation/types.d.ts +8 -0
  24. package/dist/components/pdf/PDFViewer.svelte +17 -8
  25. package/dist/components/pdf/PDFViewer.svelte.d.ts +5 -0
  26. package/dist/components/search-input/SearchInput.svelte +3 -3
  27. package/dist/components/shad-editor/colorpicker.svelte +4 -1
  28. package/dist/components/shad-editor/custom/Extentions/ImageExtention.d.ts +20 -1
  29. package/dist/components/shad-editor/custom/Extentions/ImageExtention.js +4 -1
  30. package/dist/components/shad-editor/custom/code-extended.svelte +4 -2
  31. package/dist/components/shad-editor/custom/image-extended-component.svelte +54 -32
  32. package/dist/components/shad-editor/editor.css +5 -5
  33. package/dist/components/shad-editor/i18n/context.d.ts +6 -0
  34. package/dist/components/shad-editor/i18n/context.js +11 -0
  35. package/dist/components/shad-editor/i18n/index.d.ts +8 -0
  36. package/dist/components/shad-editor/i18n/index.js +11 -0
  37. package/dist/components/shad-editor/i18n/messages.d.ts +109 -0
  38. package/dist/components/shad-editor/i18n/messages.js +116 -0
  39. package/dist/components/shad-editor/icons/block-quote.svelte +3 -1
  40. package/dist/components/shad-editor/icons/bold.svelte +3 -1
  41. package/dist/components/shad-editor/icons/buttle-list.svelte +3 -1
  42. package/dist/components/shad-editor/icons/code.svelte +3 -1
  43. package/dist/components/shad-editor/icons/highlighter.svelte +5 -3
  44. package/dist/components/shad-editor/icons/image.svelte +7 -5
  45. package/dist/components/shad-editor/icons/italic.svelte +3 -1
  46. package/dist/components/shad-editor/icons/link.svelte +8 -6
  47. package/dist/components/shad-editor/icons/ordered-list.svelte +3 -1
  48. package/dist/components/shad-editor/icons/quickcolor.svelte +24 -22
  49. package/dist/components/shad-editor/icons/redo.svelte +3 -1
  50. package/dist/components/shad-editor/icons/search-replace.svelte +16 -14
  51. package/dist/components/shad-editor/icons/strikethrough.svelte +3 -1
  52. package/dist/components/shad-editor/icons/subscript.svelte +3 -1
  53. package/dist/components/shad-editor/icons/superscript.svelte +3 -1
  54. package/dist/components/shad-editor/icons/table.svelte +21 -19
  55. package/dist/components/shad-editor/icons/task-list.svelte +3 -1
  56. package/dist/components/shad-editor/icons/text.svelte +13 -11
  57. package/dist/components/shad-editor/icons/textalign.svelte +11 -9
  58. package/dist/components/shad-editor/icons/textcolor.svelte +5 -3
  59. package/dist/components/shad-editor/icons/underline.svelte +3 -1
  60. package/dist/components/shad-editor/icons/undo.svelte +3 -1
  61. package/dist/components/shad-editor/index.d.ts +1 -0
  62. package/dist/components/shad-editor/index.js +2 -0
  63. package/dist/components/shad-editor/shad-editor.svelte +32 -3
  64. package/dist/components/shad-editor/shad-editor.svelte.d.ts +12 -0
  65. package/dist/components/textDirection.d.ts +91 -0
  66. package/dist/components/textDirection.js +99 -0
  67. package/dist/components/toast/ToasterModal.svelte +49 -14
  68. package/dist/components/toast/ToasterModal.svelte.d.ts +9 -2
  69. package/dist/components/toast/toasterRoots.d.ts +23 -0
  70. package/dist/components/toast/toasterRoots.js +36 -0
  71. package/dist/components/ui/alert/index.d.ts +2 -2
  72. package/dist/components/ui/alert/index.js +1 -1
  73. package/dist/components/ui/breadcrumb/breadcrumb-separator.svelte +1 -1
  74. package/dist/components/ui/calendar/calendar-next-button.svelte +21 -4
  75. package/dist/components/ui/calendar/calendar-next-button.svelte.d.ts +9 -2
  76. package/dist/components/ui/calendar/calendar-prev-button.svelte +21 -4
  77. package/dist/components/ui/calendar/calendar-prev-button.svelte.d.ts +9 -2
  78. package/dist/components/ui/calendar/calendar.svelte +18 -6
  79. package/dist/components/ui/calendar/calendar.svelte.d.ts +6 -1
  80. package/dist/components/ui/command/command-input.svelte +1 -1
  81. package/dist/components/ui/command/command-shortcut.svelte +1 -1
  82. package/dist/components/ui/dialog/dialog-content.svelte +5 -2
  83. package/dist/components/ui/dialog/dialog-content.svelte.d.ts +1 -0
  84. package/dist/components/ui/dialog/dialog-header.svelte +1 -1
  85. package/dist/components/ui/drawer/drawer-header.svelte +1 -1
  86. package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +2 -2
  87. package/dist/components/ui/dropdown-menu/dropdown-menu-content.svelte +6 -0
  88. package/dist/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +1 -1
  89. package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte +1 -1
  90. package/dist/components/ui/dropdown-menu/dropdown-menu-label.svelte +1 -1
  91. package/dist/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +2 -2
  92. package/dist/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +1 -1
  93. package/dist/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +7 -1
  94. package/dist/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +2 -2
  95. package/dist/components/ui/hover-card/hover-card-content.svelte +6 -0
  96. package/dist/components/ui/input-otp/input-otp-slot.svelte +1 -1
  97. package/dist/components/ui/popover/popover-content.svelte +6 -0
  98. package/dist/components/ui/progress/progress.svelte +2 -2
  99. package/dist/components/ui/range-calendar/range-calendar-cell.svelte +1 -1
  100. package/dist/components/ui/range-calendar/range-calendar-next-button.svelte +21 -4
  101. package/dist/components/ui/range-calendar/range-calendar-next-button.svelte.d.ts +9 -2
  102. package/dist/components/ui/range-calendar/range-calendar-prev-button.svelte +21 -4
  103. package/dist/components/ui/range-calendar/range-calendar-prev-button.svelte.d.ts +9 -2
  104. package/dist/components/ui/range-calendar/range-calendar.svelte +18 -6
  105. package/dist/components/ui/range-calendar/range-calendar.svelte.d.ts +7 -2
  106. package/dist/components/ui/scroll-area/scroll-area-scrollbar.svelte +1 -1
  107. package/dist/components/ui/scroll-area/scroll-area.svelte +6 -1
  108. package/dist/components/ui/select/select-content.svelte +6 -0
  109. package/dist/components/ui/select/select-group-heading.svelte +1 -1
  110. package/dist/components/ui/select/select-item.svelte +2 -2
  111. package/dist/components/ui/slider/slider.svelte +6 -0
  112. package/dist/components/ui/switch/switch.svelte +1 -1
  113. package/dist/components/ui/table/table-cell.svelte +1 -1
  114. package/dist/components/ui/table/table-head.svelte +1 -1
  115. package/dist/components/ui/tooltip/tooltip-content.svelte +6 -0
  116. package/dist/i18n/audit/catalogUsage.d.ts +36 -0
  117. package/dist/i18n/audit/catalogUsage.js +58 -0
  118. package/dist/i18n/audit/designSystemWords.d.ts +47 -0
  119. package/dist/i18n/audit/designSystemWords.js +137 -0
  120. package/dist/i18n/audit/hardCodedText.d.ts +40 -0
  121. package/dist/i18n/audit/hardCodedText.js +27 -0
  122. package/dist/i18n/audit/index.d.ts +19 -0
  123. package/dist/i18n/audit/index.js +19 -0
  124. package/dist/i18n/audit/messageCalls.d.ts +34 -0
  125. package/dist/i18n/audit/messageCalls.js +89 -0
  126. package/dist/i18n/audit/syntaxTree.d.ts +33 -0
  127. package/dist/i18n/audit/syntaxTree.js +90 -0
  128. package/dist/i18n/audit/visibleText.d.ts +57 -0
  129. package/dist/i18n/audit/visibleText.js +252 -0
  130. package/dist/i18n/createI18n.d.ts +63 -0
  131. package/dist/i18n/createI18n.js +72 -0
  132. package/dist/i18n/direction.d.ts +27 -0
  133. package/dist/i18n/direction.js +89 -0
  134. package/dist/i18n/index.d.ts +17 -0
  135. package/dist/i18n/index.js +16 -0
  136. package/dist/i18n/localeDate.d.ts +34 -0
  137. package/dist/i18n/localeDate.js +104 -0
  138. package/dist/i18n/splitMessages.d.ts +21 -0
  139. package/dist/i18n/splitMessages.js +33 -0
  140. package/dist/i18n/translator.d.ts +61 -0
  141. package/dist/i18n/translator.js +337 -0
  142. package/dist/rtl/audit/auditPhysicalPlacement.d.ts +36 -0
  143. package/dist/rtl/audit/auditPhysicalPlacement.js +58 -0
  144. package/dist/rtl/audit/index.d.ts +14 -0
  145. package/dist/rtl/audit/index.js +14 -0
  146. package/dist/rtl/audit/physicalPlacement.d.ts +64 -0
  147. package/dist/rtl/audit/physicalPlacement.js +444 -0
  148. package/dist/shad-editor/index.d.ts +1 -1
  149. package/dist/shad-editor/index.js +1 -1
  150. package/dist/styles/global.css +4 -4
  151. package/dist/utils/fillMessage.d.ts +8 -0
  152. package/dist/utils/fillMessage.js +10 -0
  153. package/dist/utils/itemName.d.ts +17 -0
  154. package/dist/utils/itemName.js +41 -0
  155. package/package.json +19 -2
@@ -1,6 +1,8 @@
1
1
  <!-- Portal component for mounting elements outside their normal DOM hierarchy -->
2
2
  <script lang="ts">
3
3
  import { onMount, onDestroy } from 'svelte';
4
+ import { readable, type Readable } from 'svelte/store';
5
+ import { useOptionalTextDirection, type TextDirection } from '../textDirection.js';
4
6
 
5
7
  /**
6
8
  * Component props
@@ -26,6 +28,11 @@
26
28
  // show logs
27
29
  let showLogs = false;
28
30
 
31
+ // The content leaves the element that carries the direction of its screen, and a pop-up in a layer of the page would read
32
+ // the direction of the page instead. When a screen above gave one, the portal says it again on its own element (and says
33
+ // nothing on a page whose screens never did, so the page's own `dir` stays).
34
+ const screenDirection: Readable<TextDirection | undefined> = useOptionalTextDirection() ?? readable<TextDirection | undefined>(undefined);
35
+
29
36
  onMount(() => {
30
37
  // if(showLogs) console.log('DS Portal target:', target);
31
38
  // If target is an HTML element, use it directly
@@ -85,6 +92,7 @@
85
92
  <!-- Portal Container -->
86
93
  <div bind:this={portal}
87
94
  class="{className ? `${className} ` : ''}portal-contents{clickThrough ? ' click-through' : ''}"
95
+ dir={$screenDirection}
88
96
  style={style}
89
97
  bind:clientWidth
90
98
  bind:clientHeight
@@ -97,7 +105,7 @@
97
105
  .portal-contents {
98
106
  position: absolute;
99
107
  top: 0;
100
- left: 0;
108
+ inset-inline-start: 0;
101
109
  width: 100%;
102
110
  height: 100%;
103
111
  z-index: 99999999;
@@ -22,6 +22,7 @@ The `ActionModal` component is a reusable confirmation modal that can be used ac
22
22
  | `message` | string | `''` | Custom message for the modal (autogenerated if empty) |
23
23
  | `confirmLabel` | string | `'Confirm'` | Label for the confirm button |
24
24
  | `cancelLabel` | string | `'Cancel'` | Label for the cancel button |
25
+ | `closeLabel` | string | `'Close modal'` | The words a screen reader says for the close button |
25
26
  | `width` | string | `'410px'` | Width of the modal |
26
27
  | `height` | string | `'168px'` | Height of the modal |
27
28
  | `modalId` | string | `'delete-confirmation-modal'` | ID for targeting the modal |
@@ -96,4 +97,36 @@ You can provide custom title and message for specific contexts:
96
97
  confirmLabel="Delete Permanently"
97
98
  on:confirm={handleDelete}
98
99
  />
99
- ```
100
+ ```
101
+
102
+ ## Words in another language
103
+
104
+ Every word a visitor can see or hear is a prop with an English default, so a page that shows the components in
105
+ another language passes its own words and nothing else changes.
106
+
107
+ | Component | Props with words |
108
+ |-----------|------------------|
109
+ | `BaseModal`, `ActionModal`, `RenameDialog`, `NewItemDialog` | `closeLabel` (screen reader name of the close button) |
110
+ | `ActionModal`, `RenameDialog`, `NewItemDialog` | `confirmLabel`, `cancelLabel` |
111
+ | `RenameDialog` | `messages`: `invalidTitle`, `enterName`, `enterDifferentName`, `enterValidName`, `tooShort`, `tooLong` (`{max}`), `renamed` (`{name}`), `failed` |
112
+ | `NewItemDialog` | `messages`: `invalidTitle`, `required`, `invalidCharacters`, `tooLong` (`{max}`). The English defaults are built from `itemType` ("Folder name is required"). |
113
+ | `Breadcrumbs` | `backLabel`, `forwardLabel`, `rootLabel`, `breadcrumbLabel` |
114
+ | `PDFViewer` | `loadingLabel`, `failedLabel`, `pageLabel` (`{page}`, `{total}`), `previousPageLabel`, `nextPageLabel` |
115
+ | `ToasterModal` (`components/toast`) | `containerAriaLabel` (the region that holds the toasts, "Notifications"; the library adds the key that moves focus to it), `closeButtonAriaLabel` ("Close toast"). The title and description of a toast are the words of whoever calls `showToast`. |
116
+
117
+ A key of `messages` that is left out keeps its English text. A message can hold the values named next to it in
118
+ curly braces: `tooLong: 'Der Name darf höchstens {max} Zeichen haben'`.
119
+
120
+ ```svelte
121
+ <RenameDialog
122
+ bind:isOpen
123
+ itemName="Reisefotos"
124
+ confirmLabel="Umbenennen"
125
+ cancelLabel="Abbrechen"
126
+ closeLabel="Dialog schließen"
127
+ messages={{ enterName: 'Bitte einen Namen eingeben', tooLong: 'Höchstens {max} Zeichen' }}
128
+ />
129
+ ```
130
+
131
+ `NewItemDialog` accepts names made of letters, numbers and spaces **of any script** ("Ordner für Müller",
132
+ "写真", "Фото 2024"). Punctuation and symbols are still refused.
@@ -7,6 +7,9 @@
7
7
  // import { toast } from '../ui/sonner';
8
8
  import { showToast } from '../toast';
9
9
  import BaseModal from './BaseModal.svelte';
10
+ // Words of the dialog and the helper that puts values into them
11
+ import type { RenameDialogMessages } from './messages';
12
+ import { fillMessage } from '../../utils/fillMessage';
10
13
 
11
14
  // Props
12
15
  let className = '';
@@ -21,6 +24,10 @@
21
24
  export let placeholder = '';
22
25
  export let confirmLabel = 'Rename';
23
26
  export let cancelLabel = 'Cancel';
27
+ // The words a screen reader says for the close button (see BaseModal)
28
+ export let closeLabel = 'Close modal';
29
+ // The words of the errors and toasts; a key that is left out keeps its English text (see ./messages)
30
+ export let messages: Partial<RenameDialogMessages> = {};
24
31
  export let width = '410px';
25
32
  export let height = '168px';
26
33
  export let target: string = 'rename-dialog'; // target for Portal
@@ -59,6 +66,18 @@
59
66
  // Computed placeholder based on itemType
60
67
  $: computedPlaceholder = placeholder || `${itemType} name...`;
61
68
 
69
+ // The words of the errors and toasts: the page's own, or English
70
+ $: text = {
71
+ invalidTitle: messages.invalidTitle ?? 'Invalid name',
72
+ enterName: messages.enterName ?? 'Please enter a name',
73
+ enterDifferentName: messages.enterDifferentName ?? 'Please enter a different name',
74
+ enterValidName: messages.enterValidName ?? 'Please enter a valid name',
75
+ tooShort: messages.tooShort ?? 'Name must be at least 1 character long',
76
+ tooLong: fillMessage(messages.tooLong ?? 'Name must be {max} characters or less', { max: maxLength }),
77
+ renamed: messages.renamed ?? 'Successfully renamed to {name}',
78
+ failed: messages.failed ?? 'Failed to rename item'
79
+ };
80
+
62
81
  // Function to focus and select the input text
63
82
  async function focusAndSelectInput() {
64
83
  // Wait for the DOM to update
@@ -95,17 +114,17 @@
95
114
  try {
96
115
  // Validate input
97
116
  if (!inputValue.trim() || inputValue.length < 1) {
98
- showToast('error', 'Invalid name', { description: 'Name must be at least 1 character long' });
117
+ showToast('error', text.invalidTitle, { description: text.tooShort });
99
118
  return;
100
119
  }
101
120
 
102
121
  if (inputValue.length > maxLength) {
103
- showToast('error', 'Invalid name', { description: `Name must be ${maxLength} characters or less` });
122
+ showToast('error', text.invalidTitle, { description: text.tooLong });
104
123
  return;
105
124
  }
106
125
 
107
126
  if (inputValue === itemName) {
108
- showToast('error', 'Invalid name', { description: 'Please enter a different name' });
127
+ showToast('error', text.invalidTitle, { description: text.enterDifferentName });
109
128
  return;
110
129
  }
111
130
 
@@ -124,17 +143,17 @@
124
143
  // with a more generic approach
125
144
  try {
126
145
  // Simulate success
127
- showToast('success', `Successfully renamed to ${inputValue}`);
146
+ showToast('success', fillMessage(text.renamed, { name: inputValue }));
128
147
  dispatch('rename', { itemId, newName: inputValue, oldName: itemName });
129
148
  isOpen = false;
130
149
  } catch (error) {
131
150
  console.error('Error during rename:', error);
132
- showToast('error', 'Failed to rename item');
151
+ showToast('error', text.failed);
133
152
  }
134
153
  }
135
154
  } catch (error) {
136
155
  console.error('Error during rename:', error);
137
- showToast('error', 'Failed to rename item');
156
+ showToast('error', text.failed);
138
157
  }
139
158
  }
140
159
 
@@ -172,6 +191,7 @@
172
191
  width={width}
173
192
  height={height}
174
193
  titlePadding="small"
194
+ {closeLabel}
175
195
  {modalType}
176
196
  {modalData}
177
197
  on:restore={handleRestore}
@@ -224,9 +244,9 @@
224
244
  />
225
245
  {#if !isValid && hasAttemptedSubmit}
226
246
  <div class="text-xs text-[hsl(var(--error))] mt-1">
227
- {inputValue.trim().length === 0 ? 'Please enter a name' :
228
- inputValue === itemName ? 'Please enter a different name' :
229
- 'Please enter a valid name'}
247
+ {inputValue.trim().length === 0 ? text.enterName :
248
+ inputValue === itemName ? text.enterDifferentName :
249
+ text.enterValidName}
230
250
  </div>
231
251
  {/if}
232
252
  </div>
@@ -1,3 +1,4 @@
1
+ import type { RenameDialogMessages } from './messages';
1
2
  interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
3
  new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
4
  $$bindings?: Bindings;
@@ -22,6 +23,8 @@ declare const RenameDialog: $$__sveltets_2_IsomorphicComponent<{
22
23
  placeholder?: string;
23
24
  confirmLabel?: string;
24
25
  cancelLabel?: string;
26
+ closeLabel?: string;
27
+ messages?: Partial<RenameDialogMessages>;
25
28
  width?: string;
26
29
  height?: string;
27
30
  target?: string;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The words the dialogs say besides their title, placeholder and button labels.
3
+ *
4
+ * Every key is optional: a key that is left out keeps its English text. A message can hold named values in curly
5
+ * braces, for example `Name must be {max} characters or less`; the dialog says which ones it fills in (see
6
+ * `fillMessage` in utils).
7
+ * A page that shows the dialogs in another language passes its own words, for example
8
+ * `<RenameDialog messages={{ enterName: 'Bitte einen Namen eingeben' }} />`.
9
+ */
10
+ /** Words of RenameDialog. */
11
+ export interface RenameDialogMessages {
12
+ /** Heading of the toast shown for a name that cannot be used. */
13
+ invalidTitle: string;
14
+ /** Under the field when it is empty. */
15
+ enterName: string;
16
+ /** Under the field and in the toast when the name has not changed. */
17
+ enterDifferentName: string;
18
+ /** Under the field when the name cannot be used for another reason. */
19
+ enterValidName: string;
20
+ /** Toast text for an empty name. */
21
+ tooShort: string;
22
+ /** Toast text for a name that is too long. Values: `{max}`, the longest name allowed. */
23
+ tooLong: string;
24
+ /** Toast when the dialog did the rename itself. Values: `{name}`, the new name. */
25
+ renamed: string;
26
+ /** Toast when the dialog did the rename itself and it failed. */
27
+ failed: string;
28
+ }
29
+ /** Words of NewItemDialog. The English defaults are built from the dialog's `itemType` ("Folder name is required"). */
30
+ export interface NewItemDialogMessages {
31
+ /** Heading of the toast shown for a name that cannot be used. */
32
+ invalidTitle: string;
33
+ /** Under the field when it is empty. */
34
+ required: string;
35
+ /** Under the field when the name has a character the dialog does not accept. */
36
+ invalidCharacters: string;
37
+ /** Under the field and in the toast when the name is too long. Values: `{max}`, the longest name allowed. */
38
+ tooLong: string;
39
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The words the dialogs say besides their title, placeholder and button labels.
3
+ *
4
+ * Every key is optional: a key that is left out keeps its English text. A message can hold named values in curly
5
+ * braces, for example `Name must be {max} characters or less`; the dialog says which ones it fills in (see
6
+ * `fillMessage` in utils).
7
+ * A page that shows the dialogs in another language passes its own words, for example
8
+ * `<RenameDialog messages={{ enterName: 'Bitte einen Namen eingeben' }} />`.
9
+ */
10
+ export {};
@@ -12,7 +12,8 @@
12
12
  import { IconArrowLeft, IconArrowRight } from '@tabler/icons-svelte';
13
13
 
14
14
  // Types Import
15
- import type { BreadcrumbsProps } from './types.ts';
15
+ import type { BreadcrumbsProps } from './types.js';
16
+ import { dirForUserText, useTextDirection } from '../textDirection.js';
16
17
 
17
18
  // Environment Import
18
19
  // import { browser } from '$app/environment';
@@ -36,6 +37,11 @@
36
37
  export let showNavigationButtons: BreadcrumbsProps['showNavigationButtons'] = true;
37
38
  export let canGoBack: BreadcrumbsProps['canGoBack'] = false;
38
39
  export let canGoForward: BreadcrumbsProps['canGoForward'] = false;
40
+ // words (tooltips and screen readers): a page that shows this in another language passes its own
41
+ export let backLabel: string = 'Back';
42
+ export let forwardLabel: string = 'Forward';
43
+ export let rootLabel: string = 'File browser';
44
+ export let breadcrumbLabel: string = 'breadcrumb';
39
45
  // event handlers
40
46
  export let onNavigate: BreadcrumbsProps['onNavigate'] = () => {};
41
47
  export let onBack: BreadcrumbsProps['onBack'] = () => {};
@@ -43,6 +49,11 @@
43
49
  // debug settings
44
50
  export let showDebugVisualizer: boolean = false;
45
51
 
52
+ // A name a visitor wrote keeps a direction of its own on a right to left screen (a Latin folder name reads left to
53
+ // right inside it); on a left to right screen no attribute is written and nothing changes.
54
+ const textDirection = useTextDirection();
55
+ $: nameDirection = dirForUserText($textDirection);
56
+
46
57
  /***
47
58
  * COMPONENT STATE VARIABLES
48
59
  **/
@@ -99,7 +110,7 @@
99
110
  navButtons: showNavigationButtons
100
111
  ? navElement.querySelector('.nav-buttons')?.getBoundingClientRect()
101
112
  : null,
102
- rightContent: containerElement.querySelector('.pr-5, .right-content')?.getBoundingClientRect(),
113
+ rightContent: containerElement.querySelector('.pe-5, .pr-5, .right-content')?.getBoundingClientRect(),
103
114
  breadcrumbs: breadcrumbsContainer.getBoundingClientRect()
104
115
  };
105
116
 
@@ -179,7 +190,7 @@
179
190
  <nav
180
191
  bind:this={navElement}
181
192
  bind:clientHeight
182
- aria-label="breadcrumb"
193
+ aria-label={breadcrumbLabel}
183
194
  class="{className ? className + ' ' : ''}breadcrumbs-component flex items-center widgetic-colors h-[56px] bg-white border-b relative w-full"
184
195
  style={style}
185
196
  >
@@ -188,58 +199,52 @@
188
199
  <Tooltip.Provider delayDuration={300}>
189
200
  <div class="nav-buttons flex items-center shrink-0">
190
201
  <Tooltip.Root open={showBackTooltip}>
191
- <Tooltip.Trigger class="inline-flex focus:outline-none">
192
- <!-- svelte-ignore a11y-click-events-have-key-events -->
193
- <div
194
- class="breadcrumbs-nav-button flex items-center justify-center w-6 h-6"
195
- style="cursor: {canGoBack && !isLoading ? 'pointer' : 'default'};"
196
- class:opacity-50={!canGoBack || isLoading}
197
- on:mouseenter={() => { showBackTooltip = true; }}
198
- on:mouseleave={() => { showBackTooltip = false; }}
199
- on:click={canGoBack && !isLoading ? onBack : undefined}
200
- role="button"
201
- tabindex={canGoBack && !isLoading ? 0 : -1}
202
- >
203
- <IconArrowLeft
204
- class="w-6 h-6 text-[hsl(var(--grey6))]"
205
- style="min-width: 24px; min-height: 24px;"
206
- />
207
- </div>
208
- </Tooltip.Trigger>
202
+ <button
203
+ type="button"
204
+ class="breadcrumbs-nav-button inline-flex items-center justify-center w-6 h-6 border-0 bg-transparent p-0 focus:outline-none"
205
+ style="cursor: {canGoBack && !isLoading ? 'pointer' : 'default'}; opacity: {canGoBack && !isLoading ? 1 : 0.35};"
206
+ aria-label={backLabel}
207
+ aria-disabled={!canGoBack || isLoading}
208
+ on:mouseenter={() => { showBackTooltip = true; }}
209
+ on:mouseleave={() => { showBackTooltip = false; }}
210
+ on:click={() => { if (!isLoading) onBack(); }}
211
+ >
212
+ <IconArrowLeft
213
+ class="w-6 h-6 text-[hsl(var(--grey6))] rtl:-scale-x-100"
214
+ style="min-width: 24px; min-height: 24px;"
215
+ />
216
+ </button>
209
217
  {#if showBackTooltip}
210
218
  <!-- Portaled to <body>: the header row around this nav is overflow-hidden, which
211
219
  otherwise clips the tooltip at the header's bottom edge. -->
212
220
  <Tooltip.Portal>
213
221
  <Tooltip.Content side="bottom" sideOffset={6} class="bg-[hsl(var(--dark))] text-white caption px-2 py-1 rounded-md z-[200000]">
214
- Back
222
+ {backLabel}
215
223
  </Tooltip.Content>
216
224
  </Tooltip.Portal>
217
225
  {/if}
218
226
  </Tooltip.Root>
219
227
 
220
228
  <Tooltip.Root open={showForwardTooltip}>
221
- <Tooltip.Trigger class="inline-flex focus:outline-none">
222
- <!-- svelte-ignore a11y-click-events-have-key-events -->
223
- <div
224
- class="breadcrumbs-nav-button flex items-center justify-center w-6 h-6 ml-4"
225
- style="cursor: {canGoForward && !isLoading ? 'pointer' : 'default'};"
226
- class:opacity-50={!canGoForward || isLoading}
227
- on:mouseenter={() => { showForwardTooltip = true; }}
228
- on:mouseleave={() => { showForwardTooltip = false; }}
229
- on:click={canGoForward && !isLoading ? onForward : undefined}
230
- role="button"
231
- tabindex={canGoForward && !isLoading ? 0 : -1}
232
- >
233
- <IconArrowRight
234
- class="w-6 h-6 text-[hsl(var(--grey6))]"
235
- style="min-width: 24px; min-height: 24px;"
236
- />
237
- </div>
238
- </Tooltip.Trigger>
229
+ <button
230
+ type="button"
231
+ class="breadcrumbs-nav-button ms-4 inline-flex items-center justify-center w-6 h-6 border-0 bg-transparent p-0 focus:outline-none"
232
+ style="cursor: {canGoForward && !isLoading ? 'pointer' : 'default'}; opacity: {canGoForward && !isLoading ? 1 : 0.35};"
233
+ aria-label={forwardLabel}
234
+ aria-disabled={!canGoForward || isLoading}
235
+ on:mouseenter={() => { showForwardTooltip = true; }}
236
+ on:mouseleave={() => { showForwardTooltip = false; }}
237
+ on:click={() => { if (!isLoading) onForward(); }}
238
+ >
239
+ <IconArrowRight
240
+ class="w-6 h-6 text-[hsl(var(--grey6))] rtl:-scale-x-100"
241
+ style="min-width: 24px; min-height: 24px;"
242
+ />
243
+ </button>
239
244
  {#if showForwardTooltip}
240
245
  <Tooltip.Portal>
241
246
  <Tooltip.Content side="bottom" sideOffset={6} class="bg-[hsl(var(--dark))] text-white caption px-2 py-1 rounded-md z-[200000]">
242
- Forward
247
+ {forwardLabel}
243
248
  </Tooltip.Content>
244
249
  </Tooltip.Portal>
245
250
  {/if}
@@ -249,8 +254,8 @@
249
254
  {/if}
250
255
 
251
256
  <!-- BREADCRUMBS CONTAINER -->
252
- <div bind:this={breadcrumbsContainer} class="flex-1 pl-[18px] overflow-hidden min-w-0">
253
- <Breadcrumb.Root>
257
+ <div bind:this={breadcrumbsContainer} class="flex-1 ps-[18px] overflow-hidden min-w-0">
258
+ <Breadcrumb.Root aria-label={breadcrumbLabel}>
254
259
  <Breadcrumb.List class="inline-flex items-center min-w-0 breadcrumbs-list">
255
260
  <div class="inline-flex items-center min-w-0 min-h-[24px]">
256
261
  <!-- Root Breadcrumb Item -->
@@ -265,7 +270,7 @@
265
270
  on:click={(e) => handleClick(e, -1)}
266
271
  on:keydown={(e) => handleKeyDown(e, -1)}
267
272
  >
268
- File browser
273
+ {rootLabel}
269
274
  </span>
270
275
  </Breadcrumb.Item>
271
276
 
@@ -285,7 +290,7 @@
285
290
  {#each currentPath.slice(0, -itemsToShow + 1) as _, index}
286
291
  <DropdownMenu.Item>
287
292
  <!-- svelte-ignore a11y-click-events-have-key-events -->
288
- <span
293
+ <span dir={nameDirection}
289
294
  role="link"
290
295
  tabindex="0"
291
296
  class="block w-full cursor-pointer"
@@ -304,12 +309,12 @@
304
309
  <Breadcrumb.Separator class="h5 text-[hsl(var(--grey6))] shrink-0">/</Breadcrumb.Separator>
305
310
  <Breadcrumb.Item class="shrink-0">
306
311
  {#if index === itemsToShow - 2}
307
- <span class="h5 text-[hsl(var(--grey3))]">
312
+ <span dir={nameDirection} class="h5 text-[hsl(var(--grey3))]">
308
313
  {currentPathNames[currentPath.length - 1]}
309
314
  </span>
310
315
  {:else}
311
316
  <!-- svelte-ignore a11y-click-events-have-key-events -->
312
- <span
317
+ <span dir={nameDirection}
313
318
  role="link"
314
319
  tabindex="0"
315
320
  class="h5 text-[hsl(var(--grey6))] transition-colors hover:text-[hsl(var(--grey7))] hover:underline hover:decoration-[hsl(var(--grey7))] cursor-pointer"
@@ -328,12 +333,12 @@
328
333
  <Breadcrumb.Separator class="h5 text-[hsl(var(--grey6))] shrink-0">/</Breadcrumb.Separator>
329
334
  <Breadcrumb.Item class="shrink-0">
330
335
  {#if index === currentPath.length - 1}
331
- <span class="h5 text-[hsl(var(--grey3))]">
336
+ <span dir={nameDirection} class="h5 text-[hsl(var(--grey3))]">
332
337
  {currentPathNames[index]}
333
338
  </span>
334
339
  {:else}
335
340
  <!-- svelte-ignore a11y-click-events-have-key-events -->
336
- <span
341
+ <span dir={nameDirection}
337
342
  role="link"
338
343
  tabindex="0"
339
344
  class="h5 text-[hsl(var(--grey6))] transition-colors hover:text-[hsl(var(--grey7))] hover:underline hover:decoration-[hsl(var(--grey7))] cursor-pointer"
@@ -355,7 +360,7 @@
355
360
  </div>
356
361
 
357
362
  {#if showDebugVisualizer}
358
- <div class="absolute bottom-1 right-1 text-xs text-blue-600 bg-white px-1 rounded shadow-2xs">
363
+ <div class="absolute bottom-1 end-1 text-xs text-blue-600 bg-white px-1 rounded shadow-2xs">
359
364
  Available width: {Math.round(availableWidth)}px
360
365
  <br>
361
366
  Items to show: {itemsToShow}
@@ -24,6 +24,10 @@ declare const Breadcrumbs: $$__sveltets_2_IsomorphicComponent<{
24
24
  showNavigationButtons?: BreadcrumbsProps["showNavigationButtons"];
25
25
  canGoBack?: BreadcrumbsProps["canGoBack"];
26
26
  canGoForward?: BreadcrumbsProps["canGoForward"];
27
+ backLabel?: string;
28
+ forwardLabel?: string;
29
+ rootLabel?: string;
30
+ breadcrumbLabel?: string;
27
31
  onNavigate?: BreadcrumbsProps["onNavigate"];
28
32
  onBack?: BreadcrumbsProps["onBack"];
29
33
  onForward?: BreadcrumbsProps["onForward"];
@@ -9,4 +9,12 @@ export interface BreadcrumbsProps {
9
9
  onForward: () => void;
10
10
  showNavigationButtons?: boolean;
11
11
  showDebugVisualizer?: boolean;
12
+ /** Tooltip and screen reader name of the back button. Default: "Back". */
13
+ backLabel?: string;
14
+ /** Tooltip and screen reader name of the forward button. Default: "Forward". */
15
+ forwardLabel?: string;
16
+ /** Name of the first item of the path. Default: "File browser". */
17
+ rootLabel?: string;
18
+ /** Screen reader name of the navigation. Default: "breadcrumb". */
19
+ breadcrumbLabel?: string;
12
20
  }
@@ -43,6 +43,9 @@
43
43
 
44
44
  // Import PDF cache store
45
45
  import { pdfCache } from '../../stores/pdfCache';
46
+
47
+ // Puts the page numbers into the words of the page counter
48
+ import { fillMessage } from '../../utils/fillMessage';
46
49
 
47
50
  // Component props
48
51
  // class and style props
@@ -52,6 +55,12 @@
52
55
  // data props
53
56
  export let url = '';
54
57
  export let enableCacheClearing: boolean = true; // Flag to enable/disable cache clearing shortcut
58
+ // words: a page that shows the viewer in another language passes its own
59
+ export let loadingLabel: string = 'Loading PDF...';
60
+ export let failedLabel: string = 'Failed to load PDF';
61
+ export let pageLabel: string = 'Page {page} of {total}'; // {total} is "?" until the page count is known
62
+ export let previousPageLabel: string = 'Previous page';
63
+ export let nextPageLabel: string = 'Next page';
55
64
 
56
65
  // Consts
57
66
  const initialContainerWidth = 500; // Default width during loading
@@ -531,11 +540,11 @@
531
540
  <!-- Always render loading or error state on top of content -->
532
541
  {#if !mounted || loading || (!isCanvasReady && !error)}
533
542
  <div class="text-[hsl(var(--grey5))] p-6 absolute z-10 widgetic-colors flex items-center justify-center inset-0">
534
- Loading PDF...
543
+ {loadingLabel}
535
544
  </div>
536
545
  {:else if error}
537
546
  <div class="pdf-error-overlay bg-[hsl(var(--grey1))] text-[hsl(var(--grey2))] inset-4 px-4 absolute z-10 flex flex-col items-center justify-center">
538
- <p class="text-[hsl(var(--error))] mb-2">Failed to load PDF</p>
547
+ <p class="text-[hsl(var(--error))] mb-2">{failedLabel}</p>
539
548
  <p class="text-[hsl(var(--grey5))] text-sm">{error}</p>
540
549
  </div>
541
550
  {/if}
@@ -546,18 +555,18 @@
546
555
  <!-- Page Navigation Bar -->
547
556
  <div class="page-navigation-bar flex items-center justify-center h-[54px] widgetic-colors border-b border-[hsl(var(--grey2))] w-full">
548
557
  <div class="flex items-center gap-3 w-full max-w-[200px] justify-between px-0">
549
- <button class="pdf-nav-button h-9 w-9 flex items-center justify-center rounded-small bg-white border-[hsl(var(--grey2))] transition-colors group disabled:cursor-not-allowed" on:click={onPrevPage} disabled={pageNum <= 1}>
558
+ <button class="pdf-nav-button h-9 w-9 flex items-center justify-center rounded-small bg-white border-[hsl(var(--grey2))] transition-colors group disabled:cursor-not-allowed" on:click={onPrevPage} disabled={pageNum <= 1} aria-label={previousPageLabel}>
550
559
  <IconChevronLeft
551
- class="!text-[hsl(var(--grey5))] group-hover:!text-[hsl(var(--grey7))]"
560
+ class="!text-[hsl(var(--grey5))] group-hover:!text-[hsl(var(--grey7))] rtl:-scale-x-100"
552
561
  style="height: 20px !important; min-width: 20px !important; max-width: 20px !important; max-height: 20px !important;"
553
562
  />
554
563
  </button>
555
564
 
556
- <span class="subtext text-[hsl(var(--grey7))]">Page {pageNum} of {pageCount || '?'}</span>
565
+ <span class="subtext text-[hsl(var(--grey7))]">{fillMessage(pageLabel, { page: pageNum, total: pageCount || '?' })}</span>
557
566
 
558
- <button class="pdf-nav-button h-9 w-9 flex items-center justify-center rounded-small bg-white border-[hsl(var(--grey2))] transition-colors group disabled:cursor-not-allowed" on:click={onNextPage} disabled={pageNum >= pageCount}>
567
+ <button class="pdf-nav-button h-9 w-9 flex items-center justify-center rounded-small bg-white border-[hsl(var(--grey2))] transition-colors group disabled:cursor-not-allowed" on:click={onNextPage} disabled={pageNum >= pageCount} aria-label={nextPageLabel}>
559
568
  <IconChevronRight
560
- class="!text-[hsl(var(--grey5))] group-hover:!text-[hsl(var(--grey7))]"
569
+ class="!text-[hsl(var(--grey5))] group-hover:!text-[hsl(var(--grey7))] rtl:-scale-x-100"
561
570
  style="height: 20px !important; min-width: 20px !important; max-width: 20px !important; max-height: 20px !important;"
562
571
  />
563
572
  </button>
@@ -591,7 +600,7 @@
591
600
  content: '';
592
601
  position: absolute;
593
602
  top: 0;
594
- left: 0;
603
+ inset-inline-start: 0;
595
604
  width: 100%;
596
605
  height: 100%;
597
606
  border: 1px solid hsl(var(--grey2));
@@ -16,6 +16,11 @@ declare const PDFViewer: $$__sveltets_2_IsomorphicComponent<{
16
16
  style?: string;
17
17
  url?: string;
18
18
  enableCacheClearing?: boolean;
19
+ loadingLabel?: string;
20
+ failedLabel?: string;
21
+ pageLabel?: string;
22
+ previousPageLabel?: string;
23
+ nextPageLabel?: string;
19
24
  }, {
20
25
  error: CustomEvent<{
21
26
  message: string;
@@ -62,7 +62,7 @@
62
62
  <div class="group relative w-full">
63
63
  <div class="relative overflow-hidden rounded-small border border-[hsl(var(--grey2))] bg-[hsl(var(--grey1))] transition-colors duration-200 hover:border-[hsl(var(--grey2))]">
64
64
  <div class="relative w-full flex items-center">
65
- <div class="absolute left-2 flex items-center pointer-events-none">
65
+ <div class="absolute start-2 flex items-center pointer-events-none">
66
66
  <IconSearch class="h-5 w-5 text-[hsl(var(--grey5))] transition-colors duration-300 {value ? 'text-[hsl(var(--grey7))]' : 'group-hover:text-[hsl(var(--grey7))] group-focus-within:text-[hsl(var(--grey7))]'}" />
67
67
  </div>
68
68
  <input
@@ -79,7 +79,7 @@
79
79
  onKeydown(e);
80
80
  }}
81
81
  onpaste={onPaste}
82
- class="w-full h-[38px] border-0 bg-transparent focus:outline-hidden focus:ring-0 focus-visible:ring-0 pl-9 pr-8 subtext
82
+ class="w-full h-[38px] border-0 bg-transparent focus:outline-hidden focus:ring-0 focus-visible:ring-0 ps-9 pe-8 subtext
83
83
  transition-all duration-300
84
84
  {value ? 'text-[hsl(var(--grey7))]' : 'text-[hsl(var(--grey3))] placeholder:text-[hsl(var(--grey3))] placeholder:transition-colors placeholder:duration-300 group-hover:text-[hsl(var(--grey7))] group-hover:placeholder:text-[hsl(var(--grey7))] group-focus-within:text-[hsl(var(--grey7))] group-focus-within:placeholder:text-[hsl(var(--grey7)))'}
85
85
  {inputClass}"
@@ -87,7 +87,7 @@
87
87
  {#if value}
88
88
  <button
89
89
  type="button"
90
- class="absolute right-2 h-6 w-6 inline-flex items-center justify-center rounded-full hover:bg-[hsl(var(--grey2))] transition-all duration-300 widgetic-colors"
90
+ class="absolute end-2 h-6 w-6 inline-flex items-center justify-center rounded-full hover:bg-[hsl(var(--grey2))] transition-all duration-300 widgetic-colors"
91
91
  onclick={onClear}
92
92
  >
93
93
  <X class="h-4 w-4 text-[hsl(var(--grey7))] transition-colors duration-300 hover:text-[hsl(var(--grey7))] widgetic-colors" />
@@ -7,13 +7,16 @@
7
7
  import { mode } from 'mode-watcher';
8
8
  let { hex = $bindable() }: ColorpickerProps = $props();
9
9
  import ColorPicker from 'svelte-awesome-color-picker';
10
+ import { useI18n } from './i18n/context.js';
11
+
12
+ const { t } = useI18n();
10
13
  </script>
11
14
 
12
15
  <div class:dark={$mode === 'dark'}>
13
16
  <ColorPicker
14
17
  bind:hex
15
18
  sliderDirection="vertical"
16
- label="Pick a color"
19
+ label={$t('shadEditor.colorPicker.label')}
17
20
  isTextInput={false}
18
21
  isAlpha
19
22
  --picker-indicator-size="1rem"
@@ -1 +1,20 @@
1
- export declare const ImageExtension: import("@tiptap/core").Node<import("@tiptap/extension-image").ImageOptions, any>;
1
+ import { type ImageOptions } from '@tiptap/extension-image';
2
+ import type { Readable } from 'svelte/store';
3
+ import type { Translate } from '../../../../i18n/index.js';
4
+ import type { ShadEditorMessageKey } from '../../i18n/messages.js';
5
+ import type { TextDirection } from '../../../textDirection.js';
6
+ export interface ImageExtensionOptions {
7
+ /**
8
+ * The words of the editor (`language.t` of the editor component). A node view is mounted by TipTap outside the
9
+ * editor's component tree, where Svelte's context does not reach, so the words travel in the options of the extension.
10
+ * English when left out.
11
+ */
12
+ translate: Readable<Translate<ShadEditorMessageKey>> | null;
13
+ /**
14
+ * The way the screen around the editor is read (`textDirection` of the editor component). It travels in the options for
15
+ * the same reason as `translate`: the menu of the image bar is drawn by a component TipTap mounts outside the editor's
16
+ * tree, so it would not find the direction in Svelte's context. Left to right when left out.
17
+ */
18
+ textDirection: Readable<TextDirection> | null;
19
+ }
20
+ export declare const ImageExtension: import("@tiptap/core").Node<ImageOptions & ImageExtensionOptions, any>;