@widgetic/editor 4.0.3 → 4.0.5

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,17 @@
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
+ /**
67
+ * concentric — always resize from center (widget-creator / Canvas).
68
+ * corner — opposite corner fixed; hold Alt for concentric (composition page).
69
+ */
70
+ export let resizeMode: 'concentric' | 'corner' = 'concentric';
71
+ /** Initial / reset placement inside the stage. */
72
+ export let defaultStageAlign: 'center' | 'right-half' = 'center';
62
73
  /** Observe stage size and fill it (desktop modal) */
63
74
  export let autoFitStage: boolean = false;
64
75
  /** No padding/border on preview-content-container — matches widget-creator Edit panel */
@@ -87,32 +98,165 @@
87
98
  const MIN_WIDTH = 120;
88
99
  const MIN_HEIGHT = 80;
89
100
  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;
101
+ /** Inset padding so resize handles stay inside bounds */
102
+ const RESIZE_PAD_X = 12;
103
+ const RESIZE_PAD_TOP = 12;
104
+ const RESIZE_PAD_BOTTOM = 12;
105
+ const PREVIEW_CHROME_HEADER_H = 36;
106
+ const PREVIEW_STROKE = '#3b82f6';
107
+ const PREVIEW_STROKE_ALT = '#f97316';
94
108
  let isResizing = false;
109
+ let isDraggingPreview = false;
110
+ /** Alt held — keeps orange stroke on composition even after pointerup. */
111
+ let isAltKeyDown = false;
112
+ let previewShellLeft = 0;
113
+ let previewShellTop = 0;
114
+ let previewPositionReady = false;
115
+ let widthInputValue = '';
116
+ let heightInputValue = '';
95
117
  let contentContainerEl: HTMLElement | null = null;
96
118
  let resizeStageEl: HTMLElement | null = null;
97
119
  let stageResizeObserver: ResizeObserver | null = null;
98
120
 
121
+ $: showPreviewChrome = enableDrag || showDimensionInputs;
122
+ $: chromeHeaderHeight = showPreviewChrome ? PREVIEW_CHROME_HEADER_H : 0;
123
+ $: if (resizeWidth != null) widthInputValue = String(Math.round(resizeWidth));
124
+ $: if (resizeHeight != null) heightInputValue = String(Math.round(resizeHeight));
125
+ $: showSymmetricalStroke = resizeMode === 'corner' && isAltKeyDown;
126
+ $: previewStrokeColor = showSymmetricalStroke ? PREVIEW_STROKE_ALT : PREVIEW_STROKE;
127
+
99
128
  export function getResizeLimits(): { maxWidth: number; maxHeight: number } {
100
129
  const stageEl = resizeStageEl ?? contentContainerEl;
130
+ const chromeH = showPreviewChrome ? PREVIEW_CHROME_HEADER_H : 0;
101
131
  if (stageEl) {
102
132
  return {
103
133
  maxWidth: Math.max(MIN_WIDTH, stageEl.clientWidth),
104
- maxHeight: Math.max(MIN_HEIGHT, stageEl.clientHeight),
134
+ maxHeight: Math.max(MIN_HEIGHT, stageEl.clientHeight - chromeH),
105
135
  };
106
136
  }
107
137
  if (hasFixedBounds && boundsWidth != null && boundsHeight != null) {
108
138
  return {
109
139
  maxWidth: Math.max(MIN_WIDTH, boundsWidth - RESIZE_PAD_X * 2),
110
- maxHeight: Math.max(MIN_HEIGHT, boundsHeight - RESIZE_PAD_TOP - RESIZE_PAD_BOTTOM),
140
+ maxHeight: Math.max(MIN_HEIGHT, boundsHeight - RESIZE_PAD_TOP - RESIZE_PAD_BOTTOM - chromeH),
111
141
  };
112
142
  }
113
143
  return { maxWidth: 720, maxHeight: 420 };
114
144
  }
115
145
 
146
+ function clampDimension(value: number, min: number, max: number): number {
147
+ return Math.max(min, Math.min(max, value));
148
+ }
149
+
150
+ function computeDefaultShellPosition(
151
+ stageWidth: number,
152
+ stageHeight: number,
153
+ shellWidth: number,
154
+ shellHeight: number
155
+ ): { left: number; top: number } {
156
+ const maxLeft = Math.max(RESIZE_PAD_X, stageWidth - shellWidth - RESIZE_PAD_X);
157
+ const maxTop = Math.max(RESIZE_PAD_TOP, stageHeight - shellHeight - RESIZE_PAD_BOTTOM);
158
+ if (defaultStageAlign === 'right-half') {
159
+ const rightHalfCenterX = stageWidth * 0.75;
160
+ const left = clampDimension(rightHalfCenterX - shellWidth / 2, RESIZE_PAD_X, maxLeft);
161
+ const top = clampDimension((stageHeight - shellHeight) / 2, RESIZE_PAD_TOP, maxTop);
162
+ return { left, top };
163
+ }
164
+ const left = clampDimension((stageWidth - shellWidth) / 2, RESIZE_PAD_X, maxLeft);
165
+ const top = clampDimension((stageHeight - shellHeight) / 2, RESIZE_PAD_TOP, maxTop);
166
+ return { left, top };
167
+ }
168
+
169
+ /** Dock / reset preview shell to defaultStageAlign. */
170
+ export function resetPreviewPosition(): void {
171
+ const stageEl = resizeStageEl ?? contentContainerEl;
172
+ if (!stageEl || resizeWidth == null || resizeHeight == null) return;
173
+ if (stageEl.clientWidth < 40 || stageEl.clientHeight < 40) {
174
+ requestAnimationFrame(() => resetPreviewPosition());
175
+ return;
176
+ }
177
+ const shellWidth = resizeWidth;
178
+ const shellHeight = resizeHeight + chromeHeaderHeight;
179
+ const next = computeDefaultShellPosition(
180
+ stageEl.clientWidth,
181
+ stageEl.clientHeight,
182
+ shellWidth,
183
+ shellHeight
184
+ );
185
+ previewShellLeft = next.left;
186
+ previewShellTop = next.top;
187
+ previewPositionReady = true;
188
+ console.log('[WidgetPreview] Reset preview position', {
189
+ align: defaultStageAlign,
190
+ left: previewShellLeft,
191
+ top: previewShellTop,
192
+ });
193
+ }
194
+
195
+ function ensurePreviewPosition(): void {
196
+ if (previewPositionReady) return;
197
+ resetPreviewPosition();
198
+ }
199
+
200
+ function applyDimensionFromInput(dimension: 'width' | 'height', raw: string) {
201
+ const { maxWidth, maxHeight } = getResizeLimits();
202
+ const parsed = Number.parseInt(raw, 10);
203
+ if (!Number.isFinite(parsed)) return;
204
+ if (dimension === 'width') {
205
+ resizeWidth = clampDimension(parsed, MIN_WIDTH, maxWidth);
206
+ widthInputValue = String(Math.round(resizeWidth));
207
+ } else {
208
+ resizeHeight = clampDimension(parsed, MIN_HEIGHT, maxHeight);
209
+ heightInputValue = String(Math.round(resizeHeight));
210
+ }
211
+ if (resizeWidth != null && resizeHeight != null) {
212
+ dispatch('resize', { width: resizeWidth, height: resizeHeight });
213
+ }
214
+ console.log('[WidgetPreview] Dimension input', { dimension, value: parsed });
215
+ }
216
+
217
+ function startPreviewDrag(event: PointerEvent) {
218
+ if (!enableDrag || isResizing) return;
219
+ const target = event.target as HTMLElement | null;
220
+ if (target?.closest('input, button, a, label')) return;
221
+ event.preventDefault();
222
+ event.stopPropagation();
223
+ isDraggingPreview = true;
224
+ const startX = event.clientX;
225
+ const startY = event.clientY;
226
+ const startLeft = previewShellLeft;
227
+ const startTop = previewShellTop;
228
+ const stageEl = resizeStageEl ?? contentContainerEl;
229
+ const shellWidth = resizeWidth ?? 0;
230
+ const shellHeight = (resizeHeight ?? 0) + chromeHeaderHeight;
231
+
232
+ function onPointerMove(e: PointerEvent) {
233
+ if (!stageEl) return;
234
+ const maxLeft = Math.max(RESIZE_PAD_X, stageEl.clientWidth - shellWidth - RESIZE_PAD_X);
235
+ const maxTop = Math.max(RESIZE_PAD_TOP, stageEl.clientHeight - shellHeight - RESIZE_PAD_BOTTOM);
236
+ previewShellLeft = clampDimension(startLeft + (e.clientX - startX), RESIZE_PAD_X, maxLeft);
237
+ previewShellTop = clampDimension(startTop + (e.clientY - startY), RESIZE_PAD_TOP, maxTop);
238
+ }
239
+
240
+ function endDrag() {
241
+ isDraggingPreview = false;
242
+ document.removeEventListener('pointermove', onPointerMove);
243
+ document.removeEventListener('pointerup', endDrag);
244
+ document.removeEventListener('pointercancel', endDrag);
245
+ document.body.style.removeProperty('user-select');
246
+ document.body.style.removeProperty('cursor');
247
+ console.log('[WidgetPreview] Preview drag end', {
248
+ left: previewShellLeft,
249
+ top: previewShellTop,
250
+ });
251
+ }
252
+
253
+ document.body.style.userSelect = 'none';
254
+ document.body.style.cursor = 'move';
255
+ document.addEventListener('pointermove', onPointerMove);
256
+ document.addEventListener('pointerup', endDrag);
257
+ document.addEventListener('pointercancel', endDrag);
258
+ }
259
+
116
260
  /** Firefox/Chrome skip painting a cross-origin iframe that started at opacity 0 or inside a CSS filter until a reflow (opening DevTools). */
117
261
  let iframePaintNudgeGeneration = 0;
118
262
  export function nudgeIframePaint(): void {
@@ -475,22 +619,65 @@
475
619
  event.preventDefault();
476
620
  event.stopPropagation();
477
621
  isResizing = true;
622
+ if (event.altKey) isAltKeyDown = true;
478
623
  const startX = event.clientX;
479
624
  const startY = event.clientY;
480
-
625
+ const startLeft = previewShellLeft;
626
+ const startTop = previewShellTop;
481
627
  const { maxWidth, maxHeight } = getResizeLimits();
482
628
  const startWidth = resizeWidth ?? maxWidth;
483
629
  const startHeight = resizeHeight ?? maxHeight;
630
+ const alwaysConcentric = resizeMode === 'concentric';
484
631
 
485
632
  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));
633
+ if (e.altKey) isAltKeyDown = true;
634
+ const useConcentric = alwaysConcentric || isAltKeyDown || e.altKey;
635
+ const dx = e.clientX - startX;
636
+ const dy = e.clientY - startY;
637
+
638
+ if (useConcentric) {
639
+ const deltaW = signX !== 0 ? dx * signX * 2 : 0;
640
+ const deltaH = signY !== 0 ? dy * signY * 2 : 0;
641
+ const nextW = clampDimension(startWidth + deltaW, MIN_WIDTH, maxWidth);
642
+ const nextH = clampDimension(startHeight + deltaH, MIN_HEIGHT, maxHeight);
643
+ resizeWidth = nextW;
644
+ resizeHeight = nextH;
645
+ previewShellLeft = startLeft + (startWidth - nextW) / 2;
646
+ previewShellTop = startTop + (startHeight - nextH) / 2;
647
+ } else {
648
+ let nextW = startWidth;
649
+ let nextH = startHeight;
650
+ let nextLeft = startLeft;
651
+ let nextTop = startTop;
652
+
653
+ if (signX === -1) {
654
+ nextW = clampDimension(startWidth - dx, MIN_WIDTH, maxWidth);
655
+ nextLeft = startLeft + (startWidth - nextW);
656
+ } else if (signX === 1) {
657
+ nextW = clampDimension(startWidth + dx, MIN_WIDTH, maxWidth);
658
+ }
659
+
660
+ if (signY === -1) {
661
+ nextH = clampDimension(startHeight - dy, MIN_HEIGHT, maxHeight);
662
+ nextTop = startTop + (startHeight - nextH);
663
+ } else if (signY === 1) {
664
+ nextH = clampDimension(startHeight + dy, MIN_HEIGHT, maxHeight);
665
+ }
666
+
667
+ resizeWidth = nextW;
668
+ resizeHeight = nextH;
669
+ previewShellLeft = nextLeft;
670
+ previewShellTop = nextTop;
671
+ }
672
+
673
+ if (resizeWidth != null && resizeHeight != null) {
674
+ dispatch('resize', { width: resizeWidth, height: resizeHeight });
675
+ }
490
676
  }
491
677
 
492
678
  function endResize() {
493
679
  isResizing = false;
680
+ // Keep orange stroke while Alt remains held (composition corner mode).
494
681
  document.removeEventListener('pointermove', onPointerMove);
495
682
  document.removeEventListener('pointerup', endResize);
496
683
  document.removeEventListener('pointercancel', endResize);
@@ -502,7 +689,14 @@
502
689
  }
503
690
 
504
691
  document.body.style.userSelect = 'none';
505
- document.body.style.cursor = 'nwse-resize';
692
+ document.body.style.cursor =
693
+ signX !== 0 && signY !== 0
694
+ ? signX * signY > 0
695
+ ? 'nwse-resize'
696
+ : 'nesw-resize'
697
+ : signX !== 0
698
+ ? 'ew-resize'
699
+ : 'ns-resize';
506
700
  document.addEventListener('pointermove', onPointerMove);
507
701
  document.addEventListener('pointerup', endResize);
508
702
  document.addEventListener('pointercancel', endResize);
@@ -510,6 +704,7 @@
510
704
 
511
705
  $: if (enableResize && contentContainerEl && (resizeWidth != null || resizeHeight != null)) {
512
706
  clampResizeToLimits();
707
+ ensurePreviewPosition();
513
708
  }
514
709
 
515
710
  $: if (autoFitStage && resizeStageEl) {
@@ -523,6 +718,27 @@
523
718
 
524
719
  onMount(() => {
525
720
  window.addEventListener('message', handleMessage);
721
+ const onAltDown = (e: KeyboardEvent) => {
722
+ if (e.key === 'Alt' || e.code === 'AltLeft' || e.code === 'AltRight') {
723
+ isAltKeyDown = true;
724
+ }
725
+ };
726
+ const onAltUp = (e: KeyboardEvent) => {
727
+ if (e.key === 'Alt' || e.code === 'AltLeft' || e.code === 'AltRight') {
728
+ isAltKeyDown = false;
729
+ }
730
+ };
731
+ const onWindowBlur = () => {
732
+ isAltKeyDown = false;
733
+ };
734
+ window.addEventListener('keydown', onAltDown);
735
+ window.addEventListener('keyup', onAltUp);
736
+ window.addEventListener('blur', onWindowBlur);
737
+ return () => {
738
+ window.removeEventListener('keydown', onAltDown);
739
+ window.removeEventListener('keyup', onAltUp);
740
+ window.removeEventListener('blur', onWindowBlur);
741
+ };
526
742
  });
527
743
 
528
744
  onDestroy(() => {
@@ -636,12 +852,66 @@
636
852
  {:else if effectiveSrc || (mode === 'standalone' && isResolvingUrl)}
637
853
  <div
638
854
  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'}"
855
+ 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
856
  >
641
857
  <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'};"
858
+ class="preview-iframe-shell relative flex flex-col {enableResize ? 'preview-iframe-shell-positioned' : ''}"
859
+ class:preview-iframe-shell-symmetrical={showSymmetricalStroke}
860
+ style="{enableResize
861
+ ? `position: absolute; left: ${previewShellLeft}px; top: ${previewShellTop}px; width: ${resizeWidth ? resizeWidth + 'px' : '100%'};`
862
+ : `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
863
  >
864
+ {#if showPreviewChrome}
865
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
866
+ <div
867
+ 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' : ''}"
868
+ style="border-color: {previewStrokeColor};"
869
+ onpointerdown={startPreviewDrag}
870
+ >
871
+ {#if enableDrag}
872
+ <span class="preview-chrome-drag-grip shrink-0 text-gray-400 text-xs leading-none" title="Drag preview" aria-hidden="true">⠿</span>
873
+ {/if}
874
+ <span class="preview-chrome-title text-[11px] font-semibold uppercase tracking-wide text-gray-500 shrink-0">Preview</span>
875
+ {#if showDimensionInputs}
876
+ <div class="preview-chrome-dimensions ml-auto flex items-center gap-1.5">
877
+ <label class="preview-dim-label flex items-center gap-1 text-[10px] font-semibold text-gray-500">
878
+ <span>W</span>
879
+ <input
880
+ type="number"
881
+ min={MIN_WIDTH}
882
+ 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"
883
+ value={widthInputValue}
884
+ oninput={(e) => {
885
+ widthInputValue = e.currentTarget.value;
886
+ applyDimensionFromInput('width', e.currentTarget.value);
887
+ }}
888
+ onpointerdown={(e) => e.stopPropagation()}
889
+ />
890
+ </label>
891
+ <span class="text-[10px] text-gray-400">×</span>
892
+ <label class="preview-dim-label flex items-center gap-1 text-[10px] font-semibold text-gray-500">
893
+ <span>H</span>
894
+ <input
895
+ type="number"
896
+ min={MIN_HEIGHT}
897
+ 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"
898
+ value={heightInputValue}
899
+ oninput={(e) => {
900
+ heightInputValue = e.currentTarget.value;
901
+ applyDimensionFromInput('height', e.currentTarget.value);
902
+ }}
903
+ onpointerdown={(e) => e.stopPropagation()}
904
+ />
905
+ </label>
906
+ </div>
907
+ {/if}
908
+ </div>
909
+ {/if}
910
+
911
+ <div
912
+ class="preview-iframe-wrapper relative"
913
+ style="width: 100%; height: {resizeHeight ? resizeHeight + 'px' : '100%'};"
914
+ >
645
915
  {#if showResizeHandles}
646
916
  <!-- svelte-ignore a11y-no-static-element-interactions -->
647
917
  <div class="preview-resize-handle preview-resize-handle-tl" onpointerdown={(e) => startResize(e, -1, -1)}></div>
@@ -653,7 +923,7 @@
653
923
  <div class="preview-resize-handle preview-resize-handle-br" onpointerdown={(e) => startResize(e, 1, 1)}></div>
654
924
  {/if}
655
925
 
656
- {#if resizeWidth || resizeHeight}
926
+ {#if (resizeWidth || resizeHeight) && !showDimensionInputs}
657
927
  <div
658
928
  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
929
  style="top: calc(100% + 4px);"
@@ -662,7 +932,10 @@
662
932
  </div>
663
933
  {/if}
664
934
 
665
- <div class="preview-iframe-container relative w-full h-full overflow-hidden {compactChrome ? '' : showUrlBar || mode === 'controlled' || enableResize ? 'border border-gray-300' : ''}">
935
+ <div
936
+ class="preview-iframe-container relative w-full h-full overflow-hidden {compactChrome && !enableResize ? '' : showUrlBar || mode === 'controlled' || enableResize ? 'border' : ''}"
937
+ style={enableResize ? `border-color: ${previewStrokeColor}; border-radius: ${showPreviewChrome ? '0 0 6px 6px' : '6px'};` : ''}
938
+ >
666
939
  {#if mode === 'standalone' && iframeError}
667
940
  <div class="widget-preview-error-ct absolute inset-0 flex items-center justify-center bg-white z-10 rounded">
668
941
  <div class="widget-preview-error-content-ct flex flex-col items-center gap-2 text-center px-4">
@@ -756,6 +1029,7 @@
756
1029
  <GenerateLoader overlay title="Generating code…" subtitle="" size="lg" />
757
1030
  {/if}
758
1031
  </div>
1032
+ </div>
759
1033
  </div>
760
1034
  </div>
761
1035
 
@@ -784,10 +1058,10 @@
784
1058
  {/if}
785
1059
  </div>
786
1060
 
787
- {#if isResizing}
1061
+ {#if isResizing || isDraggingPreview}
788
1062
  <div
789
- class="widget-preview-resize-guard-ct fixed inset-0 z-999999 cursor-nwse-resize"
790
- style="background: transparent;"
1063
+ class="widget-preview-resize-guard-ct fixed inset-0 z-999999"
1064
+ style="background: transparent; cursor: {isDraggingPreview ? 'move' : 'nwse-resize'};"
791
1065
  aria-hidden="true"
792
1066
  ></div>
793
1067
  {/if}
@@ -799,38 +1073,35 @@
799
1073
 
800
1074
  .preview-resize-handle {
801
1075
  position: absolute;
802
- width: 16px;
803
- height: 16px;
1076
+ width: 10px;
1077
+ height: 10px;
804
1078
  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;
1079
+ box-sizing: border-box;
1080
+ background: #ffffff;
1081
+ border: 1.5px solid #3b82f6;
813
1082
  border-radius: 1px;
814
1083
  }
815
1084
 
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; }
1085
+ .preview-iframe-shell-symmetrical .preview-resize-handle,
1086
+ .preview-iframe-shell-symmetrical .preview-iframe-container {
1087
+ border-color: #f97316 !important;
1088
+ }
823
1089
 
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; }
1090
+ .preview-chrome-header button:focus,
1091
+ .preview-chrome-header button:focus-visible,
1092
+ .preview-chrome-header input:focus,
1093
+ .preview-chrome-header input:focus-visible {
1094
+ outline: none;
1095
+ box-shadow: none;
1096
+ }
827
1097
 
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; }
1098
+ .preview-resize-handle-tl { top: -5px; left: -5px; cursor: nwse-resize; }
1099
+ .preview-resize-handle-tr { top: -5px; right: -5px; cursor: nesw-resize; }
1100
+ .preview-resize-handle-bl { bottom: -5px; left: -5px; cursor: nesw-resize; }
1101
+ .preview-resize-handle-br { bottom: -5px; right: -5px; cursor: nwse-resize; }
831
1102
 
832
- .preview-resize-handle:hover::before,
833
- .preview-resize-handle:hover::after {
834
- background: #64748b;
1103
+ .preview-resize-handle:hover {
1104
+ background: #eff6ff;
1105
+ border-color: #2563eb;
835
1106
  }
836
1107
  </style>
@@ -43,6 +43,13 @@ 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
+ /**
49
+ * concentric — always resize from center (widget-creator / Canvas).
50
+ * corner — opposite corner fixed; hold Alt for concentric (composition page).
51
+ */ resizeMode?: "concentric" | "corner";
52
+ /** Initial / reset placement inside the stage. */ defaultStageAlign?: "center" | "right-half";
46
53
  /** Observe stage size and fill it (desktop modal) */ autoFitStage?: boolean;
47
54
  /** No padding/border on preview-content-container — matches widget-creator Edit panel */ compactChrome?: boolean;
48
55
  /** Fixed preview stage size; inner iframe resizes within these bounds */ boundsWidth?: number | null;
@@ -54,6 +61,7 @@ declare const WidgetPreview: $$__sveltets_2_IsomorphicComponent<{
54
61
  maxWidth: number;
55
62
  maxHeight: number;
56
63
  };
64
+ resetPreviewPosition?: () => void;
57
65
  nudgeIframePaint?: () => void;
58
66
  fitToContainer?: () => void;
59
67
  sendMessageToPreview?: (message: Record<string, unknown>) => void;
@@ -90,6 +98,7 @@ declare const WidgetPreview: $$__sveltets_2_IsomorphicComponent<{
90
98
  maxWidth: number;
91
99
  maxHeight: number;
92
100
  };
101
+ resetPreviewPosition: () => void;
93
102
  nudgeIframePaint: () => void;
94
103
  fitToContainer: () => void;
95
104
  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.5",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",