@widgetic/editor 4.0.10 โ 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.
- package/README.md +242 -0
- package/dist/components/WidgetPreview.svelte +123 -86
- package/dist/components/WidgetPreview.svelte.d.ts +8 -0
- package/dist/components/color-select/ColorSelect.svelte +4 -4
- package/dist/components/editors/CompositionEditorApp.svelte +562 -0
- package/dist/components/editors/CompositionEditorApp.svelte.d.ts +95 -0
- package/dist/components/editors/EmbedEditor.svelte +33 -22
- package/dist/components/editors/EmbedEditor.svelte.d.ts +1 -1
- package/dist/components/editors/PropsEditor.svelte +267 -850
- package/dist/components/editors/PropsEditor.svelte.d.ts +10 -3
- package/dist/components/ic/AngleInputController.svelte +1 -1
- package/dist/components/ic/BrowserInputController.svelte +58 -31
- package/dist/components/ic/ColorPickerInputController.svelte +55 -21
- package/dist/components/ic/DateFieldInputController.svelte +34 -14
- package/dist/components/ic/DatePickerInputController.svelte +37 -21
- package/dist/components/ic/DropdownInputController.svelte +21 -9
- package/dist/components/ic/DropdownInputController.svelte.d.ts +1 -1
- package/dist/components/ic/FixedPositionInputController.svelte +12 -6
- package/dist/components/ic/FontInputController.svelte +100 -76
- package/dist/components/ic/InputControllerHeader.svelte +16 -9
- package/dist/components/ic/NumberControls.svelte +4 -4
- package/dist/components/ic/NumberInputController.svelte +6 -4
- package/dist/components/ic/PositionInputController.svelte +23 -15
- package/dist/components/ic/RangeCalendarInputController.svelte +36 -20
- package/dist/components/ic/RoundedCornersInputController.svelte +39 -26
- package/dist/components/ic/SliderInputController.svelte +7 -2
- package/dist/components/ic/SwitchInputController.svelte +12 -3
- package/dist/components/ic/TagsInputController.svelte +19 -4
- package/dist/components/ic/TextAreaInputController.svelte +79 -51
- package/dist/components/ic/TextAreaPreviewInputController.svelte +27 -72
- package/dist/components/ic/TextInputController.svelte +17 -1
- package/dist/components/ic/TimeInputController.svelte +11 -4
- package/dist/components/ic/VectorInputController.svelte +101 -70
- package/dist/components/ic/shared/ItemGrid.svelte +12 -4
- package/dist/components/ic/shared/RecentItemsSection.svelte +16 -11
- package/dist/components/ic/shared/RecentItemsSection.svelte.d.ts +1 -1
- package/dist/composition/compositionApi.d.ts +95 -0
- package/dist/composition/compositionApi.js +289 -0
- package/dist/composition/compositionEditorController.d.ts +263 -0
- package/dist/composition/compositionEditorController.js +746 -0
- package/dist/composition/compositionHelpers.d.ts +74 -0
- package/dist/composition/compositionHelpers.js +405 -0
- package/dist/i18n/index.d.ts +4 -0
- package/dist/i18n/index.js +4 -0
- package/dist/i18n/labels.d.ts +15 -0
- package/dist/i18n/labels.js +57 -0
- package/dist/i18n/language.d.ts +228 -0
- package/dist/i18n/language.js +34 -0
- package/dist/i18n/languageKit.d.ts +54 -0
- package/dist/i18n/languageKit.js +102 -0
- package/dist/i18n/messages.d.ts +241 -0
- package/dist/i18n/messages.js +253 -0
- package/dist/i18n/translate.d.ts +6 -0
- package/dist/i18n/translate.js +8 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +8 -0
- package/dist/mediaLibraryContext.d.ts +10 -0
- package/dist/mediaLibraryContext.js +30 -0
- package/dist/utils/FontManager.d.ts +21 -5
- package/dist/utils/FontManager.js +62 -67
- package/dist/utils/VectorManager.d.ts +0 -1
- package/dist/utils/VectorManager.js +10 -12
- package/dist/utils/dockPlacement.d.ts +12 -0
- package/dist/utils/dockPlacement.js +13 -0
- package/dist/utils/editorStorage.d.ts +23 -0
- package/dist/utils/editorStorage.js +95 -0
- package/dist/utils/fieldDirection.d.ts +13 -0
- package/dist/utils/fieldDirection.js +25 -0
- package/dist/utils/fontUtils.js +5 -3
- package/dist/utils/htmlSafety.d.ts +21 -0
- package/dist/utils/htmlSafety.js +38 -0
- package/dist/utils/itemReorder.d.ts +90 -0
- package/dist/utils/itemReorder.js +354 -0
- package/dist/utils/recentColors.js +4 -2
- package/dist/utils/richTextPreview.d.ts +38 -0
- package/dist/utils/richTextPreview.js +207 -0
- package/dist/utils/scrollActivity.d.ts +14 -0
- package/dist/utils/scrollActivity.js +63 -0
- package/dist/utils/shadowDom.d.ts +67 -0
- package/dist/utils/shadowDom.js +211 -0
- package/dist/utils/vectorUtils.js +5 -3
- package/dist/utils/visibilityCondition.d.ts +40 -0
- package/dist/utils/visibilityCondition.js +535 -0
- package/package.json +7 -6
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount, onDestroy, createEventDispatcher, tick } from 'svelte';
|
|
3
3
|
import { GenerateLoader } from '@widgetic/design-system/components';
|
|
4
|
+
import { lockPointerStyle } from '../utils/shadowDom';
|
|
5
|
+
import { useEditorLanguage } from '../i18n/language';
|
|
6
|
+
|
|
7
|
+
// The words of the editor in the language of the page: the editor above this one chose it (see `provideEditorLanguage`).
|
|
8
|
+
const { t } = useEditorLanguage();
|
|
4
9
|
|
|
5
10
|
const dispatch = createEventDispatcher<{
|
|
6
11
|
load: { src: string | null };
|
|
7
12
|
error: { src: string | null };
|
|
8
13
|
refresh: void;
|
|
9
14
|
openInTab: void;
|
|
10
|
-
|
|
15
|
+
// `source` says where a size came from, so a host can tell a size the user chose ('drag', 'input', 'fit',
|
|
16
|
+
// 'device') from one the box around the preview forced on it ('clamp': the box is smaller than the size asked for).
|
|
17
|
+
resize: { width: number; height: number; source?: 'drag' | 'input' | 'fit' | 'device' | 'clamp' };
|
|
11
18
|
mobileViewport: { width: number; height: number };
|
|
12
19
|
desktopViewport: void;
|
|
13
20
|
deviceView: { mobile: boolean };
|
|
@@ -50,6 +57,17 @@
|
|
|
50
57
|
export let debugMode: boolean = false;
|
|
51
58
|
export let iframeReloadKey = 0;
|
|
52
59
|
|
|
60
|
+
// โโ What the preview iframe may do โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
61
|
+
/**
|
|
62
|
+
* The iframe's `sandbox` tokens. `undefined` keeps what each mode always had (controlled: no sandbox; standalone:
|
|
63
|
+
* scripts, popups, forms and its own origin); a string is used exactly as given; `null` means no `sandbox` at all.
|
|
64
|
+
* A page that embeds the editor for its own visitors passes a list without `allow-top-navigation`, so that a
|
|
65
|
+
* widget's code cannot send the visitor's whole tab somewhere else.
|
|
66
|
+
*/
|
|
67
|
+
export let sandbox: string | null | undefined = undefined;
|
|
68
|
+
/** The iframe's `allow` attribute: which of the page's permissions the preview may use. */
|
|
69
|
+
export let allow: string = 'clipboard-read; clipboard-write';
|
|
70
|
+
|
|
53
71
|
// โโ Layout / chrome โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
54
72
|
export let width: string = '100%';
|
|
55
73
|
export let height: string = '100%';
|
|
@@ -74,6 +92,7 @@
|
|
|
74
92
|
*/
|
|
75
93
|
export let resizeMode: 'concentric' | 'corner' = 'concentric';
|
|
76
94
|
/** Initial / reset placement inside the stage. */
|
|
95
|
+
// rtl-ok: 'right-half' names the half of the stage by its place on the screen; the composition editor docks the preview itself
|
|
77
96
|
export let defaultStageAlign: 'center' | 'right-half' = 'center';
|
|
78
97
|
/** Observe stage size and fill it (desktop modal) */
|
|
79
98
|
export let autoFitStage: boolean = false;
|
|
@@ -91,7 +110,6 @@
|
|
|
91
110
|
let internalIframeSrc: string | null = null;
|
|
92
111
|
let iframeLoaded = false;
|
|
93
112
|
let iframeError = false;
|
|
94
|
-
let errorMessage = '';
|
|
95
113
|
let isWidgetPublished = false;
|
|
96
114
|
let triedFallback = false;
|
|
97
115
|
let pendingWidget: any = null;
|
|
@@ -160,6 +178,7 @@
|
|
|
160
178
|
): { left: number; top: number } {
|
|
161
179
|
const maxLeft = Math.max(RESIZE_PAD_X, stageWidth - shellWidth - RESIZE_PAD_X);
|
|
162
180
|
const maxTop = Math.max(RESIZE_PAD_TOP, stageHeight - shellHeight - RESIZE_PAD_BOTTOM);
|
|
181
|
+
// rtl-ok: the half of the stage is named by its place on the screen, see the prop
|
|
163
182
|
if (defaultStageAlign === 'right-half') {
|
|
164
183
|
const rightHalfCenterX = stageWidth * 0.75;
|
|
165
184
|
const left = clampDimension(rightHalfCenterX - shellWidth / 2, RESIZE_PAD_X, maxLeft);
|
|
@@ -222,11 +241,19 @@
|
|
|
222
241
|
heightInputValue = String(Math.round(resizeHeight));
|
|
223
242
|
}
|
|
224
243
|
if (resizeWidth != null && resizeHeight != null) {
|
|
225
|
-
dispatch('resize', { width: resizeWidth, height: resizeHeight });
|
|
244
|
+
dispatch('resize', { width: resizeWidth, height: resizeHeight, source: 'input' });
|
|
226
245
|
}
|
|
227
246
|
console.log('[WidgetPreview] Dimension input', { dimension, value: parsed });
|
|
228
247
|
}
|
|
229
248
|
|
|
249
|
+
/**
|
|
250
|
+
* While a drag or a resize lasts the pointer shows the matching cursor and nothing is selected as it moves
|
|
251
|
+
* (`lockPointerStyle`, in `shadowDom.ts`: set on the host of the shadow root the editor lives in, or on <body> when
|
|
252
|
+
* there is none, and put back afterwards). Puts back what the running drag or resize changed: the pointer style,
|
|
253
|
+
* and the listeners on `document`.
|
|
254
|
+
*/
|
|
255
|
+
let finishPointerGesture: (() => void) | null = null;
|
|
256
|
+
|
|
230
257
|
function startPreviewDrag(event: PointerEvent) {
|
|
231
258
|
if (!enableDrag || isResizing) return;
|
|
232
259
|
const target = event.target as HTMLElement | null;
|
|
@@ -252,22 +279,25 @@
|
|
|
252
279
|
|
|
253
280
|
function endDrag() {
|
|
254
281
|
isDraggingPreview = false;
|
|
255
|
-
|
|
256
|
-
document.removeEventListener('pointerup', endDrag);
|
|
257
|
-
document.removeEventListener('pointercancel', endDrag);
|
|
258
|
-
document.body.style.removeProperty('user-select');
|
|
259
|
-
document.body.style.removeProperty('cursor');
|
|
282
|
+
finishPointerGesture?.();
|
|
260
283
|
console.log('[WidgetPreview] Preview drag end', {
|
|
261
284
|
left: previewShellLeft,
|
|
262
285
|
top: previewShellTop,
|
|
263
286
|
});
|
|
264
287
|
}
|
|
265
288
|
|
|
266
|
-
|
|
267
|
-
|
|
289
|
+
finishPointerGesture?.();
|
|
290
|
+
const unlockPointerStyle = lockPointerStyle(event.target, 'move');
|
|
268
291
|
document.addEventListener('pointermove', onPointerMove);
|
|
269
292
|
document.addEventListener('pointerup', endDrag);
|
|
270
293
|
document.addEventListener('pointercancel', endDrag);
|
|
294
|
+
finishPointerGesture = () => {
|
|
295
|
+
document.removeEventListener('pointermove', onPointerMove);
|
|
296
|
+
document.removeEventListener('pointerup', endDrag);
|
|
297
|
+
document.removeEventListener('pointercancel', endDrag);
|
|
298
|
+
unlockPointerStyle();
|
|
299
|
+
finishPointerGesture = null;
|
|
300
|
+
};
|
|
271
301
|
}
|
|
272
302
|
|
|
273
303
|
/** Firefox/Chrome skip painting a cross-origin iframe that started at opacity 0 or inside a CSS filter until a reflow (opening DevTools). */
|
|
@@ -308,7 +338,7 @@
|
|
|
308
338
|
}
|
|
309
339
|
resizeWidth = limits.maxWidth;
|
|
310
340
|
resizeHeight = limits.maxHeight;
|
|
311
|
-
dispatch('resize', { width: limits.maxWidth, height: limits.maxHeight });
|
|
341
|
+
dispatch('resize', { width: limits.maxWidth, height: limits.maxHeight, source: 'fit' });
|
|
312
342
|
previewPositionReady = false;
|
|
313
343
|
requestAnimationFrame(() => {
|
|
314
344
|
resetPreviewPosition();
|
|
@@ -353,8 +383,8 @@
|
|
|
353
383
|
: '';
|
|
354
384
|
$: iframeTitle =
|
|
355
385
|
mode === 'controlled'
|
|
356
|
-
? '
|
|
357
|
-
:
|
|
386
|
+
? $t('editor.preview.frameTitle')
|
|
387
|
+
: $t('editor.preview.frameTitleNamed', { name: widget?.name || $t('editor.preview.widgetName') });
|
|
358
388
|
|
|
359
389
|
function buildWidgetUrl(w: any): string | null {
|
|
360
390
|
if (!w) return null;
|
|
@@ -395,7 +425,6 @@
|
|
|
395
425
|
iframeLoaded = false;
|
|
396
426
|
iframeError = false;
|
|
397
427
|
widgetReady = false;
|
|
398
|
-
errorMessage = '';
|
|
399
428
|
pendingMessages = [];
|
|
400
429
|
isResolvingUrl = false;
|
|
401
430
|
|
|
@@ -451,7 +480,6 @@
|
|
|
451
480
|
isWidgetPublished = false;
|
|
452
481
|
iframeLoaded = false;
|
|
453
482
|
iframeError = false;
|
|
454
|
-
errorMessage = '';
|
|
455
483
|
widgetReady = false;
|
|
456
484
|
isResolvingUrl = false;
|
|
457
485
|
}
|
|
@@ -569,12 +597,14 @@
|
|
|
569
597
|
iframeError = true;
|
|
570
598
|
iframeLoaded = false;
|
|
571
599
|
widgetReady = false;
|
|
572
|
-
errorMessage = 'Failed to load widget preview.';
|
|
573
600
|
console.error('[WidgetPreview] Widget iframe onerror:', effectiveSrc);
|
|
574
601
|
dispatch('error', { src: effectiveSrc });
|
|
575
602
|
}
|
|
576
603
|
|
|
577
604
|
function handleMessage(event: MessageEvent) {
|
|
605
|
+
// Only the preview iframe of this component speaks to it: any other window on the page (a partner's own frames
|
|
606
|
+
// and scripts included) can post messages to this window too.
|
|
607
|
+
if (!iframeEl?.contentWindow || event.source !== iframeEl.contentWindow) return;
|
|
578
608
|
const type = event.data?.type;
|
|
579
609
|
if (type === 'widgetic:ready' || type === 'widgetic:pong') {
|
|
580
610
|
if (mode === 'standalone') {
|
|
@@ -597,7 +627,8 @@
|
|
|
597
627
|
|
|
598
628
|
function handleOpenInTabClick() {
|
|
599
629
|
const url = effectiveDisplayUrl || effectiveSrc;
|
|
600
|
-
|
|
630
|
+
// noopener: the page that opens must not get a handle on the page that opened it (which can be a partner's page).
|
|
631
|
+
if (url) window.open(url, '_blank', 'noopener,noreferrer');
|
|
601
632
|
dispatch('openInTab');
|
|
602
633
|
}
|
|
603
634
|
|
|
@@ -607,7 +638,7 @@
|
|
|
607
638
|
const h = Math.min(mobileViewportHeight, maxHeight);
|
|
608
639
|
resizeWidth = w;
|
|
609
640
|
resizeHeight = h;
|
|
610
|
-
dispatch('resize', { width: w, height: h });
|
|
641
|
+
dispatch('resize', { width: w, height: h, source: 'device' });
|
|
611
642
|
dispatch('mobileViewport', { width: w, height: h });
|
|
612
643
|
}
|
|
613
644
|
|
|
@@ -629,7 +660,7 @@
|
|
|
629
660
|
if (nextW !== resizeWidth || nextH !== resizeHeight) {
|
|
630
661
|
resizeWidth = nextW;
|
|
631
662
|
resizeHeight = nextH;
|
|
632
|
-
dispatch('resize', { width: nextW, height: nextH });
|
|
663
|
+
dispatch('resize', { width: nextW, height: nextH, source: 'clamp' });
|
|
633
664
|
}
|
|
634
665
|
}
|
|
635
666
|
|
|
@@ -689,35 +720,40 @@
|
|
|
689
720
|
}
|
|
690
721
|
|
|
691
722
|
if (resizeWidth != null && resizeHeight != null) {
|
|
692
|
-
dispatch('resize', { width: resizeWidth, height: resizeHeight });
|
|
723
|
+
dispatch('resize', { width: resizeWidth, height: resizeHeight, source: 'drag' });
|
|
693
724
|
}
|
|
694
725
|
}
|
|
695
726
|
|
|
696
727
|
function endResize() {
|
|
697
728
|
isResizing = false;
|
|
698
729
|
// Keep orange stroke while Alt remains held (composition corner mode).
|
|
699
|
-
|
|
700
|
-
document.removeEventListener('pointerup', endResize);
|
|
701
|
-
document.removeEventListener('pointercancel', endResize);
|
|
702
|
-
document.body.style.removeProperty('user-select');
|
|
703
|
-
document.body.style.removeProperty('cursor');
|
|
730
|
+
finishPointerGesture?.();
|
|
704
731
|
if (resizeWidth != null && resizeHeight != null) {
|
|
705
|
-
dispatch('resize', { width: resizeWidth, height: resizeHeight });
|
|
732
|
+
dispatch('resize', { width: resizeWidth, height: resizeHeight, source: 'drag' });
|
|
706
733
|
}
|
|
707
734
|
}
|
|
708
735
|
|
|
709
|
-
|
|
710
|
-
|
|
736
|
+
finishPointerGesture?.();
|
|
737
|
+
const unlockPointerStyle = lockPointerStyle(
|
|
738
|
+
event.target,
|
|
711
739
|
signX !== 0 && signY !== 0
|
|
712
740
|
? signX * signY > 0
|
|
713
741
|
? 'nwse-resize'
|
|
714
742
|
: 'nesw-resize'
|
|
715
743
|
: signX !== 0
|
|
716
744
|
? 'ew-resize'
|
|
717
|
-
: 'ns-resize'
|
|
745
|
+
: 'ns-resize'
|
|
746
|
+
);
|
|
718
747
|
document.addEventListener('pointermove', onPointerMove);
|
|
719
748
|
document.addEventListener('pointerup', endResize);
|
|
720
749
|
document.addEventListener('pointercancel', endResize);
|
|
750
|
+
finishPointerGesture = () => {
|
|
751
|
+
document.removeEventListener('pointermove', onPointerMove);
|
|
752
|
+
document.removeEventListener('pointerup', endResize);
|
|
753
|
+
document.removeEventListener('pointercancel', endResize);
|
|
754
|
+
unlockPointerStyle();
|
|
755
|
+
finishPointerGesture = null;
|
|
756
|
+
};
|
|
721
757
|
}
|
|
722
758
|
|
|
723
759
|
$: if (enableResize && contentContainerEl && (resizeWidth != null || resizeHeight != null)) {
|
|
@@ -762,8 +798,8 @@
|
|
|
762
798
|
onDestroy(() => {
|
|
763
799
|
window.removeEventListener('message', handleMessage);
|
|
764
800
|
stageResizeObserver?.disconnect();
|
|
765
|
-
|
|
766
|
-
|
|
801
|
+
// A drag or resize that was still going when the editor was removed: stop listening and put the style back.
|
|
802
|
+
finishPointerGesture?.();
|
|
767
803
|
});
|
|
768
804
|
</script>
|
|
769
805
|
|
|
@@ -776,7 +812,7 @@
|
|
|
776
812
|
<span
|
|
777
813
|
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"
|
|
778
814
|
>
|
|
779
|
-
{effectiveDisplayUrl || effectiveSrc || '
|
|
815
|
+
{effectiveDisplayUrl || effectiveSrc || $t('editor.preview.noUrl')}
|
|
780
816
|
</span>
|
|
781
817
|
<button
|
|
782
818
|
type="button"
|
|
@@ -784,7 +820,7 @@
|
|
|
784
820
|
const url = effectiveDisplayUrl || effectiveSrc;
|
|
785
821
|
if (url) navigator.clipboard.writeText(url);
|
|
786
822
|
}}
|
|
787
|
-
title=
|
|
823
|
+
title={$t('editor.preview.copyUrl')}
|
|
788
824
|
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"
|
|
789
825
|
>
|
|
790
826
|
<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>
|
|
@@ -793,14 +829,14 @@
|
|
|
793
829
|
<button
|
|
794
830
|
type="button"
|
|
795
831
|
onclick={handleRefreshClick}
|
|
796
|
-
title=
|
|
832
|
+
title={$t('editor.preview.refresh')}
|
|
797
833
|
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"
|
|
798
834
|
>๐</button>
|
|
799
835
|
{#if showViewportButtons && enableResize}
|
|
800
836
|
<button
|
|
801
837
|
type="button"
|
|
802
838
|
onclick={handleFitToAreaClick}
|
|
803
|
-
title=
|
|
839
|
+
title={$t('editor.preview.fitToPanel')}
|
|
804
840
|
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"
|
|
805
841
|
>
|
|
806
842
|
<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>
|
|
@@ -808,7 +844,7 @@
|
|
|
808
844
|
<button
|
|
809
845
|
type="button"
|
|
810
846
|
onclick={applyMobileViewport}
|
|
811
|
-
title=
|
|
847
|
+
title={$t('editor.preview.mobileViewport')}
|
|
812
848
|
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"
|
|
813
849
|
>
|
|
814
850
|
<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>
|
|
@@ -816,7 +852,7 @@
|
|
|
816
852
|
<button
|
|
817
853
|
type="button"
|
|
818
854
|
onclick={handleDesktopViewport}
|
|
819
|
-
title=
|
|
855
|
+
title={$t('editor.preview.desktopViewport')}
|
|
820
856
|
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"
|
|
821
857
|
>
|
|
822
858
|
<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>
|
|
@@ -825,9 +861,9 @@
|
|
|
825
861
|
<button
|
|
826
862
|
type="button"
|
|
827
863
|
onclick={handleOpenInTabClick}
|
|
828
|
-
title=
|
|
864
|
+
title={$t('editor.preview.openInNewTab')}
|
|
829
865
|
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"
|
|
830
|
-
|
|
866
|
+
>{$t('editor.preview.newTab')}</button>
|
|
831
867
|
{/if}
|
|
832
868
|
</div>
|
|
833
869
|
{/if}
|
|
@@ -846,7 +882,7 @@
|
|
|
846
882
|
|
|
847
883
|
{:else if mode === 'standalone' && !widget}
|
|
848
884
|
<div class="widget-preview-select-ct flex items-center justify-center h-full text-sm text-gray-400">
|
|
849
|
-
|
|
885
|
+
{$t('editor.preview.selectWidget')}
|
|
850
886
|
</div>
|
|
851
887
|
|
|
852
888
|
{:else if mode === 'standalone' && widget && !effectiveSrc && !isResolvingUrl && !iframeError}
|
|
@@ -858,11 +894,11 @@
|
|
|
858
894
|
</svg>
|
|
859
895
|
</div>
|
|
860
896
|
<div>
|
|
861
|
-
<p class="text-sm font-medium text-gray-600">{widget.name || '
|
|
862
|
-
<p class="text-xs text-gray-400 mt-1">
|
|
897
|
+
<p class="text-sm font-medium text-gray-600">{widget.name || $t('editor.preview.widgetName')}</p>
|
|
898
|
+
<p class="text-xs text-gray-400 mt-1">{$t('editor.preview.notPublished')}</p>
|
|
863
899
|
</div>
|
|
864
900
|
<p class="text-[11px] text-gray-300 max-w-[220px]">
|
|
865
|
-
|
|
901
|
+
{$t('editor.preview.publishToPreview')}
|
|
866
902
|
</p>
|
|
867
903
|
</div>
|
|
868
904
|
</div>
|
|
@@ -875,7 +911,7 @@
|
|
|
875
911
|
<div
|
|
876
912
|
class="preview-iframe-shell relative flex flex-col {enableResize ? 'preview-iframe-shell-positioned' : ''}"
|
|
877
913
|
class:preview-iframe-shell-symmetrical={showSymmetricalStroke}
|
|
878
|
-
style="{enableResize
|
|
914
|
+
style="{enableResize /* rtl-ok: the preview is placed in pixels from the left edge of the stage; the dock of the editor mirrors it */
|
|
879
915
|
? `position: absolute; left: ${previewShellLeft}px; top: ${previewShellTop}px; width: ${resizeWidth ? resizeWidth + 'px' : '100%'};`
|
|
880
916
|
: `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'};"
|
|
881
917
|
>
|
|
@@ -887,18 +923,18 @@
|
|
|
887
923
|
onpointerdown={startPreviewDrag}
|
|
888
924
|
>
|
|
889
925
|
{#if enableDrag}
|
|
890
|
-
<span class="preview-chrome-drag-grip shrink-0 text-gray-400 text-xs leading-none" title=
|
|
926
|
+
<span class="preview-chrome-drag-grip shrink-0 text-gray-400 text-xs leading-none" title={$t('editor.preview.dragHandle')} aria-hidden="true">โ ฟ</span>
|
|
891
927
|
{/if}
|
|
892
|
-
<span class="preview-chrome-title text-[11px] font-semibold uppercase tracking-wide text-gray-500 shrink-0">
|
|
928
|
+
<span class="preview-chrome-title text-[11px] font-semibold uppercase tracking-wide text-gray-500 shrink-0">{$t('editor.preview.title')}</span>
|
|
893
929
|
{#if showChromeDeviceToggle}
|
|
894
930
|
<div
|
|
895
|
-
class="preview-chrome-device-toggle
|
|
931
|
+
class="preview-chrome-device-toggle ms-auto flex shrink-0 items-center overflow-hidden rounded border border-gray-300"
|
|
896
932
|
onpointerdown={(event) => event.stopPropagation()}
|
|
897
933
|
>
|
|
898
934
|
<button
|
|
899
935
|
type="button"
|
|
900
936
|
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"
|
|
901
|
-
aria-label=
|
|
937
|
+
aria-label={$t('editor.preview.desktop')}
|
|
902
938
|
aria-pressed={!deviceMobile}
|
|
903
939
|
onclick={() => dispatch('deviceView', { mobile: false })}
|
|
904
940
|
onpointerdown={(event) => event.stopPropagation()}
|
|
@@ -907,8 +943,8 @@
|
|
|
907
943
|
</button>
|
|
908
944
|
<button
|
|
909
945
|
type="button"
|
|
910
|
-
class="preview-chrome-device-mobile flex h-6 w-6 items-center justify-center border-
|
|
911
|
-
aria-label=
|
|
946
|
+
class="preview-chrome-device-mobile flex h-6 w-6 items-center justify-center border-s 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"
|
|
947
|
+
aria-label={$t('editor.preview.mobile')}
|
|
912
948
|
aria-pressed={deviceMobile}
|
|
913
949
|
onclick={() => dispatch('deviceView', { mobile: true })}
|
|
914
950
|
onpointerdown={(event) => event.stopPropagation()}
|
|
@@ -918,9 +954,9 @@
|
|
|
918
954
|
</div>
|
|
919
955
|
{/if}
|
|
920
956
|
{#if showDimensionInputs}
|
|
921
|
-
<div class="preview-chrome-dimensions flex items-center gap-1.5 {showChromeDeviceToggle ? '' : '
|
|
957
|
+
<div class="preview-chrome-dimensions flex items-center gap-1.5 {showChromeDeviceToggle ? '' : 'ms-auto'}">
|
|
922
958
|
<label class="preview-dim-label flex items-center gap-1 text-[10px] font-semibold text-gray-500">
|
|
923
|
-
<span>
|
|
959
|
+
<span>{$t('editor.preview.widthShort')}</span>
|
|
924
960
|
<input
|
|
925
961
|
type="number"
|
|
926
962
|
min={MIN_WIDTH}
|
|
@@ -935,7 +971,7 @@
|
|
|
935
971
|
</label>
|
|
936
972
|
<span class="text-[10px] text-gray-400">ร</span>
|
|
937
973
|
<label class="preview-dim-label flex items-center gap-1 text-[10px] font-semibold text-gray-500">
|
|
938
|
-
<span>
|
|
974
|
+
<span>{$t('editor.preview.heightShort')}</span>
|
|
939
975
|
<input
|
|
940
976
|
type="number"
|
|
941
977
|
min={MIN_HEIGHT}
|
|
@@ -987,20 +1023,20 @@
|
|
|
987
1023
|
<svg class="w-8 h-8 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
|
988
1024
|
<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" />
|
|
989
1025
|
</svg>
|
|
990
|
-
<span class="text-sm text-gray-500">{
|
|
1026
|
+
<span class="text-sm text-gray-500">{$t('editor.preview.loadFailedPreview')}</span>
|
|
991
1027
|
</div>
|
|
992
1028
|
</div>
|
|
993
1029
|
{:else if mode === 'controlled' && previewError && !isGeneratingCode}
|
|
994
1030
|
<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">
|
|
995
1031
|
<div class="text-4xl mb-3">โ ๏ธ</div>
|
|
996
|
-
<div class="font-semibold text-lg text-red-600 mb-2">
|
|
1032
|
+
<div class="font-semibold text-lg text-red-600 mb-2">{$t('editor.preview.unavailable')}</div>
|
|
997
1033
|
<div class="text-sm text-center max-w-md whitespace-pre-line text-gray-600 mb-4">{previewError}</div>
|
|
998
1034
|
<div class="flex gap-3">
|
|
999
1035
|
<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">
|
|
1000
|
-
๐
|
|
1036
|
+
๐ {$t('editor.preview.retry')} {previewRetryCount > 0 ? `(${previewRetryCount})` : ''}
|
|
1001
1037
|
</button>
|
|
1002
1038
|
<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">
|
|
1003
|
-
|
|
1039
|
+
{$t('editor.preview.openInNewTab')}
|
|
1004
1040
|
</button>
|
|
1005
1041
|
</div>
|
|
1006
1042
|
</div>
|
|
@@ -1008,21 +1044,17 @@
|
|
|
1008
1044
|
|
|
1009
1045
|
{#if showStandaloneLoading}
|
|
1010
1046
|
<div class="widget-preview-loading-ct absolute inset-0 z-10 flex items-center justify-center bg-gray-50/80">
|
|
1011
|
-
<GenerateLoader title=
|
|
1047
|
+
<GenerateLoader title={$t('editor.preview.loadingWidget')} subtitle="" size="md" />
|
|
1012
1048
|
</div>
|
|
1013
1049
|
{:else if showControlledLoading && !isGeneratingCode}
|
|
1014
1050
|
<div class="loading-overlay absolute inset-0 z-20 flex items-center justify-center bg-gray-50">
|
|
1015
1051
|
<GenerateLoader
|
|
1016
|
-
title={
|
|
1017
|
-
? 'Loading Previewโฆ'
|
|
1018
|
-
: previewHydrating
|
|
1019
|
-
? 'Loading Previewโฆ'
|
|
1020
|
-
: 'Loading Previewโฆ'}
|
|
1052
|
+
title={$t('editor.preview.loadingPreview')}
|
|
1021
1053
|
subtitle={!src && isLoadingUrl
|
|
1022
|
-
? '
|
|
1054
|
+
? $t('editor.preview.checkingWidget')
|
|
1023
1055
|
: previewHydrating
|
|
1024
|
-
? '
|
|
1025
|
-
: '
|
|
1056
|
+
? $t('editor.preview.applyingSettings')
|
|
1057
|
+
: $t('editor.preview.connecting')}
|
|
1026
1058
|
size="lg"
|
|
1027
1059
|
/>
|
|
1028
1060
|
</div>
|
|
@@ -1033,17 +1065,17 @@
|
|
|
1033
1065
|
<div class="text-5xl mb-4">โ ๏ธ</div>
|
|
1034
1066
|
<div class="font-semibold text-xl mb-2">
|
|
1035
1067
|
{previewError && /TypeError|Uncaught|is not a function|is not defined/i.test(previewError)
|
|
1036
|
-
? '
|
|
1037
|
-
: '
|
|
1068
|
+
? $t('editor.preview.runtimeError')
|
|
1069
|
+
: $t('editor.preview.compilationErrors')}
|
|
1038
1070
|
</div>
|
|
1039
1071
|
<div class="text-sm text-center text-red-600 mb-4 max-w-md whitespace-pre-line">
|
|
1040
1072
|
{previewError
|
|
1041
|
-
||
|
|
1073
|
+
|| $t('editor.preview.compilationErrorsDetail', { status: previewRuntimeErrorStatus ?? '?' })}
|
|
1042
1074
|
</div>
|
|
1043
1075
|
<div class="flex gap-3 flex-wrap justify-center">
|
|
1044
|
-
<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">๐ง
|
|
1045
|
-
<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">๐
|
|
1046
|
-
<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">๐
|
|
1076
|
+
<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>
|
|
1077
|
+
<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>
|
|
1078
|
+
<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>
|
|
1047
1079
|
</div>
|
|
1048
1080
|
</div>
|
|
1049
1081
|
{/if}
|
|
@@ -1057,9 +1089,13 @@
|
|
|
1057
1089
|
src={mountedPreviewSrc}
|
|
1058
1090
|
class="widget-preview-iframe h-full w-full border-0"
|
|
1059
1091
|
tabindex="-1"
|
|
1060
|
-
sandbox={
|
|
1092
|
+
sandbox={sandbox !== undefined
|
|
1093
|
+
? (sandbox ?? undefined)
|
|
1094
|
+
: mode === 'controlled'
|
|
1095
|
+
? undefined
|
|
1096
|
+
: 'allow-scripts allow-popups allow-forms allow-same-origin'}
|
|
1061
1097
|
loading="eager"
|
|
1062
|
-
allow
|
|
1098
|
+
{allow}
|
|
1063
1099
|
onload={handleIframeLoad}
|
|
1064
1100
|
onerror={handleIframeError}
|
|
1065
1101
|
></iframe>
|
|
@@ -1071,7 +1107,7 @@
|
|
|
1071
1107
|
{/if}
|
|
1072
1108
|
|
|
1073
1109
|
{#if mode === 'controlled' && isGeneratingCode}
|
|
1074
|
-
<GenerateLoader overlay title=
|
|
1110
|
+
<GenerateLoader overlay title={$t('editor.preview.generatingCode')} subtitle="" size="lg" />
|
|
1075
1111
|
{/if}
|
|
1076
1112
|
</div>
|
|
1077
1113
|
</div>
|
|
@@ -1081,22 +1117,22 @@
|
|
|
1081
1117
|
{:else if mode === 'controlled'}
|
|
1082
1118
|
{#if isLoadingUrl}
|
|
1083
1119
|
<div class="flex h-full flex-col items-center justify-center p-6">
|
|
1084
|
-
<GenerateLoader title=
|
|
1120
|
+
<GenerateLoader title={$t('editor.preview.loadingPreview')} subtitle="" size="lg" />
|
|
1085
1121
|
</div>
|
|
1086
1122
|
{:else}
|
|
1087
1123
|
<div class="flex h-full flex-col items-center justify-center p-6 text-center text-sm text-gray-600">
|
|
1088
1124
|
{#if isGeneratingCode}
|
|
1089
|
-
<GenerateLoader title=
|
|
1125
|
+
<GenerateLoader title={$t('editor.preview.generatingCode')} subtitle="" size="lg" />
|
|
1090
1126
|
{:else if previewError === 'NO_CODE_YET'}
|
|
1091
|
-
<div class="font-semibold text-lg text-gray-700 mb-2">
|
|
1127
|
+
<div class="font-semibold text-lg text-gray-700 mb-2">{$t('editor.preview.noCodeYet')}</div>
|
|
1092
1128
|
{:else if previewError}
|
|
1093
|
-
<div class="font-semibold text-lg text-red-600 mb-2">
|
|
1129
|
+
<div class="font-semibold text-lg text-red-600 mb-2">{$t('editor.preview.unavailable')}</div>
|
|
1094
1130
|
<div class="text-sm text-gray-600 mb-4">{previewError}</div>
|
|
1095
|
-
<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">๐
|
|
1131
|
+
<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>
|
|
1096
1132
|
{:else if previewLoading}
|
|
1097
|
-
<div class="text-lg font-medium text-gray-700">
|
|
1133
|
+
<div class="text-lg font-medium text-gray-700">{$t('editor.preview.building')}</div>
|
|
1098
1134
|
{:else}
|
|
1099
|
-
<div class="text-lg text-gray-600 mb-2">๐ผ๏ธ
|
|
1135
|
+
<div class="text-lg text-gray-600 mb-2">๐ผ๏ธ {$t('editor.preview.noPreview')}</div>
|
|
1100
1136
|
{/if}
|
|
1101
1137
|
</div>
|
|
1102
1138
|
{/if}
|
|
@@ -1140,10 +1176,11 @@
|
|
|
1140
1176
|
box-shadow: none;
|
|
1141
1177
|
}
|
|
1142
1178
|
|
|
1143
|
-
|
|
1144
|
-
.preview-resize-handle-
|
|
1145
|
-
.preview-resize-handle-
|
|
1146
|
-
.preview-resize-handle-
|
|
1179
|
+
/* 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. */
|
|
1180
|
+
.preview-resize-handle-tl { top: -5px; left: -5px; cursor: nwse-resize; } /* rtl-ok: top left corner */
|
|
1181
|
+
.preview-resize-handle-tr { top: -5px; right: -5px; cursor: nesw-resize; } /* rtl-ok: top right corner */
|
|
1182
|
+
.preview-resize-handle-bl { bottom: -5px; left: -5px; cursor: nesw-resize; } /* rtl-ok: bottom left corner */
|
|
1183
|
+
.preview-resize-handle-br { bottom: -5px; right: -5px; cursor: nwse-resize; } /* rtl-ok: bottom right corner */
|
|
1147
1184
|
|
|
1148
1185
|
.preview-resize-handle:hover {
|
|
1149
1186
|
background: #eff6ff;
|
|
@@ -34,6 +34,13 @@ declare const WidgetPreview: $$__sveltets_2_IsomorphicComponent<{
|
|
|
34
34
|
forceMockReady?: boolean;
|
|
35
35
|
debugMode?: boolean;
|
|
36
36
|
iframeReloadKey?: number;
|
|
37
|
+
/**
|
|
38
|
+
* The iframe's `sandbox` tokens. `undefined` keeps what each mode always had (controlled: no sandbox; standalone:
|
|
39
|
+
* scripts, popups, forms and its own origin); a string is used exactly as given; `null` means no `sandbox` at all.
|
|
40
|
+
* A page that embeds the editor for its own visitors passes a list without `allow-top-navigation`, so that a
|
|
41
|
+
* widget's code cannot send the visitor's whole tab somewhere else.
|
|
42
|
+
*/ sandbox?: string | null | undefined;
|
|
43
|
+
/** The iframe's `allow` attribute: which of the page's permissions the preview may use. */ allow?: string;
|
|
37
44
|
width?: string;
|
|
38
45
|
height?: string;
|
|
39
46
|
contentHeight?: string;
|
|
@@ -82,6 +89,7 @@ declare const WidgetPreview: $$__sveltets_2_IsomorphicComponent<{
|
|
|
82
89
|
resize: CustomEvent<{
|
|
83
90
|
width: number;
|
|
84
91
|
height: number;
|
|
92
|
+
source?: "drag" | "input" | "fit" | "device" | "clamp";
|
|
85
93
|
}>;
|
|
86
94
|
mobileViewport: CustomEvent<{
|
|
87
95
|
width: 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
|
|
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
|
|
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
|
/>
|