mithril-lynx 2.5.0 → 2.6.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.
- package/.omo/run-continuation/ses_f2f02336cffeO0ssvuxqZ05iZq.json +10 -0
- package/.omo/run-continuation/ses_f2fd5cb5fffeN1tjdUyPqdKG6B.json +10 -0
- package/LICENSE.txt +21 -0
- package/PLAN_REVIEW.md +134 -0
- package/README.md +2 -1
- package/REQUEST.md +5 -2
- package/ROUTE.md +1 -1
- package/informe-contrato-mithril-lynx.md +206 -0
- package/package.json +5 -1
- package/prompts-analisis-contrato-mithril-lynx.md +141 -0
- package/src/apply-patch.js +87 -27
- package/src/backends/virtual-backend.js +18 -4
- package/src/background.d.ts +14 -1
- package/src/channel.js +10 -2
- package/src/fake-dom.js +13 -10
- package/src/list-cell.d.ts +20 -0
- package/src/list-cell.js +73 -0
- package/src/list-support.d.ts +12 -9
- package/src/list-support.js +144 -114
- package/src/main-thread.js +15 -2
- package/src/mount-redraw.d.ts +10 -0
- package/src/mount-redraw.js +55 -2
- package/src/patch-protocol.js +61 -6
- package/src/request.d.ts +2 -0
- package/src/request.js +21 -3
- package/src/route.d.ts +22 -3
- package/src/route.js +25 -6
- package/src/testing.js +24 -11
- package/test/list.test.ts +86 -40
- package/test/patch-protocol.test.ts +41 -0
- package/test/remove-event.test.ts +26 -0
- package/test/route-hot-reload.test.ts +2 -1
- package/test/route.test.ts +5 -2
- package/test/setup.ts +10 -0
package/src/apply-patch.js
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
// low-level interpreter of the flat op array straight onto the real
|
|
7
7
|
// Element PAPI, in the spirit of ReactLynx's own `snapshotPatchApply.js`
|
|
8
8
|
// (see rspeedy-react-analysis/LYNX_PAPI_SPEC.md §4.3): a switch over op
|
|
9
|
-
// codes, one real PAPI call per case, nothing else.
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// docs/native-papi/papi-06-virtualized-lists.md in mithril-lynx-ui
|
|
9
|
+
// codes, one real PAPI call per case, nothing else. Op.CreateList's own
|
|
10
|
+
// cell content (list-support.js) is no exception to that: componentAtIndex
|
|
11
|
+
// replays ops the background thread already computed (list-cell.js), the
|
|
12
|
+
// same way this function replays the app's own top-level tree — see
|
|
13
|
+
// docs/native-papi/papi-06-virtualized-lists.md in mithril-lynx-ui.
|
|
14
14
|
//
|
|
15
15
|
// The exact `__Create*`/pageId contract below (one `pageId` shared by every
|
|
16
16
|
// element on a page, `__CreateView`/`__CreateText`/generic `__CreateElement`
|
|
@@ -191,16 +191,47 @@ function registerGestureDetector(handle, id, gestureId, gestureType, arenaPolicy
|
|
|
191
191
|
* @param {number} pageId - `__GetElementUniqueID(pageElement)` of the real
|
|
192
192
|
* page this applier is attached to. Every element this applier creates
|
|
193
193
|
* belongs to that one page — see CONTRACT.md / lynx-mithril-shim.js.
|
|
194
|
+
* @param {object} [options]
|
|
195
|
+
* @param {Function} [options.onEvent]
|
|
196
|
+
* @param {boolean} [options.flush] - Whether `applyPatch` calls the bare,
|
|
197
|
+
* whole-page `__FlushElementTree()` after applying its ops. Defaults to
|
|
198
|
+
* `true` — the right default for the ONE real top-level applier per page
|
|
199
|
+
* (main-thread.js's own use). `false` for a per-cell applier
|
|
200
|
+
* (list-support.js): a list cell's real commit point is the list-specific
|
|
201
|
+
* `__FlushElementTree(wrapperHandle, {triggerLayout, operationID,
|
|
202
|
+
* elementID, listID})` call list-support.js already makes right after —
|
|
203
|
+
* calling the bare, whole-page flush too, from inside native's own
|
|
204
|
+
* synchronous componentAtIndex callback, is a second, unrelated flush this
|
|
205
|
+
* applier was never meant to trigger on that call site's behalf.
|
|
194
206
|
*/
|
|
195
|
-
export function createPatchApplier(pageId, { onEvent } = {}) {
|
|
207
|
+
export function createPatchApplier(pageId, { onEvent, flush = true } = {}) {
|
|
196
208
|
// id (as allocated by the background's virtual backend) -> real PAPI
|
|
197
209
|
// element handle. id 0 is reserved for "the page itself" (see
|
|
198
210
|
// fake-dom.js's LynxDocument) — pre-seeded here so the very first
|
|
199
211
|
// InsertBefore/AppendChild targeting id 0 has somewhere real to land.
|
|
200
212
|
const handles = new Map();
|
|
213
|
+
// list id -> its setCells(cells) function (list-support.js) — kept here,
|
|
214
|
+
// not as a property on the list's own handle: a real native list handle
|
|
215
|
+
// does not reliably hold a custom property across calls (confirmed on
|
|
216
|
+
// device), only `handles` (a plain Map) does.
|
|
217
|
+
const listSetters = new Map();
|
|
218
|
+
|
|
219
|
+
// id -> Map<event type, listener callback>. `__AddEventListener` needs the
|
|
220
|
+
// exact callback reference back when `__RemoveEventListener` runs, so the
|
|
221
|
+
// listener cannot be an inline closure re-created per Op.AddEvent — it is
|
|
222
|
+
// stored here and reused by the Op.RemoveEvent case.
|
|
223
|
+
const eventListeners = new Map();
|
|
224
|
+
|
|
225
|
+
/** General form: seed the mapping for any id, not just the page root —
|
|
226
|
+
* list-support.js uses this to alias a list-cell.js `containerId` (an
|
|
227
|
+
* off-tree id from the background thread's OWN document) to the real
|
|
228
|
+
* native wrapper element it created for that cell. */
|
|
229
|
+
function registerRoot(id, handle) {
|
|
230
|
+
handles.set(id, handle);
|
|
231
|
+
}
|
|
201
232
|
|
|
202
233
|
function registerPageRoot(pageElementHandle) {
|
|
203
|
-
|
|
234
|
+
registerRoot(0, pageElementHandle);
|
|
204
235
|
}
|
|
205
236
|
|
|
206
237
|
function createElementHandle(tag) {
|
|
@@ -210,12 +241,15 @@ export function createPatchApplier(pageId, { onEvent } = {}) {
|
|
|
210
241
|
}
|
|
211
242
|
|
|
212
243
|
/**
|
|
213
|
-
* Applies one commit's worth of ops, then
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
244
|
+
* Applies one commit's worth of ops, then — unless this applier was
|
|
245
|
+
* created with `flush: false` (see this function's own constructor
|
|
246
|
+
* options above) — flushes exactly once with the bare, whole-page
|
|
247
|
+
* `__FlushElementTree()`; that call is the real commit for the ONE
|
|
248
|
+
* top-level applier per page, never looked up through a global. A
|
|
249
|
+
* per-cell applier (list-support.js) passes `flush: false` and issues
|
|
250
|
+
* its own list-specific `__FlushElementTree(wrapperHandle, {...})` call
|
|
251
|
+
* afterward instead — that one, not this one, is that cell's real
|
|
252
|
+
* commit point.
|
|
219
253
|
*/
|
|
220
254
|
function applyPatch(ops) {
|
|
221
255
|
for (let i = 0; i < ops.length; ) {
|
|
@@ -331,18 +365,43 @@ export function createPatchApplier(pageId, { onEvent } = {}) {
|
|
|
331
365
|
const id = ops[i++];
|
|
332
366
|
const type = ops[i++];
|
|
333
367
|
const handle = handles.get(id);
|
|
334
|
-
|
|
368
|
+
const listener = (nativeEvent) => {
|
|
335
369
|
onEvent?.(id, type, nativeEvent);
|
|
336
|
-
}
|
|
370
|
+
};
|
|
371
|
+
let byType = eventListeners.get(id);
|
|
372
|
+
if (byType == null) eventListeners.set(id, (byType = new Map()));
|
|
373
|
+
byType.set(type, listener);
|
|
374
|
+
__AddEventListener(handle, type, listener, {});
|
|
337
375
|
break;
|
|
338
376
|
}
|
|
339
377
|
case Op.RemoveEvent: {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
378
|
+
const id = ops[i++];
|
|
379
|
+
const type = ops[i++];
|
|
380
|
+
const handle = handles.get(id);
|
|
381
|
+
const byType = eventListeners.get(id);
|
|
382
|
+
const listener = byType ? byType.get(type) : undefined;
|
|
383
|
+
if (typeof __RemoveEventListener === "function") {
|
|
384
|
+
// Native Fiber requires the options argument (>= 4 params) and
|
|
385
|
+
// derives the binding slot from it — pass the same `{}` the
|
|
386
|
+
// Op.AddEvent case uses so add/remove target the same slot.
|
|
387
|
+
if (listener != null) __RemoveEventListener(handle, type, listener, {});
|
|
388
|
+
} else if (listener != null) {
|
|
389
|
+
// Failing loudly is deliberate: without __RemoveEventListener a
|
|
390
|
+
// remove→re-add cycle on a kept element would accumulate
|
|
391
|
+
// duplicate native listeners and fire the handler N times per
|
|
392
|
+
// event. A silent no-op here is exactly the class of bug the
|
|
393
|
+
// whole project refuses to ship.
|
|
394
|
+
throw new Error(
|
|
395
|
+
"[mithril-lynx] __RemoveEventListener is not available on this runtime, " +
|
|
396
|
+
"so Op.RemoveEvent cannot be applied — a conditional event handler " +
|
|
397
|
+
"would leak duplicate listeners. Keep the handler constant or " +
|
|
398
|
+
"remove the whole element instead.",
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
if (byType != null) {
|
|
402
|
+
byType.delete(type);
|
|
403
|
+
if (byType.size === 0) eventListeners.delete(id);
|
|
404
|
+
}
|
|
346
405
|
break;
|
|
347
406
|
}
|
|
348
407
|
case Op.SetGestureDetector: {
|
|
@@ -363,29 +422,30 @@ export function createPatchApplier(pageId, { onEvent } = {}) {
|
|
|
363
422
|
}
|
|
364
423
|
case Op.CreateList: {
|
|
365
424
|
const id = ops[i++];
|
|
366
|
-
const rendererKey = ops[i++];
|
|
367
425
|
const scrollOrientation = ops[i++];
|
|
368
426
|
const listType = ops[i++];
|
|
369
427
|
const spanCount = ops[i++];
|
|
370
|
-
|
|
428
|
+
const { handle, setCells } = createNativeList(pageId, scrollOrientation, listType, spanCount, createPatchApplier, onEvent);
|
|
429
|
+
handles.set(id, handle);
|
|
430
|
+
listSetters.set(id, setCells);
|
|
371
431
|
break;
|
|
372
432
|
}
|
|
373
433
|
case Op.SetListItems: {
|
|
374
434
|
const id = ops[i++];
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
listHandle.__setItems(JSON.parse(itemsJSON));
|
|
435
|
+
const cells = ops[i++];
|
|
436
|
+
listSetters.get(id)(cells);
|
|
378
437
|
break;
|
|
379
438
|
}
|
|
380
439
|
default:
|
|
381
440
|
throw new Error(`[mithril-lynx] Unknown patch opcode: ${opcode}`);
|
|
382
441
|
}
|
|
383
442
|
}
|
|
384
|
-
__FlushElementTree();
|
|
443
|
+
if (flush) __FlushElementTree();
|
|
385
444
|
}
|
|
386
445
|
|
|
387
446
|
return {
|
|
388
447
|
registerPageRoot,
|
|
448
|
+
registerRoot,
|
|
389
449
|
applyPatch,
|
|
390
450
|
/** The real PAPI element handle for a given background-side id, or
|
|
391
451
|
* `undefined` if nothing was ever created for it. Exists for tests
|
|
@@ -75,13 +75,13 @@ export function createVirtualBackend() {
|
|
|
75
75
|
removeGestureDetector(id, gestureId) {
|
|
76
76
|
pushOp(ops, Op.RemoveGestureDetector, id, gestureId);
|
|
77
77
|
},
|
|
78
|
-
createList(
|
|
78
|
+
createList(scrollOrientation, listType, spanCount) {
|
|
79
79
|
const id = nextId++;
|
|
80
|
-
pushOp(ops, Op.CreateList, id,
|
|
80
|
+
pushOp(ops, Op.CreateList, id, scrollOrientation, listType, spanCount);
|
|
81
81
|
return id;
|
|
82
82
|
},
|
|
83
|
-
setListItems(id,
|
|
84
|
-
pushOp(ops, Op.SetListItems, id,
|
|
83
|
+
setListItems(id, cells) {
|
|
84
|
+
pushOp(ops, Op.SetListItems, id, cells);
|
|
85
85
|
},
|
|
86
86
|
/** Drains and returns the accumulated ops. Called once per commit. */
|
|
87
87
|
takeOps() {
|
|
@@ -90,5 +90,19 @@ export function createVirtualBackend() {
|
|
|
90
90
|
ops = [];
|
|
91
91
|
return out;
|
|
92
92
|
},
|
|
93
|
+
/**
|
|
94
|
+
* Runs `fn` (a DOM mutation against a node from THIS backend's own
|
|
95
|
+
* document — same id space as everything else, so event dispatch
|
|
96
|
+
* keeps working normally) and returns just the ops it produced,
|
|
97
|
+
* removing them from the shared buffer so they never also go out
|
|
98
|
+
* with the next `takeOps()`. Used by list-cell.js to render one list
|
|
99
|
+
* item off-tree and ship its construction ops separately, instead of
|
|
100
|
+
* as part of the app's own visible-tree patch.
|
|
101
|
+
*/
|
|
102
|
+
captureOps(fn) {
|
|
103
|
+
const start = ops.length;
|
|
104
|
+
fn();
|
|
105
|
+
return ops.splice(start, ops.length - start);
|
|
106
|
+
},
|
|
93
107
|
};
|
|
94
108
|
}
|
package/src/background.d.ts
CHANGED
|
@@ -3,9 +3,22 @@ export interface RenderAppOptions {
|
|
|
3
3
|
sendPatch?: (ops: unknown[]) => void;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
+
/** Minimal shape of a fake-DOM node exposed for tests and HMR glue — enough
|
|
7
|
+
* to dispatch a synthetic event (e.g. `node.dispatchEvent({ type: "tap" })`)
|
|
8
|
+
* and correlate with a background-side id. */
|
|
9
|
+
export interface LynxNode {
|
|
10
|
+
dispatchEvent(event: unknown): unknown;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** The subset of the fake-DOM document exposed on the render handle — used
|
|
14
|
+
* by tests and by an app's own HMR glue, never by the channel wiring. */
|
|
15
|
+
export interface LynxDocument {
|
|
16
|
+
getNodeById(id: number): LynxNode | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
6
19
|
export interface RenderAppHandle {
|
|
7
20
|
redraw: () => void;
|
|
8
|
-
document:
|
|
21
|
+
document: LynxDocument;
|
|
9
22
|
}
|
|
10
23
|
|
|
11
24
|
export function renderApp(options: RenderAppOptions): RenderAppHandle;
|
package/src/channel.js
CHANGED
|
@@ -20,9 +20,17 @@ export const eventFromMainThreadEventName = "MithrilLynx:Event";
|
|
|
20
20
|
export const renderPageEventName = "__RenderPage";
|
|
21
21
|
export const destroyLifetimeEventName = "__DestroyLifetime";
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
import { PROTOCOL_VERSION } from "./patch-protocol.js";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Background thread: ship one commit's ops to the main thread.
|
|
27
|
+
*
|
|
28
|
+
* The patch is prefixed with `PROTOCOL_VERSION` so the main thread can
|
|
29
|
+
* detect a stale/desynced bundle (partial HMR, cache) and fail loudly
|
|
30
|
+
* instead of re-interpreting reordered opcodes against the shared id space.
|
|
31
|
+
*/
|
|
24
32
|
export function sendPatchToMainThread(ops) {
|
|
25
|
-
lynx.getCoreContext().dispatchEvent({ type: patchEventName, data: ops });
|
|
33
|
+
lynx.getCoreContext().dispatchEvent({ type: patchEventName, data: [PROTOCOL_VERSION, ...ops] });
|
|
26
34
|
}
|
|
27
35
|
|
|
28
36
|
/** Background thread: receive a forwarded native event `{ id, type, payload }`. */
|
package/src/fake-dom.js
CHANGED
|
@@ -178,7 +178,7 @@ export class LynxElement extends LynxContainerNode {
|
|
|
178
178
|
this.tag = tag;
|
|
179
179
|
this.namespaceURI = ns;
|
|
180
180
|
this._id = listConfig
|
|
181
|
-
? backend.createList(listConfig.
|
|
181
|
+
? backend.createList(listConfig.scrollOrientation, listConfig.listType, listConfig.spanCount)
|
|
182
182
|
: ns
|
|
183
183
|
? backend.createElementNS(ns, tag)
|
|
184
184
|
: backend.createElement(tag);
|
|
@@ -440,22 +440,25 @@ export class LynxDocument extends LynxContainerNode {
|
|
|
440
440
|
|
|
441
441
|
/**
|
|
442
442
|
* A native virtualized list — see patch-protocol.js's Op.CreateList.
|
|
443
|
-
*
|
|
444
|
-
*
|
|
445
|
-
* docs/native-papi/papi-06-virtualized-lists.md in mithril-lynx-ui
|
|
446
|
-
* the renderer function itself can't cross the thread boundary, only
|
|
447
|
-
* this string key can. Call `.setListItems(items)` on the result to
|
|
448
|
-
* populate it — items must be JSON-serializable, since they DO cross
|
|
449
|
-
* the boundary, as data.
|
|
443
|
+
* Populate it via `.setListItems(cells)` (list-cell.js builds `cells`
|
|
444
|
+
* from an app's own `items`/`renderItem`) — see
|
|
445
|
+
* docs/native-papi/papi-06-virtualized-lists.md in mithril-lynx-ui.
|
|
450
446
|
*/
|
|
451
|
-
createNativeList(
|
|
447
|
+
createNativeList(options = {}) {
|
|
452
448
|
return new LynxElement(this, this._backend, "list", undefined, {
|
|
453
|
-
rendererKey,
|
|
454
449
|
scrollOrientation: options.scrollOrientation ?? "vertical",
|
|
455
450
|
listType: options.listType ?? "single",
|
|
456
451
|
spanCount: options.spanCount ?? 1,
|
|
457
452
|
});
|
|
458
453
|
}
|
|
454
|
+
|
|
455
|
+
/** Delegates to the backend — see virtual-backend.js's own captureOps
|
|
456
|
+
* for what this is for. Kept behind LynxDocument's public surface like
|
|
457
|
+
* every other backend interaction in this file, rather than exposing
|
|
458
|
+
* `_backend` itself to callers (list-cell.js). */
|
|
459
|
+
captureOps(fn) {
|
|
460
|
+
return this._backend.captureOps(fn);
|
|
461
|
+
}
|
|
459
462
|
}
|
|
460
463
|
|
|
461
464
|
export function createLynxDocument(backend) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Ambient declaration for the ESM src/list-cell.js — the background-thread
|
|
2
|
+
// half of native list support. mithril-lynx-ui's <List>/<FeedList> call
|
|
3
|
+
// this once per item, from the SAME thread/document as the rest of the
|
|
4
|
+
// app's own tree — see that file's own header for why.
|
|
5
|
+
|
|
6
|
+
export interface ListCell {
|
|
7
|
+
typeKey: string;
|
|
8
|
+
containerId: number;
|
|
9
|
+
ops: unknown[];
|
|
10
|
+
rootChildIds: number[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function renderListCell(
|
|
14
|
+
document: unknown,
|
|
15
|
+
render: (dom: unknown, vnodes: unknown[], redraw: () => void) => void,
|
|
16
|
+
redraw: () => void,
|
|
17
|
+
renderItem: (item: unknown, index: number) => unknown,
|
|
18
|
+
item: unknown,
|
|
19
|
+
index: number,
|
|
20
|
+
): ListCell;
|
package/src/list-cell.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// src/list-cell.js
|
|
2
|
+
//
|
|
3
|
+
// Runs ONLY on the background thread — the counterpart to list-support.js's
|
|
4
|
+
// main-thread half. Turns one `renderItem(item, index)` call into a
|
|
5
|
+
// self-contained set of construction ops, using the app's OWN document/
|
|
6
|
+
// backend/render (the same ones background.js already created for the rest
|
|
7
|
+
// of the tree) — never a separate, isolated render pipeline. That's the
|
|
8
|
+
// whole point: the item's real fake-dom nodes end up registered in the same
|
|
9
|
+
// `document._nodesById` map as everything else in the app, so a forwarded
|
|
10
|
+
// native event for one of them dispatches through `onEventFromMainThread`
|
|
11
|
+
// exactly like any other element's event already does. No extra reporting
|
|
12
|
+
// convention needed.
|
|
13
|
+
//
|
|
14
|
+
// The main thread never calls renderItem() itself — see list-support.js.
|
|
15
|
+
|
|
16
|
+
import { Op, forEachOp } from "./patch-protocol.js";
|
|
17
|
+
|
|
18
|
+
function typeKeyOf(vnode) {
|
|
19
|
+
return typeof vnode.tag === "string" ? vnode.tag : (vnode.tag && vnode.tag.name) || "default";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The real handle ids inserted directly under `containerId` — what a
|
|
23
|
+
* recycled cell on the main thread needs to remove before it can attach a
|
|
24
|
+
* different item's content into the same native wrapper (see
|
|
25
|
+
* list-support.js's clearWrapperChildren). Scanning the ops after the fact,
|
|
26
|
+
* instead of tracking during render, keeps this file from needing any
|
|
27
|
+
* backend-internal access beyond `captureOps` itself. */
|
|
28
|
+
function findTopLevelChildIds(ops, containerId) {
|
|
29
|
+
const ids = [];
|
|
30
|
+
forEachOp(ops, (opcode, args) => {
|
|
31
|
+
if (opcode === Op.InsertBefore && args[0] === containerId) ids.push(args[1]);
|
|
32
|
+
});
|
|
33
|
+
return ids;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {import("./fake-dom.js").LynxDocument} document - the app's own
|
|
38
|
+
* document (e.g. `vnode.dom.ownerDocument` from inside a component) — NOT
|
|
39
|
+
* a separate one. Sharing it is what keeps a list cell's ids in the same
|
|
40
|
+
* `_nodesById` space as the rest of the app.
|
|
41
|
+
* @param {(dom: object, vnodes: unknown[], redraw: () => void) => void} render
|
|
42
|
+
* - a `mithril-runtime/render/render.js` instance. Callers typically keep
|
|
43
|
+
* one shared instance per `<List>` (see mithril-lynx-ui's list.js), not
|
|
44
|
+
* one per cell — render() is designed to manage multiple independent
|
|
45
|
+
* containers safely.
|
|
46
|
+
* @param {() => void} redraw - forwarded as render()'s own redraw callback,
|
|
47
|
+
* so a `ontap` (etc.) handler inside the rendered cell triggers a REAL
|
|
48
|
+
* app-wide redraw afterward, exactly like any other event in the app —
|
|
49
|
+
* see mithril-lynx/mount-redraw's `redraw()`.
|
|
50
|
+
* @param {(item: unknown, index: number) => unknown} renderItem
|
|
51
|
+
* @param {unknown} item
|
|
52
|
+
* @param {number} index
|
|
53
|
+
* @returns {{ typeKey: string, containerId: number, ops: unknown[], rootChildIds: number[] }}
|
|
54
|
+
*/
|
|
55
|
+
export function renderListCell(document, render, redraw, renderItem, item, index) {
|
|
56
|
+
// An ordinary element in the SAME document, never inserted into the
|
|
57
|
+
// visible tree (no `appendChild`/`insertBefore` call targets it) — its
|
|
58
|
+
// own `Op.CreateElement` never gets captured below since it's created
|
|
59
|
+
// OUTSIDE the capture, before there's anything to record; only what
|
|
60
|
+
// render() does INSIDE it is captured. The main thread's list-support.js
|
|
61
|
+
// treats `containerId` as an alias for whatever real native wrapper it
|
|
62
|
+
// creates for this cell, the same way apply-patch.js's top-level applier
|
|
63
|
+
// treats id 0 as an alias for the real page element.
|
|
64
|
+
const container = document.createElement("list-cell-root");
|
|
65
|
+
const vnode = renderItem(item, index);
|
|
66
|
+
const typeKey = typeKeyOf(vnode);
|
|
67
|
+
|
|
68
|
+
const ops = document.captureOps(() => {
|
|
69
|
+
render(container, [vnode], redraw);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return { typeKey, containerId: container._id, ops, rootChildIds: findTopLevelChildIds(ops, container._id) };
|
|
73
|
+
}
|
package/src/list-support.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
// Ambient declaration for the ESM src/list-support.js
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// per list your app uses — see that file's own header, and
|
|
6
|
-
// docs/native-papi/papi-06-virtualized-lists.md in mithril-lynx-ui, for
|
|
7
|
-
// why the render function itself has to be registered here rather than
|
|
8
|
-
// passed in from background.ts directly.
|
|
1
|
+
// Ambient declaration for the ESM src/list-support.js — the main-thread
|
|
2
|
+
// half of native list support. Not meant to be imported directly by app
|
|
3
|
+
// code; apply-patch.js's own Op.CreateList case is the only caller. See
|
|
4
|
+
// list-cell.d.ts for the background-thread half an app actually uses.
|
|
9
5
|
|
|
10
|
-
export function
|
|
6
|
+
export function createNativeList(
|
|
7
|
+
pageId: number,
|
|
8
|
+
scrollOrientation: string,
|
|
9
|
+
listType: string,
|
|
10
|
+
spanCount: number,
|
|
11
|
+
createPatchApplier: (pageId: number, options?: { onEvent?: Function; flush?: boolean }) => object,
|
|
12
|
+
onEvent?: Function,
|
|
13
|
+
): { handle: unknown; setCells: (cells: unknown[]) => void };
|