fix(uve): keep hover/selection/drag working on Zone.js traditional pages#36333
fix(uve): keep hover/selection/drag working on Zone.js traditional pages#36333rjvelazco wants to merge 9 commits into
Conversation
…e Zone.js iframe rewrites (#36167) On traditional (server-rendered) pages, UVE reuses a single iframe and rewrites it via `doc.open()/write()/close()` on every in-editor navigation. When the edited page loads Zone.js inside that iframe (e.g. an embedded Angular Elements / web-component widget bundle), Zone runs in global-events mode: one native gateway listener plus a JS-level task list stored on the node. `document.open()` tears down the native gateway on the persistent `document`/`window` nodes, but the JS-level Zone task list survives on them. On re-init Zone sees "already registered" and skips re-binding the native gateway, so the listener silently never fires again. The UVE SDK attached its hover (`pointermove`) and selection (`click`) listeners to `document`, so hover-to-edit and click-to-select broke after the first in-editor re-render and only came back after a hard browser reload. Fix: attach those listeners to `document.documentElement` (the `<html>` node), which is recreated fresh on every iframe rewrite and therefore carries no stale Zone task list. Both events bubble to `<html>`, and the click listener keeps capture phase, so behavior is identical on pages that do not load Zone.js — no regression. Window-level listeners (`message`, `scroll`) were left on `window`: the live diagnostic showed they still fire after a rewrite, so only the confirmed-dead `document` listeners were moved. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @rjvelazco's task in 2m 57s —— View job I'll analyze this and get back to you. |
…ays render (#36167) Dragging a new contentlet from the palette sometimes showed no dropzone drop targets even though the drag was clearly active. The dropzone renders only when `editorState === DRAGGING` AND `editorBounds` is populated. `dragstart` defers `setEditorDragItem` to the next animation frame (so the browser can snapshot the native drag image before Angular re-renders). The `dragenter` handler — the only place that posted `UVE_FLUSH_BOUNDS` to refresh bounds — gates on `editorDragItem` already being set and bails otherwise. Because of the `!fromElement` filter, only the first `dragenter` ever runs, so when it fires before the deferred frame it bails before flushing. `dragover` then flips the editor into DRAGGING but never flushes bounds, leaving the dropzone with stale/empty `editorBounds` (which get cleared to `[]` on scroll/device-switch) and no visible drop targets. The failure is intermittent because it depends on whether `dragenter` beats the animation frame and whether bounds happened to be fresh. Fix: post `UVE_FLUSH_BOUNDS` synchronously at drag start, independent of the race, so fresh bounds are requested the moment a drag begins and have arrived by the time `dragover` engages DRAGGING. The `requestAnimationFrame` defer for the drag item is kept so the drag-image snapshot is unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
🤖 Bedrock Review —
|
…rk (#36167) After a successful drop, starting a new drag sometimes showed no dropzone targets. The dropzone's bounds are flushed by `$handleIsDraggingEffect`, an Angular effect that only re-fires on an IDLE→DRAGGING transition. The dragend handler only reset editor state when `dropEffect === 'none'` (cancelled drops); successful drops relied on the async save→reload to reset. That left a window where `editorState` was still DRAGGING when the next drag began, so there was no clean IDLE→DRAGGING transition, the flush effect never re-fired, and the dropzone was left without bounds. Reset editor UI state on every dragend instead. `dragend` always fires when the gesture ends, and `handleDrop` has already consumed the drag item synchronously (drop fires before dragend), so resetting here is safe and guarantees a clean IDLE state for the next drag. The save→reload still re-renders content independently. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
🤖 Bedrock Review —
|
|
Tick the box to add this pull request to the merge queue (same as
|
🤖 dotBot Review (Bedrock)Reviewed 8 file(s); 9 candidate(s) → 3 confirmed, 0 uncertain (unverified, kept for review). Confirmed findings
us.deepseek.r1-v1:0 · Run: #28616905537 · tokens: in: 76813 · out: 29204 · total: 106017 · calls: 24 · est. ~$0.261 |
|
Why these bugs happen. UVE reuses one iframe and rewrites the whole document ( The fix we actually need is to stop rewriting the entire iframe on every change — the SDK should patch only the elements that changed, like the headless React/Angular SDKs do. That removes the teardown that kills the listeners, and the workarounds here become unnecessary. This PR is that workaround until then: re-bind the affected listeners to nodes that survive the rewrite ( |
…re reliability across Zone.js rewrites
… messaging and removing unused parameters
…hub.com/dotCMS/core into issue-36167-uve-zonejs-iframe-listeners
TestChromevideo.movFireFoxvideo-firefox.movcc: @zJaaal |
| takeUntilDestroyed(this.destroyRef), | ||
| filter((event: DragEvent) => event.dataTransfer?.dropEffect === 'none') | ||
| ) | ||
| .pipe(takeUntilDestroyed(this.destroyRef)) |
There was a problem hiding this comment.
🟠 [High] Missing filter on dragend event causes style reset after successful drops
The removed filter(event => event.dataTransfer?.dropEffect === 'none') previously ensured style cleanup only occurred on canceled drags. Without it, the handler now runs for all dragend events (including successful drops), potentially interfering with post-drop UI state by prematurely resetting dragged element styles.
Important
Summary
Why these bugs happen. UVE reuses one iframe and rewrites the whole document (
document.open()/write()/close()) on every in-editor navigation/reload. When Zone.js is in the page it keeps its own list of event listeners on thewindow/documentnodes; the rewrite wipes the real (native) listeners but not Zone's list, so when the SDK re-attaches, Zone thinks they're "already there", skips re-binding, and the listeners silently go dead.The fix we actually need is to stop rewriting the entire iframe on every change — the SDK should patch only the elements that changed, like the headless React/Angular SDKs do. That removes the teardown that kills the listeners, and the workarounds here become unnecessary.
This PR is that workaround until then: re-bind the affected listeners to nodes that survive the rewrite (
documentElement), or via Zone's native, untrackedaddEventListener.Files changed
core-web/libs/sdk/uve/src/internal/events.ts— Fix 1 (documentElementfor hover/click) + Fix 3 (native binder for inboundmessage+ auto-boundsscroll).core-web/libs/sdk/uve/src/script/utils.ts— Fix 3 (native binder forscroll/scrollend+DOMContentLoaded).core-web/libs/sdk/uve/src/lib/dom/dom.utils.ts— Fix 3 (getNativeEventBinderhelper).core-web/libs/sdk/uve/src/script/utils.spec.ts— Fix 3 test.dotCMS/src/main/webapp/ext/uve/dot-uve.js— regenerated SDK bundle (Fix 1 + Fix 3).core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-uve-drag-drop/dot-uve-drag-drop.service.ts— Fix 2.Verification
pnpm nx test sdk-uve(100/100),pnpm nx lint sdk-uve, andpnpm nx lint portlets-edit-ema-portletpass.Future work
Every fix here is a workaround for one design choice: UVE tears down and re-writes the entire iframe document on every in-editor change, which is what kills the listeners. The real fix is for the SDK to patch only the elements that actually changed — like the headless React/Angular SDKs already do — so there's no full-document teardown and these workarounds become unnecessary. Worth a dedicated issue.
Video
video.mov
🤖 Generated with Claude Code
This PR fixes: #36167