@widgetic/editor 4.0.11 → 4.1.2

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 (61) hide show
  1. package/README.md +124 -0
  2. package/dist/components/PreviewChromeControls.svelte +84 -0
  3. package/dist/components/PreviewChromeControls.svelte.d.ts +35 -0
  4. package/dist/components/WidgetPreview.svelte +90 -129
  5. package/dist/components/WidgetPreview.svelte.d.ts +1 -0
  6. package/dist/components/color-select/ColorSelect.svelte +4 -4
  7. package/dist/components/editors/CompositionEditorApp.svelte +69 -15
  8. package/dist/components/editors/CompositionEditorApp.svelte.d.ts +20 -0
  9. package/dist/components/editors/EmbedEditor.svelte +33 -22
  10. package/dist/components/editors/EmbedEditor.svelte.d.ts +1 -1
  11. package/dist/components/editors/PropsEditor.svelte +74 -58
  12. package/dist/components/editors/PropsEditor.svelte.d.ts +3 -3
  13. package/dist/components/ic/AngleInputController.svelte +1 -1
  14. package/dist/components/ic/BrowserInputController.svelte +37 -29
  15. package/dist/components/ic/ColorPickerInputController.svelte +31 -15
  16. package/dist/components/ic/DateFieldInputController.svelte +27 -11
  17. package/dist/components/ic/DatePickerInputController.svelte +27 -17
  18. package/dist/components/ic/DropdownInputController.svelte +10 -5
  19. package/dist/components/ic/DropdownInputController.svelte.d.ts +1 -1
  20. package/dist/components/ic/FixedPositionInputController.svelte +12 -6
  21. package/dist/components/ic/FontInputController.svelte +32 -27
  22. package/dist/components/ic/InputControllerHeader.svelte +5 -1
  23. package/dist/components/ic/NumberControls.svelte +4 -4
  24. package/dist/components/ic/NumberInputController.svelte +2 -2
  25. package/dist/components/ic/PositionInputController.svelte +9 -4
  26. package/dist/components/ic/RangeCalendarInputController.svelte +26 -16
  27. package/dist/components/ic/RoundedCornersInputController.svelte +22 -17
  28. package/dist/components/ic/SliderInputController.svelte +7 -2
  29. package/dist/components/ic/SwitchInputController.svelte +12 -3
  30. package/dist/components/ic/TagsInputController.svelte +19 -4
  31. package/dist/components/ic/TextAreaInputController.svelte +59 -44
  32. package/dist/components/ic/TextAreaPreviewInputController.svelte +8 -3
  33. package/dist/components/ic/TextInputController.svelte +17 -1
  34. package/dist/components/ic/TimeInputController.svelte +2 -1
  35. package/dist/components/ic/VectorInputController.svelte +21 -16
  36. package/dist/components/ic/shared/ItemGrid.svelte +7 -3
  37. package/dist/components/ic/shared/RecentItemsSection.svelte +16 -11
  38. package/dist/components/ic/shared/RecentItemsSection.svelte.d.ts +1 -1
  39. package/dist/composition/compositionEditorController.d.ts +11 -2
  40. package/dist/composition/compositionEditorController.js +33 -21
  41. package/dist/composition/compositionHelpers.d.ts +3 -1
  42. package/dist/composition/compositionHelpers.js +4 -2
  43. package/dist/i18n/index.d.ts +4 -0
  44. package/dist/i18n/index.js +4 -0
  45. package/dist/i18n/labels.d.ts +15 -0
  46. package/dist/i18n/labels.js +57 -0
  47. package/dist/i18n/language.d.ts +228 -0
  48. package/dist/i18n/language.js +34 -0
  49. package/dist/i18n/languageKit.d.ts +54 -0
  50. package/dist/i18n/languageKit.js +102 -0
  51. package/dist/i18n/messages.d.ts +241 -0
  52. package/dist/i18n/messages.js +253 -0
  53. package/dist/i18n/translate.d.ts +6 -0
  54. package/dist/i18n/translate.js +8 -0
  55. package/dist/index.d.ts +2 -0
  56. package/dist/index.js +5 -0
  57. package/dist/utils/dockPlacement.d.ts +12 -0
  58. package/dist/utils/dockPlacement.js +13 -0
  59. package/dist/utils/fieldDirection.d.ts +13 -0
  60. package/dist/utils/fieldDirection.js +25 -0
  61. package/package.json +2 -2
@@ -2,6 +2,11 @@
2
2
  import { onMount, onDestroy, createEventDispatcher, tick } from 'svelte';
3
3
  import { GenerateLoader } from '@widgetic/design-system/components';
4
4
  import { lockPointerStyle } from '../utils/shadowDom';
5
+ import { useEditorLanguage } from '../i18n/language';
6
+ import PreviewChromeControls from './PreviewChromeControls.svelte';
7
+
8
+ // The words of the editor in the language of the page: the editor above this one chose it (see `provideEditorLanguage`).
9
+ const { t } = useEditorLanguage();
5
10
 
6
11
  const dispatch = createEventDispatcher<{
7
12
  load: { src: string | null };
@@ -88,6 +93,7 @@
88
93
  */
89
94
  export let resizeMode: 'concentric' | 'corner' = 'concentric';
90
95
  /** Initial / reset placement inside the stage. */
96
+ // rtl-ok: 'right-half' names the half of the stage by its place on the screen; the composition editor docks the preview itself
91
97
  export let defaultStageAlign: 'center' | 'right-half' = 'center';
92
98
  /** Observe stage size and fill it (desktop modal) */
93
99
  export let autoFitStage: boolean = false;
@@ -105,7 +111,6 @@
105
111
  let internalIframeSrc: string | null = null;
106
112
  let iframeLoaded = false;
107
113
  let iframeError = false;
108
- let errorMessage = '';
109
114
  let isWidgetPublished = false;
110
115
  let triedFallback = false;
111
116
  let pendingWidget: any = null;
@@ -117,10 +122,8 @@
117
122
  const MIN_WIDTH = 120;
118
123
  const MIN_HEIGHT = 80;
119
124
  const HANDLE_MARGIN = 6;
120
- /** Inset padding so resize handles stay inside bounds */
121
- const RESIZE_PAD_X = 12;
122
- const RESIZE_PAD_TOP = 12;
123
- const RESIZE_PAD_BOTTOM = 12;
125
+ /** Inset so resize handles stay inside bounds. 0 sits the frame on the stage edge. */
126
+ export let edgeInset = 12;
124
127
  const PREVIEW_CHROME_HEADER_H = 36;
125
128
  const PREVIEW_STROKE = '#3b82f6';
126
129
  const PREVIEW_STROKE_ALT = '#f97316';
@@ -155,8 +158,8 @@
155
158
  }
156
159
  if (hasFixedBounds && boundsWidth != null && boundsHeight != null) {
157
160
  return {
158
- maxWidth: Math.max(MIN_WIDTH, boundsWidth - RESIZE_PAD_X * 2),
159
- maxHeight: Math.max(MIN_HEIGHT, boundsHeight - RESIZE_PAD_TOP - RESIZE_PAD_BOTTOM - chromeH),
161
+ maxWidth: Math.max(MIN_WIDTH, boundsWidth - edgeInset * 2),
162
+ maxHeight: Math.max(MIN_HEIGHT, boundsHeight - edgeInset - edgeInset - chromeH),
160
163
  };
161
164
  }
162
165
  return { maxWidth: 720, maxHeight: 420 };
@@ -172,16 +175,17 @@
172
175
  shellWidth: number,
173
176
  shellHeight: number
174
177
  ): { left: number; top: number } {
175
- const maxLeft = Math.max(RESIZE_PAD_X, stageWidth - shellWidth - RESIZE_PAD_X);
176
- const maxTop = Math.max(RESIZE_PAD_TOP, stageHeight - shellHeight - RESIZE_PAD_BOTTOM);
178
+ const maxLeft = Math.max(edgeInset, stageWidth - shellWidth - edgeInset);
179
+ const maxTop = Math.max(edgeInset, stageHeight - shellHeight - edgeInset);
180
+ // rtl-ok: the half of the stage is named by its place on the screen, see the prop
177
181
  if (defaultStageAlign === 'right-half') {
178
182
  const rightHalfCenterX = stageWidth * 0.75;
179
- const left = clampDimension(rightHalfCenterX - shellWidth / 2, RESIZE_PAD_X, maxLeft);
180
- const top = clampDimension((stageHeight - shellHeight) / 2, RESIZE_PAD_TOP, maxTop);
183
+ const left = clampDimension(rightHalfCenterX - shellWidth / 2, edgeInset, maxLeft);
184
+ const top = clampDimension((stageHeight - shellHeight) / 2, edgeInset, maxTop);
181
185
  return { left, top };
182
186
  }
183
- const left = clampDimension((stageWidth - shellWidth) / 2, RESIZE_PAD_X, maxLeft);
184
- const top = clampDimension((stageHeight - shellHeight) / 2, RESIZE_PAD_TOP, maxTop);
187
+ const left = clampDimension((stageWidth - shellWidth) / 2, edgeInset, maxLeft);
188
+ const top = clampDimension((stageHeight - shellHeight) / 2, edgeInset, maxTop);
185
189
  return { left, top };
186
190
  }
187
191
 
@@ -266,10 +270,10 @@
266
270
 
267
271
  function onPointerMove(e: PointerEvent) {
268
272
  if (!stageEl) return;
269
- const maxLeft = Math.max(RESIZE_PAD_X, stageEl.clientWidth - shellWidth - RESIZE_PAD_X);
270
- const maxTop = Math.max(RESIZE_PAD_TOP, stageEl.clientHeight - shellHeight - RESIZE_PAD_BOTTOM);
271
- previewShellLeft = clampDimension(startLeft + (e.clientX - startX), RESIZE_PAD_X, maxLeft);
272
- previewShellTop = clampDimension(startTop + (e.clientY - startY), RESIZE_PAD_TOP, maxTop);
273
+ const maxLeft = Math.max(edgeInset, stageEl.clientWidth - shellWidth - edgeInset);
274
+ const maxTop = Math.max(edgeInset, stageEl.clientHeight - shellHeight - edgeInset);
275
+ previewShellLeft = clampDimension(startLeft + (e.clientX - startX), edgeInset, maxLeft);
276
+ previewShellTop = clampDimension(startTop + (e.clientY - startY), edgeInset, maxTop);
273
277
  }
274
278
 
275
279
  function endDrag() {
@@ -360,7 +364,7 @@
360
364
  $: showResizeHandles = enableResize && (mode === 'controlled' ? isContentReady : iframeLoaded);
361
365
  $: hasFixedBounds = boundsWidth != null && boundsHeight != null;
362
366
  $: containerPaddingStyle = enableResize
363
- ? `padding: ${RESIZE_PAD_TOP}px ${RESIZE_PAD_X}px ${RESIZE_PAD_BOTTOM}px ${RESIZE_PAD_X}px;`
367
+ ? `padding: ${edgeInset}px ${edgeInset}px ${edgeInset}px ${edgeInset}px;`
364
368
  : !hasFixedBounds && !compactChrome && (showUrlBar || mode === 'controlled')
365
369
  ? `padding: ${HANDLE_MARGIN}px;`
366
370
  : '';
@@ -378,8 +382,8 @@
378
382
  : '';
379
383
  $: iframeTitle =
380
384
  mode === 'controlled'
381
- ? 'Widget preview'
382
- : `Widget preview – ${widget?.name || 'Widget'}`;
385
+ ? $t('editor.preview.frameTitle')
386
+ : $t('editor.preview.frameTitleNamed', { name: widget?.name || $t('editor.preview.widgetName') });
383
387
 
384
388
  function buildWidgetUrl(w: any): string | null {
385
389
  if (!w) return null;
@@ -420,7 +424,6 @@
420
424
  iframeLoaded = false;
421
425
  iframeError = false;
422
426
  widgetReady = false;
423
- errorMessage = '';
424
427
  pendingMessages = [];
425
428
  isResolvingUrl = false;
426
429
 
@@ -476,7 +479,6 @@
476
479
  isWidgetPublished = false;
477
480
  iframeLoaded = false;
478
481
  iframeError = false;
479
- errorMessage = '';
480
482
  widgetReady = false;
481
483
  isResolvingUrl = false;
482
484
  }
@@ -594,7 +596,6 @@
594
596
  iframeError = true;
595
597
  iframeLoaded = false;
596
598
  widgetReady = false;
597
- errorMessage = 'Failed to load widget preview.';
598
599
  console.error('[WidgetPreview] Widget iframe onerror:', effectiveSrc);
599
600
  dispatch('error', { src: effectiveSrc });
600
601
  }
@@ -810,7 +811,7 @@
810
811
  <span
811
812
  class="preview-url-text flex-1 rounded border border-gray-300 px-2 py-1 text-xs text-gray-500 min-w-0 font-mono overflow-x-auto whitespace-nowrap"
812
813
  >
813
- {effectiveDisplayUrl || effectiveSrc || 'no URL'}
814
+ {effectiveDisplayUrl || effectiveSrc || $t('editor.preview.noUrl')}
814
815
  </span>
815
816
  <button
816
817
  type="button"
@@ -818,7 +819,7 @@
818
819
  const url = effectiveDisplayUrl || effectiveSrc;
819
820
  if (url) navigator.clipboard.writeText(url);
820
821
  }}
821
- title="Copy URL"
822
+ title={$t('editor.preview.copyUrl')}
822
823
  class="shrink-0 w-7 h-7 flex items-center justify-center rounded border border-gray-300 bg-white hover:bg-gray-100 text-sm cursor-pointer"
823
824
  >
824
825
  <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
@@ -827,14 +828,14 @@
827
828
  <button
828
829
  type="button"
829
830
  onclick={handleRefreshClick}
830
- title="Refresh preview"
831
+ title={$t('editor.preview.refresh')}
831
832
  class="shrink-0 w-7 h-7 flex items-center justify-center rounded border border-gray-300 bg-white hover:bg-gray-100 text-sm cursor-pointer"
832
833
  >🔄</button>
833
834
  {#if showViewportButtons && enableResize}
834
835
  <button
835
836
  type="button"
836
837
  onclick={handleFitToAreaClick}
837
- title="Fit preview to panel"
838
+ title={$t('editor.preview.fitToPanel')}
838
839
  class="preview-fit-to-area-btn shrink-0 w-7 h-7 flex items-center justify-center rounded border border-gray-300 bg-white hover:bg-gray-100 cursor-pointer"
839
840
  >
840
841
  <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6"/><path d="M9 21H3v-6"/><path d="M21 3l-7 7"/><path d="M3 21l7-7"/></svg>
@@ -842,7 +843,7 @@
842
843
  <button
843
844
  type="button"
844
845
  onclick={applyMobileViewport}
845
- title="Mobile viewport"
846
+ title={$t('editor.preview.mobileViewport')}
846
847
  class="preview-mobile-viewport-btn shrink-0 w-7 h-7 flex items-center justify-center rounded border border-gray-300 bg-white hover:bg-gray-100 cursor-pointer"
847
848
  >
848
849
  <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg>
@@ -850,7 +851,7 @@
850
851
  <button
851
852
  type="button"
852
853
  onclick={handleDesktopViewport}
853
- title="Desktop viewport (full window)"
854
+ title={$t('editor.preview.desktopViewport')}
854
855
  class="preview-desktop-viewport-btn shrink-0 w-7 h-7 flex items-center justify-center rounded border border-gray-300 bg-white hover:bg-gray-100 cursor-pointer"
855
856
  >
856
857
  <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
@@ -859,9 +860,9 @@
859
860
  <button
860
861
  type="button"
861
862
  onclick={handleOpenInTabClick}
862
- title="Open in new tab"
863
+ title={$t('editor.preview.openInNewTab')}
863
864
  class="shrink-0 px-2 h-7 flex items-center justify-center rounded border border-gray-300 bg-white hover:bg-gray-100 text-xs cursor-pointer whitespace-nowrap"
864
- >↗ New tab</button>
865
+ >{$t('editor.preview.newTab')}</button>
865
866
  {/if}
866
867
  </div>
867
868
  {/if}
@@ -880,7 +881,7 @@
880
881
 
881
882
  {:else if mode === 'standalone' && !widget}
882
883
  <div class="widget-preview-select-ct flex items-center justify-center h-full text-sm text-gray-400">
883
- Select a widget to preview
884
+ {$t('editor.preview.selectWidget')}
884
885
  </div>
885
886
 
886
887
  {:else if mode === 'standalone' && widget && !effectiveSrc && !isResolvingUrl && !iframeError}
@@ -892,11 +893,11 @@
892
893
  </svg>
893
894
  </div>
894
895
  <div>
895
- <p class="text-sm font-medium text-gray-600">{widget.name || 'Widget'}</p>
896
- <p class="text-xs text-gray-400 mt-1">Not published yet</p>
896
+ <p class="text-sm font-medium text-gray-600">{widget.name || $t('editor.preview.widgetName')}</p>
897
+ <p class="text-xs text-gray-400 mt-1">{$t('editor.preview.notPublished')}</p>
897
898
  </div>
898
899
  <p class="text-[11px] text-gray-300 max-w-[220px]">
899
- Publish the widget in Widget Creator to preview it here.
900
+ {$t('editor.preview.publishToPreview')}
900
901
  </p>
901
902
  </div>
902
903
  </div>
@@ -909,7 +910,7 @@
909
910
  <div
910
911
  class="preview-iframe-shell relative flex flex-col {enableResize ? 'preview-iframe-shell-positioned' : ''}"
911
912
  class:preview-iframe-shell-symmetrical={showSymmetricalStroke}
912
- style="{enableResize
913
+ style="{enableResize /* rtl-ok: the preview is placed in pixels from the left edge of the stage; the dock of the editor mirrors it */
913
914
  ? `position: absolute; left: ${previewShellLeft}px; top: ${previewShellTop}px; width: ${resizeWidth ? resizeWidth + 'px' : '100%'};`
914
915
  : `width: ${resizeWidth ? resizeWidth + 'px' : '100%'}; height: ${resizeHeight ? resizeHeight + 'px' : '100%'};`} transition: {isResizing || isDraggingPreview ? 'none' : 'width 0.2s, height 0.2s, left 0.15s, top 0.15s'};"
915
916
  >
@@ -921,69 +922,32 @@
921
922
  onpointerdown={startPreviewDrag}
922
923
  >
923
924
  {#if enableDrag}
924
- <span class="preview-chrome-drag-grip shrink-0 text-gray-400 text-xs leading-none" title="Drag preview" aria-hidden="true">⠿</span>
925
- {/if}
926
- <span class="preview-chrome-title text-[11px] font-semibold uppercase tracking-wide text-gray-500 shrink-0">Preview</span>
927
- {#if showChromeDeviceToggle}
928
- <div
929
- class="preview-chrome-device-toggle ml-auto flex shrink-0 items-center overflow-hidden rounded border border-gray-300"
930
- onpointerdown={(event) => event.stopPropagation()}
931
- >
932
- <button
933
- type="button"
934
- class="preview-chrome-device-desktop flex h-6 w-6 items-center justify-center {!deviceMobile ? 'bg-gray-100 text-gray-800' : 'bg-white text-gray-400'} hover:text-gray-800 focus:outline-none focus-visible:outline-none"
935
- aria-label="Desktop preview"
936
- aria-pressed={!deviceMobile}
937
- onclick={() => dispatch('deviceView', { mobile: false })}
938
- onpointerdown={(event) => event.stopPropagation()}
939
- >
940
- <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
941
- </button>
942
- <button
943
- type="button"
944
- class="preview-chrome-device-mobile flex h-6 w-6 items-center justify-center border-l border-gray-300 {deviceMobile ? 'bg-gray-100 text-gray-800' : 'bg-white text-gray-400'} hover:text-gray-800 focus:outline-none focus-visible:outline-none"
945
- aria-label="Mobile preview"
946
- aria-pressed={deviceMobile}
947
- onclick={() => dispatch('deviceView', { mobile: true })}
948
- onpointerdown={(event) => event.stopPropagation()}
949
- >
950
- <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg>
951
- </button>
952
- </div>
953
- {/if}
954
- {#if showDimensionInputs}
955
- <div class="preview-chrome-dimensions flex items-center gap-1.5 {showChromeDeviceToggle ? '' : 'ml-auto'}">
956
- <label class="preview-dim-label flex items-center gap-1 text-[10px] font-semibold text-gray-500">
957
- <span>W</span>
958
- <input
959
- type="number"
960
- min={MIN_WIDTH}
961
- class="preview-dim-input w-14 h-6 rounded border border-gray-300 bg-white px-1 text-[11px] text-gray-800 focus:outline-none focus:border-blue-400 focus-visible:outline-none"
962
- value={widthInputValue}
963
- oninput={(e) => {
964
- widthInputValue = e.currentTarget.value;
965
- applyDimensionFromInput('width', e.currentTarget.value);
966
- }}
967
- onpointerdown={(e) => e.stopPropagation()}
968
- />
969
- </label>
970
- <span class="text-[10px] text-gray-400">×</span>
971
- <label class="preview-dim-label flex items-center gap-1 text-[10px] font-semibold text-gray-500">
972
- <span>H</span>
973
- <input
974
- type="number"
975
- min={MIN_HEIGHT}
976
- class="preview-dim-input w-14 h-6 rounded border border-gray-300 bg-white px-1 text-[11px] text-gray-800 focus:outline-none focus:border-blue-400 focus-visible:outline-none"
977
- value={heightInputValue}
978
- oninput={(e) => {
979
- heightInputValue = e.currentTarget.value;
980
- applyDimensionFromInput('height', e.currentTarget.value);
981
- }}
982
- onpointerdown={(e) => e.stopPropagation()}
983
- />
984
- </label>
985
- </div>
925
+ <span class="preview-chrome-drag-grip shrink-0 text-gray-400 text-xs leading-none" title={$t('editor.preview.dragHandle')} aria-hidden="true">⠿</span>
986
926
  {/if}
927
+ <span class="preview-chrome-title text-[11px] font-semibold uppercase tracking-wide text-gray-500 shrink-0">{$t('editor.preview.title')}</span>
928
+ <PreviewChromeControls
929
+ class="{showChromeDeviceToggle || showDimensionInputs ? 'ms-auto' : ''}"
930
+ mobile={deviceMobile}
931
+ showDevices={showChromeDeviceToggle}
932
+ showDimensions={showDimensionInputs}
933
+ widthValue={widthInputValue}
934
+ heightValue={heightInputValue}
935
+ minWidth={MIN_WIDTH}
936
+ minHeight={MIN_HEIGHT}
937
+ desktopLabel={$t('editor.preview.desktop')}
938
+ mobileLabel={$t('editor.preview.mobile')}
939
+ widthLabel={$t('editor.preview.widthShort')}
940
+ heightLabel={$t('editor.preview.heightShort')}
941
+ onDevice={(nextMobile) => dispatch('deviceView', { mobile: nextMobile })}
942
+ onWidth={(value) => {
943
+ widthInputValue = value;
944
+ applyDimensionFromInput('width', value);
945
+ }}
946
+ onHeight={(value) => {
947
+ heightInputValue = value;
948
+ applyDimensionFromInput('height', value);
949
+ }}
950
+ />
987
951
  </div>
988
952
  {/if}
989
953
 
@@ -1021,20 +985,20 @@
1021
985
  <svg class="w-8 h-8 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
1022
986
  <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" />
1023
987
  </svg>
1024
- <span class="text-sm text-gray-500">{@html errorMessage || 'Failed to load widget.'}</span>
988
+ <span class="text-sm text-gray-500">{$t('editor.preview.loadFailedPreview')}</span>
1025
989
  </div>
1026
990
  </div>
1027
991
  {:else if mode === 'controlled' && previewError && !isGeneratingCode}
1028
992
  <div class="preview-error-overlay absolute inset-0 z-10 flex h-full flex-col items-center justify-center bg-red-50/95 text-gray-700 p-6">
1029
993
  <div class="text-4xl mb-3">⚠️</div>
1030
- <div class="font-semibold text-lg text-red-600 mb-2">Preview Unavailable</div>
994
+ <div class="font-semibold text-lg text-red-600 mb-2">{$t('editor.preview.unavailable')}</div>
1031
995
  <div class="text-sm text-center max-w-md whitespace-pre-line text-gray-600 mb-4">{previewError}</div>
1032
996
  <div class="flex gap-3">
1033
997
  <button type="button" onclick={() => dispatch('retry')} class="px-3 py-1.5 text-xs rounded border border-blue-500 text-blue-600 bg-white hover:bg-blue-50 cursor-pointer">
1034
- 🔄 Retry {previewRetryCount > 0 ? `(${previewRetryCount})` : ''}
998
+ 🔄 {$t('editor.preview.retry')} {previewRetryCount > 0 ? `(${previewRetryCount})` : ''}
1035
999
  </button>
1036
1000
  <button type="button" onclick={() => dispatch('openInTab')} class="px-3 py-1.5 text-xs rounded border border-gray-300 text-gray-600 bg-white hover:bg-gray-100 cursor-pointer">
1037
- Open in new tab
1001
+ {$t('editor.preview.openInNewTab')}
1038
1002
  </button>
1039
1003
  </div>
1040
1004
  </div>
@@ -1042,21 +1006,17 @@
1042
1006
 
1043
1007
  {#if showStandaloneLoading}
1044
1008
  <div class="widget-preview-loading-ct absolute inset-0 z-10 flex items-center justify-center bg-gray-50/80">
1045
- <GenerateLoader title="Loading widget…" subtitle="" size="md" />
1009
+ <GenerateLoader title={$t('editor.preview.loadingWidget')} subtitle="" size="md" />
1046
1010
  </div>
1047
1011
  {:else if showControlledLoading && !isGeneratingCode}
1048
1012
  <div class="loading-overlay absolute inset-0 z-20 flex items-center justify-center bg-gray-50">
1049
1013
  <GenerateLoader
1050
- title={!src && isLoadingUrl
1051
- ? 'Loading Preview…'
1052
- : previewHydrating
1053
- ? 'Loading Preview…'
1054
- : 'Loading Preview…'}
1014
+ title={$t('editor.preview.loadingPreview')}
1055
1015
  subtitle={!src && isLoadingUrl
1056
- ? 'Checking widget on Worker…'
1016
+ ? $t('editor.preview.checkingWidget')
1057
1017
  : previewHydrating
1058
- ? 'Applying composition settings…'
1059
- : 'Connecting to the widget preview…'}
1018
+ ? $t('editor.preview.applyingSettings')
1019
+ : $t('editor.preview.connecting')}
1060
1020
  size="lg"
1061
1021
  />
1062
1022
  </div>
@@ -1067,17 +1027,17 @@
1067
1027
  <div class="text-5xl mb-4">⚠️</div>
1068
1028
  <div class="font-semibold text-xl mb-2">
1069
1029
  {previewError && /TypeError|Uncaught|is not a function|is not defined/i.test(previewError)
1070
- ? 'Widget runtime error'
1071
- : 'Code Has Compilation Errors'}
1030
+ ? $t('editor.preview.runtimeError')
1031
+ : $t('editor.preview.compilationErrors')}
1072
1032
  </div>
1073
1033
  <div class="text-sm text-center text-red-600 mb-4 max-w-md whitespace-pre-line">
1074
1034
  {previewError
1075
- || `The generated code has syntax or compilation errors (HTTP ${previewRuntimeErrorStatus}).`}
1035
+ || $t('editor.preview.compilationErrorsDetail', { status: previewRuntimeErrorStatus ?? '?' })}
1076
1036
  </div>
1077
1037
  <div class="flex gap-3 flex-wrap justify-center">
1078
- <button type="button" onclick={() => dispatch('fixErrors')} class="px-4 py-2 text-xs rounded bg-red-600 hover:bg-red-700 text-white cursor-pointer border-0">🔧 Fix Errors</button>
1079
- <button type="button" onclick={() => dispatch('viewErrors')} class="px-3 py-1.5 text-xs rounded border border-orange-400 text-orange-600 bg-white hover:bg-orange-50 cursor-pointer">📋 View Errors</button>
1080
- <button type="button" onclick={() => dispatch('retryPreview')} class="px-3 py-1.5 text-xs rounded border border-red-300 text-red-600 bg-white hover:bg-red-50 cursor-pointer">🔄 Retry Preview</button>
1038
+ <button type="button" onclick={() => dispatch('fixErrors')} class="px-4 py-2 text-xs rounded bg-red-600 hover:bg-red-700 text-white cursor-pointer border-0">🔧 {$t('editor.preview.fixErrors')}</button>
1039
+ <button type="button" onclick={() => dispatch('viewErrors')} class="px-3 py-1.5 text-xs rounded border border-orange-400 text-orange-600 bg-white hover:bg-orange-50 cursor-pointer">📋 {$t('editor.preview.viewErrors')}</button>
1040
+ <button type="button" onclick={() => dispatch('retryPreview')} class="px-3 py-1.5 text-xs rounded border border-red-300 text-red-600 bg-white hover:bg-red-50 cursor-pointer">🔄 {$t('editor.preview.retryPreview')}</button>
1081
1041
  </div>
1082
1042
  </div>
1083
1043
  {/if}
@@ -1109,7 +1069,7 @@
1109
1069
  {/if}
1110
1070
 
1111
1071
  {#if mode === 'controlled' && isGeneratingCode}
1112
- <GenerateLoader overlay title="Generating code…" subtitle="" size="lg" />
1072
+ <GenerateLoader overlay title={$t('editor.preview.generatingCode')} subtitle="" size="lg" />
1113
1073
  {/if}
1114
1074
  </div>
1115
1075
  </div>
@@ -1119,22 +1079,22 @@
1119
1079
  {:else if mode === 'controlled'}
1120
1080
  {#if isLoadingUrl}
1121
1081
  <div class="flex h-full flex-col items-center justify-center p-6">
1122
- <GenerateLoader title="Loading Preview…" subtitle="" size="lg" />
1082
+ <GenerateLoader title={$t('editor.preview.loadingPreview')} subtitle="" size="lg" />
1123
1083
  </div>
1124
1084
  {:else}
1125
1085
  <div class="flex h-full flex-col items-center justify-center p-6 text-center text-sm text-gray-600">
1126
1086
  {#if isGeneratingCode}
1127
- <GenerateLoader title="Generating code…" subtitle="" size="lg" />
1087
+ <GenerateLoader title={$t('editor.preview.generatingCode')} subtitle="" size="lg" />
1128
1088
  {:else if previewError === 'NO_CODE_YET'}
1129
- <div class="font-semibold text-lg text-gray-700 mb-2">No Code Generated Yet</div>
1089
+ <div class="font-semibold text-lg text-gray-700 mb-2">{$t('editor.preview.noCodeYet')}</div>
1130
1090
  {:else if previewError}
1131
- <div class="font-semibold text-lg text-red-600 mb-2">Preview Unavailable</div>
1091
+ <div class="font-semibold text-lg text-red-600 mb-2">{$t('editor.preview.unavailable')}</div>
1132
1092
  <div class="text-sm text-gray-600 mb-4">{previewError}</div>
1133
- <button type="button" onclick={() => dispatch('retry')} class="px-3 py-1.5 text-xs rounded border border-blue-500 text-blue-600 bg-white hover:bg-blue-50 cursor-pointer">🔄 Retry Build</button>
1093
+ <button type="button" onclick={() => dispatch('retry')} class="px-3 py-1.5 text-xs rounded border border-blue-500 text-blue-600 bg-white hover:bg-blue-50 cursor-pointer">🔄 {$t('editor.preview.retryBuild')}</button>
1134
1094
  {:else if previewLoading}
1135
- <div class="text-lg font-medium text-gray-700">Building Preview...</div>
1095
+ <div class="text-lg font-medium text-gray-700">{$t('editor.preview.building')}</div>
1136
1096
  {:else}
1137
- <div class="text-lg text-gray-600 mb-2">🖼️ No Preview Available</div>
1097
+ <div class="text-lg text-gray-600 mb-2">🖼️ {$t('editor.preview.noPreview')}</div>
1138
1098
  {/if}
1139
1099
  </div>
1140
1100
  {/if}
@@ -1178,10 +1138,11 @@
1178
1138
  box-shadow: none;
1179
1139
  }
1180
1140
 
1181
- .preview-resize-handle-tl { top: -5px; left: -5px; cursor: nwse-resize; }
1182
- .preview-resize-handle-tr { top: -5px; right: -5px; cursor: nesw-resize; }
1183
- .preview-resize-handle-bl { bottom: -5px; left: -5px; cursor: nesw-resize; }
1184
- .preview-resize-handle-br { bottom: -5px; right: -5px; cursor: nwse-resize; }
1141
+ /* The four corner handles of the preview are on the four corners of the box, a place on the screen: the set is the same in both directions. */
1142
+ .preview-resize-handle-tl { top: -5px; left: -5px; cursor: nwse-resize; } /* rtl-ok: top left corner */
1143
+ .preview-resize-handle-tr { top: -5px; right: -5px; cursor: nesw-resize; } /* rtl-ok: top right corner */
1144
+ .preview-resize-handle-bl { bottom: -5px; left: -5px; cursor: nesw-resize; } /* rtl-ok: bottom left corner */
1145
+ .preview-resize-handle-br { bottom: -5px; right: -5px; cursor: nwse-resize; } /* rtl-ok: bottom right corner */
1185
1146
 
1186
1147
  .preview-resize-handle:hover {
1187
1148
  background: #eff6ff;
@@ -66,6 +66,7 @@ declare const WidgetPreview: $$__sveltets_2_IsomorphicComponent<{
66
66
  resizeWidth?: number | null;
67
67
  resizeHeight?: number | null;
68
68
  iframeEl?: HTMLIFrameElement | null;
69
+ /** Inset so resize handles stay inside bounds. 0 sits the frame on the stage edge. */ edgeInset?: number;
69
70
  getResizeLimits?: () => {
70
71
  maxWidth: number;
71
72
  maxHeight: number;
@@ -173,8 +173,10 @@ $: if (rgb && typeof a === 'number') {
173
173
 
174
174
  <!-- svelte-ignore a11y-no-noninteractive-tabindex -->
175
175
  <!-- svelte-ignore a11y-no-static-element-interactions -->
176
+ <!-- The picker is a drawing: saturation grows to the right and hue is a strip next to it, on every screen, so it is a left to right island. -->
176
177
  <div
177
178
  class={$$props.class}
179
+ dir="ltr"
178
180
  tabindex="0"
179
181
  style="width: {width}px; height: {height}px;"
180
182
  on:keydown={onKeydown}
@@ -184,8 +186,7 @@ $: if (rgb && typeof a === 'number') {
184
186
  id="{instanceId}_sv"
185
187
  width={pickerSize}
186
188
  height={pickerSize}
187
- style:top="{border_size}px"
188
- style:left="{border_size}px"
189
+ style="top: {border_size}px; left: {border_size}px; /* rtl-ok: the saturation square is drawn from the left edge of the picker */"
189
190
  use:pointer={update_sv}
190
191
  use:render_main_image={color.h}
191
192
  />
@@ -193,8 +194,7 @@ $: if (rgb && typeof a === 'number') {
193
194
  <canvas
194
195
  width={sliderWidth}
195
196
  height={pickerSize}
196
- style:top="{border_size}px"
197
- style:left="{pickerSize + gap_size}px"
197
+ style="top: {border_size}px; left: {pickerSize + gap_size}px; /* rtl-ok: the hue strip is drawn next to the square, from the left edge of the picker */"
198
198
  use:pointer={update_h}
199
199
  use:render_slider_image
200
200
  />