@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,205 @@
1
+ import React, { forwardRef, useCallback, useId, useRef, useState } from 'react';
2
+ import { registerPickerComponent } from '@lingxia/elements';
3
+ import {
4
+ buildPickerNativeAttrs,
5
+ getPickerDisplayText,
6
+ getPickerValueFromIndex,
7
+ } from '@lingxia/elements';
8
+ import {
9
+ assignForwardedRef,
10
+ bindElementEvents,
11
+ getCustomEventDetail,
12
+ unbindElementEvents,
13
+ } from './text_component_shared.js';
14
+
15
+ export interface LxPickerProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onChange' | 'onScroll'> {
16
+ // For selector/multiSelector/cascading mode
17
+ columns?: string[][] | [string[], Record<string, string[]>];
18
+
19
+ // For date/time mode
20
+ mode?: 'date' | 'time';
21
+ start?: string; // Valid date range start: 'YYYY-MM-DD'
22
+ end?: string; // Valid date range end: 'YYYY-MM-DD'
23
+ fields?: 'year' | 'month' | 'day' | 'range';
24
+
25
+ // Value (type depends on mode)
26
+ value?: string | string[];
27
+
28
+ // Callbacks
29
+ onConfirm?: (value: string | string[]) => void;
30
+ onCancel?: () => void;
31
+ onScroll?: (value: string | string[]) => void;
32
+ onChange?: (event: CustomEvent) => void;
33
+ onNativeScroll?: (event: CustomEvent) => void;
34
+
35
+ // Logic bindings (CLI-generated)
36
+ pageBindings?: Record<string, string>;
37
+
38
+ // UI
39
+ placeholder?: string;
40
+ className?: string;
41
+ style?: React.CSSProperties;
42
+ disabled?: boolean;
43
+
44
+ // Button customization
45
+ cancelText?: string;
46
+ cancelTextColor?: string;
47
+ cancelButtonColor?: string;
48
+ confirmText?: string;
49
+ confirmTextColor?: string;
50
+ confirmButtonColor?: string;
51
+
52
+ children?: React.ReactNode;
53
+ }
54
+
55
+ if (typeof window !== "undefined") {
56
+ registerPickerComponent();
57
+ }
58
+
59
+ export const LxPicker = forwardRef<HTMLElement, LxPickerProps>(({
60
+ id,
61
+ columns, mode, start, end, fields, value, onConfirm, onCancel, onScroll, placeholder = 'Please select',
62
+ onChange, onNativeScroll, pageBindings,
63
+ className, style, disabled, cancelText, cancelTextColor, cancelButtonColor,
64
+ confirmText, confirmTextColor, confirmButtonColor, children,
65
+ ...rest
66
+ }, ref) => {
67
+ const [visible, setVisible] = useState(false);
68
+ const reactId = useId();
69
+ const pickerId = id ?? `lx-picker-${reactId.replace(/[:]/g, "")}`;
70
+ const elementRef = useRef<HTMLElement | null>(null);
71
+ const boundElementRef = useRef<HTMLElement | null>(null);
72
+ const handlerRef = useRef({
73
+ columns,
74
+ mode,
75
+ onConfirm,
76
+ onCancel,
77
+ onScroll,
78
+ onChange,
79
+ onNativeScroll,
80
+ });
81
+ handlerRef.current = {
82
+ columns,
83
+ mode,
84
+ onConfirm,
85
+ onCancel,
86
+ onScroll,
87
+ onChange,
88
+ onNativeScroll,
89
+ };
90
+ const listenerMapRef = useRef<Record<string, EventListenerObject>>({
91
+ change: {
92
+ handleEvent: (event: Event) => {
93
+ const detail = getCustomEventDetail<{
94
+ confirmed?: boolean;
95
+ cancelled?: boolean;
96
+ value?: string | string[];
97
+ index?: number | number[];
98
+ }>(event);
99
+ handlerRef.current.onChange?.(event as CustomEvent);
100
+ if (detail.confirmed) {
101
+ const nextValue =
102
+ handlerRef.current.mode === 'date' || handlerRef.current.mode === 'time'
103
+ ? (detail.value ?? '')
104
+ : detail.index !== undefined
105
+ ? getPickerValueFromIndex(handlerRef.current.columns, detail.index)
106
+ : '';
107
+ handlerRef.current.onConfirm?.(nextValue);
108
+ setVisible(false);
109
+ return;
110
+ }
111
+ if (detail.cancelled) {
112
+ handlerRef.current.onCancel?.();
113
+ setVisible(false);
114
+ }
115
+ },
116
+ },
117
+ scroll: {
118
+ handleEvent: (event: Event) => {
119
+ const detail = getCustomEventDetail<{
120
+ value?: string | string[];
121
+ index?: number | number[];
122
+ }>(event);
123
+ handlerRef.current.onNativeScroll?.(event as CustomEvent);
124
+ if (detail.value !== undefined) {
125
+ handlerRef.current.onScroll?.(detail.value);
126
+ return;
127
+ }
128
+ if (detail.index !== undefined) {
129
+ handlerRef.current.onScroll?.(getPickerValueFromIndex(handlerRef.current.columns, detail.index));
130
+ }
131
+ },
132
+ },
133
+ });
134
+ const elementRefCallback = useCallback((element: HTMLElement | null) => {
135
+ boundElementRef.current = bindElementEvents(boundElementRef.current, element, listenerMapRef.current);
136
+ elementRef.current = element;
137
+ assignForwardedRef(ref, element);
138
+
139
+ // Set pageBindings on custom element
140
+ if (element && pageBindings) {
141
+ (element as any).pageBindings = pageBindings;
142
+ }
143
+ }, [ref, pageBindings]);
144
+ React.useEffect(() => () => {
145
+ unbindElementEvents(boundElementRef.current, listenerMapRef.current);
146
+ boundElementRef.current = null;
147
+ elementRef.current = null;
148
+ }, []);
149
+
150
+ const handleClick = () => !disabled && setVisible(true);
151
+ const pickerProps = buildPickerNativeAttrs({
152
+ id: pickerId,
153
+ columns,
154
+ mode,
155
+ start,
156
+ end,
157
+ fields,
158
+ value,
159
+ cancelText,
160
+ cancelTextColor,
161
+ cancelButtonColor,
162
+ confirmText,
163
+ confirmTextColor,
164
+ confirmButtonColor,
165
+ }, rest as Record<string, unknown>);
166
+ const displayText = getPickerDisplayText(value, fields);
167
+
168
+ return (
169
+ <>
170
+ {children ? (
171
+ <div
172
+ onClick={handleClick}
173
+ className={className}
174
+ style={{
175
+ cursor: disabled ? 'not-allowed' : 'pointer',
176
+ opacity: disabled ? 0.5 : 1,
177
+ ...style,
178
+ }}
179
+ >
180
+ {children}
181
+ </div>
182
+ ) : (
183
+ <div
184
+ onClick={handleClick}
185
+ className={className}
186
+ style={{
187
+ display: 'flex', alignItems: 'center', justifyContent: 'space-between',
188
+ padding: '12px 14px', backgroundColor: '#fff', border: '1px solid #e5e7eb',
189
+ borderRadius: '8px', cursor: disabled ? 'not-allowed' : 'pointer',
190
+ opacity: disabled ? 0.5 : 1, width: '100%', boxSizing: 'border-box',
191
+ ...style,
192
+ }}
193
+ >
194
+ <span style={{ color: value ? '#111' : '#9ca3af' }}>{displayText || placeholder}</span>
195
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#9ca3af" strokeWidth="2">
196
+ <path d="M6 9l6 6 6-6" />
197
+ </svg>
198
+ </div>
199
+ )}
200
+ {visible && React.createElement('lx-picker', { ref: elementRefCallback, ...pickerProps })}
201
+ </>
202
+ );
203
+ });
204
+
205
+ LxPicker.displayName = 'LxPicker';
@@ -0,0 +1,216 @@
1
+ import React, { forwardRef, useCallback, useEffect, useRef } from "react";
2
+ import { registerTextareaComponent } from "@lingxia/elements";
3
+ import { buildTextareaNativeAttrs } from "@lingxia/elements";
4
+ import {
5
+ assignForwardedRef,
6
+ bindElementEvents,
7
+ getCustomEventDetail,
8
+ unbindElementEvents,
9
+ } from "./text_component_shared.js";
10
+
11
+ export interface LxTextareaEventDetail {
12
+ value?: string;
13
+ cursor?: number;
14
+ lineCount?: number;
15
+ height?: number;
16
+ duration?: number;
17
+ selectionStart?: number;
18
+ selectionEnd?: number;
19
+ heightRpx?: number;
20
+ }
21
+
22
+ export interface LxTextareaProps {
23
+ id?: string;
24
+ value?: string;
25
+ defaultValue?: string;
26
+ placeholder?: string;
27
+ placeholderStyle?: string;
28
+ placeholderClass?: string;
29
+ maxlength?: number;
30
+ disabled?: boolean;
31
+ autoFocus?: boolean;
32
+ focus?: boolean;
33
+ autoHeight?: boolean;
34
+ cursorSpacing?: number;
35
+ showConfirmBar?: boolean;
36
+ adjustPosition?: boolean;
37
+ holdKeyboard?: boolean;
38
+ disableDefaultPadding?: boolean;
39
+ confirmType?: "send" | "search" | "next" | "go" | "done" | "return";
40
+ confirmHold?: boolean;
41
+ fixed?: boolean;
42
+ adjustKeyboardTo?: "cursor" | "bottom";
43
+ cursor?: number;
44
+ selectionStart?: number;
45
+ selectionEnd?: number;
46
+
47
+ onInput?: (detail: LxTextareaEventDetail) => void;
48
+ onChange?: (detail: LxTextareaEventDetail) => void;
49
+ onFocus?: (detail: LxTextareaEventDetail) => void;
50
+ onBlur?: (detail: LxTextareaEventDetail) => void;
51
+ onConfirm?: (detail: LxTextareaEventDetail) => void;
52
+ onLineChange?: (detail: LxTextareaEventDetail) => void;
53
+ onKeyboardHeightChange?: (detail: LxTextareaEventDetail) => 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
+ registerTextareaComponent();
64
+ }
65
+
66
+ export const LxTextarea = forwardRef<HTMLElement, LxTextareaProps>(({
67
+ value,
68
+ defaultValue,
69
+ placeholder,
70
+ placeholderStyle,
71
+ placeholderClass,
72
+ maxlength,
73
+ disabled,
74
+ autoFocus,
75
+ focus,
76
+ autoHeight,
77
+ cursorSpacing,
78
+ showConfirmBar,
79
+ adjustPosition,
80
+ holdKeyboard,
81
+ disableDefaultPadding,
82
+ confirmType,
83
+ confirmHold,
84
+ fixed,
85
+ adjustKeyboardTo,
86
+ cursor,
87
+ selectionStart,
88
+ selectionEnd,
89
+ onInput,
90
+ onChange,
91
+ onFocus,
92
+ onBlur,
93
+ onConfirm,
94
+ onLineChange,
95
+ onKeyboardHeightChange,
96
+ pageBindings,
97
+ className,
98
+ style,
99
+ id,
100
+ ...rest
101
+ }, ref) => {
102
+ const elementRef = useRef<HTMLElement | null>(null);
103
+ const boundElementRef = useRef<HTMLElement | null>(null);
104
+ const handlerRef = useRef({
105
+ onInput,
106
+ onChange,
107
+ onFocus,
108
+ onBlur,
109
+ onConfirm,
110
+ onLineChange,
111
+ onKeyboardHeightChange,
112
+ });
113
+ handlerRef.current = {
114
+ onInput,
115
+ onChange,
116
+ onFocus,
117
+ onBlur,
118
+ onConfirm,
119
+ onLineChange,
120
+ onKeyboardHeightChange,
121
+ };
122
+ const listenerMapRef = useRef<Record<string, EventListenerObject>>({
123
+ input: {
124
+ handleEvent: (event: Event) => handlerRef.current.onInput?.(getCustomEventDetail<LxTextareaEventDetail>(event)),
125
+ },
126
+ change: {
127
+ handleEvent: (event: Event) => handlerRef.current.onChange?.(getCustomEventDetail<LxTextareaEventDetail>(event)),
128
+ },
129
+ focus: {
130
+ handleEvent: (event: Event) => handlerRef.current.onFocus?.(getCustomEventDetail<LxTextareaEventDetail>(event)),
131
+ },
132
+ blur: {
133
+ handleEvent: (event: Event) => handlerRef.current.onBlur?.(getCustomEventDetail<LxTextareaEventDetail>(event)),
134
+ },
135
+ confirm: {
136
+ handleEvent: (event: Event) => handlerRef.current.onConfirm?.(getCustomEventDetail<LxTextareaEventDetail>(event)),
137
+ },
138
+ linechange: {
139
+ handleEvent: (event: Event) => handlerRef.current.onLineChange?.(getCustomEventDetail<LxTextareaEventDetail>(event)),
140
+ },
141
+ keyboardheightchange: {
142
+ handleEvent: (event: Event) => handlerRef.current.onKeyboardHeightChange?.(getCustomEventDetail<LxTextareaEventDetail>(event)),
143
+ },
144
+ });
145
+ const elementRefCallback = useCallback((element: HTMLElement | null) => {
146
+ boundElementRef.current = bindElementEvents(boundElementRef.current, element, listenerMapRef.current);
147
+ elementRef.current = element;
148
+ assignForwardedRef(ref, element);
149
+ }, [ref]);
150
+
151
+ useEffect(() => () => {
152
+ unbindElementEvents(boundElementRef.current, listenerMapRef.current);
153
+ boundElementRef.current = null;
154
+ elementRef.current = null;
155
+ }, []);
156
+
157
+ React.useEffect(() => {
158
+ const el = elementRef.current;
159
+ if (!el) return;
160
+ const setAttr = (name: string, next: string | null) => {
161
+ if (next === null) {
162
+ el.removeAttribute(name);
163
+ return;
164
+ }
165
+ el.setAttribute(name, next);
166
+ };
167
+ setAttr("value", value !== undefined ? String(value) : null);
168
+ setAttr("focus", focus !== undefined ? (focus ? "true" : "false") : null);
169
+ setAttr("maxlength", maxlength !== undefined ? String(maxlength) : null);
170
+ setAttr("placeholder-style", placeholderStyle ? String(placeholderStyle) : null);
171
+ setAttr("auto-height", autoHeight ? "true" : null);
172
+ }, [value, focus, maxlength, placeholderStyle, autoHeight]);
173
+
174
+ // Set pageBindings property on custom element
175
+ useEffect(() => {
176
+ const el = elementRef.current as any;
177
+ if (el && pageBindings) {
178
+ el.pageBindings = pageBindings;
179
+ }
180
+ }, [pageBindings]);
181
+
182
+ const textareaProps = buildTextareaNativeAttrs({
183
+ id,
184
+ value,
185
+ defaultValue,
186
+ placeholder,
187
+ placeholderStyle,
188
+ placeholderClass,
189
+ maxlength,
190
+ disabled,
191
+ autoFocus,
192
+ focus,
193
+ autoHeight,
194
+ cursorSpacing,
195
+ showConfirmBar,
196
+ adjustPosition,
197
+ holdKeyboard,
198
+ disableDefaultPadding,
199
+ confirmType,
200
+ confirmHold,
201
+ fixed,
202
+ adjustKeyboardTo,
203
+ cursor,
204
+ selectionStart,
205
+ selectionEnd,
206
+ }, rest as Record<string, unknown>, boundElementRef.current !== null);
207
+
208
+ return React.createElement("lx-textarea", {
209
+ ref: elementRefCallback,
210
+ className,
211
+ style,
212
+ ...textareaProps,
213
+ });
214
+ });
215
+
216
+ LxTextarea.displayName = "LxTextarea";
@@ -0,0 +1,166 @@
1
+ import React, { forwardRef, useCallback, useEffect, useId, useMemo, useRef } from 'react';
2
+ import { registerVideoComponent, type LxVideoAttributes } from '@lingxia/elements';
3
+ import {
4
+ buildVideoNativeAttrs,
5
+ VIDEO_DOM_EVENT_MAP,
6
+ } from '@lingxia/elements';
7
+ import {
8
+ assignForwardedRef,
9
+ bindElementEvents,
10
+ unbindElementEvents,
11
+ } from './text_component_shared.js';
12
+
13
+ export interface LxVideoProps
14
+ extends LxVideoAttributes,
15
+ Omit<
16
+ React.HTMLAttributes<HTMLElement>,
17
+ keyof LxVideoAttributes | "children" | "dangerouslySetInnerHTML" | "ref" | "onPlaying"
18
+ > {}
19
+
20
+ if (typeof window !== "undefined") {
21
+ registerVideoComponent();
22
+ }
23
+ export const LxVideo = forwardRef<HTMLElement, LxVideoProps>(({
24
+ id,
25
+ src,
26
+ poster,
27
+ objectFit,
28
+ rotate,
29
+ autoplay,
30
+ loop,
31
+ muted,
32
+ controls,
33
+ progressBar,
34
+ live,
35
+ volume,
36
+ qualities,
37
+ playbackRates,
38
+ onPlayRequest,
39
+ onPlay,
40
+ onPlaying,
41
+ onPause,
42
+ onStop,
43
+ onEnded,
44
+ onTimeUpdate,
45
+ onError,
46
+ onLoadedMetadata,
47
+ onFullscreenChange,
48
+ onWaiting,
49
+ onQualityChange,
50
+ onRateChange,
51
+ pageBindings,
52
+ className,
53
+ style,
54
+ ...rest
55
+ }, ref) => {
56
+ const elementRef = useRef<HTMLElement | null>(null);
57
+ const boundElementRef = useRef<HTMLElement | null>(null);
58
+ const reactId = useId();
59
+ const resolvedId = useMemo(() => {
60
+ if (id) return id;
61
+ return `lx-video-${reactId.replace(/[:]/g, "")}`;
62
+ }, [id, reactId]);
63
+ const handlerRef = useRef({
64
+ onPlayRequest,
65
+ onPlay,
66
+ onPlaying,
67
+ onPause,
68
+ onStop,
69
+ onEnded,
70
+ onTimeUpdate,
71
+ onError,
72
+ onLoadedMetadata,
73
+ onFullscreenChange,
74
+ onWaiting,
75
+ onQualityChange,
76
+ onRateChange,
77
+ });
78
+ handlerRef.current = {
79
+ onPlayRequest,
80
+ onPlay,
81
+ onPlaying,
82
+ onPause,
83
+ onStop,
84
+ onEnded,
85
+ onTimeUpdate,
86
+ onError,
87
+ onLoadedMetadata,
88
+ onFullscreenChange,
89
+ onWaiting,
90
+ onQualityChange,
91
+ onRateChange,
92
+ };
93
+ const listenerMapRef = useRef<Record<string, EventListenerObject>>(
94
+ Object.fromEntries(
95
+ Object.entries(VIDEO_DOM_EVENT_MAP).map(([propKey, eventName]) => [
96
+ eventName,
97
+ {
98
+ handleEvent: (event: Event) => {
99
+ const handler = handlerRef.current[propKey as keyof typeof handlerRef.current];
100
+ if (typeof handler === "function") {
101
+ handler(event);
102
+ }
103
+ },
104
+ } satisfies EventListenerObject,
105
+ ])
106
+ )
107
+ );
108
+ const elementRefCallback = useCallback((element: HTMLElement | null) => {
109
+ boundElementRef.current = bindElementEvents(boundElementRef.current, element, listenerMapRef.current);
110
+ elementRef.current = element;
111
+ assignForwardedRef(ref, element);
112
+ }, [ref]);
113
+
114
+ useEffect(() => () => {
115
+ unbindElementEvents(boundElementRef.current, listenerMapRef.current);
116
+ boundElementRef.current = null;
117
+ elementRef.current = null;
118
+ }, []);
119
+
120
+ useEffect(() => {
121
+ const el = elementRef.current;
122
+ if (!el) return;
123
+ if (rotate === undefined || rotate === null) {
124
+ el.removeAttribute("rotate");
125
+ } else {
126
+ el.setAttribute("rotate", String(rotate).trim());
127
+ }
128
+ if (objectFit === undefined || objectFit === null) {
129
+ el.removeAttribute("object-fit");
130
+ } else {
131
+ el.setAttribute("object-fit", String(objectFit).trim().toLowerCase());
132
+ }
133
+ }, [rotate, objectFit]);
134
+
135
+ // Set pageBindings property on custom element
136
+ useEffect(() => {
137
+ const el = elementRef.current as any;
138
+ if (el && pageBindings) {
139
+ el.pageBindings = pageBindings;
140
+ }
141
+ }, [pageBindings]);
142
+
143
+ const domProps = buildVideoNativeAttrs({
144
+ id: resolvedId,
145
+ src,
146
+ poster,
147
+ autoplay,
148
+ loop,
149
+ muted,
150
+ controls,
151
+ progressBar,
152
+ live,
153
+ volume,
154
+ qualities,
155
+ playbackRates,
156
+ }, rest as Record<string, unknown>);
157
+
158
+ return React.createElement('lx-video', {
159
+ ref: elementRefCallback,
160
+ className,
161
+ style,
162
+ ...domProps
163
+ });
164
+ });
165
+
166
+ LxVideo.displayName = 'LxVideo';