ink-framework 0.1.0-alpha.10 → 0.1.0-alpha.13
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/DESIGN.md +168 -0
- package/package.json +9 -4
- package/src/canvas.ts +59 -0
- package/src/compiler-runtime.ts +6 -0
- package/src/controller-react.ts +33 -0
- package/src/data-views.ts +249 -0
- package/src/file-router.tsx +1 -1
- package/src/fixed-list-row.ts +65 -0
- package/src/image.ts +30 -2
- package/src/index.ts +31 -4
- package/src/jsx-runtime.ts +21 -0
- package/src/list.ts +71 -34
- package/src/native-list.ts +273 -74
- package/src/native.ts +8 -0
- package/src/prefresh.d.ts +3 -0
- package/src/react-compat.ts +24 -0
- package/src/react.ts +26 -6
- package/src/refresh.ts +29 -0
- package/src/renderer.ts +183 -137
- package/src/reorder.ts +12 -15
- package/src/snapshot.ts +1 -1
- package/src/visibility.ts +264 -0
package/src/renderer.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
declare const process: { env: { NODE_ENV: string } };
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
2
|
+
import { options, render as renderPreact, type Component, type ComponentChildren, type VNode } from "preact";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import { Activity, activityChanged, isComponentVisible } from "./visibility";
|
|
5
5
|
import { onNativeMessage } from "./native";
|
|
6
6
|
import { pressHandler } from "./press";
|
|
7
7
|
import type { Destination } from "./navigation";
|
|
@@ -11,8 +11,74 @@ declare const __inkCommit: (operations: Operation[]) => void;
|
|
|
11
11
|
declare const __inkPost: (message: string) => void;
|
|
12
12
|
|
|
13
13
|
type Props = Record<string, unknown>;
|
|
14
|
-
|
|
15
|
-
type
|
|
14
|
+
const hostProperties = Symbol("ink.hostProps");
|
|
15
|
+
type HostProps = Props & { [hostProperties]?: Props };
|
|
16
|
+
// Preact only needs tree links and mutation methods; no browser DOM is involved.
|
|
17
|
+
class HostNode {
|
|
18
|
+
id: number;
|
|
19
|
+
props: Props = {};
|
|
20
|
+
parentNode: HostNode | null = null;
|
|
21
|
+
firstChild: HostNode | null = null;
|
|
22
|
+
lastChild: HostNode | null = null;
|
|
23
|
+
previousSibling: HostNode | null = null;
|
|
24
|
+
nextSibling: HostNode | null = null;
|
|
25
|
+
hidden = false;
|
|
26
|
+
mounted = false;
|
|
27
|
+
readonly nodeType: number;
|
|
28
|
+
readonly namespaceURI = "http://www.w3.org/1999/xhtml";
|
|
29
|
+
constructor(readonly type: string, text?: string) {
|
|
30
|
+
this.id = type === "#root" ? 0 : allocateHostId();
|
|
31
|
+
this.nodeType = type === "#text" ? 3 : 1;
|
|
32
|
+
if (text !== undefined) this.props = { text: String(text) };
|
|
33
|
+
}
|
|
34
|
+
get localName() { return this.type; }
|
|
35
|
+
get childNodes(): HostNode[] {
|
|
36
|
+
const nodes: HostNode[] = [];
|
|
37
|
+
for (let child = this.firstChild; child; child = child.nextSibling) nodes.push(child);
|
|
38
|
+
return nodes;
|
|
39
|
+
}
|
|
40
|
+
get data() { return this.props.text; }
|
|
41
|
+
set data(text: unknown) {
|
|
42
|
+
this.props = { text: String(text) };
|
|
43
|
+
if (this.mounted) (textChanges ?? createTextBatch()).push(this.id, String(text));
|
|
44
|
+
}
|
|
45
|
+
// Native props and callbacks are read together from the completed host vnode.
|
|
46
|
+
setAttribute() {}
|
|
47
|
+
removeAttribute() {}
|
|
48
|
+
addEventListener() {}
|
|
49
|
+
removeEventListener() {}
|
|
50
|
+
appendChild(child: HostNode) { this.insertBefore(child, null); }
|
|
51
|
+
insertBefore(child: HostNode, before: HostNode | null) {
|
|
52
|
+
if (child === before) return;
|
|
53
|
+
if (child.parentNode) child.parentNode.unlink(child);
|
|
54
|
+
child.parentNode = this;
|
|
55
|
+
child.previousSibling = before ? before.previousSibling : this.lastChild;
|
|
56
|
+
child.nextSibling = before;
|
|
57
|
+
if (child.previousSibling) child.previousSibling.nextSibling = child;
|
|
58
|
+
else this.firstChild = child;
|
|
59
|
+
if (before) before.previousSibling = child;
|
|
60
|
+
else this.lastChild = child;
|
|
61
|
+
if (this.id === 0 || this.mounted) {
|
|
62
|
+
publish(child);
|
|
63
|
+
queue({ op: "insert", id: child.id, parent: this.id, before: before?.id ?? null });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
unlink(child: HostNode) {
|
|
67
|
+
if (child.previousSibling) child.previousSibling.nextSibling = child.nextSibling;
|
|
68
|
+
else this.firstChild = child.nextSibling;
|
|
69
|
+
if (child.nextSibling) child.nextSibling.previousSibling = child.previousSibling;
|
|
70
|
+
else this.lastChild = child.previousSibling;
|
|
71
|
+
child.parentNode = child.previousSibling = child.nextSibling = null;
|
|
72
|
+
}
|
|
73
|
+
removeChild(child: HostNode) {
|
|
74
|
+
this.unlink(child);
|
|
75
|
+
if (child.mounted) {
|
|
76
|
+
queue({ op: "remove", id: child.id, parent: this.id });
|
|
77
|
+
forget(child);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
type Instance = HostNode;
|
|
16
82
|
type Operation =
|
|
17
83
|
| { op: "create"; id: number; type: string; props: Props }
|
|
18
84
|
| { op: "update"; id: number; props: Props }
|
|
@@ -26,8 +92,6 @@ let nextId = 1;
|
|
|
26
92
|
let operations: Operation[] = [];
|
|
27
93
|
let textChanges: (number | string)[] | undefined;
|
|
28
94
|
let commitScheduled = false;
|
|
29
|
-
let priority = DefaultEventPriority;
|
|
30
|
-
const hostContext = {};
|
|
31
95
|
|
|
32
96
|
function allocateHostId() {
|
|
33
97
|
if (!Number.isSafeInteger(nextId)) throw new RangeError("Host identifiers exhausted");
|
|
@@ -47,7 +111,7 @@ function createTextBatch() {
|
|
|
47
111
|
|
|
48
112
|
function textContent(type: string, props: Props): string | null {
|
|
49
113
|
if (type !== "Text") return null;
|
|
50
|
-
const value = props.children;
|
|
114
|
+
const value = props.text ?? props.children;
|
|
51
115
|
return typeof value === "string" ? value : typeof value === "number" || typeof value === "bigint" ? String(value) : null;
|
|
52
116
|
}
|
|
53
117
|
|
|
@@ -75,34 +139,21 @@ function nativeProps(props: Props, type: string): Props {
|
|
|
75
139
|
}
|
|
76
140
|
|
|
77
141
|
function publish(instance: Instance) {
|
|
78
|
-
if (
|
|
142
|
+
if (instance.mounted) return;
|
|
143
|
+
instance.mounted = true;
|
|
79
144
|
mounted.set(instance.id, instance);
|
|
80
145
|
queue({ op: "create", id: instance.id, type: instance.type, props: nativeProps(instance.props, instance.type) });
|
|
81
146
|
if (instance.hidden) queue({ op: "hidden", id: instance.id, value: true });
|
|
82
|
-
for (
|
|
147
|
+
for (let child = instance.firstChild; child; child = child.nextSibling) {
|
|
83
148
|
publish(child);
|
|
84
149
|
queue({ op: "insert", id: child.id, parent: instance.id, before: null });
|
|
85
150
|
}
|
|
86
151
|
}
|
|
87
152
|
|
|
88
|
-
function insert(parent: Container, child: Instance, before?: Instance) {
|
|
89
|
-
const previous = parent.children.indexOf(child);
|
|
90
|
-
if (previous !== -1) parent.children.splice(previous, 1);
|
|
91
|
-
const index = before ? parent.children.indexOf(before) : parent.children.length;
|
|
92
|
-
parent.children.splice(index, 0, child);
|
|
93
|
-
publish(child);
|
|
94
|
-
queue({ op: "insert", id: child.id, parent: parent.id, before: before?.id ?? null });
|
|
95
|
-
}
|
|
96
|
-
|
|
97
153
|
function forget(instance: Instance) {
|
|
154
|
+
instance.mounted = false;
|
|
98
155
|
mounted.delete(instance.id);
|
|
99
|
-
for (
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function remove(parent: Container, child: Instance) {
|
|
103
|
-
parent.children.splice(parent.children.indexOf(child), 1);
|
|
104
|
-
queue({ op: "remove", id: child.id, parent: parent.id });
|
|
105
|
-
forget(child);
|
|
156
|
+
for (let child = instance.firstChild; child; child = child.nextSibling) forget(child);
|
|
106
157
|
}
|
|
107
158
|
|
|
108
159
|
function sameListValue(a: unknown, b: unknown): boolean {
|
|
@@ -145,9 +196,11 @@ function compiledListUpdate(previous: Props, props: Props): Props | null {
|
|
|
145
196
|
|
|
146
197
|
function update(instance: Instance, type: string, _oldProps: Props, props: Props) {
|
|
147
198
|
const previous = instance.props;
|
|
199
|
+
if (previous === props) return;
|
|
148
200
|
const isText = type === "Text";
|
|
149
201
|
if (isText) props = hostProps(type, props);
|
|
150
202
|
instance.props = props;
|
|
203
|
+
if (!instance.mounted) return;
|
|
151
204
|
if (type === "NativeList" && props.keyField === "key" && previous.template === props.template) {
|
|
152
205
|
const next = compiledListUpdate(previous, props);
|
|
153
206
|
if (next) queue({ op: "update", id: instance.id, props: next }, true);
|
|
@@ -161,10 +214,11 @@ function update(instance: Instance, type: string, _oldProps: Props, props: Props
|
|
|
161
214
|
queue({ op: "update", id: instance.id, props: nativeProps(props, type) });
|
|
162
215
|
return;
|
|
163
216
|
}
|
|
164
|
-
const children = props.children;
|
|
165
|
-
if (children !== previous.children) {
|
|
217
|
+
const children = props.text ?? props.children;
|
|
218
|
+
if (children !== (previous.text ?? previous.children)) {
|
|
166
219
|
if (typeof children === "string") return (textChanges ?? createTextBatch()).push(instance.id, children);
|
|
167
220
|
if (typeof children === "number" || typeof children === "bigint") return (textChanges ?? createTextBatch()).push(instance.id, String(children));
|
|
221
|
+
if (textContent(type, previous) !== null) (textChanges ?? createTextBatch()).push(instance.id, "");
|
|
168
222
|
}
|
|
169
223
|
return;
|
|
170
224
|
}
|
|
@@ -202,8 +256,9 @@ function update(instance: Instance, type: string, _oldProps: Props, props: Props
|
|
|
202
256
|
}
|
|
203
257
|
|
|
204
258
|
function hide(instance: Instance, value: boolean) {
|
|
259
|
+
if (instance.hidden === value) return;
|
|
205
260
|
instance.hidden = value;
|
|
206
|
-
if (
|
|
261
|
+
if (instance.mounted) queue({ op: "hidden", id: instance.id, value });
|
|
207
262
|
}
|
|
208
263
|
|
|
209
264
|
function errorMessage(error: unknown, info: { componentStack?: string | null }) {
|
|
@@ -215,117 +270,101 @@ function report(error: unknown, info: { componentStack?: string | null }) {
|
|
|
215
270
|
__inkPost(JSON.stringify({ type: "error", message: errorMessage(error, info) }));
|
|
216
271
|
}
|
|
217
272
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
273
|
+
// These fields are the stable, mangled option-hook ABI in the pinned Preact 10.
|
|
274
|
+
type HostVNode = VNode<Props> & { __e?: HostNode; __?: HostVNode; __k?: (HostVNode | null)[]; __c?: Component };
|
|
275
|
+
const hooks = options as typeof options & {
|
|
276
|
+
__c?: (vnode: HostVNode, queue: unknown[]) => void;
|
|
277
|
+
__inkNative?: boolean;
|
|
278
|
+
__inkHost?: (node: HostNode, type: string, props: Props, vnode: HostVNode) => void;
|
|
279
|
+
};
|
|
280
|
+
hooks.__inkHost = (node, type, props, vnode) => {
|
|
281
|
+
update(node, type, node.props, props);
|
|
282
|
+
if (!node.parentNode && isHidden(vnode)) node.hidden = true;
|
|
283
|
+
};
|
|
284
|
+
const previousDiff = options.diffed;
|
|
285
|
+
options.diffed = vnode => {
|
|
286
|
+
if (typeof vnode.type !== "string" && vnode.type != null) previousDiff?.(vnode);
|
|
287
|
+
const host = vnode as HostVNode;
|
|
288
|
+
if (!hooks.__inkNative) {
|
|
289
|
+
if (typeof host.type === "string" && host.__e) update(host.__e, host.type, host.__e.props, (host.props as HostProps)[hostProperties]!);
|
|
290
|
+
if (host.__e && (typeof host.type === "string" || host.type == null)) hide(host.__e, isHidden(host));
|
|
291
|
+
}
|
|
292
|
+
if (hooks.__inkNative && host.type == null && host.__e) hide(host.__e, isHidden(host));
|
|
293
|
+
if (host.type === Activity && host.__c && activityChanged(host.__c)) visitVisibility(host);
|
|
294
|
+
};
|
|
221
295
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
if (type !== "Text") return false;
|
|
235
|
-
const kind = typeof props.children;
|
|
236
|
-
return kind === "string" || kind === "number" || kind === "bigint";
|
|
237
|
-
},
|
|
238
|
-
getRootHostContext: () => hostContext,
|
|
239
|
-
getChildHostContext: () => hostContext,
|
|
240
|
-
getPublicInstance: instance => instance,
|
|
241
|
-
prepareForCommit: () => null,
|
|
242
|
-
resetAfterCommit: () => {
|
|
243
|
-
if (!operations.length || commitScheduled) return;
|
|
244
|
-
commitScheduled = true;
|
|
245
|
-
queueMicrotask(() => {
|
|
246
|
-
// Revealing an Activity reconnects store subscriptions in passive effects.
|
|
247
|
-
// Include their synchronous corrections before presenting the page.
|
|
248
|
-
while (reconciler.flushPassiveEffects()) {}
|
|
249
|
-
reconciler.flushSyncWork();
|
|
250
|
-
const committed = operations;
|
|
251
|
-
operations = [];
|
|
252
|
-
textChanges = undefined;
|
|
253
|
-
commitScheduled = false;
|
|
254
|
-
if (committed.length) __inkCommit(committed);
|
|
255
|
-
});
|
|
256
|
-
},
|
|
257
|
-
preparePortalMount: () => {},
|
|
258
|
-
scheduleTimeout: setTimeout,
|
|
259
|
-
cancelTimeout: clearTimeout,
|
|
260
|
-
noTimeout: -1,
|
|
261
|
-
scheduleMicrotask: queueMicrotask,
|
|
262
|
-
getInstanceFromNode: () => null,
|
|
263
|
-
beforeActiveInstanceBlur: () => {},
|
|
264
|
-
afterActiveInstanceBlur: () => {},
|
|
265
|
-
prepareScopeUpdate: () => {},
|
|
266
|
-
getInstanceFromScope: () => null,
|
|
267
|
-
detachDeletedInstance: () => {},
|
|
268
|
-
appendChild: insert,
|
|
269
|
-
appendChildToContainer: insert,
|
|
270
|
-
insertBefore: insert,
|
|
271
|
-
insertInContainerBefore: insert,
|
|
272
|
-
removeChild: remove,
|
|
273
|
-
removeChildFromContainer: remove,
|
|
274
|
-
resetTextContent: instance => { (textChanges ?? createTextBatch()).push(instance.id, ""); },
|
|
275
|
-
commitTextUpdate: (instance, _oldText, text) => {
|
|
276
|
-
instance.props = { text };
|
|
277
|
-
(textChanges ?? createTextBatch()).push(instance.id, text);
|
|
278
|
-
},
|
|
279
|
-
commitUpdate: update,
|
|
280
|
-
hideInstance: instance => hide(instance, true),
|
|
281
|
-
hideTextInstance: instance => hide(instance, true),
|
|
282
|
-
unhideInstance: instance => hide(instance, false),
|
|
283
|
-
unhideTextInstance: instance => hide(instance, false),
|
|
284
|
-
clearContainer: container => {
|
|
285
|
-
for (const child of [...container.children]) remove(container, child);
|
|
286
|
-
},
|
|
287
|
-
NotPendingTransition: null,
|
|
288
|
-
// React's public Context type omits the fields consumed by its reconciler.
|
|
289
|
-
HostTransitionContext: createContext(null) as unknown as Reconciler.ReactContext<null>,
|
|
290
|
-
setCurrentUpdatePriority: value => { priority = value; },
|
|
291
|
-
getCurrentUpdatePriority: () => priority,
|
|
292
|
-
resolveUpdatePriority: () => priority,
|
|
293
|
-
resetFormInstance: () => {},
|
|
294
|
-
requestPostPaintCallback: callback => { setTimeout(() => callback(performance.now()), 0); },
|
|
295
|
-
shouldAttemptEagerTransition: () => false,
|
|
296
|
-
trackSchedulerEvent: () => {},
|
|
297
|
-
resolveEventType: () => null,
|
|
298
|
-
resolveEventTimeStamp: () => performance.now(),
|
|
299
|
-
maySuspendCommit: () => false,
|
|
300
|
-
preloadInstance: () => true,
|
|
301
|
-
startSuspendingCommit: () => {},
|
|
302
|
-
suspendInstance: () => {},
|
|
303
|
-
waitForCommitToBeReady: () => null,
|
|
304
|
-
});
|
|
296
|
+
function isHidden(vnode: HostVNode) {
|
|
297
|
+
let component: Component | undefined;
|
|
298
|
+
for (let parent = vnode.__; parent; parent = parent.__) {
|
|
299
|
+
// A native ancestor already carries visibility for this entire branch.
|
|
300
|
+
if (typeof parent.type === "string") return false;
|
|
301
|
+
if (parent.__c) {
|
|
302
|
+
component ??= parent.__c;
|
|
303
|
+
if (parent.type === Activity) return !isComponentVisible(parent.__c);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return component ? !isComponentVisible(component) : false;
|
|
307
|
+
}
|
|
305
308
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
priority = DiscreteEventPriority;
|
|
311
|
-
try {
|
|
312
|
-
handler(...args);
|
|
313
|
-
} finally {
|
|
314
|
-
priority = previous;
|
|
309
|
+
function visitVisibility(vnode: HostVNode) {
|
|
310
|
+
if (typeof vnode.type === "string" || vnode.type == null) {
|
|
311
|
+
if (vnode.__e) hide(vnode.__e, isHidden(vnode));
|
|
312
|
+
return;
|
|
315
313
|
}
|
|
314
|
+
for (const child of vnode.__k ?? []) if (child) visitVisibility(child);
|
|
316
315
|
}
|
|
317
316
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
317
|
+
const previousVNode = options.vnode;
|
|
318
|
+
options.vnode = vnode => {
|
|
319
|
+
if (typeof vnode.type !== "string") {
|
|
320
|
+
previousVNode?.(vnode);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const props = vnode.props as HostProps;
|
|
324
|
+
// Preact reconciles children; Ink compares native props once, after the diff.
|
|
325
|
+
// Keep the original props in this envelope so cloned compiler-cached vnodes
|
|
326
|
+
// retain them without copying or normalising every native property.
|
|
327
|
+
if (!hooks.__inkNative && !props[hostProperties]) {
|
|
328
|
+
(vnode as VNode<HostProps>).props = {
|
|
329
|
+
children: (textContent(vnode.type, props) === null ? props.children : undefined) as ComponentChildren,
|
|
330
|
+
[hostProperties]: props,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
// QuickJS has no browser paint cycle. Run effects before presenting queued ops.
|
|
336
|
+
options.requestAnimationFrame = callback => { queueMicrotask(callback); };
|
|
337
|
+
options.debounceRendering = queueMicrotask;
|
|
338
|
+
const previousCommit = hooks.__c;
|
|
339
|
+
hooks.__c = (vnode, components) => {
|
|
340
|
+
previousCommit?.(vnode, components);
|
|
341
|
+
if (commitScheduled) return;
|
|
342
|
+
commitScheduled = true;
|
|
343
|
+
queueMicrotask(() => queueMicrotask(() => {
|
|
344
|
+
const committed = operations;
|
|
345
|
+
operations = [];
|
|
346
|
+
textChanges = undefined;
|
|
347
|
+
commitScheduled = false;
|
|
348
|
+
if (committed.length) __inkCommit(committed);
|
|
349
|
+
}));
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
// Preact creates nodes through document, but all layout and painting stay native.
|
|
353
|
+
Object.assign(globalThis, { document: {
|
|
354
|
+
createElementNS: (_namespace: string, type: string) => new HostNode(type),
|
|
355
|
+
createElement: (type: string) => new HostNode(type),
|
|
356
|
+
createTextNode: (text: string) => new HostNode("#text", text),
|
|
357
|
+
} });
|
|
358
|
+
|
|
359
|
+
export function dispatchEvent(id: number, name: string, args: unknown[]) {
|
|
360
|
+
const handler = mounted.get(id)?.props[name];
|
|
361
|
+
if (typeof handler === "function") handler(...args);
|
|
321
362
|
}
|
|
322
363
|
|
|
364
|
+
let developmentRoot: HostNode | undefined;
|
|
323
365
|
export function render(element: ReactNode) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
reconciler.updateContainer(element, root, null, null);
|
|
327
|
-
return () => reconciler.updateContainer(null, root, null, null);
|
|
328
|
-
}
|
|
366
|
+
const container = process.env.NODE_ENV === "development" && developmentRoot || new HostNode("#root");
|
|
367
|
+
if (process.env.NODE_ENV === "development") developmentRoot = container;
|
|
329
368
|
onNativeMessage("event", message => {
|
|
330
369
|
if (typeof message.id !== "number" || !Number.isSafeInteger(message.id)
|
|
331
370
|
|| typeof message.name !== "string" || !Array.isArray(message.args)) {
|
|
@@ -333,9 +372,16 @@ export function render(element: ReactNode) {
|
|
|
333
372
|
}
|
|
334
373
|
dispatchEvent(message.id, message.name, message.args);
|
|
335
374
|
});
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
375
|
+
const updateRoot = () => {
|
|
376
|
+
try {
|
|
377
|
+
renderPreact(element as ComponentChildren, container as unknown as Parameters<typeof renderPreact>[1]);
|
|
378
|
+
} catch (error) {
|
|
379
|
+
report(error, {});
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
// Prefresh replaces mounted component types at the end of the bundle.
|
|
383
|
+
// Apply the new root afterwards so matching components keep their state.
|
|
384
|
+
if (process.env.NODE_ENV === "development") queueMicrotask(updateRoot);
|
|
385
|
+
else updateRoot();
|
|
386
|
+
return () => renderPreact(null, container as unknown as Parameters<typeof renderPreact>[1]);
|
|
341
387
|
}
|
package/src/reorder.ts
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { createElement } from "./react";
|
|
2
|
-
import { Button, Stack, Text } from "./index";
|
|
3
2
|
import { keyboardArrowDown, keyboardArrowUp } from "./icons";
|
|
4
3
|
import { List } from "./list";
|
|
5
|
-
import {
|
|
4
|
+
import { fixedListRow } from "./native-list";
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
type ReorderFields = {
|
|
7
|
+
label: string; maxLines?: number; first: boolean; last: boolean;
|
|
8
|
+
down: () => void; up: () => void;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const reorderRow = /* @__PURE__ */ fixedListRow<ReorderFields>((node, field) => node("Stack", { axis: "horizontal", align: "center", justify: "space-between", gap: 12 }, [
|
|
12
|
+
node("Text", { text: field(values => values.label), maxLines: field(values => values.maxLines) }),
|
|
9
13
|
node("Stack", { axis: "horizontal", align: "center", gap: 4 }, [
|
|
10
|
-
node("Button", { icon: keyboardArrowDown, disabled: field(
|
|
11
|
-
node("Button", { icon: keyboardArrowUp, disabled: field(
|
|
14
|
+
node("Button", { icon: keyboardArrowDown, disabled: field(values => values.last), onPress: field(values => values.down) }),
|
|
15
|
+
node("Button", { icon: keyboardArrowUp, disabled: field(values => values.first), onPress: field(values => values.up) }),
|
|
12
16
|
]),
|
|
13
17
|
]));
|
|
14
18
|
|
|
@@ -35,15 +39,8 @@ export function ReorderList<T>({ items, keyExtractor, getLabel, onChange, multil
|
|
|
35
39
|
keyExtractor,
|
|
36
40
|
gap,
|
|
37
41
|
measurementKey: multiline ? "multiline" : "single-line",
|
|
38
|
-
renderItem:
|
|
39
|
-
axis: "horizontal", align: "center", justify: "space-between", gap: 12,
|
|
40
|
-
},
|
|
41
|
-
createElement(Text, { maxLines: multiline ? undefined : 1 }, getLabel(item)),
|
|
42
|
-
createElement(Stack, { axis: "horizontal", align: "center", gap: 4 },
|
|
43
|
-
createElement(Button, { icon: keyboardArrowDown, disabled: index === items.length - 1, onPress: () => move(index, 1) }),
|
|
44
|
-
createElement(Button, { icon: keyboardArrowUp, disabled: index === 0, onPress: () => move(index, -1) }),
|
|
45
|
-
)), (item, index) => [{ label: getLabel(item), maxLines: multiline ? undefined : 1,
|
|
42
|
+
renderItem: reorderRow((item: T, index: number) => ({ label: getLabel(item), maxLines: multiline ? undefined : 1,
|
|
46
43
|
first: index === 0, last: index === items.length - 1,
|
|
47
|
-
down: () => move(index, 1), up: () => move(index, -1) }
|
|
44
|
+
down: () => move(index, 1), up: () => move(index, -1) })),
|
|
48
45
|
});
|
|
49
46
|
}
|
package/src/snapshot.ts
CHANGED
|
@@ -13,6 +13,6 @@ export interface SnapshotSource<T> {
|
|
|
13
13
|
|
|
14
14
|
export function useSnapshot<T>(source: ResourceSource<T>): ResourceSnapshot<T>;
|
|
15
15
|
export function useSnapshot<T>(source: SnapshotSource<T>): Snapshot<T>;
|
|
16
|
-
export function useSnapshot
|
|
16
|
+
export function useSnapshot(source: { subscribe(listener: () => void): () => void; getSnapshot(): unknown }): unknown {
|
|
17
17
|
return useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);
|
|
18
18
|
}
|