@widgetic/editor 4.0.11 → 4.1.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 (58) hide show
  1. package/README.md +124 -0
  2. package/dist/components/WidgetPreview.svelte +58 -59
  3. package/dist/components/color-select/ColorSelect.svelte +4 -4
  4. package/dist/components/editors/CompositionEditorApp.svelte +69 -15
  5. package/dist/components/editors/CompositionEditorApp.svelte.d.ts +20 -0
  6. package/dist/components/editors/EmbedEditor.svelte +33 -22
  7. package/dist/components/editors/EmbedEditor.svelte.d.ts +1 -1
  8. package/dist/components/editors/PropsEditor.svelte +74 -58
  9. package/dist/components/editors/PropsEditor.svelte.d.ts +3 -3
  10. package/dist/components/ic/AngleInputController.svelte +1 -1
  11. package/dist/components/ic/BrowserInputController.svelte +37 -29
  12. package/dist/components/ic/ColorPickerInputController.svelte +31 -15
  13. package/dist/components/ic/DateFieldInputController.svelte +27 -11
  14. package/dist/components/ic/DatePickerInputController.svelte +27 -17
  15. package/dist/components/ic/DropdownInputController.svelte +10 -5
  16. package/dist/components/ic/DropdownInputController.svelte.d.ts +1 -1
  17. package/dist/components/ic/FixedPositionInputController.svelte +12 -6
  18. package/dist/components/ic/FontInputController.svelte +32 -27
  19. package/dist/components/ic/InputControllerHeader.svelte +5 -1
  20. package/dist/components/ic/NumberControls.svelte +4 -4
  21. package/dist/components/ic/NumberInputController.svelte +2 -2
  22. package/dist/components/ic/PositionInputController.svelte +9 -4
  23. package/dist/components/ic/RangeCalendarInputController.svelte +26 -16
  24. package/dist/components/ic/RoundedCornersInputController.svelte +22 -17
  25. package/dist/components/ic/SliderInputController.svelte +7 -2
  26. package/dist/components/ic/SwitchInputController.svelte +12 -3
  27. package/dist/components/ic/TagsInputController.svelte +19 -4
  28. package/dist/components/ic/TextAreaInputController.svelte +59 -44
  29. package/dist/components/ic/TextAreaPreviewInputController.svelte +8 -3
  30. package/dist/components/ic/TextInputController.svelte +17 -1
  31. package/dist/components/ic/TimeInputController.svelte +2 -1
  32. package/dist/components/ic/VectorInputController.svelte +21 -16
  33. package/dist/components/ic/shared/ItemGrid.svelte +7 -3
  34. package/dist/components/ic/shared/RecentItemsSection.svelte +16 -11
  35. package/dist/components/ic/shared/RecentItemsSection.svelte.d.ts +1 -1
  36. package/dist/composition/compositionEditorController.d.ts +11 -2
  37. package/dist/composition/compositionEditorController.js +33 -21
  38. package/dist/composition/compositionHelpers.d.ts +3 -1
  39. package/dist/composition/compositionHelpers.js +4 -2
  40. package/dist/i18n/index.d.ts +4 -0
  41. package/dist/i18n/index.js +4 -0
  42. package/dist/i18n/labels.d.ts +15 -0
  43. package/dist/i18n/labels.js +57 -0
  44. package/dist/i18n/language.d.ts +228 -0
  45. package/dist/i18n/language.js +34 -0
  46. package/dist/i18n/languageKit.d.ts +54 -0
  47. package/dist/i18n/languageKit.js +102 -0
  48. package/dist/i18n/messages.d.ts +241 -0
  49. package/dist/i18n/messages.js +253 -0
  50. package/dist/i18n/translate.d.ts +6 -0
  51. package/dist/i18n/translate.js +8 -0
  52. package/dist/index.d.ts +1 -0
  53. package/dist/index.js +4 -0
  54. package/dist/utils/dockPlacement.d.ts +12 -0
  55. package/dist/utils/dockPlacement.js +13 -0
  56. package/dist/utils/fieldDirection.d.ts +13 -0
  57. package/dist/utils/fieldDirection.js +25 -0
  58. package/package.json +2 -2
@@ -68,6 +68,10 @@
68
68
  // Import Shad Editor
69
69
  import { ShadEditor, type TipTapEditor, type TipTapEditorLevel } from '@widgetic/design-system/shad-editor';
70
70
 
71
+ // The words of the editor in the language of the page, and the share of them that belongs to the rich text editor below.
72
+ import { useEditorLanguage } from '../../i18n/language';
73
+ import type { EditorMessageKey } from '../../i18n/messages';
74
+
71
75
  // Add local ColorPickerInputController import
72
76
  import ColorPickerInputController from './ColorPickerInputController.svelte';
73
77
 
@@ -94,6 +98,8 @@
94
98
  // Use config values with fallbacks
95
99
  const textColor = $derived(config.textColor || '#1a1a1a');
96
100
 
101
+ const { t, shadEditor: shadEditorLanguage } = useEditorLanguage();
102
+
97
103
  /**
98
104
  * State Variables
99
105
  */
@@ -111,7 +117,7 @@
111
117
  // Add type definition for formats
112
118
  type FormatType = 'bold' | 'italic' | 'underline' | 'link';
113
119
  type ActiveFormats = Record<FormatType, boolean>;
114
- type FormatMapping = Record<FormatType, { value: string; icon: any }>;
120
+ type FormatMapping = Record<FormatType, { value: string; icon: any; messageKey: EditorMessageKey }>;
115
121
 
116
122
  let activeFormats = $state<ActiveFormats>({
117
123
  bold: false,
@@ -121,10 +127,10 @@
121
127
  });
122
128
 
123
129
  const formatMapping: FormatMapping = {
124
- bold: { value: 'b', icon: IconBold },
125
- italic: { value: 'i', icon: IconItalic },
126
- underline: { value: 'u', icon: IconUnderline },
127
- link: { value: 'link', icon: IconLink }
130
+ bold: { value: 'b', icon: IconBold, messageKey: 'editor.textArea.toggleBold' },
131
+ italic: { value: 'i', icon: IconItalic, messageKey: 'editor.textArea.toggleItalic' },
132
+ underline: { value: 'u', icon: IconUnderline, messageKey: 'editor.textArea.toggleUnderline' },
133
+ link: { value: 'link', icon: IconLink, messageKey: 'editor.textArea.toggleLink' }
128
134
  };
129
135
 
130
136
  let currentTextStyle = $state('text'); // Track current text style
@@ -133,17 +139,23 @@
133
139
  let currentColor = $derived(textColor);
134
140
  let selectedTextRange: { from: number, to: number } | null = $state(null);
135
141
 
136
- const textOptions = [
137
- { value: 'text', label: 'Paragraph', icon: IconPilcrow },
138
- { value: 'h1', label: 'H1' },
139
- { value: 'h2', label: 'H2' },
140
- { value: 'h3', label: 'H3' }
142
+ // Each option is shown by its message (`messageKey`), in the language of the page.
143
+ const textOptions: { value: string; messageKey: EditorMessageKey; icon?: any }[] = [
144
+ { value: 'text', messageKey: 'editor.textArea.paragraph', icon: IconPilcrow },
145
+ { value: 'h1', messageKey: 'editor.textArea.heading1' },
146
+ { value: 'h2', messageKey: 'editor.textArea.heading2' },
147
+ { value: 'h3', messageKey: 'editor.textArea.heading3' }
141
148
  ];
142
149
 
143
- const listOptions = [
144
- { value: 'bulletList', label: 'Bullet List', icon: IconList },
145
- { value: 'orderedList', label: 'Numbered List', icon: IconListNumbers },
146
- { value: 'checkList', label: 'Check List', icon: IconListCheck }
150
+ /** The message of the style the toolbar button shows: the plain text's when the style is none of the options. */
151
+ let currentTextStyleMessageKey = $derived<EditorMessageKey>(
152
+ textOptions.find((option) => option.value === currentTextStyle)?.messageKey ?? 'editor.textArea.text'
153
+ );
154
+
155
+ const listOptions: { value: string; messageKey: EditorMessageKey; icon?: any }[] = [
156
+ { value: 'bulletList', messageKey: 'editor.textArea.bulletList', icon: IconList },
157
+ { value: 'orderedList', messageKey: 'editor.textArea.numberedList', icon: IconListNumbers },
158
+ { value: 'checkList', messageKey: 'editor.textArea.checkList', icon: IconListCheck }
147
159
  ];
148
160
 
149
161
  const content = writable(value);
@@ -395,7 +407,7 @@
395
407
  break;
396
408
  case 'link':
397
409
  if (isAdding) {
398
- const href = window.prompt('Enter URL');
410
+ const href = window.prompt($t('editor.textArea.enterUrl'));
399
411
  if (href) {
400
412
  chain.setLink({ href }).run();
401
413
  }
@@ -1116,17 +1128,17 @@
1116
1128
  <!-- Toolbar at the top -->
1117
1129
  <div
1118
1130
  bind:this={toolbarRef}
1119
- class="toolbar-container sticky top-0 left-0 right-0 h-[56px] bg-grey1 z-10 border-b border-grey2 overflow-visible select-none"
1131
+ class="toolbar-container sticky top-0 start-0 end-0 h-[56px] bg-grey1 z-10 border-b border-grey2 overflow-visible select-none"
1120
1132
  on:mousedown={handleToolbarInteraction}
1121
1133
  on:click={handleToolbarInteraction}
1122
1134
  on:keydown={handleToolbarKeyDown}
1123
1135
  role="toolbar"
1124
- aria-label="Text formatting toolbar"
1136
+ aria-label={$t('editor.textArea.toolbar')}
1125
1137
  tabindex="-1"
1126
1138
  >
1127
1139
  <div class="toolbar-items-row relative flex items-center h-full w-full">
1128
1140
  <!-- Format cluster centered; close stays on the right -->
1129
- <div class={`toolbar-items-container absolute inset-y-0 left-0 right-10 flex items-center justify-center gap-2 transition-transform duration-200 ease-out ${
1141
+ <div class={`toolbar-items-container absolute inset-y-0 start-0 end-10 flex items-center justify-center gap-2 transition-transform duration-200 ease-out ${
1130
1142
  isColorPickerAnimating ? 'translate-y-[-200%]' :
1131
1143
  showColorPicker ? 'translate-y-[-200%]' :
1132
1144
  'translate-y-0'
@@ -1164,10 +1176,10 @@
1164
1176
  handleToggleChange(value);
1165
1177
  }}
1166
1178
  >
1167
- {#each Object.entries(formatMapping) as [format, { value, icon: Icon }] (format)}
1179
+ {#each Object.entries(formatMapping) as [format, { value, icon: Icon, messageKey }] (format)}
1168
1180
  <ToggleGroup.Item
1169
1181
  {value}
1170
- aria-label={`Toggle ${format}`}
1182
+ aria-label={$t(messageKey)}
1171
1183
  data-state={activeFormats[format as FormatType] ? 'on' : 'off'}
1172
1184
  class="toolbar-button shrink-0 !h-8 !w-8 !min-w-8 !p-0 {activeFormats[format as FormatType] ? 'bg-muted' : ''}"
1173
1185
  >
@@ -1212,7 +1224,7 @@
1212
1224
  {#if currentTextStyle === 'text'}
1213
1225
  <IconPilcrow class="h-3.5 w-3.5 shrink-0" />
1214
1226
  {:else}
1215
- <span class="text-xs">{textOptions.find(opt => opt.value === currentTextStyle)?.label || 'Text'}</span>
1227
+ <span class="text-xs">{$t(currentTextStyleMessageKey)}</span>
1216
1228
  {/if}
1217
1229
  <ChevronDown class="h-3 w-3 shrink-0" />
1218
1230
  </div>
@@ -1223,7 +1235,7 @@
1223
1235
  class="w-[200px] p-0"
1224
1236
  >
1225
1237
  <Command.Root>
1226
- <Command.List>
1238
+ <Command.List aria-label={$t('editor.textArea.textStyles')}>
1227
1239
  {#each textOptions as option}
1228
1240
  <Command.Item
1229
1241
  value={option.value}
@@ -1239,10 +1251,10 @@
1239
1251
  {#if option.icon}
1240
1252
  <option.icon class="h-4 w-4" />
1241
1253
  {/if}
1242
- <span>{option.label}</span>
1254
+ <span>{$t(option.messageKey)}</span>
1243
1255
  </div>
1244
1256
  {:else}
1245
- <span>{option.label}</span>
1257
+ <span>{$t(option.messageKey)}</span>
1246
1258
  {/if}
1247
1259
  {#if option.value === currentTextStyle}
1248
1260
  <div class="h-4 w-4">✓</div>
@@ -1296,7 +1308,7 @@
1296
1308
  class="style-dropdown-container w-[300px] p-0"
1297
1309
  >
1298
1310
  <Command.Root>
1299
- <Command.List>
1311
+ <Command.List aria-label={$t('editor.textArea.listStyles')}>
1300
1312
  {#each listOptions as option}
1301
1313
  <Command.Item
1302
1314
  value={option.value}
@@ -1311,7 +1323,7 @@
1311
1323
  {#if option.icon}
1312
1324
  <option.icon class="h-4 w-4" />
1313
1325
  {/if}
1314
- <span>{option.label}</span>
1326
+ <span>{$t(option.messageKey)}</span>
1315
1327
  </div>
1316
1328
  {#if option.value === currentTextStyle}
1317
1329
  <div class="h-4 w-4">✓</div>
@@ -1328,7 +1340,7 @@
1328
1340
  {#if onClose && !showColorPicker && !isColorPickerAnimating}
1329
1341
  <Button
1330
1342
  variant="ghost"
1331
- class="toolbar-close-button ml-auto shrink-0 p-0 hover:bg-transparent h-8 w-8 mr-2 z-10"
1343
+ class="toolbar-close-button ms-auto shrink-0 p-0 hover:bg-transparent h-8 w-8 me-2 z-10"
1332
1344
  onclick={(e) => {
1333
1345
  e.stopPropagation();
1334
1346
  e.preventDefault();
@@ -1402,6 +1414,8 @@
1402
1414
  <ShadEditor
1403
1415
  bind:content={value}
1404
1416
  showToolbar={false}
1417
+ locale={$shadEditorLanguage.locale ?? undefined}
1418
+ messages={$shadEditorLanguage.messages ?? undefined}
1405
1419
  class="shad-editor subtext {editorClass} {showColorPicker || textStyleDropdownOpen ? 'color-picking-active' : ''} {editorHasFocus ? 'editor-focused' : ''} {isSelectionActive ? 'selection-active' : ''}"
1406
1420
  on:mount={handleEditorMount}
1407
1421
  on:update={handleEditorUpdate}
@@ -1446,18 +1460,18 @@
1446
1460
  /* Keep scrollbar styles */
1447
1461
  :global(.ProseMirror::-webkit-scrollbar) {
1448
1462
  width: 6px;
1449
- margin-right: 2px;
1463
+ margin-inline-end: 2px;
1450
1464
  }
1451
1465
 
1452
1466
  :global(.ProseMirror::-webkit-scrollbar-track) {
1453
1467
  background-color: transparent;
1454
- margin-right: 2px;
1468
+ margin-inline-end: 2px;
1455
1469
  }
1456
1470
 
1457
1471
  :global(.ProseMirror::-webkit-scrollbar-thumb) {
1458
1472
  background-color: hsl(var(--border)) !important;
1459
1473
  border-radius: 3px;
1460
- margin-right: 2px;
1474
+ margin-inline-end: 2px;
1461
1475
  }
1462
1476
 
1463
1477
  :global(.ProseMirror::-webkit-scrollbar-thumb:hover) {
@@ -1484,7 +1498,7 @@
1484
1498
  :global(.ProseMirror ul),
1485
1499
  :global(.ProseMirror ol),
1486
1500
  :global(.ProseMirror [data-type="taskList"]) {
1487
- padding-left: 1.5rem;
1501
+ padding-inline-start: 1.5rem;
1488
1502
  margin: 0;
1489
1503
  line-height: 1.4;
1490
1504
  }
@@ -1492,21 +1506,21 @@
1492
1506
  :global(.ProseMirror li),
1493
1507
  :global(.ProseMirror [data-type="taskItem"]) {
1494
1508
  position: relative;
1495
- padding-left: 0.5rem;
1509
+ padding-inline-start: 0.5rem;
1496
1510
  margin: 0;
1497
1511
  }
1498
1512
 
1499
1513
  /* Task list specific styles */
1500
1514
  :global(.ProseMirror [data-type="taskList"]) {
1501
1515
  list-style: none;
1502
- padding-left: 0rem !important;
1516
+ padding-inline-start: 0rem !important;
1503
1517
  }
1504
1518
 
1505
1519
  :global(.ProseMirror [data-type="taskItem"]) {
1506
1520
  display: flex;
1507
1521
  align-items: flex-start;
1508
- padding-left: 0 !important;
1509
- margin-left: 0 !important;
1522
+ padding-inline-start: 0 !important;
1523
+ margin-inline-start: 0 !important;
1510
1524
  gap: 0.5rem;
1511
1525
  }
1512
1526
 
@@ -1799,11 +1813,12 @@
1799
1813
  gap: 8px !important;
1800
1814
  height: 100% !important;
1801
1815
  position: absolute !important;
1802
- inset: 0 2.5rem 0 0 !important;
1816
+ inset-block: 0 !important;
1817
+ inset-inline: 0 2.5rem !important;
1803
1818
  }
1804
1819
 
1805
1820
  :global(.text-area #text-style-trigger) {
1806
- margin-right: 0 !important;
1821
+ margin-inline-end: 0 !important;
1807
1822
  }
1808
1823
 
1809
1824
  /* Color the brush icon based on selected color */
@@ -1816,12 +1831,12 @@
1816
1831
  }
1817
1832
  /* Button spacing */
1818
1833
  :global(.text-area .color-picker-preview-button) {
1819
- margin-left: 0 !important;
1820
- margin-right: 0 !important;
1834
+ margin-inline-start: 0 !important;
1835
+ margin-inline-end: 0 !important;
1821
1836
  }
1822
1837
 
1823
1838
  :global(.text-area .text-formatting-toggle-group) {
1824
- margin-right: 0 !important;
1839
+ margin-inline-end: 0 !important;
1825
1840
  display: flex !important;
1826
1841
  gap: 8px !important;
1827
1842
  }
@@ -1831,7 +1846,7 @@
1831
1846
  }
1832
1847
 
1833
1848
  :global(.text-area .text-formatting-toggle-group button:not(:last-child)) {
1834
- margin-right: 0 !important;
1849
+ margin-inline-end: 0 !important;
1835
1850
  }
1836
1851
 
1837
1852
  :global(.text-area #text-style-trigger),
@@ -1874,7 +1889,7 @@
1874
1889
 
1875
1890
  :global(.text-area .toolbar-text-style-ct),
1876
1891
  :global(.text-area .toolbar-list-style-ct) {
1877
- margin-left: 0 !important;
1892
+ margin-inline-start: 0 !important;
1878
1893
  }
1879
1894
 
1880
1895
  /* Update the color picker button to show selected color */
@@ -1915,8 +1930,8 @@
1915
1930
  width: 44px !important;
1916
1931
  min-width: 44px !important;
1917
1932
  max-width: 44px !important;
1918
- margin-left: 0 !important;
1919
- padding-left: 0 !important;
1933
+ margin-inline-start: 0 !important;
1934
+ padding-inline-start: 0 !important;
1920
1935
  }
1921
1936
 
1922
1937
  /* Update color picker container styles */
@@ -16,12 +16,16 @@
16
16
  <script lang="ts">
17
17
  import { Portal } from '@widgetic/design-system/components';
18
18
  import { IconArrowsDiagonal } from '@widgetic/design-system/icons';
19
+ import { dirForUserText, useTextDirection } from '@widgetic/design-system/components/textDirection';
19
20
  import { onMount, onDestroy } from 'svelte';
20
21
 
21
22
  import { TextAreaInputController } from './index';
22
23
  import { createPreviewHtml } from '../../utils/richTextPreview';
23
24
  import { escapeHtml } from '../../utils/htmlSafety';
24
25
 
26
+ // The way the screen is read: the text in the preview is what a person wrote, so on a right to left screen it is read by its own first letter.
27
+ const textDirection = useTextDirection();
28
+
25
29
  /**
26
30
  * Component props
27
31
  */
@@ -195,15 +199,16 @@
195
199
  role="button"
196
200
  tabindex="0"
197
201
  >
198
- <div class="textarea-ic-preview-copy min-w-0 overflow-hidden pr-10 pt-[13px] pb-4 pl-4">
202
+ <div class="textarea-ic-preview-copy min-w-0 overflow-hidden pe-10 pt-[13px] pb-4 ps-4">
199
203
  <div
200
204
  class="textarea-ic-preview-html break-words text-grey7 text-sm leading-5"
201
205
  class:text-grey3={isShowingPlaceholder}
206
+ dir={dirForUserText($textDirection)}
202
207
  >
203
208
  {@html previewText}
204
209
  </div>
205
210
  </div>
206
- <div class="textarea-ic-expand-icon absolute top-[14px] right-[12px]">
211
+ <div class="textarea-ic-expand-icon absolute top-[14px] end-[12px]">
207
212
  <IconArrowsDiagonal
208
213
  class="w-5 h-5 text-grey5 group-hover:text-grey7 transition-colors duration-200"
209
214
  />
@@ -220,7 +225,7 @@
220
225
  {#if isShowingTextArea}
221
226
  <Portal
222
227
  target={portalTarget}
223
- class="text-area-portal absolute top-0 left-0 w-full h-full"
228
+ class="text-area-portal absolute top-0 start-0 w-full h-full"
224
229
  >
225
230
  <!-- Blocking overlay -->
226
231
  <div
@@ -16,6 +16,8 @@
16
16
  <script lang="ts">
17
17
  // Import Shadcn UI components from Widgetic design-system package
18
18
  import { Input } from '@widgetic/design-system/components';
19
+ import { useTextDirection } from '@widgetic/design-system/components/textDirection';
20
+ import { directionOfFieldValue } from '../../utils/fieldDirection';
19
21
  import { InputControllerHeader } from './index';
20
22
 
21
23
  // Component props
@@ -35,6 +37,18 @@
35
37
  export let description: TextInputControllerProps['description'] = undefined;
36
38
  export let onFocus: TextInputControllerProps['onFocus'] = () => {};
37
39
 
40
+ // The way the screen is read. What a visitor types reads by its own first letter on a right to left screen; an address
41
+ // (an email, a link) is read left to right on every screen. A left to right screen is written no attribute, as it always was.
42
+ // (The `type` is the name of the kind of field in the schema, `textInput` for a plain text field: see fieldDirection.ts.)
43
+ const textDirection = useTextDirection();
44
+ // What is in the field right now: the value the page gave it, then what is typed (only an empty field has no first letter).
45
+ let typedText = '';
46
+ $: typedText = value ?? '';
47
+ $: hasTypedValue = typedText.length > 0;
48
+ let valueDirection: 'auto' | 'ltr' | undefined;
49
+ // (An empty field takes the direction of the screen, so that its hint and its caret start at the start of the line.)
50
+ $: valueDirection = directionOfFieldValue(type, $textDirection, hasTypedValue);
51
+
38
52
  /**
39
53
  * Methods
40
54
  */
@@ -56,7 +70,7 @@
56
70
  <InputControllerHeader {label} />
57
71
  </div>
58
72
 
59
- <div class="absolute right-0">
73
+ <div class="absolute end-0">
60
74
  <div
61
75
  class="h-[48px] w-[185px] rounded-large border border-grey2 flex items-center bg-grey1 transition-colors duration-200 hover:border-forest-green"
62
76
  on:focusin={onFocus}
@@ -66,9 +80,11 @@
66
80
  {type}
67
81
  {value}
68
82
  {placeholder}
83
+ dir={valueDirection}
69
84
  onfocus={onFocus}
70
85
  oninput={(e) => {
71
86
  const newValue = e.currentTarget.value;
87
+ typedText = newValue;
72
88
  if (validate(newValue)) {
73
89
  onChange(newValue);
74
90
  }
@@ -240,6 +240,7 @@
240
240
  if (dropdownContainer && timeButton) {
241
241
  const rect = timeButton.getBoundingClientRect();
242
242
  dropdownContainer.style.top = `${rect.bottom + 4}px`;
243
+ // rtl-ok: the list is as wide as the button it opens from, so the left edge of the button is both of its edges
243
244
  dropdownContainer.style.left = `${rect.left}px`;
244
245
  isDropdownPositioned = true;
245
246
  }
@@ -339,7 +340,7 @@
339
340
  <InputControllerHeader {label} {helpText} />
340
341
  </div>
341
342
 
342
- <div class="absolute right-0 h-[48px] flex items-center">
343
+ <div class="absolute end-0 h-[48px] flex items-center">
343
344
  <div class="flex items-center justify-end gap-2">
344
345
  <!-- Clock Face -->
345
346
  <!-- svelte-ignore a11y_no_static_element_interactions -->
@@ -55,6 +55,11 @@
55
55
  import { SearchInput } from '@widgetic/design-system/components';
56
56
  import CategoryTabs from './shared/CategoryTabs.svelte';
57
57
  import { RecentItemsSection, ItemGrid } from './shared';
58
+ import { useEditorLanguage } from '../../i18n/language';
59
+ import { categoryLabel } from '../../i18n/labels';
60
+
61
+ // The words of the editor in the language of the page.
62
+ const { t } = useEditorLanguage();
58
63
 
59
64
  // Utils imports
60
65
  import {
@@ -1499,14 +1504,14 @@
1499
1504
 
1500
1505
  <div bind:this={controllerElement} class="flex flex-col gap-2 widgetic-colors">
1501
1506
  <div class="flex items-center w-full relative">
1502
- <div class="w-[185px] flex-shrink-0 ml-auto">
1507
+ <div class="w-[185px] flex-shrink-0 ms-auto">
1503
1508
  <InputControllerHeader
1504
1509
  label={label}
1505
1510
  helpText={description}
1506
1511
  />
1507
1512
  </div>
1508
1513
 
1509
- <div class="absolute right-0">
1514
+ <div class="absolute end-0">
1510
1515
  <div
1511
1516
  class="h-[48px] w-[185px] rounded-large border border-grey2 flex items-center bg-grey1 transition-colors duration-200 hover:border-forest-green"
1512
1517
  onfocusin={onFocus}
@@ -1552,7 +1557,7 @@
1552
1557
  }}
1553
1558
  >
1554
1559
  <Popover.Trigger
1555
- class="w-full h-full flex items-center border-0 bg-transparent hover:bg-transparent focus:bg-transparent pl-3 pr-[16px] group"
1560
+ class="w-full h-full flex items-center border-0 bg-transparent hover:bg-transparent focus:bg-transparent ps-3 pe-[16px] group"
1556
1561
  onclick={handleButtonClick}
1557
1562
  onkeydown={handleKeyDown}
1558
1563
  onmouseenter={() => {
@@ -1577,8 +1582,8 @@
1577
1582
  <path d="M12 17h.01"></path>
1578
1583
  </svg>
1579
1584
  </div>
1580
- <span class="truncate text-left capitalize subtext text-grey7">
1581
- {value?.name || "Select vector..."}
1585
+ <span class="truncate text-start capitalize subtext text-grey7">
1586
+ {value?.name || $t('editor.vector.select')}
1582
1587
  </span>
1583
1588
  {:else}
1584
1589
  <!-- Use a simple img tag with Svelte action for error handling -->
@@ -1588,13 +1593,13 @@
1588
1593
  class="w-5 h-5 object-contain"
1589
1594
  use:handleImageError={value.name}
1590
1595
  />
1591
- <span class="truncate text-left capitalize subtext text-grey7">
1592
- {value?.name || "Select vector..."}
1596
+ <span class="truncate text-start capitalize subtext text-grey7">
1597
+ {value?.name || $t('editor.vector.select')}
1593
1598
  </span>
1594
1599
  {/if}
1595
1600
  {:else}
1596
- <span class="truncate text-left subtext text-grey7">
1597
- Select vector...
1601
+ <span class="truncate text-start subtext text-grey7">
1602
+ {$t('editor.vector.select')}
1598
1603
  </span>
1599
1604
  {/if}
1600
1605
  </div>
@@ -1605,10 +1610,10 @@
1605
1610
  <div class="flex flex-col h-full vector-popover-content">
1606
1611
  <!-- Title and upload icon -->
1607
1612
  <div class="relative pt-6 pb-[8px] px-4">
1608
- <div class="h5 text-grey7 widgetic-colors">Vectors</div>
1613
+ <div class="h5 text-grey7 widgetic-colors">{$t('editor.vector.title')}</div>
1609
1614
  <button
1610
1615
  type="button"
1611
- class="absolute right-6 top-[26px] bg-transparent border-0 cursor-pointer p-0 rounded-full hover:bg-grey3 transition-all duration-300"
1616
+ class="absolute end-6 top-[26px] bg-transparent border-0 cursor-pointer p-0 rounded-full hover:bg-grey3 transition-all duration-300"
1612
1617
  onclick={() => {
1613
1618
  try {
1614
1619
  openFileBrowser();
@@ -1628,7 +1633,7 @@
1628
1633
  <!-- Search input -->
1629
1634
  <div class="relative px-4">
1630
1635
  <SearchInput
1631
- placeholder="Search vectors..."
1636
+ placeholder={$t('editor.vector.search')}
1632
1637
  bind:value={searchTerm}
1633
1638
  onInput={(e: Event) => {
1634
1639
  // Use debounced search for better performance
@@ -1707,6 +1712,7 @@
1707
1712
  categories={filterOptions}
1708
1713
  activeCategory={currentFilter}
1709
1714
  onCategorySelect={handleFilterChange}
1715
+ getCategoryLabel={(category) => categoryLabel(category, $t)}
1710
1716
  filterVisible={!searchTerm}
1711
1717
  />
1712
1718
  </div>
@@ -1724,14 +1730,13 @@
1724
1730
  }}
1725
1731
  id={`vector-list-${controllerId}`}
1726
1732
  role="listbox"
1727
- aria-label="Vector list"
1733
+ aria-label={$t('editor.vector.list')}
1728
1734
  >
1729
1735
  <div class="space-y-0">
1730
1736
  <!-- Recently used section - only show when not searching -->
1731
1737
  {#if recentVectors.length > 0 && !searchTerm}
1732
1738
  <RecentItemsSection
1733
1739
  recentItems={recentVectors}
1734
- title="Recently used"
1735
1740
  onItemSelect={handleVectorSelect}
1736
1741
  onItemDelete={handleVectorDelete}
1737
1742
  isItemSelected={isVectorSelected}
@@ -1750,7 +1755,7 @@
1750
1755
  {#if filteredVectors.length > 0}
1751
1756
  <div id="vector-header-{controllerId}" class="sticky top-0 -mt-2 bg-white z-10 pt-2 {isScrolled ? 'border-b border-grey2' : 'border-b border-transparent'} transition-border">
1752
1757
  <h3 class="subtext text-grey7 px-4 pb-2">
1753
- {searchTerm ? 'Search results' : 'More'}
1758
+ {searchTerm ? $t('editor.list.searchResults') : $t('editor.list.more')}
1754
1759
  </h3>
1755
1760
  </div>
1756
1761
  {/if}
@@ -1758,7 +1763,7 @@
1758
1763
  {#if filteredVectors.length === 0}
1759
1764
  <div class="empty-state-wrapper">
1760
1765
  <EmptyState
1761
- message={searchTerm ? 'No vectors found' : 'No vectors available'}
1766
+ message={searchTerm ? $t('editor.vector.noneFound') : $t('editor.vector.noneAvailable')}
1762
1767
  height="auto"
1763
1768
  />
1764
1769
  </div>
@@ -15,6 +15,10 @@
15
15
  <script lang="ts">
16
16
  import { Check } from '@widgetic/design-system/icons';
17
17
  import { escapeHtml } from '../../../utils/htmlSafety';
18
+ import { useEditorLanguage } from '../../../i18n/language';
19
+
20
+ // The words of the editor in the language of the page.
21
+ const { t } = useEditorLanguage();
18
22
 
19
23
  export let items: any[] = [];
20
24
  export let containerRef: (node: HTMLElement) => void;
@@ -117,7 +121,7 @@
117
121
  data-item-type="font"
118
122
  >
119
123
  <div class="flex items-center flex-1 min-w-0 group">
120
- <Check class="mr-2 h-6 w-6 flex-shrink-0 {isItemSelected(item) ? 'text-white' : 'text-sage-green opacity-0'}"/>
124
+ <Check class="me-2 h-6 w-6 flex-shrink-0 {isItemSelected(item) ? 'text-white' : 'text-sage-green opacity-0'}"/>
121
125
  <div class="flex-1 overflow-hidden">
122
126
  {@html renderContent(item)}
123
127
  </div>
@@ -136,7 +140,7 @@
136
140
  data-item-type="font"
137
141
  >
138
142
  <div class="flex items-center flex-1 min-w-0 group">
139
- <Check class="mr-2 h-6 w-6 flex-shrink-0 {isItemSelected(item) ? 'text-white' : 'text-sage-green opacity-0'}"/>
143
+ <Check class="me-2 h-6 w-6 flex-shrink-0 {isItemSelected(item) ? 'text-white' : 'text-sage-green opacity-0'}"/>
140
144
  <div class="flex-1 overflow-hidden">
141
145
  {@html renderContent(item)}
142
146
  </div>
@@ -149,7 +153,7 @@
149
153
 
150
154
  {#if loadingMore}
151
155
  <div class="py-4 subtext text-center text-grey5">
152
- Loading more items...
156
+ {$t('editor.list.loadingMore')}
153
157
  </div>
154
158
  {/if}
155
159
  </div>