@lingxia/page-runtime 0.5.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.
Files changed (54) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +1 -0
  3. package/dist/react/LxInput.d.ts +47 -0
  4. package/dist/react/LxInput.js +128 -0
  5. package/dist/react/LxNavigator.d.ts +23 -0
  6. package/dist/react/LxNavigator.js +61 -0
  7. package/dist/react/LxPicker.d.ts +27 -0
  8. package/dist/react/LxPicker.js +113 -0
  9. package/dist/react/LxTextarea.d.ts +47 -0
  10. package/dist/react/LxTextarea.js +118 -0
  11. package/dist/react/LxVideo.d.ts +5 -0
  12. package/dist/react/LxVideo.js +113 -0
  13. package/dist/react/hook.d.ts +39 -0
  14. package/dist/react/hook.js +212 -0
  15. package/dist/react/index.d.ts +6 -0
  16. package/dist/react/index.js +6 -0
  17. package/dist/react/text_component_shared.d.ts +5 -0
  18. package/dist/react/text_component_shared.js +33 -0
  19. package/dist/shared/runtime.d.ts +21 -0
  20. package/dist/shared/runtime.js +142 -0
  21. package/dist/vue/LxInput.vue +92 -0
  22. package/dist/vue/LxNavigator.vue +79 -0
  23. package/dist/vue/LxPicker.vue +144 -0
  24. package/dist/vue/LxTextarea.vue +90 -0
  25. package/dist/vue/LxVideo.vue +111 -0
  26. package/dist/vue/hook.d.ts +42 -0
  27. package/dist/vue/hook.js +194 -0
  28. package/dist/vue/index.d.ts +7 -0
  29. package/dist/vue/index.js +6 -0
  30. package/dist/vue/text_component_shared.d.ts +3 -0
  31. package/dist/vue/text_component_shared.js +26 -0
  32. package/dist/vue/types.d.ts +101 -0
  33. package/dist/vue/types.js +1 -0
  34. package/package.json +67 -0
  35. package/src/index.ts +21 -0
  36. package/src/react/LxInput.tsx +224 -0
  37. package/src/react/LxNavigator.tsx +132 -0
  38. package/src/react/LxPicker.tsx +205 -0
  39. package/src/react/LxTextarea.tsx +216 -0
  40. package/src/react/LxVideo.tsx +166 -0
  41. package/src/react/hook.ts +317 -0
  42. package/src/react/index.ts +14 -0
  43. package/src/react/text_component_shared.ts +43 -0
  44. package/src/shared/runtime.ts +186 -0
  45. package/src/vue/LxInput.vue +92 -0
  46. package/src/vue/LxNavigator.vue +79 -0
  47. package/src/vue/LxPicker.vue +144 -0
  48. package/src/vue/LxTextarea.vue +90 -0
  49. package/src/vue/LxVideo.vue +111 -0
  50. package/src/vue/hook.ts +313 -0
  51. package/src/vue/index.ts +22 -0
  52. package/src/vue/shims-vue.d.ts +5 -0
  53. package/src/vue/text_component_shared.ts +33 -0
  54. package/src/vue/types.ts +107 -0
@@ -0,0 +1,313 @@
1
+ import {
2
+ onUnmounted,
3
+ reactive,
4
+ ref,
5
+ unref,
6
+ watch,
7
+ type Ref,
8
+ } from "vue";
9
+ import type {
10
+ LxChannel,
11
+ LxBridgeError,
12
+ LxStream,
13
+ } from "@lingxia/bridge";
14
+ import {
15
+ ensurePageBridgeSubscription,
16
+ getMethodKey,
17
+ getPageActions,
18
+ invokeMethod,
19
+ resolveParams,
20
+ stableParamKey,
21
+ subscribePageData,
22
+ toBridgeError,
23
+ type ActionMap,
24
+ type ChannelIn,
25
+ type ChannelOut,
26
+ type MethodParams,
27
+ type ParamsSource,
28
+ type Snapshot,
29
+ type StreamData,
30
+ type StreamResult,
31
+ } from "../shared/runtime.js";
32
+
33
+ type MethodSource<T> = T | Ref<T>;
34
+
35
+ function resolveMethod<TMethod>(source: MethodSource<TMethod>): TMethod {
36
+ return unref(source) as TMethod;
37
+ }
38
+
39
+ const reactiveSnapshot = reactive<Snapshot>({});
40
+ let snapshotSubscribed = false;
41
+
42
+ function ensureReactiveSnapshot(): void {
43
+ if (snapshotSubscribed) return;
44
+ snapshotSubscribed = true;
45
+ subscribePageData((next) => {
46
+ const normalized: Snapshot =
47
+ next && typeof next === "object" ? (next as Snapshot) : {};
48
+
49
+ for (const key of Object.keys(reactiveSnapshot)) {
50
+ if (!Object.prototype.hasOwnProperty.call(normalized, key)) {
51
+ delete reactiveSnapshot[key];
52
+ }
53
+ }
54
+ Object.assign(reactiveSnapshot, normalized);
55
+ });
56
+ }
57
+
58
+ export function useLxPage<
59
+ TData = Snapshot,
60
+ TActions extends ActionMap = ActionMap,
61
+ >(): { data: TData; actions: TActions } {
62
+ ensurePageBridgeSubscription();
63
+ ensureReactiveSnapshot();
64
+ return { data: reactiveSnapshot as TData, actions: getPageActions<TActions>() };
65
+ }
66
+
67
+ export interface LxStreamOptions<TData, TReduced> {
68
+ params?: unknown | (() => unknown);
69
+ manual?: boolean;
70
+ reduce?: (accumulated: TReduced, chunk: TData) => TReduced;
71
+ initial?: TReduced;
72
+ }
73
+
74
+ export interface LxStreamState<TData, TResult = unknown> {
75
+ data: Ref<TData | undefined>;
76
+ result: Ref<TResult | undefined>;
77
+ error: Ref<LxBridgeError | undefined>;
78
+ streaming: Ref<boolean>;
79
+ cancel: () => void;
80
+ start: () => void;
81
+ }
82
+
83
+ export function useLxStream<
84
+ TMethod extends (...args: any[]) => LxStream<any, any>,
85
+ TReduced = StreamData<TMethod>,
86
+ >(
87
+ method: MethodSource<TMethod>,
88
+ options?: LxStreamOptions<StreamData<TMethod>, TReduced> & {
89
+ params?: ParamsSource<MethodParams<TMethod>>;
90
+ },
91
+ ): LxStreamState<
92
+ TReduced extends StreamData<TMethod> ? StreamData<TMethod> : TReduced,
93
+ StreamResult<TMethod>
94
+ > {
95
+ type TData = StreamData<TMethod>;
96
+ type TResult = StreamResult<TMethod>;
97
+ type TOut = TReduced extends TData ? TData : TReduced;
98
+
99
+ const data = ref<TOut | undefined>(
100
+ (options?.reduce ? options.initial : undefined) as TOut | undefined,
101
+ ) as Ref<TOut | undefined>;
102
+ const result = ref<TResult | undefined>(undefined) as Ref<TResult | undefined>;
103
+ const error = ref<LxBridgeError | undefined>(undefined);
104
+ const streaming = ref(false);
105
+
106
+ let handle: LxStream<TData, TResult> | null = null;
107
+ let acc: TReduced | undefined = options?.initial;
108
+ let runId = 0;
109
+
110
+ function cancel(): void {
111
+ runId += 1;
112
+ handle?.cancel();
113
+ handle = null;
114
+ streaming.value = false;
115
+ }
116
+
117
+ function start(): void {
118
+ handle?.cancel();
119
+ const currentRunId = runId + 1;
120
+ runId = currentRunId;
121
+
122
+ acc = options?.initial;
123
+ data.value = (options?.reduce ? options.initial : undefined) as TOut | undefined;
124
+ result.value = undefined;
125
+ error.value = undefined;
126
+ streaming.value = true;
127
+
128
+ let nextHandle: LxStream<TData, TResult>;
129
+ try {
130
+ const params = resolveParams(options?.params);
131
+ nextHandle = invokeMethod(resolveMethod(method), params) as LxStream<TData, TResult>;
132
+ } catch (err: unknown) {
133
+ if (runId !== currentRunId) return;
134
+ handle = null;
135
+ error.value = toBridgeError(err);
136
+ streaming.value = false;
137
+ return;
138
+ }
139
+
140
+ handle = nextHandle;
141
+
142
+ nextHandle.on("data", (chunk: TData) => {
143
+ if (runId !== currentRunId) return;
144
+ if (options?.reduce) {
145
+ acc = options.reduce(acc as TReduced, chunk);
146
+ data.value = acc as TOut;
147
+ } else {
148
+ data.value = chunk as unknown as TOut;
149
+ }
150
+ });
151
+
152
+ nextHandle.on("end", (res: TResult) => {
153
+ if (runId !== currentRunId) return;
154
+ handle = null;
155
+ result.value = res;
156
+ streaming.value = false;
157
+ });
158
+
159
+ nextHandle.on("error", (err: LxBridgeError) => {
160
+ if (runId !== currentRunId) return;
161
+ handle = null;
162
+ error.value = err;
163
+ streaming.value = false;
164
+ });
165
+ }
166
+
167
+ watch(
168
+ () => {
169
+ if (options?.manual) return null;
170
+ const resolvedMethod = resolveMethod(method);
171
+ return [
172
+ getMethodKey(resolvedMethod) ?? resolvedMethod,
173
+ stableParamKey(resolveParams(options?.params)),
174
+ ];
175
+ },
176
+ () => {
177
+ if (!options?.manual) {
178
+ start();
179
+ }
180
+ },
181
+ { immediate: !options?.manual },
182
+ );
183
+
184
+ onUnmounted(() => {
185
+ runId += 1;
186
+ handle?.cancel();
187
+ handle = null;
188
+ });
189
+
190
+ return { data, result, error, streaming, cancel, start };
191
+ }
192
+
193
+ export interface LxChannelOptions {
194
+ params?: unknown | (() => unknown);
195
+ manual?: boolean;
196
+ }
197
+
198
+ export interface LxChannelState<TData, TOut = TData> {
199
+ last: Ref<TData | undefined>;
200
+ error: Ref<LxBridgeError | undefined>;
201
+ connecting: Ref<boolean>;
202
+ connected: Ref<boolean>;
203
+ send: (payload: TOut) => void;
204
+ close: (code?: string, reason?: string) => void;
205
+ reopen: () => void;
206
+ }
207
+
208
+ export function useLxChannel<
209
+ TMethod extends (...args: any[]) => Promise<LxChannel<any, any>>,
210
+ >(
211
+ method: MethodSource<TMethod>,
212
+ options?: LxChannelOptions & {
213
+ params?: ParamsSource<MethodParams<TMethod>>;
214
+ },
215
+ ): LxChannelState<ChannelIn<TMethod>, ChannelOut<TMethod>> {
216
+ type TIn = ChannelIn<TMethod>;
217
+ type TOut = ChannelOut<TMethod>;
218
+
219
+ const last = ref<TIn | undefined>(undefined) as Ref<TIn | undefined>;
220
+ const error = ref<LxBridgeError | undefined>(undefined);
221
+ const connecting = ref(false);
222
+ const connected = ref(false);
223
+
224
+ let ch: LxChannel<TIn, TOut> | null = null;
225
+ let runId = 0;
226
+
227
+ function send(payload: TOut): void {
228
+ ch?.send(payload);
229
+ }
230
+
231
+ function close(code?: string, reason?: string): void {
232
+ runId += 1;
233
+ ch?.close(code, reason);
234
+ ch = null;
235
+ connecting.value = false;
236
+ connected.value = false;
237
+ }
238
+
239
+ function reopen(): void {
240
+ ch?.close();
241
+ ch = null;
242
+
243
+ const thisRunId = ++runId;
244
+ last.value = undefined;
245
+ error.value = undefined;
246
+ connecting.value = true;
247
+ connected.value = false;
248
+
249
+ Promise.resolve(
250
+ invokeMethod(resolveMethod(method), resolveParams(options?.params)) as Promise<LxChannel<TIn, TOut>>,
251
+ )
252
+ .then((nextChannel) => {
253
+ if (runId !== thisRunId) {
254
+ nextChannel.close();
255
+ return;
256
+ }
257
+ ch = nextChannel;
258
+ connecting.value = false;
259
+ connected.value = true;
260
+
261
+ nextChannel.on("data", (payload) => {
262
+ if (runId !== thisRunId) return;
263
+ last.value = payload as TIn;
264
+ });
265
+
266
+ nextChannel.on("close", () => {
267
+ if (runId !== thisRunId) return;
268
+ ch = null;
269
+ connecting.value = false;
270
+ connected.value = false;
271
+ });
272
+
273
+ nextChannel.on("error", (err: LxBridgeError) => {
274
+ if (runId !== thisRunId) return;
275
+ ch = null;
276
+ error.value = err;
277
+ connecting.value = false;
278
+ connected.value = false;
279
+ });
280
+ })
281
+ .catch((err: unknown) => {
282
+ if (runId !== thisRunId) return;
283
+ error.value = toBridgeError(err);
284
+ connecting.value = false;
285
+ connected.value = false;
286
+ });
287
+ }
288
+
289
+ watch(
290
+ () => {
291
+ if (options?.manual) return null;
292
+ const resolvedMethod = resolveMethod(method);
293
+ return [
294
+ getMethodKey(resolvedMethod) ?? resolvedMethod,
295
+ stableParamKey(resolveParams(options?.params)),
296
+ ];
297
+ },
298
+ () => {
299
+ if (!options?.manual) {
300
+ reopen();
301
+ }
302
+ },
303
+ { immediate: !options?.manual },
304
+ );
305
+
306
+ onUnmounted(() => {
307
+ runId += 1;
308
+ ch?.close();
309
+ ch = null;
310
+ });
311
+
312
+ return { last, error, connecting, connected, send, close, reopen };
313
+ }
@@ -0,0 +1,22 @@
1
+ export {
2
+ useLxPage,
3
+ useLxStream,
4
+ useLxChannel,
5
+ type LxStreamOptions,
6
+ type LxStreamState,
7
+ type LxChannelOptions,
8
+ type LxChannelState,
9
+ } from "./hook.js";
10
+ export { default as LxVideo } from "./LxVideo.vue";
11
+ export { default as LxPicker } from "./LxPicker.vue";
12
+ export { default as LxNavigator } from "./LxNavigator.vue";
13
+ export { default as LxInput } from "./LxInput.vue";
14
+ export { default as LxTextarea } from "./LxTextarea.vue";
15
+ export type {
16
+ LxVideoProps,
17
+ LxPickerProps,
18
+ LxNavigatorProps,
19
+ LxNavigatorEvent,
20
+ LxInputProps,
21
+ LxTextareaProps,
22
+ } from "./types.js";
@@ -0,0 +1,5 @@
1
+ declare module '*.vue' {
2
+ import type { DefineComponent } from 'vue';
3
+ const component: DefineComponent<{}, {}, any>;
4
+ export default component;
5
+ }
@@ -0,0 +1,33 @@
1
+ export function getCustomEventDetail<T>(event: Event): T {
2
+ return ((event as CustomEvent).detail ?? {}) as T;
3
+ }
4
+
5
+ export function bindElementEvents(
6
+ currentBoundElement: HTMLElement | null,
7
+ nextElement: HTMLElement | null,
8
+ listeners: Record<string, EventListenerObject>
9
+ ): HTMLElement | null {
10
+ if (currentBoundElement && currentBoundElement !== nextElement) {
11
+ for (const [event, listener] of Object.entries(listeners)) {
12
+ currentBoundElement.removeEventListener(event, listener);
13
+ }
14
+ currentBoundElement = null;
15
+ }
16
+ if (nextElement && currentBoundElement !== nextElement) {
17
+ for (const [event, listener] of Object.entries(listeners)) {
18
+ nextElement.addEventListener(event, listener);
19
+ }
20
+ currentBoundElement = nextElement;
21
+ }
22
+ return currentBoundElement;
23
+ }
24
+
25
+ export function unbindElementEvents(
26
+ boundElement: HTMLElement | null,
27
+ listeners: Record<string, EventListenerObject>
28
+ ): void {
29
+ if (!boundElement) return;
30
+ for (const [event, listener] of Object.entries(listeners)) {
31
+ boundElement.removeEventListener(event, listener);
32
+ }
33
+ }
@@ -0,0 +1,107 @@
1
+ import type { CSSProperties } from 'vue';
2
+ import type { LxVideoAttributes } from '@lingxia/elements';
3
+ import type { LxNavigatorEvent, NavigatorOpenType, NavigatorTarget } from '@lingxia/elements';
4
+
5
+ export interface LxVideoProps extends Omit<LxVideoAttributes, 'ref' | 'className' | 'style'> {
6
+ class?: string;
7
+ style?: CSSProperties;
8
+ }
9
+
10
+ export interface LxPickerProps {
11
+ columns?: string[][] | [string[], Record<string, string[]>];
12
+ mode?: 'date' | 'time';
13
+ start?: string;
14
+ end?: string;
15
+ fields?: 'year' | 'month' | 'day' | 'range';
16
+ modelValue?: string | string[];
17
+ placeholder?: string;
18
+ class?: string;
19
+ style?: CSSProperties;
20
+ disabled?: boolean;
21
+ cancelText?: string;
22
+ cancelTextColor?: string;
23
+ cancelButtonColor?: string;
24
+ confirmText?: string;
25
+ confirmTextColor?: string;
26
+ confirmButtonColor?: string;
27
+ onChange?: (event: Event) => void;
28
+ onNativeScroll?: (event: Event) => void;
29
+ pageBindings?: Record<string, string>;
30
+ }
31
+
32
+ export interface LxNavigatorProps {
33
+ url?: string;
34
+ openType?: NavigatorOpenType;
35
+ target?: NavigatorTarget;
36
+ delta?: number;
37
+ appId?: string;
38
+ path?: string;
39
+ phoneNumber?: string;
40
+ hoverClass?: string;
41
+ hoverStopPropagation?: boolean;
42
+ hoverStartTime?: number;
43
+ hoverStayTime?: number;
44
+ class?: string;
45
+ style?: CSSProperties;
46
+ }
47
+
48
+ export interface LxInputProps {
49
+ id?: string;
50
+ modelValue?: string;
51
+ value?: string;
52
+ defaultValue?: string;
53
+ type?: 'text' | 'number' | 'password' | 'digit';
54
+ password?: boolean;
55
+ placeholder?: string;
56
+ placeholderStyle?: string;
57
+ placeholderClass?: string;
58
+ maxlength?: number;
59
+ cursorSpacing?: number;
60
+ autoFocus?: boolean;
61
+ disabled?: boolean;
62
+ focus?: boolean;
63
+ confirmType?: 'send' | 'search' | 'next' | 'go' | 'done';
64
+ alwaysEmbed?: boolean;
65
+ confirmHold?: boolean;
66
+ cursor?: number;
67
+ cursorColor?: string;
68
+ selectionStart?: number;
69
+ selectionEnd?: number;
70
+ adjustPosition?: boolean;
71
+ holdKeyboard?: boolean;
72
+ class?: string;
73
+ style?: CSSProperties;
74
+ pageBindings?: Record<string, string>;
75
+ }
76
+
77
+ export interface LxTextareaProps {
78
+ id?: string;
79
+ modelValue?: string;
80
+ value?: string;
81
+ defaultValue?: string;
82
+ placeholder?: string;
83
+ placeholderStyle?: string;
84
+ placeholderClass?: string;
85
+ maxlength?: number;
86
+ disabled?: boolean;
87
+ autoFocus?: boolean;
88
+ focus?: boolean;
89
+ autoHeight?: boolean;
90
+ cursorSpacing?: number;
91
+ showConfirmBar?: boolean;
92
+ adjustPosition?: boolean;
93
+ holdKeyboard?: boolean;
94
+ disableDefaultPadding?: boolean;
95
+ confirmType?: 'send' | 'search' | 'next' | 'go' | 'done' | 'return';
96
+ confirmHold?: boolean;
97
+ fixed?: boolean;
98
+ adjustKeyboardTo?: 'cursor' | 'bottom';
99
+ cursor?: number;
100
+ selectionStart?: number;
101
+ selectionEnd?: number;
102
+ class?: string;
103
+ style?: CSSProperties;
104
+ pageBindings?: Record<string, string>;
105
+ }
106
+
107
+ export type { LxNavigatorEvent };