@sigx/lynx-runtime 0.2.4
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/LICENSE +21 -0
- package/README.md +42 -0
- package/dist/animated/animated-value.d.ts +16 -0
- package/dist/animated/shared-value.d.ts +67 -0
- package/dist/animated/use-animated-style.d.ts +37 -0
- package/dist/animated-bridge.d.ts +45 -0
- package/dist/bg-bridge.d.ts +29 -0
- package/dist/event-registry.d.ts +37 -0
- package/dist/flush.d.ts +8 -0
- package/dist/hmr.d.ts +35 -0
- package/dist/hmr.js +38 -0
- package/dist/hmr.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +849 -0
- package/dist/index.js.map +1 -0
- package/dist/jsx.d.ts +340 -0
- package/dist/main-thread-ref.d.ts +88 -0
- package/dist/model-processor.d.ts +24 -0
- package/dist/mt-hmr-bridge.d.ts +47 -0
- package/dist/mt-hmr-bridge.js +58 -0
- package/dist/mt-hmr-bridge.js.map +1 -0
- package/dist/native/gesture-detector.d.ts +151 -0
- package/dist/native/index.d.ts +2 -0
- package/dist/nodeOps.d.ts +14 -0
- package/dist/op-queue.d.ts +55 -0
- package/dist/render.d.ts +22 -0
- package/dist/run-on-background.d.ts +37 -0
- package/dist/shadow-element.d.ts +30 -0
- package/dist/threading.d.ts +41 -0
- package/dist/types.d.ts +17 -0
- package/package.json +66 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MainThreadRef — a ref whose `.current` value lives on the Main Thread.
|
|
3
|
+
*
|
|
4
|
+
* On the Background Thread, `.current` is always the initial value (typically
|
|
5
|
+
* `null`). On the Main Thread, `.current` is set to the real Lynx element
|
|
6
|
+
* handle when the ref is bound via `main-thread:ref={ref}`.
|
|
7
|
+
*
|
|
8
|
+
* This is the sigx equivalent of react-lynx's `useMainThreadRef()` and
|
|
9
|
+
* vue-lynx's `useMainThreadRef()`. It enables zero-latency style updates
|
|
10
|
+
* and animations by giving main-thread event handlers synchronous access
|
|
11
|
+
* to native elements.
|
|
12
|
+
*
|
|
13
|
+
* Architecture:
|
|
14
|
+
* BG: useMainThreadRef(init) → MainThreadRef { wvid, current: init }
|
|
15
|
+
* → pushOp(INIT_MT_REF, wvid, init)
|
|
16
|
+
* BG: patchProp('main-thread:ref', ref) → pushOp(SET_MT_REF, elId, wvid)
|
|
17
|
+
* MT: INIT_MT_REF → workletRefs.set(wvid, { current: init })
|
|
18
|
+
* MT: SET_MT_REF → workletRefs.get(wvid).current = elements.get(elId)
|
|
19
|
+
*/
|
|
20
|
+
export declare function resetWvidCounter(): void;
|
|
21
|
+
/**
|
|
22
|
+
* A ref whose `.current` property is managed on the Main Thread.
|
|
23
|
+
*
|
|
24
|
+
* On the BG thread, `.current` returns the `initValue` and is read-only
|
|
25
|
+
* (setting it has no effect — the real value lives on MT).
|
|
26
|
+
*
|
|
27
|
+
* In main-thread event handlers and `runOnMainThread` callbacks, `.current`
|
|
28
|
+
* is the real Lynx element handle with methods like `setStyleProperties()`,
|
|
29
|
+
* `getComputedStyleProperty()`, and `animate()`.
|
|
30
|
+
*/
|
|
31
|
+
export declare class MainThreadRef<T = unknown> {
|
|
32
|
+
/**
|
|
33
|
+
* Worklet variable ID — uniquely identifies this ref across threads.
|
|
34
|
+
* Underscored to match the field name `transformWorklet` walks for
|
|
35
|
+
* in @lynx-js/react/worklet-runtime when expanding `_c` captures.
|
|
36
|
+
*/
|
|
37
|
+
readonly _wvid: number;
|
|
38
|
+
/**
|
|
39
|
+
* Initial value snapshot — sent to MT in INIT_MT_REF and used by
|
|
40
|
+
* the worklet-runtime to seed the firstScreen ref map.
|
|
41
|
+
*/
|
|
42
|
+
readonly _initValue: T;
|
|
43
|
+
/**
|
|
44
|
+
* On BG: the init value (read-only snapshot).
|
|
45
|
+
* On MT: the real element handle (set by SET_MT_REF op).
|
|
46
|
+
*/
|
|
47
|
+
current: T;
|
|
48
|
+
constructor(initValue: T);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Walk a captured `_c` map and serialize MainThreadRef instances to plain
|
|
52
|
+
* `{ _wvid, _initValue }` objects so they survive the JSON round-trip across
|
|
53
|
+
* the BG→MT bridge. Upstream's worklet-runtime walks `_c` looking for `_wvid`
|
|
54
|
+
* to recognize ref captures and resolve them via
|
|
55
|
+
* `lynxWorkletImpl._refImpl._workletRefMap`.
|
|
56
|
+
*
|
|
57
|
+
* Used by both the SET_WORKLET_EVENT path (`nodeOps.patchProp`) and the
|
|
58
|
+
* SET_GESTURE_DETECTOR path (`native/gesture-detector.ts`).
|
|
59
|
+
*/
|
|
60
|
+
export declare function sanitizeCaptured(captured: Record<string, unknown>): Record<string, unknown>;
|
|
61
|
+
/**
|
|
62
|
+
* Create a ref that provides synchronous access to a native element on the
|
|
63
|
+
* Main Thread. Bind it to an element via `main-thread:ref={ref}`.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```tsx
|
|
67
|
+
* const elRef = useMainThreadRef<MainThread.Element>(null);
|
|
68
|
+
*
|
|
69
|
+
* function handleScroll(e: ScrollEvent) {
|
|
70
|
+
* 'main thread';
|
|
71
|
+
* const offset = e.detail.scrollTop;
|
|
72
|
+
* elRef.current?.setStyleProperties({
|
|
73
|
+
* transform: `translateY(${-offset}px)`,
|
|
74
|
+
* });
|
|
75
|
+
* }
|
|
76
|
+
*
|
|
77
|
+
* return (
|
|
78
|
+
* <scroll-view
|
|
79
|
+
* main-thread-bindscroll={handleScroll}
|
|
80
|
+
* >
|
|
81
|
+
* <view main-thread:ref={elRef}>
|
|
82
|
+
* <text>Sticky header</text>
|
|
83
|
+
* </view>
|
|
84
|
+
* </scroll-view>
|
|
85
|
+
* );
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export declare function useMainThreadRef<T = unknown>(initValue: T): MainThreadRef<T>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform-specific model processor for Lynx form elements.
|
|
3
|
+
*
|
|
4
|
+
* Wires sigx's `model={() => state.field}` two-way binding directive to
|
|
5
|
+
* Lynx's <input> and <textarea> elements. The processor runs at JSX
|
|
6
|
+
* creation time (called from runtime-core/jsx-runtime.ts when an
|
|
7
|
+
* intrinsic element has a `model` prop) and rewrites the props to:
|
|
8
|
+
*
|
|
9
|
+
* 1. Set the initial `value` from the bound state
|
|
10
|
+
* 2. Install a `bindinput` handler that pushes the new value back into state
|
|
11
|
+
*
|
|
12
|
+
* Lynx differs from DOM in two important ways:
|
|
13
|
+
*
|
|
14
|
+
* - Lynx <input> fires `bindinput` with `event.detail.value` containing
|
|
15
|
+
* the new text. There is no DOM-style `target.value` property — the
|
|
16
|
+
* value lives on the event detail object.
|
|
17
|
+
* - Lynx Go (and most current Lynx hosts) have no native checkbox or
|
|
18
|
+
* radio elements. We only handle text-style <input> and <textarea>
|
|
19
|
+
* here. Adding checkbox/radio later is straightforward — mirror the
|
|
20
|
+
* branches in packages/runtime-dom/src/model-processor.ts.
|
|
21
|
+
*
|
|
22
|
+
* Mirrors packages/runtime-dom/src/model-processor.ts.
|
|
23
|
+
*/
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BG → MT hot-update bridge.
|
|
3
|
+
*
|
|
4
|
+
* The MT bundle has rspack's HMR runtime in code (because `module.hot.accept`
|
|
5
|
+
* is referenced) but no transport feeding it updates. After a save, BG's HMR
|
|
6
|
+
* client patches App.tsx and re-renders, generating worklet placeholders with
|
|
7
|
+
* new content-hash `_wkltId`s. MT's `_workletMap` still holds the old IDs, so
|
|
8
|
+
* the lookup fails (`bind of undefined`) when the user taps.
|
|
9
|
+
*
|
|
10
|
+
* This bridge closes the gap. We hook the same `webpackHotUpdate` event the
|
|
11
|
+
* rspack HMR client subscribes to. On every cycle:
|
|
12
|
+
* 1. Fetch the matching `main__main-thread.<hash>.hot-update.js` over the
|
|
13
|
+
* dev server URL (`__webpack_require__.p`).
|
|
14
|
+
* 2. Extract every `registerWorkletInternal(...)` call from the response.
|
|
15
|
+
* 3. Forward the concatenated calls to MT via
|
|
16
|
+
* `callLepusMethod('sigxApplyMtHotUpdate', { code }, ...)`.
|
|
17
|
+
* The MT handler `eval`s them in the existing realm, registering the new IDs
|
|
18
|
+
* into the live `_workletMap` before the user taps a freshly re-rendered
|
|
19
|
+
* button.
|
|
20
|
+
*
|
|
21
|
+
* Loaded only in dev mode — wired into the BG entry by `lynx-plugin`'s
|
|
22
|
+
* `applyEntry` when `enabledHMR` is true.
|
|
23
|
+
*/
|
|
24
|
+
interface RspackEmitter {
|
|
25
|
+
on(event: 'webpackHotUpdate', cb: (currentHash: string) => void): void;
|
|
26
|
+
}
|
|
27
|
+
declare const __webpack_require__: {
|
|
28
|
+
p?: string;
|
|
29
|
+
c?: Record<string, {
|
|
30
|
+
exports: unknown;
|
|
31
|
+
}>;
|
|
32
|
+
hu?: (chunkId: string) => string;
|
|
33
|
+
};
|
|
34
|
+
declare function findRspackEmitter(): RspackEmitter | undefined;
|
|
35
|
+
interface HotUpdate {
|
|
36
|
+
modules: Record<string, (...args: unknown[]) => unknown>;
|
|
37
|
+
runtime?: unknown;
|
|
38
|
+
}
|
|
39
|
+
declare function fetchAndForward(): void;
|
|
40
|
+
/**
|
|
41
|
+
* Extract `registerWorkletInternal(...)` calls from a hot-update body.
|
|
42
|
+
*
|
|
43
|
+
* Mirrors `lynx-plugin/src/loaders/worklet-utils.ts:extractRegistrations`
|
|
44
|
+
* (duplicated here to avoid a runtime → build-time dep). Bracket-depth count
|
|
45
|
+
* handles nested braces in the function body.
|
|
46
|
+
*/
|
|
47
|
+
declare function extractRegistrations(source: string): string;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
//#region src/mt-hmr-bridge.ts
|
|
2
|
+
function e() {
|
|
3
|
+
let e = __webpack_require__?.c;
|
|
4
|
+
if (e) for (let t in e) {
|
|
5
|
+
if (!t.includes("@rspack") || !t.endsWith("emitter.js")) continue;
|
|
6
|
+
let n = e[t]?.exports;
|
|
7
|
+
if (n && typeof n.on == "function" && typeof n.emit == "function") return n;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
Promise.resolve().then(() => {
|
|
11
|
+
let n = e();
|
|
12
|
+
if (!n) {
|
|
13
|
+
console.log("[sigx-mt-hmr-bridge] rspack emitter not found — bridge inactive");
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
n.on("webpackHotUpdate", () => {
|
|
17
|
+
t();
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
function t() {
|
|
21
|
+
let e = __webpack_require__?.p ?? "", t = __webpack_require__?.hu;
|
|
22
|
+
if (typeof t != "function") return;
|
|
23
|
+
let r = e + t("main__main-thread"), i = lynx?.requireModuleAsync;
|
|
24
|
+
typeof i == "function" && i(r, (e, t) => {
|
|
25
|
+
if (e) {
|
|
26
|
+
console.log("[sigx-mt-hmr-bridge] requireModuleAsync failed:", String(e));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
let r = "";
|
|
30
|
+
for (let e in t.modules) {
|
|
31
|
+
let n = t.modules[e];
|
|
32
|
+
typeof n == "function" && (r += n.toString() + "\n");
|
|
33
|
+
}
|
|
34
|
+
let i = n(r);
|
|
35
|
+
if (!i) return;
|
|
36
|
+
let a = lynx?.getNativeApp?.();
|
|
37
|
+
!a || typeof a.callLepusMethod != "function" || a.callLepusMethod("sigxApplyMtHotUpdate", { code: i }, () => {});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function n(e) {
|
|
41
|
+
let t = [], n = 0;
|
|
42
|
+
for (;;) {
|
|
43
|
+
let r = e.indexOf("registerWorkletInternal(", n);
|
|
44
|
+
if (r === -1) break;
|
|
45
|
+
let i = 0, a = r + 24 - 1;
|
|
46
|
+
for (; a < e.length; a++) {
|
|
47
|
+
let t = e[a];
|
|
48
|
+
if (t === "(") i++;
|
|
49
|
+
else if (t === ")" && (i--, i === 0)) break;
|
|
50
|
+
}
|
|
51
|
+
let o = a + 1;
|
|
52
|
+
o < e.length && e[o] === ";" && o++, t.push(e.slice(r, o)), n = o;
|
|
53
|
+
}
|
|
54
|
+
return t.join("\n");
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
|
|
58
|
+
//# sourceMappingURL=mt-hmr-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mt-hmr-bridge.js","names":[],"sources":["../src/mt-hmr-bridge.ts"],"sourcesContent":["/**\n * BG → MT hot-update bridge.\n *\n * The MT bundle has rspack's HMR runtime in code (because `module.hot.accept`\n * is referenced) but no transport feeding it updates. After a save, BG's HMR\n * client patches App.tsx and re-renders, generating worklet placeholders with\n * new content-hash `_wkltId`s. MT's `_workletMap` still holds the old IDs, so\n * the lookup fails (`bind of undefined`) when the user taps.\n *\n * This bridge closes the gap. We hook the same `webpackHotUpdate` event the\n * rspack HMR client subscribes to. On every cycle:\n * 1. Fetch the matching `main__main-thread.<hash>.hot-update.js` over the\n * dev server URL (`__webpack_require__.p`).\n * 2. Extract every `registerWorkletInternal(...)` call from the response.\n * 3. Forward the concatenated calls to MT via\n * `callLepusMethod('sigxApplyMtHotUpdate', { code }, ...)`.\n * The MT handler `eval`s them in the existing realm, registering the new IDs\n * into the live `_workletMap` before the user taps a freshly re-rendered\n * button.\n *\n * Loaded only in dev mode — wired into the BG entry by `lynx-plugin`'s\n * `applyEntry` when `enabledHMR` is true.\n */\n\ninterface RspackEmitter {\n on(event: 'webpackHotUpdate', cb: (currentHash: string) => void): void;\n}\n\n// `@rspack/core/hot/emitter` is a CJS singleton EventEmitter the rspack HMR\n// client subscribes to. We can't `import` it here — `@rspack/core` is a\n// transitive build-time dep of the user app via rspeedy, not a runtime dep\n// of this package, so rspack's resolver fails from our install location.\n//\n// Instead, locate it through the BG bundle's webpack module cache at runtime:\n// after `@rspack/core/hot/dev-server` initialises (also injected into the BG\n// entry by lynx-plugin), the emitter module is registered in\n// `__webpack_require__.c` keyed by its resolved path. We scan the cache for\n// the one whose `module.exports` looks like the EventEmitter singleton (has\n// `on` + `emit` + an `events` bag).\ndeclare const __webpack_require__: {\n p?: string;\n c?: Record<string, { exports: unknown }>;\n hu?: (chunkId: string) => string;\n};\n\nfunction findRspackEmitter(): RspackEmitter | undefined {\n const cache = __webpack_require__?.c;\n if (!cache) return undefined;\n for (const id in cache) {\n if (!id.includes('@rspack') || !id.endsWith('emitter.js')) continue;\n const exp = cache[id]?.exports as Record<string, unknown> | undefined;\n if (\n exp\n && typeof (exp as { on?: unknown }).on === 'function'\n && typeof (exp as { emit?: unknown }).emit === 'function'\n ) {\n return exp as unknown as RspackEmitter;\n }\n }\n return undefined;\n}\n\n// Defer subscription via a microtask: the entry chain prepends this bridge\n// before `@rspack/core/hot/dev-server`, so at module-eval time the emitter\n// isn't in the webpack cache yet. By the next microtask, dev-server has run\n// its top-level code and the emitter module is cached.\nPromise.resolve().then(() => {\n const emitter = findRspackEmitter();\n if (!emitter) {\n console.log('[sigx-mt-hmr-bridge] rspack emitter not found — bridge inactive');\n return;\n }\n emitter.on('webpackHotUpdate', () => {\n fetchAndForward();\n });\n});\n\ninterface HotUpdate {\n modules: Record<string, (...args: unknown[]) => unknown>;\n runtime?: unknown;\n}\n\nfunction fetchAndForward(): void {\n // The `webpackHotUpdate` event payload is the *new* hash, but hot-update\n // chunks are named with the *previous* hash (the \"delta-from\" hash). Use\n // `__webpack_require__.hu` — same helper rspack's own loader uses — which\n // reads `__webpack_require__.h()` to build the URL with the right hash.\n const publicPath = __webpack_require__?.p ?? '';\n const hu = __webpack_require__?.hu;\n if (typeof hu !== 'function') return;\n const url = publicPath + hu('main__main-thread');\n\n // Lynx's BG runtime doesn't have a working `fetch` for arbitrary URLs in\n // many hosts; rspack's own HMR loader uses `lynx.requireModuleAsync` (see\n // `loadUpdateChunk` in the BG bundle). It returns the parsed hot-update\n // shape `{ modules, runtime }` — each `modules[id]` is the compiled JS\n // factory function. We extract registerWorkletInternal calls from\n // `factory.toString()` so we don't have to actually evaluate the factory\n // (which would import worklet-runtime / install-hybrid into BG's webpack\n // module graph, with unwanted side effects).\n const requireModuleAsync = (lynx as unknown as {\n requireModuleAsync?: (\n url: string,\n cb: (err: unknown, update: HotUpdate) => void,\n ) => void;\n })?.requireModuleAsync;\n if (typeof requireModuleAsync !== 'function') return;\n\n requireModuleAsync(url, (err, update) => {\n if (err) {\n console.log('[sigx-mt-hmr-bridge] requireModuleAsync failed:', String(err));\n return;\n }\n let combined = '';\n for (const id in update.modules) {\n const factory = update.modules[id];\n if (typeof factory === 'function') combined += factory.toString() + '\\n';\n }\n const code = extractRegistrations(combined);\n if (!code) return;\n const app = lynx?.getNativeApp?.();\n if (!app || typeof app.callLepusMethod !== 'function') return;\n app.callLepusMethod('sigxApplyMtHotUpdate', { code }, () => {});\n });\n}\n\n/**\n * Extract `registerWorkletInternal(...)` calls from a hot-update body.\n *\n * Mirrors `lynx-plugin/src/loaders/worklet-utils.ts:extractRegistrations`\n * (duplicated here to avoid a runtime → build-time dep). Bracket-depth count\n * handles nested braces in the function body.\n */\nfunction extractRegistrations(source: string): string {\n const out: string[] = [];\n const marker = 'registerWorkletInternal(';\n let from = 0;\n\n while (true) {\n const idx = source.indexOf(marker, from);\n if (idx === -1) break;\n\n let depth = 0;\n let i = idx + marker.length - 1; // points at the opening '('\n for (; i < source.length; i++) {\n const ch = source[i];\n if (ch === '(') depth++;\n else if (ch === ')') {\n depth--;\n if (depth === 0) break;\n }\n }\n\n let end = i + 1;\n if (end < source.length && source[end] === ';') end++;\n out.push(source.slice(idx, end));\n from = end;\n }\n\n return out.join('\\n');\n}\n"],"mappings":";AA6CA,SAAS,IAA+C;CACtD,IAAM,IAAQ,qBAAqB;CAC9B,OACL,KAAK,IAAM,KAAM,GAAO;EACtB,IAAI,CAAC,EAAG,SAAS,UAAU,IAAI,CAAC,EAAG,SAAS,aAAa,EAAE;EAC3D,IAAM,IAAM,EAAM,IAAK;EACvB,IACE,KACG,OAAQ,EAAyB,MAAO,cACxC,OAAQ,EAA2B,QAAS,YAE/C,OAAO;;;AAUb,QAAQ,SAAS,CAAC,WAAW;CAC3B,IAAM,IAAU,GAAmB;CACnC,IAAI,CAAC,GAAS;EACZ,QAAQ,IAAI,kEAAkE;EAC9E;;CAEF,EAAQ,GAAG,0BAA0B;EACnC,GAAiB;GACjB;EACF;AAOF,SAAS,IAAwB;CAK/B,IAAM,IAAa,qBAAqB,KAAK,IACvC,IAAK,qBAAqB;CAChC,IAAI,OAAO,KAAO,YAAY;CAC9B,IAAM,IAAM,IAAa,EAAG,oBAAoB,EAU1C,IAAsB,MAKxB;CACA,OAAO,KAAuB,cAElC,EAAmB,IAAM,GAAK,MAAW;EACvC,IAAI,GAAK;GACP,QAAQ,IAAI,mDAAmD,OAAO,EAAI,CAAC;GAC3E;;EAEF,IAAI,IAAW;EACf,KAAK,IAAM,KAAM,EAAO,SAAS;GAC/B,IAAM,IAAU,EAAO,QAAQ;GAC/B,AAAI,OAAO,KAAY,eAAY,KAAY,EAAQ,UAAU,GAAG;;EAEtE,IAAM,IAAO,EAAqB,EAAS;EAC3C,IAAI,CAAC,GAAM;EACX,IAAM,IAAM,MAAM,gBAAgB;EAC9B,CAAC,KAAO,OAAO,EAAI,mBAAoB,cAC3C,EAAI,gBAAgB,wBAAwB,EAAE,SAAM,QAAQ,GAAG;GAC/D;;AAUJ,SAAS,EAAqB,GAAwB;CACpD,IAAM,IAAgB,EAAE,EAEpB,IAAO;CAEX,SAAa;EACX,IAAM,IAAM,EAAO,QAAQ,4BAAQ,EAAK;EACxC,IAAI,MAAQ,IAAI;EAEhB,IAAI,IAAQ,GACR,IAAI,IAAM,KAAgB;EAC9B,OAAO,IAAI,EAAO,QAAQ,KAAK;GAC7B,IAAM,IAAK,EAAO;GAClB,IAAI,MAAO,KAAK;QACX,IAAI,MAAO,QACd,KACI,MAAU,IAAG;;EAIrB,IAAI,IAAM,IAAI;EAGd,AAFI,IAAM,EAAO,UAAU,EAAO,OAAS,OAAK,KAChD,EAAI,KAAK,EAAO,MAAM,GAAK,EAAI,CAAC,EAChC,IAAO;;CAGT,OAAO,EAAI,KAAK,KAAK"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native gesture detector — BG-side wrapper around Lynx's
|
|
3
|
+
* `__SetGestureDetector(dom, id, type, config, relationMap)` PAPI.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the contract from upstream `@lynx-js/react/runtime/lib/gesture/`:
|
|
6
|
+
* - GestureType enum values match `GestureTypeInner`.
|
|
7
|
+
* - relationMap keys: `waitFor`, `simultaneous`, `continueWith`.
|
|
8
|
+
* - COMPOSED gestures are walked client-side and each base is registered
|
|
9
|
+
* with `__SetGestureDetector` separately (the platform receives bases).
|
|
10
|
+
*
|
|
11
|
+
* Public surface:
|
|
12
|
+
* - `Gesture.Pan() / .Tap() / .LongPress() / ...` — chainable builders.
|
|
13
|
+
* - `Gesture.Race(...) / .Simultaneous(...) / .Exclusive(...)` — composers.
|
|
14
|
+
* - `useGestureDetector(elRef, gesture)` — attaches the gesture to the
|
|
15
|
+
* element pointed at by elRef. Op-emit is deferred to `onMounted` so the
|
|
16
|
+
* SET_MT_REF op (pushed during the first JSX render) is applied before
|
|
17
|
+
* the SET_GESTURE_DETECTOR op tries to resolve the workletRefMap entry.
|
|
18
|
+
*/
|
|
19
|
+
import { MainThreadRef } from '../main-thread-ref.js';
|
|
20
|
+
export declare const GestureType: {
|
|
21
|
+
readonly COMPOSED: -1;
|
|
22
|
+
readonly PAN: 0;
|
|
23
|
+
readonly FLING: 1;
|
|
24
|
+
readonly DEFAULT: 2;
|
|
25
|
+
readonly TAP: 3;
|
|
26
|
+
readonly LONGPRESS: 4;
|
|
27
|
+
readonly ROTATION: 5;
|
|
28
|
+
readonly PINCH: 6;
|
|
29
|
+
readonly NATIVE: 7;
|
|
30
|
+
};
|
|
31
|
+
export type GestureTypeValue = (typeof GestureType)[keyof typeof GestureType];
|
|
32
|
+
export interface GestureWorklet {
|
|
33
|
+
_wkltId: string;
|
|
34
|
+
_c?: Record<string, unknown>;
|
|
35
|
+
_jsFn?: Record<string, unknown>;
|
|
36
|
+
_execId?: number;
|
|
37
|
+
_workletType?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* What users write at the source level — a `'main thread'` arrow function.
|
|
41
|
+
* The SWC LEPUS transform replaces it with a `GestureWorklet` placeholder
|
|
42
|
+
* before the BG bundle ships, so by the time the runtime sees the value
|
|
43
|
+
* it's already in placeholder shape. The union lets TypeScript accept the
|
|
44
|
+
* source-level function while the runtime branch treats it as a placeholder.
|
|
45
|
+
*/
|
|
46
|
+
export type GestureCallback = GestureWorklet | ((event: never) => void);
|
|
47
|
+
export interface BaseGesture {
|
|
48
|
+
__isSerialized: true;
|
|
49
|
+
type: number;
|
|
50
|
+
id: number;
|
|
51
|
+
callbacks: Record<string, GestureWorklet>;
|
|
52
|
+
waitFor: BaseGesture[];
|
|
53
|
+
simultaneousWith: BaseGesture[];
|
|
54
|
+
continueWith: BaseGesture[];
|
|
55
|
+
config?: Record<string, unknown>;
|
|
56
|
+
}
|
|
57
|
+
export interface ComposedGesture {
|
|
58
|
+
__isSerialized: true;
|
|
59
|
+
type: -1;
|
|
60
|
+
gestures: AnyGesture[];
|
|
61
|
+
}
|
|
62
|
+
export type AnyGesture = BaseGesture | ComposedGesture;
|
|
63
|
+
export declare function resetGestureIdCounter(): void;
|
|
64
|
+
declare class GestureBuilderBase {
|
|
65
|
+
protected gesture: BaseGesture;
|
|
66
|
+
constructor(type: number);
|
|
67
|
+
protected setConfigKey(key: string, value: unknown): this;
|
|
68
|
+
onBegin(cb: GestureCallback): this;
|
|
69
|
+
onStart(cb: GestureCallback): this;
|
|
70
|
+
onUpdate(cb: GestureCallback): this;
|
|
71
|
+
onEnd(cb: GestureCallback): this;
|
|
72
|
+
onFinalize(cb: GestureCallback): this;
|
|
73
|
+
waitFor(...gestures: BaseGesture[]): this;
|
|
74
|
+
simultaneousWith(...gestures: BaseGesture[]): this;
|
|
75
|
+
continueWith(...gestures: BaseGesture[]): this;
|
|
76
|
+
build(): BaseGesture;
|
|
77
|
+
}
|
|
78
|
+
declare class PanBuilder extends GestureBuilderBase {
|
|
79
|
+
constructor();
|
|
80
|
+
axis(a: 'x' | 'y' | 'xy'): this;
|
|
81
|
+
minDistance(n: number): this;
|
|
82
|
+
}
|
|
83
|
+
declare class FlingBuilder extends GestureBuilderBase {
|
|
84
|
+
constructor();
|
|
85
|
+
minVelocity(n: number): this;
|
|
86
|
+
direction(d: 'left' | 'right' | 'up' | 'down'): this;
|
|
87
|
+
}
|
|
88
|
+
declare class TapBuilder extends GestureBuilderBase {
|
|
89
|
+
constructor();
|
|
90
|
+
numberOfTaps(n: number): this;
|
|
91
|
+
maxDistance(n: number): this;
|
|
92
|
+
maxDuration(ms: number): this;
|
|
93
|
+
}
|
|
94
|
+
declare class LongPressBuilder extends GestureBuilderBase {
|
|
95
|
+
constructor();
|
|
96
|
+
/**
|
|
97
|
+
* Minimum hold duration in ms before the gesture activates and `onStart`
|
|
98
|
+
* fires. Native iOS handler (`LynxLongPressGestureHandler`) reads the
|
|
99
|
+
* `minDuration` config key — defaults to 500 ms if not set.
|
|
100
|
+
*/
|
|
101
|
+
minDuration(ms: number): this;
|
|
102
|
+
/**
|
|
103
|
+
* @deprecated alias for `minDuration` kept for source compatibility. The
|
|
104
|
+
* native handler only honours `minDuration`; this method now writes both
|
|
105
|
+
* keys so older call sites keep working until they migrate.
|
|
106
|
+
*/
|
|
107
|
+
duration(ms: number): this;
|
|
108
|
+
maxDistance(n: number): this;
|
|
109
|
+
}
|
|
110
|
+
declare class PinchBuilder extends GestureBuilderBase {
|
|
111
|
+
constructor();
|
|
112
|
+
}
|
|
113
|
+
declare class RotationBuilder extends GestureBuilderBase {
|
|
114
|
+
constructor();
|
|
115
|
+
}
|
|
116
|
+
declare class NativeBuilder extends GestureBuilderBase {
|
|
117
|
+
constructor();
|
|
118
|
+
}
|
|
119
|
+
export declare const Gesture: {
|
|
120
|
+
Pan: () => PanBuilder;
|
|
121
|
+
Fling: () => FlingBuilder;
|
|
122
|
+
Tap: () => TapBuilder;
|
|
123
|
+
LongPress: () => LongPressBuilder;
|
|
124
|
+
Pinch: () => PinchBuilder;
|
|
125
|
+
Rotation: () => RotationBuilder;
|
|
126
|
+
Native: () => NativeBuilder;
|
|
127
|
+
/**
|
|
128
|
+
* Race — first recognizer to claim wins. Sibling bases mutually waitFor
|
|
129
|
+
* each other so the platform's gesture arena resolves the priority.
|
|
130
|
+
*/
|
|
131
|
+
Race(...gs: (AnyGesture | {
|
|
132
|
+
build(): BaseGesture;
|
|
133
|
+
})[]): ComposedGesture;
|
|
134
|
+
/**
|
|
135
|
+
* Simultaneous — all recognizers can fire at once. Sibling bases declare
|
|
136
|
+
* mutual `simultaneousWith`.
|
|
137
|
+
*/
|
|
138
|
+
Simultaneous(...gs: (AnyGesture | {
|
|
139
|
+
build(): BaseGesture;
|
|
140
|
+
})[]): ComposedGesture;
|
|
141
|
+
/**
|
|
142
|
+
* Exclusive — sequential. Later items waitFor all earlier items.
|
|
143
|
+
*/
|
|
144
|
+
Exclusive(...gs: (AnyGesture | {
|
|
145
|
+
build(): BaseGesture;
|
|
146
|
+
})[]): ComposedGesture;
|
|
147
|
+
};
|
|
148
|
+
export declare function useGestureDetector(elRef: MainThreadRef<unknown>, gesture: AnyGesture | {
|
|
149
|
+
build(): BaseGesture;
|
|
150
|
+
}): void;
|
|
151
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lynx renderer node operations (Background Thread).
|
|
3
|
+
*
|
|
4
|
+
* Builds a ShadowElement tree and pushes ops into the op queue. NO Lynx PAPI
|
|
5
|
+
* globals (__CreateElement, __AppendElement, etc.) are referenced here — those
|
|
6
|
+
* only exist on the Main Thread. The op queue is flushed to MT via
|
|
7
|
+
* sigxPatchUpdate, where ops-apply.ts dispatches them to real PAPI calls.
|
|
8
|
+
*/
|
|
9
|
+
import type { RendererOptions } from '@sigx/runtime-core/internals';
|
|
10
|
+
import { ShadowElement } from './shadow-element.js';
|
|
11
|
+
export type LynxNode = ShadowElement;
|
|
12
|
+
export type LynxElement = ShadowElement;
|
|
13
|
+
export declare function resolveClass(el: ShadowElement): string;
|
|
14
|
+
export declare const nodeOps: RendererOptions<ShadowElement, ShadowElement>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Op queue — accumulates renderer ops on the Background Thread and flushes
|
|
3
|
+
* them to the Main Thread via the Lynx host bridge.
|
|
4
|
+
*
|
|
5
|
+
* The bridge identifiers `lynx` and `lynxCoreInject` are NOT on globalThis.
|
|
6
|
+
* They are injected as closure parameters by RuntimeWrapperWebpackPlugin
|
|
7
|
+
* (peer dep `@lynx-js/runtime-wrapper-webpack-plugin`), which wraps the BG
|
|
8
|
+
* bundle in `__init_card_bundle__(lynxCoreInject, lynx, ...)`. Once wrapped,
|
|
9
|
+
* any module in the bundle can reference them as bare identifiers.
|
|
10
|
+
*
|
|
11
|
+
*/
|
|
12
|
+
export { OP } from '@sigx/lynx-runtime-internal';
|
|
13
|
+
/**
|
|
14
|
+
* Push one op (opcode + arguments) into the buffer as a flat sequence.
|
|
15
|
+
* Example: pushOp(OP.CREATE, id, type) → buffer gets [0, id, type].
|
|
16
|
+
*/
|
|
17
|
+
export declare function pushOp(...args: unknown[]): void;
|
|
18
|
+
/** Take all buffered ops and reset the buffer. */
|
|
19
|
+
export declare function takeOps(): unknown[];
|
|
20
|
+
/**
|
|
21
|
+
* Schedule a flush of the ops buffer at the end of the current microtask.
|
|
22
|
+
* Multiple calls within one tick are coalesced into one cross-thread call.
|
|
23
|
+
*/
|
|
24
|
+
export declare function scheduleFlush(): void;
|
|
25
|
+
/**
|
|
26
|
+
* Immediately flush all buffered ops — used on initial mount so the first
|
|
27
|
+
* frame is committed synchronously.
|
|
28
|
+
*/
|
|
29
|
+
export declare function flushNow(): void;
|
|
30
|
+
/** Reset module state — for testing only. */
|
|
31
|
+
export declare function resetOpQueue(): void;
|
|
32
|
+
/**
|
|
33
|
+
* Resolves once the most recent ops batch has been applied on the main
|
|
34
|
+
* thread. If no ops are in flight, resolves immediately.
|
|
35
|
+
*/
|
|
36
|
+
export declare function waitForFlush(): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Tell the Main Thread to reset its element tree in preparation for a hot
|
|
39
|
+
* reload. The MT handler (`sigxHotReload`) calls `resetMainThreadState()`,
|
|
40
|
+
* re-creates the page root, and flushes — so the next `sigxPatchUpdate`
|
|
41
|
+
* batch builds on a clean tree.
|
|
42
|
+
*
|
|
43
|
+
* This is fire-and-forget: callLepusMethod messages are ordered, so
|
|
44
|
+
* sigxHotReload will be processed before any subsequent sigxPatchUpdate.
|
|
45
|
+
*/
|
|
46
|
+
export declare function sendHotReloadSignal(): void;
|
|
47
|
+
/**
|
|
48
|
+
* Install our event dispatcher on `lynxCoreInject.tt` — the official place
|
|
49
|
+
* the Lynx host calls when it forwards Main Thread events to the BG.
|
|
50
|
+
*
|
|
51
|
+
* Idempotent. Called from render.ts on module load and from lynxMount() as
|
|
52
|
+
* a defensive re-install in case the host swaps the tt namespace between
|
|
53
|
+
* card loads.
|
|
54
|
+
*/
|
|
55
|
+
export declare function installEventPublisher(): void;
|
package/dist/render.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { MountFn } from '@sigx/runtime-core';
|
|
2
|
+
import { type ShadowElement } from './shadow-element.js';
|
|
3
|
+
export declare const render: import("@sigx/runtime-core/internals").RootRenderFunction<ShadowElement, ShadowElement>;
|
|
4
|
+
/**
|
|
5
|
+
* Mount function for Lynx environments.
|
|
6
|
+
*
|
|
7
|
+
* The page root is a ShadowElement with id=1 — the Main Thread creates the
|
|
8
|
+
* real page element in renderPage() before the BG thread runs. All subsequent
|
|
9
|
+
* ops reference this root by id so the MT can resolve it.
|
|
10
|
+
*
|
|
11
|
+
* On subsequent calls (hot reload), the previous tree is torn down and the
|
|
12
|
+
* Main Thread is signalled to reset before the new tree is mounted.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* import '@sigx/lynx-runtime'; // side-effect: registers lynxMount
|
|
17
|
+
* import { defineApp } from '@sigx/sigx';
|
|
18
|
+
*
|
|
19
|
+
* defineApp(<App />).mount();
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare const lynxMount: MountFn;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runOnBackground — BG-side wiring for the MT→BG cross-thread call channel.
|
|
3
|
+
*
|
|
4
|
+
* Two responsibilities:
|
|
5
|
+
* 1. `transformToWorklet(fn)` — wraps a BG function as a `JsFnHandle`
|
|
6
|
+
* `{ _jsFnId, _fn }` so the SWC transform can serialise it into the
|
|
7
|
+
* `_jsFn` slot of a worklet ctx. The BG worklet-loader emits inline
|
|
8
|
+
* `transformToWorklet(...)` calls when the user writes `runOnBackground(fn)`
|
|
9
|
+
* inside a `'main thread'` body.
|
|
10
|
+
* 2. `Lynx.Sigx.RunOnBackground` listener — when the MT-side dispatcher
|
|
11
|
+
* fires, finds the matching JsFnHandle by `(execId, fnId)` from the
|
|
12
|
+
* registered worklet ctxs, runs `_fn(...params)`, dispatches
|
|
13
|
+
* `Lynx.Sigx.FunctionCallRet` back with `{resolveId, returnValue}`.
|
|
14
|
+
*
|
|
15
|
+
* Mirrors @lynx-js/react/runtime/lib/worklet/call/runOnBackground +
|
|
16
|
+
* vue-lynx's run-on-background.ts (same protocol shape, sigx-namespaced
|
|
17
|
+
* event types).
|
|
18
|
+
*/
|
|
19
|
+
export interface JsFnHandle {
|
|
20
|
+
_jsFnId?: number;
|
|
21
|
+
_execId?: number;
|
|
22
|
+
_fn?: (...args: unknown[]) => unknown;
|
|
23
|
+
_isFirstScreen?: boolean;
|
|
24
|
+
_error?: string;
|
|
25
|
+
}
|
|
26
|
+
interface WorkletCtx {
|
|
27
|
+
_wkltId: string;
|
|
28
|
+
_execId?: number;
|
|
29
|
+
_c?: Record<string, unknown>;
|
|
30
|
+
_jsFn?: Record<string, unknown>;
|
|
31
|
+
[key: string]: unknown;
|
|
32
|
+
}
|
|
33
|
+
export declare function transformToWorklet(fn: (...args: unknown[]) => unknown): JsFnHandle;
|
|
34
|
+
export declare function registerWorkletCtx(ctx: WorkletCtx): void;
|
|
35
|
+
export declare function runOnBackground<R, Fn extends (...args: never[]) => R>(_fn: Fn): (...args: Parameters<Fn>) => Promise<R>;
|
|
36
|
+
export declare function resetRunOnBackgroundState(): void;
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ShadowElement: a lightweight doubly-linked tree node that lives entirely in
|
|
3
|
+
* the Background Thread. It lets the renderer call parentNode() / nextSibling()
|
|
4
|
+
* synchronously, while the real Lynx elements exist only on the Main Thread.
|
|
5
|
+
*
|
|
6
|
+
* id=1 is reserved for the page root (created via __CreatePage on Main Thread).
|
|
7
|
+
* Regular elements start from id=2.
|
|
8
|
+
*/
|
|
9
|
+
export declare class ShadowElement {
|
|
10
|
+
static nextId: number;
|
|
11
|
+
id: number;
|
|
12
|
+
type: string;
|
|
13
|
+
parent: ShadowElement | null;
|
|
14
|
+
firstChild: ShadowElement | null;
|
|
15
|
+
lastChild: ShadowElement | null;
|
|
16
|
+
prev: ShadowElement | null;
|
|
17
|
+
next: ShadowElement | null;
|
|
18
|
+
_style: Record<string, unknown>;
|
|
19
|
+
_vShowHidden: boolean;
|
|
20
|
+
_baseClass: string;
|
|
21
|
+
_transitionClasses: Set<string>;
|
|
22
|
+
constructor(type: string, forceId?: number);
|
|
23
|
+
insertBefore(child: ShadowElement, anchor: ShadowElement | null): void;
|
|
24
|
+
removeChild(child: ShadowElement): void;
|
|
25
|
+
}
|
|
26
|
+
export declare const PAGE_ROOT_ID = 1;
|
|
27
|
+
/** Create the page root shadow element with the reserved id=1. */
|
|
28
|
+
export declare function createPageRoot(): ShadowElement;
|
|
29
|
+
/** Reset the ID counter — for testing only. */
|
|
30
|
+
export declare function resetShadowState(): void;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-thread function invocation primitives for Lynx MTS.
|
|
3
|
+
*
|
|
4
|
+
* - runOnMainThread(fn): BG → MT — invoke a function on the Main Thread
|
|
5
|
+
* - runOnBackground(fn): MT → BG — invoke a function on the Background Thread
|
|
6
|
+
*
|
|
7
|
+
* ## Worklet placeholder semantics (Phase 1b)
|
|
8
|
+
*
|
|
9
|
+
* The `'main thread'` directive inside the function passed to runOnMainThread
|
|
10
|
+
* tells the build pipeline (`@lynx-js/react/transform`) to:
|
|
11
|
+
* 1. Replace the function with a `{ _wkltId }` placeholder in the BG bundle.
|
|
12
|
+
* 2. Emit a `registerWorkletInternal("main-thread", "<wkltId>", function(...) { ... })`
|
|
13
|
+
* call into the MT bundle that registers the function in the MT-side worklet
|
|
14
|
+
* registry.
|
|
15
|
+
*
|
|
16
|
+
* At runtime, runOnMainThread receives the placeholder, returns a callable, and
|
|
17
|
+
* each invocation ships `{ wkltId, args }` over `callLepusMethod('sigxRunOnMT')`
|
|
18
|
+
* to the MT bridge installed in `entry-main.ts`.
|
|
19
|
+
*
|
|
20
|
+
* ## Usage
|
|
21
|
+
*
|
|
22
|
+
* ```tsx
|
|
23
|
+
* import { runOnMainThread, runOnBackground } from '@sigx/lynx-runtime';
|
|
24
|
+
*
|
|
25
|
+
* const updateOffset = runOnMainThread((offset: number) => {
|
|
26
|
+
* 'main thread';
|
|
27
|
+
* ref.current?.setStyleProperties({ transform: `translateX(${offset}px)` });
|
|
28
|
+
* });
|
|
29
|
+
* updateOffset(100);
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* The `(offset) => { 'main thread'; ... }` source is what the user writes; after
|
|
33
|
+
* the build transform it becomes `{ _wkltId: '...' }` at this call site.
|
|
34
|
+
*/
|
|
35
|
+
interface WorkletPlaceholder {
|
|
36
|
+
_wkltId: string;
|
|
37
|
+
_c?: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
export declare function runOnMainThread<TArgs extends unknown[]>(worklet: WorkletPlaceholder | ((...args: TArgs) => unknown)): (...args: TArgs) => Promise<unknown>;
|
|
40
|
+
export { runOnBackground, transformToWorklet, resetRunOnBackgroundState, } from './run-on-background.js';
|
|
41
|
+
export declare function resetThreading(): void;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module augmentation for Lynx platform.
|
|
3
|
+
*
|
|
4
|
+
* Sets `PlatformTypes.element = ShadowElement` so generic helpers in
|
|
5
|
+
* @sigx/runtime-core that read the per-platform element type pick up
|
|
6
|
+
* our shadow tree node.
|
|
7
|
+
*
|
|
8
|
+
* Mirrors packages/runtime-terminal/src/types.ts exactly.
|
|
9
|
+
*/
|
|
10
|
+
import type { ShadowElement } from './shadow-element.js';
|
|
11
|
+
declare module '@sigx/runtime-core' {
|
|
12
|
+
/** Lynx platform sets ShadowElement as the default element type */
|
|
13
|
+
interface PlatformTypes {
|
|
14
|
+
element: ShadowElement;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export {};
|