@dotcms/uve 1.4.0-next.2 → 1.4.0-next.3
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 +2 -2
- package/internal.cjs.js +2 -0
- package/internal.esm.js +1 -1
- package/package.json +1 -1
- package/public.cjs.js +203 -1
- package/public.esm.js +202 -2
- package/src/internal/constants.d.ts +7 -0
- package/src/internal/events.d.ts +14 -0
- package/src/internal.d.ts +1 -0
- package/src/lib/dom/document-height-observer.d.ts +18 -0
- package/src/script/utils.d.ts +37 -0
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# dotCMS UVE SDK
|
|
2
2
|
|
|
3
|
-
The `@dotcms/uve` SDK adds live editing to your JavaScript app using the dotCMS Universal Visual Editor (UVE). It provides low-level tools that power our framework-specific SDKs, such as [
|
|
3
|
+
The `@dotcms/uve` SDK adds live editing to your JavaScript app using the dotCMS Universal Visual Editor (UVE). It provides low-level tools that power our framework-specific SDKs, such as [@dotcms/react](https://github.com/dotCMS/core/blob/main/core-web/libs/sdk/react/README.md) and [@dotcms/angular](https://github.com/dotCMS/core/blob/main/core-web/libs/sdk/angular/README.md).
|
|
4
4
|
|
|
5
|
-
> ⚠️ We **do not recommend using this SDK directly** for most use cases, you should use a [framework SDK that handles setup](#
|
|
5
|
+
> ⚠️ We **do not recommend using this SDK directly** for most use cases, you should use a [framework SDK that handles setup](#getting-started-recommended-examples), rendering, and event wiring for you.
|
|
6
6
|
|
|
7
7
|
With `@dotcms/uve`, framework SDKs are able to:
|
|
8
8
|
- Make pages and contentlets editable
|
package/internal.cjs.js
CHANGED
|
@@ -76,6 +76,7 @@ const __TINYMCE_PATH_ON_DOTCMS__ = '/ext/tinymcev7/tinymce.min.js';
|
|
|
76
76
|
|
|
77
77
|
exports.CUSTOM_NO_COMPONENT = _public.CUSTOM_NO_COMPONENT;
|
|
78
78
|
exports.DEVELOPMENT_MODE = _public.DEVELOPMENT_MODE;
|
|
79
|
+
exports.DOT_SECTION_ID_PREFIX = _public.DOT_SECTION_ID_PREFIX;
|
|
79
80
|
exports.EMPTY_CONTAINER_STYLE_ANGULAR = _public.EMPTY_CONTAINER_STYLE_ANGULAR;
|
|
80
81
|
exports.EMPTY_CONTAINER_STYLE_REACT = _public.EMPTY_CONTAINER_STYLE_REACT;
|
|
81
82
|
exports.END_CLASS = _public.END_CLASS;
|
|
@@ -99,6 +100,7 @@ exports.getDotContainerAttributes = _public.getDotContainerAttributes;
|
|
|
99
100
|
exports.getDotContentletAttributes = _public.getDotContentletAttributes;
|
|
100
101
|
exports.getUVEState = _public.getUVEState;
|
|
101
102
|
exports.isValidBlocks = _public.isValidBlocks;
|
|
103
|
+
exports.observeDocumentHeight = _public.observeDocumentHeight;
|
|
102
104
|
exports.setBounds = _public.setBounds;
|
|
103
105
|
exports.__BASE_TINYMCE_CONFIG_WITH_NO_DEFAULT__ = __BASE_TINYMCE_CONFIG_WITH_NO_DEFAULT__;
|
|
104
106
|
exports.__DEFAULT_TINYMCE_CONFIG__ = __DEFAULT_TINYMCE_CONFIG__;
|
package/internal.esm.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { C as CUSTOM_NO_COMPONENT, D as DEVELOPMENT_MODE, E as EMPTY_CONTAINER_STYLE_ANGULAR,
|
|
1
|
+
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, _ as __UVE_EVENTS__, k as __UVE_EVENT_ERROR_FALLBACK__, l as combineClasses, m as computeScrollIsInBottom, a as createUVESubscription, n as findDotCMSElement, o as findDotCMSVTLData, p as getClosestDotCMSContainerData, q as getColumnPositionClasses, t as getContainersData, v as getContentletsInContainer, w as getDotCMSContainerData, x as getDotCMSContentletsBound, y as getDotCMSPageBounds, z as getDotContainerAttributes, A as getDotContentletAttributes, g as getUVEState, B as isValidBlocks, F as observeDocumentHeight, G as setBounds } from './public.esm.js';
|
|
2
2
|
import '@dotcms/types';
|
|
3
3
|
import '@dotcms/types/internal';
|
|
4
4
|
|
package/package.json
CHANGED
package/public.cjs.js
CHANGED
|
@@ -438,6 +438,39 @@ function onIframeScroll(callback) {
|
|
|
438
438
|
event: types.UVEEventType.IFRAME_SCROLL
|
|
439
439
|
};
|
|
440
440
|
}
|
|
441
|
+
/**
|
|
442
|
+
* Listens for scroll-to-section requests from the UVE editor.
|
|
443
|
+
*
|
|
444
|
+
* Queries `#dot-section-{n}` first, then falls back to `#section-{n}`.
|
|
445
|
+
* If the element is found, calls the callback with `{ sectionIndex, offsetTop }`.
|
|
446
|
+
* If not found, the callback is not invoked.
|
|
447
|
+
*
|
|
448
|
+
* @param {UVEEventHandler} callback - Receives `{ sectionIndex: number; offsetTop: number }`.
|
|
449
|
+
* @internal
|
|
450
|
+
*/
|
|
451
|
+
function onScrollToSection(callback) {
|
|
452
|
+
const messageCallback = event => {
|
|
453
|
+
if (event.data.name !== internal.__DOTCMS_UVE_EVENT__.UVE_SCROLL_TO_SECTION) {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
const sectionIndex = event.data.sectionIndex;
|
|
457
|
+
const el = document.querySelector(`#${DOT_SECTION_ID_PREFIX}${sectionIndex}`) ?? document.querySelector(`#section-${sectionIndex}`);
|
|
458
|
+
if (!el) {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
callback({
|
|
462
|
+
sectionIndex,
|
|
463
|
+
offsetTop: el.offsetTop
|
|
464
|
+
});
|
|
465
|
+
};
|
|
466
|
+
window.addEventListener('message', messageCallback);
|
|
467
|
+
return {
|
|
468
|
+
unsubscribe: () => {
|
|
469
|
+
window.removeEventListener('message', messageCallback);
|
|
470
|
+
},
|
|
471
|
+
event: types.UVEEventType.SCROLL_TO_SECTION
|
|
472
|
+
};
|
|
473
|
+
}
|
|
441
474
|
/**
|
|
442
475
|
* Subscribes to contentlet hover events in the UVE editor
|
|
443
476
|
*
|
|
@@ -527,6 +560,9 @@ const __UVE_EVENTS__ = {
|
|
|
527
560
|
},
|
|
528
561
|
[types.UVEEventType.CONTENTLET_HOVERED]: callback => {
|
|
529
562
|
return onContentletHovered(callback);
|
|
563
|
+
},
|
|
564
|
+
[types.UVEEventType.SCROLL_TO_SECTION]: callback => {
|
|
565
|
+
return onScrollToSection(callback);
|
|
530
566
|
}
|
|
531
567
|
};
|
|
532
568
|
/**
|
|
@@ -601,6 +637,13 @@ const EMPTY_CONTAINER_STYLE_ANGULAR = {
|
|
|
601
637
|
* @internal
|
|
602
638
|
*/
|
|
603
639
|
const CUSTOM_NO_COMPONENT = 'CustomNoComponent';
|
|
640
|
+
/**
|
|
641
|
+
* ID prefix applied to page section wrappers for editor scroll-to-section support.
|
|
642
|
+
* Used by SDK row components and the UVE scroll event handler.
|
|
643
|
+
*
|
|
644
|
+
* @internal
|
|
645
|
+
*/
|
|
646
|
+
const DOT_SECTION_ID_PREFIX = 'dot-section-';
|
|
604
647
|
|
|
605
648
|
/**
|
|
606
649
|
* Gets the current state of the Universal Visual Editor (UVE).
|
|
@@ -687,6 +730,97 @@ function createUVESubscription(eventType, callback) {
|
|
|
687
730
|
return eventCallback(callback);
|
|
688
731
|
}
|
|
689
732
|
|
|
733
|
+
/**
|
|
734
|
+
* Observes rendered document height changes and notifies the caller after layout settles.
|
|
735
|
+
*
|
|
736
|
+
* Uses ResizeObserver on <html> for layout/viewport-driven changes and MutationObserver
|
|
737
|
+
* on <body> to catch DOM additions/removals that may shrink the page without a resize.
|
|
738
|
+
* Measurement reads `body.offsetHeight`, which tracks actual content height and
|
|
739
|
+
* decreases correctly after DOM removals, unaffected by CSS min-height on the html element.
|
|
740
|
+
*/
|
|
741
|
+
function observeDocumentHeight({
|
|
742
|
+
onHeightChange,
|
|
743
|
+
documentRef = document,
|
|
744
|
+
windowRef = window,
|
|
745
|
+
debounceMs = 50
|
|
746
|
+
}) {
|
|
747
|
+
const html = documentRef.documentElement;
|
|
748
|
+
const body = documentRef.body;
|
|
749
|
+
let debounceTimer = null;
|
|
750
|
+
let rafOuter = null;
|
|
751
|
+
let rafInner = null;
|
|
752
|
+
let lastHeight = null;
|
|
753
|
+
let destroyed = false;
|
|
754
|
+
const measureAndNotify = () => {
|
|
755
|
+
const height = body.offsetHeight;
|
|
756
|
+
if (!height || height === lastHeight) {
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
lastHeight = height;
|
|
760
|
+
onHeightChange(height);
|
|
761
|
+
};
|
|
762
|
+
const scheduleNotify = () => {
|
|
763
|
+
if (destroyed) {
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
if (debounceTimer !== null) {
|
|
767
|
+
clearTimeout(debounceTimer);
|
|
768
|
+
}
|
|
769
|
+
debounceTimer = setTimeout(() => {
|
|
770
|
+
debounceTimer = null;
|
|
771
|
+
if (destroyed) {
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
rafOuter = windowRef.requestAnimationFrame(() => {
|
|
775
|
+
rafOuter = null;
|
|
776
|
+
if (destroyed) {
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
rafInner = windowRef.requestAnimationFrame(() => {
|
|
780
|
+
rafInner = null;
|
|
781
|
+
if (!destroyed) {
|
|
782
|
+
measureAndNotify();
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
});
|
|
786
|
+
}, debounceMs);
|
|
787
|
+
};
|
|
788
|
+
const onLoad = () => scheduleNotify();
|
|
789
|
+
if (documentRef.readyState === 'complete' || documentRef.readyState === 'interactive') {
|
|
790
|
+
scheduleNotify();
|
|
791
|
+
} else {
|
|
792
|
+
windowRef.addEventListener('load', onLoad);
|
|
793
|
+
}
|
|
794
|
+
const resizeObserver = new ResizeObserver(() => scheduleNotify());
|
|
795
|
+
resizeObserver.observe(html);
|
|
796
|
+
const mutationObserver = new MutationObserver(() => scheduleNotify());
|
|
797
|
+
mutationObserver.observe(documentRef.body ?? html, {
|
|
798
|
+
childList: true,
|
|
799
|
+
subtree: true
|
|
800
|
+
});
|
|
801
|
+
return {
|
|
802
|
+
destroy: () => {
|
|
803
|
+
destroyed = true;
|
|
804
|
+
if (debounceTimer !== null) {
|
|
805
|
+
clearTimeout(debounceTimer);
|
|
806
|
+
debounceTimer = null;
|
|
807
|
+
}
|
|
808
|
+
if (rafOuter !== null) {
|
|
809
|
+
windowRef.cancelAnimationFrame(rafOuter);
|
|
810
|
+
rafOuter = null;
|
|
811
|
+
}
|
|
812
|
+
if (rafInner !== null) {
|
|
813
|
+
windowRef.cancelAnimationFrame(rafInner);
|
|
814
|
+
rafInner = null;
|
|
815
|
+
}
|
|
816
|
+
lastHeight = null;
|
|
817
|
+
resizeObserver.disconnect();
|
|
818
|
+
mutationObserver.disconnect();
|
|
819
|
+
windowRef.removeEventListener('load', onLoad);
|
|
820
|
+
}
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
|
|
690
824
|
/**
|
|
691
825
|
* Sets the bounds of the containers in the editor.
|
|
692
826
|
* Retrieves the containers from the DOM and sends their position data to the editor.
|
|
@@ -873,8 +1007,14 @@ function registerUVEEvents() {
|
|
|
873
1007
|
payload: contentletHovered
|
|
874
1008
|
});
|
|
875
1009
|
});
|
|
1010
|
+
const scrollToSectionSubscription = createUVESubscription(types.UVEEventType.SCROLL_TO_SECTION, payload => {
|
|
1011
|
+
sendMessageToUVE({
|
|
1012
|
+
action: types.DotCMSUVEAction.SECTION_OFFSET,
|
|
1013
|
+
payload
|
|
1014
|
+
});
|
|
1015
|
+
});
|
|
876
1016
|
return {
|
|
877
|
-
subscriptions: [pageReloadSubscription, requestBoundsSubscription, iframeScrollSubscription, contentletHoveredSubscription]
|
|
1017
|
+
subscriptions: [pageReloadSubscription, requestBoundsSubscription, iframeScrollSubscription, contentletHoveredSubscription, scrollToSectionSubscription]
|
|
878
1018
|
};
|
|
879
1019
|
}
|
|
880
1020
|
/**
|
|
@@ -917,6 +1057,60 @@ function listenBlockEditorInlineEvent() {
|
|
|
917
1057
|
}
|
|
918
1058
|
};
|
|
919
1059
|
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Returns whether iframe height must be synchronized via postMessage.
|
|
1062
|
+
*
|
|
1063
|
+
* Same-origin parents can measure iframe content directly, so they do not need
|
|
1064
|
+
* child-driven height reporting. Cross-origin parents cannot access the iframe
|
|
1065
|
+
* DOM, so they still need the reporter fallback.
|
|
1066
|
+
*/
|
|
1067
|
+
function shouldReportIframeHeightToParent() {
|
|
1068
|
+
if (window.parent === window) {
|
|
1069
|
+
return false;
|
|
1070
|
+
}
|
|
1071
|
+
try {
|
|
1072
|
+
const parentDocument = window.parent.document;
|
|
1073
|
+
return !parentDocument;
|
|
1074
|
+
} catch {
|
|
1075
|
+
return true;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
1079
|
+
* Reports the iframe document height to the parent UVE shell via postMessage.
|
|
1080
|
+
*
|
|
1081
|
+
* Uses ResizeObserver on <html> for viewport/font/image-driven size changes, and
|
|
1082
|
+
* MutationObserver on <body> to catch DOM removals (e.g. contentlets deleted by
|
|
1083
|
+
* the editor) that shrink the page without triggering a resize event.
|
|
1084
|
+
*
|
|
1085
|
+
* Measurement reads `document.documentElement.offsetHeight` — the actual rendered
|
|
1086
|
+
* height of the <html> element after layout. `scrollHeight` is intentionally avoided
|
|
1087
|
+
* because it does not reliably decrease when content is removed from the DOM.
|
|
1088
|
+
*
|
|
1089
|
+
* Height sends are coalesced to at most one per double-requestAnimationFrame pair
|
|
1090
|
+
* so they always run after layout and paint have settled.
|
|
1091
|
+
*
|
|
1092
|
+
* @returns {{ destroyHeightReporter: () => void }} Cleanup function that removes
|
|
1093
|
+
* all listeners and disconnects the observers.
|
|
1094
|
+
*/
|
|
1095
|
+
function reportIframeHeight() {
|
|
1096
|
+
const {
|
|
1097
|
+
destroy
|
|
1098
|
+
} = observeDocumentHeight({
|
|
1099
|
+
onHeightChange: height => {
|
|
1100
|
+
sendMessageToUVE({
|
|
1101
|
+
action: types.DotCMSUVEAction.IFRAME_HEIGHT,
|
|
1102
|
+
payload: {
|
|
1103
|
+
height
|
|
1104
|
+
}
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
return {
|
|
1109
|
+
destroyHeightReporter: () => {
|
|
1110
|
+
destroy();
|
|
1111
|
+
}
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
920
1114
|
const listenBlockEditorClick = () => {
|
|
921
1115
|
const editBlockEditorNodes = document.querySelectorAll('[data-block-editor-content]');
|
|
922
1116
|
if (!editBlockEditorNodes.length) {
|
|
@@ -1122,17 +1316,24 @@ function initUVE(config = {}) {
|
|
|
1122
1316
|
const {
|
|
1123
1317
|
destroyListenBlockEditorInlineEvent
|
|
1124
1318
|
} = listenBlockEditorInlineEvent();
|
|
1319
|
+
const {
|
|
1320
|
+
destroyHeightReporter
|
|
1321
|
+
} = shouldReportIframeHeightToParent() ? reportIframeHeight() : {
|
|
1322
|
+
destroyHeightReporter: () => undefined
|
|
1323
|
+
};
|
|
1125
1324
|
return {
|
|
1126
1325
|
destroyUVESubscriptions: () => {
|
|
1127
1326
|
subscriptions.forEach(subscription => subscription.unsubscribe());
|
|
1128
1327
|
destroyScrollHandler();
|
|
1129
1328
|
destroyListenBlockEditorInlineEvent();
|
|
1329
|
+
destroyHeightReporter();
|
|
1130
1330
|
}
|
|
1131
1331
|
};
|
|
1132
1332
|
}
|
|
1133
1333
|
|
|
1134
1334
|
exports.CUSTOM_NO_COMPONENT = CUSTOM_NO_COMPONENT;
|
|
1135
1335
|
exports.DEVELOPMENT_MODE = DEVELOPMENT_MODE;
|
|
1336
|
+
exports.DOT_SECTION_ID_PREFIX = DOT_SECTION_ID_PREFIX;
|
|
1136
1337
|
exports.EMPTY_CONTAINER_STYLE_ANGULAR = EMPTY_CONTAINER_STYLE_ANGULAR;
|
|
1137
1338
|
exports.EMPTY_CONTAINER_STYLE_REACT = EMPTY_CONTAINER_STYLE_REACT;
|
|
1138
1339
|
exports.END_CLASS = END_CLASS;
|
|
@@ -1161,6 +1362,7 @@ exports.getUVEState = getUVEState;
|
|
|
1161
1362
|
exports.initInlineEditing = initInlineEditing;
|
|
1162
1363
|
exports.initUVE = initUVE;
|
|
1163
1364
|
exports.isValidBlocks = isValidBlocks;
|
|
1365
|
+
exports.observeDocumentHeight = observeDocumentHeight;
|
|
1164
1366
|
exports.reorderMenu = reorderMenu;
|
|
1165
1367
|
exports.sendMessageToUVE = sendMessageToUVE;
|
|
1166
1368
|
exports.setBounds = setBounds;
|
package/public.esm.js
CHANGED
|
@@ -436,6 +436,39 @@ function onIframeScroll(callback) {
|
|
|
436
436
|
event: UVEEventType.IFRAME_SCROLL
|
|
437
437
|
};
|
|
438
438
|
}
|
|
439
|
+
/**
|
|
440
|
+
* Listens for scroll-to-section requests from the UVE editor.
|
|
441
|
+
*
|
|
442
|
+
* Queries `#dot-section-{n}` first, then falls back to `#section-{n}`.
|
|
443
|
+
* If the element is found, calls the callback with `{ sectionIndex, offsetTop }`.
|
|
444
|
+
* If not found, the callback is not invoked.
|
|
445
|
+
*
|
|
446
|
+
* @param {UVEEventHandler} callback - Receives `{ sectionIndex: number; offsetTop: number }`.
|
|
447
|
+
* @internal
|
|
448
|
+
*/
|
|
449
|
+
function onScrollToSection(callback) {
|
|
450
|
+
const messageCallback = event => {
|
|
451
|
+
if (event.data.name !== __DOTCMS_UVE_EVENT__.UVE_SCROLL_TO_SECTION) {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
const sectionIndex = event.data.sectionIndex;
|
|
455
|
+
const el = document.querySelector(`#${DOT_SECTION_ID_PREFIX}${sectionIndex}`) ?? document.querySelector(`#section-${sectionIndex}`);
|
|
456
|
+
if (!el) {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
callback({
|
|
460
|
+
sectionIndex,
|
|
461
|
+
offsetTop: el.offsetTop
|
|
462
|
+
});
|
|
463
|
+
};
|
|
464
|
+
window.addEventListener('message', messageCallback);
|
|
465
|
+
return {
|
|
466
|
+
unsubscribe: () => {
|
|
467
|
+
window.removeEventListener('message', messageCallback);
|
|
468
|
+
},
|
|
469
|
+
event: UVEEventType.SCROLL_TO_SECTION
|
|
470
|
+
};
|
|
471
|
+
}
|
|
439
472
|
/**
|
|
440
473
|
* Subscribes to contentlet hover events in the UVE editor
|
|
441
474
|
*
|
|
@@ -525,6 +558,9 @@ const __UVE_EVENTS__ = {
|
|
|
525
558
|
},
|
|
526
559
|
[UVEEventType.CONTENTLET_HOVERED]: callback => {
|
|
527
560
|
return onContentletHovered(callback);
|
|
561
|
+
},
|
|
562
|
+
[UVEEventType.SCROLL_TO_SECTION]: callback => {
|
|
563
|
+
return onScrollToSection(callback);
|
|
528
564
|
}
|
|
529
565
|
};
|
|
530
566
|
/**
|
|
@@ -599,6 +635,13 @@ const EMPTY_CONTAINER_STYLE_ANGULAR = {
|
|
|
599
635
|
* @internal
|
|
600
636
|
*/
|
|
601
637
|
const CUSTOM_NO_COMPONENT = 'CustomNoComponent';
|
|
638
|
+
/**
|
|
639
|
+
* ID prefix applied to page section wrappers for editor scroll-to-section support.
|
|
640
|
+
* Used by SDK row components and the UVE scroll event handler.
|
|
641
|
+
*
|
|
642
|
+
* @internal
|
|
643
|
+
*/
|
|
644
|
+
const DOT_SECTION_ID_PREFIX = 'dot-section-';
|
|
602
645
|
|
|
603
646
|
/**
|
|
604
647
|
* Gets the current state of the Universal Visual Editor (UVE).
|
|
@@ -685,6 +728,97 @@ function createUVESubscription(eventType, callback) {
|
|
|
685
728
|
return eventCallback(callback);
|
|
686
729
|
}
|
|
687
730
|
|
|
731
|
+
/**
|
|
732
|
+
* Observes rendered document height changes and notifies the caller after layout settles.
|
|
733
|
+
*
|
|
734
|
+
* Uses ResizeObserver on <html> for layout/viewport-driven changes and MutationObserver
|
|
735
|
+
* on <body> to catch DOM additions/removals that may shrink the page without a resize.
|
|
736
|
+
* Measurement reads `body.offsetHeight`, which tracks actual content height and
|
|
737
|
+
* decreases correctly after DOM removals, unaffected by CSS min-height on the html element.
|
|
738
|
+
*/
|
|
739
|
+
function observeDocumentHeight({
|
|
740
|
+
onHeightChange,
|
|
741
|
+
documentRef = document,
|
|
742
|
+
windowRef = window,
|
|
743
|
+
debounceMs = 50
|
|
744
|
+
}) {
|
|
745
|
+
const html = documentRef.documentElement;
|
|
746
|
+
const body = documentRef.body;
|
|
747
|
+
let debounceTimer = null;
|
|
748
|
+
let rafOuter = null;
|
|
749
|
+
let rafInner = null;
|
|
750
|
+
let lastHeight = null;
|
|
751
|
+
let destroyed = false;
|
|
752
|
+
const measureAndNotify = () => {
|
|
753
|
+
const height = body.offsetHeight;
|
|
754
|
+
if (!height || height === lastHeight) {
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
lastHeight = height;
|
|
758
|
+
onHeightChange(height);
|
|
759
|
+
};
|
|
760
|
+
const scheduleNotify = () => {
|
|
761
|
+
if (destroyed) {
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
if (debounceTimer !== null) {
|
|
765
|
+
clearTimeout(debounceTimer);
|
|
766
|
+
}
|
|
767
|
+
debounceTimer = setTimeout(() => {
|
|
768
|
+
debounceTimer = null;
|
|
769
|
+
if (destroyed) {
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
rafOuter = windowRef.requestAnimationFrame(() => {
|
|
773
|
+
rafOuter = null;
|
|
774
|
+
if (destroyed) {
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
rafInner = windowRef.requestAnimationFrame(() => {
|
|
778
|
+
rafInner = null;
|
|
779
|
+
if (!destroyed) {
|
|
780
|
+
measureAndNotify();
|
|
781
|
+
}
|
|
782
|
+
});
|
|
783
|
+
});
|
|
784
|
+
}, debounceMs);
|
|
785
|
+
};
|
|
786
|
+
const onLoad = () => scheduleNotify();
|
|
787
|
+
if (documentRef.readyState === 'complete' || documentRef.readyState === 'interactive') {
|
|
788
|
+
scheduleNotify();
|
|
789
|
+
} else {
|
|
790
|
+
windowRef.addEventListener('load', onLoad);
|
|
791
|
+
}
|
|
792
|
+
const resizeObserver = new ResizeObserver(() => scheduleNotify());
|
|
793
|
+
resizeObserver.observe(html);
|
|
794
|
+
const mutationObserver = new MutationObserver(() => scheduleNotify());
|
|
795
|
+
mutationObserver.observe(documentRef.body ?? html, {
|
|
796
|
+
childList: true,
|
|
797
|
+
subtree: true
|
|
798
|
+
});
|
|
799
|
+
return {
|
|
800
|
+
destroy: () => {
|
|
801
|
+
destroyed = true;
|
|
802
|
+
if (debounceTimer !== null) {
|
|
803
|
+
clearTimeout(debounceTimer);
|
|
804
|
+
debounceTimer = null;
|
|
805
|
+
}
|
|
806
|
+
if (rafOuter !== null) {
|
|
807
|
+
windowRef.cancelAnimationFrame(rafOuter);
|
|
808
|
+
rafOuter = null;
|
|
809
|
+
}
|
|
810
|
+
if (rafInner !== null) {
|
|
811
|
+
windowRef.cancelAnimationFrame(rafInner);
|
|
812
|
+
rafInner = null;
|
|
813
|
+
}
|
|
814
|
+
lastHeight = null;
|
|
815
|
+
resizeObserver.disconnect();
|
|
816
|
+
mutationObserver.disconnect();
|
|
817
|
+
windowRef.removeEventListener('load', onLoad);
|
|
818
|
+
}
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
|
|
688
822
|
/**
|
|
689
823
|
* Sets the bounds of the containers in the editor.
|
|
690
824
|
* Retrieves the containers from the DOM and sends their position data to the editor.
|
|
@@ -871,8 +1005,14 @@ function registerUVEEvents() {
|
|
|
871
1005
|
payload: contentletHovered
|
|
872
1006
|
});
|
|
873
1007
|
});
|
|
1008
|
+
const scrollToSectionSubscription = createUVESubscription(UVEEventType.SCROLL_TO_SECTION, payload => {
|
|
1009
|
+
sendMessageToUVE({
|
|
1010
|
+
action: DotCMSUVEAction.SECTION_OFFSET,
|
|
1011
|
+
payload
|
|
1012
|
+
});
|
|
1013
|
+
});
|
|
874
1014
|
return {
|
|
875
|
-
subscriptions: [pageReloadSubscription, requestBoundsSubscription, iframeScrollSubscription, contentletHoveredSubscription]
|
|
1015
|
+
subscriptions: [pageReloadSubscription, requestBoundsSubscription, iframeScrollSubscription, contentletHoveredSubscription, scrollToSectionSubscription]
|
|
876
1016
|
};
|
|
877
1017
|
}
|
|
878
1018
|
/**
|
|
@@ -915,6 +1055,60 @@ function listenBlockEditorInlineEvent() {
|
|
|
915
1055
|
}
|
|
916
1056
|
};
|
|
917
1057
|
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Returns whether iframe height must be synchronized via postMessage.
|
|
1060
|
+
*
|
|
1061
|
+
* Same-origin parents can measure iframe content directly, so they do not need
|
|
1062
|
+
* child-driven height reporting. Cross-origin parents cannot access the iframe
|
|
1063
|
+
* DOM, so they still need the reporter fallback.
|
|
1064
|
+
*/
|
|
1065
|
+
function shouldReportIframeHeightToParent() {
|
|
1066
|
+
if (window.parent === window) {
|
|
1067
|
+
return false;
|
|
1068
|
+
}
|
|
1069
|
+
try {
|
|
1070
|
+
const parentDocument = window.parent.document;
|
|
1071
|
+
return !parentDocument;
|
|
1072
|
+
} catch {
|
|
1073
|
+
return true;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Reports the iframe document height to the parent UVE shell via postMessage.
|
|
1078
|
+
*
|
|
1079
|
+
* Uses ResizeObserver on <html> for viewport/font/image-driven size changes, and
|
|
1080
|
+
* MutationObserver on <body> to catch DOM removals (e.g. contentlets deleted by
|
|
1081
|
+
* the editor) that shrink the page without triggering a resize event.
|
|
1082
|
+
*
|
|
1083
|
+
* Measurement reads `document.documentElement.offsetHeight` — the actual rendered
|
|
1084
|
+
* height of the <html> element after layout. `scrollHeight` is intentionally avoided
|
|
1085
|
+
* because it does not reliably decrease when content is removed from the DOM.
|
|
1086
|
+
*
|
|
1087
|
+
* Height sends are coalesced to at most one per double-requestAnimationFrame pair
|
|
1088
|
+
* so they always run after layout and paint have settled.
|
|
1089
|
+
*
|
|
1090
|
+
* @returns {{ destroyHeightReporter: () => void }} Cleanup function that removes
|
|
1091
|
+
* all listeners and disconnects the observers.
|
|
1092
|
+
*/
|
|
1093
|
+
function reportIframeHeight() {
|
|
1094
|
+
const {
|
|
1095
|
+
destroy
|
|
1096
|
+
} = observeDocumentHeight({
|
|
1097
|
+
onHeightChange: height => {
|
|
1098
|
+
sendMessageToUVE({
|
|
1099
|
+
action: DotCMSUVEAction.IFRAME_HEIGHT,
|
|
1100
|
+
payload: {
|
|
1101
|
+
height
|
|
1102
|
+
}
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
});
|
|
1106
|
+
return {
|
|
1107
|
+
destroyHeightReporter: () => {
|
|
1108
|
+
destroy();
|
|
1109
|
+
}
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
918
1112
|
const listenBlockEditorClick = () => {
|
|
919
1113
|
const editBlockEditorNodes = document.querySelectorAll('[data-block-editor-content]');
|
|
920
1114
|
if (!editBlockEditorNodes.length) {
|
|
@@ -1120,13 +1314,19 @@ function initUVE(config = {}) {
|
|
|
1120
1314
|
const {
|
|
1121
1315
|
destroyListenBlockEditorInlineEvent
|
|
1122
1316
|
} = listenBlockEditorInlineEvent();
|
|
1317
|
+
const {
|
|
1318
|
+
destroyHeightReporter
|
|
1319
|
+
} = shouldReportIframeHeightToParent() ? reportIframeHeight() : {
|
|
1320
|
+
destroyHeightReporter: () => undefined
|
|
1321
|
+
};
|
|
1123
1322
|
return {
|
|
1124
1323
|
destroyUVESubscriptions: () => {
|
|
1125
1324
|
subscriptions.forEach(subscription => subscription.unsubscribe());
|
|
1126
1325
|
destroyScrollHandler();
|
|
1127
1326
|
destroyListenBlockEditorInlineEvent();
|
|
1327
|
+
destroyHeightReporter();
|
|
1128
1328
|
}
|
|
1129
1329
|
};
|
|
1130
1330
|
}
|
|
1131
1331
|
|
|
1132
|
-
export {
|
|
1332
|
+
export { getDotContentletAttributes as A, isValidBlocks as B, CUSTOM_NO_COMPONENT as C, DEVELOPMENT_MODE as D, EMPTY_CONTAINER_STYLE_ANGULAR as E, observeDocumentHeight as F, setBounds as G, PRODUCTION_MODE as P, START_CLASS as S, __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, __UVE_EVENT_ERROR_FALLBACK__ as k, combineClasses as l, computeScrollIsInBottom as m, findDotCMSElement as n, findDotCMSVTLData as o, getClosestDotCMSContainerData as p, getColumnPositionClasses as q, reorderMenu as r, sendMessageToUVE as s, getContainersData as t, updateNavigation as u, getContentletsInContainer as v, getDotCMSContainerData as w, getDotCMSContentletsBound as x, getDotCMSPageBounds as y, getDotContainerAttributes as z };
|
|
@@ -74,3 +74,10 @@ export declare const EMPTY_CONTAINER_STYLE_ANGULAR: {
|
|
|
74
74
|
* @internal
|
|
75
75
|
*/
|
|
76
76
|
export declare const CUSTOM_NO_COMPONENT = "CustomNoComponent";
|
|
77
|
+
/**
|
|
78
|
+
* ID prefix applied to page section wrappers for editor scroll-to-section support.
|
|
79
|
+
* Used by SDK row components and the UVE scroll event handler.
|
|
80
|
+
*
|
|
81
|
+
* @internal
|
|
82
|
+
*/
|
|
83
|
+
export declare const DOT_SECTION_ID_PREFIX = "dot-section-";
|
package/src/internal/events.d.ts
CHANGED
|
@@ -51,6 +51,20 @@ export declare function onIframeScroll(callback: UVEEventHandler): {
|
|
|
51
51
|
unsubscribe: () => void;
|
|
52
52
|
event: UVEEventType;
|
|
53
53
|
};
|
|
54
|
+
/**
|
|
55
|
+
* Listens for scroll-to-section requests from the UVE editor.
|
|
56
|
+
*
|
|
57
|
+
* Queries `#dot-section-{n}` first, then falls back to `#section-{n}`.
|
|
58
|
+
* If the element is found, calls the callback with `{ sectionIndex, offsetTop }`.
|
|
59
|
+
* If not found, the callback is not invoked.
|
|
60
|
+
*
|
|
61
|
+
* @param {UVEEventHandler} callback - Receives `{ sectionIndex: number; offsetTop: number }`.
|
|
62
|
+
* @internal
|
|
63
|
+
*/
|
|
64
|
+
export declare function onScrollToSection(callback: UVEEventHandler): {
|
|
65
|
+
unsubscribe: () => void;
|
|
66
|
+
event: UVEEventType;
|
|
67
|
+
};
|
|
54
68
|
/**
|
|
55
69
|
* Subscribes to contentlet hover events in the UVE editor
|
|
56
70
|
*
|
package/src/internal.d.ts
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
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;
|
package/src/script/utils.d.ts
CHANGED
|
@@ -51,3 +51,40 @@ 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
|
+
/**
|
|
83
|
+
* Injects UVE editor styles for empty containers and contentlets into the page.
|
|
84
|
+
* Provides visual placeholders so editors can identify and interact with empty areas.
|
|
85
|
+
*
|
|
86
|
+
* The empty-container label is read from the dotCMS i18n cache in localStorage
|
|
87
|
+
* (`dotMessagesKeys`). Falls back to 'Empty container' if the cache is unavailable
|
|
88
|
+
* (e.g. headless pages on a different origin).
|
|
89
|
+
*/
|
|
90
|
+
export declare function injectEmptyStateStyles(): void;
|