@widgetic/editor 4.0.9 → 4.0.11
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 +118 -0
- package/dist/components/WidgetPreview.svelte +65 -27
- package/dist/components/WidgetPreview.svelte.d.ts +8 -0
- package/dist/components/editors/CompositionEditorApp.svelte +508 -0
- package/dist/components/editors/CompositionEditorApp.svelte.d.ts +75 -0
- package/dist/components/editors/PropsEditor.svelte +193 -792
- package/dist/components/editors/PropsEditor.svelte.d.ts +7 -0
- package/dist/components/ic/BrowserInputController.svelte +21 -2
- package/dist/components/ic/ColorPickerInputController.svelte +24 -6
- package/dist/components/ic/DateFieldInputController.svelte +7 -3
- package/dist/components/ic/DatePickerInputController.svelte +10 -4
- package/dist/components/ic/DropdownInputController.svelte +11 -4
- package/dist/components/ic/FontInputController.svelte +68 -44
- package/dist/components/ic/InputControllerHeader.svelte +11 -8
- package/dist/components/ic/NumberInputController.svelte +4 -2
- package/dist/components/ic/PositionInputController.svelte +14 -11
- package/dist/components/ic/RangeCalendarInputController.svelte +10 -4
- package/dist/components/ic/RoundedCornersInputController.svelte +17 -9
- package/dist/components/ic/TextAreaInputController.svelte +20 -7
- package/dist/components/ic/TextAreaPreviewInputController.svelte +19 -69
- package/dist/components/ic/TimeInputController.svelte +9 -3
- package/dist/components/ic/VectorInputController.svelte +80 -54
- package/dist/components/ic/shared/ItemGrid.svelte +5 -1
- package/dist/composition/compositionApi.d.ts +95 -0
- package/dist/composition/compositionApi.js +289 -0
- package/dist/composition/compositionEditorController.d.ts +254 -0
- package/dist/composition/compositionEditorController.js +734 -0
- package/dist/composition/compositionHelpers.d.ts +72 -0
- package/dist/composition/compositionHelpers.js +403 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -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/editorStorage.d.ts +23 -0
- package/dist/utils/editorStorage.js +95 -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 +6 -5
package/README.md
CHANGED
|
@@ -38,6 +38,119 @@ dispatch('propertyChange', { schema, property, value});
|
|
|
38
38
|
}} />
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
## Using your own media library
|
|
42
|
+
|
|
43
|
+
By default the file pickers inside the editor (the controls that open the File Browser) list and upload to the
|
|
44
|
+
user's Widgetic uploads. A platform that keeps its media somewhere else passes a `mediaLibrary` and every picker uses
|
|
45
|
+
it instead:
|
|
46
|
+
|
|
47
|
+
```svelte
|
|
48
|
+
<PropsEditor {designSchema} {contentSchema} mediaLibrary={myMediaLibrary} />
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`mediaLibrary` is a `MediaLibraryProvider` (`list`, and optionally `upload`, `rename`, `delete`), exactly as the File
|
|
52
|
+
Browser defines it: see "Using your own media library" in the
|
|
53
|
+
[File Browser README](../file-browser/README.md). The value the editor reports for a picked file is `{ id, file, url }`
|
|
54
|
+
(plus `hash` when the library gives one), taken from the library's file: `id` is the library's own id, `file` its
|
|
55
|
+
name and `url` its address.
|
|
56
|
+
|
|
57
|
+
With a library set, the pickers make no Widgetic upload request, not even when the page address carries `apiUrl` and
|
|
58
|
+
`token`, and the File Browser keeps nothing on the device. Without one, nothing changes.
|
|
59
|
+
|
|
60
|
+
Needs a `@widgetic/file-browser` that supports `mediaLibrary`; an older one ignores the prop and keeps using the
|
|
61
|
+
Widgetic uploads.
|
|
62
|
+
|
|
63
|
+
## Editing a whole composition
|
|
64
|
+
|
|
65
|
+
`CompositionEditorApp` is the editor with everything around it. It loads one Widgetic composition, shows its settings
|
|
66
|
+
(the `PropsEditor`) beside a live preview of the widget, and saves what the user changes. A page that has a token for
|
|
67
|
+
the Widgetic API (a user's own, or an editor session token for a partner's managed user) only has to name the
|
|
68
|
+
composition:
|
|
69
|
+
|
|
70
|
+
```svelte
|
|
71
|
+
<script>
|
|
72
|
+
import { CompositionEditorApp } from '@widgetic/editor';
|
|
73
|
+
|
|
74
|
+
// Called before every request, and again with 'expired' when the API refuses the token.
|
|
75
|
+
async function getAccessToken(reason) {
|
|
76
|
+
const response = await fetch('/my-backend/widgetic-editor-session', { method: 'POST' });
|
|
77
|
+
return (await response.json()).token;
|
|
78
|
+
}
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
<!-- It fills the box it is put in, so give the box a height. -->
|
|
82
|
+
<div style="height: 100vh">
|
|
83
|
+
<CompositionEditorApp
|
|
84
|
+
compositionId="comp_123"
|
|
85
|
+
apiBasePath="https://api.widgetic.com/v1"
|
|
86
|
+
{getAccessToken}
|
|
87
|
+
on:saved={(event) => console.log('saved at', event.detail.savedAt)}
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Props:
|
|
93
|
+
|
|
94
|
+
- `compositionId`: the composition to edit. One component edits one composition: for another one, make another component.
|
|
95
|
+
- `apiBasePath`: the API's address with its version (`https://api.widgetic.com/v1`). A blank one throws when the
|
|
96
|
+
component is made.
|
|
97
|
+
- `getAccessToken`: `(reason: 'initial' | 'expired', refusedToken?: string) => string | Promise<string>`. See "The
|
|
98
|
+
token" below.
|
|
99
|
+
- `mediaLibrary` (optional): the partner's own `MediaLibraryProvider` for the file pickers (see above). Without it the
|
|
100
|
+
pickers use the Widgetic library the token can see.
|
|
101
|
+
- `googleFontsApiKey` (optional): the key the font pickers list Google Fonts with.
|
|
102
|
+
- `autosaveDelayMs` (optional): how long to wait after the last change before saving by itself (default 2500); `0`
|
|
103
|
+
turns saving by itself off. Read once, when the component is made.
|
|
104
|
+
- `class`, `style`: for the box the editor fills.
|
|
105
|
+
|
|
106
|
+
**The token.** `getAccessToken` is called before every request, so a page that keeps its token fresh is always used
|
|
107
|
+
up to date. When the API answers 401 the function is called again with `'expired'` and the token that was refused
|
|
108
|
+
(`refusedToken`) and, only if the token that comes back is a different one, the request is sent once more (a save is
|
|
109
|
+
sent with the same `Idempotency-Key` and body, so it cannot be applied twice). Several requests that start together with
|
|
110
|
+
the same token are refused together, and each asks: if your function has already replaced `refusedToken`, hand out the
|
|
111
|
+
new token instead of asking your server for another one. If the session is really over, the editor says so and `error`
|
|
112
|
+
reports `failure.sessionEnded`.
|
|
113
|
+
|
|
114
|
+
**Saving.** A change is saved a moment after the last one (2.5 seconds), or at once with the "Save" button. Only one
|
|
115
|
+
save is on its way at a time, and a change made meanwhile is saved by the next one. A failure that may pass (no
|
|
116
|
+
connection, a timeout, 429, 5xx) is tried again after 5, 15 and 45 seconds, the user can press "Try again" at any time,
|
|
117
|
+
and a failure that cannot pass (the session ended, a refusal) is not retried.
|
|
118
|
+
|
|
119
|
+
**The size.** The size the user gives the preview, by dragging its corner or typing it in, is saved with the composition
|
|
120
|
+
(`embedSettings.width` and `height`) and is the size the widget gets where it is embedded. Nothing else changes it: the
|
|
121
|
+
desktop and mobile buttons only change how the preview is looked at, and a box smaller than the preview shrinks it to
|
|
122
|
+
fit without changing what is saved (so opening a composition in a small window does not edit it).
|
|
123
|
+
|
|
124
|
+
Events (the data is in `event.detail`):
|
|
125
|
+
|
|
126
|
+
- `ready` `{ name, widgetId }`: the editor is on the screen.
|
|
127
|
+
- `dirty` `{ dirty }`: there is, or is no longer, work that is not saved.
|
|
128
|
+
- `saved` `{ composition, savedAt }`: a save went through (`composition` is what the API answered).
|
|
129
|
+
- `error` `{ during, failure, fatal }`: `during` is `'load'`, `'schemas'` or `'save'`; `failure` is
|
|
130
|
+
`{ message, code, status, retryable, sessionEnded, correlationId? }`; `fatal` is true when the composition could not
|
|
131
|
+
be loaded at all (the editor then shows its own message with "Try again").
|
|
132
|
+
- `deviceView` `{ deviceView }`: the preview is looked at as `'desktop'` or `'mobile'` now, whether a button of the
|
|
133
|
+
preview, a size the user gave it or `setDeviceView()` did it. Told once per change.
|
|
134
|
+
|
|
135
|
+
The component also has the methods `save()`, `flush()`, `isDirty()`, `getState()` and `reload()`. **Before removing
|
|
136
|
+
the editor, `await flush()`**: removing it cancels whatever is on its way, and changes that were not saved are lost.
|
|
137
|
+
|
|
138
|
+
For a page that has controls of its own around the editor:
|
|
139
|
+
|
|
140
|
+
- `setDeviceView('desktop' | 'mobile')` does what the preview's desktop and mobile buttons do. It is a way of looking,
|
|
141
|
+
not an edit: nothing becomes unsaved.
|
|
142
|
+
- `adoptEmbedSettings(settings)` is for a page that saves the composition's `embedSettings` itself (its own "Embed"
|
|
143
|
+
screen, say). The editor sends back the embed settings it loaded with every save, so without this a later save would put
|
|
144
|
+
them back. Call it after `await flush()` (so no save of the editor is on its way), with the settings the API now holds.
|
|
145
|
+
Nothing becomes unsaved, and the preview takes the size the settings name.
|
|
146
|
+
|
|
147
|
+
What it asks of the API (all of it is on an editor session's allow-list): `GET /compositions/{id}`,
|
|
148
|
+
`GET /compositions/{id}/preview`, `GET /compositions/{id}/schema/design`, `GET /compositions/{id}/schema/content` and
|
|
149
|
+
`PUT /compositions/{id}`, plus `/uploads` for the default file library. A composition whose widget is not published
|
|
150
|
+
has no preview address (`WIDGET_NOT_PUBLISHED`): its settings stay editable and the editor says why there is no preview.
|
|
151
|
+
|
|
152
|
+
It is a Svelte component for a page that can import it, and it takes the page's styles and fonts as they are. A
|
|
153
|
+
plain-JavaScript module that works on any page, inside a shadow root, is a separate step.
|
|
41
154
|
|
|
42
155
|
## Input Controllers
|
|
43
156
|
|
|
@@ -91,6 +204,11 @@ npm run dev
|
|
|
91
204
|
npm run dev -- --open
|
|
92
205
|
```
|
|
93
206
|
|
|
207
|
+
The dev server also serves three playground pages that need no sign-in:
|
|
208
|
+
`/ic` (an input controller on its own), `/embed` (the Embed editor) and
|
|
209
|
+
`/mock-widget`. They compile the sibling packages from source, so a change in
|
|
210
|
+
`design-system` or `file-browser` shows up there without a build.
|
|
211
|
+
|
|
94
212
|
## Build the project
|
|
95
213
|
|
|
96
214
|
To create a production version of your app:
|
|
@@ -1,13 +1,16 @@
|
|
|
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';
|
|
4
5
|
|
|
5
6
|
const dispatch = createEventDispatcher<{
|
|
6
7
|
load: { src: string | null };
|
|
7
8
|
error: { src: string | null };
|
|
8
9
|
refresh: void;
|
|
9
10
|
openInTab: void;
|
|
10
|
-
|
|
11
|
+
// `source` says where a size came from, so a host can tell a size the user chose ('drag', 'input', 'fit',
|
|
12
|
+
// 'device') from one the box around the preview forced on it ('clamp': the box is smaller than the size asked for).
|
|
13
|
+
resize: { width: number; height: number; source?: 'drag' | 'input' | 'fit' | 'device' | 'clamp' };
|
|
11
14
|
mobileViewport: { width: number; height: number };
|
|
12
15
|
desktopViewport: void;
|
|
13
16
|
deviceView: { mobile: boolean };
|
|
@@ -50,6 +53,17 @@
|
|
|
50
53
|
export let debugMode: boolean = false;
|
|
51
54
|
export let iframeReloadKey = 0;
|
|
52
55
|
|
|
56
|
+
// ── What the preview iframe may do ──────────────────────────────────────
|
|
57
|
+
/**
|
|
58
|
+
* The iframe's `sandbox` tokens. `undefined` keeps what each mode always had (controlled: no sandbox; standalone:
|
|
59
|
+
* scripts, popups, forms and its own origin); a string is used exactly as given; `null` means no `sandbox` at all.
|
|
60
|
+
* A page that embeds the editor for its own visitors passes a list without `allow-top-navigation`, so that a
|
|
61
|
+
* widget's code cannot send the visitor's whole tab somewhere else.
|
|
62
|
+
*/
|
|
63
|
+
export let sandbox: string | null | undefined = undefined;
|
|
64
|
+
/** The iframe's `allow` attribute: which of the page's permissions the preview may use. */
|
|
65
|
+
export let allow: string = 'clipboard-read; clipboard-write';
|
|
66
|
+
|
|
53
67
|
// ── Layout / chrome ─────────────────────────────────────────────────────
|
|
54
68
|
export let width: string = '100%';
|
|
55
69
|
export let height: string = '100%';
|
|
@@ -222,11 +236,19 @@
|
|
|
222
236
|
heightInputValue = String(Math.round(resizeHeight));
|
|
223
237
|
}
|
|
224
238
|
if (resizeWidth != null && resizeHeight != null) {
|
|
225
|
-
dispatch('resize', { width: resizeWidth, height: resizeHeight });
|
|
239
|
+
dispatch('resize', { width: resizeWidth, height: resizeHeight, source: 'input' });
|
|
226
240
|
}
|
|
227
241
|
console.log('[WidgetPreview] Dimension input', { dimension, value: parsed });
|
|
228
242
|
}
|
|
229
243
|
|
|
244
|
+
/**
|
|
245
|
+
* While a drag or a resize lasts the pointer shows the matching cursor and nothing is selected as it moves
|
|
246
|
+
* (`lockPointerStyle`, in `shadowDom.ts`: set on the host of the shadow root the editor lives in, or on <body> when
|
|
247
|
+
* there is none, and put back afterwards). Puts back what the running drag or resize changed: the pointer style,
|
|
248
|
+
* and the listeners on `document`.
|
|
249
|
+
*/
|
|
250
|
+
let finishPointerGesture: (() => void) | null = null;
|
|
251
|
+
|
|
230
252
|
function startPreviewDrag(event: PointerEvent) {
|
|
231
253
|
if (!enableDrag || isResizing) return;
|
|
232
254
|
const target = event.target as HTMLElement | null;
|
|
@@ -252,22 +274,25 @@
|
|
|
252
274
|
|
|
253
275
|
function endDrag() {
|
|
254
276
|
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');
|
|
277
|
+
finishPointerGesture?.();
|
|
260
278
|
console.log('[WidgetPreview] Preview drag end', {
|
|
261
279
|
left: previewShellLeft,
|
|
262
280
|
top: previewShellTop,
|
|
263
281
|
});
|
|
264
282
|
}
|
|
265
283
|
|
|
266
|
-
|
|
267
|
-
|
|
284
|
+
finishPointerGesture?.();
|
|
285
|
+
const unlockPointerStyle = lockPointerStyle(event.target, 'move');
|
|
268
286
|
document.addEventListener('pointermove', onPointerMove);
|
|
269
287
|
document.addEventListener('pointerup', endDrag);
|
|
270
288
|
document.addEventListener('pointercancel', endDrag);
|
|
289
|
+
finishPointerGesture = () => {
|
|
290
|
+
document.removeEventListener('pointermove', onPointerMove);
|
|
291
|
+
document.removeEventListener('pointerup', endDrag);
|
|
292
|
+
document.removeEventListener('pointercancel', endDrag);
|
|
293
|
+
unlockPointerStyle();
|
|
294
|
+
finishPointerGesture = null;
|
|
295
|
+
};
|
|
271
296
|
}
|
|
272
297
|
|
|
273
298
|
/** 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 +333,7 @@
|
|
|
308
333
|
}
|
|
309
334
|
resizeWidth = limits.maxWidth;
|
|
310
335
|
resizeHeight = limits.maxHeight;
|
|
311
|
-
dispatch('resize', { width: limits.maxWidth, height: limits.maxHeight });
|
|
336
|
+
dispatch('resize', { width: limits.maxWidth, height: limits.maxHeight, source: 'fit' });
|
|
312
337
|
previewPositionReady = false;
|
|
313
338
|
requestAnimationFrame(() => {
|
|
314
339
|
resetPreviewPosition();
|
|
@@ -575,6 +600,9 @@
|
|
|
575
600
|
}
|
|
576
601
|
|
|
577
602
|
function handleMessage(event: MessageEvent) {
|
|
603
|
+
// Only the preview iframe of this component speaks to it: any other window on the page (a partner's own frames
|
|
604
|
+
// and scripts included) can post messages to this window too.
|
|
605
|
+
if (!iframeEl?.contentWindow || event.source !== iframeEl.contentWindow) return;
|
|
578
606
|
const type = event.data?.type;
|
|
579
607
|
if (type === 'widgetic:ready' || type === 'widgetic:pong') {
|
|
580
608
|
if (mode === 'standalone') {
|
|
@@ -597,7 +625,8 @@
|
|
|
597
625
|
|
|
598
626
|
function handleOpenInTabClick() {
|
|
599
627
|
const url = effectiveDisplayUrl || effectiveSrc;
|
|
600
|
-
|
|
628
|
+
// noopener: the page that opens must not get a handle on the page that opened it (which can be a partner's page).
|
|
629
|
+
if (url) window.open(url, '_blank', 'noopener,noreferrer');
|
|
601
630
|
dispatch('openInTab');
|
|
602
631
|
}
|
|
603
632
|
|
|
@@ -607,7 +636,7 @@
|
|
|
607
636
|
const h = Math.min(mobileViewportHeight, maxHeight);
|
|
608
637
|
resizeWidth = w;
|
|
609
638
|
resizeHeight = h;
|
|
610
|
-
dispatch('resize', { width: w, height: h });
|
|
639
|
+
dispatch('resize', { width: w, height: h, source: 'device' });
|
|
611
640
|
dispatch('mobileViewport', { width: w, height: h });
|
|
612
641
|
}
|
|
613
642
|
|
|
@@ -629,7 +658,7 @@
|
|
|
629
658
|
if (nextW !== resizeWidth || nextH !== resizeHeight) {
|
|
630
659
|
resizeWidth = nextW;
|
|
631
660
|
resizeHeight = nextH;
|
|
632
|
-
dispatch('resize', { width: nextW, height: nextH });
|
|
661
|
+
dispatch('resize', { width: nextW, height: nextH, source: 'clamp' });
|
|
633
662
|
}
|
|
634
663
|
}
|
|
635
664
|
|
|
@@ -689,35 +718,40 @@
|
|
|
689
718
|
}
|
|
690
719
|
|
|
691
720
|
if (resizeWidth != null && resizeHeight != null) {
|
|
692
|
-
dispatch('resize', { width: resizeWidth, height: resizeHeight });
|
|
721
|
+
dispatch('resize', { width: resizeWidth, height: resizeHeight, source: 'drag' });
|
|
693
722
|
}
|
|
694
723
|
}
|
|
695
724
|
|
|
696
725
|
function endResize() {
|
|
697
726
|
isResizing = false;
|
|
698
727
|
// 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');
|
|
728
|
+
finishPointerGesture?.();
|
|
704
729
|
if (resizeWidth != null && resizeHeight != null) {
|
|
705
|
-
dispatch('resize', { width: resizeWidth, height: resizeHeight });
|
|
730
|
+
dispatch('resize', { width: resizeWidth, height: resizeHeight, source: 'drag' });
|
|
706
731
|
}
|
|
707
732
|
}
|
|
708
733
|
|
|
709
|
-
|
|
710
|
-
|
|
734
|
+
finishPointerGesture?.();
|
|
735
|
+
const unlockPointerStyle = lockPointerStyle(
|
|
736
|
+
event.target,
|
|
711
737
|
signX !== 0 && signY !== 0
|
|
712
738
|
? signX * signY > 0
|
|
713
739
|
? 'nwse-resize'
|
|
714
740
|
: 'nesw-resize'
|
|
715
741
|
: signX !== 0
|
|
716
742
|
? 'ew-resize'
|
|
717
|
-
: 'ns-resize'
|
|
743
|
+
: 'ns-resize'
|
|
744
|
+
);
|
|
718
745
|
document.addEventListener('pointermove', onPointerMove);
|
|
719
746
|
document.addEventListener('pointerup', endResize);
|
|
720
747
|
document.addEventListener('pointercancel', endResize);
|
|
748
|
+
finishPointerGesture = () => {
|
|
749
|
+
document.removeEventListener('pointermove', onPointerMove);
|
|
750
|
+
document.removeEventListener('pointerup', endResize);
|
|
751
|
+
document.removeEventListener('pointercancel', endResize);
|
|
752
|
+
unlockPointerStyle();
|
|
753
|
+
finishPointerGesture = null;
|
|
754
|
+
};
|
|
721
755
|
}
|
|
722
756
|
|
|
723
757
|
$: if (enableResize && contentContainerEl && (resizeWidth != null || resizeHeight != null)) {
|
|
@@ -762,8 +796,8 @@
|
|
|
762
796
|
onDestroy(() => {
|
|
763
797
|
window.removeEventListener('message', handleMessage);
|
|
764
798
|
stageResizeObserver?.disconnect();
|
|
765
|
-
|
|
766
|
-
|
|
799
|
+
// A drag or resize that was still going when the editor was removed: stop listening and put the style back.
|
|
800
|
+
finishPointerGesture?.();
|
|
767
801
|
});
|
|
768
802
|
</script>
|
|
769
803
|
|
|
@@ -1057,9 +1091,13 @@
|
|
|
1057
1091
|
src={mountedPreviewSrc}
|
|
1058
1092
|
class="widget-preview-iframe h-full w-full border-0"
|
|
1059
1093
|
tabindex="-1"
|
|
1060
|
-
sandbox={
|
|
1094
|
+
sandbox={sandbox !== undefined
|
|
1095
|
+
? (sandbox ?? undefined)
|
|
1096
|
+
: mode === 'controlled'
|
|
1097
|
+
? undefined
|
|
1098
|
+
: 'allow-scripts allow-popups allow-forms allow-same-origin'}
|
|
1061
1099
|
loading="eager"
|
|
1062
|
-
allow
|
|
1100
|
+
{allow}
|
|
1063
1101
|
onload={handleIframeLoad}
|
|
1064
1102
|
onerror={handleIframeError}
|
|
1065
1103
|
></iframe>
|
|
@@ -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;
|