@widgetic/editor 4.0.3 → 4.0.4

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.
@@ -59,6 +59,12 @@
59
59
  export let mobileViewportWidth: number = 390;
60
60
  export let mobileViewportHeight: number = 667;
61
61
  export let enableResize: boolean = false;
62
+ /** Drag only via the preview chrome header (keeps iframe interactive). */
63
+ export let enableDrag: boolean = false;
64
+ /** W×H number inputs in the chrome header, live-synced with resize. */
65
+ export let showDimensionInputs: boolean = false;
66
+ /** Initial / reset placement inside the stage. */
67
+ export let defaultStageAlign: 'center' | 'right-half' = 'center';
62
68
  /** Observe stage size and fill it (desktop modal) */
63
69
  export let autoFitStage: boolean = false;
64
70
  /** No padding/border on preview-content-container — matches widget-creator Edit panel */
@@ -87,32 +93,163 @@
87
93
  const MIN_WIDTH = 120;
88
94
  const MIN_HEIGHT = 80;
89
95
  const HANDLE_MARGIN = 6;
90
- /** Inset padding so resize handles (-4px) and dimension badge stay inside bounds */
91
- const RESIZE_PAD_X = 8;
92
- const RESIZE_PAD_TOP = 8;
93
- const RESIZE_PAD_BOTTOM = 22;
96
+ /** Inset padding so resize handles stay inside bounds */
97
+ const RESIZE_PAD_X = 12;
98
+ const RESIZE_PAD_TOP = 12;
99
+ const RESIZE_PAD_BOTTOM = 12;
100
+ const PREVIEW_CHROME_HEADER_H = 36;
101
+ const PREVIEW_STROKE = '#3b82f6';
102
+ const PREVIEW_STROKE_ALT = '#f97316';
94
103
  let isResizing = false;
104
+ let isDraggingPreview = false;
105
+ let isSymmetricalResize = false;
106
+ let previewShellLeft = 0;
107
+ let previewShellTop = 0;
108
+ let previewPositionReady = false;
109
+ let widthInputValue = '';
110
+ let heightInputValue = '';
95
111
  let contentContainerEl: HTMLElement | null = null;
96
112
  let resizeStageEl: HTMLElement | null = null;
97
113
  let stageResizeObserver: ResizeObserver | null = null;
98
114
 
115
+ $: showPreviewChrome = enableDrag || showDimensionInputs;
116
+ $: chromeHeaderHeight = showPreviewChrome ? PREVIEW_CHROME_HEADER_H : 0;
117
+ $: if (resizeWidth != null) widthInputValue = String(Math.round(resizeWidth));
118
+ $: if (resizeHeight != null) heightInputValue = String(Math.round(resizeHeight));
119
+ $: previewStrokeColor = isSymmetricalResize ? PREVIEW_STROKE_ALT : PREVIEW_STROKE;
120
+
99
121
  export function getResizeLimits(): { maxWidth: number; maxHeight: number } {
100
122
  const stageEl = resizeStageEl ?? contentContainerEl;
123
+ const chromeH = showPreviewChrome ? PREVIEW_CHROME_HEADER_H : 0;
101
124
  if (stageEl) {
102
125
  return {
103
126
  maxWidth: Math.max(MIN_WIDTH, stageEl.clientWidth),
104
- maxHeight: Math.max(MIN_HEIGHT, stageEl.clientHeight),
127
+ maxHeight: Math.max(MIN_HEIGHT, stageEl.clientHeight - chromeH),
105
128
  };
106
129
  }
107
130
  if (hasFixedBounds && boundsWidth != null && boundsHeight != null) {
108
131
  return {
109
132
  maxWidth: Math.max(MIN_WIDTH, boundsWidth - RESIZE_PAD_X * 2),
110
- maxHeight: Math.max(MIN_HEIGHT, boundsHeight - RESIZE_PAD_TOP - RESIZE_PAD_BOTTOM),
133
+ maxHeight: Math.max(MIN_HEIGHT, boundsHeight - RESIZE_PAD_TOP - RESIZE_PAD_BOTTOM - chromeH),
111
134
  };
112
135
  }
113
136
  return { maxWidth: 720, maxHeight: 420 };
114
137
  }
115
138
 
139
+ function clampDimension(value: number, min: number, max: number): number {
140
+ return Math.max(min, Math.min(max, value));
141
+ }
142
+
143
+ function computeDefaultShellPosition(
144
+ stageWidth: number,
145
+ stageHeight: number,
146
+ shellWidth: number,
147
+ shellHeight: number
148
+ ): { left: number; top: number } {
149
+ const maxLeft = Math.max(RESIZE_PAD_X, stageWidth - shellWidth - RESIZE_PAD_X);
150
+ const maxTop = Math.max(RESIZE_PAD_TOP, stageHeight - shellHeight - RESIZE_PAD_BOTTOM);
151
+ if (defaultStageAlign === 'right-half') {
152
+ const rightHalfCenterX = stageWidth * 0.75;
153
+ const left = clampDimension(rightHalfCenterX - shellWidth / 2, RESIZE_PAD_X, maxLeft);
154
+ const top = clampDimension((stageHeight - shellHeight) / 2, RESIZE_PAD_TOP, maxTop);
155
+ return { left, top };
156
+ }
157
+ const left = clampDimension((stageWidth - shellWidth) / 2, RESIZE_PAD_X, maxLeft);
158
+ const top = clampDimension((stageHeight - shellHeight) / 2, RESIZE_PAD_TOP, maxTop);
159
+ return { left, top };
160
+ }
161
+
162
+ /** Dock / reset preview shell to defaultStageAlign. */
163
+ export function resetPreviewPosition(): void {
164
+ const stageEl = resizeStageEl ?? contentContainerEl;
165
+ if (!stageEl || resizeWidth == null || resizeHeight == null) return;
166
+ if (stageEl.clientWidth < 40 || stageEl.clientHeight < 40) {
167
+ requestAnimationFrame(() => resetPreviewPosition());
168
+ return;
169
+ }
170
+ const shellWidth = resizeWidth;
171
+ const shellHeight = resizeHeight + chromeHeaderHeight;
172
+ const next = computeDefaultShellPosition(
173
+ stageEl.clientWidth,
174
+ stageEl.clientHeight,
175
+ shellWidth,
176
+ shellHeight
177
+ );
178
+ previewShellLeft = next.left;
179
+ previewShellTop = next.top;
180
+ previewPositionReady = true;
181
+ console.log('[WidgetPreview] Reset preview position', {
182
+ align: defaultStageAlign,
183
+ left: previewShellLeft,
184
+ top: previewShellTop,
185
+ });
186
+ }
187
+
188
+ function ensurePreviewPosition(): void {
189
+ if (previewPositionReady) return;
190
+ resetPreviewPosition();
191
+ }
192
+
193
+ function applyDimensionFromInput(dimension: 'width' | 'height', raw: string) {
194
+ const { maxWidth, maxHeight } = getResizeLimits();
195
+ const parsed = Number.parseInt(raw, 10);
196
+ if (!Number.isFinite(parsed)) return;
197
+ if (dimension === 'width') {
198
+ resizeWidth = clampDimension(parsed, MIN_WIDTH, maxWidth);
199
+ widthInputValue = String(Math.round(resizeWidth));
200
+ } else {
201
+ resizeHeight = clampDimension(parsed, MIN_HEIGHT, maxHeight);
202
+ heightInputValue = String(Math.round(resizeHeight));
203
+ }
204
+ if (resizeWidth != null && resizeHeight != null) {
205
+ dispatch('resize', { width: resizeWidth, height: resizeHeight });
206
+ }
207
+ console.log('[WidgetPreview] Dimension input', { dimension, value: parsed });
208
+ }
209
+
210
+ function startPreviewDrag(event: PointerEvent) {
211
+ if (!enableDrag || isResizing) return;
212
+ const target = event.target as HTMLElement | null;
213
+ if (target?.closest('input, button, a, label')) return;
214
+ event.preventDefault();
215
+ event.stopPropagation();
216
+ isDraggingPreview = true;
217
+ const startX = event.clientX;
218
+ const startY = event.clientY;
219
+ const startLeft = previewShellLeft;
220
+ const startTop = previewShellTop;
221
+ const stageEl = resizeStageEl ?? contentContainerEl;
222
+ const shellWidth = resizeWidth ?? 0;
223
+ const shellHeight = (resizeHeight ?? 0) + chromeHeaderHeight;
224
+
225
+ function onPointerMove(e: PointerEvent) {
226
+ if (!stageEl) return;
227
+ const maxLeft = Math.max(RESIZE_PAD_X, stageEl.clientWidth - shellWidth - RESIZE_PAD_X);
228
+ const maxTop = Math.max(RESIZE_PAD_TOP, stageEl.clientHeight - shellHeight - RESIZE_PAD_BOTTOM);
229
+ previewShellLeft = clampDimension(startLeft + (e.clientX - startX), RESIZE_PAD_X, maxLeft);
230
+ previewShellTop = clampDimension(startTop + (e.clientY - startY), RESIZE_PAD_TOP, maxTop);
231
+ }
232
+
233
+ function endDrag() {
234
+ isDraggingPreview = false;
235
+ document.removeEventListener('pointermove', onPointerMove);
236
+ document.removeEventListener('pointerup', endDrag);
237
+ document.removeEventListener('pointercancel', endDrag);
238
+ document.body.style.removeProperty('user-select');
239
+ document.body.style.removeProperty('cursor');
240
+ console.log('[WidgetPreview] Preview drag end', {
241
+ left: previewShellLeft,
242
+ top: previewShellTop,
243
+ });
244
+ }
245
+
246
+ document.body.style.userSelect = 'none';
247
+ document.body.style.cursor = 'move';
248
+ document.addEventListener('pointermove', onPointerMove);
249
+ document.addEventListener('pointerup', endDrag);
250
+ document.addEventListener('pointercancel', endDrag);
251
+ }
252
+
116
253
  /** Firefox/Chrome skip painting a cross-origin iframe that started at opacity 0 or inside a CSS filter until a reflow (opening DevTools). */
117
254
  let iframePaintNudgeGeneration = 0;
118
255
  export function nudgeIframePaint(): void {
@@ -475,22 +612,64 @@
475
612
  event.preventDefault();
476
613
  event.stopPropagation();
477
614
  isResizing = true;
615
+ isSymmetricalResize = event.altKey;
478
616
  const startX = event.clientX;
479
617
  const startY = event.clientY;
480
-
618
+ const startLeft = previewShellLeft;
619
+ const startTop = previewShellTop;
481
620
  const { maxWidth, maxHeight } = getResizeLimits();
482
621
  const startWidth = resizeWidth ?? maxWidth;
483
622
  const startHeight = resizeHeight ?? maxHeight;
484
623
 
485
624
  function onPointerMove(e: PointerEvent) {
486
- const deltaX = (e.clientX - startX) * signX * 2;
487
- const deltaY = (e.clientY - startY) * signY * 2;
488
- resizeWidth = Math.max(MIN_WIDTH, Math.min(maxWidth, startWidth + deltaX));
489
- resizeHeight = Math.max(MIN_HEIGHT, Math.min(maxHeight, startHeight + deltaY));
625
+ const useAlt = e.altKey;
626
+ isSymmetricalResize = useAlt;
627
+ const dx = e.clientX - startX;
628
+ const dy = e.clientY - startY;
629
+
630
+ if (useAlt) {
631
+ const deltaW = signX !== 0 ? dx * signX * 2 : 0;
632
+ const deltaH = signY !== 0 ? dy * signY * 2 : 0;
633
+ const nextW = clampDimension(startWidth + deltaW, MIN_WIDTH, maxWidth);
634
+ const nextH = clampDimension(startHeight + deltaH, MIN_HEIGHT, maxHeight);
635
+ resizeWidth = nextW;
636
+ resizeHeight = nextH;
637
+ previewShellLeft = startLeft + (startWidth - nextW) / 2;
638
+ previewShellTop = startTop + (startHeight - nextH) / 2;
639
+ } else {
640
+ let nextW = startWidth;
641
+ let nextH = startHeight;
642
+ let nextLeft = startLeft;
643
+ let nextTop = startTop;
644
+
645
+ if (signX === -1) {
646
+ nextW = clampDimension(startWidth - dx, MIN_WIDTH, maxWidth);
647
+ nextLeft = startLeft + (startWidth - nextW);
648
+ } else if (signX === 1) {
649
+ nextW = clampDimension(startWidth + dx, MIN_WIDTH, maxWidth);
650
+ }
651
+
652
+ if (signY === -1) {
653
+ nextH = clampDimension(startHeight - dy, MIN_HEIGHT, maxHeight);
654
+ nextTop = startTop + (startHeight - nextH);
655
+ } else if (signY === 1) {
656
+ nextH = clampDimension(startHeight + dy, MIN_HEIGHT, maxHeight);
657
+ }
658
+
659
+ resizeWidth = nextW;
660
+ resizeHeight = nextH;
661
+ previewShellLeft = nextLeft;
662
+ previewShellTop = nextTop;
663
+ }
664
+
665
+ if (resizeWidth != null && resizeHeight != null) {
666
+ dispatch('resize', { width: resizeWidth, height: resizeHeight });
667
+ }
490
668
  }
491
669
 
492
670
  function endResize() {
493
671
  isResizing = false;
672
+ isSymmetricalResize = false;
494
673
  document.removeEventListener('pointermove', onPointerMove);
495
674
  document.removeEventListener('pointerup', endResize);
496
675
  document.removeEventListener('pointercancel', endResize);
@@ -502,7 +681,14 @@
502
681
  }
503
682
 
504
683
  document.body.style.userSelect = 'none';
505
- document.body.style.cursor = 'nwse-resize';
684
+ document.body.style.cursor =
685
+ signX !== 0 && signY !== 0
686
+ ? signX * signY > 0
687
+ ? 'nwse-resize'
688
+ : 'nesw-resize'
689
+ : signX !== 0
690
+ ? 'ew-resize'
691
+ : 'ns-resize';
506
692
  document.addEventListener('pointermove', onPointerMove);
507
693
  document.addEventListener('pointerup', endResize);
508
694
  document.addEventListener('pointercancel', endResize);
@@ -510,6 +696,7 @@
510
696
 
511
697
  $: if (enableResize && contentContainerEl && (resizeWidth != null || resizeHeight != null)) {
512
698
  clampResizeToLimits();
699
+ ensurePreviewPosition();
513
700
  }
514
701
 
515
702
  $: if (autoFitStage && resizeStageEl) {
@@ -523,6 +710,18 @@
523
710
 
524
711
  onMount(() => {
525
712
  window.addEventListener('message', handleMessage);
713
+ const onAltDown = (e: KeyboardEvent) => {
714
+ if (e.key === 'Alt') isSymmetricalResize = true;
715
+ };
716
+ const onAltUp = (e: KeyboardEvent) => {
717
+ if (e.key === 'Alt' && !isResizing) isSymmetricalResize = false;
718
+ };
719
+ window.addEventListener('keydown', onAltDown);
720
+ window.addEventListener('keyup', onAltUp);
721
+ return () => {
722
+ window.removeEventListener('keydown', onAltDown);
723
+ window.removeEventListener('keyup', onAltUp);
724
+ };
526
725
  });
527
726
 
528
727
  onDestroy(() => {
@@ -636,12 +835,66 @@
636
835
  {:else if effectiveSrc || (mode === 'standalone' && isResolvingUrl)}
637
836
  <div
638
837
  bind:this={resizeStageEl}
639
- class="{enableResize ? 'preview-resize-stage-ct absolute inset-0 overflow-hidden flex items-center justify-center' : 'preview-resize-stage-ct relative flex items-center justify-center w-full h-full'}"
838
+ class="{enableResize ? 'preview-resize-stage-ct absolute inset-0 overflow-hidden' : 'preview-resize-stage-ct relative flex items-center justify-center w-full h-full'}"
640
839
  >
641
840
  <div
642
- class="preview-iframe-wrapper relative"
643
- style="width: {resizeWidth ? resizeWidth + 'px' : '100%'}; height: {resizeHeight ? resizeHeight + 'px' : '100%'}; transition: {isResizing ? 'none' : 'width 0.2s, height 0.2s'};"
841
+ class="preview-iframe-shell relative flex flex-col {enableResize ? 'preview-iframe-shell-positioned' : ''}"
842
+ class:preview-iframe-shell-symmetrical={isSymmetricalResize}
843
+ style="{enableResize
844
+ ? `position: absolute; left: ${previewShellLeft}px; top: ${previewShellTop}px; width: ${resizeWidth ? resizeWidth + 'px' : '100%'};`
845
+ : `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'};"
644
846
  >
847
+ {#if showPreviewChrome}
848
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
849
+ <div
850
+ class="preview-chrome-header flex items-center gap-2 h-9 px-2 border border-b-0 rounded-t-md bg-white select-none {enableDrag ? 'cursor-move' : ''}"
851
+ style="border-color: {previewStrokeColor};"
852
+ onpointerdown={startPreviewDrag}
853
+ >
854
+ {#if enableDrag}
855
+ <span class="preview-chrome-drag-grip shrink-0 text-gray-400 text-xs leading-none" title="Drag preview" aria-hidden="true">⠿</span>
856
+ {/if}
857
+ <span class="preview-chrome-title text-[11px] font-semibold uppercase tracking-wide text-gray-500 shrink-0">Preview</span>
858
+ {#if showDimensionInputs}
859
+ <div class="preview-chrome-dimensions ml-auto flex items-center gap-1.5">
860
+ <label class="preview-dim-label flex items-center gap-1 text-[10px] font-semibold text-gray-500">
861
+ <span>W</span>
862
+ <input
863
+ type="number"
864
+ min={MIN_WIDTH}
865
+ 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"
866
+ value={widthInputValue}
867
+ oninput={(e) => {
868
+ widthInputValue = e.currentTarget.value;
869
+ applyDimensionFromInput('width', e.currentTarget.value);
870
+ }}
871
+ onpointerdown={(e) => e.stopPropagation()}
872
+ />
873
+ </label>
874
+ <span class="text-[10px] text-gray-400">×</span>
875
+ <label class="preview-dim-label flex items-center gap-1 text-[10px] font-semibold text-gray-500">
876
+ <span>H</span>
877
+ <input
878
+ type="number"
879
+ min={MIN_HEIGHT}
880
+ 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"
881
+ value={heightInputValue}
882
+ oninput={(e) => {
883
+ heightInputValue = e.currentTarget.value;
884
+ applyDimensionFromInput('height', e.currentTarget.value);
885
+ }}
886
+ onpointerdown={(e) => e.stopPropagation()}
887
+ />
888
+ </label>
889
+ </div>
890
+ {/if}
891
+ </div>
892
+ {/if}
893
+
894
+ <div
895
+ class="preview-iframe-wrapper relative"
896
+ style="width: 100%; height: {resizeHeight ? resizeHeight + 'px' : '100%'};"
897
+ >
645
898
  {#if showResizeHandles}
646
899
  <!-- svelte-ignore a11y-no-static-element-interactions -->
647
900
  <div class="preview-resize-handle preview-resize-handle-tl" onpointerdown={(e) => startResize(e, -1, -1)}></div>
@@ -651,9 +904,17 @@
651
904
  <div class="preview-resize-handle preview-resize-handle-bl" onpointerdown={(e) => startResize(e, -1, 1)}></div>
652
905
  <!-- svelte-ignore a11y-no-static-element-interactions -->
653
906
  <div class="preview-resize-handle preview-resize-handle-br" onpointerdown={(e) => startResize(e, 1, 1)}></div>
907
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
908
+ <div class="preview-resize-handle preview-resize-handle-t" onpointerdown={(e) => startResize(e, 0, -1)}></div>
909
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
910
+ <div class="preview-resize-handle preview-resize-handle-r" onpointerdown={(e) => startResize(e, 1, 0)}></div>
911
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
912
+ <div class="preview-resize-handle preview-resize-handle-b" onpointerdown={(e) => startResize(e, 0, 1)}></div>
913
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
914
+ <div class="preview-resize-handle preview-resize-handle-l" onpointerdown={(e) => startResize(e, -1, 0)}></div>
654
915
  {/if}
655
916
 
656
- {#if resizeWidth || resizeHeight}
917
+ {#if (resizeWidth || resizeHeight) && !showDimensionInputs}
657
918
  <div
658
919
  class="widget-preview-dimension-badge absolute left-1/2 -translate-x-1/2 text-[10px] bg-black/70 px-1.5 py-px rounded text-gray-300 select-none pointer-events-none z-20 whitespace-nowrap"
659
920
  style="top: calc(100% + 4px);"
@@ -662,7 +923,10 @@
662
923
  </div>
663
924
  {/if}
664
925
 
665
- <div class="preview-iframe-container relative w-full h-full overflow-hidden {compactChrome ? '' : showUrlBar || mode === 'controlled' || enableResize ? 'border border-gray-300' : ''}">
926
+ <div
927
+ class="preview-iframe-container relative w-full h-full overflow-hidden {compactChrome && !enableResize ? '' : showUrlBar || mode === 'controlled' || enableResize ? 'border' : ''}"
928
+ style={enableResize ? `border-color: ${previewStrokeColor}; border-radius: ${showPreviewChrome ? '0 0 6px 6px' : '6px'};` : ''}
929
+ >
666
930
  {#if mode === 'standalone' && iframeError}
667
931
  <div class="widget-preview-error-ct absolute inset-0 flex items-center justify-center bg-white z-10 rounded">
668
932
  <div class="widget-preview-error-content-ct flex flex-col items-center gap-2 text-center px-4">
@@ -756,6 +1020,7 @@
756
1020
  <GenerateLoader overlay title="Generating code…" subtitle="" size="lg" />
757
1021
  {/if}
758
1022
  </div>
1023
+ </div>
759
1024
  </div>
760
1025
  </div>
761
1026
 
@@ -784,10 +1049,10 @@
784
1049
  {/if}
785
1050
  </div>
786
1051
 
787
- {#if isResizing}
1052
+ {#if isResizing || isDraggingPreview}
788
1053
  <div
789
- class="widget-preview-resize-guard-ct fixed inset-0 z-999999 cursor-nwse-resize"
790
- style="background: transparent;"
1054
+ class="widget-preview-resize-guard-ct fixed inset-0 z-999999"
1055
+ style="background: transparent; cursor: {isDraggingPreview ? 'move' : 'nwse-resize'};"
791
1056
  aria-hidden="true"
792
1057
  ></div>
793
1058
  {/if}
@@ -799,38 +1064,39 @@
799
1064
 
800
1065
  .preview-resize-handle {
801
1066
  position: absolute;
802
- width: 16px;
803
- height: 16px;
1067
+ width: 10px;
1068
+ height: 10px;
804
1069
  z-index: 30;
805
- border-radius: 2px;
806
- }
807
-
808
- .preview-resize-handle::before,
809
- .preview-resize-handle::after {
810
- content: '';
811
- position: absolute;
812
- background: #94a3b8;
1070
+ box-sizing: border-box;
1071
+ background: #ffffff;
1072
+ border: 1.5px solid #3b82f6;
813
1073
  border-radius: 1px;
814
1074
  }
815
1075
 
816
- .preview-resize-handle-tl { top: -4px; left: -4px; cursor: nwse-resize; }
817
- .preview-resize-handle-tl::before { top: 0; left: 0; width: 10px; height: 2px; }
818
- .preview-resize-handle-tl::after { top: 0; left: 0; width: 2px; height: 10px; }
819
-
820
- .preview-resize-handle-tr { top: -4px; right: -4px; cursor: nesw-resize; }
821
- .preview-resize-handle-tr::before { top: 0; right: 0; width: 10px; height: 2px; }
822
- .preview-resize-handle-tr::after { top: 0; right: 0; width: 2px; height: 10px; }
823
-
824
- .preview-resize-handle-bl { bottom: -4px; left: -4px; cursor: nesw-resize; }
825
- .preview-resize-handle-bl::before { bottom: 0; left: 0; width: 10px; height: 2px; }
826
- .preview-resize-handle-bl::after { bottom: 0; left: 0; width: 2px; height: 10px; }
1076
+ .preview-iframe-shell-symmetrical .preview-resize-handle,
1077
+ .preview-iframe-shell-symmetrical .preview-iframe-container {
1078
+ border-color: #f97316 !important;
1079
+ }
827
1080
 
828
- .preview-resize-handle-br { bottom: -4px; right: -4px; cursor: nwse-resize; }
829
- .preview-resize-handle-br::before { bottom: 0; right: 0; width: 10px; height: 2px; }
830
- .preview-resize-handle-br::after { bottom: 0; right: 0; width: 2px; height: 10px; }
1081
+ .preview-chrome-header button:focus,
1082
+ .preview-chrome-header button:focus-visible,
1083
+ .preview-chrome-header input:focus,
1084
+ .preview-chrome-header input:focus-visible {
1085
+ outline: none;
1086
+ box-shadow: none;
1087
+ }
831
1088
 
832
- .preview-resize-handle:hover::before,
833
- .preview-resize-handle:hover::after {
834
- background: #64748b;
1089
+ .preview-resize-handle-tl { top: -5px; left: -5px; cursor: nwse-resize; }
1090
+ .preview-resize-handle-tr { top: -5px; right: -5px; cursor: nesw-resize; }
1091
+ .preview-resize-handle-bl { bottom: -5px; left: -5px; cursor: nesw-resize; }
1092
+ .preview-resize-handle-br { bottom: -5px; right: -5px; cursor: nwse-resize; }
1093
+ .preview-resize-handle-t { top: -5px; left: 50%; margin-left: -5px; cursor: ns-resize; }
1094
+ .preview-resize-handle-b { bottom: -5px; left: 50%; margin-left: -5px; cursor: ns-resize; }
1095
+ .preview-resize-handle-l { left: -5px; top: 50%; margin-top: -5px; cursor: ew-resize; }
1096
+ .preview-resize-handle-r { right: -5px; top: 50%; margin-top: -5px; cursor: ew-resize; }
1097
+
1098
+ .preview-resize-handle:hover {
1099
+ background: #eff6ff;
1100
+ border-color: #2563eb;
835
1101
  }
836
1102
  </style>
@@ -43,6 +43,9 @@ declare const WidgetPreview: $$__sveltets_2_IsomorphicComponent<{
43
43
  mobileViewportWidth?: number;
44
44
  mobileViewportHeight?: number;
45
45
  enableResize?: boolean;
46
+ /** Drag only via the preview chrome header (keeps iframe interactive). */ enableDrag?: boolean;
47
+ /** W×H number inputs in the chrome header, live-synced with resize. */ showDimensionInputs?: boolean;
48
+ /** Initial / reset placement inside the stage. */ defaultStageAlign?: "center" | "right-half";
46
49
  /** Observe stage size and fill it (desktop modal) */ autoFitStage?: boolean;
47
50
  /** No padding/border on preview-content-container — matches widget-creator Edit panel */ compactChrome?: boolean;
48
51
  /** Fixed preview stage size; inner iframe resizes within these bounds */ boundsWidth?: number | null;
@@ -54,6 +57,7 @@ declare const WidgetPreview: $$__sveltets_2_IsomorphicComponent<{
54
57
  maxWidth: number;
55
58
  maxHeight: number;
56
59
  };
60
+ resetPreviewPosition?: () => void;
57
61
  nudgeIframePaint?: () => void;
58
62
  fitToContainer?: () => void;
59
63
  sendMessageToPreview?: (message: Record<string, unknown>) => void;
@@ -90,6 +94,7 @@ declare const WidgetPreview: $$__sveltets_2_IsomorphicComponent<{
90
94
  maxWidth: number;
91
95
  maxHeight: number;
92
96
  };
97
+ resetPreviewPosition: () => void;
93
98
  nudgeIframePaint: () => void;
94
99
  fitToContainer: () => void;
95
100
  sendMessageToPreview: (message: Record<string, unknown>) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@widgetic/editor",
3
- "version": "4.0.3",
3
+ "version": "4.0.4",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",