ink-framework 0.1.0-alpha.12 → 0.1.0-alpha.14
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 +8 -4
- package/src/compiler-runtime.ts +6 -0
- package/src/controller-react.ts +33 -0
- package/src/controller.ts +9 -2
- package/src/file-router.tsx +1 -1
- package/src/fixed-list-row.ts +65 -0
- package/src/icons.ts +2 -0
- package/src/image.ts +16 -2
- package/src/index.ts +30 -2
- package/src/jsx-runtime.ts +21 -0
- package/src/list.ts +71 -34
- package/src/native-list.ts +12 -18
- package/src/native.ts +8 -0
- package/src/playing.ts +4 -4
- 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 -140
- package/src/reorder.ts +12 -15
- package/src/visibility.ts +264 -0
package/src/list.ts
CHANGED
|
@@ -5,9 +5,7 @@ import { Button, Stack, Text } from "./index";
|
|
|
5
5
|
import { CompiledList, nativeListPlan } from "./native-list";
|
|
6
6
|
import { ScrollToEndContext } from "./scroll";
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export function ListLoadError({ message, onRetry }: { message: string; onRetry: () => void }) {
|
|
8
|
+
function ListLoadError({ message, onRetry }: { message: string; onRetry: () => void }) {
|
|
11
9
|
return createElement(Stack, { gap: 24 },
|
|
12
10
|
createElement(Text, { size: 18 }, message),
|
|
13
11
|
createElement(Button, { onPress: onRetry }, "Try again"),
|
|
@@ -28,7 +26,65 @@ export type ListProps<T> = {
|
|
|
28
26
|
initialPosition?: "start" | "end";
|
|
29
27
|
};
|
|
30
28
|
|
|
29
|
+
type PaginationProps = Pick<ListProps<unknown>, "onLoadMore" | "hasMore" | "onLoadOlder" | "hasOlder">;
|
|
30
|
+
export type ListPagination = ReturnType<typeof useListPagination>;
|
|
31
|
+
|
|
32
|
+
function useListPage(load: (() => Promise<void>) | undefined, available: boolean) {
|
|
33
|
+
const accepted = useRef<string | undefined>(undefined);
|
|
34
|
+
const action = useAction((edge?: string) => {
|
|
35
|
+
// useAction may decline a pending request; record acceptance only when it starts.
|
|
36
|
+
if (edge !== undefined) accepted.current = edge;
|
|
37
|
+
return load?.();
|
|
38
|
+
});
|
|
39
|
+
return {
|
|
40
|
+
available: available && action.status !== "error",
|
|
41
|
+
enabled: !!load,
|
|
42
|
+
pending: action.status === "pending",
|
|
43
|
+
error: action.status === "error" ? action.error : undefined,
|
|
44
|
+
retry: () => action.run(),
|
|
45
|
+
request(edge: string) {
|
|
46
|
+
if (available && load && action.status !== "pending" && action.status !== "error" && accepted.current !== edge) action.run(edge);
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function useListPagination({ onLoadMore, hasMore = false, onLoadOlder, hasOlder = false }: PaginationProps) {
|
|
52
|
+
const more = useListPage(onLoadMore, hasMore);
|
|
53
|
+
const older = useListPage(onLoadOlder, hasOlder);
|
|
54
|
+
return {
|
|
55
|
+
more,
|
|
56
|
+
older,
|
|
57
|
+
wrap(list: ReactNode) {
|
|
58
|
+
return createElement(Fragment, null,
|
|
59
|
+
older.error && createElement(ListLoadError, { message: older.error.message, onRetry: older.retry }),
|
|
60
|
+
list,
|
|
61
|
+
more.error && createElement(ListLoadError, { message: more.error.message, onRetry: more.retry }),
|
|
62
|
+
);
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function useListPaginationEvents(pagination: ListPagination, paused: boolean, count: number, first?: string, last?: string) {
|
|
68
|
+
const [startRequest, setStartRequest] = useState<string>();
|
|
69
|
+
const [endRequest, setEndRequest] = useState<string>();
|
|
70
|
+
const start = JSON.stringify([count, first ?? null]);
|
|
71
|
+
const end = JSON.stringify([count, last ?? null]);
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (!paused && startRequest === start) pagination.older.request(startRequest);
|
|
74
|
+
if (!paused && endRequest === end) pagination.more.request(endRequest);
|
|
75
|
+
}, [pagination, paused, startRequest, endRequest, start, end]);
|
|
76
|
+
return {
|
|
77
|
+
hasMore: !paused && pagination.more.available && !pagination.more.pending,
|
|
78
|
+
hasOlder: !paused && pagination.older.available && !pagination.older.pending,
|
|
79
|
+
moreAvailable: pagination.more.available && pagination.more.enabled,
|
|
80
|
+
// Retain notifications even when the current commit cannot start loading yet.
|
|
81
|
+
onEndReached: setEndRequest,
|
|
82
|
+
onStartReached: setStartRequest,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
31
86
|
export function List<T>(props: ListProps<T>) {
|
|
87
|
+
const pagination = useListPagination(props);
|
|
32
88
|
const requestEnd = useContext(ScrollToEndContext);
|
|
33
89
|
const initialised = useRef(false);
|
|
34
90
|
useLayoutEffect(() => {
|
|
@@ -37,10 +93,12 @@ export function List<T>(props: ListProps<T>) {
|
|
|
37
93
|
if (props.initialPosition === "end") requestEnd?.();
|
|
38
94
|
}, [props.initialPosition, props.items.length, requestEnd]);
|
|
39
95
|
const plan = nativeListPlan(props.renderItem);
|
|
40
|
-
return
|
|
96
|
+
return pagination.wrap(plan
|
|
97
|
+
? createElement(CompiledList<T>, { ...props, pagination, plan, key: plan.template })
|
|
98
|
+
: createElement(ReactList<T>, { ...props, pagination }));
|
|
41
99
|
}
|
|
42
100
|
|
|
43
|
-
export function ReactList<T>({ items, keyExtractor, renderItem, gap = 47, followEnd = false, measurementKey,
|
|
101
|
+
export function ReactList<T>({ items, keyExtractor, renderItem, gap = 47, followEnd = false, measurementKey, initialPosition = "start", pagination }: ListProps<T> & { pagination: ListPagination }) {
|
|
44
102
|
if (!Number.isFinite(gap) || gap < 0) {
|
|
45
103
|
throw new Error("List gap must be finite and non-negative");
|
|
46
104
|
}
|
|
@@ -51,11 +109,7 @@ export function ReactList<T>({ items, keyExtractor, renderItem, gap = 47, follow
|
|
|
51
109
|
items: readonly T[]; keys: string[]; contentVersions: number[]; revision: number;
|
|
52
110
|
records: Map<string, { item: T; version: number }>;
|
|
53
111
|
} | null>(null);
|
|
54
|
-
const [window, setWindow] = useState({ start: initialPosition === "end" ? Math.max(0, items.length - 32) : 0, end: initialPosition === "end" ? items.length : 32, revision: 0
|
|
55
|
-
const requested = useRef<string | null>(null);
|
|
56
|
-
const load = useAction(() => onLoadMore?.());
|
|
57
|
-
const olderRequested = useRef<string | null>(null);
|
|
58
|
-
const older = useAction(() => onLoadOlder?.());
|
|
112
|
+
const [window, setWindow] = useState({ start: initialPosition === "end" ? Math.max(0, items.length - 32) : 0, end: initialPosition === "end" ? items.length : 32, revision: 0 });
|
|
59
113
|
let start = Math.min(window.start, items.length);
|
|
60
114
|
let end = Math.min(window.end, items.length);
|
|
61
115
|
if (initialWindow.current && items.length) {
|
|
@@ -80,12 +134,12 @@ export function ReactList<T>({ items, keyExtractor, renderItem, gap = 47, follow
|
|
|
80
134
|
throw new Error("List metadata exceeds 128 KiB; use shorter keys or a bounded data window");
|
|
81
135
|
}
|
|
82
136
|
if (initialWindow.current && items.length) {
|
|
83
|
-
if (start !== window.start || end !== window.end) setWindow({ start, end, revision
|
|
137
|
+
if (start !== window.start || end !== window.end) setWindow({ start, end, revision });
|
|
84
138
|
} else if (old && window.revision !== revision && old.keys[window.start] !== undefined) {
|
|
85
139
|
const mapped = keys.indexOf(old.keys[window.start]);
|
|
86
140
|
start = mapped < 0 ? Math.min(window.start, Math.max(0, items.length - 1)) : mapped;
|
|
87
141
|
end = Math.min(start + window.end - window.start, items.length);
|
|
88
|
-
if (start !== window.start || end !== window.end) setWindow({ start, end, revision
|
|
142
|
+
if (start !== window.start || end !== window.end) setWindow({ start, end, revision });
|
|
89
143
|
}
|
|
90
144
|
data = { items, keys, keyExtractor, contentVersions, revision, records, measurementKey };
|
|
91
145
|
}
|
|
@@ -95,40 +149,23 @@ export function ReactList<T>({ items, keyExtractor, renderItem, gap = 47, follow
|
|
|
95
149
|
if (items.length) initialWindow.current = false;
|
|
96
150
|
});
|
|
97
151
|
const { revision, contentVersions } = data;
|
|
98
|
-
const
|
|
99
|
-
useEffect(() => {
|
|
100
|
-
if (!onLoadMore || !hasMore || load.status === "pending" || load.status === "error"
|
|
101
|
-
|| window.revision !== revision || !window.nearEnd || requested.current === boundary) return;
|
|
102
|
-
requested.current = boundary;
|
|
103
|
-
load.run();
|
|
104
|
-
}, [onLoadMore, hasMore, load.status, load.run, window, revision, boundary]);
|
|
105
|
-
const firstBoundary = JSON.stringify([keys.length, keys[0]]);
|
|
106
|
-
useEffect(() => {
|
|
107
|
-
if (!onLoadOlder || !hasOlder || older.status === "pending" || older.status === "error"
|
|
108
|
-
|| window.revision !== revision || window.start > OLDER_PAGE_AHEAD_ITEMS || olderRequested.current === firstBoundary) return;
|
|
109
|
-
olderRequested.current = firstBoundary;
|
|
110
|
-
older.run();
|
|
111
|
-
}, [onLoadOlder, hasOlder, older.status, older.run, window, revision, firstBoundary]);
|
|
152
|
+
const paginationProps = useListPaginationEvents(pagination, window.revision !== revision, keys.length, keys[0], keys.at(-1));
|
|
112
153
|
const children: ReactNode[] = [];
|
|
113
154
|
for (let index = start; index < end; index++) {
|
|
114
155
|
children.push(createElement("Stack", { key: keys[index], axis: "vertical", align: "stretch" }, renderItem(items[index], index)));
|
|
115
156
|
}
|
|
116
157
|
const props = {
|
|
158
|
+
...paginationProps,
|
|
117
159
|
keys: data.keys,
|
|
118
160
|
contentVersions,
|
|
119
161
|
revision,
|
|
120
162
|
gap,
|
|
121
163
|
followEnd,
|
|
122
164
|
start,
|
|
123
|
-
onWindow(start: number, end: number, eventRevision: number
|
|
165
|
+
onWindow(start: number, end: number, eventRevision: number) {
|
|
124
166
|
if (eventRevision !== revision) return;
|
|
125
|
-
setWindow(previous => previous.start === start && previous.end === end && previous.revision === eventRevision
|
|
167
|
+
setWindow(previous => previous.start === start && previous.end === end && previous.revision === eventRevision ? previous : { start, end, revision: eventRevision });
|
|
126
168
|
},
|
|
127
169
|
};
|
|
128
|
-
|
|
129
|
-
return createElement(Fragment, null,
|
|
130
|
-
older.status === "error" && createElement(ListLoadError, { message: older.error.message, onRetry: older.run }),
|
|
131
|
-
list,
|
|
132
|
-
load.status === "error" && createElement(ListLoadError, { message: load.error.message, onRetry: load.run }),
|
|
133
|
-
);
|
|
170
|
+
return createElement("List", props, children);
|
|
134
171
|
}
|
package/src/native-list.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { createElement,
|
|
2
|
-
import {
|
|
3
|
-
import { useAction } from "./action";
|
|
1
|
+
import { createElement, useLayoutEffect, useMemo, useRef, useState, type ComponentProps, type ReactNode } from "./react";
|
|
2
|
+
import { ReactList, useListPaginationEvents, type ListPagination, type ListProps } from "./list";
|
|
4
3
|
import type { Row } from "./image";
|
|
5
4
|
import { pressHandler } from "./press";
|
|
6
5
|
import { rememberListPatch, type NativeListItem } from "./list-patch";
|
|
7
6
|
|
|
7
|
+
export { fixedListRow } from "./fixed-list-row";
|
|
8
|
+
|
|
8
9
|
type Captured = { value: unknown; prototype?: object | null; fields?: [PropertyKey, boolean, Captured][] };
|
|
9
10
|
type Scope = { identity: string; values: readonly Captured[] };
|
|
10
11
|
type Capture = { identity: string; values: () => readonly unknown[]; usesIndex?: boolean };
|
|
@@ -73,7 +74,7 @@ export function listText(parts: unknown[]): string {
|
|
|
73
74
|
export function listRowFields(props: ComponentProps<typeof Row>) {
|
|
74
75
|
return { image: props.image, title: props.title, titleMaxLines: props.titleMaxLines,
|
|
75
76
|
titleIcon: props.titleIcon, subtitle: props.subtitle, subtitleIcon: props.subtitleIcon,
|
|
76
|
-
onLongPress: props.onLongPress,
|
|
77
|
+
selected: props.selected, onLongPress: props.onLongPress,
|
|
77
78
|
onPress: pressHandler(props) ?? (props.onLongPress ? () => {} : undefined) };
|
|
78
79
|
}
|
|
79
80
|
|
|
@@ -286,11 +287,9 @@ export function nativeListPlan<T>(render: ListProps<T>["renderItem"]): Plan<T> |
|
|
|
286
287
|
return plans.get(render) as Plan<T> | undefined;
|
|
287
288
|
}
|
|
288
289
|
|
|
289
|
-
export function CompiledList<T>({ plan, ...props }: ListProps<T> & { plan: Plan<T
|
|
290
|
-
const { items, keyExtractor, gap = 47, followEnd, initialPosition, measurementKey
|
|
290
|
+
export function CompiledList<T>({ plan, pagination, ...props }: ListProps<T> & { plan: Plan<T>; pagination: ListPagination }) {
|
|
291
|
+
const { items, keyExtractor, gap = 47, followEnd, initialPosition, measurementKey } = props;
|
|
291
292
|
if (!Number.isFinite(gap) || gap < 0) throw new Error("List gap must be finite and non-negative");
|
|
292
|
-
const load = useAction(() => onLoadMore?.());
|
|
293
|
-
const older = useAction(() => onLoadOlder?.());
|
|
294
293
|
const committed = useRef<Records<T> | null>(null);
|
|
295
294
|
const committedRequest = useRef<Request<T> | null>(null);
|
|
296
295
|
const committedPlan = useRef<Plan<T> | null>(null);
|
|
@@ -388,14 +387,13 @@ export function CompiledList<T>({ plan, ...props }: ListProps<T> & { plan: Plan<
|
|
|
388
387
|
if (timer !== undefined) clearTimeout(timer);
|
|
389
388
|
};
|
|
390
389
|
}, [request]);
|
|
390
|
+
const paginationProps = useListPaginationEvents(pagination, preparing || !records,
|
|
391
|
+
records?.data.length ?? 0, records?.data[0]?.key, records?.data.at(-1)?.key);
|
|
391
392
|
if (outcome?.status === "error") throw outcome.error;
|
|
392
|
-
if (!records) return createElement(ReactList<T>, props);
|
|
393
|
+
if (!records) return createElement(ReactList<T>, { ...props, pagination });
|
|
393
394
|
const nativeProps = {
|
|
394
395
|
items: records.data, keyField: "key", template: records.template, gap, followEnd, initialEnd: initialPosition === "end",
|
|
395
|
-
|
|
396
|
-
hasOlder: !preparing && hasOlder && older.status !== "pending" && older.status !== "error",
|
|
397
|
-
onEndReached: !preparing && onLoadMore ? load.run : undefined,
|
|
398
|
-
onStartReached: !preparing && onLoadOlder ? older.run : undefined,
|
|
396
|
+
...paginationProps,
|
|
399
397
|
onRowEvent(key: string, target: number, name: string, ...args: unknown[]) {
|
|
400
398
|
const latest = events.current;
|
|
401
399
|
const path = latest?.targets.get(target)?.[name];
|
|
@@ -432,11 +430,7 @@ export function CompiledList<T>({ plan, ...props }: ListProps<T> & { plan: Plan<
|
|
|
432
430
|
if (typeof callback === "function") callback(...args);
|
|
433
431
|
},
|
|
434
432
|
};
|
|
435
|
-
return createElement(
|
|
436
|
-
older.status === "error" && createElement(ListLoadError, { message: older.error.message, onRetry: older.run }),
|
|
437
|
-
createElement("NativeList", nativeProps),
|
|
438
|
-
load.status === "error" && createElement(ListLoadError, { message: load.error.message, onRetry: load.run }),
|
|
439
|
-
);
|
|
433
|
+
return createElement("NativeList", nativeProps);
|
|
440
434
|
}
|
|
441
435
|
|
|
442
436
|
/** @internal Fixed native descriptions for Ink's own list components. */
|
package/src/native.ts
CHANGED
|
@@ -34,6 +34,14 @@ Object.defineProperty(globalThis, "__inkReceive", {
|
|
|
34
34
|
},
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
+
Object.defineProperty(globalThis, "__inkResolveNativeResult", {
|
|
38
|
+
value: (id: number, value?: string, kind?: string, message?: string, retryable?: boolean) => {
|
|
39
|
+
listeners.get("result")?.(typeof value === "string"
|
|
40
|
+
? { id, type: "result", value }
|
|
41
|
+
: { id, kind, message, retryable, type: "result" });
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
37
45
|
onNativeMessage("result", message => {
|
|
38
46
|
if (typeof message.id !== "number") throw new Error("Invalid native result ID");
|
|
39
47
|
const request = pending.get(message.id);
|
package/src/playing.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { createElement, useRef } from "./react";
|
|
|
2
2
|
import { Screen, ErrorState, type IconAsset } from "./index";
|
|
3
3
|
import { useAction } from "./action";
|
|
4
4
|
import { playbackController } from "./playback-binding";
|
|
5
|
-
import { forward10Filled, forward30Filled, forward5Filled, pauseFilled, playArrowFilled, replay10Filled, replay30Filled, replay5Filled, skipNextFilled, skipPreviousFilled } from "./icons";
|
|
5
|
+
import { forward10Filled, forward15Filled, forward30Filled, forward5Filled, pauseFilled, playArrowFilled, replay10Filled, replay15Filled, replay30Filled, replay5Filled, skipNextFilled, skipPreviousFilled } from "./icons";
|
|
6
6
|
|
|
7
7
|
type PlaybackAction = () => void | Promise<void>;
|
|
8
8
|
export interface Playback {
|
|
@@ -17,7 +17,7 @@ export interface Playback {
|
|
|
17
17
|
|
|
18
18
|
export type TransportControl = { onLongPress?: PlaybackAction; disabled?: boolean } & (
|
|
19
19
|
| { kind: "track"; seconds?: never; icon?: never; onPress?: never }
|
|
20
|
-
| { kind: "skip"; seconds: 5 | 10 | 30; icon?: never; onPress?: never }
|
|
20
|
+
| { kind: "skip"; seconds: 5 | 10 | 15 | 30; icon?: never; onPress?: never }
|
|
21
21
|
| { kind: "custom"; icon: IconAsset; onPress: PlaybackAction; seconds?: never }
|
|
22
22
|
);
|
|
23
23
|
export type PlayingScreenProps = {
|
|
@@ -32,8 +32,8 @@ export type PlayingScreenProps = {
|
|
|
32
32
|
actions?: readonly (({ icon: IconAsset; label?: never } | { label: string; icon?: never }) & { selected?: boolean; disabled?: boolean; onPress: PlaybackAction })[];
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
-
const replayIcons = { 5: replay5Filled, 10: replay10Filled, 30: replay30Filled };
|
|
36
|
-
const forwardIcons = { 5: forward5Filled, 10: forward10Filled, 30: forward30Filled };
|
|
35
|
+
const replayIcons = { 5: replay5Filled, 10: replay10Filled, 15: replay15Filled, 30: replay30Filled };
|
|
36
|
+
const forwardIcons = { 5: forward5Filled, 10: forward10Filled, 15: forward15Filled, 30: forward30Filled };
|
|
37
37
|
|
|
38
38
|
export function PlayingScreen({ image, preloadImages = [], title, onTitlePress, artists, playback, previous = { kind: "track" }, next = { kind: "track" }, actions = [] }: PlayingScreenProps) {
|
|
39
39
|
const retry = useRef<PlaybackAction>(() => {});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from "preact/compat";
|
|
2
|
+
import { cloneElement, isValidElement } from "preact";
|
|
3
|
+
import { Activity, useEffect, useLayoutEffect, useSyncExternalStore, useImperativeHandle } from "./visibility";
|
|
4
|
+
import { useRef, useCallback, useLayoutEffect as commitLayoutEffect } from "preact/hooks";
|
|
5
|
+
const compatibleReact = /* @__PURE__ */ Object.assign({}, React, { Activity, cloneElement, isValidElement, useEffect, useLayoutEffect, useSyncExternalStore, useImperativeHandle, useEffectEvent });
|
|
6
|
+
export default compatibleReact;
|
|
7
|
+
export {
|
|
8
|
+
Children, Component, Fragment, PureComponent, StrictMode, Suspense, SuspenseList,
|
|
9
|
+
createContext, createElement, createFactory, createPortal, createRef,
|
|
10
|
+
findDOMNode, flushSync, forwardRef, hydrate, lazy, memo, render,
|
|
11
|
+
startTransition, unmountComponentAtNode, unstable_batchedUpdates,
|
|
12
|
+
useCallback, useContext, useDebugValue, useDeferredValue,
|
|
13
|
+
useId, useInsertionEffect, useMemo, useReducer,
|
|
14
|
+
useRef, useState, useTransition, version,
|
|
15
|
+
} from "preact/compat";
|
|
16
|
+
export { cloneElement, isValidElement } from "preact";
|
|
17
|
+
export { Activity, useEffect, useLayoutEffect, useSyncExternalStore, useImperativeHandle } from "./visibility";
|
|
18
|
+
|
|
19
|
+
/** Effect events retain their identity and read the latest committed callback. */
|
|
20
|
+
export function useEffectEvent<T extends (...args: never[]) => unknown>(callback: T): T {
|
|
21
|
+
const current = useRef(callback);
|
|
22
|
+
commitLayoutEffect(() => { current.current = callback; });
|
|
23
|
+
return useCallback(((...args: Parameters<T>) => current.current(...args)) as T, []);
|
|
24
|
+
}
|
package/src/react.ts
CHANGED
|
@@ -1,6 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { createElement as element, createContext as context, isValidElement as valid, Fragment as fragment, Component, type ComponentChildren } from "preact";
|
|
2
|
+
import { useCallback, useContext as contextValue, useMemo, useRef, useState } from "preact/hooks";
|
|
3
|
+
import type * as React from "react";
|
|
4
|
+
|
|
5
|
+
// Keep the public React declarations while executing Preact core in Ink.
|
|
6
|
+
export const createElement = element as unknown as typeof React.createElement;
|
|
7
|
+
export const createContext = context as unknown as typeof React.createContext;
|
|
8
|
+
export const useContext = contextValue as unknown as typeof React.useContext;
|
|
9
|
+
export const isValidElement = valid as unknown as typeof React.isValidElement;
|
|
10
|
+
export const Fragment = fragment as unknown as typeof React.Fragment;
|
|
11
|
+
export { Children } from "preact/compat";
|
|
12
|
+
export { useCallback, useMemo, useRef, useState };
|
|
13
|
+
export { Activity, useEffect, useLayoutEffect, useSyncExternalStore } from "./visibility";
|
|
14
|
+
export type { ComponentProps, ComponentType, ReactElement, ReactNode } from "react";
|
|
15
|
+
|
|
16
|
+
// A single core component avoids compat memo's extra function-component layer.
|
|
17
|
+
export function memo<P extends object>(render: (props: P) => React.ReactNode) {
|
|
18
|
+
return class extends Component<P> {
|
|
19
|
+
shouldComponentUpdate(next: P) {
|
|
20
|
+
const previous = this.props;
|
|
21
|
+
const keys = Object.keys(next) as (keyof P)[];
|
|
22
|
+
return keys.length !== Object.keys(previous).length || keys.some(key => !Object.is(previous[key], next[key]));
|
|
23
|
+
}
|
|
24
|
+
render() { return render(this.props) as ComponentChildren; }
|
|
25
|
+
} as unknown as (props: P) => React.ReactNode;
|
|
26
|
+
}
|
package/src/refresh.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import "@prefresh/core";
|
|
2
|
+
import { flush } from "@prefresh/utils";
|
|
3
|
+
|
|
4
|
+
declare const self: { __PREFRESH__: {
|
|
5
|
+
register(type: unknown, id: string): void;
|
|
6
|
+
sign(type: unknown, key: string, forceReset: boolean, getCustomHooks: (() => unknown[]) | undefined, status: string): string;
|
|
7
|
+
} };
|
|
8
|
+
|
|
9
|
+
export const register = (type: unknown, id: string) => self.__PREFRESH__.register(type, id);
|
|
10
|
+
export const performReactRefresh = flush;
|
|
11
|
+
|
|
12
|
+
// Oxc emits React Refresh's signature protocol; translate it to Prefresh.
|
|
13
|
+
export function createSignatureFunctionForTransform() {
|
|
14
|
+
let savedType: unknown;
|
|
15
|
+
let savedKey = "";
|
|
16
|
+
let savedReset = false;
|
|
17
|
+
let savedHooks: (() => unknown[]) | undefined;
|
|
18
|
+
let status = "begin";
|
|
19
|
+
return (type?: unknown, key?: string, forceReset = false, getCustomHooks?: () => unknown[]) => {
|
|
20
|
+
if (type) {
|
|
21
|
+
savedType = type;
|
|
22
|
+
savedKey = key ?? "";
|
|
23
|
+
savedReset = forceReset;
|
|
24
|
+
savedHooks = getCustomHooks;
|
|
25
|
+
}
|
|
26
|
+
status = self.__PREFRESH__.sign(savedType, savedKey, savedReset, savedHooks, status);
|
|
27
|
+
return type;
|
|
28
|
+
};
|
|
29
|
+
}
|