@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.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/react/LxInput.d.ts +47 -0
- package/dist/react/LxInput.js +128 -0
- package/dist/react/LxNavigator.d.ts +23 -0
- package/dist/react/LxNavigator.js +61 -0
- package/dist/react/LxPicker.d.ts +27 -0
- package/dist/react/LxPicker.js +113 -0
- package/dist/react/LxTextarea.d.ts +47 -0
- package/dist/react/LxTextarea.js +118 -0
- package/dist/react/LxVideo.d.ts +5 -0
- package/dist/react/LxVideo.js +113 -0
- package/dist/react/hook.d.ts +39 -0
- package/dist/react/hook.js +212 -0
- package/dist/react/index.d.ts +6 -0
- package/dist/react/index.js +6 -0
- package/dist/react/text_component_shared.d.ts +5 -0
- package/dist/react/text_component_shared.js +33 -0
- package/dist/shared/runtime.d.ts +21 -0
- package/dist/shared/runtime.js +142 -0
- package/dist/vue/LxInput.vue +92 -0
- package/dist/vue/LxNavigator.vue +79 -0
- package/dist/vue/LxPicker.vue +144 -0
- package/dist/vue/LxTextarea.vue +90 -0
- package/dist/vue/LxVideo.vue +111 -0
- package/dist/vue/hook.d.ts +42 -0
- package/dist/vue/hook.js +194 -0
- package/dist/vue/index.d.ts +7 -0
- package/dist/vue/index.js +6 -0
- package/dist/vue/text_component_shared.d.ts +3 -0
- package/dist/vue/text_component_shared.js +26 -0
- package/dist/vue/types.d.ts +101 -0
- package/dist/vue/types.js +1 -0
- package/package.json +67 -0
- package/src/index.ts +21 -0
- package/src/react/LxInput.tsx +224 -0
- package/src/react/LxNavigator.tsx +132 -0
- package/src/react/LxPicker.tsx +205 -0
- package/src/react/LxTextarea.tsx +216 -0
- package/src/react/LxVideo.tsx +166 -0
- package/src/react/hook.ts +317 -0
- package/src/react/index.ts +14 -0
- package/src/react/text_component_shared.ts +43 -0
- package/src/shared/runtime.ts +186 -0
- package/src/vue/LxInput.vue +92 -0
- package/src/vue/LxNavigator.vue +79 -0
- package/src/vue/LxPicker.vue +144 -0
- package/src/vue/LxTextarea.vue +90 -0
- package/src/vue/LxVideo.vue +111 -0
- package/src/vue/hook.ts +313 -0
- package/src/vue/index.ts +22 -0
- package/src/vue/shims-vue.d.ts +5 -0
- package/src/vue/text_component_shared.ts +33 -0
- 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';
|