@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
|
@@ -78,7 +78,19 @@
|
|
|
78
78
|
import InputControllerRenderer, { type ControllerDescriptor } from '../ic/InputControllerRenderer.svelte';
|
|
79
79
|
import MediaManager from '../../utils/MediaManager';
|
|
80
80
|
import FontManager from '../../utils/FontManager';
|
|
81
|
+
import { conditionHolds } from '../../utils/visibilityCondition';
|
|
82
|
+
import { removeEditorStorageValuesStartingWith } from '../../utils/editorStorage';
|
|
83
|
+
import { reorderableList, moveListItem, type ItemReorderOptions } from '../../utils/itemReorder';
|
|
84
|
+
import {
|
|
85
|
+
lockPointerStyle,
|
|
86
|
+
queryAllInTreeOf,
|
|
87
|
+
queryInTreeOf,
|
|
88
|
+
treeOf,
|
|
89
|
+
type SearchableTree
|
|
90
|
+
} from '../../utils/shadowDom';
|
|
81
91
|
import { config as appConfig } from '../../config';
|
|
92
|
+
import { provideEditorMediaLibrary } from '../../mediaLibraryContext';
|
|
93
|
+
import type { MediaLibraryProvider } from '@widgetic/file-browser';
|
|
82
94
|
|
|
83
95
|
/**
|
|
84
96
|
* STYLES from Widgetic Design System
|
|
@@ -161,6 +173,16 @@
|
|
|
161
173
|
export let initialDesignValues: Record<string, any> = {};
|
|
162
174
|
export let initialContentValues: Record<string, any> = {};
|
|
163
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Where the file controls (images, video, audio, documents) get their files from. Without it they show the
|
|
178
|
+
* Widgetic account's uploads, as they always have (configured with `setFileBrowserUploadConfig`). With a
|
|
179
|
+
* `MediaLibraryProvider` they list, upload, rename and delete through its functions instead, so a partner's own
|
|
180
|
+
* storage (a CMS, an S3 bucket, a DAM) can be used with no Widgetic storage at all.
|
|
181
|
+
*/
|
|
182
|
+
export let mediaLibrary: MediaLibraryProvider | undefined = undefined;
|
|
183
|
+
// The file controls are created from the schema several components down: give them the library through context.
|
|
184
|
+
provideEditorMediaLibrary(() => mediaLibrary);
|
|
185
|
+
|
|
164
186
|
/**
|
|
165
187
|
* CONSTANTS
|
|
166
188
|
*/
|
|
@@ -254,18 +276,9 @@
|
|
|
254
276
|
|
|
255
277
|
// Clear font weight keys immediately when script loads
|
|
256
278
|
if (browser) {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (key && key.includes('font-weight-state-')) {
|
|
261
|
-
keysToRemove.push(key);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if (keysToRemove.length > 0) {
|
|
266
|
-
// console.log('🧹 [PropsEditor] Clearing font weight states on init:', keysToRemove);
|
|
267
|
-
keysToRemove.forEach(key => localStorage.removeItem(key));
|
|
268
|
-
}
|
|
279
|
+
// Only the editor's own keys are looked at: this used to walk the whole localStorage and remove every key with
|
|
280
|
+
// this text in it, and inside a customer's page that reaches keys that are the customer's.
|
|
281
|
+
removeEditorStorageValuesStartingWith('font-weight-state-');
|
|
269
282
|
}
|
|
270
283
|
|
|
271
284
|
/**
|
|
@@ -356,521 +369,71 @@
|
|
|
356
369
|
}
|
|
357
370
|
};
|
|
358
371
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
372
|
+
/**
|
|
373
|
+
* REORDERING CONTENT ITEMS
|
|
374
|
+
* Dragging an item's handle moves the item in the list. The dragging itself is in utils/itemReorder.ts, which is
|
|
375
|
+
* not a library because the editor can be inside a shadow root on a customer's page (see the file for why no
|
|
376
|
+
* library works there). While an item is dragged the stylesheet folds every item to one row so the whole list is
|
|
377
|
+
* within reach (an item that is open keeps its content, it is only hidden), so nothing has to be closed and
|
|
378
|
+
* opened again around a drag.
|
|
379
|
+
*/
|
|
380
|
+
let isReordering = false;
|
|
369
381
|
|
|
370
|
-
|
|
371
|
-
|
|
382
|
+
function handleReorderStart() {
|
|
383
|
+
isReordering = true;
|
|
384
|
+
}
|
|
372
385
|
|
|
386
|
+
/** An item was dropped at another place in the list: `oldIndex` and `newIndex` are its places before and after. */
|
|
387
|
+
function handleItemsReordered({ oldIndex, newIndex }: { oldIndex: number; newIndex: number }) {
|
|
373
388
|
try {
|
|
374
|
-
if (contentSchema
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
const containerElement = document.querySelector('.content-items');
|
|
379
|
-
// console.log('🔄 [PropsEditor] Found content container:', containerElement);
|
|
380
|
-
|
|
381
|
-
if (containerElement instanceof HTMLElement) {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
if (currentDraggable) {
|
|
385
|
-
// console.log('🧹 Cleaning up existing draggable instance');
|
|
386
|
-
currentDraggable.destroy();
|
|
387
|
-
currentDraggable = null;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
const { Sortable, Plugins } = await import('@shopify/draggable');
|
|
391
|
-
const { SortAnimation } = Plugins;
|
|
392
|
-
|
|
393
|
-
let dragStartTime = 0;
|
|
394
|
-
let isDragStarted = false;
|
|
395
|
-
let dragThreshold = 0; // pixels to move before considering it a drag
|
|
396
|
-
let initialMousePosition = { x: 0, y: 0 };
|
|
397
|
-
let grabOffsetY = 0;
|
|
398
|
-
let grabOffsetX = 0;
|
|
399
|
-
|
|
400
|
-
// Add forceCleanupState function
|
|
401
|
-
function forceCleanupState() {
|
|
402
|
-
// console.log('🧹 Performing emergency state cleanup');
|
|
403
|
-
|
|
404
|
-
// Clean up any existing mirror elements first
|
|
405
|
-
const mirrors = document.querySelectorAll('.draggable-mirror');
|
|
406
|
-
mirrors.forEach(mirror => {
|
|
407
|
-
if (mirror.parentNode) {
|
|
408
|
-
mirror.parentNode.removeChild(mirror);
|
|
409
|
-
}
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
// Reset all global state
|
|
413
|
-
isDragging = false;
|
|
414
|
-
isDragStarted = false;
|
|
415
|
-
document.documentElement.classList.remove('draggable--is-dragging');
|
|
416
|
-
|
|
417
|
-
// Remove dragging classes from body and document
|
|
418
|
-
document.body.classList.remove('dragging-active');
|
|
419
|
-
document.documentElement.classList.remove('dragging-active');
|
|
420
|
-
|
|
421
|
-
// Force cleanup all items
|
|
422
|
-
document.querySelectorAll('.content-item').forEach((item: Element) => {
|
|
423
|
-
if (item instanceof HTMLElement) {
|
|
424
|
-
// Remove all possible dragging classes
|
|
425
|
-
item.classList.remove('draggable-source--is-dragging');
|
|
426
|
-
item.classList.remove('draggable--over');
|
|
427
|
-
item.classList.remove('draggable--original');
|
|
428
|
-
item.classList.remove('draggable-source--placed');
|
|
429
|
-
item.classList.remove('draggable-mirror');
|
|
430
|
-
|
|
431
|
-
// Reset all possible styles
|
|
432
|
-
item.style.transform = '';
|
|
433
|
-
item.style.transition = 'none';
|
|
434
|
-
// Ensure inline order values are cleared so visual order matches DOM order
|
|
435
|
-
item.style.order = '';
|
|
436
|
-
void item.offsetHeight; // Force reflow
|
|
437
|
-
item.style.transition = '';
|
|
438
|
-
item.style.position = 'relative';
|
|
439
|
-
item.style.zIndex = '1';
|
|
440
|
-
item.style.opacity = '1';
|
|
441
|
-
item.style.visibility = 'visible';
|
|
442
|
-
item.style.pointerEvents = '';
|
|
443
|
-
item.style.cursor = 'pointer';
|
|
444
|
-
|
|
445
|
-
// Update collapsible state based on our openItems
|
|
446
|
-
const itemId = item.getAttribute('data-id');
|
|
447
|
-
const isOpen = itemId && openItems.has(itemId);
|
|
448
|
-
|
|
449
|
-
// Find the collapsible elements
|
|
450
|
-
const collapsibleRoot = item.querySelector('[data-radix-collapsible-root]');
|
|
451
|
-
const trigger = item.querySelector('[data-radix-collapsible-trigger]');
|
|
452
|
-
const content = item.querySelector('[data-radix-collapsible-content]');
|
|
453
|
-
const chevron = item.querySelector('.chevron');
|
|
454
|
-
|
|
455
|
-
// Update states
|
|
456
|
-
if (collapsibleRoot instanceof HTMLElement) {
|
|
457
|
-
collapsibleRoot.setAttribute('data-state', isOpen ? 'open' : 'closed');
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
if (trigger instanceof HTMLElement) {
|
|
461
|
-
trigger.setAttribute('data-state', isOpen ? 'open' : 'closed');
|
|
462
|
-
trigger.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
if (content instanceof HTMLElement) {
|
|
466
|
-
if (!isOpen) {
|
|
467
|
-
content.style.height = '0';
|
|
468
|
-
content.style.overflow = 'hidden';
|
|
469
|
-
} else {
|
|
470
|
-
content.style.height = 'auto';
|
|
471
|
-
content.style.overflow = 'visible';
|
|
472
|
-
}
|
|
473
|
-
content.setAttribute('data-state', isOpen ? 'open' : 'closed');
|
|
474
|
-
content.style.display = ''; // Always reset display property
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
if (chevron instanceof HTMLElement) {
|
|
478
|
-
chevron.style.transform = isOpen ? 'rotate(180deg)' : 'rotate(0deg)';
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
// Clean container
|
|
484
|
-
const container = document.querySelector('.content-items');
|
|
485
|
-
if (container instanceof HTMLElement) {
|
|
486
|
-
container.classList.remove('content-items--is-dragging');
|
|
487
|
-
container.classList.remove('dragging-active');
|
|
488
|
-
container.classList.remove('draggable-container--over');
|
|
489
|
-
container.style.pointerEvents = '';
|
|
490
|
-
container.style.cursor = '';
|
|
491
|
-
|
|
492
|
-
// Force reflow
|
|
493
|
-
void container.offsetHeight;
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
currentDraggable = new Sortable(containerElement, {
|
|
498
|
-
draggable: '.content-item',
|
|
499
|
-
handle: '.drag-handle',
|
|
500
|
-
delay: 0,
|
|
501
|
-
mirror: {
|
|
502
|
-
constrainDimensions: true,
|
|
503
|
-
appendTo: 'body',
|
|
504
|
-
xAxis: true,
|
|
505
|
-
yAxis: true
|
|
506
|
-
},
|
|
507
|
-
plugins: [SortAnimation],
|
|
508
|
-
sortAnimation: {
|
|
509
|
-
duration: 200,
|
|
510
|
-
easingFunction: 'ease-in-out'
|
|
511
|
-
},
|
|
512
|
-
classes: {
|
|
513
|
-
'body:dragging': 'draggable--is-dragging',
|
|
514
|
-
'container:dragging': 'dragging-active',
|
|
515
|
-
'source:dragging': 'draggable-source--is-dragging',
|
|
516
|
-
'source:placed': 'draggable-source--placed',
|
|
517
|
-
'container:placed': 'draggable-container--placed',
|
|
518
|
-
'draggable:over': 'draggable--over',
|
|
519
|
-
'container:over': 'draggable-container--over',
|
|
520
|
-
'source:original': 'draggable--original',
|
|
521
|
-
'mirror': 'draggable-mirror'
|
|
522
|
-
}
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
// Update event handlers
|
|
528
|
-
currentDraggable.on('drag:start', (event: any) => {
|
|
529
|
-
// console.log('[Draggable] drag:start event');
|
|
530
|
-
isDragging = true; // Set flag immediately to prevent afterUpdate interference
|
|
531
|
-
|
|
532
|
-
const dragHandle = event.originalSource?.querySelector('.drag-handle');
|
|
533
|
-
const target = event.originalEvent?.target;
|
|
534
|
-
|
|
535
|
-
if (!dragHandle?.contains(target)) {
|
|
536
|
-
event.cancel();
|
|
537
|
-
return;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// Store initial mouse position for threshold calculation
|
|
541
|
-
if (event.originalEvent instanceof MouseEvent) {
|
|
542
|
-
initialMousePosition = {
|
|
543
|
-
x: event.originalEvent.clientX,
|
|
544
|
-
y: event.originalEvent.clientY
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
// Store previously open items
|
|
549
|
-
previouslyOpen = new Set(openItems);
|
|
550
|
-
|
|
551
|
-
// Reset open items
|
|
552
|
-
openItems = new Set();
|
|
553
|
-
|
|
554
|
-
// Add a class to body to trigger our CSS rules
|
|
555
|
-
document.body.classList.add('dragging-active');
|
|
556
|
-
|
|
557
|
-
// Force reflow to apply height changes immediately
|
|
558
|
-
document.body.offsetHeight;
|
|
559
|
-
|
|
560
|
-
// Add design system classes to all content items for consistent styling
|
|
561
|
-
document.querySelectorAll('.content-item').forEach(item => {
|
|
562
|
-
item.classList.add('widgetic-colors', 'widgetic-radius');
|
|
563
|
-
});
|
|
564
|
-
|
|
565
|
-
// Force visual update of all dropdown chevrons
|
|
566
|
-
document.querySelectorAll('.content-item .chevron').forEach(chevron => {
|
|
567
|
-
if (chevron instanceof HTMLElement) {
|
|
568
|
-
chevron.style.transform = 'rotate(0deg)';
|
|
569
|
-
}
|
|
570
|
-
});
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
// Add drag:move handler - update mirror position to follow mouse
|
|
574
|
-
currentDraggable.on('drag:move', (event: any) => {
|
|
575
|
-
if (isDragStarted) {
|
|
576
|
-
// Update mirror position to follow mouse cursor, accounting for scroll
|
|
577
|
-
if (event.originalEvent instanceof MouseEvent) {
|
|
578
|
-
requestAnimationFrame(() => {
|
|
579
|
-
const mirror = document.querySelector('.draggable-mirror');
|
|
580
|
-
if (mirror instanceof HTMLElement) {
|
|
581
|
-
// Position mirror accounting for grab offset
|
|
582
|
-
mirror.style.top = `${event.originalEvent.clientY - grabOffsetY}px`;
|
|
583
|
-
mirror.style.left = `${event.originalEvent.clientX - grabOffsetX}px`;
|
|
584
|
-
mirror.style.transform = 'translate3d(0,0,0)';
|
|
585
|
-
}
|
|
586
|
-
});
|
|
587
|
-
}
|
|
588
|
-
return;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
if (event.originalEvent instanceof MouseEvent) {
|
|
592
|
-
const deltaX = Math.abs(event.originalEvent.clientX - initialMousePosition.x);
|
|
593
|
-
const deltaY = Math.abs(event.originalEvent.clientY - initialMousePosition.y);
|
|
594
|
-
|
|
595
|
-
// Only start drag if moved more than threshold
|
|
596
|
-
if (deltaX > dragThreshold || deltaY > dragThreshold) {
|
|
597
|
-
isDragStarted = true;
|
|
598
|
-
// isDragging = true; - This is now set in drag:start
|
|
599
|
-
// console.log('[Draggable] isDragStarted set to true');
|
|
600
|
-
|
|
601
|
-
requestAnimationFrame(() => {
|
|
602
|
-
const mirror = document.querySelector('.draggable-mirror');
|
|
603
|
-
if (mirror instanceof HTMLElement) {
|
|
604
|
-
// First, snap to the correct position to prevent flickering
|
|
605
|
-
mirror.style.top = `${event.originalEvent.clientY - grabOffsetY}px`;
|
|
606
|
-
mirror.style.left = `${event.originalEvent.clientX - grabOffsetX}px`;
|
|
607
|
-
mirror.style.transform = 'translate3d(0,0,0)';
|
|
608
|
-
|
|
609
|
-
// Then apply styling
|
|
610
|
-
mirror.style.height = '56px';
|
|
611
|
-
mirror.style.overflow = 'hidden';
|
|
612
|
-
mirror.style.border = '2px solid hsl(var(--grey2))';
|
|
613
|
-
mirror.style.borderRadius = 'var(--radius-small)';
|
|
614
|
-
mirror.style.backgroundColor = 'white';
|
|
615
|
-
mirror.style.boxShadow = 'none';
|
|
616
|
-
|
|
617
|
-
// Add our styling classes without changing any positioning
|
|
618
|
-
mirror.classList.add('widgetic-colors', 'widgetic-radius');
|
|
619
|
-
|
|
620
|
-
// Remove borders from all children
|
|
621
|
-
Array.from(mirror.querySelectorAll('*')).forEach(el => {
|
|
622
|
-
if (el instanceof HTMLElement) {
|
|
623
|
-
el.style.border = 'none';
|
|
624
|
-
el.style.borderTop = 'none';
|
|
625
|
-
el.style.borderBottom = 'none';
|
|
626
|
-
el.style.borderLeft = 'none';
|
|
627
|
-
el.style.borderRight = 'none';
|
|
628
|
-
}
|
|
629
|
-
});
|
|
630
|
-
|
|
631
|
-
// Ensure the mirror content is in collapsed state
|
|
632
|
-
const collapsibleContent = mirror.querySelector('[data-radix-collapsible-content]');
|
|
633
|
-
if (collapsibleContent instanceof HTMLElement) {
|
|
634
|
-
collapsibleContent.style.height = '0';
|
|
635
|
-
collapsibleContent.style.display = 'none';
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
});
|
|
639
|
-
} else {
|
|
640
|
-
// Cancel the drag if we haven't moved enough
|
|
641
|
-
event.cancel();
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
});
|
|
645
|
-
|
|
646
|
-
currentDraggable.on('drag:stop', (event: any) => {
|
|
647
|
-
// Clean up any mirror elements immediately
|
|
648
|
-
requestAnimationFrame(() => {
|
|
649
|
-
const mirrors = document.querySelectorAll('.draggable-mirror');
|
|
650
|
-
mirrors.forEach(mirror => {
|
|
651
|
-
if (mirror.parentNode) {
|
|
652
|
-
mirror.parentNode.removeChild(mirror);
|
|
653
|
-
}
|
|
654
|
-
});
|
|
655
|
-
});
|
|
656
|
-
|
|
657
|
-
// If we never started dragging, this was just a click
|
|
658
|
-
if (!isDragStarted) {
|
|
659
|
-
event.cancel();
|
|
660
|
-
forceCleanupState();
|
|
661
|
-
return;
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
// Reset drag state
|
|
665
|
-
isDragStarted = false;
|
|
666
|
-
isDragging = false;
|
|
667
|
-
dragStartTime = 0;
|
|
668
|
-
initialMousePosition = { x: 0, y: 0 };
|
|
669
|
-
grabOffsetY = 0;
|
|
670
|
-
grabOffsetX = 0;
|
|
671
|
-
|
|
672
|
-
// Remove the class that forces fixed heights
|
|
673
|
-
document.body.classList.remove('dragging-active');
|
|
674
|
-
|
|
675
|
-
// Reset fixed heights from all items
|
|
676
|
-
document.querySelectorAll('.content-item[data-forced-collapsed="true"]').forEach(item => {
|
|
677
|
-
if (item instanceof HTMLElement) {
|
|
678
|
-
item.style.height = '';
|
|
679
|
-
item.style.minHeight = '';
|
|
680
|
-
item.style.maxHeight = '';
|
|
681
|
-
item.style.overflow = '';
|
|
682
|
-
item.removeAttribute('data-forced-collapsed');
|
|
683
|
-
}
|
|
684
|
-
});
|
|
685
|
-
|
|
686
|
-
// Reset all collapsible content elements
|
|
687
|
-
document.querySelectorAll('[data-radix-collapsible-content]').forEach(content => {
|
|
688
|
-
if (content instanceof HTMLElement) {
|
|
689
|
-
content.style.display = '';
|
|
690
|
-
}
|
|
691
|
-
});
|
|
692
|
-
|
|
693
|
-
// Restore previously open items
|
|
694
|
-
setTimeout(() => {
|
|
695
|
-
openItems = new Set(previouslyOpen);
|
|
696
|
-
|
|
697
|
-
// Force update collapsible elements to match our state
|
|
698
|
-
document.querySelectorAll('[data-radix-collapsible-root]').forEach(root => {
|
|
699
|
-
if (root instanceof HTMLElement) {
|
|
700
|
-
const itemId = root.closest('.content-item')?.getAttribute('data-id');
|
|
701
|
-
if (itemId && openItems.has(itemId)) {
|
|
702
|
-
root.setAttribute('data-state', 'open');
|
|
703
|
-
const trigger = root.querySelector('[data-radix-collapsible-trigger]');
|
|
704
|
-
if (trigger instanceof HTMLElement) {
|
|
705
|
-
trigger.setAttribute('data-state', 'open');
|
|
706
|
-
trigger.setAttribute('aria-expanded', 'true');
|
|
707
|
-
}
|
|
708
|
-
const chevron = root.querySelector('.chevron');
|
|
709
|
-
if (chevron instanceof HTMLElement) {
|
|
710
|
-
chevron.style.transform = 'rotate(180deg)';
|
|
711
|
-
}
|
|
712
|
-
const content = root.querySelector('[data-radix-collapsible-content]');
|
|
713
|
-
if (content instanceof HTMLElement) {
|
|
714
|
-
content.style.height = 'auto';
|
|
715
|
-
content.style.overflow = 'visible';
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
});
|
|
720
|
-
}, 50);
|
|
721
|
-
|
|
722
|
-
// Perform normal cleanup
|
|
723
|
-
forceCleanupState();
|
|
724
|
-
});
|
|
725
|
-
|
|
726
|
-
currentDraggable.on('sortable:sorted', (event: { newIndex: number; oldIndex: number }) => {
|
|
727
|
-
// console.log('[Draggable] sortable:sorted event fired', { oldIndex: event.oldIndex, newIndex: event.newIndex });
|
|
728
|
-
try {
|
|
729
|
-
const { oldIndex, newIndex } = event;
|
|
730
|
-
|
|
731
|
-
// Add robust checks for valid indices
|
|
732
|
-
if (typeof oldIndex !== 'number' || typeof newIndex !== 'number' || oldIndex < 0 || newIndex < 0) {
|
|
733
|
-
console.error('[Drag] Invalid or missing indices from event. Aborting reorder.');
|
|
734
|
-
isDragStarted = false;
|
|
735
|
-
return;
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
// No need to do anything if the item was dropped in the same place
|
|
739
|
-
if (oldIndex === newIndex) {
|
|
740
|
-
// console.log('[Drag] Item dropped in the same position. No change needed.');
|
|
741
|
-
isDragStarted = false;
|
|
742
|
-
return;
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
if (!Array.isArray(contentSchema?.contentItems)) {
|
|
746
|
-
console.warn('[Drag] contentItems is not an array, skipping reorder');
|
|
747
|
-
isDragStarted = false;
|
|
748
|
-
return;
|
|
749
|
-
}
|
|
750
|
-
// console.log('[Drag] Before:', contentSchema.contentItems.map((i: ContentItem) => ({ id: i.id, title: i.title })));
|
|
389
|
+
if (!Array.isArray(contentSchema?.contentItems)) {
|
|
390
|
+
console.warn('[Reorder] contentItems is not an array, skipping reorder');
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
751
393
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
394
|
+
// Robust checks for valid indices
|
|
395
|
+
const itemCount = contentSchema.contentItems.length;
|
|
396
|
+
if (
|
|
397
|
+
!Number.isInteger(oldIndex) || !Number.isInteger(newIndex) ||
|
|
398
|
+
oldIndex < 0 || newIndex < 0 || oldIndex >= itemCount || newIndex >= itemCount
|
|
399
|
+
) {
|
|
400
|
+
console.error('[Reorder] Invalid indices for the dropped item. Aborting reorder.', { oldIndex, newIndex, itemCount });
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
755
403
|
|
|
756
|
-
|
|
757
|
-
if (hasDuplicateIds(items)) {
|
|
758
|
-
console.error('[Drag] Duplicate IDs detected after reordering with indices. Aborting update.', items.map(i => i.id));
|
|
759
|
-
isDragStarted = false;
|
|
760
|
-
return;
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
if (items.length !== contentSchema.contentItems.length) {
|
|
764
|
-
console.error('[Drag] Array length mismatch after reordering with indices. Aborting update.');
|
|
765
|
-
isDragStarted = false;
|
|
766
|
-
return;
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
const renumbered = renumberAutoItemTitles(items);
|
|
770
|
-
contentSchema = { ...contentSchema, contentItems: renumbered };
|
|
771
|
-
// console.log('[Drag] After (reordered with indices):', contentSchema.contentItems.map((i: ContentItem) => ({ id: i.id, title: i.title })));
|
|
772
|
-
dispatch('updated', { contentSchema });
|
|
773
|
-
dispatchContentItemsChanged(contentSchema.contentItems);
|
|
774
|
-
|
|
775
|
-
} catch (err) {
|
|
776
|
-
console.error('[Drag] Error updating order in sortable:sorted:', err);
|
|
777
|
-
}
|
|
778
|
-
isDragStarted = false;
|
|
779
|
-
isDragging = false; // Allow afterUpdate to run again
|
|
780
|
-
});
|
|
404
|
+
const items = moveListItem<ContentItem>(contentSchema.contentItems, oldIndex, newIndex);
|
|
781
405
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
mirror.style.borderRadius = 'var(--radius-small)';
|
|
792
|
-
mirror.style.boxShadow = 'none';
|
|
793
|
-
|
|
794
|
-
// Style the mirror element, but do NOT set top, left, or transform.
|
|
795
|
-
mirror.style.height = '56px';
|
|
796
|
-
mirror.style.overflow = 'hidden';
|
|
797
|
-
mirror.style.position = 'fixed';
|
|
798
|
-
mirror.style.width = `${event.originalSource.offsetWidth}px`;
|
|
799
|
-
mirror.style.backgroundColor = 'white';
|
|
800
|
-
mirror.style.pointerEvents = 'none'; // Ensure it doesn't interfere with mouse events
|
|
801
|
-
mirror.style.zIndex = '1000'; // Ensure it's above other elements
|
|
802
|
-
|
|
803
|
-
// Position mirror at exact mouse cursor location
|
|
804
|
-
const mouseEvent = event.originalEvent;
|
|
805
|
-
if (mouseEvent instanceof MouseEvent) {
|
|
806
|
-
// Position mirror centered on mouse cursor
|
|
807
|
-
const mirrorHeight = 56; // Fixed height we set
|
|
808
|
-
const mirrorWidth = event.originalSource.offsetWidth;
|
|
809
|
-
|
|
810
|
-
// Center the mirror on the mouse cursor
|
|
811
|
-
grabOffsetY = mirrorHeight / 2;
|
|
812
|
-
grabOffsetX = mirrorWidth / 2 + DRAG_SHIFT_X;
|
|
813
|
-
|
|
814
|
-
// Position mirror so mouse is at center
|
|
815
|
-
mirror.style.top = `${mouseEvent.clientY - grabOffsetY}px`;
|
|
816
|
-
mirror.style.left = `${mouseEvent.clientX - grabOffsetX}px`;
|
|
817
|
-
mirror.style.transform = 'translate3d(0,0,0)';
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
// Style text with sage-green CSS variable
|
|
821
|
-
const textElements = mirror.querySelectorAll('h4, .content-title, span');
|
|
822
|
-
textElements.forEach(el => {
|
|
823
|
-
if (el instanceof HTMLElement) {
|
|
824
|
-
el.style.color = 'hsl(var(--sage-green))';
|
|
825
|
-
}
|
|
826
|
-
});
|
|
827
|
-
|
|
828
|
-
// Style only chevrons with sage-green CSS variable
|
|
829
|
-
const chevrons = mirror.querySelectorAll('.chevron, svg');
|
|
830
|
-
chevrons.forEach(el => {
|
|
831
|
-
if (el instanceof SVGElement) {
|
|
832
|
-
el.style.stroke = 'hsl(var(--sage-green))';
|
|
833
|
-
el.style.fill = 'transparent';
|
|
834
|
-
|
|
835
|
-
// Also handle any paths inside SVGs
|
|
836
|
-
const paths = el.querySelectorAll('path');
|
|
837
|
-
paths.forEach(path => {
|
|
838
|
-
path.setAttribute('stroke', 'hsl(var(--sage-green))');
|
|
839
|
-
path.setAttribute('fill', 'transparent');
|
|
840
|
-
});
|
|
841
|
-
|
|
842
|
-
// Check if this item was previously open and rotate the chevron accordingly
|
|
843
|
-
const sourceId = event.originalSource.getAttribute('data-id');
|
|
844
|
-
if (sourceId && previouslyOpen && previouslyOpen.has(sourceId) && el.classList.contains('chevron')) {
|
|
845
|
-
// Apply the same transition effect as the normal open/close operation
|
|
846
|
-
el.style.transition = 'transform 200ms ease-in-out';
|
|
847
|
-
el.style.transform = 'rotate(360deg)';
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
});
|
|
851
|
-
}
|
|
852
|
-
});
|
|
406
|
+
// Safety checks before updating state
|
|
407
|
+
if (hasDuplicateIds(items)) {
|
|
408
|
+
console.error('[Reorder] Duplicate IDs detected after reordering with indices. Aborting update.', items.map(i => i.id));
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
if (items.length !== itemCount) {
|
|
412
|
+
console.error('[Reorder] Array length mismatch after reordering with indices. Aborting update.');
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
853
415
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
416
|
+
const renumbered = renumberAutoItemTitles(items);
|
|
417
|
+
contentSchema = { ...contentSchema, contentItems: renumbered };
|
|
418
|
+
dispatch('updated', { contentSchema });
|
|
419
|
+
dispatchContentItemsChanged(contentSchema.contentItems);
|
|
420
|
+
} catch (err) {
|
|
421
|
+
console.error('[Reorder] Error updating the order after a drop:', err);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
861
424
|
|
|
862
|
-
|
|
425
|
+
/** The drag is over (dropped or cancelled): the list is drawn as usual again. */
|
|
426
|
+
function handleReorderEnd() {
|
|
427
|
+
isReordering = false;
|
|
428
|
+
}
|
|
863
429
|
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
} finally {
|
|
872
|
-
isInitializing = false;
|
|
873
|
-
}
|
|
430
|
+
/** What the list of content items needs to be reorderable; used with `use:reorderableList` on the list. */
|
|
431
|
+
const contentItemsReorderOptions: ItemReorderOptions = {
|
|
432
|
+
itemSelector: '.content-item',
|
|
433
|
+
handleSelector: '.drag-handle',
|
|
434
|
+
onDragStart: handleReorderStart,
|
|
435
|
+
onReorder: handleItemsReordered,
|
|
436
|
+
onDragEnd: handleReorderEnd
|
|
874
437
|
};
|
|
875
438
|
|
|
876
439
|
// Add this near other reactive declarations at the top level
|
|
@@ -910,6 +473,14 @@
|
|
|
910
473
|
}
|
|
911
474
|
}
|
|
912
475
|
|
|
476
|
+
// The tree the editor is drawn in (the shadow root inside a module, the document on a normal page), kept from the
|
|
477
|
+
// moment it is mounted: when the editor is torn down it is no longer in that tree to ask.
|
|
478
|
+
let editorTree: SearchableTree | null = null;
|
|
479
|
+
|
|
480
|
+
onMount(() => {
|
|
481
|
+
editorTree = treeOf(rootElement);
|
|
482
|
+
});
|
|
483
|
+
|
|
913
484
|
onMount(() => {
|
|
914
485
|
// console.log('PropsEditor Mounted!');
|
|
915
486
|
|
|
@@ -956,7 +527,8 @@
|
|
|
956
527
|
if (browser) {
|
|
957
528
|
document.removeEventListener('mousemove', handleDragMove);
|
|
958
529
|
document.removeEventListener('mouseup', handleDragEnd);
|
|
959
|
-
|
|
530
|
+
releasePointerStyle?.();
|
|
531
|
+
releasePointerStyle = null;
|
|
960
532
|
}
|
|
961
533
|
|
|
962
534
|
// Clean up scroll listeners
|
|
@@ -967,9 +539,9 @@
|
|
|
967
539
|
contentScrollViewport.removeEventListener('scroll', (event) => handleScroll(event, 'content'));
|
|
968
540
|
}
|
|
969
541
|
|
|
970
|
-
// Clean up any remaining tooltips
|
|
971
|
-
if (browser) {
|
|
972
|
-
const tooltips =
|
|
542
|
+
// Clean up any remaining tooltips, the editor's own: the page it is on has tooltips of its own that must stay
|
|
543
|
+
if (browser && editorTree) {
|
|
544
|
+
const tooltips = Array.from(editorTree.querySelectorAll('[role="tooltip"]'));
|
|
973
545
|
tooltips.forEach(tooltip => {
|
|
974
546
|
if (tooltip.parentNode) {
|
|
975
547
|
tooltip.parentNode.removeChild(tooltip);
|
|
@@ -1024,12 +596,11 @@
|
|
|
1024
596
|
// Replace ALL hyphens with underscores in the condition string
|
|
1025
597
|
const safeCondition = condition.replace(/-/g, '_');
|
|
1026
598
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
return evalFunction(...Object.values(context));
|
|
599
|
+
// The condition is text from the widget's schema and this editor can run inside a customer's page, so it
|
|
600
|
+
// is read by a small parser and never run as code (it used to go through `new Function`, which ran the
|
|
601
|
+
// widget author's JavaScript with the page's rights and needed 'unsafe-eval'). A condition the parser
|
|
602
|
+
// cannot use keeps the control visible, as a failing one always did, and is reported once in the console.
|
|
603
|
+
return conditionHolds(safeCondition, context);
|
|
1033
604
|
} catch (error) {
|
|
1034
605
|
// console.error('Error evaluating visibility condition:', error);
|
|
1035
606
|
// console.log('Condition:', condition);
|
|
@@ -1200,10 +771,12 @@
|
|
|
1200
771
|
// });
|
|
1201
772
|
}
|
|
1202
773
|
|
|
1203
|
-
// First find and remove all modal-related elements, but preserve
|
|
774
|
+
// First find and remove all modal-related elements, but preserve the item being dragged
|
|
1204
775
|
const cleanup = () => {
|
|
1205
|
-
// Get all possible modal elements with more specific selectors
|
|
1206
|
-
|
|
776
|
+
// Get all possible modal elements with more specific selectors. Only the ones in the editor's own tree (the
|
|
777
|
+
// shadow root inside a module): the customer's page has dialogs and popovers of its own that are none of the
|
|
778
|
+
// editor's business.
|
|
779
|
+
const modalElements = queryAllInTreeOf(rootElement, [
|
|
1207
780
|
'[data-radix-popper-content-wrapper]',
|
|
1208
781
|
'[role="dialog"]',
|
|
1209
782
|
'[cmdk-root]',
|
|
@@ -1228,9 +801,9 @@
|
|
|
1228
801
|
// }))
|
|
1229
802
|
// });
|
|
1230
803
|
|
|
1231
|
-
// Remove each modal immediately, but protect scroll-related and
|
|
804
|
+
// Remove each modal immediately, but protect scroll-related and dragged elements
|
|
1232
805
|
modalElements.forEach(element => {
|
|
1233
|
-
// Don't remove if it's part of the core UI structure or
|
|
806
|
+
// Don't remove if it's part of the core UI structure or being dragged
|
|
1234
807
|
if (
|
|
1235
808
|
element.closest('[role="tabpanel"]') ||
|
|
1236
809
|
element.closest('[data-accordion-content]') ||
|
|
@@ -1242,8 +815,8 @@
|
|
|
1242
815
|
element.classList.contains('scrollbar') ||
|
|
1243
816
|
element.classList.contains('scroll-area') ||
|
|
1244
817
|
element.hasAttribute('data-scrollable') ||
|
|
1245
|
-
element.classList.contains('
|
|
1246
|
-
element.classList.contains('
|
|
818
|
+
element.classList.contains('reorder-mirror') ||
|
|
819
|
+
element.classList.contains('is-reorder-source')
|
|
1247
820
|
) {
|
|
1248
821
|
return;
|
|
1249
822
|
}
|
|
@@ -1270,28 +843,6 @@
|
|
|
1270
843
|
// Reset active controller
|
|
1271
844
|
activeControllerId = null;
|
|
1272
845
|
|
|
1273
|
-
// If switching to content tab and content exists, ensure draggable is initialized
|
|
1274
|
-
if (currentValue === 'content' && contentSchema) {
|
|
1275
|
-
// Wait for tab content to be rendered
|
|
1276
|
-
setTimeout(async () => {
|
|
1277
|
-
// console.log('🎯 Checking draggable state in content tab');
|
|
1278
|
-
|
|
1279
|
-
// Check if draggable needs initialization
|
|
1280
|
-
const containers = document.querySelectorAll('.content-items');
|
|
1281
|
-
const needsInit = !isDraggableInitialized ||
|
|
1282
|
-
!currentDraggable ||
|
|
1283
|
-
containers.length === 0 ||
|
|
1284
|
-
!document.querySelector('.draggable-container');
|
|
1285
|
-
|
|
1286
|
-
if (needsInit) {
|
|
1287
|
-
// console.log('🔄 Reinitializing draggable');
|
|
1288
|
-
await initializeDraggable();
|
|
1289
|
-
} else {
|
|
1290
|
-
// console.log('✅ Draggable already initialized and working');
|
|
1291
|
-
}
|
|
1292
|
-
}, 50);
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
846
|
// Restore open items state for the selected tab
|
|
1296
847
|
openItems = currentValue === 'design' ? designOpenItems : contentOpenItems;
|
|
1297
848
|
const accordionToOpen = currentValue === 'design' ? designOpenAccordion : contentOpenAccordion;
|
|
@@ -1308,7 +859,7 @@
|
|
|
1308
859
|
setTimeout(() => {
|
|
1309
860
|
const itemsToOpen = Array.isArray(accordionToOpen) ? accordionToOpen : (accordionToOpen ? [accordionToOpen] : []);
|
|
1310
861
|
for (const name of itemsToOpen) {
|
|
1311
|
-
const accordion =
|
|
862
|
+
const accordion = queryInTreeOf(rootElement, `[data-accordion-id="${name}"]`);
|
|
1312
863
|
if (accordion) {
|
|
1313
864
|
accordion.setAttribute('data-state', 'open');
|
|
1314
865
|
}
|
|
@@ -1330,7 +881,8 @@
|
|
|
1330
881
|
focusController(controllerToFocus);
|
|
1331
882
|
} else {
|
|
1332
883
|
// Focus first controller
|
|
1333
|
-
const firstController =
|
|
884
|
+
const firstController = queryInTreeOf(
|
|
885
|
+
rootElement,
|
|
1334
886
|
`[data-tab="${currentValue}"] .input-controller:first-child`
|
|
1335
887
|
);
|
|
1336
888
|
if (firstController) {
|
|
@@ -1774,7 +1326,9 @@
|
|
|
1774
1326
|
function focusController(controllerId: string) {
|
|
1775
1327
|
// console.log('Attempting to focus controller:', controllerId);
|
|
1776
1328
|
activeControllerId = controllerId;
|
|
1777
|
-
|
|
1329
|
+
// Looked up in the editor's own tree: in a module that is the shadow root, where `document.getElementById`
|
|
1330
|
+
// finds nothing (and on the customer's page it could find an element of theirs with the same id).
|
|
1331
|
+
const element = treeOf(rootElement)?.getElementById(controllerId);
|
|
1778
1332
|
if (element) {
|
|
1779
1333
|
const componentInstance = element.querySelector(':scope > *');
|
|
1780
1334
|
if (componentInstance && 'focus' in componentInstance) {
|
|
@@ -1840,6 +1394,9 @@
|
|
|
1840
1394
|
// Keydown handler: SHIFT + '+' expands all, SHIFT + '-' collapses all
|
|
1841
1395
|
function handleKeyDown(event: KeyboardEvent) {
|
|
1842
1396
|
// console.log("handleKeyDown triggered: key=", event.key, "shiftKey=", event.shiftKey);
|
|
1397
|
+
// The listener is on the window, so it hears every key typed on the page. Only the ones typed in the editor
|
|
1398
|
+
// count: on a customer's page Shift+Arrow in their own text boxes must not open and close our items.
|
|
1399
|
+
if (rootElement && !event.composedPath().includes(rootElement)) return;
|
|
1843
1400
|
if (event.shiftKey && event.key === 'ArrowDown') {
|
|
1844
1401
|
// console.log("Expanding all content items");
|
|
1845
1402
|
expandAllContentItems();
|
|
@@ -2063,33 +1620,11 @@
|
|
|
2063
1620
|
// console.log('[Add] After (should be at end):', contentSchema.contentItems.map((i: ContentItem) => ({ id: i.id, title: i.title })));
|
|
2064
1621
|
// console.log('[Add] New item added:', newItem);
|
|
2065
1622
|
|
|
2066
|
-
//
|
|
1623
|
+
// The list is drawn from the array, so the new item is at the end of it. Once it is drawn, open it to reveal
|
|
1624
|
+
// its content. (The DOM order no longer needs to be put right by hand here: nothing moves the items in the
|
|
1625
|
+
// DOM behind the list's back since the drag and drop is done by utils/itemReorder.ts.)
|
|
2067
1626
|
setTimeout(() => {
|
|
2068
|
-
|
|
2069
|
-
if (container && Array.isArray(contentSchema?.contentItems)) {
|
|
2070
|
-
const elementMap = new Map<string, HTMLElement>();
|
|
2071
|
-
Array.from(container.querySelectorAll('.content-item')).forEach((el: Element) => {
|
|
2072
|
-
const id = (el as HTMLElement).dataset.id;
|
|
2073
|
-
if (id) elementMap.set(id, el as HTMLElement);
|
|
2074
|
-
});
|
|
2075
|
-
|
|
2076
|
-
const desiredOrderIds = contentSchema.contentItems.map((item: ContentItem) => item.id);
|
|
2077
|
-
|
|
2078
|
-
// Surgical re-ordering to minimize DOM moves and flicker
|
|
2079
|
-
for (let i = 0; i < desiredOrderIds.length; i++) {
|
|
2080
|
-
const desiredId = desiredOrderIds[i];
|
|
2081
|
-
const currentDomNode = container.children[i] as HTMLElement | undefined;
|
|
2082
|
-
|
|
2083
|
-
// Only move the element if it's not already in the correct position
|
|
2084
|
-
if (currentDomNode?.dataset.id !== desiredId) {
|
|
2085
|
-
const elementToMove = elementMap.get(desiredId);
|
|
2086
|
-
if (elementToMove) {
|
|
2087
|
-
container.insertBefore(elementToMove, currentDomNode || null);
|
|
2088
|
-
}
|
|
2089
|
-
}
|
|
2090
|
-
}
|
|
2091
|
-
|
|
2092
|
-
// Now that the item is positioned, open it to reveal content
|
|
1627
|
+
if (Array.isArray(contentSchema?.contentItems)) {
|
|
2093
1628
|
openItems = new Set([...openItems, uniqueId]);
|
|
2094
1629
|
}
|
|
2095
1630
|
}, 0);
|
|
@@ -2235,10 +1770,6 @@
|
|
|
2235
1770
|
const targetIndex = Math.max(0, deletedIndex - 1);
|
|
2236
1771
|
dispatch('goToItem', { index: targetIndex });
|
|
2237
1772
|
}
|
|
2238
|
-
setTimeout(() => {
|
|
2239
|
-
// console.log('🔄 Reinitializing draggable after deletion');
|
|
2240
|
-
initializeDraggable();
|
|
2241
|
-
}, 0);
|
|
2242
1773
|
}
|
|
2243
1774
|
} catch (error) {
|
|
2244
1775
|
console.error('❌ Error deleting content item:', error);
|
|
@@ -2264,8 +1795,9 @@
|
|
|
2264
1795
|
}
|
|
2265
1796
|
);
|
|
2266
1797
|
|
|
2267
|
-
// Observe content items
|
|
2268
|
-
|
|
1798
|
+
// Observe content items: the ones in the editor's own list. `.content-item` is a name any page may use, and
|
|
1799
|
+
// an element of the page's that is observed here would be hidden whenever it scrolls out of view.
|
|
1800
|
+
contentItemsContainer?.querySelectorAll('.content-item').forEach(item => {
|
|
2269
1801
|
observer.observe(item);
|
|
2270
1802
|
});
|
|
2271
1803
|
|
|
@@ -2274,13 +1806,14 @@
|
|
|
2274
1806
|
|
|
2275
1807
|
// Keep CSS --index in sync with DOM position **after** Svelte has rendered updates
|
|
2276
1808
|
afterUpdate(() => {
|
|
2277
|
-
if (isDragging) return;
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
1809
|
+
if (isDragging || isReordering) return;
|
|
1810
|
+
// The items of the editor's own list, in the order they are drawn (a lookup by data-id on the whole document
|
|
1811
|
+
// would find nothing inside a shadow root, and could find an element of the customer's page).
|
|
1812
|
+
const itemElements = Array.from(contentItemsContainer?.children ?? []);
|
|
1813
|
+
if (itemElements.length === 0) return;
|
|
1814
|
+
|
|
1815
|
+
itemElements.forEach((el: Element, index: number) => {
|
|
1816
|
+
if (el instanceof HTMLElement && el.classList.contains('content-item')) {
|
|
2284
1817
|
el.style.setProperty('--index', index.toString());
|
|
2285
1818
|
}
|
|
2286
1819
|
});
|
|
@@ -2305,11 +1838,8 @@
|
|
|
2305
1838
|
currentTab = 'content';
|
|
2306
1839
|
|
|
2307
1840
|
// Wait for DOM to be ready
|
|
2308
|
-
setTimeout(
|
|
1841
|
+
setTimeout(() => {
|
|
2309
1842
|
try {
|
|
2310
|
-
// console.log('🔄 Initializing draggable for content tab');
|
|
2311
|
-
await initializeDraggable();
|
|
2312
|
-
|
|
2313
1843
|
// Set initial states
|
|
2314
1844
|
isInitialLoad = false;
|
|
2315
1845
|
isFirstLoad = false;
|
|
@@ -2392,6 +1922,8 @@
|
|
|
2392
1922
|
// ─── BUILT-IN DRAG ───────────────────────────────────────────
|
|
2393
1923
|
let rootElement: HTMLElement;
|
|
2394
1924
|
let dragState = { active: false, startX: 0, startY: 0, initialLeft: 0, initialTop: 0 };
|
|
1925
|
+
// Puts the pointer back as it was once the panel is no longer dragged (see lockPointerStyle).
|
|
1926
|
+
let releasePointerStyle: (() => void) | null = null;
|
|
2395
1927
|
|
|
2396
1928
|
function handleDragStart(e: MouseEvent) {
|
|
2397
1929
|
const target = positionTarget || rootElement;
|
|
@@ -2423,7 +1955,10 @@
|
|
|
2423
1955
|
target.style.zIndex = '50';
|
|
2424
1956
|
document.addEventListener('mousemove', handleDragMove);
|
|
2425
1957
|
document.addEventListener('mouseup', handleDragEnd);
|
|
2426
|
-
|
|
1958
|
+
// The cursor is shown on the host of the shadow root the editor is in (on <body> only when it is on a normal page):
|
|
1959
|
+
// the <body> of a customer's page is not the editor's to change.
|
|
1960
|
+
releasePointerStyle?.();
|
|
1961
|
+
releasePointerStyle = lockPointerStyle(rootElement, 'move', { blockTextSelection: false });
|
|
2427
1962
|
}
|
|
2428
1963
|
|
|
2429
1964
|
function handleDragMove(e: MouseEvent) {
|
|
@@ -2439,7 +1974,8 @@
|
|
|
2439
1974
|
dragState.active = false;
|
|
2440
1975
|
document.removeEventListener('mousemove', handleDragMove);
|
|
2441
1976
|
document.removeEventListener('mouseup', handleDragEnd);
|
|
2442
|
-
|
|
1977
|
+
releasePointerStyle?.();
|
|
1978
|
+
releasePointerStyle = null;
|
|
2443
1979
|
dispatch('moveEnd', getPosition());
|
|
2444
1980
|
}
|
|
2445
1981
|
|
|
@@ -2481,17 +2017,6 @@
|
|
|
2481
2017
|
return ids.length !== new Set(ids).size;
|
|
2482
2018
|
}
|
|
2483
2019
|
|
|
2484
|
-
// Helper: log DOM visual order for debugging
|
|
2485
|
-
function logOrder(label: string) {
|
|
2486
|
-
const titles = Array.from(document.querySelectorAll('.content-item .content-title')).map(el => el.textContent?.trim());
|
|
2487
|
-
// console.log(`[Order-check] ${label}:`, titles);
|
|
2488
|
-
const transforms = Array.from(document.querySelectorAll('.content-item')).map(el => ({ id: (el as HTMLElement).dataset.id, transform: (el as HTMLElement).style.transform }));
|
|
2489
|
-
console.table(transforms);
|
|
2490
|
-
}
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
2020
|
</script>
|
|
2496
2021
|
|
|
2497
2022
|
<div
|
|
@@ -2576,9 +2101,6 @@
|
|
|
2576
2101
|
</Tabs.Trigger>
|
|
2577
2102
|
{#if contentSchema}
|
|
2578
2103
|
<Tabs.Trigger
|
|
2579
|
-
onclick={() => {
|
|
2580
|
-
initializeDraggable();
|
|
2581
|
-
}}
|
|
2582
2104
|
value="content"
|
|
2583
2105
|
class="flex-1 h-full rounded-[11px] transition-colors duration-250 text-grey5 hover:text-grey7 hover:underline data-[state=active]:bg-white data-[state=active]:text-grey7 data-[state=active]:hover:text-grey7"
|
|
2584
2106
|
>
|
|
@@ -2800,7 +2322,7 @@
|
|
|
2800
2322
|
<!-- Content Items Container -->
|
|
2801
2323
|
<div class="content-items-wrapper mt-0" style="position: relative; z-index: 1;">
|
|
2802
2324
|
<!-- Sortable Container -->
|
|
2803
|
-
<div class="content-items flex flex-col gap-4" bind:this={contentItemsContainer} style="position: relative; z-index: 1;">
|
|
2325
|
+
<div class="content-items flex flex-col gap-4" bind:this={contentItemsContainer} use:reorderableList={contentItemsReorderOptions} style="position: relative; z-index: 1;">
|
|
2804
2326
|
{@html (() => {
|
|
2805
2327
|
const items = contentSchema?.contentItems;
|
|
2806
2328
|
if (!Array.isArray(items)) return '';
|
|
@@ -3134,24 +2656,6 @@
|
|
|
3134
2656
|
justify-content: center !important;
|
|
3135
2657
|
}
|
|
3136
2658
|
|
|
3137
|
-
:global(.draggable-mirror) {
|
|
3138
|
-
opacity: 1 !important;
|
|
3139
|
-
transform-origin: 50% 50%;
|
|
3140
|
-
z-index: 100;
|
|
3141
|
-
pointer-events: none;
|
|
3142
|
-
background-color: white !important;
|
|
3143
|
-
border: 2px solid hsl(var(--grey2)) !important;
|
|
3144
|
-
border-radius: var(--radius-small) !important;
|
|
3145
|
-
}
|
|
3146
|
-
|
|
3147
|
-
:global(.draggable-source--is-dragging) {
|
|
3148
|
-
opacity: 0.3 !important;
|
|
3149
|
-
}
|
|
3150
|
-
|
|
3151
|
-
:global(html.draggable--is-dragging) {
|
|
3152
|
-
cursor: move !important;
|
|
3153
|
-
}
|
|
3154
|
-
|
|
3155
2659
|
:global([data-scrollable="false"] .scrollbar) {
|
|
3156
2660
|
display: none;
|
|
3157
2661
|
}
|
|
@@ -3266,11 +2770,6 @@
|
|
|
3266
2770
|
z-index: 99999 !important; /* Force highest z-index */
|
|
3267
2771
|
}
|
|
3268
2772
|
|
|
3269
|
-
/* Update existing styles to ensure proper stacking */
|
|
3270
|
-
:global(.draggable-mirror) {
|
|
3271
|
-
z-index: 100;
|
|
3272
|
-
}
|
|
3273
|
-
|
|
3274
2773
|
:global(.popover-content) {
|
|
3275
2774
|
z-index: 50;
|
|
3276
2775
|
}
|
|
@@ -3863,7 +3362,6 @@
|
|
|
3863
3362
|
text-decoration: underline;
|
|
3864
3363
|
}
|
|
3865
3364
|
|
|
3866
|
-
/* Add Draggable Sort Animation styles */
|
|
3867
3365
|
:global(.content-item) {
|
|
3868
3366
|
position: relative;
|
|
3869
3367
|
z-index: 1;
|
|
@@ -3871,95 +3369,77 @@
|
|
|
3871
3369
|
transform: translate3d(0, 0, 0);
|
|
3872
3370
|
}
|
|
3873
3371
|
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
:global(.draggable-source--is-dragging .content-title) {
|
|
3883
|
-
text-decoration: none !important;
|
|
3884
|
-
}
|
|
3885
|
-
|
|
3886
|
-
:global(.draggable-source--placed) {
|
|
3887
|
-
visibility: visible !important;
|
|
3888
|
-
transition: none !important;
|
|
3889
|
-
opacity: 1 !important;
|
|
3890
|
-
}
|
|
3891
|
-
|
|
3892
|
-
:global(.content-items--is-dragging .content-item:not(.draggable-source--is-dragging)) {
|
|
3893
|
-
transition: transform 200ms ease-in-out;
|
|
3372
|
+
/*
|
|
3373
|
+
* Reordering the items by dragging a handle (utils/itemReorder.ts). While an item is dragged, every item is
|
|
3374
|
+
* folded to one row at once (the items that were open keep their content, it is only hidden), so the whole
|
|
3375
|
+
* list is within reach and the places to drop do not move under the pointer.
|
|
3376
|
+
*/
|
|
3377
|
+
:global(.content-items.is-reordering) {
|
|
3378
|
+
user-select: none;
|
|
3379
|
+
-webkit-user-select: none;
|
|
3894
3380
|
}
|
|
3895
3381
|
|
|
3896
|
-
:
|
|
3897
|
-
|
|
3382
|
+
/* The card is what is folded, not the item: the line that shows the drop place sits outside the item's box */
|
|
3383
|
+
:global(.content-items.is-reordering .content-item-card) {
|
|
3384
|
+
height: 56px !important;
|
|
3385
|
+
min-height: 56px !important;
|
|
3386
|
+
max-height: 56px !important;
|
|
3387
|
+
overflow: hidden !important;
|
|
3898
3388
|
}
|
|
3899
3389
|
|
|
3900
|
-
:global(.content-
|
|
3901
|
-
|
|
3390
|
+
:global(.content-items.is-reordering [data-collapsible-content]) {
|
|
3391
|
+
display: none !important;
|
|
3902
3392
|
}
|
|
3903
3393
|
|
|
3904
|
-
|
|
3394
|
+
/* The item being dragged stays in place, faded; the floating copy is what follows the pointer */
|
|
3395
|
+
:global(.content-item.is-reorder-source) {
|
|
3905
3396
|
opacity: 0.3;
|
|
3906
3397
|
}
|
|
3907
3398
|
|
|
3908
|
-
:
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
color: hsl(var(--
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
:global(.draggable--original) {
|
|
3921
|
-
display: none;
|
|
3399
|
+
/* A line in the gap where the dragged item would be dropped: in front of an item, or behind the last one */
|
|
3400
|
+
:global(.content-item.is-reorder-drop-before)::before,
|
|
3401
|
+
:global(.content-item.is-reorder-drop-after)::after {
|
|
3402
|
+
content: '';
|
|
3403
|
+
position: absolute;
|
|
3404
|
+
left: 0;
|
|
3405
|
+
right: 0;
|
|
3406
|
+
height: 3px;
|
|
3407
|
+
border-radius: 2px;
|
|
3408
|
+
background-color: hsl(var(--sage-green));
|
|
3409
|
+
pointer-events: none;
|
|
3922
3410
|
}
|
|
3923
3411
|
|
|
3924
|
-
:global(.content-
|
|
3925
|
-
|
|
3412
|
+
:global(.content-item.is-reorder-drop-before)::before {
|
|
3413
|
+
top: -9px;
|
|
3926
3414
|
}
|
|
3927
3415
|
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
background-color: transparent;
|
|
3416
|
+
:global(.content-item.is-reorder-drop-after)::after {
|
|
3417
|
+
bottom: -9px;
|
|
3931
3418
|
}
|
|
3932
3419
|
|
|
3933
|
-
|
|
3934
|
-
|
|
3420
|
+
/* The floating copy of the dragged item: one row, with the editor's green outline */
|
|
3421
|
+
:global(.reorder-mirror) {
|
|
3422
|
+
box-sizing: border-box;
|
|
3423
|
+
border: 2px solid hsl(var(--sage-green));
|
|
3424
|
+
border-radius: var(--radius-small);
|
|
3425
|
+
background-color: white;
|
|
3426
|
+
box-shadow: none;
|
|
3427
|
+
will-change: transform;
|
|
3935
3428
|
}
|
|
3936
3429
|
|
|
3937
|
-
:global(.
|
|
3430
|
+
:global(.reorder-mirror .content-title) {
|
|
3431
|
+
color: hsl(var(--sage-green));
|
|
3938
3432
|
text-decoration: none !important;
|
|
3939
3433
|
}
|
|
3940
3434
|
|
|
3941
|
-
:global(.
|
|
3942
|
-
|
|
3435
|
+
:global(.reorder-mirror .chevron),
|
|
3436
|
+
:global(.reorder-mirror .drag-handle svg) {
|
|
3437
|
+
color: hsl(var(--sage-green));
|
|
3943
3438
|
}
|
|
3944
3439
|
|
|
3945
|
-
:
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
:global(.content-item.draggable--original) {
|
|
3950
|
-
opacity: 0;
|
|
3951
|
-
}
|
|
3952
|
-
|
|
3953
|
-
:global(.content-item.draggable-source--is-dragging) {
|
|
3954
|
-
opacity: 0.3;
|
|
3955
|
-
}
|
|
3956
|
-
|
|
3957
|
-
:global(.content-item.draggable-source--is-dragging .content-title) {
|
|
3958
|
-
text-decoration: none !important;
|
|
3959
|
-
}
|
|
3960
|
-
|
|
3961
|
-
:global(.content-item.draggable-mirror .content-title) {
|
|
3962
|
-
text-decoration: none !important;
|
|
3440
|
+
/* The card inside the copy has its own border: the outline above is the only one */
|
|
3441
|
+
:global(.reorder-mirror .content-item-card) {
|
|
3442
|
+
border: none;
|
|
3963
3443
|
}
|
|
3964
3444
|
|
|
3965
3445
|
/* Update drag handle hover area and content title behavior */
|
|
@@ -3970,6 +3450,8 @@
|
|
|
3970
3450
|
justify-content: center !important;
|
|
3971
3451
|
height: 100% !important;
|
|
3972
3452
|
cursor: move !important;
|
|
3453
|
+
/* Touching the handle drags the item; it must not scroll the page */
|
|
3454
|
+
touch-action: none;
|
|
3973
3455
|
}
|
|
3974
3456
|
|
|
3975
3457
|
/* Keep the icon size consistent */
|
|
@@ -3998,37 +3480,12 @@
|
|
|
3998
3480
|
padding-left: 0 !important;
|
|
3999
3481
|
}
|
|
4000
3482
|
|
|
4001
|
-
:global(.draggable-mirror .drag-handle svg) {
|
|
4002
|
-
color: hsl(var(--grey3)) !important;
|
|
4003
|
-
}
|
|
4004
|
-
|
|
4005
|
-
:global(.draggable--original) {
|
|
4006
|
-
display: none;
|
|
4007
|
-
}
|
|
4008
|
-
|
|
4009
|
-
:global(.content-items--is-dragging) {
|
|
4010
|
-
cursor: grabbing;
|
|
4011
|
-
}
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
3483
|
:global(.content-item) {
|
|
4015
3484
|
contain: layout style;
|
|
4016
3485
|
will-change: transform;
|
|
4017
3486
|
transform: translate3d(0, 0, 0);
|
|
4018
3487
|
}
|
|
4019
3488
|
|
|
4020
|
-
:global(.draggable-mirror) {
|
|
4021
|
-
contain: layout style;
|
|
4022
|
-
will-change: transform;
|
|
4023
|
-
transform: translate3d(0, 0, 0);
|
|
4024
|
-
}
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
:global(.content-items--is-dragging .content-item:not(.draggable-source--is-dragging)) {
|
|
4028
|
-
transition: transform 200ms cubic-bezier(0.2, 0, 0.2, 1);
|
|
4029
|
-
will-change: transform;
|
|
4030
|
-
}
|
|
4031
|
-
|
|
4032
3489
|
/* Animation classes */
|
|
4033
3490
|
:global(.design-tab-content[data-state="active"]) {
|
|
4034
3491
|
animation: none;
|
|
@@ -4094,8 +3551,6 @@
|
|
|
4094
3551
|
}
|
|
4095
3552
|
}
|
|
4096
3553
|
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
3554
|
@keyframes slideUp {
|
|
4100
3555
|
from {
|
|
4101
3556
|
opacity: 0;
|
|
@@ -4147,46 +3602,6 @@
|
|
|
4147
3602
|
}
|
|
4148
3603
|
}
|
|
4149
3604
|
|
|
4150
|
-
/* Draggable mirror styles - styling only, no positioning */
|
|
4151
|
-
:global(.draggable-mirror) {
|
|
4152
|
-
opacity: 1 !important;
|
|
4153
|
-
background-color: white !important;
|
|
4154
|
-
border: 2px solid hsl(var(--grey2)) !important;
|
|
4155
|
-
border-radius: var(--radius-small) !important;
|
|
4156
|
-
box-shadow: none !important;
|
|
4157
|
-
}
|
|
4158
|
-
|
|
4159
|
-
/* Style for item being hovered over (potential drop target) */
|
|
4160
|
-
:global(.content-items--is-dragging .content-item.draggable--over) {
|
|
4161
|
-
transform: scale(1.015) !important;
|
|
4162
|
-
border: 2px solid hsl(var(--grey2)) !important;
|
|
4163
|
-
border-radius: var(--radius-small) !important;
|
|
4164
|
-
background-color: hsl(var(--grey1)) !important;
|
|
4165
|
-
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05) !important;
|
|
4166
|
-
z-index: 10 !important;
|
|
4167
|
-
}
|
|
4168
|
-
|
|
4169
|
-
/* Remove all borders and shadows from draggable mirror */
|
|
4170
|
-
:global(.draggable-mirror) {
|
|
4171
|
-
border: 2px solid hsl(var(--grey2)) !important;
|
|
4172
|
-
border-radius: var(--radius-small) !important;
|
|
4173
|
-
box-shadow: none !important;
|
|
4174
|
-
}
|
|
4175
|
-
|
|
4176
|
-
/* Remove borders from all children inside the mirror */
|
|
4177
|
-
:global(.draggable-mirror *) {
|
|
4178
|
-
border: none !important;
|
|
4179
|
-
border-top: none !important;
|
|
4180
|
-
border-bottom: none !important;
|
|
4181
|
-
border-left: none !important;
|
|
4182
|
-
border-right: none !important;
|
|
4183
|
-
}
|
|
4184
|
-
|
|
4185
|
-
/* Make the original item completely transparent during dragging */
|
|
4186
|
-
:global(.draggable-source--is-dragging) {
|
|
4187
|
-
opacity: 0 !important;
|
|
4188
|
-
}
|
|
4189
|
-
|
|
4190
3605
|
:global(body.dragging-preserve-borders .form-input),
|
|
4191
3606
|
:global(body.dragging-preserve-borders [id^="number-input"]),
|
|
4192
3607
|
:global(body.dragging-preserve-borders .number-input-button),
|
|
@@ -4197,20 +3612,6 @@
|
|
|
4197
3612
|
border: 1px solid hsl(var(--grey2)) !important;
|
|
4198
3613
|
}
|
|
4199
3614
|
|
|
4200
|
-
/* Add a new rule to force fixed heights during dragging */
|
|
4201
|
-
:global(.dragging-active .content-item) {
|
|
4202
|
-
height: 56px !important;
|
|
4203
|
-
min-height: 56px !important;
|
|
4204
|
-
max-height: 56px !important;
|
|
4205
|
-
overflow: hidden !important;
|
|
4206
|
-
}
|
|
4207
|
-
|
|
4208
|
-
:global(.dragging-active [data-radix-collapsible-content]) {
|
|
4209
|
-
height: 0 !important;
|
|
4210
|
-
display: none !important;
|
|
4211
|
-
overflow: hidden !important;
|
|
4212
|
-
}
|
|
4213
|
-
|
|
4214
3615
|
/* Add explicit transition for content tab chevrons */
|
|
4215
3616
|
:global(.content-item-card .chevron),
|
|
4216
3617
|
:global(.generic-content-card .chevron) {
|