@sigx/lynx-runtime-main 0.4.0 → 0.4.2

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.
@@ -0,0 +1,449 @@
1
+ /**
2
+ * Main Thread ops executor.
3
+ *
4
+ * Receives the flat-array ops buffer sent by the Background Thread via
5
+ * callLepusMethod('sigxPatchUpdate', { data: JSON.stringify(ops) }) and applies
6
+ * each operation using Lynx PAPI.
7
+ */
8
+ import { OP } from '@sigx/lynx-runtime-internal';
9
+ import { processGesture } from './upstream/processGesture.js';
10
+ import { elements, pageUniqueId, setPageUniqueId, } from './element-registry.js';
11
+ import { resetWorkletEvents } from './worklet-events.js';
12
+ import { setSlotBgSign, setSlotWorklet, flushDirtySlots, resetSlotStates, } from './event-slots.js';
13
+ import { flushAvBridgePublishes, flushAnimatedStyleBindings, registerAnimatedStyleBinding, unregisterAnimatedStyleBinding, resetAnimatedStyleBindings, } from './animated-bridge-mt.js';
14
+ /**
15
+ * Placeholder element inserted by renderPage() to give the host a non-empty
16
+ * tree immediately, suppressing the "loadCard failed USER_RUNTIME_ERROR"
17
+ * timeout. Removed on the first applyOps() call.
18
+ */
19
+ let placeholderParent = null;
20
+ let placeholderEl = null;
21
+ /**
22
+ * SharedValue bridge state — registered wvids and last-published snapshots.
23
+ * The op handlers (`OP.REGISTER_AV_BRIDGE` / `OP.UNREGISTER_AV_BRIDGE` below)
24
+ * mutate these collections; `animated-bridge-mt.ts:flushAvBridgePublishes`
25
+ * reads them on every flush boundary to compute the diff to publish to BG.
26
+ */
27
+ export const bridgedAvWvids = new Set();
28
+ export const bridgedAvLastValues = new Map();
29
+ /**
30
+ * Gesture-detector tracking — per-element wvid → set of attached gesture ids.
31
+ * Used to drive the `has-react-gesture` setup attribute lifecycle: set on the
32
+ * first SET_GESTURE_DETECTOR for an element, cleared when the last gesture is
33
+ * removed via REMOVE_GESTURE_DETECTOR.
34
+ */
35
+ const gesturesByElementWvid = new Map();
36
+ /**
37
+ * Last-set BaseGesture tree per element wvid. Vendored upstream
38
+ * `processGesture` takes an `oldGesture` arg to diff against — pass the prior
39
+ * tree so callback updates wire correctly through `onWorkletCtxUpdate`.
40
+ */
41
+ const lastTreeByElementWvid = new Map();
42
+ /**
43
+ * elementWvid → elementId mapping populated by SET_MT_REF when a
44
+ * `main-thread:ref` binds to an element. Gesture ops carry the elRef's wvid
45
+ * (set at BG-side useGestureDetector time, before the renderer assigns an
46
+ * element id), so resolution is wvid → elementId → raw MainThreadElement.
47
+ *
48
+ * We deliberately do NOT use `lynxWorkletImpl._refImpl._workletRefMap[wvid].current`:
49
+ * that map stores the upstream-wrapped `Element` class (with `setStyleProperties`
50
+ * etc.) which is what worklets need, but the platform's `__SetAttribute` /
51
+ * `__SetGestureDetector` PAPI expect the raw RefCounted element handle.
52
+ * Passing the wrapper trips the `FiberSetAttribute param 0 should be RefCounted`
53
+ * native error.
54
+ */
55
+ const elementIdByWvid = new Map();
56
+ function resolveElementByWvid(wvid) {
57
+ const elementId = elementIdByWvid.get(wvid);
58
+ if (elementId === undefined)
59
+ return undefined;
60
+ return elements.get(elementId);
61
+ }
62
+ export function setPlaceholder(parent, el) {
63
+ placeholderParent = parent;
64
+ placeholderEl = el;
65
+ }
66
+ function removePlaceholderOnce() {
67
+ if (placeholderEl != null && placeholderParent != null) {
68
+ __RemoveElement(placeholderParent, placeholderEl);
69
+ placeholderParent = null;
70
+ placeholderEl = null;
71
+ }
72
+ }
73
+ /**
74
+ * Use typed PAPI creators for known element types.
75
+ * Native Lynx may set up type-specific internals (e.g. overflow clipping
76
+ * for View, hardware-accelerated decoding for Image) via the typed functions
77
+ * that the generic __CreateElement does not.
78
+ */
79
+ function createTypedElement(type, parentComponentUniqueId) {
80
+ switch (type) {
81
+ case 'view':
82
+ return __CreateView(parentComponentUniqueId);
83
+ case 'text':
84
+ return __CreateText(parentComponentUniqueId);
85
+ case 'image':
86
+ return __CreateImage(parentComponentUniqueId);
87
+ case 'scroll-view':
88
+ return __CreateScrollView(parentComponentUniqueId);
89
+ case 'page':
90
+ // The page root is special — it's created once by __CreatePage() in
91
+ // renderPage() and aliased to ShadowElement id=1 in BG. Lynx hosts
92
+ // (e.g. Lynx Go) have no behavior class for a second `page` element
93
+ // and will throw "No BehaviorController defined for class page".
94
+ // If user code wraps content in <page>...</page>, treat it as a
95
+ // transparent <view> on the Main Thread so the tree stays valid.
96
+ return __CreateView(parentComponentUniqueId);
97
+ default:
98
+ return __CreateElement(type, parentComponentUniqueId);
99
+ }
100
+ }
101
+ export function applyOps(ops) {
102
+ const len = ops.length;
103
+ if (len === 0)
104
+ return;
105
+ // On the first real ops batch, remove the placeholder element that
106
+ // renderPage() inserted to suppress the host's USER_RUNTIME_ERROR timeout.
107
+ removePlaceholderOnce();
108
+ // Detect duplicate batch from double BG bundle evaluation.
109
+ // Each __init_card_bundle__ invocation gets a fresh webpack module cache, so
110
+ // ShadowElement.nextId resets to 2, producing the same element IDs.
111
+ // If the first CREATE op targets an ID that already exists in our elements Map,
112
+ // this is a duplicate batch — skip it entirely.
113
+ if (len >= 3 && ops[0] === OP.CREATE) {
114
+ const firstId = ops[1];
115
+ if (elements.has(firstId)) {
116
+ return;
117
+ }
118
+ }
119
+ let i = 0;
120
+ while (i < len) {
121
+ const code = ops[i++];
122
+ switch (code) {
123
+ case OP.CREATE: {
124
+ const id = ops[i++];
125
+ const type = ops[i++];
126
+ let el;
127
+ if (type === '__comment') {
128
+ el = __CreateRawText('');
129
+ }
130
+ else {
131
+ el = createTypedElement(type, pageUniqueId);
132
+ __SetCSSId([el], 0);
133
+ }
134
+ elements.set(id, el);
135
+ if (type !== '__comment') {
136
+ __SetAttribute(el, `sigx-ref-${id}`, 1);
137
+ }
138
+ break;
139
+ }
140
+ case OP.CREATE_TEXT: {
141
+ const id = ops[i++];
142
+ const el = __CreateText(pageUniqueId);
143
+ __SetCSSId([el], 0);
144
+ elements.set(id, el);
145
+ __SetAttribute(el, `sigx-ref-${id}`, 1);
146
+ break;
147
+ }
148
+ case OP.INSERT: {
149
+ const parentId = ops[i++];
150
+ const childId = ops[i++];
151
+ const anchorId = ops[i++];
152
+ const parent = elements.get(parentId);
153
+ const child = elements.get(childId);
154
+ if (parent && child) {
155
+ if (anchorId === -1) {
156
+ __AppendElement(parent, child);
157
+ }
158
+ else {
159
+ const anchor = elements.get(anchorId);
160
+ if (anchor)
161
+ __InsertElementBefore(parent, child, anchor);
162
+ }
163
+ }
164
+ break;
165
+ }
166
+ case OP.REMOVE: {
167
+ const _parentId = ops[i++];
168
+ const childId = ops[i++];
169
+ const parent = elements.get(_parentId);
170
+ const child = elements.get(childId);
171
+ if (parent && child) {
172
+ __RemoveElement(parent, child);
173
+ }
174
+ break;
175
+ }
176
+ case OP.SET_PROP: {
177
+ const id = ops[i++];
178
+ const key = ops[i++];
179
+ const value = ops[i++];
180
+ const el = elements.get(id);
181
+ if (el)
182
+ __SetAttribute(el, key, value);
183
+ break;
184
+ }
185
+ case OP.SET_TEXT: {
186
+ const id = ops[i++];
187
+ const text = ops[i++];
188
+ const el = elements.get(id);
189
+ if (el)
190
+ __SetAttribute(el, 'text', text);
191
+ break;
192
+ }
193
+ case OP.SET_EVENT: {
194
+ const id = ops[i++];
195
+ const eventType = ops[i++];
196
+ const eventName = ops[i++];
197
+ const sign = ops[i++];
198
+ // Defer __AddEvent to flushDirtySlots (end of batch). When a worklet
199
+ // is also registered for the same slot, the slot machine combines
200
+ // them into a single hybrid registration.
201
+ setSlotBgSign(id, eventType, eventName, sign);
202
+ break;
203
+ }
204
+ case OP.REMOVE_EVENT: {
205
+ const id = ops[i++];
206
+ const eventType = ops[i++];
207
+ const eventName = ops[i++];
208
+ // Clear the BG side of this slot. The MT worklet (if any) survives.
209
+ // No worklet-removal op exists yet (see plan Open items).
210
+ setSlotBgSign(id, eventType, eventName, undefined);
211
+ break;
212
+ }
213
+ case OP.SET_STYLE: {
214
+ const id = ops[i++];
215
+ const value = ops[i++];
216
+ const el = elements.get(id);
217
+ if (el)
218
+ __SetInlineStyles(el, value);
219
+ break;
220
+ }
221
+ case OP.SET_CLASS: {
222
+ const id = ops[i++];
223
+ const cls = ops[i++];
224
+ const el = elements.get(id);
225
+ if (el)
226
+ __SetClasses(el, cls);
227
+ break;
228
+ }
229
+ case OP.SET_ID: {
230
+ const id = ops[i++];
231
+ const idStr = ops[i++];
232
+ const el = elements.get(id);
233
+ if (el)
234
+ __SetID(el, idStr ?? undefined);
235
+ break;
236
+ }
237
+ case OP.SET_WORKLET_EVENT: {
238
+ const id = ops[i++];
239
+ const eventType = ops[i++];
240
+ const eventName = ops[i++];
241
+ const ctx = ops[i++];
242
+ if (ctx && ctx._wkltId) {
243
+ ctx['_workletType'] = 'main-thread';
244
+ // Defer __AddEvent — flushDirtySlots will pick the right shape:
245
+ // worklet-only ({type:'worklet', value: ctx}) when no BG handler
246
+ // shares this slot, or hybrid ctx when one does.
247
+ setSlotWorklet(id, eventType, eventName, ctx);
248
+ }
249
+ break;
250
+ }
251
+ case OP.SET_MT_REF: {
252
+ const id = ops[i++];
253
+ const wvid = ops[i++];
254
+ const el = elements.get(id);
255
+ if (el) {
256
+ // Delegate to upstream's worklet-runtime. updateWorkletRef wraps the
257
+ // element in its own Element class and stores it under _wvid.
258
+ const impl = globalThis['lynxWorkletImpl'];
259
+ if (impl?._refImpl) {
260
+ const refMap = impl._refImpl._workletRefMap;
261
+ if (!(wvid in refMap)) {
262
+ refMap[wvid] = { current: null, _wvid: wvid };
263
+ }
264
+ impl._refImpl.updateWorkletRef({ _wvid: wvid }, el);
265
+ }
266
+ // Record wvid → raw elementId so SET_GESTURE_DETECTOR can resolve
267
+ // the unwrapped MainThreadElement for `__SetAttribute` /
268
+ // `__SetGestureDetector` (which require RefCounted handles, not
269
+ // upstream's Element wrapper).
270
+ elementIdByWvid.set(wvid, id);
271
+ }
272
+ break;
273
+ }
274
+ case OP.INIT_MT_REF: {
275
+ const wvid = ops[i++];
276
+ const initValue = ops[i++];
277
+ const impl = globalThis['lynxWorkletImpl'];
278
+ if (impl?._refImpl) {
279
+ const refMap = impl._refImpl._workletRefMap;
280
+ if (!(wvid in refMap)) {
281
+ refMap[wvid] = { current: initValue, _wvid: wvid };
282
+ }
283
+ }
284
+ break;
285
+ }
286
+ case OP.RELEASE_MT_REF: {
287
+ // Owning component unmounted on BG; drop the MT-side holder so the
288
+ // worklet ref map doesn't grow unbounded across navigation. Mirrors
289
+ // upstream's WorkletEvents.releaseWorkletRef path (we don't dispatch
290
+ // upstream's event because we manage the map ourselves via ops).
291
+ const wvid = ops[i++];
292
+ const impl = globalThis['lynxWorkletImpl'];
293
+ if (impl?._refImpl) {
294
+ delete impl._refImpl._workletRefMap[wvid];
295
+ }
296
+ elementIdByWvid.delete(wvid);
297
+ break;
298
+ }
299
+ case OP.REGISTER_AV_BRIDGE: {
300
+ const wvid = ops[i++];
301
+ const initValue = ops[i++];
302
+ bridgedAvWvids.add(wvid);
303
+ bridgedAvLastValues.set(wvid, initValue);
304
+ break;
305
+ }
306
+ case OP.UNREGISTER_AV_BRIDGE: {
307
+ const wvid = ops[i++];
308
+ bridgedAvWvids.delete(wvid);
309
+ bridgedAvLastValues.delete(wvid);
310
+ break;
311
+ }
312
+ case OP.REGISTER_AV_STYLE_BINDING: {
313
+ const bindingId = ops[i++];
314
+ const elementWvid = ops[i++];
315
+ const avWvid = ops[i++];
316
+ const mapperName = ops[i++];
317
+ const params = ops[i++];
318
+ registerAnimatedStyleBinding(bindingId, elementWvid, avWvid, mapperName, params);
319
+ break;
320
+ }
321
+ case OP.UNREGISTER_AV_STYLE_BINDING: {
322
+ const bindingId = ops[i++];
323
+ unregisterAnimatedStyleBinding(bindingId);
324
+ break;
325
+ }
326
+ case OP.SET_GESTURE_DETECTOR: {
327
+ // Wire format: [op, wvid, gestureId, type, config, relationMap].
328
+ // We reconstruct upstream's BaseGesture shape and delegate to vendored
329
+ // `processGesture` so the platform-call sequence is byte-for-byte
330
+ // identical to `@lynx-js/react`'s snapshot pipeline. Per-base wire
331
+ // means we register one base per op; processGesture handles the
332
+ // single-base fast path. Composed gestures arrive as multiple ops,
333
+ // each carrying its relationMap.
334
+ const elementWvid = ops[i++];
335
+ const gestureId = ops[i++];
336
+ const type = ops[i++];
337
+ const config = ops[i++];
338
+ const relationMap = ops[i++];
339
+ const el = resolveElementByWvid(elementWvid);
340
+ if (!el)
341
+ break;
342
+ // Reconstruct callbacks Record from the wire's array shape.
343
+ const callbacksRecord = {};
344
+ for (const cb of config.callbacks) {
345
+ callbacksRecord[cb.name] = cb.callback;
346
+ }
347
+ // Build a fake BaseGesture: relation arrays are id-stubs `[{id}]`
348
+ // because vendored `getGestureInfo` reads `.id` off each entry to
349
+ // produce the relationMap. The platform never sees these objects.
350
+ const stub = (ids) => ids.map((id) => ({ id }));
351
+ const fakeBaseGesture = {
352
+ __isSerialized: true,
353
+ type,
354
+ id: gestureId,
355
+ callbacks: callbacksRecord,
356
+ waitFor: stub(relationMap.waitFor),
357
+ simultaneousWith: stub(relationMap.simultaneous),
358
+ continueWith: stub(relationMap.continueWith),
359
+ ...(config.config ? { config: config.config } : {}),
360
+ };
361
+ // Phase 2.12.1 bug fix: pass `undefined` as oldGesture, NOT the last
362
+ // tree we saw on this element.
363
+ //
364
+ // Our wire format is one SET_GESTURE_DETECTOR op per BaseGesture.
365
+ // When `<Pressable>` registers `Simultaneous(Tap, LongPress)`, two
366
+ // ops arrive in sequence on the same element. If we pass the previous
367
+ // tree to `processGesture`, its diff path treats the second op as
368
+ // "Tap → LongPress" and emits a `__RemoveGestureDetector` for Tap
369
+ // before installing LongPress. Result: only the last gesture stays
370
+ // registered; all earlier gestures are silently uninstalled.
371
+ //
372
+ // The right model for our wire is additive: each op installs ONE
373
+ // gesture without disturbing siblings. Removal is explicit via the
374
+ // REMOVE_GESTURE_DETECTOR op (emitted from `useGestureDetector`'s
375
+ // unmount cleanup), which calls `__RemoveGestureDetector` directly.
376
+ // Note: this means we don't get diff-based callback updates if the
377
+ // BG side re-emits a gesture with the same id — but our wire never
378
+ // does that today; on prop changes BG emits REMOVE then SET.
379
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
380
+ processGesture(el, fakeBaseGesture, undefined, false);
381
+ lastTreeByElementWvid.set(elementWvid, fakeBaseGesture);
382
+ // Track for REMOVE op cleanup and to drive `has-react-gesture` toggle
383
+ // when the last gesture goes (vendored function clears it on remove).
384
+ let attached = gesturesByElementWvid.get(elementWvid);
385
+ if (!attached) {
386
+ attached = new Set();
387
+ gesturesByElementWvid.set(elementWvid, attached);
388
+ }
389
+ attached.add(gestureId);
390
+ break;
391
+ }
392
+ case OP.REMOVE_GESTURE_DETECTOR: {
393
+ const elementWvid = ops[i++];
394
+ const gestureId = ops[i++];
395
+ const el = resolveElementByWvid(elementWvid);
396
+ if (el && typeof __RemoveGestureDetector === 'function') {
397
+ __RemoveGestureDetector(el, gestureId);
398
+ const attached = gesturesByElementWvid.get(elementWvid);
399
+ if (attached) {
400
+ attached.delete(gestureId);
401
+ if (attached.size === 0) {
402
+ gesturesByElementWvid.delete(elementWvid);
403
+ lastTreeByElementWvid.delete(elementWvid);
404
+ __SetAttribute(el, 'has-react-gesture', null);
405
+ }
406
+ }
407
+ }
408
+ break;
409
+ }
410
+ default:
411
+ // Unknown op – skip (future-compat)
412
+ break;
413
+ }
414
+ }
415
+ // Commit deferred __AddEvent registrations now that the entire batch is
416
+ // processed — this is what lets worklet + BG handler on the same slot
417
+ // coexist via the hybrid worklet ctx, without one overwriting the other.
418
+ flushDirtySlots();
419
+ // Diff registered SharedValues against their last-published snapshots
420
+ // and dispatch a batched Lynx.Sigx.AvPublish event with anything that
421
+ // changed during this op batch. See animated-bridge-mt.ts for details.
422
+ flushAvBridgePublishes();
423
+ // Apply any useAnimatedStyle bindings whose source SharedValue changed
424
+ // during this batch. Runs after flushAvBridgePublishes so the BG mirror
425
+ // stays consistent with the styles we're about to commit.
426
+ flushAnimatedStyleBindings();
427
+ // Flush all pending PAPI changes to the native layer in one shot.
428
+ __FlushElementTree();
429
+ }
430
+ /** Reset module state — for testing and hot reload. */
431
+ export function resetMainThreadState() {
432
+ elements.clear();
433
+ setPageUniqueId(1);
434
+ placeholderParent = null;
435
+ placeholderEl = null;
436
+ // Also defined in this module's imports — reset worklet state
437
+ resetWorkletEvents();
438
+ resetSlotStates();
439
+ bridgedAvWvids.clear();
440
+ bridgedAvLastValues.clear();
441
+ gesturesByElementWvid.clear();
442
+ lastTreeByElementWvid.clear();
443
+ elementIdByWvid.clear();
444
+ resetAnimatedStyleBindings();
445
+ // Clear upstream's worklet ref map too on hard reset (HMR / test).
446
+ const impl = globalThis['lynxWorkletImpl'];
447
+ if (impl?._refImpl)
448
+ impl._refImpl._workletRefMap = {};
449
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * MT-side runOnBackground — dispatches function calls from the Main Thread
3
+ * to the Background Thread via 'Lynx.Sigx.RunOnBackground' events.
4
+ *
5
+ * Called inside extracted worklet bodies on the Main Thread. The SWC LEPUS
6
+ * pass leaves bare `runOnBackground(_jsFnK)` references in the registered
7
+ * worklet body; we install this implementation as `globalThis.runOnBackground`
8
+ * from `entry-main.ts` so the bare identifier resolves at runtime.
9
+ *
10
+ * Mirrors @lynx-js/react/runtime/lib/worklet/call/runOnMainThread (the dual
11
+ * direction) and vue-lynx's run-on-background-mt.ts. Sigx-namespaced event
12
+ * types so we don't conflict with upstream's own bridge if it ships in the
13
+ * same lynx process.
14
+ */
15
+ const RUN_ON_BACKGROUND = 'Lynx.Sigx.RunOnBackground';
16
+ const FUNCTION_CALL_RET = 'Lynx.Sigx.FunctionCallRet';
17
+ // ---------------------------------------------------------------------------
18
+ // Return-value resolver — correlates resolveId → Promise resolve callback
19
+ // ---------------------------------------------------------------------------
20
+ let resolveMap;
21
+ let nextResolveId = 1;
22
+ function getJSContext() {
23
+ // On MT, `lynx` is a globalThis property (no closure injection like BG).
24
+ const lynxObj = globalThis.lynx;
25
+ return lynxObj?.getJSContext?.();
26
+ }
27
+ function initReturnListener() {
28
+ resolveMap = new Map();
29
+ getJSContext()?.addEventListener?.(FUNCTION_CALL_RET, (event) => {
30
+ let payload;
31
+ try {
32
+ payload = JSON.parse(event.data);
33
+ }
34
+ catch {
35
+ return;
36
+ }
37
+ const resolve = resolveMap?.get(payload.resolveId);
38
+ if (resolve) {
39
+ resolveMap.delete(payload.resolveId);
40
+ resolve(payload.returnValue);
41
+ }
42
+ });
43
+ }
44
+ // ---------------------------------------------------------------------------
45
+ // dispatch — ship the call across to BG
46
+ // ---------------------------------------------------------------------------
47
+ function dispatch(fnId, params, execId, resolveId) {
48
+ getJSContext()?.dispatchEvent?.({
49
+ type: RUN_ON_BACKGROUND,
50
+ data: JSON.stringify({
51
+ obj: { _jsFnId: fnId, _execId: execId },
52
+ params,
53
+ resolveId,
54
+ }),
55
+ });
56
+ }
57
+ // ---------------------------------------------------------------------------
58
+ // runOnBackground — the global function called in extracted LEPUS code
59
+ // ---------------------------------------------------------------------------
60
+ export function runOnBackground(handle) {
61
+ return (...params) => {
62
+ return new Promise((resolve) => {
63
+ if (!resolveMap)
64
+ initReturnListener();
65
+ const resolveId = nextResolveId++;
66
+ resolveMap.set(resolveId, resolve);
67
+ if (handle._isFirstScreen
68
+ && typeof lynxWorkletImpl !== 'undefined'
69
+ && lynxWorkletImpl?._runOnBackgroundDelayImpl) {
70
+ lynxWorkletImpl._runOnBackgroundDelayImpl.delayRunOnBackground(handle, (fnId, execId) => dispatch(fnId, params, execId, resolveId));
71
+ return;
72
+ }
73
+ if (handle._jsFnId == null || handle._execId == null) {
74
+ // Handle never carried a (fnId, execId) pair — most likely the BG
75
+ // sender did not call registerWorkletCtx. Resolve undefined so the
76
+ // worklet promise settles instead of leaking.
77
+ resolveMap.delete(resolveId);
78
+ resolve(undefined);
79
+ return;
80
+ }
81
+ dispatch(handle._jsFnId, params, handle._execId, resolveId);
82
+ });
83
+ };
84
+ }
85
+ // ---------------------------------------------------------------------------
86
+ // Reset — for testing only
87
+ // ---------------------------------------------------------------------------
88
+ export function resetRunOnBackgroundMtState() {
89
+ resolveMap = undefined;
90
+ nextResolveId = 1;
91
+ }
@@ -0,0 +1,39 @@
1
+ // Copyright 2025 The Lynx Authors. All rights reserved.
2
+ // TypeScript types added 2026 by SignalX contributors.
3
+ //
4
+ // Licensed under the Apache License, Version 2.0. The full license text and
5
+ // upstream attribution are reproduced in `THIRD_PARTY_NOTICES.md` at the
6
+ // root of this package (`@sigx/lynx-runtime-main`). The MIT LICENSE at the
7
+ // repository root governs the rest of this repository; it does NOT apply
8
+ // to this file.
9
+ //
10
+ // Vendored from `@lynx-js/react@0.120.0`'s
11
+ // `runtime/lib/worklet-runtime/bindings/observers.js`. Source preserved
12
+ // verbatim; only types added. We vendor this 15-LOC helper to drop the
13
+ // `@lynx-js/react` runtime dependency from `@sigx/lynx-runtime-main`, which
14
+ // in turn keeps `@types/react` out of the install tree of consumer apps
15
+ // (sigx-lynx apps use sigx primitives, not ReactLynx JSX).
16
+ function impl() {
17
+ return globalThis.lynxWorkletImpl;
18
+ }
19
+ /**
20
+ * Must be called when a worklet context is updated. Mirrors the upstream
21
+ * ReactLynx behaviour:
22
+ * 1. Register the new worklet with the JS-side lifecycle manager (so the
23
+ * background thread can free it).
24
+ * 2. On first screen with a previous ctx, hydrate the new ctx from the old.
25
+ * 3. On first screen, flush any worklets that were delayed waiting for this
26
+ * element (legacy dynamic-component compat path).
27
+ */
28
+ export function onWorkletCtxUpdate(worklet, oldWorklet, isFirstScreen, element) {
29
+ const w = impl();
30
+ if (worklet._execId !== undefined) {
31
+ w?._jsFunctionLifecycleManager?.addRef(worklet._execId, worklet);
32
+ }
33
+ if (isFirstScreen && oldWorklet) {
34
+ w?._hydrateCtx(worklet, oldWorklet);
35
+ }
36
+ if (isFirstScreen) {
37
+ w?._eventDelayImpl.runDelayedWorklet(worklet, element);
38
+ }
39
+ }