@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,317 @@
1
+ import * as React from "react";
2
+ import type {
3
+ LxChannel,
4
+ LxBridgeError,
5
+ LxStream,
6
+ } from "@lingxia/bridge";
7
+ import {
8
+ ensurePageBridgeSubscription,
9
+ getMethodKey,
10
+ getPageActions,
11
+ getPageSnapshot,
12
+ invokeMethod,
13
+ resolveParams,
14
+ stableParamKey,
15
+ subscribePageSnapshot,
16
+ toBridgeError,
17
+ type ActionMap,
18
+ type ChannelIn,
19
+ type ChannelOut,
20
+ type Listener,
21
+ type MethodParams,
22
+ type ParamsSource,
23
+ type Snapshot,
24
+ type StreamData,
25
+ type StreamResult,
26
+ } from "../shared/runtime.js";
27
+
28
+ export function useLxPage<
29
+ TData = Snapshot,
30
+ TActions extends ActionMap = ActionMap,
31
+ >(): { data: TData; actions: TActions } {
32
+ ensurePageBridgeSubscription();
33
+ const [, setVersion] = React.useState(0);
34
+
35
+ React.useEffect(() => {
36
+ ensurePageBridgeSubscription();
37
+ const listener: Listener = () => setVersion((v) => v + 1);
38
+ const unsubscribe = subscribePageSnapshot(listener);
39
+ setVersion((v) => v + 1);
40
+ return unsubscribe;
41
+ }, []);
42
+
43
+ const actions = React.useMemo(() => getPageActions<TActions>(), []);
44
+ return { data: getPageSnapshot<TData>(), actions };
45
+ }
46
+
47
+ export interface LxStreamOptions<TData, TReduced> {
48
+ params?: unknown | (() => unknown);
49
+ manual?: boolean;
50
+ reduce?: (accumulated: TReduced, chunk: TData) => TReduced;
51
+ initial?: TReduced;
52
+ }
53
+
54
+ export interface LxStreamState<TData, TResult = unknown> {
55
+ data: TData | undefined;
56
+ result: TResult | undefined;
57
+ error: LxBridgeError | undefined;
58
+ streaming: boolean;
59
+ cancel: () => void;
60
+ start: () => void;
61
+ }
62
+
63
+ export function useLxStream<
64
+ TMethod extends (...args: any[]) => LxStream<any, any>,
65
+ TReduced = StreamData<TMethod>,
66
+ >(
67
+ method: TMethod,
68
+ options?: LxStreamOptions<StreamData<TMethod>, TReduced> & {
69
+ params?: ParamsSource<MethodParams<TMethod>>;
70
+ },
71
+ ): LxStreamState<
72
+ TReduced extends StreamData<TMethod> ? StreamData<TMethod> : TReduced,
73
+ StreamResult<TMethod>
74
+ > {
75
+ type TData = StreamData<TMethod>;
76
+ type TResult = StreamResult<TMethod>;
77
+ type TOut = TReduced extends TData ? TData : TReduced;
78
+
79
+ const [state, setState] = React.useState<{
80
+ data: TOut | undefined;
81
+ result: TResult | undefined;
82
+ error: LxBridgeError | undefined;
83
+ streaming: boolean;
84
+ }>({
85
+ data: (options?.reduce ? options.initial : undefined) as TOut | undefined,
86
+ result: undefined,
87
+ error: undefined,
88
+ streaming: false,
89
+ });
90
+
91
+ const handleRef = React.useRef<LxStream<TData, TResult> | null>(null);
92
+ const accRef = React.useRef<TReduced | undefined>(options?.initial);
93
+ const optionsRef = React.useRef(options);
94
+ const methodRef = React.useRef(method);
95
+ const paramsRef = React.useRef(resolveParams(options?.params));
96
+ const runIdRef = React.useRef(0);
97
+ const resolvedParams = resolveParams(options?.params);
98
+ const paramsKey = options?.manual ? "" : stableParamKey(resolvedParams);
99
+ const methodDep = getMethodKey(method) ?? method;
100
+
101
+ optionsRef.current = options;
102
+ methodRef.current = method;
103
+ paramsRef.current = resolvedParams;
104
+
105
+ const cancel = React.useCallback(() => {
106
+ runIdRef.current += 1;
107
+ handleRef.current?.cancel();
108
+ handleRef.current = null;
109
+ setState((prev) => ({ ...prev, streaming: false }));
110
+ }, []);
111
+
112
+ const start = React.useCallback(() => {
113
+ handleRef.current?.cancel();
114
+ const runId = runIdRef.current + 1;
115
+ runIdRef.current = runId;
116
+
117
+ const opts = optionsRef.current;
118
+ accRef.current = opts?.initial;
119
+ setState({
120
+ data: (opts?.reduce ? opts.initial : undefined) as TOut | undefined,
121
+ result: undefined,
122
+ error: undefined,
123
+ streaming: true,
124
+ });
125
+
126
+ let handle: LxStream<TData, TResult>;
127
+ try {
128
+ handle = invokeMethod(methodRef.current, paramsRef.current) as LxStream<TData, TResult>;
129
+ } catch (err: unknown) {
130
+ if (runIdRef.current !== runId) return;
131
+ handleRef.current = null;
132
+ setState((prev) => ({
133
+ ...prev,
134
+ error: toBridgeError(err),
135
+ streaming: false,
136
+ }));
137
+ return;
138
+ }
139
+ handleRef.current = handle;
140
+
141
+ handle.on("data", (chunk: TData) => {
142
+ if (runIdRef.current !== runId) return;
143
+ const currentOpts = optionsRef.current;
144
+ if (currentOpts?.reduce) {
145
+ accRef.current = currentOpts.reduce(
146
+ accRef.current as TReduced,
147
+ chunk,
148
+ );
149
+ setState((prev) => ({
150
+ ...prev,
151
+ data: accRef.current as TOut,
152
+ }));
153
+ } else {
154
+ setState((prev) => ({
155
+ ...prev,
156
+ data: chunk as unknown as TOut,
157
+ }));
158
+ }
159
+ });
160
+
161
+ handle.on("end", (result: TResult) => {
162
+ if (runIdRef.current !== runId) return;
163
+ handleRef.current = null;
164
+ setState((prev) => ({
165
+ ...prev,
166
+ result,
167
+ streaming: false,
168
+ }));
169
+ });
170
+
171
+ handle.on("error", (err: LxBridgeError) => {
172
+ if (runIdRef.current !== runId) return;
173
+ handleRef.current = null;
174
+ setState((prev) => ({
175
+ ...prev,
176
+ error: err,
177
+ streaming: false,
178
+ }));
179
+ });
180
+ }, []);
181
+
182
+ React.useEffect(() => {
183
+ if (!options?.manual) {
184
+ start();
185
+ }
186
+ return () => {
187
+ runIdRef.current += 1;
188
+ handleRef.current?.cancel();
189
+ handleRef.current = null;
190
+ };
191
+ }, [methodDep, options?.manual, paramsKey, start]);
192
+
193
+ return { ...state, cancel, start };
194
+ }
195
+
196
+ export interface LxChannelOptions {
197
+ params?: unknown | (() => unknown);
198
+ manual?: boolean;
199
+ }
200
+
201
+ export interface LxChannelState<TData, TOut = TData> {
202
+ last: TData | undefined;
203
+ error: LxBridgeError | undefined;
204
+ connecting: boolean;
205
+ connected: boolean;
206
+ send: (payload: TOut) => void;
207
+ close: (code?: string, reason?: string) => void;
208
+ reopen: () => void;
209
+ }
210
+
211
+ export function useLxChannel<
212
+ TMethod extends (...args: any[]) => Promise<LxChannel<any, any>>,
213
+ >(
214
+ method: TMethod,
215
+ options?: LxChannelOptions & {
216
+ params?: ParamsSource<MethodParams<TMethod>>;
217
+ },
218
+ ): LxChannelState<ChannelIn<TMethod>, ChannelOut<TMethod>> {
219
+ type TIn = ChannelIn<TMethod>;
220
+ type TOut = ChannelOut<TMethod>;
221
+
222
+ const [state, setState] = React.useState<{
223
+ last: TIn | undefined;
224
+ error: LxBridgeError | undefined;
225
+ connecting: boolean;
226
+ connected: boolean;
227
+ }>({
228
+ last: undefined,
229
+ error: undefined,
230
+ connecting: false,
231
+ connected: false,
232
+ });
233
+
234
+ const chRef = React.useRef<LxChannel<TIn, TOut> | null>(null);
235
+ const methodRef = React.useRef(method);
236
+ const paramsRef = React.useRef(resolveParams(options?.params));
237
+ const runIdRef = React.useRef(0);
238
+ const resolvedParams = resolveParams(options?.params);
239
+ const paramsKey = options?.manual ? "" : stableParamKey(resolvedParams);
240
+ const methodDep = getMethodKey(method) ?? method;
241
+
242
+ methodRef.current = method;
243
+ paramsRef.current = resolvedParams;
244
+
245
+ const send = React.useCallback((payload: TOut) => {
246
+ chRef.current?.send(payload);
247
+ }, []);
248
+
249
+ const close = React.useCallback((code?: string, reason?: string) => {
250
+ runIdRef.current += 1;
251
+ chRef.current?.close(code, reason);
252
+ chRef.current = null;
253
+ setState((prev) => ({ ...prev, connecting: false, connected: false }));
254
+ }, []);
255
+
256
+ const reopen = React.useCallback(() => {
257
+ chRef.current?.close();
258
+ chRef.current = null;
259
+
260
+ const runId = ++runIdRef.current;
261
+ setState({
262
+ last: undefined,
263
+ error: undefined,
264
+ connecting: true,
265
+ connected: false,
266
+ });
267
+
268
+ Promise.resolve(
269
+ invokeMethod(methodRef.current, paramsRef.current) as Promise<LxChannel<TIn, TOut>>,
270
+ )
271
+ .then((ch) => {
272
+ if (runIdRef.current !== runId) {
273
+ ch.close();
274
+ return;
275
+ }
276
+ chRef.current = ch;
277
+ setState((prev) => ({ ...prev, connecting: false, connected: true }));
278
+
279
+ ch.on("data", (payload) => {
280
+ if (runIdRef.current !== runId) return;
281
+ setState((prev) => ({ ...prev, last: payload as TIn }));
282
+ });
283
+ ch.on("close", () => {
284
+ if (runIdRef.current !== runId) return;
285
+ chRef.current = null;
286
+ setState((prev) => ({ ...prev, connecting: false, connected: false }));
287
+ });
288
+ ch.on("error", (err: LxBridgeError) => {
289
+ if (runIdRef.current !== runId) return;
290
+ chRef.current = null;
291
+ setState((prev) => ({ ...prev, error: err, connecting: false, connected: false }));
292
+ });
293
+ })
294
+ .catch((err: unknown) => {
295
+ if (runIdRef.current !== runId) return;
296
+ setState({
297
+ last: undefined,
298
+ error: toBridgeError(err),
299
+ connecting: false,
300
+ connected: false,
301
+ });
302
+ });
303
+ }, []);
304
+
305
+ React.useEffect(() => {
306
+ if (!options?.manual) {
307
+ reopen();
308
+ }
309
+ return () => {
310
+ runIdRef.current += 1;
311
+ chRef.current?.close();
312
+ chRef.current = null;
313
+ };
314
+ }, [methodDep, options?.manual, paramsKey, reopen]);
315
+
316
+ return { ...state, send, close, reopen };
317
+ }
@@ -0,0 +1,14 @@
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 { LxVideo, type LxVideoProps } from "./LxVideo.js";
11
+ export { LxPicker, type LxPickerProps } from "./LxPicker.js";
12
+ export { LxNavigator, type LxNavigatorProps } from "./LxNavigator.js";
13
+ export { LxInput, type LxInputProps } from "./LxInput.js";
14
+ export { LxTextarea, type LxTextareaProps } from "./LxTextarea.js";
@@ -0,0 +1,43 @@
1
+ import type { ForwardedRef } from "react";
2
+
3
+ export function getCustomEventDetail<T>(event: Event): T {
4
+ return ((event as CustomEvent).detail ?? {}) as T;
5
+ }
6
+
7
+ export function assignForwardedRef<T>(ref: ForwardedRef<T>, value: T | null): void {
8
+ if (typeof ref === "function") {
9
+ ref(value);
10
+ return;
11
+ }
12
+ if (ref) {
13
+ ref.current = value;
14
+ }
15
+ }
16
+
17
+ export function bindElementEvents(
18
+ boundElement: HTMLElement | null,
19
+ nextElement: HTMLElement | null,
20
+ listeners: Record<string, EventListenerObject>
21
+ ): HTMLElement | null {
22
+ if (boundElement && boundElement !== nextElement) {
23
+ unbindElementEvents(boundElement, listeners);
24
+ boundElement = null;
25
+ }
26
+ if (nextElement && boundElement !== nextElement) {
27
+ for (const [event, listener] of Object.entries(listeners)) {
28
+ nextElement.addEventListener(event, listener);
29
+ }
30
+ boundElement = nextElement;
31
+ }
32
+ return boundElement;
33
+ }
34
+
35
+ export function unbindElementEvents(
36
+ boundElement: HTMLElement | null,
37
+ listeners: Record<string, EventListenerObject>
38
+ ): void {
39
+ if (!boundElement) return;
40
+ for (const [event, listener] of Object.entries(listeners)) {
41
+ boundElement.removeEventListener(event, listener);
42
+ }
43
+ }
@@ -0,0 +1,186 @@
1
+ import type {
2
+ DataSubscriber,
3
+ LxBridgeError,
4
+ LxChannel,
5
+ LxStream,
6
+ StateInfo,
7
+ } from "@lingxia/bridge";
8
+
9
+ export type ActionMap = Record<string, (...args: unknown[]) => unknown>;
10
+ export type Snapshot = Record<string, unknown>;
11
+ export type Listener = () => void;
12
+ export type ParamsSource<T> = T | (() => T);
13
+ export type MethodParams<TMethod> = TMethod extends () => any
14
+ ? undefined
15
+ : TMethod extends (params: infer P) => any
16
+ ? P
17
+ : never;
18
+ export type StreamData<TMethod> =
19
+ TMethod extends (...args: any[]) => LxStream<infer TData, any> ? TData : never;
20
+ export type StreamResult<TMethod> =
21
+ TMethod extends (...args: any[]) => LxStream<any, infer TResult>
22
+ ? TResult
23
+ : never;
24
+ export type ChannelIn<TMethod> =
25
+ TMethod extends (...args: any[]) => Promise<LxChannel<infer TIn, any>>
26
+ ? TIn
27
+ : never;
28
+ export type ChannelOut<TMethod> =
29
+ TMethod extends (...args: any[]) => Promise<LxChannel<any, infer TOut>>
30
+ ? TOut
31
+ : never;
32
+
33
+ let snapshot: Snapshot = {};
34
+ let stateInfo: StateInfo = { rev: -1, initial: true };
35
+ let subscribed = false;
36
+ let subscribeRetryTimer: ReturnType<typeof setTimeout> | null = null;
37
+ let initialSnapshotResolved = false;
38
+ let snapshotRequestInFlight = false;
39
+ const listeners = new Set<Listener>();
40
+
41
+ export function toBridgeError(err: unknown): LxBridgeError {
42
+ return err && typeof err === "object" && "code" in err
43
+ ? (err as LxBridgeError)
44
+ : {
45
+ code: "BRIDGE_INTERNAL_ERROR",
46
+ message: err instanceof Error ? err.message : String(err),
47
+ };
48
+ }
49
+
50
+ export function resolveParams<T>(source?: ParamsSource<T>): T | undefined {
51
+ if (typeof source === "function") {
52
+ return (source as () => T)();
53
+ }
54
+ return source;
55
+ }
56
+
57
+ export function stableParamKey(value: unknown): string {
58
+ if (value === undefined) return "undefined";
59
+ try {
60
+ return JSON.stringify(value) ?? "undefined";
61
+ } catch {
62
+ return String(value);
63
+ }
64
+ }
65
+
66
+ export function invokeMethod<TMethod extends (...args: any[]) => any>(
67
+ method: TMethod,
68
+ params: unknown,
69
+ ): ReturnType<TMethod> {
70
+ if (params === undefined) {
71
+ return method() as ReturnType<TMethod>;
72
+ }
73
+ return method(params) as ReturnType<TMethod>;
74
+ }
75
+
76
+ export function getMethodKey(method: unknown): string | undefined {
77
+ if (typeof method !== "function") return undefined;
78
+ const candidate = (method as { __funcName?: unknown }).__funcName;
79
+ return typeof candidate === "string" && candidate !== "" ? candidate : undefined;
80
+ }
81
+
82
+ function notifyListeners(): void {
83
+ listeners.forEach((listener) => {
84
+ try {
85
+ listener();
86
+ } catch {
87
+ // Ignore listener errors to avoid breaking state fanout.
88
+ }
89
+ });
90
+ }
91
+
92
+ function updateSnapshot(next: unknown, info: StateInfo): void {
93
+ snapshot = next && typeof next === "object" ? (next as Snapshot) : {};
94
+ stateInfo = info;
95
+ notifyListeners();
96
+ }
97
+
98
+ function scheduleSubscribeRetry(): void {
99
+ if (subscribeRetryTimer !== null || subscribed) return;
100
+ subscribeRetryTimer = setTimeout(() => {
101
+ subscribeRetryTimer = null;
102
+ ensurePageBridgeSubscription();
103
+ }, 10);
104
+ }
105
+
106
+ function requestInitialSnapshot(bridge: Window["LingXiaBridge"] | undefined): void {
107
+ if (initialSnapshotResolved || snapshotRequestInFlight) return;
108
+ if (!bridge?.call) return;
109
+ snapshotRequestInFlight = true;
110
+ bridge
111
+ .call("state.getSnapshot", { scope: "page" })
112
+ .then(() => {
113
+ initialSnapshotResolved = true;
114
+ })
115
+ .catch(() => {
116
+ scheduleSubscribeRetry();
117
+ })
118
+ .finally(() => {
119
+ snapshotRequestInFlight = false;
120
+ });
121
+ }
122
+
123
+ export function ensurePageBridgeSubscription(): void {
124
+ if (subscribed) return;
125
+ const bridge = window.LingXiaBridge;
126
+ const subscribeState = bridge?.state?.subscribe;
127
+ if (!subscribeState) {
128
+ scheduleSubscribeRetry();
129
+ return;
130
+ }
131
+ subscribeState((next, info) => {
132
+ updateSnapshot(next, info);
133
+ });
134
+ subscribed = true;
135
+ requestInitialSnapshot(bridge);
136
+ }
137
+
138
+ export function subscribePageSnapshot(listener: Listener): () => void {
139
+ ensurePageBridgeSubscription();
140
+ listeners.add(listener);
141
+ return () => {
142
+ listeners.delete(listener);
143
+ };
144
+ }
145
+
146
+ export function subscribePageData(
147
+ callback: DataSubscriber,
148
+ ): () => void {
149
+ if (typeof callback !== "function") return () => {};
150
+ ensurePageBridgeSubscription();
151
+
152
+ if (stateInfo.rev >= 0) {
153
+ callback(snapshot, { rev: stateInfo.rev, initial: true });
154
+ }
155
+
156
+ return subscribePageSnapshot(() => {
157
+ callback(snapshot, stateInfo);
158
+ });
159
+ }
160
+
161
+ export function getPageSnapshot<TData = Snapshot>(): TData {
162
+ ensurePageBridgeSubscription();
163
+ return snapshot as TData;
164
+ }
165
+
166
+ export function getPageActions<TActions extends ActionMap>(): TActions {
167
+ const actions: ActionMap = {};
168
+ const bridge = window.__pageBridge;
169
+ if (!bridge?.__names) {
170
+ return actions as TActions;
171
+ }
172
+
173
+ for (const name of bridge.__names) {
174
+ if (typeof name !== "string") continue;
175
+ const fn = bridge[name];
176
+ if (typeof fn === "function") {
177
+ actions[name] = fn as (...args: unknown[]) => unknown;
178
+ }
179
+ }
180
+
181
+ return actions as TActions;
182
+ }
183
+
184
+ export function getPageStateInfo(): StateInfo {
185
+ return stateInfo;
186
+ }
@@ -0,0 +1,92 @@
1
+ <script setup lang="ts">
2
+ import { computed, h, onBeforeUnmount, ref, useAttrs, watch } from 'vue';
3
+ import { registerInputComponent } from '@lingxia/elements';
4
+ import { buildInputNativeAttrs } from '@lingxia/elements';
5
+ import type { LxInputProps } from './types.js';
6
+ import {
7
+ bindElementEvents,
8
+ getCustomEventDetail,
9
+ unbindElementEvents,
10
+ } from './text_component_shared.js';
11
+
12
+ const props = withDefaults(defineProps<LxInputProps>(), {
13
+ type: 'text',
14
+ disabled: false,
15
+ confirmType: 'done',
16
+ });
17
+ const attrs = useAttrs();
18
+
19
+ const emit = defineEmits<{
20
+ 'update:modelValue': [value: string];
21
+ input: [detail: Record<string, unknown>];
22
+ change: [detail: Record<string, unknown>];
23
+ focus: [detail: Record<string, unknown>];
24
+ blur: [detail: Record<string, unknown>];
25
+ confirm: [detail: Record<string, unknown>];
26
+ keyboardHeightChange: [detail: Record<string, unknown>];
27
+ nicknameReview: [detail: Record<string, unknown>];
28
+ }>();
29
+
30
+ if (typeof window !== 'undefined') {
31
+ registerInputComponent();
32
+ }
33
+
34
+ const elementRef = ref<HTMLElement | null>(null);
35
+ let boundElement: HTMLElement | null = null;
36
+
37
+ const inputEventListeners: Record<string, EventListenerObject> = {
38
+ input: {
39
+ handleEvent: (event: Event) => {
40
+ const detail = getCustomEventDetail<Record<string, unknown>>(event);
41
+ if (typeof detail.value === 'string') {
42
+ emit('update:modelValue', detail.value);
43
+ }
44
+ emit('input', detail);
45
+ },
46
+ },
47
+ change: {
48
+ handleEvent: (event: Event) => emit('change', getCustomEventDetail<Record<string, unknown>>(event)),
49
+ },
50
+ focus: {
51
+ handleEvent: (event: Event) => emit('focus', getCustomEventDetail<Record<string, unknown>>(event)),
52
+ },
53
+ blur: {
54
+ handleEvent: (event: Event) => emit('blur', getCustomEventDetail<Record<string, unknown>>(event)),
55
+ },
56
+ confirm: {
57
+ handleEvent: (event: Event) => emit('confirm', getCustomEventDetail<Record<string, unknown>>(event)),
58
+ },
59
+ keyboardheightchange: {
60
+ handleEvent: (event: Event) => emit('keyboardHeightChange', getCustomEventDetail<Record<string, unknown>>(event)),
61
+ },
62
+ nicknamereview: {
63
+ handleEvent: (event: Event) => emit('nicknameReview', getCustomEventDetail<Record<string, unknown>>(event)),
64
+ },
65
+ };
66
+
67
+ watch(elementRef, (element) => {
68
+ boundElement = bindElementEvents(boundElement, element, inputEventListeners);
69
+ });
70
+
71
+ onBeforeUnmount(() => {
72
+ unbindElementEvents(boundElement, inputEventListeners);
73
+ });
74
+
75
+ const inputProps = computed<Record<string, unknown>>(() => {
76
+ const result: Record<string, unknown> = buildInputNativeAttrs({
77
+ ...props,
78
+ id: props.id ?? (typeof attrs.id === 'string' ? attrs.id : undefined),
79
+ }, attrs as Record<string, unknown>, elementRef.value !== null);
80
+ result.class = props.class ?? attrs.class;
81
+ result.style = props.style ?? attrs.style;
82
+ return result;
83
+ });
84
+
85
+ defineExpose({ el: elementRef });
86
+
87
+ const renderInput = () => h('lx-input', { ref: elementRef, ...inputProps.value });
88
+ </script>
89
+
90
+ <template>
91
+ <renderInput />
92
+ </template>