@dotcms/uve 1.5.6 → 1.6.0-next.36

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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
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](#getting-started-recommended-examples), rendering, and event wiring for you.
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
@@ -17,11 +17,11 @@ With `@dotcms/uve`, framework SDKs are able to:
17
17
  - [🚩 Custom Setup: Manual Rendering (Not Recommended)](#-custom-setup-manual-rendering-not-recommended)
18
18
  - [Prerequisites & Setup](#prerequisites--setup)
19
19
  - [Get a dotCMS Environment](#get-a-dotcms-environment)
20
- - [Create a dotCMS API Key](#create-a-dotcms-api-key)
20
+ - [Configure The Universal Visual Editor App](#configure-the-universal-visual-editor-app)
21
21
  - [Installation](#installation)
22
22
  - [Using the SDK with TypeScript](#using-the-sdk-with-typescript)
23
23
  - [SDK Reference](#sdk-reference)
24
- - [`initUVE()`](#inituveconfig-dotcmsuveconfig)
24
+ - [`initUVE()`](#inituveconfig-dotcmspageresponse)
25
25
  - [`getUVEState()`](#getuvestate)
26
26
  - [`createUVESubscription()`](#createuvesubscriptioneventtype-callback)
27
27
  - [`editContentlet()`](#editcontentletcontentlet)
@@ -49,7 +49,7 @@ With `@dotcms/uve`, framework SDKs are able to:
49
49
  We strongly recommend using one of our official framework SDKs, which are designed to handle UVE integration, routing, rendering, and more—out of the box. These examples are the best way to get started:
50
50
 
51
51
  - [dotCMS Angular SDK: Angular Example](https://github.com/dotCMS/core/tree/main/examples/angular) – Ideal for Angular apps 🅰️
52
- - [dotCMS React SDK: NextJS Example](https://github.com/dotCMS/core/tree/main/examples/react) – Ideal for NextJS projects ⚛️
52
+ - [dotCMS React SDK: NextJS Example](https://github.com/dotCMS/core/tree/main/examples/nextjs) – Ideal for NextJS projects ⚛️
53
53
  - [dotCMS React SDK: Astro Example](https://github.com/dotCMS/core/tree/main/examples/astro) – Ideal for Astro projects 🌌
54
54
 
55
55
  These examples handle UVE integration, routing, rendering, and more—out of the box. **If you're building a headless dotCMS front-end, start there.**
@@ -66,7 +66,7 @@ You can use `@dotcms/uve` directly, but **it’s not recommended or supported**
66
66
 
67
67
  Here's a minimal setup using `@dotcms/client` and `@dotcms/uve`:
68
68
 
69
- 1. Initializa the Client and get the page response:
69
+ 1. Initialize the Client and get the page response:
70
70
 
71
71
  ```ts
72
72
  // getPage.ts
@@ -94,12 +94,13 @@ const getPage = async () => {
94
94
 
95
95
  ```ts
96
96
  import { initUVE, createUVESubscription } from '@dotcms/uve';
97
+ import { UVEEventType } from '@dotcms/types';
97
98
  import { getPage } from './getPage';
98
99
 
99
100
  const pageResponse = await getPage();
100
101
 
101
102
  initUVE(pageResponse);
102
- createUVESubscription('changes', (newPageResponse) => {
103
+ createUVESubscription(UVEEventType.CONTENT_CHANGES, (newPageResponse) => {
103
104
  // Handle page updates (e.g. re-render)
104
105
  });
105
106
  ```
@@ -118,7 +119,7 @@ createUVESubscription('changes', (newPageResponse) => {
118
119
  #### 🔄 How to Render a dotCMS Page
119
120
 
120
121
  > 📚 For a complete guide, here is a full tutorial:
121
- > 👉 [dotCMS Page Rendering Architecture]( https://dev.dotcms.com/docs/dotcms-page-rendering-architecture)
122
+ > 👉 [dotCMS Page Rendering Architecture](https://dev.dotcms.com/docs/dotcms-page-rendering-architecture)
122
123
 
123
124
  dotCMS pages are structured as nested layout objects:
124
125
 
@@ -218,7 +219,6 @@ The SDK uses several key types from `@dotcms/types`:
218
219
  import {
219
220
  DotCMSBasicContentlet,
220
221
  DotCMSPageResponse,
221
- DotCMSUVEConfig,
222
222
  DotCMSInlineEditingType,
223
223
  UVEEventType,
224
224
  UVEState
@@ -229,7 +229,7 @@ For a complete reference of all available types and interfaces, please refer to
229
229
 
230
230
  ## SDK Reference
231
231
 
232
- ### `initUVE(config?: DotCMSUVEConfig)`
232
+ ### `initUVE(config?: DotCMSPageResponse)`
233
233
 
234
234
  `initUVE` is a function that initializes the Universal Visual Editor (UVE). It sets up the necessary communication between your app and the editor, enabling seamless integration and interaction.
235
235
 
@@ -243,7 +243,7 @@ For a complete reference of all available types and interfaces, please refer to
243
243
  const { destroyUVESubscriptions } = initUVE(pageResponse);
244
244
  ```
245
245
 
246
- > ⚠️ If you don't provide a `pageResponse`, we can't assure that the UVE will be initialized correctly.
246
+ > ⚠️ If you don't provide a `pageResponse`, we cannot guarantee that the UVE will be initialized correctly.
247
247
 
248
248
  ### `getUVEState()`
249
249
 
@@ -303,14 +303,16 @@ sub.unsubscribe();
303
303
 
304
304
  - `UVEEventType.CONTENT_CHANGES`: Triggered when the content of the page changes.
305
305
  - `UVEEventType.PAGE_RELOAD`: Triggered when the page is reloaded.
306
- - `UVEEventType.REQUEST_BOUNDS`: Triggered when the editor requests the bounds of the page.
307
- - `UVEEventType.IFRAME_SCROLL`: Triggered when the iframe is scrolled.
308
- - `UVEEventType.IFRAME_SCROLL_END`: Triggered when the iframe has stopped scrolling.
306
+ - `UVEEventType.IFRAME_SCROLL`: Triggered when scroll action is needed inside the iframe (`'up'` or `'down'`).
309
307
  - `UVEEventType.CONTENTLET_HOVERED`: Triggered when a contentlet is hovered.
308
+ - `UVEEventType.CONTENTLET_CLICKED`: Triggered when a contentlet is clicked.
309
+ - `UVEEventType.AUTO_BOUNDS`: Triggered when the SDK syncs page bounds after layout changes (internal SDK use).
310
+ - `UVEEventType.SCROLL_TO_SECTION`: Triggered when the editor requests a scroll to a specific page section (internal).
311
+ - `UVEEventType.SELECTION_CLEARED`: Triggered when the editor clears its selection (internal).
310
312
 
311
313
  ### `editContentlet(contentlet)`
312
314
 
313
- `editContentlet` is a function that opens the dotCMS modal editor for any contentlet in or out of page area.
315
+ `editContentlet` is a function that opens the dotCMS modal editor for any contentlet in or out of the page area.
314
316
 
315
317
  | Input | Type | Required | Description |
316
318
  | ------------ | ------------------ | -------- | -------------------------------- |
@@ -340,7 +342,7 @@ const myEditButton = ({ contentlet }) => {
340
342
  | Input | Type | Required | Description |
341
343
  | ----------- | ---------------------------- | -------- | ------------------------------------------------------------------------------ |
342
344
  | `type` | `DotCMSInlineEditingType` | ✅ | `'BLOCK_EDITOR'` or `'WYSIWYG'` |
343
- | `fieldData` | `DotCMSInlineEditingPayload` | ✅ | [Field content required to enable inline editing](#dotcmsinlineeditingpayload) |
345
+ | `data` | `DotCMSInlineEditingPayload` | ✅ | [Field content required to enable inline editing](#dotcmsinlineeditingpayload) |
344
346
 
345
347
  #### Usage
346
348
 
@@ -449,7 +451,7 @@ reorderMenu({ startLevel: 2, depth: 3 });
449
451
 
450
452
  | Input | Type | Required | Description |
451
453
  | --------- | ------------------------------------------ | -------- | ---------------------------- |
452
- | `message` | [`DotCMSUVEMessage<T>`](#dotcmsuvemessage) | ✅ | Object with action + payload |
454
+ | `message` | [`DotCMSUVEMessage<T>`](#dotcmsuvemessaget) | ✅ | Object with action + payload |
453
455
 
454
456
  #### Usage
455
457
 
@@ -488,7 +490,7 @@ Style editor schemas are configured in the DotCMS admin UI under **Content Types
488
490
  **Key Benefits:**
489
491
 
490
492
  - **Real-Time Visual Editing**: Modify component styles and see changes instantly in the editor
491
- - **Content-Specific Customization**: Different content types can have unique style schemas, and the same contentlet could have different styles depending on if it is located in a different container or page
493
+ - **Content-Specific Customization**: Different content types can have unique style schemas, and the same contentlet could have different styles depending on whether it is located in a different container or page
492
494
  - **Admin-Managed**: Style schemas are defined in the DotCMS admin UI under Content Types and fetched automatically by the SDK
493
495
 
494
496
  **Use Cases:**
@@ -627,7 +629,7 @@ When **defining styles** for a contentlet within a page using **Style Editor**,
627
629
 
628
630
  > **NOTE:** (🎨 Styles are different) means the capability to define distinct styles, even when utilizing the identical Contentlet.
629
631
 
630
- The only known limitation is that moving a contentlet with defined styles between different container types (5th scenario), results in the loss of those styles. See the [technical details document](https://docs.google.com/document/d/1UiuJlIn8ZjybIB-0oHeoTLXZo1k-YExITEqEMfyvVlU/edit?tab=t.0) for our planned solution.
632
+ The only known limitation is that moving a contentlet with defined styles between different container types (5th scenario) results in the loss of those styles. A fix for this scenario is on our roadmap.
631
633
 
632
634
  ## Troubleshooting
633
635
 
@@ -713,7 +715,7 @@ We offer multiple channels to get help with the dotCMS UVE SDK:
713
715
  - **GitHub Issues**: For bug reports and feature requests, please [open an issue](https://github.com/dotCMS/core/issues/new/choose) in the GitHub repository
714
716
  - **Community Forum**: Join our [community discussions](https://community.dotcms.com/) to ask questions and share solutions
715
717
  - **Stack Overflow**: Use the tag `dotcms-uve` when posting questions
716
- - **Enterprise Support**: Enterprise customers can access premium support through the [dotCMS Support Portal](https://helpdesk.dotcms.com/support/)
718
+ - **Enterprise Support**: Enterprise customers can access premium support through the [dotCMS Support Portal](https://www.dotcms.com/support)
717
719
 
718
720
  When reporting issues, please include:
719
721
 
package/index.esm.js CHANGED
@@ -1,3 +1,3 @@
1
- export { c as createContentlet, a as createUVESubscription, e as editContentlet, b as enableBlockEditorInline, g as getUVEState, i as initInlineEditing, d as initUVE, r as reorderMenu, s as sendMessageToUVE, u as updateNavigation } from './public.esm.js';
1
+ export { b as createContentlet, c as createUVESubscription, e as editContentlet, a as enableBlockEditorInline, g as getUVEState, i as initInlineEditing, d as initUVE, r as reorderMenu, s as sendMessageToUVE, u as updateNavigation } from './public.esm.js';
2
2
  import '@dotcms/types';
3
3
  import '@dotcms/types/internal';
package/internal.cjs.js CHANGED
@@ -370,6 +370,8 @@ const styleEditorField = {
370
370
  })
371
371
  };
372
372
 
373
+ exports.ANALYTICS_ACTIVE_WINDOW_KEY = _public.ANALYTICS_ACTIVE_WINDOW_KEY;
374
+ exports.ANALYTICS_READY_EVENT = _public.ANALYTICS_READY_EVENT;
373
375
  exports.CUSTOM_NO_COMPONENT = _public.CUSTOM_NO_COMPONENT;
374
376
  exports.DEVELOPMENT_MODE = _public.DEVELOPMENT_MODE;
375
377
  exports.DOT_SECTION_ID_PREFIX = _public.DOT_SECTION_ID_PREFIX;
@@ -387,6 +389,7 @@ exports.computeScrollIsInBottom = _public.computeScrollIsInBottom;
387
389
  exports.createUVESubscription = _public.createUVESubscription;
388
390
  exports.findDotCMSElement = _public.findDotCMSElement;
389
391
  exports.findDotCMSVTLData = _public.findDotCMSVTLData;
392
+ exports.getAnalyticsContentletAttributes = _public.getAnalyticsContentletAttributes;
390
393
  exports.getClosestDotCMSContainerData = _public.getClosestDotCMSContainerData;
391
394
  exports.getColumnPositionClasses = _public.getColumnPositionClasses;
392
395
  exports.getContainersData = _public.getContainersData;
@@ -396,7 +399,9 @@ exports.getDotCMSContentletsBound = _public.getDotCMSContentletsBound;
396
399
  exports.getDotCMSPageBounds = _public.getDotCMSPageBounds;
397
400
  exports.getDotContainerAttributes = _public.getDotContainerAttributes;
398
401
  exports.getDotContentletAttributes = _public.getDotContentletAttributes;
402
+ exports.getNativeEventBinder = _public.getNativeEventBinder;
399
403
  exports.getUVEState = _public.getUVEState;
404
+ exports.isDotAnalyticsActive = _public.isDotAnalyticsActive;
400
405
  exports.isValidBlocks = _public.isValidBlocks;
401
406
  exports.readContentletDataset = _public.readContentletDataset;
402
407
  exports.setBounds = _public.setBounds;
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 readContentletDataset, H as setBounds } from './public.esm.js';
2
+ export { A as ANALYTICS_ACTIVE_WINDOW_KEY, l as ANALYTICS_READY_EVENT, C as CUSTOM_NO_COMPONENT, D as DEVELOPMENT_MODE, k as DOT_SECTION_ID_PREFIX, j as EMPTY_CONTAINER_STYLE_ANGULAR, h as EMPTY_CONTAINER_STYLE_REACT, E as END_CLASS, P as PRODUCTION_MODE, S as START_CLASS, T as TEMP_EMPTY_CONTENTLET, m as TEMP_EMPTY_CONTENTLET_TYPE, _ as __UVE_EVENTS__, f as __UVE_EVENT_ERROR_FALLBACK__, x as combineClasses, w as computeScrollIsInBottom, c as createUVESubscription, t as findDotCMSElement, v as findDotCMSVTLData, B as getAnalyticsContentletAttributes, q as getClosestDotCMSContainerData, y as getColumnPositionClasses, G as getContainersData, H as getContentletsInContainer, p as getDotCMSContainerData, o as getDotCMSContentletsBound, n as getDotCMSPageBounds, I as getDotContainerAttributes, z as getDotContentletAttributes, K as getNativeEventBinder, F as isDotAnalyticsActive, M as isValidBlocks, J as readContentletDataset, L 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotcms/uve",
3
- "version": "1.5.6",
3
+ "version": "1.6.0-next.36",
4
4
  "description": "Official JavaScript library for interacting with Universal Visual Editor (UVE)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -50,4 +50,4 @@
50
50
  "module": "./index.esm.js",
51
51
  "main": "./index.cjs.js",
52
52
  "types": "./index.d.ts"
53
- }
53
+ }
package/public.cjs.js CHANGED
@@ -257,6 +257,38 @@ function getDotContentletAttributes(contentlet, container) {
257
257
  })
258
258
  };
259
259
  }
260
+ /**
261
+ *
262
+ * Returns the minimal set of contentlet data attributes required by DotCMS
263
+ * Analytics (impression & click tracking) to identify a contentlet.
264
+ *
265
+ * Used in live mode where the full editor metadata is stripped but Analytics
266
+ * still needs to resolve the contentlet behind an impression/click.
267
+ *
268
+ * @param {DotCMSBasicContentlet} contentlet - The contentlet to get the attributes for
269
+ * @returns {DotAnalyticsContentletAttributes} The Analytics-required data attributes
270
+ */
271
+ function getAnalyticsContentletAttributes(contentlet) {
272
+ return {
273
+ 'data-dot-identifier': contentlet?.identifier,
274
+ 'data-dot-inode': contentlet?.inode,
275
+ 'data-dot-title': contentlet?.['widgetTitle'] || contentlet?.title,
276
+ 'data-dot-type': contentlet?.contentType,
277
+ 'data-dot-basetype': contentlet?.baseType
278
+ };
279
+ }
280
+ /**
281
+ *
282
+ * Checks whether DotCMS Analytics is initialized and active on the page.
283
+ *
284
+ * The SDKs use this in live mode to decide whether to keep the minimal
285
+ * contentlet attributes Analytics depends on.
286
+ *
287
+ * @returns {boolean} `true` when analytics is active, otherwise `false`
288
+ */
289
+ function isDotAnalyticsActive() {
290
+ return typeof window !== 'undefined' && window[ANALYTICS_ACTIVE_WINDOW_KEY] === true;
291
+ }
260
292
  /**
261
293
  *
262
294
  *
@@ -377,6 +409,38 @@ function readContentletDataset(element) {
377
409
  })
378
410
  };
379
411
  }
412
+ /**
413
+ * Returns Zone.js's *unpatched* native `addEventListener` / `removeEventListener`
414
+ * bound to `target`, falling back to the standard methods when Zone.js is absent.
415
+ *
416
+ * UVE reuses a single iframe and rewrites it with `document.open()/write()/close()`
417
+ * on every in-editor navigation. When Zone.js is loaded inside that iframe it runs
418
+ * in global-events mode: one native "gateway" listener per (target, eventType)
419
+ * plus a JS-level task list stored on the target node. `document.open()` tears down
420
+ * the native gateway on the persistent `window`/`document` nodes, but the task list
421
+ * survives on them, so Zone sees "already registered" on re-init and skips
422
+ * re-installing the native gateway — the listener silently goes dead after the
423
+ * first navigation.
424
+ *
425
+ * Hover/click dodge this by binding to `document.documentElement`, a node that
426
+ * `write()` recreates fresh. `scroll`/`message` (on `window`) and
427
+ * `DOMContentLoaded` (on `document`) can't: none of them fire on `<html>`, so
428
+ * there is no fresh node to rebind to. Going through Zone's native (untracked)
429
+ * methods sidesteps the dedup entirely, so the listener rebinds cleanly after
430
+ * every rewrite.
431
+ */
432
+ function getNativeEventBinder(target) {
433
+ const zone = globalThis.Zone;
434
+ const symbolFor = name => typeof zone?.__symbol__ === 'function' ? zone.__symbol__(name) : `__zone_symbol__${name}`;
435
+ const source = target;
436
+ const base = target;
437
+ const nativeAdd = source[symbolFor('addEventListener')];
438
+ const nativeRemove = source[symbolFor('removeEventListener')];
439
+ return {
440
+ addEventListener: (nativeAdd ?? base.addEventListener).bind(base),
441
+ removeEventListener: (nativeRemove ?? base.removeEventListener).bind(base)
442
+ };
443
+ }
380
444
 
381
445
  /**
382
446
  * Subscribes to content changes in the UVE editor
@@ -393,10 +457,12 @@ function onContentChanges(callback) {
393
457
  callback(event.data.payload);
394
458
  }
395
459
  };
396
- window.addEventListener('message', messageCallback);
460
+ // Native binder: survives the iframe rewrite under Zone.js. See getNativeEventBinder.
461
+ const nativeWindow = getNativeEventBinder(window);
462
+ nativeWindow.addEventListener('message', messageCallback);
397
463
  return {
398
464
  unsubscribe: () => {
399
- window.removeEventListener('message', messageCallback);
465
+ nativeWindow.removeEventListener('message', messageCallback);
400
466
  },
401
467
  event: types.UVEEventType.CONTENT_CHANGES
402
468
  };
@@ -416,10 +482,12 @@ function onPageReload(callback) {
416
482
  callback();
417
483
  }
418
484
  };
419
- window.addEventListener('message', messageCallback);
485
+ // Native binder: survives the iframe rewrite under Zone.js. See getNativeEventBinder.
486
+ const nativeWindow = getNativeEventBinder(window);
487
+ nativeWindow.addEventListener('message', messageCallback);
420
488
  return {
421
489
  unsubscribe: () => {
422
- window.removeEventListener('message', messageCallback);
490
+ nativeWindow.removeEventListener('message', messageCallback);
423
491
  },
424
492
  event: types.UVEEventType.PAGE_RELOAD
425
493
  };
@@ -507,13 +575,13 @@ function onAutoBounds(callback) {
507
575
  childList: true,
508
576
  subtree: true
509
577
  });
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.
578
+ // Scrolling doesn't change layout (ResizeObserver won't fire) but moves
579
+ // every contentlet, so re-emit bounds after the scroll settles to re-anchor
580
+ // the selected overlay. Native binder so it survives the iframe rewrite
581
+ // under Zone.js. See getNativeEventBinder.
582
+ const nativeWindow = getNativeEventBinder(window);
515
583
  const onScroll = () => scheduleEmit();
516
- window.addEventListener('scroll', onScroll, {
584
+ nativeWindow.addEventListener('scroll', onScroll, {
517
585
  passive: true
518
586
  });
519
587
  // Flush channel: the editor occasionally needs an immediate snapshot
@@ -528,7 +596,8 @@ function onAutoBounds(callback) {
528
596
  }
529
597
  emit();
530
598
  };
531
- window.addEventListener('message', onFlush);
599
+ // Native binder (reused from `scroll` above): survives the iframe rewrite too.
600
+ nativeWindow.addEventListener('message', onFlush);
532
601
  return {
533
602
  unsubscribe: () => {
534
603
  if (debounceTimer !== null) {
@@ -537,8 +606,8 @@ function onAutoBounds(callback) {
537
606
  }
538
607
  resizeObserver.disconnect();
539
608
  mutationObserver.disconnect();
540
- window.removeEventListener('scroll', onScroll);
541
- window.removeEventListener('message', onFlush);
609
+ nativeWindow.removeEventListener('scroll', onScroll);
610
+ nativeWindow.removeEventListener('message', onFlush);
542
611
  observed = [];
543
612
  },
544
613
  event: types.UVEEventType.AUTO_BOUNDS
@@ -560,10 +629,12 @@ function onIframeScroll(callback) {
560
629
  callback(direction);
561
630
  }
562
631
  };
563
- window.addEventListener('message', messageCallback);
632
+ // Native binder: survives the iframe rewrite under Zone.js. See getNativeEventBinder.
633
+ const nativeWindow = getNativeEventBinder(window);
634
+ nativeWindow.addEventListener('message', messageCallback);
564
635
  return {
565
636
  unsubscribe: () => {
566
- window.removeEventListener('message', messageCallback);
637
+ nativeWindow.removeEventListener('message', messageCallback);
567
638
  },
568
639
  event: types.UVEEventType.IFRAME_SCROLL
569
640
  };
@@ -593,10 +664,12 @@ function onScrollToSection(callback) {
593
664
  offsetTop: el.offsetTop
594
665
  });
595
666
  };
596
- window.addEventListener('message', messageCallback);
667
+ // Native binder: survives the iframe rewrite under Zone.js. See getNativeEventBinder.
668
+ const nativeWindow = getNativeEventBinder(window);
669
+ nativeWindow.addEventListener('message', messageCallback);
597
670
  return {
598
671
  unsubscribe: () => {
599
- window.removeEventListener('message', messageCallback);
672
+ nativeWindow.removeEventListener('message', messageCallback);
600
673
  },
601
674
  event: types.UVEEventType.SCROLL_TO_SECTION
602
675
  };
@@ -666,16 +739,18 @@ function onContentletHovered(callback) {
666
739
  hasHover = true;
667
740
  callback(contentletHoveredPayload);
668
741
  };
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.
675
- document.addEventListener('pointermove', pointerMoveCallback);
742
+ // We intentionally do not fire null on `pointerleave`: the editor's hover
743
+ // toolbar lives in the parent window, so leaving the iframe usually means
744
+ // the user is reaching for it killing the overlay would yank it away.
745
+ //
746
+ // Bind to `document.documentElement` (a fresh node on each iframe rewrite)
747
+ // rather than `document`, which goes dead under Zone.js after a rewrite.
748
+ // `pointermove` bubbles to <html>, so no-Zone behavior is identical. See
749
+ // getNativeEventBinder for the full rationale.
750
+ document.documentElement.addEventListener('pointermove', pointerMoveCallback);
676
751
  return {
677
752
  unsubscribe: () => {
678
- document.removeEventListener('pointermove', pointerMoveCallback);
753
+ document.documentElement.removeEventListener('pointermove', pointerMoveCallback);
679
754
  },
680
755
  event: types.UVEEventType.CONTENTLET_HOVERED
681
756
  };
@@ -749,18 +824,22 @@ function onContentletClicked(callback) {
749
824
  lastSelectedInode = undefined;
750
825
  }
751
826
  };
752
- // Capture phase so we run BEFORE the page's own click handlers and can
753
- // preventDefault/stopPropagation effectively.
754
- document.addEventListener('click', clickCallback, {
827
+ // Capture phase so we run BEFORE the page's own click handlers. Bound to
828
+ // `document.documentElement` (a fresh node on each iframe rewrite) rather
829
+ // than `document`, which goes dead under Zone.js after a rewrite; capture on
830
+ // <html> still runs first. See getNativeEventBinder for the full rationale.
831
+ document.documentElement.addEventListener('click', clickCallback, {
755
832
  capture: true
756
833
  });
757
- window.addEventListener('message', selectionClearedCallback);
834
+ // Native binder: survives the iframe rewrite under Zone.js. See getNativeEventBinder.
835
+ const nativeWindow = getNativeEventBinder(window);
836
+ nativeWindow.addEventListener('message', selectionClearedCallback);
758
837
  return {
759
838
  unsubscribe: () => {
760
- document.removeEventListener('click', clickCallback, {
839
+ document.documentElement.removeEventListener('click', clickCallback, {
761
840
  capture: true
762
841
  });
763
- window.removeEventListener('message', selectionClearedCallback);
842
+ nativeWindow.removeEventListener('message', selectionClearedCallback);
764
843
  },
765
844
  event: types.UVEEventType.CONTENTLET_CLICKED
766
845
  };
@@ -885,6 +964,29 @@ const CUSTOM_NO_COMPONENT = 'CustomNoComponent';
885
964
  * @internal
886
965
  */
887
966
  const DOT_SECTION_ID_PREFIX = 'dot-section-';
967
+ /**
968
+ * Window flag set by `@dotcms/analytics` when content analytics is initialized
969
+ * and active on the page.
970
+ *
971
+ * @important This value is intentionally duplicated from `@dotcms/analytics`
972
+ * (`ANALYTICS_WINDOWS_ACTIVE_KEY` in dot-analytics.constants.ts). The SDKs read
973
+ * it in live mode to decide whether to keep the minimal contentlet attributes
974
+ * Analytics depends on. Both constants MUST stay in sync.
975
+ *
976
+ * @internal
977
+ */
978
+ const ANALYTICS_ACTIVE_WINDOW_KEY = '__dotAnalyticsActive__';
979
+ /**
980
+ * Event dispatched by `@dotcms/analytics` once analytics is ready. The SDKs
981
+ * listen for it so live-mode contentlets can re-render with the attributes
982
+ * Analytics needs, regardless of initialization order.
983
+ *
984
+ * @important Kept in sync with the `dotcms:analytics:ready` event dispatched by
985
+ * `@dotcms/analytics` (initializeContentAnalytics).
986
+ *
987
+ * @internal
988
+ */
989
+ const ANALYTICS_READY_EVENT = 'dotcms:analytics:ready';
888
990
 
889
991
  /**
890
992
  * Gets the current state of the Universal Visual Editor (UVE).
@@ -1073,7 +1175,6 @@ const isValidBlocks = blocks => {
1073
1175
  };
1074
1176
  };
1075
1177
 
1076
- /* eslint-disable @typescript-eslint/no-explicit-any */
1077
1178
  /**
1078
1179
  * Sets up scroll event handlers for the window to notify the editor about scroll events.
1079
1180
  * Adds listeners for both 'scroll' and 'scrollend' events, sending appropriate messages
@@ -1090,12 +1191,19 @@ function scrollHandler() {
1090
1191
  action: types.DotCMSUVEAction.IFRAME_SCROLL_END
1091
1192
  });
1092
1193
  };
1093
- window.addEventListener('scroll', scrollCallback);
1094
- window.addEventListener('scrollend', scrollEndCallback);
1194
+ // Native binder: scroll survives the iframe rewrite under Zone.js. It can't
1195
+ // move to a fresh `documentElement` like hover/click (viewport scroll only
1196
+ // fires on `window`). See getNativeEventBinder.
1197
+ const {
1198
+ addEventListener,
1199
+ removeEventListener
1200
+ } = getNativeEventBinder(window);
1201
+ addEventListener('scroll', scrollCallback);
1202
+ addEventListener('scrollend', scrollEndCallback);
1095
1203
  return {
1096
1204
  destroyScrollHandler: () => {
1097
- window.removeEventListener('scroll', scrollCallback);
1098
- window.removeEventListener('scrollend', scrollEndCallback);
1205
+ removeEventListener('scroll', scrollCallback);
1206
+ removeEventListener('scrollend', scrollEndCallback);
1099
1207
  }
1100
1208
  };
1101
1209
  }
@@ -1209,14 +1317,19 @@ function listenBlockEditorInlineEvent() {
1209
1317
  }
1210
1318
  };
1211
1319
  }
1212
- // If the page is not fully loaded, listen for the DOMContentLoaded event
1320
+ // Native binder: `DOMContentLoaded` fires on `document` (not `<html>`), so
1321
+ // it survives the iframe rewrite under Zone.js. See getNativeEventBinder.
1213
1322
  const handleDOMContentLoaded = () => {
1214
1323
  listenBlockEditorClick();
1215
1324
  };
1216
- document.addEventListener('DOMContentLoaded', handleDOMContentLoaded);
1325
+ const {
1326
+ addEventListener,
1327
+ removeEventListener
1328
+ } = getNativeEventBinder(document);
1329
+ addEventListener('DOMContentLoaded', handleDOMContentLoaded);
1217
1330
  return {
1218
1331
  destroyListenBlockEditorInlineEvent: () => {
1219
- document.removeEventListener('DOMContentLoaded', handleDOMContentLoaded);
1332
+ removeEventListener('DOMContentLoaded', handleDOMContentLoaded);
1220
1333
  }
1221
1334
  };
1222
1335
  }
@@ -1434,6 +1547,8 @@ function initUVE(config = {}) {
1434
1547
  };
1435
1548
  }
1436
1549
 
1550
+ exports.ANALYTICS_ACTIVE_WINDOW_KEY = ANALYTICS_ACTIVE_WINDOW_KEY;
1551
+ exports.ANALYTICS_READY_EVENT = ANALYTICS_READY_EVENT;
1437
1552
  exports.CUSTOM_NO_COMPONENT = CUSTOM_NO_COMPONENT;
1438
1553
  exports.DEVELOPMENT_MODE = DEVELOPMENT_MODE;
1439
1554
  exports.DOT_SECTION_ID_PREFIX = DOT_SECTION_ID_PREFIX;
@@ -1454,6 +1569,7 @@ exports.editContentlet = editContentlet;
1454
1569
  exports.enableBlockEditorInline = enableBlockEditorInline;
1455
1570
  exports.findDotCMSElement = findDotCMSElement;
1456
1571
  exports.findDotCMSVTLData = findDotCMSVTLData;
1572
+ exports.getAnalyticsContentletAttributes = getAnalyticsContentletAttributes;
1457
1573
  exports.getClosestDotCMSContainerData = getClosestDotCMSContainerData;
1458
1574
  exports.getColumnPositionClasses = getColumnPositionClasses;
1459
1575
  exports.getContainersData = getContainersData;
@@ -1463,9 +1579,11 @@ exports.getDotCMSContentletsBound = getDotCMSContentletsBound;
1463
1579
  exports.getDotCMSPageBounds = getDotCMSPageBounds;
1464
1580
  exports.getDotContainerAttributes = getDotContainerAttributes;
1465
1581
  exports.getDotContentletAttributes = getDotContentletAttributes;
1582
+ exports.getNativeEventBinder = getNativeEventBinder;
1466
1583
  exports.getUVEState = getUVEState;
1467
1584
  exports.initInlineEditing = initInlineEditing;
1468
1585
  exports.initUVE = initUVE;
1586
+ exports.isDotAnalyticsActive = isDotAnalyticsActive;
1469
1587
  exports.isValidBlocks = isValidBlocks;
1470
1588
  exports.readContentletDataset = readContentletDataset;
1471
1589
  exports.reorderMenu = reorderMenu;
package/public.esm.js CHANGED
@@ -255,6 +255,38 @@ function getDotContentletAttributes(contentlet, container) {
255
255
  })
256
256
  };
257
257
  }
258
+ /**
259
+ *
260
+ * Returns the minimal set of contentlet data attributes required by DotCMS
261
+ * Analytics (impression & click tracking) to identify a contentlet.
262
+ *
263
+ * Used in live mode where the full editor metadata is stripped but Analytics
264
+ * still needs to resolve the contentlet behind an impression/click.
265
+ *
266
+ * @param {DotCMSBasicContentlet} contentlet - The contentlet to get the attributes for
267
+ * @returns {DotAnalyticsContentletAttributes} The Analytics-required data attributes
268
+ */
269
+ function getAnalyticsContentletAttributes(contentlet) {
270
+ return {
271
+ 'data-dot-identifier': contentlet?.identifier,
272
+ 'data-dot-inode': contentlet?.inode,
273
+ 'data-dot-title': contentlet?.['widgetTitle'] || contentlet?.title,
274
+ 'data-dot-type': contentlet?.contentType,
275
+ 'data-dot-basetype': contentlet?.baseType
276
+ };
277
+ }
278
+ /**
279
+ *
280
+ * Checks whether DotCMS Analytics is initialized and active on the page.
281
+ *
282
+ * The SDKs use this in live mode to decide whether to keep the minimal
283
+ * contentlet attributes Analytics depends on.
284
+ *
285
+ * @returns {boolean} `true` when analytics is active, otherwise `false`
286
+ */
287
+ function isDotAnalyticsActive() {
288
+ return typeof window !== 'undefined' && window[ANALYTICS_ACTIVE_WINDOW_KEY] === true;
289
+ }
258
290
  /**
259
291
  *
260
292
  *
@@ -375,6 +407,38 @@ function readContentletDataset(element) {
375
407
  })
376
408
  };
377
409
  }
410
+ /**
411
+ * Returns Zone.js's *unpatched* native `addEventListener` / `removeEventListener`
412
+ * bound to `target`, falling back to the standard methods when Zone.js is absent.
413
+ *
414
+ * UVE reuses a single iframe and rewrites it with `document.open()/write()/close()`
415
+ * on every in-editor navigation. When Zone.js is loaded inside that iframe it runs
416
+ * in global-events mode: one native "gateway" listener per (target, eventType)
417
+ * plus a JS-level task list stored on the target node. `document.open()` tears down
418
+ * the native gateway on the persistent `window`/`document` nodes, but the task list
419
+ * survives on them, so Zone sees "already registered" on re-init and skips
420
+ * re-installing the native gateway — the listener silently goes dead after the
421
+ * first navigation.
422
+ *
423
+ * Hover/click dodge this by binding to `document.documentElement`, a node that
424
+ * `write()` recreates fresh. `scroll`/`message` (on `window`) and
425
+ * `DOMContentLoaded` (on `document`) can't: none of them fire on `<html>`, so
426
+ * there is no fresh node to rebind to. Going through Zone's native (untracked)
427
+ * methods sidesteps the dedup entirely, so the listener rebinds cleanly after
428
+ * every rewrite.
429
+ */
430
+ function getNativeEventBinder(target) {
431
+ const zone = globalThis.Zone;
432
+ const symbolFor = name => typeof zone?.__symbol__ === 'function' ? zone.__symbol__(name) : `__zone_symbol__${name}`;
433
+ const source = target;
434
+ const base = target;
435
+ const nativeAdd = source[symbolFor('addEventListener')];
436
+ const nativeRemove = source[symbolFor('removeEventListener')];
437
+ return {
438
+ addEventListener: (nativeAdd ?? base.addEventListener).bind(base),
439
+ removeEventListener: (nativeRemove ?? base.removeEventListener).bind(base)
440
+ };
441
+ }
378
442
 
379
443
  /**
380
444
  * Subscribes to content changes in the UVE editor
@@ -391,10 +455,12 @@ function onContentChanges(callback) {
391
455
  callback(event.data.payload);
392
456
  }
393
457
  };
394
- window.addEventListener('message', messageCallback);
458
+ // Native binder: survives the iframe rewrite under Zone.js. See getNativeEventBinder.
459
+ const nativeWindow = getNativeEventBinder(window);
460
+ nativeWindow.addEventListener('message', messageCallback);
395
461
  return {
396
462
  unsubscribe: () => {
397
- window.removeEventListener('message', messageCallback);
463
+ nativeWindow.removeEventListener('message', messageCallback);
398
464
  },
399
465
  event: UVEEventType.CONTENT_CHANGES
400
466
  };
@@ -414,10 +480,12 @@ function onPageReload(callback) {
414
480
  callback();
415
481
  }
416
482
  };
417
- window.addEventListener('message', messageCallback);
483
+ // Native binder: survives the iframe rewrite under Zone.js. See getNativeEventBinder.
484
+ const nativeWindow = getNativeEventBinder(window);
485
+ nativeWindow.addEventListener('message', messageCallback);
418
486
  return {
419
487
  unsubscribe: () => {
420
- window.removeEventListener('message', messageCallback);
488
+ nativeWindow.removeEventListener('message', messageCallback);
421
489
  },
422
490
  event: UVEEventType.PAGE_RELOAD
423
491
  };
@@ -505,13 +573,13 @@ function onAutoBounds(callback) {
505
573
  childList: true,
506
574
  subtree: true
507
575
  });
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.
576
+ // Scrolling doesn't change layout (ResizeObserver won't fire) but moves
577
+ // every contentlet, so re-emit bounds after the scroll settles to re-anchor
578
+ // the selected overlay. Native binder so it survives the iframe rewrite
579
+ // under Zone.js. See getNativeEventBinder.
580
+ const nativeWindow = getNativeEventBinder(window);
513
581
  const onScroll = () => scheduleEmit();
514
- window.addEventListener('scroll', onScroll, {
582
+ nativeWindow.addEventListener('scroll', onScroll, {
515
583
  passive: true
516
584
  });
517
585
  // Flush channel: the editor occasionally needs an immediate snapshot
@@ -526,7 +594,8 @@ function onAutoBounds(callback) {
526
594
  }
527
595
  emit();
528
596
  };
529
- window.addEventListener('message', onFlush);
597
+ // Native binder (reused from `scroll` above): survives the iframe rewrite too.
598
+ nativeWindow.addEventListener('message', onFlush);
530
599
  return {
531
600
  unsubscribe: () => {
532
601
  if (debounceTimer !== null) {
@@ -535,8 +604,8 @@ function onAutoBounds(callback) {
535
604
  }
536
605
  resizeObserver.disconnect();
537
606
  mutationObserver.disconnect();
538
- window.removeEventListener('scroll', onScroll);
539
- window.removeEventListener('message', onFlush);
607
+ nativeWindow.removeEventListener('scroll', onScroll);
608
+ nativeWindow.removeEventListener('message', onFlush);
540
609
  observed = [];
541
610
  },
542
611
  event: UVEEventType.AUTO_BOUNDS
@@ -558,10 +627,12 @@ function onIframeScroll(callback) {
558
627
  callback(direction);
559
628
  }
560
629
  };
561
- window.addEventListener('message', messageCallback);
630
+ // Native binder: survives the iframe rewrite under Zone.js. See getNativeEventBinder.
631
+ const nativeWindow = getNativeEventBinder(window);
632
+ nativeWindow.addEventListener('message', messageCallback);
562
633
  return {
563
634
  unsubscribe: () => {
564
- window.removeEventListener('message', messageCallback);
635
+ nativeWindow.removeEventListener('message', messageCallback);
565
636
  },
566
637
  event: UVEEventType.IFRAME_SCROLL
567
638
  };
@@ -591,10 +662,12 @@ function onScrollToSection(callback) {
591
662
  offsetTop: el.offsetTop
592
663
  });
593
664
  };
594
- window.addEventListener('message', messageCallback);
665
+ // Native binder: survives the iframe rewrite under Zone.js. See getNativeEventBinder.
666
+ const nativeWindow = getNativeEventBinder(window);
667
+ nativeWindow.addEventListener('message', messageCallback);
595
668
  return {
596
669
  unsubscribe: () => {
597
- window.removeEventListener('message', messageCallback);
670
+ nativeWindow.removeEventListener('message', messageCallback);
598
671
  },
599
672
  event: UVEEventType.SCROLL_TO_SECTION
600
673
  };
@@ -664,16 +737,18 @@ function onContentletHovered(callback) {
664
737
  hasHover = true;
665
738
  callback(contentletHoveredPayload);
666
739
  };
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.
673
- document.addEventListener('pointermove', pointerMoveCallback);
740
+ // We intentionally do not fire null on `pointerleave`: the editor's hover
741
+ // toolbar lives in the parent window, so leaving the iframe usually means
742
+ // the user is reaching for it killing the overlay would yank it away.
743
+ //
744
+ // Bind to `document.documentElement` (a fresh node on each iframe rewrite)
745
+ // rather than `document`, which goes dead under Zone.js after a rewrite.
746
+ // `pointermove` bubbles to <html>, so no-Zone behavior is identical. See
747
+ // getNativeEventBinder for the full rationale.
748
+ document.documentElement.addEventListener('pointermove', pointerMoveCallback);
674
749
  return {
675
750
  unsubscribe: () => {
676
- document.removeEventListener('pointermove', pointerMoveCallback);
751
+ document.documentElement.removeEventListener('pointermove', pointerMoveCallback);
677
752
  },
678
753
  event: UVEEventType.CONTENTLET_HOVERED
679
754
  };
@@ -747,18 +822,22 @@ function onContentletClicked(callback) {
747
822
  lastSelectedInode = undefined;
748
823
  }
749
824
  };
750
- // Capture phase so we run BEFORE the page's own click handlers and can
751
- // preventDefault/stopPropagation effectively.
752
- document.addEventListener('click', clickCallback, {
825
+ // Capture phase so we run BEFORE the page's own click handlers. Bound to
826
+ // `document.documentElement` (a fresh node on each iframe rewrite) rather
827
+ // than `document`, which goes dead under Zone.js after a rewrite; capture on
828
+ // <html> still runs first. See getNativeEventBinder for the full rationale.
829
+ document.documentElement.addEventListener('click', clickCallback, {
753
830
  capture: true
754
831
  });
755
- window.addEventListener('message', selectionClearedCallback);
832
+ // Native binder: survives the iframe rewrite under Zone.js. See getNativeEventBinder.
833
+ const nativeWindow = getNativeEventBinder(window);
834
+ nativeWindow.addEventListener('message', selectionClearedCallback);
756
835
  return {
757
836
  unsubscribe: () => {
758
- document.removeEventListener('click', clickCallback, {
837
+ document.documentElement.removeEventListener('click', clickCallback, {
759
838
  capture: true
760
839
  });
761
- window.removeEventListener('message', selectionClearedCallback);
840
+ nativeWindow.removeEventListener('message', selectionClearedCallback);
762
841
  },
763
842
  event: UVEEventType.CONTENTLET_CLICKED
764
843
  };
@@ -883,6 +962,29 @@ const CUSTOM_NO_COMPONENT = 'CustomNoComponent';
883
962
  * @internal
884
963
  */
885
964
  const DOT_SECTION_ID_PREFIX = 'dot-section-';
965
+ /**
966
+ * Window flag set by `@dotcms/analytics` when content analytics is initialized
967
+ * and active on the page.
968
+ *
969
+ * @important This value is intentionally duplicated from `@dotcms/analytics`
970
+ * (`ANALYTICS_WINDOWS_ACTIVE_KEY` in dot-analytics.constants.ts). The SDKs read
971
+ * it in live mode to decide whether to keep the minimal contentlet attributes
972
+ * Analytics depends on. Both constants MUST stay in sync.
973
+ *
974
+ * @internal
975
+ */
976
+ const ANALYTICS_ACTIVE_WINDOW_KEY = '__dotAnalyticsActive__';
977
+ /**
978
+ * Event dispatched by `@dotcms/analytics` once analytics is ready. The SDKs
979
+ * listen for it so live-mode contentlets can re-render with the attributes
980
+ * Analytics needs, regardless of initialization order.
981
+ *
982
+ * @important Kept in sync with the `dotcms:analytics:ready` event dispatched by
983
+ * `@dotcms/analytics` (initializeContentAnalytics).
984
+ *
985
+ * @internal
986
+ */
987
+ const ANALYTICS_READY_EVENT = 'dotcms:analytics:ready';
886
988
 
887
989
  /**
888
990
  * Gets the current state of the Universal Visual Editor (UVE).
@@ -1071,7 +1173,6 @@ const isValidBlocks = blocks => {
1071
1173
  };
1072
1174
  };
1073
1175
 
1074
- /* eslint-disable @typescript-eslint/no-explicit-any */
1075
1176
  /**
1076
1177
  * Sets up scroll event handlers for the window to notify the editor about scroll events.
1077
1178
  * Adds listeners for both 'scroll' and 'scrollend' events, sending appropriate messages
@@ -1088,12 +1189,19 @@ function scrollHandler() {
1088
1189
  action: DotCMSUVEAction.IFRAME_SCROLL_END
1089
1190
  });
1090
1191
  };
1091
- window.addEventListener('scroll', scrollCallback);
1092
- window.addEventListener('scrollend', scrollEndCallback);
1192
+ // Native binder: scroll survives the iframe rewrite under Zone.js. It can't
1193
+ // move to a fresh `documentElement` like hover/click (viewport scroll only
1194
+ // fires on `window`). See getNativeEventBinder.
1195
+ const {
1196
+ addEventListener,
1197
+ removeEventListener
1198
+ } = getNativeEventBinder(window);
1199
+ addEventListener('scroll', scrollCallback);
1200
+ addEventListener('scrollend', scrollEndCallback);
1093
1201
  return {
1094
1202
  destroyScrollHandler: () => {
1095
- window.removeEventListener('scroll', scrollCallback);
1096
- window.removeEventListener('scrollend', scrollEndCallback);
1203
+ removeEventListener('scroll', scrollCallback);
1204
+ removeEventListener('scrollend', scrollEndCallback);
1097
1205
  }
1098
1206
  };
1099
1207
  }
@@ -1207,14 +1315,19 @@ function listenBlockEditorInlineEvent() {
1207
1315
  }
1208
1316
  };
1209
1317
  }
1210
- // If the page is not fully loaded, listen for the DOMContentLoaded event
1318
+ // Native binder: `DOMContentLoaded` fires on `document` (not `<html>`), so
1319
+ // it survives the iframe rewrite under Zone.js. See getNativeEventBinder.
1211
1320
  const handleDOMContentLoaded = () => {
1212
1321
  listenBlockEditorClick();
1213
1322
  };
1214
- document.addEventListener('DOMContentLoaded', handleDOMContentLoaded);
1323
+ const {
1324
+ addEventListener,
1325
+ removeEventListener
1326
+ } = getNativeEventBinder(document);
1327
+ addEventListener('DOMContentLoaded', handleDOMContentLoaded);
1215
1328
  return {
1216
1329
  destroyListenBlockEditorInlineEvent: () => {
1217
- document.removeEventListener('DOMContentLoaded', handleDOMContentLoaded);
1330
+ removeEventListener('DOMContentLoaded', handleDOMContentLoaded);
1218
1331
  }
1219
1332
  };
1220
1333
  }
@@ -1432,4 +1545,4 @@ function initUVE(config = {}) {
1432
1545
  };
1433
1546
  }
1434
1547
 
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 };
1548
+ export { ANALYTICS_ACTIVE_WINDOW_KEY as A, getAnalyticsContentletAttributes as B, CUSTOM_NO_COMPONENT as C, DEVELOPMENT_MODE as D, END_CLASS as E, isDotAnalyticsActive as F, getContainersData as G, getContentletsInContainer as H, getDotContainerAttributes as I, readContentletDataset as J, getNativeEventBinder as K, setBounds as L, isValidBlocks as M, PRODUCTION_MODE as P, START_CLASS as S, TEMP_EMPTY_CONTENTLET as T, __UVE_EVENTS__ as _, enableBlockEditorInline as a, createContentlet as b, createUVESubscription as c, initUVE as d, editContentlet as e, __UVE_EVENT_ERROR_FALLBACK__ as f, getUVEState as g, EMPTY_CONTAINER_STYLE_REACT as h, initInlineEditing as i, EMPTY_CONTAINER_STYLE_ANGULAR as j, DOT_SECTION_ID_PREFIX as k, ANALYTICS_READY_EVENT as l, TEMP_EMPTY_CONTENTLET_TYPE as m, getDotCMSPageBounds as n, getDotCMSContentletsBound as o, getDotCMSContainerData as p, getClosestDotCMSContainerData as q, reorderMenu as r, sendMessageToUVE as s, findDotCMSElement as t, updateNavigation as u, findDotCMSVTLData as v, computeScrollIsInBottom as w, combineClasses as x, getColumnPositionClasses as y, getDotContentletAttributes as z };
@@ -81,3 +81,26 @@ export declare const CUSTOM_NO_COMPONENT = "CustomNoComponent";
81
81
  * @internal
82
82
  */
83
83
  export declare const DOT_SECTION_ID_PREFIX = "dot-section-";
84
+ /**
85
+ * Window flag set by `@dotcms/analytics` when content analytics is initialized
86
+ * and active on the page.
87
+ *
88
+ * @important This value is intentionally duplicated from `@dotcms/analytics`
89
+ * (`ANALYTICS_WINDOWS_ACTIVE_KEY` in dot-analytics.constants.ts). The SDKs read
90
+ * it in live mode to decide whether to keep the minimal contentlet attributes
91
+ * Analytics depends on. Both constants MUST stay in sync.
92
+ *
93
+ * @internal
94
+ */
95
+ export declare const ANALYTICS_ACTIVE_WINDOW_KEY = "__dotAnalyticsActive__";
96
+ /**
97
+ * Event dispatched by `@dotcms/analytics` once analytics is ready. The SDKs
98
+ * listen for it so live-mode contentlets can re-render with the attributes
99
+ * Analytics needs, regardless of initialization order.
100
+ *
101
+ * @important Kept in sync with the `dotcms:analytics:ready` event dispatched by
102
+ * `@dotcms/analytics` (initializeContentAnalytics).
103
+ *
104
+ * @internal
105
+ */
106
+ export declare const ANALYTICS_READY_EVENT = "dotcms:analytics:ready";
@@ -1,5 +1,5 @@
1
1
  import { DotCMSBasicContentlet, DotCMSColumnContainer, DotCMSPageAsset, DotPageAssetLayoutColumn, EditableContainerData } from '@dotcms/types';
2
- import { DotCMSContainerBound, DotCMSContentletBound, DotContainerAttributes, DotContentletAttributes } from '@dotcms/types/internal';
2
+ import { DotAnalyticsContentletAttributes, DotCMSContainerBound, DotCMSContentletBound, DotContainerAttributes, DotContentletAttributes } from '@dotcms/types/internal';
3
3
  /**
4
4
  * Calculates the bounding information for each page element within the given containers.
5
5
  *
@@ -152,6 +152,28 @@ export declare const getColumnPositionClasses: (column: DotPageAssetLayoutColumn
152
152
  * @returns {DotContentletAttributes} The dotCMS data attributes
153
153
  */
154
154
  export declare function getDotContentletAttributes(contentlet: DotCMSBasicContentlet, container: string): DotContentletAttributes;
155
+ /**
156
+ *
157
+ * Returns the minimal set of contentlet data attributes required by DotCMS
158
+ * Analytics (impression & click tracking) to identify a contentlet.
159
+ *
160
+ * Used in live mode where the full editor metadata is stripped but Analytics
161
+ * still needs to resolve the contentlet behind an impression/click.
162
+ *
163
+ * @param {DotCMSBasicContentlet} contentlet - The contentlet to get the attributes for
164
+ * @returns {DotAnalyticsContentletAttributes} The Analytics-required data attributes
165
+ */
166
+ export declare function getAnalyticsContentletAttributes(contentlet: DotCMSBasicContentlet): DotAnalyticsContentletAttributes;
167
+ /**
168
+ *
169
+ * Checks whether DotCMS Analytics is initialized and active on the page.
170
+ *
171
+ * The SDKs use this in live mode to decide whether to keep the minimal
172
+ * contentlet attributes Analytics depends on.
173
+ *
174
+ * @returns {boolean} `true` when analytics is active, otherwise `false`
175
+ */
176
+ export declare function isDotAnalyticsActive(): boolean;
155
177
  /**
156
178
  *
157
179
  *
@@ -219,3 +241,24 @@ export declare function readContentletDataset(element: HTMLElement): {
219
241
  widgetTitle: string | undefined;
220
242
  onNumberOfPages: string | undefined;
221
243
  };
244
+ /**
245
+ * Returns Zone.js's *unpatched* native `addEventListener` / `removeEventListener`
246
+ * bound to `target`, falling back to the standard methods when Zone.js is absent.
247
+ *
248
+ * UVE reuses a single iframe and rewrites it with `document.open()/write()/close()`
249
+ * on every in-editor navigation. When Zone.js is loaded inside that iframe it runs
250
+ * in global-events mode: one native "gateway" listener per (target, eventType)
251
+ * plus a JS-level task list stored on the target node. `document.open()` tears down
252
+ * the native gateway on the persistent `window`/`document` nodes, but the task list
253
+ * survives on them, so Zone sees "already registered" on re-init and skips
254
+ * re-installing the native gateway — the listener silently goes dead after the
255
+ * first navigation.
256
+ *
257
+ * Hover/click dodge this by binding to `document.documentElement`, a node that
258
+ * `write()` recreates fresh. `scroll`/`message` (on `window`) and
259
+ * `DOMContentLoaded` (on `document`) can't: none of them fire on `<html>`, so
260
+ * there is no fresh node to rebind to. Going through Zone's native (untracked)
261
+ * methods sidesteps the dedup entirely, so the listener rebinds cleanly after
262
+ * every rewrite.
263
+ */
264
+ export declare function getNativeEventBinder<T extends Window | Document>(target: T): Pick<T, 'addEventListener' | 'removeEventListener'>;