@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,26 @@
1
+ export function getCustomEventDetail(event) {
2
+ var _a;
3
+ return ((_a = event.detail) !== null && _a !== void 0 ? _a : {});
4
+ }
5
+ export function bindElementEvents(currentBoundElement, nextElement, listeners) {
6
+ if (currentBoundElement && currentBoundElement !== nextElement) {
7
+ for (const [event, listener] of Object.entries(listeners)) {
8
+ currentBoundElement.removeEventListener(event, listener);
9
+ }
10
+ currentBoundElement = null;
11
+ }
12
+ if (nextElement && currentBoundElement !== nextElement) {
13
+ for (const [event, listener] of Object.entries(listeners)) {
14
+ nextElement.addEventListener(event, listener);
15
+ }
16
+ currentBoundElement = nextElement;
17
+ }
18
+ return currentBoundElement;
19
+ }
20
+ export function unbindElementEvents(boundElement, listeners) {
21
+ if (!boundElement)
22
+ return;
23
+ for (const [event, listener] of Object.entries(listeners)) {
24
+ boundElement.removeEventListener(event, listener);
25
+ }
26
+ }
@@ -0,0 +1,101 @@
1
+ import type { CSSProperties } from 'vue';
2
+ import type { LxVideoAttributes } from '@lingxia/elements';
3
+ import type { LxNavigatorEvent, NavigatorOpenType, NavigatorTarget } from '@lingxia/elements';
4
+ export interface LxVideoProps extends Omit<LxVideoAttributes, 'ref' | 'className' | 'style'> {
5
+ class?: string;
6
+ style?: CSSProperties;
7
+ }
8
+ export interface LxPickerProps {
9
+ columns?: string[][] | [string[], Record<string, string[]>];
10
+ mode?: 'date' | 'time';
11
+ start?: string;
12
+ end?: string;
13
+ fields?: 'year' | 'month' | 'day' | 'range';
14
+ modelValue?: string | string[];
15
+ placeholder?: string;
16
+ class?: string;
17
+ style?: CSSProperties;
18
+ disabled?: boolean;
19
+ cancelText?: string;
20
+ cancelTextColor?: string;
21
+ cancelButtonColor?: string;
22
+ confirmText?: string;
23
+ confirmTextColor?: string;
24
+ confirmButtonColor?: string;
25
+ onChange?: (event: Event) => void;
26
+ onNativeScroll?: (event: Event) => void;
27
+ pageBindings?: Record<string, string>;
28
+ }
29
+ export interface LxNavigatorProps {
30
+ url?: string;
31
+ openType?: NavigatorOpenType;
32
+ target?: NavigatorTarget;
33
+ delta?: number;
34
+ appId?: string;
35
+ path?: string;
36
+ phoneNumber?: string;
37
+ hoverClass?: string;
38
+ hoverStopPropagation?: boolean;
39
+ hoverStartTime?: number;
40
+ hoverStayTime?: number;
41
+ class?: string;
42
+ style?: CSSProperties;
43
+ }
44
+ export interface LxInputProps {
45
+ id?: string;
46
+ modelValue?: string;
47
+ value?: string;
48
+ defaultValue?: string;
49
+ type?: 'text' | 'number' | 'password' | 'digit';
50
+ password?: boolean;
51
+ placeholder?: string;
52
+ placeholderStyle?: string;
53
+ placeholderClass?: string;
54
+ maxlength?: number;
55
+ cursorSpacing?: number;
56
+ autoFocus?: boolean;
57
+ disabled?: boolean;
58
+ focus?: boolean;
59
+ confirmType?: 'send' | 'search' | 'next' | 'go' | 'done';
60
+ alwaysEmbed?: boolean;
61
+ confirmHold?: boolean;
62
+ cursor?: number;
63
+ cursorColor?: string;
64
+ selectionStart?: number;
65
+ selectionEnd?: number;
66
+ adjustPosition?: boolean;
67
+ holdKeyboard?: boolean;
68
+ class?: string;
69
+ style?: CSSProperties;
70
+ pageBindings?: Record<string, string>;
71
+ }
72
+ export interface LxTextareaProps {
73
+ id?: string;
74
+ modelValue?: string;
75
+ value?: string;
76
+ defaultValue?: string;
77
+ placeholder?: string;
78
+ placeholderStyle?: string;
79
+ placeholderClass?: string;
80
+ maxlength?: number;
81
+ disabled?: boolean;
82
+ autoFocus?: boolean;
83
+ focus?: boolean;
84
+ autoHeight?: boolean;
85
+ cursorSpacing?: number;
86
+ showConfirmBar?: boolean;
87
+ adjustPosition?: boolean;
88
+ holdKeyboard?: boolean;
89
+ disableDefaultPadding?: boolean;
90
+ confirmType?: 'send' | 'search' | 'next' | 'go' | 'done' | 'return';
91
+ confirmHold?: boolean;
92
+ fixed?: boolean;
93
+ adjustKeyboardTo?: 'cursor' | 'bottom';
94
+ cursor?: number;
95
+ selectionStart?: number;
96
+ selectionEnd?: number;
97
+ class?: string;
98
+ style?: CSSProperties;
99
+ pageBindings?: Record<string, string>;
100
+ }
101
+ export type { LxNavigatorEvent };
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@lingxia/page-runtime",
3
+ "version": "0.5.0",
4
+ "private": false,
5
+ "description": "LingXia page runtime for HTML, React, and Vue lxapp views",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./react": {
15
+ "types": "./dist/react/index.d.ts",
16
+ "default": "./dist/react/index.js"
17
+ },
18
+ "./vue": {
19
+ "types": "./dist/vue/index.d.ts",
20
+ "default": "./dist/vue/index.js"
21
+ },
22
+ "./package.json": "./package.json"
23
+ },
24
+ "scripts": {
25
+ "build": "npm run clean && tsc -p tsconfig.json && mkdir -p dist/vue && cp src/vue/*.vue dist/vue/",
26
+ "clean": "rm -rf dist",
27
+ "prepublishOnly": "npm run build"
28
+ },
29
+ "dependencies": {
30
+ "@lingxia/bridge": ">=0.5.0",
31
+ "@lingxia/elements": ">=0.5.0"
32
+ },
33
+ "peerDependencies": {
34
+ "react": "^19.0.0",
35
+ "vue": ">=3.5.0"
36
+ },
37
+ "peerDependenciesMeta": {
38
+ "react": {
39
+ "optional": true
40
+ },
41
+ "vue": {
42
+ "optional": true
43
+ }
44
+ },
45
+ "devDependencies": {
46
+ "@lingxia/bridge": "file:../lingxia-bridge",
47
+ "@lingxia/elements": "file:../lingxia-elements",
48
+ "@types/react": "^19.2.7",
49
+ "react": "^19.0.0",
50
+ "typescript": "^5.7.2",
51
+ "vue": "^3.5.0"
52
+ },
53
+ "files": [
54
+ "dist",
55
+ "src"
56
+ ],
57
+ "publishConfig": {
58
+ "registry": "https://registry.npmjs.org",
59
+ "access": "public"
60
+ },
61
+ "repository": {
62
+ "type": "git",
63
+ "url": "https://github.com/LingXia-Dev/LingXia.git",
64
+ "directory": "packages/lingxia-page-runtime"
65
+ },
66
+ "license": "MIT"
67
+ }
package/src/index.ts ADDED
@@ -0,0 +1,21 @@
1
+ export {
2
+ ensurePageBridgeSubscription,
3
+ getPageActions,
4
+ getPageSnapshot,
5
+ getPageStateInfo,
6
+ subscribePageData,
7
+ subscribePageSnapshot,
8
+ toBridgeError,
9
+ resolveParams,
10
+ stableParamKey,
11
+ invokeMethod,
12
+ getMethodKey,
13
+ type ActionMap,
14
+ type Snapshot,
15
+ type ParamsSource,
16
+ type MethodParams,
17
+ type StreamData,
18
+ type StreamResult,
19
+ type ChannelIn,
20
+ type ChannelOut,
21
+ } from "./shared/runtime.js";
@@ -0,0 +1,224 @@
1
+ import React, { forwardRef, useCallback, useEffect, useRef } from "react";
2
+ import { registerInputComponent } from "@lingxia/elements";
3
+ import { buildInputNativeAttrs } from "@lingxia/elements";
4
+ import {
5
+ assignForwardedRef,
6
+ bindElementEvents,
7
+ getCustomEventDetail,
8
+ unbindElementEvents,
9
+ } from "./text_component_shared.js";
10
+
11
+ export interface LxInputEventDetail {
12
+ value?: string;
13
+ cursor?: number;
14
+ keyCode?: number;
15
+ height?: number;
16
+ duration?: number;
17
+ encryptedValue?: string;
18
+ encryptError?: string;
19
+ pass?: boolean;
20
+ timeout?: boolean;
21
+ }
22
+
23
+ export interface LxInputProps {
24
+ id?: string;
25
+ value?: string;
26
+ defaultValue?: string;
27
+ type?: "text" | "number" | "password" | "digit";
28
+ password?: boolean;
29
+ placeholder?: string;
30
+ placeholderStyle?: string;
31
+ placeholderClass?: string;
32
+ maxlength?: number;
33
+ cursorSpacing?: number;
34
+ autoFocus?: boolean;
35
+ disabled?: boolean;
36
+ focus?: boolean;
37
+ confirmType?: "send" | "search" | "next" | "go" | "done";
38
+ alwaysEmbed?: boolean;
39
+ confirmHold?: boolean;
40
+ cursor?: number;
41
+ cursorColor?: string;
42
+ selectionStart?: number;
43
+ selectionEnd?: number;
44
+ adjustPosition?: boolean;
45
+ holdKeyboard?: boolean;
46
+
47
+ onInput?: (detail: LxInputEventDetail) => void;
48
+ onChange?: (detail: LxInputEventDetail) => void;
49
+ onFocus?: (detail: LxInputEventDetail) => void;
50
+ onBlur?: (detail: LxInputEventDetail) => void;
51
+ onConfirm?: (detail: LxInputEventDetail) => void;
52
+ onKeyboardHeightChange?: (detail: LxInputEventDetail) => void;
53
+ onNicknameReview?: (detail: LxInputEventDetail) => void;
54
+
55
+ // Logic bindings (CLI-generated)
56
+ pageBindings?: Record<string, string>;
57
+
58
+ className?: string;
59
+ style?: React.CSSProperties;
60
+ }
61
+
62
+ if (typeof window !== "undefined") {
63
+ registerInputComponent();
64
+ }
65
+
66
+ export const LxInput = forwardRef<HTMLElement, LxInputProps>(({
67
+ value,
68
+ defaultValue,
69
+ type,
70
+ password,
71
+ placeholder,
72
+ placeholderStyle,
73
+ placeholderClass,
74
+ maxlength,
75
+ cursorSpacing,
76
+ autoFocus,
77
+ disabled,
78
+ focus,
79
+ confirmType,
80
+ alwaysEmbed,
81
+ confirmHold,
82
+ cursor,
83
+ cursorColor,
84
+ selectionStart,
85
+ selectionEnd,
86
+ adjustPosition,
87
+ holdKeyboard,
88
+ onInput,
89
+ onChange,
90
+ onFocus,
91
+ onBlur,
92
+ onConfirm,
93
+ onKeyboardHeightChange,
94
+ onNicknameReview,
95
+ pageBindings,
96
+ className,
97
+ style,
98
+ id,
99
+ ...rest
100
+ }, ref) => {
101
+ const elementRef = useRef<HTMLElement | null>(null);
102
+ const boundElementRef = useRef<HTMLElement | null>(null);
103
+ const handlerRef = useRef({
104
+ onInput,
105
+ onChange,
106
+ onFocus,
107
+ onBlur,
108
+ onConfirm,
109
+ onKeyboardHeightChange,
110
+ onNicknameReview,
111
+ });
112
+ handlerRef.current = {
113
+ onInput,
114
+ onChange,
115
+ onFocus,
116
+ onBlur,
117
+ onConfirm,
118
+ onKeyboardHeightChange,
119
+ onNicknameReview,
120
+ };
121
+ const listenerMapRef = useRef<Record<string, EventListenerObject>>({
122
+ input: {
123
+ handleEvent: (event: Event) => handlerRef.current.onInput?.(getCustomEventDetail<LxInputEventDetail>(event)),
124
+ },
125
+ change: {
126
+ handleEvent: (event: Event) => handlerRef.current.onChange?.(getCustomEventDetail<LxInputEventDetail>(event)),
127
+ },
128
+ focus: {
129
+ handleEvent: (event: Event) => handlerRef.current.onFocus?.(getCustomEventDetail<LxInputEventDetail>(event)),
130
+ },
131
+ blur: {
132
+ handleEvent: (event: Event) => handlerRef.current.onBlur?.(getCustomEventDetail<LxInputEventDetail>(event)),
133
+ },
134
+ confirm: {
135
+ handleEvent: (event: Event) => handlerRef.current.onConfirm?.(getCustomEventDetail<LxInputEventDetail>(event)),
136
+ },
137
+ keyboardheightchange: {
138
+ handleEvent: (event: Event) => handlerRef.current.onKeyboardHeightChange?.(getCustomEventDetail<LxInputEventDetail>(event)),
139
+ },
140
+ nicknamereview: {
141
+ handleEvent: (event: Event) => handlerRef.current.onNicknameReview?.(getCustomEventDetail<LxInputEventDetail>(event)),
142
+ },
143
+ });
144
+ const elementRefCallback = useCallback((element: HTMLElement | null) => {
145
+ boundElementRef.current = bindElementEvents(boundElementRef.current, element, listenerMapRef.current);
146
+ elementRef.current = element;
147
+ assignForwardedRef(ref, element);
148
+ }, [ref]);
149
+
150
+ useEffect(() => () => {
151
+ unbindElementEvents(boundElementRef.current, listenerMapRef.current);
152
+ boundElementRef.current = null;
153
+ elementRef.current = null;
154
+ }, []);
155
+
156
+ useEffect(() => {
157
+ const el = elementRef.current;
158
+ if (!el) return;
159
+ const setAttr = (name: string, next: string | null) => {
160
+ if (next === null) {
161
+ el.removeAttribute(name);
162
+ return;
163
+ }
164
+ el.setAttribute(name, next);
165
+ };
166
+
167
+ if (type && String(type).trim().length > 0) {
168
+ el.setAttribute("type", String(type).trim().toLowerCase());
169
+ } else {
170
+ el.removeAttribute("type");
171
+ }
172
+ if (password) {
173
+ el.setAttribute("password", "true");
174
+ } else {
175
+ el.removeAttribute("password");
176
+ }
177
+ setAttr("value", value !== undefined ? String(value) : null);
178
+ setAttr("focus", focus !== undefined ? (focus ? "true" : "false") : null);
179
+ setAttr("maxlength", maxlength !== undefined ? String(maxlength) : null);
180
+ setAttr("placeholder-style", placeholderStyle ? String(placeholderStyle) : null);
181
+ }, [type, password, value, focus, maxlength, placeholderStyle]);
182
+
183
+ // Set pageBindings property on custom element
184
+ useEffect(() => {
185
+ const el = elementRef.current as any;
186
+ if (el && pageBindings) {
187
+ el.pageBindings = pageBindings;
188
+ }
189
+ }, [pageBindings]);
190
+
191
+ const inputProps = buildInputNativeAttrs({
192
+ id,
193
+ value,
194
+ defaultValue,
195
+ type,
196
+ password,
197
+ placeholder,
198
+ placeholderStyle,
199
+ placeholderClass,
200
+ maxlength,
201
+ cursorSpacing,
202
+ autoFocus,
203
+ disabled,
204
+ focus,
205
+ confirmType,
206
+ alwaysEmbed,
207
+ confirmHold,
208
+ cursor,
209
+ cursorColor,
210
+ selectionStart,
211
+ selectionEnd,
212
+ adjustPosition,
213
+ holdKeyboard,
214
+ }, rest as Record<string, unknown>, boundElementRef.current !== null);
215
+
216
+ return React.createElement("lx-input", {
217
+ ref: elementRefCallback,
218
+ className,
219
+ style,
220
+ ...inputProps,
221
+ });
222
+ });
223
+
224
+ LxInput.displayName = "LxInput";
@@ -0,0 +1,132 @@
1
+ import React from 'react';
2
+ import type {
3
+ LxNavigatorAttributes,
4
+ LxNavigatorEvent,
5
+ NavigatorOpenType,
6
+ NavigatorTarget
7
+ } from '@lingxia/elements';
8
+ import { buildNavigatorNativeAttrs } from '@lingxia/elements';
9
+
10
+ // Import to ensure custom element is registered
11
+ import '@lingxia/elements';
12
+
13
+ export interface LxNavigatorProps extends Omit<LxNavigatorAttributes, 'onSuccess' | 'onFail' | 'onComplete'> {
14
+ // Navigation
15
+ url?: string;
16
+ openType?: NavigatorOpenType;
17
+ target?: NavigatorTarget; // Auto-inferred if not specified
18
+ delta?: number;
19
+
20
+ // Open external lxapp
21
+ appId?: string;
22
+ path?: string; // Supports query string
23
+
24
+ // Phone call
25
+ phoneNumber?: string;
26
+
27
+ // Hover effect
28
+ hoverClass?: string;
29
+ hoverStopPropagation?: boolean;
30
+ hoverStartTime?: number;
31
+ hoverStayTime?: number;
32
+
33
+ // Styling
34
+ className?: string;
35
+ style?: React.CSSProperties;
36
+
37
+ // Events
38
+ onSuccess?: (e: LxNavigatorEvent) => void;
39
+ onFail?: (e: LxNavigatorEvent) => void;
40
+ onComplete?: (e: LxNavigatorEvent) => void;
41
+
42
+ // Children
43
+ children?: React.ReactNode;
44
+ }
45
+
46
+ export const LxNavigator = React.forwardRef<HTMLElement, LxNavigatorProps>(
47
+ (props, ref) => {
48
+ const {
49
+ url,
50
+ openType = 'navigate',
51
+ target, // Auto-inferred, no default
52
+ delta = 1,
53
+ appId,
54
+ path,
55
+ phoneNumber,
56
+ hoverClass = 'navigator-hover',
57
+ hoverStopPropagation = false,
58
+ hoverStartTime = 20,
59
+ hoverStayTime = 70,
60
+ className,
61
+ style,
62
+ onSuccess,
63
+ onFail,
64
+ onComplete,
65
+ children,
66
+ ...rest
67
+ } = props;
68
+
69
+ const elementRef = React.useRef<HTMLElement>(null);
70
+
71
+ React.useImperativeHandle(ref, () => elementRef.current!);
72
+
73
+ React.useEffect(() => {
74
+ const element = elementRef.current;
75
+ if (!element) return;
76
+
77
+ const handleSuccess = (e: Event) => {
78
+ if (onSuccess) {
79
+ onSuccess(e as LxNavigatorEvent);
80
+ }
81
+ };
82
+
83
+ const handleFail = (e: Event) => {
84
+ if (onFail) {
85
+ onFail(e as LxNavigatorEvent);
86
+ }
87
+ };
88
+
89
+ const handleComplete = (e: Event) => {
90
+ if (onComplete) {
91
+ onComplete(e as LxNavigatorEvent);
92
+ }
93
+ };
94
+ const successListener: EventListenerObject = { handleEvent: handleSuccess };
95
+ const failListener: EventListenerObject = { handleEvent: handleFail };
96
+ const completeListener: EventListenerObject = { handleEvent: handleComplete };
97
+
98
+ element.addEventListener('success', successListener);
99
+ element.addEventListener('fail', failListener);
100
+ element.addEventListener('complete', completeListener);
101
+
102
+ return () => {
103
+ element.removeEventListener('success', successListener);
104
+ element.removeEventListener('fail', failListener);
105
+ element.removeEventListener('complete', completeListener);
106
+ };
107
+ }, [onSuccess, onFail, onComplete]);
108
+
109
+ const navigatorProps = buildNavigatorNativeAttrs({
110
+ url,
111
+ openType,
112
+ target,
113
+ delta,
114
+ appId,
115
+ path,
116
+ phoneNumber,
117
+ hoverClass,
118
+ hoverStopPropagation,
119
+ hoverStartTime,
120
+ hoverStayTime,
121
+ }, rest as Record<string, unknown>);
122
+
123
+ return React.createElement('lx-navigator', {
124
+ ref: elementRef,
125
+ className,
126
+ style,
127
+ ...navigatorProps
128
+ }, children);
129
+ }
130
+ );
131
+
132
+ LxNavigator.displayName = 'LxNavigator';