@widgetic/editor 4.1.0 → 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.
- package/dist/components/PreviewChromeControls.svelte +84 -0
- package/dist/components/PreviewChromeControls.svelte.d.ts +35 -0
- package/dist/components/WidgetPreview.svelte +39 -77
- package/dist/components/WidgetPreview.svelte.d.ts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let className = '';
|
|
3
|
+
export { className as class };
|
|
4
|
+
export let style = '';
|
|
5
|
+
/** Desktop is the unpressed state of the phone icon. */
|
|
6
|
+
export let mobile = false;
|
|
7
|
+
export let showDevices = true;
|
|
8
|
+
export let showDimensions = true;
|
|
9
|
+
export let widthValue = '';
|
|
10
|
+
export let heightValue = '';
|
|
11
|
+
export let minWidth = 120;
|
|
12
|
+
export let minHeight = 80;
|
|
13
|
+
export let onDevice: (mobile: boolean) => void = () => {};
|
|
14
|
+
export let onWidth: (value: string) => void = () => {};
|
|
15
|
+
export let onHeight: (value: string) => void = () => {};
|
|
16
|
+
export let desktopLabel = 'Desktop preview';
|
|
17
|
+
export let mobileLabel = 'Mobile preview';
|
|
18
|
+
export let widthLabel = 'W';
|
|
19
|
+
export let heightLabel = 'H';
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<div class="preview-chrome-controls flex items-center gap-2 {className}" style={style}>
|
|
23
|
+
{#if showDevices}
|
|
24
|
+
<div class="preview-chrome-device-toggle flex shrink-0 items-center overflow-hidden rounded border border-gray-300">
|
|
25
|
+
<button
|
|
26
|
+
type="button"
|
|
27
|
+
class="preview-chrome-device-desktop flex h-6 w-6 items-center justify-center {!mobile ? 'bg-gray-100 text-gray-800' : 'bg-white text-gray-400'} hover:text-gray-800 focus:outline-none focus-visible:outline-none"
|
|
28
|
+
aria-label={desktopLabel}
|
|
29
|
+
aria-pressed={!mobile}
|
|
30
|
+
onclick={() => onDevice(false)}
|
|
31
|
+
onpointerdown={(event) => event.stopPropagation()}
|
|
32
|
+
>
|
|
33
|
+
<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>
|
|
34
|
+
</button>
|
|
35
|
+
<button
|
|
36
|
+
type="button"
|
|
37
|
+
class="preview-chrome-device-mobile flex h-6 w-6 items-center justify-center border-s border-gray-300 {mobile ? 'bg-gray-100 text-gray-800' : 'bg-white text-gray-400'} hover:text-gray-800 focus:outline-none focus-visible:outline-none"
|
|
38
|
+
aria-label={mobileLabel}
|
|
39
|
+
aria-pressed={mobile}
|
|
40
|
+
onclick={() => onDevice(true)}
|
|
41
|
+
onpointerdown={(event) => event.stopPropagation()}
|
|
42
|
+
>
|
|
43
|
+
<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>
|
|
44
|
+
</button>
|
|
45
|
+
</div>
|
|
46
|
+
{/if}
|
|
47
|
+
{#if showDimensions}
|
|
48
|
+
<div class="preview-chrome-dimensions flex items-center gap-1.5">
|
|
49
|
+
<label class="preview-dim-label flex items-center gap-1 text-[10px] font-semibold text-gray-500">
|
|
50
|
+
<span>{widthLabel}</span>
|
|
51
|
+
<input
|
|
52
|
+
type="number"
|
|
53
|
+
min={minWidth}
|
|
54
|
+
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"
|
|
55
|
+
value={widthValue}
|
|
56
|
+
oninput={(event) => onWidth(event.currentTarget.value)}
|
|
57
|
+
onpointerdown={(event) => event.stopPropagation()}
|
|
58
|
+
/>
|
|
59
|
+
</label>
|
|
60
|
+
<span class="text-[10px] text-gray-400">×</span>
|
|
61
|
+
<label class="preview-dim-label flex items-center gap-1 text-[10px] font-semibold text-gray-500">
|
|
62
|
+
<span>{heightLabel}</span>
|
|
63
|
+
<input
|
|
64
|
+
type="number"
|
|
65
|
+
min={minHeight}
|
|
66
|
+
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"
|
|
67
|
+
value={heightValue}
|
|
68
|
+
oninput={(event) => onHeight(event.currentTarget.value)}
|
|
69
|
+
onpointerdown={(event) => event.stopPropagation()}
|
|
70
|
+
/>
|
|
71
|
+
</label>
|
|
72
|
+
</div>
|
|
73
|
+
{/if}
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<style>
|
|
77
|
+
.preview-chrome-controls button:focus,
|
|
78
|
+
.preview-chrome-controls button:focus-visible,
|
|
79
|
+
.preview-chrome-controls input:focus,
|
|
80
|
+
.preview-chrome-controls input:focus-visible {
|
|
81
|
+
outline: none;
|
|
82
|
+
box-shadow: none;
|
|
83
|
+
}
|
|
84
|
+
</style>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
+
$$bindings?: Bindings;
|
|
4
|
+
} & Exports;
|
|
5
|
+
(internal: unknown, props: Props & {
|
|
6
|
+
$$events?: Events;
|
|
7
|
+
$$slots?: Slots;
|
|
8
|
+
}): Exports & {
|
|
9
|
+
$set?: any;
|
|
10
|
+
$on?: any;
|
|
11
|
+
};
|
|
12
|
+
z_$$bindings?: Bindings;
|
|
13
|
+
}
|
|
14
|
+
declare const PreviewChromeControls: $$__sveltets_2_IsomorphicComponent<{
|
|
15
|
+
class?: string;
|
|
16
|
+
style?: string;
|
|
17
|
+
/** Desktop is the unpressed state of the phone icon. */ mobile?: boolean;
|
|
18
|
+
showDevices?: boolean;
|
|
19
|
+
showDimensions?: boolean;
|
|
20
|
+
widthValue?: string;
|
|
21
|
+
heightValue?: string;
|
|
22
|
+
minWidth?: number;
|
|
23
|
+
minHeight?: number;
|
|
24
|
+
onDevice?: (mobile: boolean) => void;
|
|
25
|
+
onWidth?: (value: string) => void;
|
|
26
|
+
onHeight?: (value: string) => void;
|
|
27
|
+
desktopLabel?: string;
|
|
28
|
+
mobileLabel?: string;
|
|
29
|
+
widthLabel?: string;
|
|
30
|
+
heightLabel?: string;
|
|
31
|
+
}, {
|
|
32
|
+
[evt: string]: CustomEvent<any>;
|
|
33
|
+
}, {}, {}, string>;
|
|
34
|
+
type PreviewChromeControls = InstanceType<typeof PreviewChromeControls>;
|
|
35
|
+
export default PreviewChromeControls;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { GenerateLoader } from '@widgetic/design-system/components';
|
|
4
4
|
import { lockPointerStyle } from '../utils/shadowDom';
|
|
5
5
|
import { useEditorLanguage } from '../i18n/language';
|
|
6
|
+
import PreviewChromeControls from './PreviewChromeControls.svelte';
|
|
6
7
|
|
|
7
8
|
// The words of the editor in the language of the page: the editor above this one chose it (see `provideEditorLanguage`).
|
|
8
9
|
const { t } = useEditorLanguage();
|
|
@@ -121,10 +122,8 @@
|
|
|
121
122
|
const MIN_WIDTH = 120;
|
|
122
123
|
const MIN_HEIGHT = 80;
|
|
123
124
|
const HANDLE_MARGIN = 6;
|
|
124
|
-
/** Inset
|
|
125
|
-
|
|
126
|
-
const RESIZE_PAD_TOP = 12;
|
|
127
|
-
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;
|
|
128
127
|
const PREVIEW_CHROME_HEADER_H = 36;
|
|
129
128
|
const PREVIEW_STROKE = '#3b82f6';
|
|
130
129
|
const PREVIEW_STROKE_ALT = '#f97316';
|
|
@@ -159,8 +158,8 @@
|
|
|
159
158
|
}
|
|
160
159
|
if (hasFixedBounds && boundsWidth != null && boundsHeight != null) {
|
|
161
160
|
return {
|
|
162
|
-
maxWidth: Math.max(MIN_WIDTH, boundsWidth -
|
|
163
|
-
maxHeight: Math.max(MIN_HEIGHT, boundsHeight -
|
|
161
|
+
maxWidth: Math.max(MIN_WIDTH, boundsWidth - edgeInset * 2),
|
|
162
|
+
maxHeight: Math.max(MIN_HEIGHT, boundsHeight - edgeInset - edgeInset - chromeH),
|
|
164
163
|
};
|
|
165
164
|
}
|
|
166
165
|
return { maxWidth: 720, maxHeight: 420 };
|
|
@@ -176,17 +175,17 @@
|
|
|
176
175
|
shellWidth: number,
|
|
177
176
|
shellHeight: number
|
|
178
177
|
): { left: number; top: number } {
|
|
179
|
-
const maxLeft = Math.max(
|
|
180
|
-
const maxTop = Math.max(
|
|
178
|
+
const maxLeft = Math.max(edgeInset, stageWidth - shellWidth - edgeInset);
|
|
179
|
+
const maxTop = Math.max(edgeInset, stageHeight - shellHeight - edgeInset);
|
|
181
180
|
// rtl-ok: the half of the stage is named by its place on the screen, see the prop
|
|
182
181
|
if (defaultStageAlign === 'right-half') {
|
|
183
182
|
const rightHalfCenterX = stageWidth * 0.75;
|
|
184
|
-
const left = clampDimension(rightHalfCenterX - shellWidth / 2,
|
|
185
|
-
const top = clampDimension((stageHeight - shellHeight) / 2,
|
|
183
|
+
const left = clampDimension(rightHalfCenterX - shellWidth / 2, edgeInset, maxLeft);
|
|
184
|
+
const top = clampDimension((stageHeight - shellHeight) / 2, edgeInset, maxTop);
|
|
186
185
|
return { left, top };
|
|
187
186
|
}
|
|
188
|
-
const left = clampDimension((stageWidth - shellWidth) / 2,
|
|
189
|
-
const top = clampDimension((stageHeight - shellHeight) / 2,
|
|
187
|
+
const left = clampDimension((stageWidth - shellWidth) / 2, edgeInset, maxLeft);
|
|
188
|
+
const top = clampDimension((stageHeight - shellHeight) / 2, edgeInset, maxTop);
|
|
190
189
|
return { left, top };
|
|
191
190
|
}
|
|
192
191
|
|
|
@@ -271,10 +270,10 @@
|
|
|
271
270
|
|
|
272
271
|
function onPointerMove(e: PointerEvent) {
|
|
273
272
|
if (!stageEl) return;
|
|
274
|
-
const maxLeft = Math.max(
|
|
275
|
-
const maxTop = Math.max(
|
|
276
|
-
previewShellLeft = clampDimension(startLeft + (e.clientX - startX),
|
|
277
|
-
previewShellTop = clampDimension(startTop + (e.clientY - startY),
|
|
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);
|
|
278
277
|
}
|
|
279
278
|
|
|
280
279
|
function endDrag() {
|
|
@@ -365,7 +364,7 @@
|
|
|
365
364
|
$: showResizeHandles = enableResize && (mode === 'controlled' ? isContentReady : iframeLoaded);
|
|
366
365
|
$: hasFixedBounds = boundsWidth != null && boundsHeight != null;
|
|
367
366
|
$: containerPaddingStyle = enableResize
|
|
368
|
-
? `padding: ${
|
|
367
|
+
? `padding: ${edgeInset}px ${edgeInset}px ${edgeInset}px ${edgeInset}px;`
|
|
369
368
|
: !hasFixedBounds && !compactChrome && (showUrlBar || mode === 'controlled')
|
|
370
369
|
? `padding: ${HANDLE_MARGIN}px;`
|
|
371
370
|
: '';
|
|
@@ -926,66 +925,29 @@
|
|
|
926
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>
|
|
927
926
|
{/if}
|
|
928
927
|
<span class="preview-chrome-title text-[11px] font-semibold uppercase tracking-wide text-gray-500 shrink-0">{$t('editor.preview.title')}</span>
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
<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>
|
|
953
|
-
</button>
|
|
954
|
-
</div>
|
|
955
|
-
{/if}
|
|
956
|
-
{#if showDimensionInputs}
|
|
957
|
-
<div class="preview-chrome-dimensions flex items-center gap-1.5 {showChromeDeviceToggle ? '' : 'ms-auto'}">
|
|
958
|
-
<label class="preview-dim-label flex items-center gap-1 text-[10px] font-semibold text-gray-500">
|
|
959
|
-
<span>{$t('editor.preview.widthShort')}</span>
|
|
960
|
-
<input
|
|
961
|
-
type="number"
|
|
962
|
-
min={MIN_WIDTH}
|
|
963
|
-
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"
|
|
964
|
-
value={widthInputValue}
|
|
965
|
-
oninput={(e) => {
|
|
966
|
-
widthInputValue = e.currentTarget.value;
|
|
967
|
-
applyDimensionFromInput('width', e.currentTarget.value);
|
|
968
|
-
}}
|
|
969
|
-
onpointerdown={(e) => e.stopPropagation()}
|
|
970
|
-
/>
|
|
971
|
-
</label>
|
|
972
|
-
<span class="text-[10px] text-gray-400">×</span>
|
|
973
|
-
<label class="preview-dim-label flex items-center gap-1 text-[10px] font-semibold text-gray-500">
|
|
974
|
-
<span>{$t('editor.preview.heightShort')}</span>
|
|
975
|
-
<input
|
|
976
|
-
type="number"
|
|
977
|
-
min={MIN_HEIGHT}
|
|
978
|
-
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"
|
|
979
|
-
value={heightInputValue}
|
|
980
|
-
oninput={(e) => {
|
|
981
|
-
heightInputValue = e.currentTarget.value;
|
|
982
|
-
applyDimensionFromInput('height', e.currentTarget.value);
|
|
983
|
-
}}
|
|
984
|
-
onpointerdown={(e) => e.stopPropagation()}
|
|
985
|
-
/>
|
|
986
|
-
</label>
|
|
987
|
-
</div>
|
|
988
|
-
{/if}
|
|
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
|
+
/>
|
|
989
951
|
</div>
|
|
990
952
|
{/if}
|
|
991
953
|
|
|
@@ -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;
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export type { ContentSchemaItemView, ContentSchemaPropertyView, ContentSchemaVie
|
|
|
2
2
|
export { default as PropsEditor } from './components/editors/PropsEditor.svelte';
|
|
3
3
|
export { default as FontInputController } from './components/ic/FontInputController.svelte';
|
|
4
4
|
export { default as WidgetPreview } from './components/WidgetPreview.svelte';
|
|
5
|
+
export { default as PreviewChromeControls } from './components/PreviewChromeControls.svelte';
|
|
5
6
|
export { setFileBrowserUploadConfig } from '@widgetic/file-browser';
|
|
6
7
|
export { setGoogleFontsApiKey, getGoogleFontsKey } from './config';
|
|
7
8
|
export { default as EmbedEditor } from './components/editors/EmbedEditor.svelte';
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
export { default as PropsEditor } from './components/editors/PropsEditor.svelte';
|
|
5
5
|
export { default as FontInputController } from './components/ic/FontInputController.svelte';
|
|
6
6
|
export { default as WidgetPreview } from './components/WidgetPreview.svelte';
|
|
7
|
+
export { default as PreviewChromeControls } from './components/PreviewChromeControls.svelte';
|
|
7
8
|
export { setFileBrowserUploadConfig } from '@widgetic/file-browser';
|
|
8
9
|
export { setGoogleFontsApiKey, getGoogleFontsKey } from './config';
|
|
9
10
|
// Embed Editor
|