@lingxia/elements 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 (46) hide show
  1. package/dist/component.d.ts +50 -0
  2. package/dist/component.js +139 -0
  3. package/dist/dom.d.ts +10 -0
  4. package/dist/dom.js +70 -0
  5. package/dist/index.d.ts +9 -0
  6. package/dist/index.js +9 -0
  7. package/dist/input.d.ts +117 -0
  8. package/dist/input.js +699 -0
  9. package/dist/native_component_wrapper_shared.d.ts +74 -0
  10. package/dist/native_component_wrapper_shared.js +193 -0
  11. package/dist/nativecomponent.d.ts +6 -0
  12. package/dist/nativecomponent.js +82 -0
  13. package/dist/navigator.d.ts +83 -0
  14. package/dist/navigator.js +388 -0
  15. package/dist/picker-web.d.ts +2 -0
  16. package/dist/picker-web.js +727 -0
  17. package/dist/picker.d.ts +77 -0
  18. package/dist/picker.js +342 -0
  19. package/dist/platform.d.ts +6 -0
  20. package/dist/platform.js +24 -0
  21. package/dist/text_component_native_attrs.d.ts +53 -0
  22. package/dist/text_component_native_attrs.js +111 -0
  23. package/dist/text_component_shared.d.ts +7 -0
  24. package/dist/text_component_shared.js +148 -0
  25. package/dist/textarea.d.ts +129 -0
  26. package/dist/textarea.js +841 -0
  27. package/dist/types.d.ts +21 -0
  28. package/dist/types.js +1 -0
  29. package/dist/video.d.ts +134 -0
  30. package/dist/video.js +741 -0
  31. package/package.json +39 -0
  32. package/src/component.ts +164 -0
  33. package/src/dom.ts +83 -0
  34. package/src/index.ts +69 -0
  35. package/src/input.ts +799 -0
  36. package/src/native_component_wrapper_shared.ts +243 -0
  37. package/src/nativecomponent.ts +97 -0
  38. package/src/navigator.ts +533 -0
  39. package/src/picker-web.ts +719 -0
  40. package/src/picker.ts +418 -0
  41. package/src/platform.ts +25 -0
  42. package/src/text_component_native_attrs.ts +137 -0
  43. package/src/text_component_shared.ts +147 -0
  44. package/src/textarea.ts +959 -0
  45. package/src/types.ts +20 -0
  46. package/src/video.ts +855 -0
@@ -0,0 +1,243 @@
1
+ import type { NavigatorOpenType, NavigatorTarget } from "./navigator.js";
2
+ import type { LxVideoQuality } from "./video.js";
3
+
4
+ export function appendDataAttrs(
5
+ attrs: Record<string, unknown>,
6
+ target: Record<string, string>
7
+ ): void {
8
+ for (const [key, value] of Object.entries(attrs)) {
9
+ if (value === undefined || value === null) continue;
10
+ if (!key.startsWith("data-")) continue;
11
+ target[key] = String(value);
12
+ }
13
+ }
14
+
15
+ export function appendPassthroughAttrs(
16
+ attrs: Record<string, unknown>,
17
+ target: Record<string, string>
18
+ ): void {
19
+ for (const [key, value] of Object.entries(attrs)) {
20
+ if (value === undefined || value === null) continue;
21
+ if (key in target) continue;
22
+ if (key === "class" || key === "className" || key === "style" || key === "children" || key === "ref") {
23
+ continue;
24
+ }
25
+ if (key.startsWith("on")) continue;
26
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
27
+ target[key] = String(value);
28
+ }
29
+ }
30
+ }
31
+
32
+ export interface NavigatorNativeAttrOptions {
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
+ }
45
+
46
+ export function buildNavigatorNativeAttrs(
47
+ options: NavigatorNativeAttrOptions,
48
+ extraAttrs: Record<string, unknown> = {}
49
+ ): Record<string, string> {
50
+ const result: Record<string, string> = {
51
+ "open-type": options.openType ?? "navigate",
52
+ delta: String(options.delta ?? 1),
53
+ "hover-class": options.hoverClass ?? "navigator-hover",
54
+ "hover-stop-propagation": String(options.hoverStopPropagation ?? false),
55
+ "hover-start-time": String(options.hoverStartTime ?? 20),
56
+ "hover-stay-time": String(options.hoverStayTime ?? 70),
57
+ };
58
+
59
+ if (options.url) result.url = options.url;
60
+ if (options.target) result.target = options.target;
61
+ if (options.appId) result["app-id"] = options.appId;
62
+ if (options.path) result.path = options.path;
63
+ if (options.phoneNumber) result["phone-number"] = options.phoneNumber;
64
+
65
+ appendPassthroughAttrs(extraAttrs, result);
66
+ return result;
67
+ }
68
+
69
+ export type PickerColumns = string[][] | [string[], Record<string, string[]>];
70
+ export type PickerValue = string | string[] | undefined;
71
+ export type PickerFields = "year" | "month" | "day" | "range";
72
+
73
+ export interface PickerNativeAttrOptions {
74
+ id?: string;
75
+ columns?: PickerColumns;
76
+ mode?: "date" | "time";
77
+ start?: string;
78
+ end?: string;
79
+ fields?: PickerFields;
80
+ modelValue?: string | string[];
81
+ value?: string | string[];
82
+ cancelText?: string;
83
+ cancelTextColor?: string;
84
+ cancelButtonColor?: string;
85
+ confirmText?: string;
86
+ confirmTextColor?: string;
87
+ confirmButtonColor?: string;
88
+ }
89
+
90
+ export function isPickerDateMode(mode?: "date" | "time"): boolean {
91
+ return mode === "date" || mode === "time";
92
+ }
93
+
94
+ export function isPickerCascading(columns?: PickerColumns): columns is [string[], Record<string, string[]>] {
95
+ return !!columns && columns.length === 2 && typeof columns[1] === "object" && !Array.isArray(columns[1]);
96
+ }
97
+
98
+ export function isPickerSingle(columns?: PickerColumns): boolean {
99
+ return !!columns && columns.length === 1;
100
+ }
101
+
102
+ export function getPickerIndexFromValue(
103
+ columns: PickerColumns | undefined,
104
+ value: PickerValue
105
+ ): number | number[] {
106
+ if (!columns) return 0;
107
+ if (isPickerSingle(columns)) {
108
+ if (typeof value !== "string") return 0;
109
+ const idx = columns[0].indexOf(value);
110
+ return idx >= 0 ? idx : 0;
111
+ }
112
+ if (!Array.isArray(value)) {
113
+ return Array.from({ length: columns.length }, () => 0);
114
+ }
115
+ if (isPickerCascading(columns)) {
116
+ const [keys, map] = columns;
117
+ const keyIdx = Math.max(0, keys.indexOf(value[0]));
118
+ const valIdx = Math.max(0, map[keys[keyIdx]]?.indexOf(value[1]) ?? 0);
119
+ return [keyIdx, valIdx];
120
+ }
121
+ const indexes = value.map((item, index) => Math.max(0, columns[index]?.indexOf(item) ?? 0));
122
+ while (indexes.length < columns.length) indexes.push(0);
123
+ return indexes;
124
+ }
125
+
126
+ export function getPickerValueFromIndex(
127
+ columns: PickerColumns | undefined,
128
+ index: number | number[]
129
+ ): string | string[] {
130
+ if (!columns) return "";
131
+ if (typeof index === "number") {
132
+ return columns[0]?.[index] ?? "";
133
+ }
134
+ if (isPickerCascading(columns)) {
135
+ const [keys, map] = columns;
136
+ const key = keys[index[0]] ?? "";
137
+ return [key, map[key]?.[index[1]] ?? ""];
138
+ }
139
+ return index.map((item, column) => columns[column]?.[item] ?? "");
140
+ }
141
+
142
+ export function getPickerDisplayText(value: PickerValue, fields?: PickerFields): string {
143
+ if (!value) return "";
144
+ if (fields === "range" && Array.isArray(value)) {
145
+ return `${value[0]} ~ ${value[1]}`;
146
+ }
147
+ return typeof value === "string" ? value : value.join(" - ");
148
+ }
149
+
150
+ export function buildPickerNativeAttrs(
151
+ options: PickerNativeAttrOptions,
152
+ extraAttrs: Record<string, unknown> = {}
153
+ ): Record<string, string> {
154
+ const result: Record<string, string> = {};
155
+ const explicitId = typeof options.id === "string" ? options.id.trim() : "";
156
+ const nextValue = options.modelValue ?? options.value;
157
+
158
+ if (explicitId.length > 0) result.id = explicitId;
159
+
160
+ if (isPickerDateMode(options.mode)) {
161
+ result.mode = options.mode!;
162
+ if (options.fields) result.fields = options.fields;
163
+ if (nextValue) {
164
+ result.value = typeof nextValue === "string" ? nextValue : JSON.stringify(nextValue);
165
+ }
166
+ if (options.start) result.start = options.start;
167
+ if (options.end) result.end = options.end;
168
+ } else {
169
+ result.mode = isPickerCascading(options.columns)
170
+ ? "cascading"
171
+ : isPickerSingle(options.columns)
172
+ ? "selector"
173
+ : "multiSelector";
174
+ result.columns = JSON.stringify(options.columns ?? []);
175
+ result["default-index"] = JSON.stringify(getPickerIndexFromValue(options.columns, nextValue));
176
+ }
177
+
178
+ if (options.cancelText) result["cancel-text"] = options.cancelText;
179
+ if (options.cancelTextColor) result["cancel-text-color"] = options.cancelTextColor;
180
+ if (options.cancelButtonColor) result["cancel-button-color"] = options.cancelButtonColor;
181
+ if (options.confirmText) result["confirm-text"] = options.confirmText;
182
+ if (options.confirmTextColor) result["confirm-text-color"] = options.confirmTextColor;
183
+ if (options.confirmButtonColor) result["confirm-button-color"] = options.confirmButtonColor;
184
+
185
+ appendPassthroughAttrs(extraAttrs, result);
186
+ return result;
187
+ }
188
+
189
+ export const VIDEO_DOM_EVENT_MAP = {
190
+ onPlayRequest: "playrequest",
191
+ onPlay: "play",
192
+ onPlaying: "playing",
193
+ onPause: "pause",
194
+ onStop: "stop",
195
+ onEnded: "ended",
196
+ onTimeUpdate: "timeupdate",
197
+ onError: "error",
198
+ onLoadedMetadata: "loadedmetadata",
199
+ onFullscreenChange: "fullscreenchange",
200
+ onWaiting: "waiting",
201
+ onQualityChange: "qualitychange",
202
+ onRateChange: "ratechange",
203
+ } as const;
204
+
205
+ export interface VideoNativeAttrOptions {
206
+ id?: string;
207
+ src?: string;
208
+ poster?: string;
209
+ autoplay?: boolean;
210
+ loop?: boolean;
211
+ muted?: boolean;
212
+ controls?: boolean;
213
+ progressBar?: boolean;
214
+ live?: boolean;
215
+ volume?: string | number;
216
+ qualities?: LxVideoQuality[];
217
+ playbackRates?: number[];
218
+ }
219
+
220
+ export function buildVideoNativeAttrs(
221
+ options: VideoNativeAttrOptions,
222
+ extraAttrs: Record<string, unknown> = {}
223
+ ): Record<string, string | number> {
224
+ const result: Record<string, string | number> = {};
225
+ const explicitId = typeof options.id === "string" ? options.id.trim() : "";
226
+
227
+ if (explicitId.length > 0) result.id = explicitId;
228
+ if (options.src) result.src = options.src;
229
+ if (options.poster) result.poster = options.poster;
230
+ if (options.autoplay) result.autoplay = "";
231
+ if (options.loop) result.loop = "";
232
+ if (options.muted) result.muted = "";
233
+ if (options.controls) result.controls = "";
234
+ if (options.progressBar === false) result["progress-bar"] = "false";
235
+ if (options.live) result.live = "";
236
+ if (options.volume !== undefined) result.volume = options.volume;
237
+ if (options.qualities?.length) result.qualities = JSON.stringify(options.qualities);
238
+ if (options.playbackRates?.length) result["playback-rates"] = JSON.stringify(options.playbackRates);
239
+
240
+ const extraNativeAttrs: Record<string, string> = {};
241
+ appendPassthroughAttrs(extraAttrs, extraNativeAttrs);
242
+ return { ...result, ...extraNativeAttrs };
243
+ }
@@ -0,0 +1,97 @@
1
+ import type {} from '@lingxia/bridge';
2
+ import type { NativeComponentMessage } from './types.js';
3
+ export type { NativeComponentMessage } from './types.js';
4
+
5
+ let warnedNoHandler = false;
6
+ const NATIVE_COMPONENT_LAYOUT_INVALIDATED_EVENT = "lingxia:native-component-layout-invalidated";
7
+ let pendingLayoutInvalidationFrame: number | null = null;
8
+ const pendingLayoutInvalidationTimers = new Map<number, number>();
9
+
10
+ export function sendNativeComponentMessage(message: NativeComponentMessage) {
11
+ const sender =
12
+ typeof window !== "undefined"
13
+ ? window.LingXiaBridge?.nativeComponents?.send
14
+ : undefined;
15
+ if (typeof sender !== "function") {
16
+ if (!warnedNoHandler) {
17
+ warnedNoHandler = true;
18
+ console.warn("[LingXia NativeComponent] message handler not available");
19
+ }
20
+ return;
21
+ }
22
+ sender(message);
23
+ }
24
+
25
+ export function registerNativeComponentHandler(
26
+ id: string,
27
+ handler: (msg: NativeComponentMessage) => void
28
+ ): () => void {
29
+ const nativeComponents =
30
+ typeof window !== "undefined"
31
+ ? window.LingXiaBridge?.nativeComponents
32
+ : undefined;
33
+ const registerFn = nativeComponents?.register;
34
+ if (typeof registerFn === "function") {
35
+ const unregister = registerFn(id, handler as Parameters<typeof registerFn>[1]);
36
+ const platform = window.LingXiaBridge?.platform;
37
+ const requiresReadyHandshake = !!(
38
+ platform?.isIOS?.() ||
39
+ platform?.isAndroid?.() ||
40
+ platform?.isMacOS?.()
41
+ );
42
+ const hasHandler = nativeComponents?.hasHandler;
43
+ const send = nativeComponents?.send;
44
+ if (typeof send === "function") {
45
+ const nativeReady = typeof hasHandler === "function" ? hasHandler() : true;
46
+ if (requiresReadyHandshake || nativeReady) {
47
+ send({ action: "component.ready", id });
48
+ }
49
+ }
50
+ return unregister;
51
+ }
52
+ if (!warnedNoHandler) {
53
+ warnedNoHandler = true;
54
+ console.warn("[LingXia NativeComponent] message handler not available");
55
+ }
56
+ return () => {};
57
+ }
58
+
59
+ function dispatchNativeComponentLayoutInvalidated(): void {
60
+ if (typeof window === "undefined") return;
61
+ window.dispatchEvent(new CustomEvent(NATIVE_COMPONENT_LAYOUT_INVALIDATED_EVENT));
62
+ }
63
+
64
+ export function invalidateNativeComponentLayout(delays: number[] = [32, 96]): void {
65
+ if (typeof window === "undefined") return;
66
+ if (pendingLayoutInvalidationFrame === null) {
67
+ pendingLayoutInvalidationFrame = window.requestAnimationFrame(() => {
68
+ pendingLayoutInvalidationFrame = null;
69
+ dispatchNativeComponentLayoutInvalidated();
70
+ });
71
+ }
72
+ const uniqueDelays = new Set(
73
+ delays
74
+ .map((delay) => Math.round(delay))
75
+ .filter((delay) => delay > 0)
76
+ );
77
+ uniqueDelays.forEach((delay) => {
78
+ if (pendingLayoutInvalidationTimers.has(delay)) {
79
+ return;
80
+ }
81
+ const timer = window.setTimeout(() => {
82
+ pendingLayoutInvalidationTimers.delete(delay);
83
+ dispatchNativeComponentLayoutInvalidated();
84
+ }, delay);
85
+ pendingLayoutInvalidationTimers.set(delay, timer);
86
+ });
87
+ }
88
+
89
+ export function addNativeComponentLayoutInvalidationListener(
90
+ listener: EventListenerOrEventListenerObject
91
+ ): () => void {
92
+ if (typeof window === "undefined") return () => {};
93
+ window.addEventListener(NATIVE_COMPONENT_LAYOUT_INVALIDATED_EVENT, listener);
94
+ return () => {
95
+ window.removeEventListener(NATIVE_COMPONENT_LAYOUT_INVALIDATED_EVENT, listener);
96
+ };
97
+ }