|
7 | 7 | */ |
8 | 8 |
|
9 | 9 | // tslint:disable:no-duplicate-imports |
10 | | -import {EventContract} from '../primitives/event-dispatch'; |
| 10 | +import type {EventContract} from '../primitives/event-dispatch'; |
11 | 11 | import {Attribute} from '../primitives/event-dispatch'; |
| 12 | +import {APP_ID} from './application/application_tokens'; |
12 | 13 | import {InjectionToken} from './di'; |
13 | | -import {RElement} from './render3/interfaces/renderer_dom'; |
| 14 | +import type {RElement, RNode} from './render3/interfaces/renderer_dom'; |
| 15 | +import {INJECTOR, type LView} from './render3/interfaces/view'; |
14 | 16 |
|
15 | 17 | export const DEFER_BLOCK_SSR_ID_ATTRIBUTE = 'ngb'; |
16 | 18 |
|
@@ -109,3 +111,89 @@ export function invokeListeners(event: Event, currentTarget: Element | null) { |
109 | 111 | handler(event); |
110 | 112 | } |
111 | 113 | } |
| 114 | + |
| 115 | +/** Shorthand for an event listener callback function to reduce duplication. */ |
| 116 | +export type EventCallback = (event?: any) => any; |
| 117 | + |
| 118 | +/** Utility type used to make it harder to swap a wrapped and unwrapped callback. */ |
| 119 | +export type WrappedEventCallback = EventCallback & {__wrapped: boolean}; |
| 120 | + |
| 121 | +/** |
| 122 | + * Represents a signature of a function that disables event replay feature |
| 123 | + * for server-side rendered applications. This function is overridden with |
| 124 | + * an actual implementation when the event replay feature is enabled via |
| 125 | + * `withEventReplay()` call. |
| 126 | + */ |
| 127 | +type StashEventListener = (el: RNode, eventName: string, listenerFn: EventCallback) => void; |
| 128 | + |
| 129 | +const stashEventListeners = new Map<string, StashEventListener>(); |
| 130 | + |
| 131 | +/** |
| 132 | + * Registers a stashing function for a specific application ID. |
| 133 | + * |
| 134 | + * @param appId The unique identifier for the application instance. |
| 135 | + * @param fn The stashing function to associate with this app ID. |
| 136 | + * @returns A cleanup function that removes the stashing function when called. |
| 137 | + */ |
| 138 | +export function setStashFn(appId: string, fn: StashEventListener) { |
| 139 | + stashEventListeners.set(appId, fn); |
| 140 | + return () => stashEventListeners.delete(appId); |
| 141 | +} |
| 142 | + |
| 143 | +/** |
| 144 | + * Indicates whether the stashing code was added, prevents adding it multiple times. |
| 145 | + */ |
| 146 | +let isStashEventListenerImplEnabled = false; |
| 147 | + |
| 148 | +let _stashEventListenerImpl = ( |
| 149 | + lView: LView, |
| 150 | + target: RElement | EventTarget, |
| 151 | + eventName: string, |
| 152 | + wrappedListener: WrappedEventCallback, |
| 153 | +) => {}; |
| 154 | + |
| 155 | +/** |
| 156 | + * Optionally stashes an event listener for later replay during hydration. |
| 157 | + * |
| 158 | + * This function delegates to an internal `_stashEventListenerImpl`, which may |
| 159 | + * be a no-op unless the event replay feature is enabled. When active, this |
| 160 | + * allows capturing event listener metadata before hydration completes, so that |
| 161 | + * user interactions during SSR can be replayed. |
| 162 | + * |
| 163 | + * @param lView The logical view (LView) where the listener is being registered. |
| 164 | + * @param target The DOM element or event target the listener is attached to. |
| 165 | + * @param eventName The name of the event being listened for (e.g., 'click'). |
| 166 | + * @param wrappedListener The event handler that was registered. |
| 167 | + */ |
| 168 | +export function stashEventListenerImpl( |
| 169 | + lView: LView, |
| 170 | + target: RElement | EventTarget, |
| 171 | + eventName: string, |
| 172 | + wrappedListener: WrappedEventCallback, |
| 173 | +): void { |
| 174 | + _stashEventListenerImpl(lView, target, eventName, wrappedListener); |
| 175 | +} |
| 176 | + |
| 177 | +/** |
| 178 | + * Enables the event listener stashing logic in a tree-shakable way. |
| 179 | + * |
| 180 | + * This function lazily sets the implementation of `_stashEventListenerImpl` |
| 181 | + * so that it becomes active only when `withEventReplay` is invoked. This ensures |
| 182 | + * that the stashing logic is excluded from production builds unless needed. |
| 183 | + */ |
| 184 | +export function enableStashEventListenerImpl(): void { |
| 185 | + if (!isStashEventListenerImplEnabled) { |
| 186 | + _stashEventListenerImpl = ( |
| 187 | + lView: LView, |
| 188 | + target: RElement | EventTarget, |
| 189 | + eventName: string, |
| 190 | + wrappedListener: EventCallback, |
| 191 | + ) => { |
| 192 | + const appId = lView[INJECTOR].get(APP_ID); |
| 193 | + const stashEventListener = stashEventListeners.get(appId); |
| 194 | + stashEventListener?.(target as RElement, eventName, wrappedListener); |
| 195 | + }; |
| 196 | + |
| 197 | + isStashEventListenerImplEnabled = true; |
| 198 | + } |
| 199 | +} |
0 commit comments