mithril-lynx 2.6.2 → 3.0.0
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/CHANGELOG.md +50 -0
- package/FETCH_INVESTIGATION.md +3 -2
- package/INPUT.md +115 -0
- package/README.md +47 -8
- package/ROUTE.md +111 -16
- package/ROUTE_CONTRACT_ANALYSIS.md +224 -0
- package/UPDATE_ANIMATION_GAP.md +133 -0
- package/package.json +1 -9
- package/plugin.js +33 -0
- package/src/apply-patch.js +369 -60
- package/src/backends/virtual-backend.js +120 -22
- package/src/background.d.ts +26 -0
- package/src/background.js +116 -7
- package/src/channel.js +22 -5
- package/src/commit.js +9 -0
- package/src/dev-reload-client.js +46 -0
- package/src/dev-transport-noop.js +3 -0
- package/src/fake-dom.js +559 -61
- package/src/list-attributes.js +69 -0
- package/src/list-runtime.js +425 -0
- package/src/main-thread.js +18 -1
- package/src/mount-redraw.js +14 -1
- package/src/patch-protocol.js +41 -16
- package/src/reload/version.js +10 -1
- package/src/request.js +38 -0
- package/src/route.d.ts +30 -2
- package/src/route.js +214 -56
- package/src/testing.d.ts +14 -1
- package/src/testing.js +56 -1
- package/test/event-render-coalescing.test.ts +117 -0
- package/test/gesture.test.ts +75 -0
- package/test/input.test.ts +388 -0
- package/test/list.test.ts +417 -113
- package/test/remove-subtree-cleanup.test.ts +75 -0
- package/test/route-hot-reload.test.ts +2 -1
- package/test/route.test.ts +201 -4
- package/test/style-clear.test.ts +97 -0
- package/.omo/plans/m-request-fetch-lynx.md +0 -306
- package/.omo/plans/m-route-en-memoria.md +0 -397
- package/.omo/plans/mithril-lynx-v2-desde-cero.md +0 -548
- package/.omo/run-continuation/ses_f2f02336cffeO0ssvuxqZ05iZq.json +0 -10
- package/.omo/run-continuation/ses_f2fd5cb5fffeN1tjdUyPqdKG6B.json +0 -10
- package/src/list-cell.d.ts +0 -20
- package/src/list-cell.js +0 -73
- package/src/list-support.d.ts +0 -13
- package/src/list-support.js +0 -197
package/src/list-support.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
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.
|
|
5
|
-
|
|
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 };
|
package/src/list-support.js
DELETED
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
// src/list-support.js
|
|
2
|
-
//
|
|
3
|
-
// Runs ONLY on the main thread — the counterpart to list-cell.js's
|
|
4
|
-
// background-thread half. Wires up the native list contract
|
|
5
|
-
// (__CreateList/__UpdateListCallbacks, the synchronous componentAtIndex/
|
|
6
|
-
// enqueueComponent pair, recycling cells by a type key) — unchanged from
|
|
7
|
-
// mithril-lynx v1's own device-verified list.js.
|
|
8
|
-
//
|
|
9
|
-
// What's different from v1, and from this file's own first version: a
|
|
10
|
-
// cell's content is never rendered here. `componentAtIndex` only replays
|
|
11
|
-
// ops the background thread already computed (list-cell.js), the same way
|
|
12
|
-
// apply-patch.js's own top-level applyPatch replays the app's own tree —
|
|
13
|
-
// this file has no dependency on mithril-runtime, fake-dom.js, or a virtual
|
|
14
|
-
// backend at all anymore, because it never runs a render pass of its own.
|
|
15
|
-
// componentAtIndex(itemCount) cost is therefore the cost of creating/
|
|
16
|
-
// updating real elements from an already-known op list, same as any other
|
|
17
|
-
// patch — not a render.
|
|
18
|
-
//
|
|
19
|
-
// Recycling a cell for a different item clears its real children (removed
|
|
20
|
-
// by the handle ids list-cell.js recorded — see clearWrapperChildren below)
|
|
21
|
-
// and replays the new item's ops fresh; this is a real teardown-and-rebuild
|
|
22
|
-
// of that cell's content, not a diff against what was there before. A real
|
|
23
|
-
// Mithril diff on recycle would need a persistent per-slot fake-dom
|
|
24
|
-
// document kept in sync with which native cell native actually chose to
|
|
25
|
-
// reuse — information only native has, on the main thread, which the
|
|
26
|
-
// background thread (where the diff would have to run) can't see without a
|
|
27
|
-
// round trip. Not implemented.
|
|
28
|
-
|
|
29
|
-
export function createNativeList(pageId, scrollOrientation, listType, spanCount, createPatchApplier, onEvent) {
|
|
30
|
-
let cells = []; // current cells, indexed by cellIndex — see patch-protocol.js's Op.SetListItems
|
|
31
|
-
let count = 0;
|
|
32
|
-
const signMap = new Map(); // sign -> { wrapperHandle, applier, rootChildIds, cellIndex, typeKey }
|
|
33
|
-
const recycleMap = new Map(); // typeKey -> Map<sign, entry> (entries currently off-screen, available to reuse)
|
|
34
|
-
|
|
35
|
-
function replayCell(wrapperHandle, cell) {
|
|
36
|
-
const applier = createPatchApplier(pageId, { onEvent, flush: false });
|
|
37
|
-
applier.registerRoot(cell.containerId, wrapperHandle);
|
|
38
|
-
applier.applyPatch(cell.ops);
|
|
39
|
-
return applier;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function clearWrapperChildren(entry) {
|
|
43
|
-
for (const id of entry.rootChildIds) {
|
|
44
|
-
const handle = entry.applier.getHandle(id);
|
|
45
|
-
if (handle != null) __RemoveElement(entry.wrapperHandle, handle);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** The wrapper of the lowest currently-attached cellIndex greater than
|
|
50
|
-
* `cellIndex`, or null if none — the DOM position a cell at `cellIndex`
|
|
51
|
-
* needs to be inserted before to land in the right place. Native lays
|
|
52
|
-
* cells out in document-tree order, not by `item-key`, so a recycled
|
|
53
|
-
* wrapper that keeps its OLD tree position (this file's own first
|
|
54
|
-
* version never repositioned it at all) shows up out of order the
|
|
55
|
-
* moment it's reused for a different index — confirmed on real
|
|
56
|
-
* hardware via a scrambled child order after scrolling (item-keys
|
|
57
|
-
* 7,8,9,3,4,5,6). Same problem `@lynx-js/react`'s own
|
|
58
|
-
* `attachListItemAtIndex`/`findNextAttachedItem` solves for its
|
|
59
|
-
* Element Template list. */
|
|
60
|
-
function findNextAttachedWrapper(cellIndex) {
|
|
61
|
-
let best = null;
|
|
62
|
-
for (const entry of signMap.values()) {
|
|
63
|
-
if (entry.cellIndex > cellIndex && (best == null || entry.cellIndex < best.cellIndex)) best = entry;
|
|
64
|
-
}
|
|
65
|
-
return best ? best.wrapperHandle : null;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function attachWrapper(listHandle, wrapperHandle, cellIndex) {
|
|
69
|
-
const refHandle = findNextAttachedWrapper(cellIndex);
|
|
70
|
-
if (refHandle != null) __InsertElementBefore(listHandle, wrapperHandle, refHandle);
|
|
71
|
-
else __AppendElement(listHandle, wrapperHandle);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function bindFreshItem(listHandle, listId, cellIndex, opId, cell, flush) {
|
|
75
|
-
const wrapperHandle = __CreateElement("list-item", pageId, {});
|
|
76
|
-
__SetAttribute(wrapperHandle, "item-key", String(cellIndex));
|
|
77
|
-
attachWrapper(listHandle, wrapperHandle, cellIndex);
|
|
78
|
-
|
|
79
|
-
const applier = replayCell(wrapperHandle, cell);
|
|
80
|
-
const sign = __GetElementUniqueID(wrapperHandle);
|
|
81
|
-
signMap.set(sign, { wrapperHandle, applier, rootChildIds: cell.rootChildIds, cellIndex, typeKey: cell.typeKey });
|
|
82
|
-
if (flush) __FlushElementTree(wrapperHandle, { triggerLayout: true, operationID: opId, elementID: sign, listID: listId });
|
|
83
|
-
return sign;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function bindRecycledItem(listHandle, listId, cellIndex, opId, cell, pool, flush) {
|
|
87
|
-
const [sign, entry] = pool.entries().next().value;
|
|
88
|
-
pool.delete(sign);
|
|
89
|
-
clearWrapperChildren(entry);
|
|
90
|
-
__SetAttribute(entry.wrapperHandle, "item-key", String(cellIndex));
|
|
91
|
-
attachWrapper(listHandle, entry.wrapperHandle, cellIndex);
|
|
92
|
-
const applier = replayCell(entry.wrapperHandle, cell);
|
|
93
|
-
entry.applier = applier;
|
|
94
|
-
entry.rootChildIds = cell.rootChildIds;
|
|
95
|
-
entry.cellIndex = cellIndex;
|
|
96
|
-
signMap.set(sign, entry);
|
|
97
|
-
if (flush) __FlushElementTree(entry.wrapperHandle, { triggerLayout: true, operationID: opId, elementID: sign, listID: listId });
|
|
98
|
-
return sign;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function bindCell(listHandle, listId, cellIndex, opId, flush) {
|
|
102
|
-
if (cellIndex < 0 || cellIndex >= count) {
|
|
103
|
-
throw new Error(`[mithril-lynx] list: cellIndex ${cellIndex} out of range (itemCount=${count})`);
|
|
104
|
-
}
|
|
105
|
-
const cell = cells[cellIndex];
|
|
106
|
-
const pool = recycleMap.get(cell.typeKey);
|
|
107
|
-
if (pool && pool.size > 0) return bindRecycledItem(listHandle, listId, cellIndex, opId, cell, pool, flush);
|
|
108
|
-
return bindFreshItem(listHandle, listId, cellIndex, opId, cell, flush);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function componentAtIndex(listHandle, listId, cellIndex, opId) {
|
|
112
|
-
return bindCell(listHandle, listId, cellIndex, opId, true);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* The batched form of componentAtIndex — real native calls this (not a
|
|
117
|
-
* fallback: confirmed against `@lynx-js/react`'s own
|
|
118
|
-
* componentAtIndexFactory/attachListItemAtIndex, which always registers
|
|
119
|
-
* BOTH forms) for several cells at once, expecting exactly ONE
|
|
120
|
-
* `__FlushElementTree` covering all of them (`operationIDs`/`elementIDs`
|
|
121
|
-
* arrays), not one per cell — the real reason a native list's initial,
|
|
122
|
-
* simultaneously-visible cells need this at all.
|
|
123
|
-
*/
|
|
124
|
-
function componentAtIndexes(listHandle, listId, cellIndexes, operationIDs) {
|
|
125
|
-
const elementIDs = cellIndexes.map((cellIndex, i) => bindCell(listHandle, listId, cellIndex, operationIDs[i], false));
|
|
126
|
-
__FlushElementTree(listHandle, { triggerLayout: true, operationIDs, elementIDs, listID: listId });
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function enqueueComponent(_listHandle, _listId, sign) {
|
|
130
|
-
const entry = signMap.get(sign);
|
|
131
|
-
if (entry == null) return;
|
|
132
|
-
signMap.delete(sign);
|
|
133
|
-
if (!recycleMap.has(entry.typeKey)) recycleMap.set(entry.typeKey, new Map());
|
|
134
|
-
recycleMap.get(entry.typeKey).set(sign, entry);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function sendListInfo(listHandle, listId, insertAction, removeAction, updateAction) {
|
|
138
|
-
__SetAttribute(listHandle, "update-list-info", { insertAction, removeAction, updateAction });
|
|
139
|
-
__UpdateListCallbacks(listHandle, componentAtIndex, enqueueComponent, componentAtIndexes);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const listHandle = __CreateList(pageId, componentAtIndex, enqueueComponent, {}, componentAtIndexes);
|
|
143
|
-
const listId = __GetElementUniqueID(listHandle);
|
|
144
|
-
__SetAttribute(listHandle, "scroll-orientation", scrollOrientation);
|
|
145
|
-
__SetAttribute(listHandle, "list-type", listType);
|
|
146
|
-
__SetAttribute(listHandle, "span-count", String(spanCount));
|
|
147
|
-
|
|
148
|
-
// Re-flushes every currently-attached (on-screen) cell with its newest
|
|
149
|
-
// ops — e.g. after a redraw triggered by a tap inside a cell, or any
|
|
150
|
-
// other app state change that reaches this list's items. Unconditional,
|
|
151
|
-
// same as List's own onupdate already unconditionally resends the whole
|
|
152
|
-
// `items` array on every relevant redraw (see mithril-lynx-ui's
|
|
153
|
-
// list/list.js) — a real "did this cell's content actually change"
|
|
154
|
-
// check would need to compare `ops` arrays, not implemented yet. Cells
|
|
155
|
-
// that aren't attached right now just pick up their new content next
|
|
156
|
-
// time componentAtIndex asks for that index.
|
|
157
|
-
function refreshAttachedCells(nextCells) {
|
|
158
|
-
for (const [sign, entry] of signMap) {
|
|
159
|
-
const nextCell = nextCells[entry.cellIndex];
|
|
160
|
-
clearWrapperChildren(entry);
|
|
161
|
-
entry.applier = replayCell(entry.wrapperHandle, nextCell);
|
|
162
|
-
entry.rootChildIds = nextCell.rootChildIds;
|
|
163
|
-
entry.typeKey = nextCell.typeKey;
|
|
164
|
-
signMap.set(sign, entry);
|
|
165
|
-
__FlushElementTree(entry.wrapperHandle, { triggerLayout: false });
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function setCells(nextCells) {
|
|
170
|
-
const nextCount = nextCells.length;
|
|
171
|
-
refreshAttachedCells(nextCells);
|
|
172
|
-
cells = nextCells;
|
|
173
|
-
if (nextCount === count) return;
|
|
174
|
-
if (nextCount > count) {
|
|
175
|
-
sendListInfo(
|
|
176
|
-
listHandle,
|
|
177
|
-
listId,
|
|
178
|
-
Array.from({ length: nextCount - count }, (_, i) => ({ position: count + i, type: "cell", "item-key": String(count + i) })),
|
|
179
|
-
[],
|
|
180
|
-
[],
|
|
181
|
-
);
|
|
182
|
-
} else {
|
|
183
|
-
sendListInfo(listHandle, listId, [], Array.from({ length: count - nextCount }, (_, i) => nextCount + i), []);
|
|
184
|
-
}
|
|
185
|
-
count = nextCount;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Returned separately, not as a property stuck onto `listHandle` itself:
|
|
189
|
-
// confirmed on real hardware (unlike @lynx-js/testing-environment's
|
|
190
|
-
// simulated PAPI) that a native list's own returned handle does not
|
|
191
|
-
// reliably hold a custom property across calls — assigning one there
|
|
192
|
-
// and reading it back later from apply-patch.js's Op.SetListItems case
|
|
193
|
-
// threw "TypeError: not a function" on the very first SetListItems, even
|
|
194
|
-
// with zero items. apply-patch.js keeps `setCells` in its own map
|
|
195
|
-
// instead, keyed the same way `handles` already is.
|
|
196
|
-
return { handle: listHandle, setCells };
|
|
197
|
-
}
|