@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
@@ -70,6 +70,14 @@
70
70
  import { getColorSuggestions, getNextSuggestion, type AutocompleteResult } from '../../utils/colorAutocomplete';
71
71
  import { recentColorsUtil } from '../../utils/recentColors';
72
72
  import { listenForScroll, lockPointerStyle } from '../../utils/shadowDom';
73
+ import { useEditorLanguage } from '../../i18n/language';
74
+ import { useTextDirection } from '@widgetic/design-system/components/textDirection';
75
+
76
+ // The words of the editor in the language of the page.
77
+ const { t } = useEditorLanguage();
78
+
79
+ // The way the screen is read: the pop-up opens from the end of the field, which is its left edge on a right to left screen.
80
+ const textDirection = useTextDirection();
73
81
 
74
82
 
75
83
 
@@ -1190,11 +1198,16 @@
1190
1198
  const top = openAbove
1191
1199
  ? Math.round(rect.top - popoverHeight - 4)
1192
1200
  : Math.round(rect.bottom + 4);
1193
- // Align popup's right edge with the right edge of the full input container
1201
+ // Align the popup's end edge with the end edge of the full input container: the right edge on a left to right screen,
1202
+ // the left edge on a right to left one (the popup itself is a drawing, drawn left to right, and opens toward the inside)
1194
1203
  const inputContainer = triggerRef.closest('.color-input-main-container') as HTMLElement | null;
1195
- const rightRef = inputContainer ? inputContainer.getBoundingClientRect().right : rect.right;
1196
- const right = Math.round(window.innerWidth - rightRef);
1197
- popoverStyle = `top: ${top}px; right: ${right}px;`;
1204
+ const containerRect = inputContainer ? inputContainer.getBoundingClientRect() : rect;
1205
+ if ($textDirection === 'rtl') {
1206
+ popoverStyle = `top: ${top}px; left: ${Math.round(containerRect.left)}px;`; // rtl-ok: the end edge of a right to left field is its left edge
1207
+ } else {
1208
+ const right = Math.round(window.innerWidth - containerRect.right);
1209
+ popoverStyle = `top: ${top}px; right: ${right}px;`; // rtl-ok: the end edge of a left to right field is its right edge
1210
+ }
1198
1211
  }
1199
1212
 
1200
1213
  // Position is calculated in handleToggle after tick()
@@ -1243,17 +1256,19 @@
1243
1256
  </div>
1244
1257
 
1245
1258
  <!-- Input Controller Container -->
1246
- <div class="input-controller-ct absolute right-0">
1259
+ <div class="input-controller-ct absolute end-0">
1247
1260
  <div class="color-input-main-container rounded-large flex items-center transition-colors duration-200"
1248
1261
  style="height: 48px; width: 185px; background: hsl(var(--grey1)); {showRing ? 'border: 1px solid hsl(var(--grey2));' : ''}">
1249
- <div class="flex items-center w-full justify-between" style="padding-right: 12px;">
1262
+ <div class="flex items-center w-full justify-between" style="padding-inline-end: 12px;">
1250
1263
 
1251
1264
  <!-- Hex Input Container -->
1252
1265
  <div class="hex-input-container flex-1" bind:this={inputRef}>
1266
+ <!-- A colour code (#FF00AA) is read left to right on every screen: in a right to left line its "#" would end up at the wrong end -->
1253
1267
  <Input
1254
1268
  bind:this={inputComponent}
1255
1269
  type="text"
1256
- class="h-9 border-0 bg-transparent subtext text-grey7 placeholder:text-grey3 focus-visible:ring-0 focus-visible:ring-offset-0 pl-4"
1270
+ dir="ltr"
1271
+ class="h-9 border-0 bg-transparent subtext text-grey7 placeholder:text-grey3 focus-visible:ring-0 focus-visible:ring-offset-0 ps-4"
1257
1272
  value={hexInput}
1258
1273
  onfocus={(e) => {
1259
1274
  dispatch('focus', e);
@@ -1317,7 +1332,7 @@
1317
1332
  z-index: 99999;
1318
1333
  max-height: 12rem;
1319
1334
  top: {inputRect.bottom + 4}px;
1320
- left: {inputRect.left}px;
1335
+ left: {inputRect.left}px; /* rtl-ok: the list is as wide as the field it opens from, so its left edge is the field's */
1321
1336
  width: {inputRect.width}px;
1322
1337
  "
1323
1338
  data-allow-scroll="true"
@@ -1366,7 +1381,8 @@
1366
1381
 
1367
1382
  <!-- svelte-ignore a11y_no_static_element_interactions -->
1368
1383
  <!-- svelte-ignore a11y_click_events_have_key_events -->
1369
- <div class="color-picker-popover-content w-auto pt-2 pb-2 pl-2 pr-0 widgetic-colors widgetic-radius modal-shadow"
1384
+ <div class="color-picker-popover-content w-auto pt-2 pb-2 ps-2 pe-0 widgetic-colors widgetic-radius modal-shadow"
1385
+ dir="ltr"
1370
1386
  style="{popoverStyle} border: 1px solid hsl(var(--grey2)); z-index: 2;"
1371
1387
  onmousedown={(e) => e.stopPropagation()}
1372
1388
  onclick={(e) => e.stopPropagation()}
@@ -1376,7 +1392,7 @@
1376
1392
 
1377
1393
  <!-- Recent Colors Container -->
1378
1394
  {#if saveRecentColors && (isShiftPressed || isSpacePressed)}
1379
- <div class="recent-colors-container" style="width: {pickerSize + sliderWidth + 14}px; margin-left: 10px; padding-top: 11px; padding-bottom: 8px;">
1395
+ <div class="recent-colors-container" style="width: {pickerSize + sliderWidth + 14}px; margin-inline-start: 10px; padding-top: 11px; padding-bottom: 8px;">
1380
1396
  {#if recentColors.length > 0}
1381
1397
  <div class="recent-colors-grid" style="display: grid; grid-template-columns: repeat(5, 1fr); width: 100%; gap: {swatchGap}px;">
1382
1398
  {#each recentColors.slice(0, 5) as {color, alpha}, i}
@@ -1410,7 +1426,7 @@
1410
1426
  </div>
1411
1427
  {:else}
1412
1428
  <div class="text-sm text-gray-500 text-center py-2">
1413
- No recent colors
1429
+ {$t('editor.color.noRecent')}
1414
1430
  </div>
1415
1431
  {/if}
1416
1432
  </div>
@@ -1421,7 +1437,7 @@
1421
1437
  <div
1422
1438
  class="color-picker-wrapper relative flex flex-col"
1423
1439
  role="application"
1424
- aria-label="Color picker"
1440
+ aria-label={$t('editor.color.picker')}
1425
1441
  onmousedown={handleDragStart}
1426
1442
  style="padding: 0;"
1427
1443
  >
@@ -1446,7 +1462,7 @@
1446
1462
  <div
1447
1463
  bind:this={opacitySliderRef}
1448
1464
  class="relative cursor-pointer opacity-slider"
1449
- style="width: {pickerSize}px; height: {sliderWidth}px; margin-left: 10px;"
1465
+ style="width: {pickerSize}px; height: {sliderWidth}px; margin-inline-start: 10px;"
1450
1466
  onmousedown={handleOpacityMouseDown}
1451
1467
  >
1452
1468
  <div class="absolute inset-0 overflow-hidden">
@@ -1467,13 +1483,13 @@
1467
1483
  </div>
1468
1484
  </div>
1469
1485
  <!-- Opacity number input — square, matching swatch size -->
1470
- <div class="opacity-number-ct flex items-center justify-center" style="width: {swatchSize + 6}px; min-width: {swatchSize + 6}px; height: {swatchSize}px; margin-left: 8px;">
1486
+ <div class="opacity-number-ct flex items-center justify-center" style="width: {swatchSize + 6}px; min-width: {swatchSize + 6}px; height: {swatchSize}px; margin-inline-start: 8px;">
1471
1487
  <Input
1472
1488
  type="number"
1473
1489
  min="0"
1474
1490
  max="100"
1475
1491
  class="w-full h-full text-center [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none ring-0 focus:ring-0 shadow-none widgetic-colors subtext"
1476
- style="border: 1px solid hsl(var(--forest-green)); color: hsl(var(--grey7)); padding-left: 8px; padding-right: 0;"
1492
+ style="border: 1px solid hsl(var(--forest-green)); color: hsl(var(--grey7)); padding-inline-start: 8px; padding-inline-end: 0;"
1477
1493
  value={opacityPercentage}
1478
1494
  oninput={(e) => {
1479
1495
  e.stopPropagation();
@@ -30,6 +30,15 @@
30
30
  import { onMount, tick } from 'svelte';
31
31
  import InputControllerHeader from './InputControllerHeader.svelte';
32
32
  import { activeElementIn } from '../../utils/shadowDom';
33
+ import { useEditorLanguage } from '../../i18n/language';
34
+ import type { EditorMessageKey } from '../../i18n/messages';
35
+ import { useTextDirection } from '@widgetic/design-system/components/textDirection';
36
+
37
+ // The words of the editor in the language of the page.
38
+ const { t } = useEditorLanguage();
39
+
40
+ // The way the screen is read: the row of fields turns around with it, and so do the keys that move from one field to the next.
41
+ const textDirection = useTextDirection();
33
42
 
34
43
  // Constants
35
44
  const MIN_DAY = 1;
@@ -93,27 +102,30 @@
93
102
  }
94
103
  });
95
104
 
96
- // Get field configuration
105
+ // Get field configuration. The hint in a box and its name for a screen reader are messages (`hintKey`, `labelKey`).
97
106
  function getFieldConfig(field: 'day' | 'month' | 'year') {
98
- const configs = {
107
+ const configs: Record<'day' | 'month' | 'year', { min: number; max: number; hintKey: EditorMessageKey; labelKey: EditorMessageKey; width: string; maxLength: number }> = {
99
108
  day: {
100
109
  min: MIN_DAY,
101
110
  max: MAX_DAY,
102
- placeholder: 'DD',
111
+ hintKey: 'editor.dateField.dayHint',
112
+ labelKey: 'editor.dateField.dayLabel',
103
113
  width: '40px',
104
114
  maxLength: 2
105
115
  },
106
116
  month: {
107
117
  min: MIN_MONTH,
108
118
  max: MAX_MONTH,
109
- placeholder: 'MM',
119
+ hintKey: 'editor.dateField.monthHint',
120
+ labelKey: 'editor.dateField.monthLabel',
110
121
  width: '40px',
111
122
  maxLength: 2
112
123
  },
113
124
  year: {
114
125
  min: MIN_YEAR,
115
126
  max: MAX_YEAR,
116
- placeholder: 'YYYY',
127
+ hintKey: 'editor.dateField.yearHint',
128
+ labelKey: 'editor.dateField.yearLabel',
117
129
  width: '60px',
118
130
  maxLength: 4
119
131
  }
@@ -337,11 +349,14 @@
337
349
  return;
338
350
  }
339
351
 
340
- // Handle arrow navigation
352
+ // Handle arrow navigation. The digits of a field run left to right on every screen, so the key that leaves a field at the
353
+ // right end of its digits is ArrowRight on every screen, and what lies on that side is the next field on a left to right
354
+ // screen and the one before it on a right to left one (where the row of fields starts on the right).
341
355
  if (e.key === 'ArrowRight') {
342
356
  if (input.selectionStart === input.value.length) {
343
357
  e.preventDefault();
344
- focusNextField(field);
358
+ if ($textDirection === 'rtl') focusPreviousField(field);
359
+ else focusNextField(field);
345
360
  }
346
361
  return;
347
362
  }
@@ -349,7 +364,8 @@
349
364
  if (e.key === 'ArrowLeft') {
350
365
  if (input.selectionStart === 0) {
351
366
  e.preventDefault();
352
- focusPreviousField(field);
367
+ if ($textDirection === 'rtl') focusNextField(field);
368
+ else focusPreviousField(field);
353
369
  }
354
370
  return;
355
371
  }
@@ -403,7 +419,7 @@
403
419
  <InputControllerHeader {label} {helpText} />
404
420
  </div>
405
421
 
406
- <div class="absolute right-0">
422
+ <div class="absolute end-0">
407
423
  <div class="h-[48px] w-[185px] rounded-large border border-grey2 bg-grey1 flex items-center transition-colors duration-200 hover:border-forest-green">
408
424
  <div class="flex items-center justify-between px-4 w-full">
409
425
  {#each fieldOrder as field}
@@ -416,14 +432,14 @@
416
432
  pattern="[0-9]*"
417
433
  bind:this={inputRefs[field]}
418
434
  value={field === 'day' ? day : field === 'month' ? month : year}
419
- placeholder={config.placeholder}
435
+ placeholder={$t(config.hintKey)}
420
436
  on:input={field === 'day' ? handleDayInput : field === 'month' ? handleMonthInput : handleYearInput}
421
437
  on:change={handleChange}
422
438
  on:focus={() => handleFocus(field)}
423
439
  on:blur={() => handleBlur(field)}
424
440
  on:keydown={(e) => handleKeyDown(field, e)}
425
441
  class={getFieldClasses(field).input}
426
- aria-label={`${field} input`}
442
+ aria-label={$t(config.labelKey)}
427
443
  maxlength={config.maxLength}
428
444
  />
429
445
  </div>
@@ -38,6 +38,12 @@
38
38
  // Svelte imports
39
39
  import { onMount, onDestroy } from 'svelte';
40
40
  import { listenForScroll } from '../../utils/shadowDom';
41
+ import { useEditorLanguage } from '../../i18n/language';
42
+ import type { EditorMessageKey } from '../../i18n/messages';
43
+ import type { Translate } from '@widgetic/design-system/i18n';
44
+
45
+ // The words of the editor in the language of the page, and the language itself (how a month and a date are written).
46
+ const { t, locale } = useEditorLanguage();
41
47
 
42
48
  // Shadcn UI Components
43
49
  import { Button, Select } from '@widgetic/design-system/components';
@@ -66,12 +72,12 @@
66
72
  let placeholder = today(getLocalTimeZone());
67
73
  let currentValue: CalendarDate | undefined;
68
74
 
69
- // Month and year options setup
70
- const monthFmt = new DateFormatter('en-US', { month: 'long' });
71
- const monthOptions = [
72
- "January", "February", "March", "April", "May", "June",
73
- "July", "August", "September", "October", "November", "December"
74
- ].map((month, i) => ({ value: String(i + 1), label: month }));
75
+ // Month and year options setup: the names of the months are written by the language of the page
76
+ $: monthFmt = new DateFormatter($locale, { month: 'long' });
77
+ $: monthOptions = Array.from({ length: 12 }, (_, monthIndex) => ({
78
+ value: String(monthIndex + 1),
79
+ label: monthFmt.format(new Date(2021, monthIndex, 1))
80
+ }));
75
81
 
76
82
  const yearOptions = Array.from({ length: 100 }, (_, i) => {
77
83
  const year = String(new Date().getFullYear() - i);
@@ -141,15 +147,15 @@
141
147
  * @param date - The date to format
142
148
  * @returns Formatted date string
143
149
  */
144
- function formatDate(date: CalendarDate | undefined): string {
145
- if (!date || !(date instanceof CalendarDate)) return 'Pick a date';
150
+ function formatDate(date: CalendarDate | undefined, languageTag: string, translate: Translate<EditorMessageKey>): string {
151
+ if (!date || !(date instanceof CalendarDate)) return translate('editor.date.pick');
146
152
  try {
147
153
  // Convert to JavaScript Date for localized formatting
148
154
  const jsDate = new Date(date.year, date.month - 1, date.day);
149
- return jsDate.toLocaleDateString('en-US', { dateStyle: 'long' });
155
+ return jsDate.toLocaleDateString(languageTag, { dateStyle: 'long' });
150
156
  } catch (error) {
151
157
  console.error('Error formatting date:', error);
152
- return 'Pick a date';
158
+ return translate('editor.date.pick');
153
159
  }
154
160
  }
155
161
 
@@ -233,7 +239,7 @@
233
239
  <InputControllerHeader {label} />
234
240
  </div>
235
241
 
236
- <div class="absolute right-0">
242
+ <div class="absolute end-0">
237
243
  <div
238
244
  class="h-[48px] w-[185px] rounded-large border border-grey2 flex items-center bg-grey1 transition-colors duration-200 hover:border-forest-green"
239
245
  on:focusin={onFocus}
@@ -244,9 +250,9 @@
244
250
  <Button
245
251
  id="date-picker"
246
252
  variant="ghost"
247
- class="w-full h-full justify-between text-left border-0 bg-transparent hover:bg-transparent focus:bg-transparent p-0 group"
253
+ class="w-full h-full justify-between text-start border-0 bg-transparent hover:bg-transparent focus:bg-transparent p-0 group"
248
254
  >
249
- <span class="subtext text-grey7 {!currentValue ? 'text-grey3' : ''}">{formatDate(currentValue)}</span>
255
+ <span class="subtext text-grey7 {!currentValue ? 'text-grey3' : ''}">{formatDate(currentValue, $locale, $t)}</span>
250
256
  <IconCalendar class="w-[20px] h-[20px] text-grey5 transition-colors duration-200 group-hover:text-grey7" />
251
257
  </Button>
252
258
  </div>
@@ -280,8 +286,8 @@
280
286
  }
281
287
  }}
282
288
  >
283
- <Select.Trigger aria-label="Select month" class="w-[60%] month-year-dropdown-content">
284
- <span class="text-sm">{defaultMonth?.label || "Select month"}</span>
289
+ <Select.Trigger aria-label={$t('editor.date.selectMonth')} class="w-[60%] month-year-dropdown-content">
290
+ <span class="text-sm">{defaultMonth?.label || $t('editor.date.selectMonth')}</span>
285
291
  </Select.Trigger>
286
292
  <Select.Content class="max-h-[200px] overflow-y-auto month-year-dropdown-content">
287
293
  {#each monthOptions as { value, label }}
@@ -315,8 +321,8 @@
315
321
  }
316
322
  }}
317
323
  >
318
- <Select.Trigger aria-label="Select year" class="w-[40%] month-year-dropdown-content">
319
- <span class="text-sm">{defaultYear?.label || "Select year"}</span>
324
+ <Select.Trigger aria-label={$t('editor.date.selectYear')} class="w-[40%] month-year-dropdown-content">
325
+ <span class="text-sm">{defaultYear?.label || $t('editor.date.selectYear')}</span>
320
326
  </Select.Trigger>
321
327
  <Select.Content class="max-h-[200px] overflow-y-auto month-year-dropdown-content">
322
328
  {#each yearOptions as { value, label }}
@@ -330,6 +336,10 @@
330
336
  <div class="calendar-no-header">
331
337
  <Calendar
332
338
  mode="single"
339
+ locale={$locale}
340
+ calendarLabel={$t('editor.date.calendarLabel')}
341
+ previousLabel={$t('editor.date.previousMonth')}
342
+ nextLabel={$t('editor.date.nextMonth')}
333
343
  onValueChange={(date: DateValue | undefined) => {
334
344
  // console.log('📅 [DatePicker] Calendar onValueChange:', { date, isCalendarDate: date instanceof CalendarDate });
335
345
  if (date instanceof CalendarDate) {
@@ -32,6 +32,10 @@
32
32
  */
33
33
  import { onMount, onDestroy } from 'svelte';
34
34
  import { listenForScroll } from '../../utils/shadowDom';
35
+ import { useEditorLanguage } from '../../i18n/language';
36
+
37
+ // The words of the editor in the language of the page.
38
+ const { t } = useEditorLanguage();
35
39
 
36
40
  // Local Components imports
37
41
  import { InputControllerHeader } from './index';
@@ -50,7 +54,8 @@
50
54
  * External properties that configure the dropdown's behavior and appearance
51
55
  */
52
56
  export let label: DropdownInputControllerProps['label'];
53
- export let defaultSelectedLabel: DropdownInputControllerProps['defaultSelectedLabel'] = "Select an option";
57
+ /** What the button says while nothing is chosen. Without one, the editor's own word, in the language of the page. */
58
+ export let defaultSelectedLabel: DropdownInputControllerProps['defaultSelectedLabel'] = undefined;
54
59
  export let value: DropdownInputControllerProps['value'];
55
60
  export let onChange: DropdownInputControllerProps['onChange'];
56
61
  export let config: DropdownInputControllerProps['config'] = {};
@@ -157,7 +162,7 @@
157
162
  */
158
163
  $: selectedValue = typeof value === 'string' ? value : (value?.value ?? '');
159
164
  $: triggerContent =
160
- options.find((o) => o.value === selectedValue)?.label ?? defaultSelectedLabel;
165
+ options.find((o) => o.value === selectedValue)?.label ?? defaultSelectedLabel ?? $t('editor.dropdown.select');
161
166
  </script>
162
167
 
163
168
  <!-- Dropdown Container -->
@@ -167,7 +172,7 @@
167
172
  <InputControllerHeader {label} />
168
173
  </div>
169
174
 
170
- <div class="absolute right-0">
175
+ <div class="absolute end-0">
171
176
  <div
172
177
  class="dropdown-main-container rounded-large flex items-center transition-colors duration-200 group"
173
178
  style="height: 48px; width: 185px; border: 1px solid hsl(var(--grey2)); background: hsl(var(--grey1));"
@@ -182,10 +187,10 @@
182
187
  >
183
188
  <Select.Trigger
184
189
  class="dropdown-ic-trigger h-full w-full border-0 bg-transparent subtext hover:bg-transparent focus:bg-transparent data-[state=open]:bg-transparent"
185
- style="padding-left: 16px; padding-right: 16px; color: hsl(var(--grey7));"
190
+ style="padding-inline-start: 16px; padding-inline-end: 16px; color: hsl(var(--grey7));"
186
191
  id={controllerId}
187
192
  >
188
- <span class="truncate flex-1 text-left subtext" style="color: hsl(var(--grey7));">
193
+ <span class="truncate flex-1 text-start subtext" style="color: hsl(var(--grey7));">
189
194
  {triggerContent}
190
195
  </span>
191
196
  <IconCaretDownFilled class="dropdown-ic-trigger-icon" style="width: 20px; height: 20px; color: hsl(var(--grey5));"/>
@@ -39,7 +39,7 @@ declare const DropdownInputController: $$__sveltets_2_IsomorphicComponent<{
39
39
  * Component Props
40
40
  * External properties that configure the dropdown's behavior and appearance
41
41
  */ label: DropdownInputControllerProps["label"];
42
- defaultSelectedLabel?: DropdownInputControllerProps["defaultSelectedLabel"];
42
+ /** What the button says while nothing is chosen. Without one, the editor's own word, in the language of the page. */ defaultSelectedLabel?: DropdownInputControllerProps["defaultSelectedLabel"];
43
43
  value: DropdownInputControllerProps["value"];
44
44
  onChange: DropdownInputControllerProps["onChange"];
45
45
  config?: DropdownInputControllerProps["config"];
@@ -40,6 +40,11 @@
40
40
  <script lang="ts">
41
41
  // Svelte imports
42
42
  import { onDestroy } from 'svelte';
43
+ import { useEditorLanguage } from '../../i18n/language';
44
+ import { fixedPositionLabel } from '../../i18n/labels';
45
+
46
+ // The words of the editor in the language of the page.
47
+ const { t } = useEditorLanguage();
43
48
 
44
49
  // Import icons from Tabler
45
50
  import {
@@ -259,10 +264,10 @@
259
264
 
260
265
  function getCornerRounding(pos: Position): string {
261
266
  switch (pos) {
262
- case 'top-left': return 'rounded-tl-[15px]';
263
- case 'top-right': return 'rounded-tr-[15px]';
264
- case 'bottom-left': return 'rounded-bl-[15px]';
265
- case 'bottom-right': return 'rounded-br-[15px]';
267
+ case 'top-left': return 'rounded-tl-[15px]'; // rtl-ok: the corner of the picture of a screen
268
+ case 'top-right': return 'rounded-tr-[15px]'; // rtl-ok: the corner of the picture of a screen
269
+ case 'bottom-left': return 'rounded-bl-[15px]'; // rtl-ok: the corner of the picture of a screen
270
+ case 'bottom-right': return 'rounded-br-[15px]'; // rtl-ok: the corner of the picture of a screen
266
271
  default: return '';
267
272
  }
268
273
  }
@@ -270,9 +275,10 @@
270
275
 
271
276
  <!-- Container with relative positioning for overlay -->
272
277
  <div class="relative widgetic-colors">
273
- <!-- Grid component -->
278
+ <!-- Grid component: the nine places of a screen, top left to bottom right, are a picture of the screen, so it is a left to right island -->
274
279
  <div
275
280
  class="fixed-position-grid grid grid-cols-3 gap-[1px] w-fit bg-white rounded-large relative"
281
+ dir="ltr"
276
282
  {id}
277
283
  >
278
284
  {#each positions as position}
@@ -284,7 +290,7 @@
284
290
  }"
285
291
  on:click={() => handlePositionClick(position)}
286
292
  on:focus={onFocus}
287
- aria-label={`Set position to ${position}`}
293
+ aria-label={$t('editor.fixedPosition.set', { position: fixedPositionLabel(position, $t) })}
288
294
  aria-pressed={position === value}
289
295
  >
290
296
  <svelte:component