@dotcms/uve 1.5.1-next.2011 → 1.5.1-next.2023
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/internal.cjs.js +1 -1
- package/internal.esm.js +1 -1
- package/package.json +1 -1
- package/public.cjs.js +277 -188
- package/public.esm.js +277 -188
- package/src/internal/events.d.ts +36 -8
- package/src/internal.d.ts +0 -1
- package/src/lib/dom/dom.utils.d.ts +16 -0
- package/src/script/utils.d.ts +0 -28
- package/src/lib/dom/document-height-observer.d.ts +0 -18
package/internal.cjs.js
CHANGED
|
@@ -398,7 +398,7 @@ exports.getDotContainerAttributes = _public.getDotContainerAttributes;
|
|
|
398
398
|
exports.getDotContentletAttributes = _public.getDotContentletAttributes;
|
|
399
399
|
exports.getUVEState = _public.getUVEState;
|
|
400
400
|
exports.isValidBlocks = _public.isValidBlocks;
|
|
401
|
-
exports.
|
|
401
|
+
exports.readContentletDataset = _public.readContentletDataset;
|
|
402
402
|
exports.setBounds = _public.setBounds;
|
|
403
403
|
exports.__BASE_TINYMCE_CONFIG_WITH_NO_DEFAULT__ = __BASE_TINYMCE_CONFIG_WITH_NO_DEFAULT__;
|
|
404
404
|
exports.__DEFAULT_TINYMCE_CONFIG__ = __DEFAULT_TINYMCE_CONFIG__;
|
package/internal.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { g as getUVEState, s as sendMessageToUVE } from './public.esm.js';
|
|
2
|
-
export { C as CUSTOM_NO_COMPONENT, D as DEVELOPMENT_MODE, f as DOT_SECTION_ID_PREFIX, E as EMPTY_CONTAINER_STYLE_ANGULAR, h as EMPTY_CONTAINER_STYLE_REACT, j as END_CLASS, P as PRODUCTION_MODE, S as START_CLASS, T as TEMP_EMPTY_CONTENTLET, k as TEMP_EMPTY_CONTENTLET_TYPE, _ as __UVE_EVENTS__, l as __UVE_EVENT_ERROR_FALLBACK__, m as combineClasses, n as computeScrollIsInBottom, a as createUVESubscription, o as findDotCMSElement, p as findDotCMSVTLData, q as getClosestDotCMSContainerData, t as getColumnPositionClasses, v as getContainersData, w as getContentletsInContainer, x as getDotCMSContainerData, y as getDotCMSContentletsBound, z as getDotCMSPageBounds, A as getDotContainerAttributes, B as getDotContentletAttributes, F as isValidBlocks, G as
|
|
2
|
+
export { C as CUSTOM_NO_COMPONENT, D as DEVELOPMENT_MODE, f as DOT_SECTION_ID_PREFIX, E as EMPTY_CONTAINER_STYLE_ANGULAR, h as EMPTY_CONTAINER_STYLE_REACT, j as END_CLASS, P as PRODUCTION_MODE, S as START_CLASS, T as TEMP_EMPTY_CONTENTLET, k as TEMP_EMPTY_CONTENTLET_TYPE, _ as __UVE_EVENTS__, l as __UVE_EVENT_ERROR_FALLBACK__, m as combineClasses, n as computeScrollIsInBottom, a as createUVESubscription, o as findDotCMSElement, p as findDotCMSVTLData, q as getClosestDotCMSContainerData, t as getColumnPositionClasses, v as getContainersData, w as getContentletsInContainer, x as getDotCMSContainerData, y as getDotCMSContentletsBound, z as getDotCMSPageBounds, A as getDotContainerAttributes, B as getDotContentletAttributes, F as isValidBlocks, G as readContentletDataset, H as setBounds } from './public.esm.js';
|
|
3
3
|
import { UVE_MODE, DotCMSUVEAction } from '@dotcms/types';
|
|
4
4
|
import '@dotcms/types/internal';
|
|
5
5
|
|
package/package.json
CHANGED
package/public.cjs.js
CHANGED
|
@@ -356,6 +356,27 @@ function getDotContainerAttributes({
|
|
|
356
356
|
'data-dot-uuid': uuid
|
|
357
357
|
};
|
|
358
358
|
}
|
|
359
|
+
/**
|
|
360
|
+
* Read a contentlet's dataset attributes off a DOM element and return a
|
|
361
|
+
* normalized contentlet object. Mirrors the shape consumed by the editor's
|
|
362
|
+
* SET_BOUNDS and CONTENTLET_CLICKED events. Optionally parses the
|
|
363
|
+
* `dotStyleProperties` JSON when present.
|
|
364
|
+
*/
|
|
365
|
+
function readContentletDataset(element) {
|
|
366
|
+
const dataset = element.dataset ?? {};
|
|
367
|
+
return {
|
|
368
|
+
identifier: dataset['dotIdentifier'],
|
|
369
|
+
title: dataset['dotTitle'],
|
|
370
|
+
inode: dataset['dotInode'],
|
|
371
|
+
contentType: dataset['dotType'],
|
|
372
|
+
baseType: dataset['dotBasetype'],
|
|
373
|
+
widgetTitle: dataset['dotWidgetTitle'],
|
|
374
|
+
onNumberOfPages: dataset['dotOnNumberOfPages'],
|
|
375
|
+
...(dataset['dotStyleProperties'] && {
|
|
376
|
+
dotStyleProperties: JSON.parse(dataset['dotStyleProperties'])
|
|
377
|
+
})
|
|
378
|
+
};
|
|
379
|
+
}
|
|
359
380
|
|
|
360
381
|
/**
|
|
361
382
|
* Subscribes to content changes in the UVE editor
|
|
@@ -403,29 +424,124 @@ function onPageReload(callback) {
|
|
|
403
424
|
event: types.UVEEventType.PAGE_RELOAD
|
|
404
425
|
};
|
|
405
426
|
}
|
|
427
|
+
const AUTO_BOUNDS_DEBOUNCE_MS = 100;
|
|
406
428
|
/**
|
|
407
|
-
*
|
|
429
|
+
* The single bounds-sync channel. Observes the iframe document and
|
|
430
|
+
* every `[data-dot-object="container"]` with a single ResizeObserver,
|
|
431
|
+
* debounces the trailing edge by {@link AUTO_BOUNDS_DEBOUNCE_MS}ms, and
|
|
432
|
+
* emits the full `getDotCMSPageBounds(...)` payload whenever the layout
|
|
433
|
+
* settles. Also listens on `scroll` (since scrolling moves contentlets
|
|
434
|
+
* without changing layout) and on `UVE_FLUSH_BOUNDS` (the editor's
|
|
435
|
+
* "give me bounds NOW, skip the debounce" message used during drag).
|
|
436
|
+
*
|
|
437
|
+
* Re-runs `querySelectorAll` and the observer wiring whenever a
|
|
438
|
+
* MutationObserver detects child changes that touch container nodes,
|
|
439
|
+
* so containers that mount/unmount after page-load are picked up
|
|
440
|
+
* automatically.
|
|
408
441
|
*
|
|
409
|
-
* @param {UVEEventHandler} callback - Function to be called when bounds are requested
|
|
410
|
-
* @returns {Object} Object containing unsubscribe function and event type
|
|
411
|
-
* @returns {Function} .unsubscribe - Function to remove the event listener
|
|
412
|
-
* @returns {UVEEventType} .event - The event type being subscribed to
|
|
413
442
|
* @internal
|
|
414
443
|
*/
|
|
415
|
-
function
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
444
|
+
function onAutoBounds(callback) {
|
|
445
|
+
let debounceTimer = null;
|
|
446
|
+
let observed = [];
|
|
447
|
+
const emit = () => {
|
|
448
|
+
const containers = Array.from(document.querySelectorAll('[data-dot-object="container"]'));
|
|
449
|
+
callback(getDotCMSPageBounds(containers));
|
|
450
|
+
};
|
|
451
|
+
const scheduleEmit = () => {
|
|
452
|
+
if (debounceTimer !== null) {
|
|
453
|
+
clearTimeout(debounceTimer);
|
|
454
|
+
}
|
|
455
|
+
debounceTimer = setTimeout(() => {
|
|
456
|
+
debounceTimer = null;
|
|
457
|
+
emit();
|
|
458
|
+
}, AUTO_BOUNDS_DEBOUNCE_MS);
|
|
459
|
+
};
|
|
460
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
461
|
+
scheduleEmit();
|
|
462
|
+
});
|
|
463
|
+
const observeAll = () => {
|
|
464
|
+
// Tear down previous observations before re-wiring.
|
|
465
|
+
for (const el of observed) {
|
|
466
|
+
resizeObserver.unobserve(el);
|
|
467
|
+
}
|
|
468
|
+
observed = Array.from(document.querySelectorAll('[data-dot-object="container"]'));
|
|
469
|
+
resizeObserver.observe(document.documentElement);
|
|
470
|
+
for (const container of observed) {
|
|
471
|
+
resizeObserver.observe(container);
|
|
421
472
|
}
|
|
422
473
|
};
|
|
423
|
-
|
|
474
|
+
observeAll();
|
|
475
|
+
// Containers can mount/unmount after the page first paints (route
|
|
476
|
+
// changes in headless apps, lazy-loaded sections, etc.). Re-wire only
|
|
477
|
+
// when a node carrying [data-dot-object="container"] is added or
|
|
478
|
+
// removed — ignoring text/attribute churn keeps this observer cheap on
|
|
479
|
+
// busy pages.
|
|
480
|
+
const containsContainerNode = nodes => {
|
|
481
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
482
|
+
const node = nodes[i];
|
|
483
|
+
if (node.nodeType !== Node.ELEMENT_NODE) {
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
const el = node;
|
|
487
|
+
if (el.matches?.('[data-dot-object="container"]') || el.querySelector?.('[data-dot-object="container"]')) {
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return false;
|
|
492
|
+
};
|
|
493
|
+
const mutationObserver = new MutationObserver(mutations => {
|
|
494
|
+
for (const m of mutations) {
|
|
495
|
+
if (m.type !== 'childList') continue;
|
|
496
|
+
if (containsContainerNode(m.addedNodes) || containsContainerNode(m.removedNodes)) {
|
|
497
|
+
observeAll();
|
|
498
|
+
scheduleEmit();
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
// The SDK script can run from <head> before <body> exists. Fall back to
|
|
504
|
+
// <html> in that case — childList+subtree on the documentElement still
|
|
505
|
+
// catches container nodes that mount once <body> arrives.
|
|
506
|
+
mutationObserver.observe(document.body ?? document.documentElement, {
|
|
507
|
+
childList: true,
|
|
508
|
+
subtree: true
|
|
509
|
+
});
|
|
510
|
+
// Scrolling inside the iframe doesn't change layout, so ResizeObserver
|
|
511
|
+
// doesn't fire, but every contentlet's viewport-relative position
|
|
512
|
+
// (getBoundingClientRect) does change. Re-emit bounds after each
|
|
513
|
+
// scroll burst settles so the editor's pinned selected overlay
|
|
514
|
+
// re-anchors to the on-screen position.
|
|
515
|
+
const onScroll = () => scheduleEmit();
|
|
516
|
+
window.addEventListener('scroll', onScroll, {
|
|
517
|
+
passive: true
|
|
518
|
+
});
|
|
519
|
+
// Flush channel: the editor occasionally needs an immediate snapshot
|
|
520
|
+
// of bounds (drag enter, where the dropzone has to know container
|
|
521
|
+
// rectangles before the user moves another pixel). Bypass the
|
|
522
|
+
// debounce timer and emit synchronously.
|
|
523
|
+
const onFlush = event => {
|
|
524
|
+
if (event?.data?.name !== internal.__DOTCMS_UVE_EVENT__.UVE_FLUSH_BOUNDS) return;
|
|
525
|
+
if (debounceTimer !== null) {
|
|
526
|
+
clearTimeout(debounceTimer);
|
|
527
|
+
debounceTimer = null;
|
|
528
|
+
}
|
|
529
|
+
emit();
|
|
530
|
+
};
|
|
531
|
+
window.addEventListener('message', onFlush);
|
|
424
532
|
return {
|
|
425
533
|
unsubscribe: () => {
|
|
426
|
-
|
|
534
|
+
if (debounceTimer !== null) {
|
|
535
|
+
clearTimeout(debounceTimer);
|
|
536
|
+
debounceTimer = null;
|
|
537
|
+
}
|
|
538
|
+
resizeObserver.disconnect();
|
|
539
|
+
mutationObserver.disconnect();
|
|
540
|
+
window.removeEventListener('scroll', onScroll);
|
|
541
|
+
window.removeEventListener('message', onFlush);
|
|
542
|
+
observed = [];
|
|
427
543
|
},
|
|
428
|
-
event: types.UVEEventType.
|
|
544
|
+
event: types.UVEEventType.AUTO_BOUNDS
|
|
429
545
|
};
|
|
430
546
|
}
|
|
431
547
|
/**
|
|
@@ -486,18 +602,34 @@ function onScrollToSection(callback) {
|
|
|
486
602
|
};
|
|
487
603
|
}
|
|
488
604
|
/**
|
|
489
|
-
* Subscribes to contentlet hover events in the UVE editor
|
|
605
|
+
* Subscribes to contentlet hover events in the UVE editor.
|
|
606
|
+
*
|
|
607
|
+
* The callback is invoked with a payload while the pointer is over a
|
|
608
|
+
* DotCMS element, and once with `null` when the pointer leaves the last
|
|
609
|
+
* reported element (transitions onto dead space). The editor uses the
|
|
610
|
+
* `null` signal to clear the hover overlay so it doesn't linger over
|
|
611
|
+
* areas that no longer have a contentlet under the pointer.
|
|
490
612
|
*
|
|
491
|
-
* @param {UVEEventHandler} callback - Function to be called when
|
|
613
|
+
* @param {UVEEventHandler} callback - Function to be called when hover state changes
|
|
492
614
|
* @returns {Object} Object containing unsubscribe function and event type
|
|
493
615
|
* @returns {Function} .unsubscribe - Function to remove the event listener
|
|
494
616
|
* @returns {UVEEventType} .event - The event type being subscribed to
|
|
495
617
|
* @internal
|
|
496
618
|
*/
|
|
497
619
|
function onContentletHovered(callback) {
|
|
620
|
+
let hasHover = false;
|
|
498
621
|
const pointerMoveCallback = event => {
|
|
499
622
|
const foundElement = findDotCMSElement(event.target);
|
|
500
|
-
if (!foundElement)
|
|
623
|
+
if (!foundElement) {
|
|
624
|
+
// Transitioning from a hovered contentlet to dead space — emit
|
|
625
|
+
// a single null so the editor can clear its hover overlay.
|
|
626
|
+
// Subsequent moves over dead space are no-ops.
|
|
627
|
+
if (hasHover) {
|
|
628
|
+
hasHover = false;
|
|
629
|
+
callback(null);
|
|
630
|
+
}
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
501
633
|
const {
|
|
502
634
|
x,
|
|
503
635
|
y,
|
|
@@ -514,18 +646,7 @@ function onContentletHovered(callback) {
|
|
|
514
646
|
baseType: TEMP_EMPTY_CONTENTLET,
|
|
515
647
|
onNumberOfPages: 1
|
|
516
648
|
};
|
|
517
|
-
const contentlet =
|
|
518
|
-
identifier: foundElement.dataset?.['dotIdentifier'],
|
|
519
|
-
title: foundElement.dataset?.['dotTitle'],
|
|
520
|
-
inode: foundElement.dataset?.['dotInode'],
|
|
521
|
-
contentType: foundElement.dataset?.['dotType'],
|
|
522
|
-
baseType: foundElement.dataset?.['dotBasetype'],
|
|
523
|
-
widgetTitle: foundElement.dataset?.['dotWidgetTitle'],
|
|
524
|
-
onNumberOfPages: foundElement.dataset?.['dotOnNumberOfPages'],
|
|
525
|
-
...(foundElement.dataset?.['dotStyleProperties'] && {
|
|
526
|
-
dotStyleProperties: JSON.parse(foundElement.dataset['dotStyleProperties'])
|
|
527
|
-
})
|
|
528
|
-
};
|
|
649
|
+
const contentlet = readContentletDataset(foundElement);
|
|
529
650
|
const vtlFiles = findDotCMSVTLData(foundElement);
|
|
530
651
|
const contentletPayload = {
|
|
531
652
|
container:
|
|
@@ -542,8 +663,15 @@ function onContentletHovered(callback) {
|
|
|
542
663
|
height,
|
|
543
664
|
payload: contentletPayload
|
|
544
665
|
};
|
|
666
|
+
hasHover = true;
|
|
545
667
|
callback(contentletHoveredPayload);
|
|
546
668
|
};
|
|
669
|
+
// We intentionally do not fire null on document `pointerleave`: the
|
|
670
|
+
// editor's hover toolbar lives in the parent window (outside the
|
|
671
|
+
// iframe), so leaving the iframe usually means the user is heading
|
|
672
|
+
// for the toolbar. Killing the overlay there would yank the toolbar
|
|
673
|
+
// away just as the user reaches for it. Dead-space-inside-iframe
|
|
674
|
+
// is already covered by the `pointermove` null branch above.
|
|
547
675
|
document.addEventListener('pointermove', pointerMoveCallback);
|
|
548
676
|
return {
|
|
549
677
|
unsubscribe: () => {
|
|
@@ -552,6 +680,91 @@ function onContentletHovered(callback) {
|
|
|
552
680
|
event: types.UVEEventType.CONTENTLET_HOVERED
|
|
553
681
|
};
|
|
554
682
|
}
|
|
683
|
+
/**
|
|
684
|
+
* Subscribes to contentlet click events in the UVE editor.
|
|
685
|
+
*
|
|
686
|
+
* The editor's hover overlay is `pointer-events: none` so wheel events pass
|
|
687
|
+
* through to the iframe. We detect the user's selection click here instead and
|
|
688
|
+
* post it back to the editor.
|
|
689
|
+
*
|
|
690
|
+
* @param {UVEEventHandler} callback - Function to be called when a contentlet is clicked
|
|
691
|
+
* @returns {Object} Object containing unsubscribe function and event type
|
|
692
|
+
* @internal
|
|
693
|
+
*/
|
|
694
|
+
function onContentletClicked(callback) {
|
|
695
|
+
// Track the last selected contentlet so a second click on the same one
|
|
696
|
+
// lets the page's native click through (links, accordions, etc.). The
|
|
697
|
+
// first click is "select"; subsequent clicks on the selected contentlet
|
|
698
|
+
// are "interact with the page".
|
|
699
|
+
let lastSelectedInode;
|
|
700
|
+
const clickCallback = event => {
|
|
701
|
+
const foundElement = findDotCMSElement(event.target);
|
|
702
|
+
if (!foundElement) return;
|
|
703
|
+
const isContainer = foundElement.dataset?.['dotObject'] === 'container';
|
|
704
|
+
// Only emit for contentlet clicks; an empty container click is a no-op
|
|
705
|
+
// for selection purposes (there's nothing to select).
|
|
706
|
+
if (isContainer) return;
|
|
707
|
+
const inode = foundElement.dataset?.['dotInode'];
|
|
708
|
+
// If the user is clicking the already-selected contentlet, let the
|
|
709
|
+
// page handle the click natively (link navigation, button handlers,
|
|
710
|
+
// form submission). The editor selection toolbar already exposes the
|
|
711
|
+
// edit/delete/etc actions; the contentlet's own UI should still work.
|
|
712
|
+
if (inode && inode === lastSelectedInode) {
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
// First click on this contentlet (or a different one) — select it in
|
|
716
|
+
// the editor and block the page's natural click. Capture phase +
|
|
717
|
+
// preventDefault + stopPropagation suppresses both the default action
|
|
718
|
+
// and any subscribers further down the tree.
|
|
719
|
+
event.preventDefault();
|
|
720
|
+
event.stopPropagation();
|
|
721
|
+
lastSelectedInode = inode;
|
|
722
|
+
const {
|
|
723
|
+
x,
|
|
724
|
+
y,
|
|
725
|
+
width,
|
|
726
|
+
height
|
|
727
|
+
} = foundElement.getBoundingClientRect();
|
|
728
|
+
const contentlet = readContentletDataset(foundElement);
|
|
729
|
+
const vtlFiles = findDotCMSVTLData(foundElement);
|
|
730
|
+
callback({
|
|
731
|
+
x,
|
|
732
|
+
y,
|
|
733
|
+
width,
|
|
734
|
+
height,
|
|
735
|
+
payload: {
|
|
736
|
+
container: foundElement.dataset?.['dotContainer'] ? JSON.parse(foundElement.dataset?.['dotContainer']) : getClosestDotCMSContainerData(foundElement),
|
|
737
|
+
contentlet,
|
|
738
|
+
vtlFiles
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
};
|
|
742
|
+
// The editor clears its selection on canvas resize / scroll. When that
|
|
743
|
+
// happens, our lastSelectedInode is stale: a click on what used to be the
|
|
744
|
+
// selected contentlet would be treated as a passthrough (page click) even
|
|
745
|
+
// though the editor no longer has it selected. Listen for the
|
|
746
|
+
// UVE_SELECTION_CLEARED message and reset the tracker.
|
|
747
|
+
const selectionClearedCallback = event => {
|
|
748
|
+
if (event?.data?.name === internal.__DOTCMS_UVE_EVENT__.UVE_SELECTION_CLEARED) {
|
|
749
|
+
lastSelectedInode = undefined;
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
// Capture phase so we run BEFORE the page's own click handlers and can
|
|
753
|
+
// preventDefault/stopPropagation effectively.
|
|
754
|
+
document.addEventListener('click', clickCallback, {
|
|
755
|
+
capture: true
|
|
756
|
+
});
|
|
757
|
+
window.addEventListener('message', selectionClearedCallback);
|
|
758
|
+
return {
|
|
759
|
+
unsubscribe: () => {
|
|
760
|
+
document.removeEventListener('click', clickCallback, {
|
|
761
|
+
capture: true
|
|
762
|
+
});
|
|
763
|
+
window.removeEventListener('message', selectionClearedCallback);
|
|
764
|
+
},
|
|
765
|
+
event: types.UVEEventType.CONTENTLET_CLICKED
|
|
766
|
+
};
|
|
767
|
+
}
|
|
555
768
|
|
|
556
769
|
/**
|
|
557
770
|
* Events that can be subscribed to in the UVE
|
|
@@ -566,17 +779,31 @@ const __UVE_EVENTS__ = {
|
|
|
566
779
|
[types.UVEEventType.PAGE_RELOAD]: callback => {
|
|
567
780
|
return onPageReload(callback);
|
|
568
781
|
},
|
|
569
|
-
[types.UVEEventType.REQUEST_BOUNDS]: callback => {
|
|
570
|
-
return onRequestBounds(callback);
|
|
571
|
-
},
|
|
572
782
|
[types.UVEEventType.IFRAME_SCROLL]: callback => {
|
|
573
783
|
return onIframeScroll(callback);
|
|
574
784
|
},
|
|
575
785
|
[types.UVEEventType.CONTENTLET_HOVERED]: callback => {
|
|
576
786
|
return onContentletHovered(callback);
|
|
577
787
|
},
|
|
788
|
+
[types.UVEEventType.CONTENTLET_CLICKED]: callback => {
|
|
789
|
+
return onContentletClicked(callback);
|
|
790
|
+
},
|
|
578
791
|
[types.UVEEventType.SCROLL_TO_SECTION]: callback => {
|
|
579
792
|
return onScrollToSection(callback);
|
|
793
|
+
},
|
|
794
|
+
// SELECTION_CLEARED is editor→SDK only. No public subscriber surface;
|
|
795
|
+
// onContentletClicked listens for the underlying postMessage internally
|
|
796
|
+
// to reset its lastSelectedInode tracker.
|
|
797
|
+
[types.UVEEventType.SELECTION_CLEARED]: _callback => {
|
|
798
|
+
return {
|
|
799
|
+
unsubscribe: () => {
|
|
800
|
+
/* no-op: SELECTION_CLEARED has no consumer-facing subscription */
|
|
801
|
+
},
|
|
802
|
+
event: types.UVEEventType.SELECTION_CLEARED
|
|
803
|
+
};
|
|
804
|
+
},
|
|
805
|
+
[types.UVEEventType.AUTO_BOUNDS]: callback => {
|
|
806
|
+
return onAutoBounds(callback);
|
|
580
807
|
}
|
|
581
808
|
};
|
|
582
809
|
/**
|
|
@@ -744,97 +971,6 @@ function createUVESubscription(eventType, callback) {
|
|
|
744
971
|
return eventCallback(callback);
|
|
745
972
|
}
|
|
746
973
|
|
|
747
|
-
/**
|
|
748
|
-
* Observes rendered document height changes and notifies the caller after layout settles.
|
|
749
|
-
*
|
|
750
|
-
* Uses ResizeObserver on <html> for layout/viewport-driven changes and MutationObserver
|
|
751
|
-
* on <body> to catch DOM additions/removals that may shrink the page without a resize.
|
|
752
|
-
* Measurement reads `body.offsetHeight`, which tracks actual content height and
|
|
753
|
-
* decreases correctly after DOM removals, unaffected by CSS min-height on the html element.
|
|
754
|
-
*/
|
|
755
|
-
function observeDocumentHeight({
|
|
756
|
-
onHeightChange,
|
|
757
|
-
documentRef = document,
|
|
758
|
-
windowRef = window,
|
|
759
|
-
debounceMs = 50
|
|
760
|
-
}) {
|
|
761
|
-
const html = documentRef.documentElement;
|
|
762
|
-
const body = documentRef.body;
|
|
763
|
-
let debounceTimer = null;
|
|
764
|
-
let rafOuter = null;
|
|
765
|
-
let rafInner = null;
|
|
766
|
-
let lastHeight = null;
|
|
767
|
-
let destroyed = false;
|
|
768
|
-
const measureAndNotify = () => {
|
|
769
|
-
const height = body.offsetHeight;
|
|
770
|
-
if (!height || height === lastHeight) {
|
|
771
|
-
return;
|
|
772
|
-
}
|
|
773
|
-
lastHeight = height;
|
|
774
|
-
onHeightChange(height);
|
|
775
|
-
};
|
|
776
|
-
const scheduleNotify = () => {
|
|
777
|
-
if (destroyed) {
|
|
778
|
-
return;
|
|
779
|
-
}
|
|
780
|
-
if (debounceTimer !== null) {
|
|
781
|
-
clearTimeout(debounceTimer);
|
|
782
|
-
}
|
|
783
|
-
debounceTimer = setTimeout(() => {
|
|
784
|
-
debounceTimer = null;
|
|
785
|
-
if (destroyed) {
|
|
786
|
-
return;
|
|
787
|
-
}
|
|
788
|
-
rafOuter = windowRef.requestAnimationFrame(() => {
|
|
789
|
-
rafOuter = null;
|
|
790
|
-
if (destroyed) {
|
|
791
|
-
return;
|
|
792
|
-
}
|
|
793
|
-
rafInner = windowRef.requestAnimationFrame(() => {
|
|
794
|
-
rafInner = null;
|
|
795
|
-
if (!destroyed) {
|
|
796
|
-
measureAndNotify();
|
|
797
|
-
}
|
|
798
|
-
});
|
|
799
|
-
});
|
|
800
|
-
}, debounceMs);
|
|
801
|
-
};
|
|
802
|
-
const onLoad = () => scheduleNotify();
|
|
803
|
-
if (documentRef.readyState === 'complete' || documentRef.readyState === 'interactive') {
|
|
804
|
-
scheduleNotify();
|
|
805
|
-
} else {
|
|
806
|
-
windowRef.addEventListener('load', onLoad);
|
|
807
|
-
}
|
|
808
|
-
const resizeObserver = new ResizeObserver(() => scheduleNotify());
|
|
809
|
-
resizeObserver.observe(html);
|
|
810
|
-
const mutationObserver = new MutationObserver(() => scheduleNotify());
|
|
811
|
-
mutationObserver.observe(documentRef.body ?? html, {
|
|
812
|
-
childList: true,
|
|
813
|
-
subtree: true
|
|
814
|
-
});
|
|
815
|
-
return {
|
|
816
|
-
destroy: () => {
|
|
817
|
-
destroyed = true;
|
|
818
|
-
if (debounceTimer !== null) {
|
|
819
|
-
clearTimeout(debounceTimer);
|
|
820
|
-
debounceTimer = null;
|
|
821
|
-
}
|
|
822
|
-
if (rafOuter !== null) {
|
|
823
|
-
windowRef.cancelAnimationFrame(rafOuter);
|
|
824
|
-
rafOuter = null;
|
|
825
|
-
}
|
|
826
|
-
if (rafInner !== null) {
|
|
827
|
-
windowRef.cancelAnimationFrame(rafInner);
|
|
828
|
-
rafInner = null;
|
|
829
|
-
}
|
|
830
|
-
lastHeight = null;
|
|
831
|
-
resizeObserver.disconnect();
|
|
832
|
-
mutationObserver.disconnect();
|
|
833
|
-
windowRef.removeEventListener('load', onLoad);
|
|
834
|
-
}
|
|
835
|
-
};
|
|
836
|
-
}
|
|
837
|
-
|
|
838
974
|
/**
|
|
839
975
|
* Sets the bounds of the containers in the editor.
|
|
840
976
|
* Retrieves the containers from the DOM and sends their position data to the editor.
|
|
@@ -999,9 +1135,6 @@ function registerUVEEvents() {
|
|
|
999
1135
|
const pageReloadSubscription = createUVESubscription(types.UVEEventType.PAGE_RELOAD, () => {
|
|
1000
1136
|
window.location.reload();
|
|
1001
1137
|
});
|
|
1002
|
-
const requestBoundsSubscription = createUVESubscription(types.UVEEventType.REQUEST_BOUNDS, bounds => {
|
|
1003
|
-
setBounds(bounds);
|
|
1004
|
-
});
|
|
1005
1138
|
const iframeScrollSubscription = createUVESubscription(types.UVEEventType.IFRAME_SCROLL, direction => {
|
|
1006
1139
|
if (window.scrollY === 0 && direction === 'up' || computeScrollIsInBottom() && direction === 'down') {
|
|
1007
1140
|
// If the iframe scroll is at the top or bottom, do not send anything.
|
|
@@ -1021,14 +1154,30 @@ function registerUVEEvents() {
|
|
|
1021
1154
|
payload: contentletHovered
|
|
1022
1155
|
});
|
|
1023
1156
|
});
|
|
1157
|
+
const contentletClickedSubscription = createUVESubscription(types.UVEEventType.CONTENTLET_CLICKED, contentletClicked => {
|
|
1158
|
+
sendMessageToUVE({
|
|
1159
|
+
action: types.DotCMSUVEAction.SET_SELECTED_CONTENTLET,
|
|
1160
|
+
payload: contentletClicked
|
|
1161
|
+
});
|
|
1162
|
+
});
|
|
1024
1163
|
const scrollToSectionSubscription = createUVESubscription(types.UVEEventType.SCROLL_TO_SECTION, payload => {
|
|
1025
1164
|
sendMessageToUVE({
|
|
1026
1165
|
action: types.DotCMSUVEAction.SECTION_OFFSET,
|
|
1027
1166
|
payload
|
|
1028
1167
|
});
|
|
1029
1168
|
});
|
|
1169
|
+
// The single bounds-sync channel. The SDK observes layout changes
|
|
1170
|
+
// inside the iframe (media-query reflows, image/font load shifts,
|
|
1171
|
+
// container mount/unmount, scroll, etc.) and emits SET_BOUNDS on the
|
|
1172
|
+
// trailing edge of a debounce window. The editor can also send a
|
|
1173
|
+
// UVE_FLUSH_BOUNDS message to request an immediate synchronous emit
|
|
1174
|
+
// (used during drag/drop, where the dropzone needs current bounds
|
|
1175
|
+
// before the user moves another pixel).
|
|
1176
|
+
const autoBoundsSubscription = createUVESubscription(types.UVEEventType.AUTO_BOUNDS, bounds => {
|
|
1177
|
+
setBounds(bounds);
|
|
1178
|
+
});
|
|
1030
1179
|
return {
|
|
1031
|
-
subscriptions: [pageReloadSubscription,
|
|
1180
|
+
subscriptions: [pageReloadSubscription, iframeScrollSubscription, contentletHoveredSubscription, contentletClickedSubscription, scrollToSectionSubscription, autoBoundsSubscription]
|
|
1032
1181
|
};
|
|
1033
1182
|
}
|
|
1034
1183
|
/**
|
|
@@ -1071,60 +1220,6 @@ function listenBlockEditorInlineEvent() {
|
|
|
1071
1220
|
}
|
|
1072
1221
|
};
|
|
1073
1222
|
}
|
|
1074
|
-
/**
|
|
1075
|
-
* Returns whether iframe height must be synchronized via postMessage.
|
|
1076
|
-
*
|
|
1077
|
-
* Same-origin parents can measure iframe content directly, so they do not need
|
|
1078
|
-
* child-driven height reporting. Cross-origin parents cannot access the iframe
|
|
1079
|
-
* DOM, so they still need the reporter fallback.
|
|
1080
|
-
*/
|
|
1081
|
-
function shouldReportIframeHeightToParent() {
|
|
1082
|
-
if (window.parent === window) {
|
|
1083
|
-
return false;
|
|
1084
|
-
}
|
|
1085
|
-
try {
|
|
1086
|
-
const parentDocument = window.parent.document;
|
|
1087
|
-
return !parentDocument;
|
|
1088
|
-
} catch {
|
|
1089
|
-
return true;
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
/**
|
|
1093
|
-
* Reports the iframe document height to the parent UVE shell via postMessage.
|
|
1094
|
-
*
|
|
1095
|
-
* Uses ResizeObserver on <html> for viewport/font/image-driven size changes, and
|
|
1096
|
-
* MutationObserver on <body> to catch DOM removals (e.g. contentlets deleted by
|
|
1097
|
-
* the editor) that shrink the page without triggering a resize event.
|
|
1098
|
-
*
|
|
1099
|
-
* Measurement reads `document.documentElement.offsetHeight` — the actual rendered
|
|
1100
|
-
* height of the <html> element after layout. `scrollHeight` is intentionally avoided
|
|
1101
|
-
* because it does not reliably decrease when content is removed from the DOM.
|
|
1102
|
-
*
|
|
1103
|
-
* Height sends are coalesced to at most one per double-requestAnimationFrame pair
|
|
1104
|
-
* so they always run after layout and paint have settled.
|
|
1105
|
-
*
|
|
1106
|
-
* @returns {{ destroyHeightReporter: () => void }} Cleanup function that removes
|
|
1107
|
-
* all listeners and disconnects the observers.
|
|
1108
|
-
*/
|
|
1109
|
-
function reportIframeHeight() {
|
|
1110
|
-
const {
|
|
1111
|
-
destroy
|
|
1112
|
-
} = observeDocumentHeight({
|
|
1113
|
-
onHeightChange: height => {
|
|
1114
|
-
sendMessageToUVE({
|
|
1115
|
-
action: types.DotCMSUVEAction.IFRAME_HEIGHT,
|
|
1116
|
-
payload: {
|
|
1117
|
-
height
|
|
1118
|
-
}
|
|
1119
|
-
});
|
|
1120
|
-
}
|
|
1121
|
-
});
|
|
1122
|
-
return {
|
|
1123
|
-
destroyHeightReporter: () => {
|
|
1124
|
-
destroy();
|
|
1125
|
-
}
|
|
1126
|
-
};
|
|
1127
|
-
}
|
|
1128
1223
|
const listenBlockEditorClick = () => {
|
|
1129
1224
|
const editBlockEditorNodes = document.querySelectorAll('[data-block-editor-content]');
|
|
1130
1225
|
if (!editBlockEditorNodes.length) {
|
|
@@ -1330,17 +1425,11 @@ function initUVE(config = {}) {
|
|
|
1330
1425
|
const {
|
|
1331
1426
|
destroyListenBlockEditorInlineEvent
|
|
1332
1427
|
} = listenBlockEditorInlineEvent();
|
|
1333
|
-
const {
|
|
1334
|
-
destroyHeightReporter
|
|
1335
|
-
} = shouldReportIframeHeightToParent() ? reportIframeHeight() : {
|
|
1336
|
-
destroyHeightReporter: () => undefined
|
|
1337
|
-
};
|
|
1338
1428
|
return {
|
|
1339
1429
|
destroyUVESubscriptions: () => {
|
|
1340
1430
|
subscriptions.forEach(subscription => subscription.unsubscribe());
|
|
1341
1431
|
destroyScrollHandler();
|
|
1342
1432
|
destroyListenBlockEditorInlineEvent();
|
|
1343
|
-
destroyHeightReporter();
|
|
1344
1433
|
}
|
|
1345
1434
|
};
|
|
1346
1435
|
}
|
|
@@ -1378,7 +1467,7 @@ exports.getUVEState = getUVEState;
|
|
|
1378
1467
|
exports.initInlineEditing = initInlineEditing;
|
|
1379
1468
|
exports.initUVE = initUVE;
|
|
1380
1469
|
exports.isValidBlocks = isValidBlocks;
|
|
1381
|
-
exports.
|
|
1470
|
+
exports.readContentletDataset = readContentletDataset;
|
|
1382
1471
|
exports.reorderMenu = reorderMenu;
|
|
1383
1472
|
exports.sendMessageToUVE = sendMessageToUVE;
|
|
1384
1473
|
exports.setBounds = setBounds;
|
package/public.esm.js
CHANGED
|
@@ -354,6 +354,27 @@ function getDotContainerAttributes({
|
|
|
354
354
|
'data-dot-uuid': uuid
|
|
355
355
|
};
|
|
356
356
|
}
|
|
357
|
+
/**
|
|
358
|
+
* Read a contentlet's dataset attributes off a DOM element and return a
|
|
359
|
+
* normalized contentlet object. Mirrors the shape consumed by the editor's
|
|
360
|
+
* SET_BOUNDS and CONTENTLET_CLICKED events. Optionally parses the
|
|
361
|
+
* `dotStyleProperties` JSON when present.
|
|
362
|
+
*/
|
|
363
|
+
function readContentletDataset(element) {
|
|
364
|
+
const dataset = element.dataset ?? {};
|
|
365
|
+
return {
|
|
366
|
+
identifier: dataset['dotIdentifier'],
|
|
367
|
+
title: dataset['dotTitle'],
|
|
368
|
+
inode: dataset['dotInode'],
|
|
369
|
+
contentType: dataset['dotType'],
|
|
370
|
+
baseType: dataset['dotBasetype'],
|
|
371
|
+
widgetTitle: dataset['dotWidgetTitle'],
|
|
372
|
+
onNumberOfPages: dataset['dotOnNumberOfPages'],
|
|
373
|
+
...(dataset['dotStyleProperties'] && {
|
|
374
|
+
dotStyleProperties: JSON.parse(dataset['dotStyleProperties'])
|
|
375
|
+
})
|
|
376
|
+
};
|
|
377
|
+
}
|
|
357
378
|
|
|
358
379
|
/**
|
|
359
380
|
* Subscribes to content changes in the UVE editor
|
|
@@ -401,29 +422,124 @@ function onPageReload(callback) {
|
|
|
401
422
|
event: UVEEventType.PAGE_RELOAD
|
|
402
423
|
};
|
|
403
424
|
}
|
|
425
|
+
const AUTO_BOUNDS_DEBOUNCE_MS = 100;
|
|
404
426
|
/**
|
|
405
|
-
*
|
|
427
|
+
* The single bounds-sync channel. Observes the iframe document and
|
|
428
|
+
* every `[data-dot-object="container"]` with a single ResizeObserver,
|
|
429
|
+
* debounces the trailing edge by {@link AUTO_BOUNDS_DEBOUNCE_MS}ms, and
|
|
430
|
+
* emits the full `getDotCMSPageBounds(...)` payload whenever the layout
|
|
431
|
+
* settles. Also listens on `scroll` (since scrolling moves contentlets
|
|
432
|
+
* without changing layout) and on `UVE_FLUSH_BOUNDS` (the editor's
|
|
433
|
+
* "give me bounds NOW, skip the debounce" message used during drag).
|
|
434
|
+
*
|
|
435
|
+
* Re-runs `querySelectorAll` and the observer wiring whenever a
|
|
436
|
+
* MutationObserver detects child changes that touch container nodes,
|
|
437
|
+
* so containers that mount/unmount after page-load are picked up
|
|
438
|
+
* automatically.
|
|
406
439
|
*
|
|
407
|
-
* @param {UVEEventHandler} callback - Function to be called when bounds are requested
|
|
408
|
-
* @returns {Object} Object containing unsubscribe function and event type
|
|
409
|
-
* @returns {Function} .unsubscribe - Function to remove the event listener
|
|
410
|
-
* @returns {UVEEventType} .event - The event type being subscribed to
|
|
411
440
|
* @internal
|
|
412
441
|
*/
|
|
413
|
-
function
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
442
|
+
function onAutoBounds(callback) {
|
|
443
|
+
let debounceTimer = null;
|
|
444
|
+
let observed = [];
|
|
445
|
+
const emit = () => {
|
|
446
|
+
const containers = Array.from(document.querySelectorAll('[data-dot-object="container"]'));
|
|
447
|
+
callback(getDotCMSPageBounds(containers));
|
|
448
|
+
};
|
|
449
|
+
const scheduleEmit = () => {
|
|
450
|
+
if (debounceTimer !== null) {
|
|
451
|
+
clearTimeout(debounceTimer);
|
|
452
|
+
}
|
|
453
|
+
debounceTimer = setTimeout(() => {
|
|
454
|
+
debounceTimer = null;
|
|
455
|
+
emit();
|
|
456
|
+
}, AUTO_BOUNDS_DEBOUNCE_MS);
|
|
457
|
+
};
|
|
458
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
459
|
+
scheduleEmit();
|
|
460
|
+
});
|
|
461
|
+
const observeAll = () => {
|
|
462
|
+
// Tear down previous observations before re-wiring.
|
|
463
|
+
for (const el of observed) {
|
|
464
|
+
resizeObserver.unobserve(el);
|
|
465
|
+
}
|
|
466
|
+
observed = Array.from(document.querySelectorAll('[data-dot-object="container"]'));
|
|
467
|
+
resizeObserver.observe(document.documentElement);
|
|
468
|
+
for (const container of observed) {
|
|
469
|
+
resizeObserver.observe(container);
|
|
419
470
|
}
|
|
420
471
|
};
|
|
421
|
-
|
|
472
|
+
observeAll();
|
|
473
|
+
// Containers can mount/unmount after the page first paints (route
|
|
474
|
+
// changes in headless apps, lazy-loaded sections, etc.). Re-wire only
|
|
475
|
+
// when a node carrying [data-dot-object="container"] is added or
|
|
476
|
+
// removed — ignoring text/attribute churn keeps this observer cheap on
|
|
477
|
+
// busy pages.
|
|
478
|
+
const containsContainerNode = nodes => {
|
|
479
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
480
|
+
const node = nodes[i];
|
|
481
|
+
if (node.nodeType !== Node.ELEMENT_NODE) {
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
const el = node;
|
|
485
|
+
if (el.matches?.('[data-dot-object="container"]') || el.querySelector?.('[data-dot-object="container"]')) {
|
|
486
|
+
return true;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return false;
|
|
490
|
+
};
|
|
491
|
+
const mutationObserver = new MutationObserver(mutations => {
|
|
492
|
+
for (const m of mutations) {
|
|
493
|
+
if (m.type !== 'childList') continue;
|
|
494
|
+
if (containsContainerNode(m.addedNodes) || containsContainerNode(m.removedNodes)) {
|
|
495
|
+
observeAll();
|
|
496
|
+
scheduleEmit();
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
// The SDK script can run from <head> before <body> exists. Fall back to
|
|
502
|
+
// <html> in that case — childList+subtree on the documentElement still
|
|
503
|
+
// catches container nodes that mount once <body> arrives.
|
|
504
|
+
mutationObserver.observe(document.body ?? document.documentElement, {
|
|
505
|
+
childList: true,
|
|
506
|
+
subtree: true
|
|
507
|
+
});
|
|
508
|
+
// Scrolling inside the iframe doesn't change layout, so ResizeObserver
|
|
509
|
+
// doesn't fire, but every contentlet's viewport-relative position
|
|
510
|
+
// (getBoundingClientRect) does change. Re-emit bounds after each
|
|
511
|
+
// scroll burst settles so the editor's pinned selected overlay
|
|
512
|
+
// re-anchors to the on-screen position.
|
|
513
|
+
const onScroll = () => scheduleEmit();
|
|
514
|
+
window.addEventListener('scroll', onScroll, {
|
|
515
|
+
passive: true
|
|
516
|
+
});
|
|
517
|
+
// Flush channel: the editor occasionally needs an immediate snapshot
|
|
518
|
+
// of bounds (drag enter, where the dropzone has to know container
|
|
519
|
+
// rectangles before the user moves another pixel). Bypass the
|
|
520
|
+
// debounce timer and emit synchronously.
|
|
521
|
+
const onFlush = event => {
|
|
522
|
+
if (event?.data?.name !== __DOTCMS_UVE_EVENT__.UVE_FLUSH_BOUNDS) return;
|
|
523
|
+
if (debounceTimer !== null) {
|
|
524
|
+
clearTimeout(debounceTimer);
|
|
525
|
+
debounceTimer = null;
|
|
526
|
+
}
|
|
527
|
+
emit();
|
|
528
|
+
};
|
|
529
|
+
window.addEventListener('message', onFlush);
|
|
422
530
|
return {
|
|
423
531
|
unsubscribe: () => {
|
|
424
|
-
|
|
532
|
+
if (debounceTimer !== null) {
|
|
533
|
+
clearTimeout(debounceTimer);
|
|
534
|
+
debounceTimer = null;
|
|
535
|
+
}
|
|
536
|
+
resizeObserver.disconnect();
|
|
537
|
+
mutationObserver.disconnect();
|
|
538
|
+
window.removeEventListener('scroll', onScroll);
|
|
539
|
+
window.removeEventListener('message', onFlush);
|
|
540
|
+
observed = [];
|
|
425
541
|
},
|
|
426
|
-
event: UVEEventType.
|
|
542
|
+
event: UVEEventType.AUTO_BOUNDS
|
|
427
543
|
};
|
|
428
544
|
}
|
|
429
545
|
/**
|
|
@@ -484,18 +600,34 @@ function onScrollToSection(callback) {
|
|
|
484
600
|
};
|
|
485
601
|
}
|
|
486
602
|
/**
|
|
487
|
-
* Subscribes to contentlet hover events in the UVE editor
|
|
603
|
+
* Subscribes to contentlet hover events in the UVE editor.
|
|
604
|
+
*
|
|
605
|
+
* The callback is invoked with a payload while the pointer is over a
|
|
606
|
+
* DotCMS element, and once with `null` when the pointer leaves the last
|
|
607
|
+
* reported element (transitions onto dead space). The editor uses the
|
|
608
|
+
* `null` signal to clear the hover overlay so it doesn't linger over
|
|
609
|
+
* areas that no longer have a contentlet under the pointer.
|
|
488
610
|
*
|
|
489
|
-
* @param {UVEEventHandler} callback - Function to be called when
|
|
611
|
+
* @param {UVEEventHandler} callback - Function to be called when hover state changes
|
|
490
612
|
* @returns {Object} Object containing unsubscribe function and event type
|
|
491
613
|
* @returns {Function} .unsubscribe - Function to remove the event listener
|
|
492
614
|
* @returns {UVEEventType} .event - The event type being subscribed to
|
|
493
615
|
* @internal
|
|
494
616
|
*/
|
|
495
617
|
function onContentletHovered(callback) {
|
|
618
|
+
let hasHover = false;
|
|
496
619
|
const pointerMoveCallback = event => {
|
|
497
620
|
const foundElement = findDotCMSElement(event.target);
|
|
498
|
-
if (!foundElement)
|
|
621
|
+
if (!foundElement) {
|
|
622
|
+
// Transitioning from a hovered contentlet to dead space — emit
|
|
623
|
+
// a single null so the editor can clear its hover overlay.
|
|
624
|
+
// Subsequent moves over dead space are no-ops.
|
|
625
|
+
if (hasHover) {
|
|
626
|
+
hasHover = false;
|
|
627
|
+
callback(null);
|
|
628
|
+
}
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
499
631
|
const {
|
|
500
632
|
x,
|
|
501
633
|
y,
|
|
@@ -512,18 +644,7 @@ function onContentletHovered(callback) {
|
|
|
512
644
|
baseType: TEMP_EMPTY_CONTENTLET,
|
|
513
645
|
onNumberOfPages: 1
|
|
514
646
|
};
|
|
515
|
-
const contentlet =
|
|
516
|
-
identifier: foundElement.dataset?.['dotIdentifier'],
|
|
517
|
-
title: foundElement.dataset?.['dotTitle'],
|
|
518
|
-
inode: foundElement.dataset?.['dotInode'],
|
|
519
|
-
contentType: foundElement.dataset?.['dotType'],
|
|
520
|
-
baseType: foundElement.dataset?.['dotBasetype'],
|
|
521
|
-
widgetTitle: foundElement.dataset?.['dotWidgetTitle'],
|
|
522
|
-
onNumberOfPages: foundElement.dataset?.['dotOnNumberOfPages'],
|
|
523
|
-
...(foundElement.dataset?.['dotStyleProperties'] && {
|
|
524
|
-
dotStyleProperties: JSON.parse(foundElement.dataset['dotStyleProperties'])
|
|
525
|
-
})
|
|
526
|
-
};
|
|
647
|
+
const contentlet = readContentletDataset(foundElement);
|
|
527
648
|
const vtlFiles = findDotCMSVTLData(foundElement);
|
|
528
649
|
const contentletPayload = {
|
|
529
650
|
container:
|
|
@@ -540,8 +661,15 @@ function onContentletHovered(callback) {
|
|
|
540
661
|
height,
|
|
541
662
|
payload: contentletPayload
|
|
542
663
|
};
|
|
664
|
+
hasHover = true;
|
|
543
665
|
callback(contentletHoveredPayload);
|
|
544
666
|
};
|
|
667
|
+
// We intentionally do not fire null on document `pointerleave`: the
|
|
668
|
+
// editor's hover toolbar lives in the parent window (outside the
|
|
669
|
+
// iframe), so leaving the iframe usually means the user is heading
|
|
670
|
+
// for the toolbar. Killing the overlay there would yank the toolbar
|
|
671
|
+
// away just as the user reaches for it. Dead-space-inside-iframe
|
|
672
|
+
// is already covered by the `pointermove` null branch above.
|
|
545
673
|
document.addEventListener('pointermove', pointerMoveCallback);
|
|
546
674
|
return {
|
|
547
675
|
unsubscribe: () => {
|
|
@@ -550,6 +678,91 @@ function onContentletHovered(callback) {
|
|
|
550
678
|
event: UVEEventType.CONTENTLET_HOVERED
|
|
551
679
|
};
|
|
552
680
|
}
|
|
681
|
+
/**
|
|
682
|
+
* Subscribes to contentlet click events in the UVE editor.
|
|
683
|
+
*
|
|
684
|
+
* The editor's hover overlay is `pointer-events: none` so wheel events pass
|
|
685
|
+
* through to the iframe. We detect the user's selection click here instead and
|
|
686
|
+
* post it back to the editor.
|
|
687
|
+
*
|
|
688
|
+
* @param {UVEEventHandler} callback - Function to be called when a contentlet is clicked
|
|
689
|
+
* @returns {Object} Object containing unsubscribe function and event type
|
|
690
|
+
* @internal
|
|
691
|
+
*/
|
|
692
|
+
function onContentletClicked(callback) {
|
|
693
|
+
// Track the last selected contentlet so a second click on the same one
|
|
694
|
+
// lets the page's native click through (links, accordions, etc.). The
|
|
695
|
+
// first click is "select"; subsequent clicks on the selected contentlet
|
|
696
|
+
// are "interact with the page".
|
|
697
|
+
let lastSelectedInode;
|
|
698
|
+
const clickCallback = event => {
|
|
699
|
+
const foundElement = findDotCMSElement(event.target);
|
|
700
|
+
if (!foundElement) return;
|
|
701
|
+
const isContainer = foundElement.dataset?.['dotObject'] === 'container';
|
|
702
|
+
// Only emit for contentlet clicks; an empty container click is a no-op
|
|
703
|
+
// for selection purposes (there's nothing to select).
|
|
704
|
+
if (isContainer) return;
|
|
705
|
+
const inode = foundElement.dataset?.['dotInode'];
|
|
706
|
+
// If the user is clicking the already-selected contentlet, let the
|
|
707
|
+
// page handle the click natively (link navigation, button handlers,
|
|
708
|
+
// form submission). The editor selection toolbar already exposes the
|
|
709
|
+
// edit/delete/etc actions; the contentlet's own UI should still work.
|
|
710
|
+
if (inode && inode === lastSelectedInode) {
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
// First click on this contentlet (or a different one) — select it in
|
|
714
|
+
// the editor and block the page's natural click. Capture phase +
|
|
715
|
+
// preventDefault + stopPropagation suppresses both the default action
|
|
716
|
+
// and any subscribers further down the tree.
|
|
717
|
+
event.preventDefault();
|
|
718
|
+
event.stopPropagation();
|
|
719
|
+
lastSelectedInode = inode;
|
|
720
|
+
const {
|
|
721
|
+
x,
|
|
722
|
+
y,
|
|
723
|
+
width,
|
|
724
|
+
height
|
|
725
|
+
} = foundElement.getBoundingClientRect();
|
|
726
|
+
const contentlet = readContentletDataset(foundElement);
|
|
727
|
+
const vtlFiles = findDotCMSVTLData(foundElement);
|
|
728
|
+
callback({
|
|
729
|
+
x,
|
|
730
|
+
y,
|
|
731
|
+
width,
|
|
732
|
+
height,
|
|
733
|
+
payload: {
|
|
734
|
+
container: foundElement.dataset?.['dotContainer'] ? JSON.parse(foundElement.dataset?.['dotContainer']) : getClosestDotCMSContainerData(foundElement),
|
|
735
|
+
contentlet,
|
|
736
|
+
vtlFiles
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
};
|
|
740
|
+
// The editor clears its selection on canvas resize / scroll. When that
|
|
741
|
+
// happens, our lastSelectedInode is stale: a click on what used to be the
|
|
742
|
+
// selected contentlet would be treated as a passthrough (page click) even
|
|
743
|
+
// though the editor no longer has it selected. Listen for the
|
|
744
|
+
// UVE_SELECTION_CLEARED message and reset the tracker.
|
|
745
|
+
const selectionClearedCallback = event => {
|
|
746
|
+
if (event?.data?.name === __DOTCMS_UVE_EVENT__.UVE_SELECTION_CLEARED) {
|
|
747
|
+
lastSelectedInode = undefined;
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
// Capture phase so we run BEFORE the page's own click handlers and can
|
|
751
|
+
// preventDefault/stopPropagation effectively.
|
|
752
|
+
document.addEventListener('click', clickCallback, {
|
|
753
|
+
capture: true
|
|
754
|
+
});
|
|
755
|
+
window.addEventListener('message', selectionClearedCallback);
|
|
756
|
+
return {
|
|
757
|
+
unsubscribe: () => {
|
|
758
|
+
document.removeEventListener('click', clickCallback, {
|
|
759
|
+
capture: true
|
|
760
|
+
});
|
|
761
|
+
window.removeEventListener('message', selectionClearedCallback);
|
|
762
|
+
},
|
|
763
|
+
event: UVEEventType.CONTENTLET_CLICKED
|
|
764
|
+
};
|
|
765
|
+
}
|
|
553
766
|
|
|
554
767
|
/**
|
|
555
768
|
* Events that can be subscribed to in the UVE
|
|
@@ -564,17 +777,31 @@ const __UVE_EVENTS__ = {
|
|
|
564
777
|
[UVEEventType.PAGE_RELOAD]: callback => {
|
|
565
778
|
return onPageReload(callback);
|
|
566
779
|
},
|
|
567
|
-
[UVEEventType.REQUEST_BOUNDS]: callback => {
|
|
568
|
-
return onRequestBounds(callback);
|
|
569
|
-
},
|
|
570
780
|
[UVEEventType.IFRAME_SCROLL]: callback => {
|
|
571
781
|
return onIframeScroll(callback);
|
|
572
782
|
},
|
|
573
783
|
[UVEEventType.CONTENTLET_HOVERED]: callback => {
|
|
574
784
|
return onContentletHovered(callback);
|
|
575
785
|
},
|
|
786
|
+
[UVEEventType.CONTENTLET_CLICKED]: callback => {
|
|
787
|
+
return onContentletClicked(callback);
|
|
788
|
+
},
|
|
576
789
|
[UVEEventType.SCROLL_TO_SECTION]: callback => {
|
|
577
790
|
return onScrollToSection(callback);
|
|
791
|
+
},
|
|
792
|
+
// SELECTION_CLEARED is editor→SDK only. No public subscriber surface;
|
|
793
|
+
// onContentletClicked listens for the underlying postMessage internally
|
|
794
|
+
// to reset its lastSelectedInode tracker.
|
|
795
|
+
[UVEEventType.SELECTION_CLEARED]: _callback => {
|
|
796
|
+
return {
|
|
797
|
+
unsubscribe: () => {
|
|
798
|
+
/* no-op: SELECTION_CLEARED has no consumer-facing subscription */
|
|
799
|
+
},
|
|
800
|
+
event: UVEEventType.SELECTION_CLEARED
|
|
801
|
+
};
|
|
802
|
+
},
|
|
803
|
+
[UVEEventType.AUTO_BOUNDS]: callback => {
|
|
804
|
+
return onAutoBounds(callback);
|
|
578
805
|
}
|
|
579
806
|
};
|
|
580
807
|
/**
|
|
@@ -742,97 +969,6 @@ function createUVESubscription(eventType, callback) {
|
|
|
742
969
|
return eventCallback(callback);
|
|
743
970
|
}
|
|
744
971
|
|
|
745
|
-
/**
|
|
746
|
-
* Observes rendered document height changes and notifies the caller after layout settles.
|
|
747
|
-
*
|
|
748
|
-
* Uses ResizeObserver on <html> for layout/viewport-driven changes and MutationObserver
|
|
749
|
-
* on <body> to catch DOM additions/removals that may shrink the page without a resize.
|
|
750
|
-
* Measurement reads `body.offsetHeight`, which tracks actual content height and
|
|
751
|
-
* decreases correctly after DOM removals, unaffected by CSS min-height on the html element.
|
|
752
|
-
*/
|
|
753
|
-
function observeDocumentHeight({
|
|
754
|
-
onHeightChange,
|
|
755
|
-
documentRef = document,
|
|
756
|
-
windowRef = window,
|
|
757
|
-
debounceMs = 50
|
|
758
|
-
}) {
|
|
759
|
-
const html = documentRef.documentElement;
|
|
760
|
-
const body = documentRef.body;
|
|
761
|
-
let debounceTimer = null;
|
|
762
|
-
let rafOuter = null;
|
|
763
|
-
let rafInner = null;
|
|
764
|
-
let lastHeight = null;
|
|
765
|
-
let destroyed = false;
|
|
766
|
-
const measureAndNotify = () => {
|
|
767
|
-
const height = body.offsetHeight;
|
|
768
|
-
if (!height || height === lastHeight) {
|
|
769
|
-
return;
|
|
770
|
-
}
|
|
771
|
-
lastHeight = height;
|
|
772
|
-
onHeightChange(height);
|
|
773
|
-
};
|
|
774
|
-
const scheduleNotify = () => {
|
|
775
|
-
if (destroyed) {
|
|
776
|
-
return;
|
|
777
|
-
}
|
|
778
|
-
if (debounceTimer !== null) {
|
|
779
|
-
clearTimeout(debounceTimer);
|
|
780
|
-
}
|
|
781
|
-
debounceTimer = setTimeout(() => {
|
|
782
|
-
debounceTimer = null;
|
|
783
|
-
if (destroyed) {
|
|
784
|
-
return;
|
|
785
|
-
}
|
|
786
|
-
rafOuter = windowRef.requestAnimationFrame(() => {
|
|
787
|
-
rafOuter = null;
|
|
788
|
-
if (destroyed) {
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
rafInner = windowRef.requestAnimationFrame(() => {
|
|
792
|
-
rafInner = null;
|
|
793
|
-
if (!destroyed) {
|
|
794
|
-
measureAndNotify();
|
|
795
|
-
}
|
|
796
|
-
});
|
|
797
|
-
});
|
|
798
|
-
}, debounceMs);
|
|
799
|
-
};
|
|
800
|
-
const onLoad = () => scheduleNotify();
|
|
801
|
-
if (documentRef.readyState === 'complete' || documentRef.readyState === 'interactive') {
|
|
802
|
-
scheduleNotify();
|
|
803
|
-
} else {
|
|
804
|
-
windowRef.addEventListener('load', onLoad);
|
|
805
|
-
}
|
|
806
|
-
const resizeObserver = new ResizeObserver(() => scheduleNotify());
|
|
807
|
-
resizeObserver.observe(html);
|
|
808
|
-
const mutationObserver = new MutationObserver(() => scheduleNotify());
|
|
809
|
-
mutationObserver.observe(documentRef.body ?? html, {
|
|
810
|
-
childList: true,
|
|
811
|
-
subtree: true
|
|
812
|
-
});
|
|
813
|
-
return {
|
|
814
|
-
destroy: () => {
|
|
815
|
-
destroyed = true;
|
|
816
|
-
if (debounceTimer !== null) {
|
|
817
|
-
clearTimeout(debounceTimer);
|
|
818
|
-
debounceTimer = null;
|
|
819
|
-
}
|
|
820
|
-
if (rafOuter !== null) {
|
|
821
|
-
windowRef.cancelAnimationFrame(rafOuter);
|
|
822
|
-
rafOuter = null;
|
|
823
|
-
}
|
|
824
|
-
if (rafInner !== null) {
|
|
825
|
-
windowRef.cancelAnimationFrame(rafInner);
|
|
826
|
-
rafInner = null;
|
|
827
|
-
}
|
|
828
|
-
lastHeight = null;
|
|
829
|
-
resizeObserver.disconnect();
|
|
830
|
-
mutationObserver.disconnect();
|
|
831
|
-
windowRef.removeEventListener('load', onLoad);
|
|
832
|
-
}
|
|
833
|
-
};
|
|
834
|
-
}
|
|
835
|
-
|
|
836
972
|
/**
|
|
837
973
|
* Sets the bounds of the containers in the editor.
|
|
838
974
|
* Retrieves the containers from the DOM and sends their position data to the editor.
|
|
@@ -997,9 +1133,6 @@ function registerUVEEvents() {
|
|
|
997
1133
|
const pageReloadSubscription = createUVESubscription(UVEEventType.PAGE_RELOAD, () => {
|
|
998
1134
|
window.location.reload();
|
|
999
1135
|
});
|
|
1000
|
-
const requestBoundsSubscription = createUVESubscription(UVEEventType.REQUEST_BOUNDS, bounds => {
|
|
1001
|
-
setBounds(bounds);
|
|
1002
|
-
});
|
|
1003
1136
|
const iframeScrollSubscription = createUVESubscription(UVEEventType.IFRAME_SCROLL, direction => {
|
|
1004
1137
|
if (window.scrollY === 0 && direction === 'up' || computeScrollIsInBottom() && direction === 'down') {
|
|
1005
1138
|
// If the iframe scroll is at the top or bottom, do not send anything.
|
|
@@ -1019,14 +1152,30 @@ function registerUVEEvents() {
|
|
|
1019
1152
|
payload: contentletHovered
|
|
1020
1153
|
});
|
|
1021
1154
|
});
|
|
1155
|
+
const contentletClickedSubscription = createUVESubscription(UVEEventType.CONTENTLET_CLICKED, contentletClicked => {
|
|
1156
|
+
sendMessageToUVE({
|
|
1157
|
+
action: DotCMSUVEAction.SET_SELECTED_CONTENTLET,
|
|
1158
|
+
payload: contentletClicked
|
|
1159
|
+
});
|
|
1160
|
+
});
|
|
1022
1161
|
const scrollToSectionSubscription = createUVESubscription(UVEEventType.SCROLL_TO_SECTION, payload => {
|
|
1023
1162
|
sendMessageToUVE({
|
|
1024
1163
|
action: DotCMSUVEAction.SECTION_OFFSET,
|
|
1025
1164
|
payload
|
|
1026
1165
|
});
|
|
1027
1166
|
});
|
|
1167
|
+
// The single bounds-sync channel. The SDK observes layout changes
|
|
1168
|
+
// inside the iframe (media-query reflows, image/font load shifts,
|
|
1169
|
+
// container mount/unmount, scroll, etc.) and emits SET_BOUNDS on the
|
|
1170
|
+
// trailing edge of a debounce window. The editor can also send a
|
|
1171
|
+
// UVE_FLUSH_BOUNDS message to request an immediate synchronous emit
|
|
1172
|
+
// (used during drag/drop, where the dropzone needs current bounds
|
|
1173
|
+
// before the user moves another pixel).
|
|
1174
|
+
const autoBoundsSubscription = createUVESubscription(UVEEventType.AUTO_BOUNDS, bounds => {
|
|
1175
|
+
setBounds(bounds);
|
|
1176
|
+
});
|
|
1028
1177
|
return {
|
|
1029
|
-
subscriptions: [pageReloadSubscription,
|
|
1178
|
+
subscriptions: [pageReloadSubscription, iframeScrollSubscription, contentletHoveredSubscription, contentletClickedSubscription, scrollToSectionSubscription, autoBoundsSubscription]
|
|
1030
1179
|
};
|
|
1031
1180
|
}
|
|
1032
1181
|
/**
|
|
@@ -1069,60 +1218,6 @@ function listenBlockEditorInlineEvent() {
|
|
|
1069
1218
|
}
|
|
1070
1219
|
};
|
|
1071
1220
|
}
|
|
1072
|
-
/**
|
|
1073
|
-
* Returns whether iframe height must be synchronized via postMessage.
|
|
1074
|
-
*
|
|
1075
|
-
* Same-origin parents can measure iframe content directly, so they do not need
|
|
1076
|
-
* child-driven height reporting. Cross-origin parents cannot access the iframe
|
|
1077
|
-
* DOM, so they still need the reporter fallback.
|
|
1078
|
-
*/
|
|
1079
|
-
function shouldReportIframeHeightToParent() {
|
|
1080
|
-
if (window.parent === window) {
|
|
1081
|
-
return false;
|
|
1082
|
-
}
|
|
1083
|
-
try {
|
|
1084
|
-
const parentDocument = window.parent.document;
|
|
1085
|
-
return !parentDocument;
|
|
1086
|
-
} catch {
|
|
1087
|
-
return true;
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
/**
|
|
1091
|
-
* Reports the iframe document height to the parent UVE shell via postMessage.
|
|
1092
|
-
*
|
|
1093
|
-
* Uses ResizeObserver on <html> for viewport/font/image-driven size changes, and
|
|
1094
|
-
* MutationObserver on <body> to catch DOM removals (e.g. contentlets deleted by
|
|
1095
|
-
* the editor) that shrink the page without triggering a resize event.
|
|
1096
|
-
*
|
|
1097
|
-
* Measurement reads `document.documentElement.offsetHeight` — the actual rendered
|
|
1098
|
-
* height of the <html> element after layout. `scrollHeight` is intentionally avoided
|
|
1099
|
-
* because it does not reliably decrease when content is removed from the DOM.
|
|
1100
|
-
*
|
|
1101
|
-
* Height sends are coalesced to at most one per double-requestAnimationFrame pair
|
|
1102
|
-
* so they always run after layout and paint have settled.
|
|
1103
|
-
*
|
|
1104
|
-
* @returns {{ destroyHeightReporter: () => void }} Cleanup function that removes
|
|
1105
|
-
* all listeners and disconnects the observers.
|
|
1106
|
-
*/
|
|
1107
|
-
function reportIframeHeight() {
|
|
1108
|
-
const {
|
|
1109
|
-
destroy
|
|
1110
|
-
} = observeDocumentHeight({
|
|
1111
|
-
onHeightChange: height => {
|
|
1112
|
-
sendMessageToUVE({
|
|
1113
|
-
action: DotCMSUVEAction.IFRAME_HEIGHT,
|
|
1114
|
-
payload: {
|
|
1115
|
-
height
|
|
1116
|
-
}
|
|
1117
|
-
});
|
|
1118
|
-
}
|
|
1119
|
-
});
|
|
1120
|
-
return {
|
|
1121
|
-
destroyHeightReporter: () => {
|
|
1122
|
-
destroy();
|
|
1123
|
-
}
|
|
1124
|
-
};
|
|
1125
|
-
}
|
|
1126
1221
|
const listenBlockEditorClick = () => {
|
|
1127
1222
|
const editBlockEditorNodes = document.querySelectorAll('[data-block-editor-content]');
|
|
1128
1223
|
if (!editBlockEditorNodes.length) {
|
|
@@ -1328,19 +1423,13 @@ function initUVE(config = {}) {
|
|
|
1328
1423
|
const {
|
|
1329
1424
|
destroyListenBlockEditorInlineEvent
|
|
1330
1425
|
} = listenBlockEditorInlineEvent();
|
|
1331
|
-
const {
|
|
1332
|
-
destroyHeightReporter
|
|
1333
|
-
} = shouldReportIframeHeightToParent() ? reportIframeHeight() : {
|
|
1334
|
-
destroyHeightReporter: () => undefined
|
|
1335
|
-
};
|
|
1336
1426
|
return {
|
|
1337
1427
|
destroyUVESubscriptions: () => {
|
|
1338
1428
|
subscriptions.forEach(subscription => subscription.unsubscribe());
|
|
1339
1429
|
destroyScrollHandler();
|
|
1340
1430
|
destroyListenBlockEditorInlineEvent();
|
|
1341
|
-
destroyHeightReporter();
|
|
1342
1431
|
}
|
|
1343
1432
|
};
|
|
1344
1433
|
}
|
|
1345
1434
|
|
|
1346
|
-
export { getDotContainerAttributes as A, getDotContentletAttributes as B, CUSTOM_NO_COMPONENT as C, DEVELOPMENT_MODE as D, EMPTY_CONTAINER_STYLE_ANGULAR as E, isValidBlocks as F,
|
|
1435
|
+
export { getDotContainerAttributes as A, getDotContentletAttributes as B, CUSTOM_NO_COMPONENT as C, DEVELOPMENT_MODE as D, EMPTY_CONTAINER_STYLE_ANGULAR as E, isValidBlocks as F, readContentletDataset as G, setBounds as H, PRODUCTION_MODE as P, START_CLASS as S, TEMP_EMPTY_CONTENTLET as T, __UVE_EVENTS__ as _, createUVESubscription as a, enableBlockEditorInline as b, createContentlet as c, initUVE as d, editContentlet as e, DOT_SECTION_ID_PREFIX as f, getUVEState as g, EMPTY_CONTAINER_STYLE_REACT as h, initInlineEditing as i, END_CLASS as j, TEMP_EMPTY_CONTENTLET_TYPE as k, __UVE_EVENT_ERROR_FALLBACK__ as l, combineClasses as m, computeScrollIsInBottom as n, findDotCMSElement as o, findDotCMSVTLData as p, getClosestDotCMSContainerData as q, reorderMenu as r, sendMessageToUVE as s, getColumnPositionClasses as t, updateNavigation as u, getContainersData as v, getContentletsInContainer as w, getDotCMSContainerData as x, getDotCMSContentletsBound as y, getDotCMSPageBounds as z };
|
package/src/internal/events.d.ts
CHANGED
|
@@ -26,15 +26,22 @@ export declare function onPageReload(callback: UVEEventHandler): {
|
|
|
26
26
|
event: UVEEventType;
|
|
27
27
|
};
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
29
|
+
* The single bounds-sync channel. Observes the iframe document and
|
|
30
|
+
* every `[data-dot-object="container"]` with a single ResizeObserver,
|
|
31
|
+
* debounces the trailing edge by {@link AUTO_BOUNDS_DEBOUNCE_MS}ms, and
|
|
32
|
+
* emits the full `getDotCMSPageBounds(...)` payload whenever the layout
|
|
33
|
+
* settles. Also listens on `scroll` (since scrolling moves contentlets
|
|
34
|
+
* without changing layout) and on `UVE_FLUSH_BOUNDS` (the editor's
|
|
35
|
+
* "give me bounds NOW, skip the debounce" message used during drag).
|
|
36
|
+
*
|
|
37
|
+
* Re-runs `querySelectorAll` and the observer wiring whenever a
|
|
38
|
+
* MutationObserver detects child changes that touch container nodes,
|
|
39
|
+
* so containers that mount/unmount after page-load are picked up
|
|
40
|
+
* automatically.
|
|
30
41
|
*
|
|
31
|
-
* @param {UVEEventHandler} callback - Function to be called when bounds are requested
|
|
32
|
-
* @returns {Object} Object containing unsubscribe function and event type
|
|
33
|
-
* @returns {Function} .unsubscribe - Function to remove the event listener
|
|
34
|
-
* @returns {UVEEventType} .event - The event type being subscribed to
|
|
35
42
|
* @internal
|
|
36
43
|
*/
|
|
37
|
-
export declare function
|
|
44
|
+
export declare function onAutoBounds(callback: UVEEventHandler): {
|
|
38
45
|
unsubscribe: () => void;
|
|
39
46
|
event: UVEEventType;
|
|
40
47
|
};
|
|
@@ -66,9 +73,15 @@ export declare function onScrollToSection(callback: UVEEventHandler): {
|
|
|
66
73
|
event: UVEEventType;
|
|
67
74
|
};
|
|
68
75
|
/**
|
|
69
|
-
* Subscribes to contentlet hover events in the UVE editor
|
|
76
|
+
* Subscribes to contentlet hover events in the UVE editor.
|
|
70
77
|
*
|
|
71
|
-
*
|
|
78
|
+
* The callback is invoked with a payload while the pointer is over a
|
|
79
|
+
* DotCMS element, and once with `null` when the pointer leaves the last
|
|
80
|
+
* reported element (transitions onto dead space). The editor uses the
|
|
81
|
+
* `null` signal to clear the hover overlay so it doesn't linger over
|
|
82
|
+
* areas that no longer have a contentlet under the pointer.
|
|
83
|
+
*
|
|
84
|
+
* @param {UVEEventHandler} callback - Function to be called when hover state changes
|
|
72
85
|
* @returns {Object} Object containing unsubscribe function and event type
|
|
73
86
|
* @returns {Function} .unsubscribe - Function to remove the event listener
|
|
74
87
|
* @returns {UVEEventType} .event - The event type being subscribed to
|
|
@@ -78,3 +91,18 @@ export declare function onContentletHovered(callback: UVEEventHandler): {
|
|
|
78
91
|
unsubscribe: () => void;
|
|
79
92
|
event: UVEEventType;
|
|
80
93
|
};
|
|
94
|
+
/**
|
|
95
|
+
* Subscribes to contentlet click events in the UVE editor.
|
|
96
|
+
*
|
|
97
|
+
* The editor's hover overlay is `pointer-events: none` so wheel events pass
|
|
98
|
+
* through to the iframe. We detect the user's selection click here instead and
|
|
99
|
+
* post it back to the editor.
|
|
100
|
+
*
|
|
101
|
+
* @param {UVEEventHandler} callback - Function to be called when a contentlet is clicked
|
|
102
|
+
* @returns {Object} Object containing unsubscribe function and event type
|
|
103
|
+
* @internal
|
|
104
|
+
*/
|
|
105
|
+
export declare function onContentletClicked(callback: UVEEventHandler): {
|
|
106
|
+
unsubscribe: () => void;
|
|
107
|
+
event: UVEEventType;
|
|
108
|
+
};
|
package/src/internal.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export * from './internal/index';
|
|
2
2
|
export * from './lib/core/core.utils';
|
|
3
|
-
export * from './lib/dom/document-height-observer';
|
|
4
3
|
export * from './lib/dom/dom.utils';
|
|
5
4
|
export * from './lib/editor/internal';
|
|
6
5
|
export { defineStyleEditorSchema, normalizeForm, registerStyleEditorSchemas } from './lib/style-editor/internal';
|
|
@@ -203,3 +203,19 @@ export declare const getContentletsInContainer: (dotCMSPageAsset: DotCMSPageAsse
|
|
|
203
203
|
* // Returns: { 'data-dot-object': 'container', 'data-dot-identifier': 'cont1', ... }
|
|
204
204
|
*/
|
|
205
205
|
export declare function getDotContainerAttributes({ uuid, identifier, acceptTypes, maxContentlets }: EditableContainerData): DotContainerAttributes;
|
|
206
|
+
/**
|
|
207
|
+
* Read a contentlet's dataset attributes off a DOM element and return a
|
|
208
|
+
* normalized contentlet object. Mirrors the shape consumed by the editor's
|
|
209
|
+
* SET_BOUNDS and CONTENTLET_CLICKED events. Optionally parses the
|
|
210
|
+
* `dotStyleProperties` JSON when present.
|
|
211
|
+
*/
|
|
212
|
+
export declare function readContentletDataset(element: HTMLElement): {
|
|
213
|
+
dotStyleProperties?: any;
|
|
214
|
+
identifier: string | undefined;
|
|
215
|
+
title: string | undefined;
|
|
216
|
+
inode: string | undefined;
|
|
217
|
+
contentType: string | undefined;
|
|
218
|
+
baseType: string | undefined;
|
|
219
|
+
widgetTitle: string | undefined;
|
|
220
|
+
onNumberOfPages: string | undefined;
|
|
221
|
+
};
|
package/src/script/utils.d.ts
CHANGED
|
@@ -51,34 +51,6 @@ export declare function setClientIsReady(config?: DotCMSPageResponse): void;
|
|
|
51
51
|
export declare function listenBlockEditorInlineEvent(): {
|
|
52
52
|
destroyListenBlockEditorInlineEvent: () => void;
|
|
53
53
|
};
|
|
54
|
-
/**
|
|
55
|
-
* Returns whether iframe height must be synchronized via postMessage.
|
|
56
|
-
*
|
|
57
|
-
* Same-origin parents can measure iframe content directly, so they do not need
|
|
58
|
-
* child-driven height reporting. Cross-origin parents cannot access the iframe
|
|
59
|
-
* DOM, so they still need the reporter fallback.
|
|
60
|
-
*/
|
|
61
|
-
export declare function shouldReportIframeHeightToParent(): boolean;
|
|
62
|
-
/**
|
|
63
|
-
* Reports the iframe document height to the parent UVE shell via postMessage.
|
|
64
|
-
*
|
|
65
|
-
* Uses ResizeObserver on <html> for viewport/font/image-driven size changes, and
|
|
66
|
-
* MutationObserver on <body> to catch DOM removals (e.g. contentlets deleted by
|
|
67
|
-
* the editor) that shrink the page without triggering a resize event.
|
|
68
|
-
*
|
|
69
|
-
* Measurement reads `document.documentElement.offsetHeight` — the actual rendered
|
|
70
|
-
* height of the <html> element after layout. `scrollHeight` is intentionally avoided
|
|
71
|
-
* because it does not reliably decrease when content is removed from the DOM.
|
|
72
|
-
*
|
|
73
|
-
* Height sends are coalesced to at most one per double-requestAnimationFrame pair
|
|
74
|
-
* so they always run after layout and paint have settled.
|
|
75
|
-
*
|
|
76
|
-
* @returns {{ destroyHeightReporter: () => void }} Cleanup function that removes
|
|
77
|
-
* all listeners and disconnects the observers.
|
|
78
|
-
*/
|
|
79
|
-
export declare function reportIframeHeight(): {
|
|
80
|
-
destroyHeightReporter: () => void;
|
|
81
|
-
};
|
|
82
54
|
/**
|
|
83
55
|
* Injects UVE editor styles for empty containers and contentlets into the page.
|
|
84
56
|
* Provides visual placeholders so editors can identify and interact with empty areas.
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export interface ObserveDocumentHeightOptions {
|
|
2
|
-
onHeightChange: (height: number) => void;
|
|
3
|
-
documentRef?: Document;
|
|
4
|
-
windowRef?: Window;
|
|
5
|
-
debounceMs?: number;
|
|
6
|
-
}
|
|
7
|
-
export interface DocumentHeightObserverHandle {
|
|
8
|
-
destroy: () => void;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Observes rendered document height changes and notifies the caller after layout settles.
|
|
12
|
-
*
|
|
13
|
-
* Uses ResizeObserver on <html> for layout/viewport-driven changes and MutationObserver
|
|
14
|
-
* on <body> to catch DOM additions/removals that may shrink the page without a resize.
|
|
15
|
-
* Measurement reads `body.offsetHeight`, which tracks actual content height and
|
|
16
|
-
* decreases correctly after DOM removals, unaffected by CSS min-height on the html element.
|
|
17
|
-
*/
|
|
18
|
-
export declare function observeDocumentHeight({ onHeightChange, documentRef, windowRef, debounceMs }: ObserveDocumentHeightOptions): DocumentHeightObserverHandle;
|