@lingxia/vue 0.5.1

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.
@@ -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>
@@ -0,0 +1,79 @@
1
+ <script setup lang="ts">
2
+ import { ref, h, onMounted, onBeforeUnmount, useAttrs } from 'vue';
3
+ import type { LxNavigatorEvent } from '@lingxia/elements';
4
+ import { buildNavigatorNativeAttrs } from '@lingxia/elements';
5
+ import type { LxNavigatorProps } from './types.js';
6
+ import '@lingxia/elements';
7
+
8
+ const props = withDefaults(defineProps<LxNavigatorProps>(), {
9
+ openType: 'navigate',
10
+ delta: 1,
11
+ hoverClass: 'navigator-hover',
12
+ hoverStopPropagation: false,
13
+ hoverStartTime: 20,
14
+ hoverStayTime: 70,
15
+ });
16
+
17
+ const slots = defineSlots();
18
+ const attrs = useAttrs();
19
+
20
+ const emit = defineEmits<{
21
+ success: [e: LxNavigatorEvent];
22
+ fail: [e: LxNavigatorEvent];
23
+ complete: [e: LxNavigatorEvent];
24
+ }>();
25
+
26
+ const elementRef = ref<HTMLElement | null>(null);
27
+
28
+ const handleSuccess = (e: Event) => emit('success', e as LxNavigatorEvent);
29
+ const handleFail = (e: Event) => emit('fail', e as LxNavigatorEvent);
30
+ const handleComplete = (e: Event) => emit('complete', e as LxNavigatorEvent);
31
+ const successListener: EventListenerObject = { handleEvent: handleSuccess };
32
+ const failListener: EventListenerObject = { handleEvent: handleFail };
33
+ const completeListener: EventListenerObject = { handleEvent: handleComplete };
34
+
35
+ onMounted(() => {
36
+ const el = elementRef.value;
37
+ if (!el) return;
38
+ el.addEventListener('success', successListener);
39
+ el.addEventListener('fail', failListener);
40
+ el.addEventListener('complete', completeListener);
41
+ });
42
+
43
+ onBeforeUnmount(() => {
44
+ const el = elementRef.value;
45
+ if (!el) return;
46
+ el.removeEventListener('success', successListener);
47
+ el.removeEventListener('fail', failListener);
48
+ el.removeEventListener('complete', completeListener);
49
+ });
50
+
51
+ defineExpose({ el: elementRef });
52
+
53
+ const render = () => h(
54
+ 'lx-navigator',
55
+ {
56
+ ref: elementRef,
57
+ ...buildNavigatorNativeAttrs({
58
+ url: props.url,
59
+ openType: props.openType,
60
+ target: props.target,
61
+ delta: props.delta,
62
+ appId: props.appId,
63
+ path: props.path,
64
+ phoneNumber: props.phoneNumber,
65
+ hoverClass: props.hoverClass,
66
+ hoverStopPropagation: props.hoverStopPropagation,
67
+ hoverStartTime: props.hoverStartTime,
68
+ hoverStayTime: props.hoverStayTime,
69
+ }, attrs as Record<string, unknown>),
70
+ class: props.class ?? attrs.class,
71
+ style: props.style ?? attrs.style,
72
+ },
73
+ slots.default?.()
74
+ );
75
+ </script>
76
+
77
+ <template>
78
+ <render />
79
+ </template>
@@ -0,0 +1,144 @@
1
+ <script setup lang="ts">
2
+ import { ref, computed, h, onBeforeUnmount, useAttrs, useId, watch } from 'vue';
3
+ import { registerPickerComponent } from '@lingxia/elements';
4
+ import {
5
+ buildPickerNativeAttrs,
6
+ getPickerDisplayText,
7
+ getPickerValueFromIndex,
8
+ } from '@lingxia/elements';
9
+ import { bindElementEvents, getCustomEventDetail, unbindElementEvents } from './text_component_shared.js';
10
+ import type { LxPickerProps } from './types.js';
11
+
12
+ const props = withDefaults(defineProps<LxPickerProps>(), {
13
+ placeholder: 'Please select',
14
+ disabled: false,
15
+ });
16
+ const attrs = useAttrs();
17
+
18
+ const slots = defineSlots();
19
+
20
+ const emit = defineEmits<{
21
+ 'update:modelValue': [value: string | string[]];
22
+ confirm: [value: string | string[]];
23
+ cancel: [];
24
+ scroll: [value: string | string[]];
25
+ }>();
26
+
27
+ if (typeof window !== 'undefined') {
28
+ registerPickerComponent();
29
+ }
30
+
31
+ const visible = ref(false);
32
+ const vueId = useId();
33
+ const pickerId = computed(() => `lx-picker-${vueId.replace(/[:]/g, '')}`);
34
+ const pickerRef = ref<HTMLElement | null>(null);
35
+ let boundElement: HTMLElement | null = null;
36
+ const pickerEventListeners: Record<string, EventListenerObject> = {
37
+ change: {
38
+ handleEvent: (event: Event) => handleChange(event),
39
+ },
40
+ scroll: {
41
+ handleEvent: (event: Event) => handleScroll(event),
42
+ },
43
+ };
44
+
45
+ const displayText = computed(() => getPickerDisplayText(props.modelValue, props.fields));
46
+
47
+ function handleChange(e: Event) {
48
+ props.onChange?.(e);
49
+ const detail = getCustomEventDetail<{
50
+ confirmed?: boolean;
51
+ cancelled?: boolean;
52
+ value?: string | string[];
53
+ index?: number | number[];
54
+ }>(e);
55
+ if (detail.confirmed) {
56
+ if (props.mode === 'date' || props.mode === 'time') {
57
+ const nextValue = detail.value ?? '';
58
+ emit('update:modelValue', nextValue);
59
+ emit('confirm', nextValue);
60
+ } else if (detail.index !== undefined) {
61
+ const nextValue = getPickerValueFromIndex(props.columns, detail.index);
62
+ emit('update:modelValue', nextValue);
63
+ emit('confirm', nextValue);
64
+ }
65
+ visible.value = false;
66
+ } else if (detail.cancelled) {
67
+ emit('cancel');
68
+ visible.value = false;
69
+ }
70
+ }
71
+
72
+ function handleScroll(e: Event) {
73
+ props.onNativeScroll?.(e);
74
+ const detail = getCustomEventDetail<{
75
+ value?: string | string[];
76
+ index?: number | number[];
77
+ }>(e);
78
+ if (detail.value !== undefined) {
79
+ emit('scroll', detail.value);
80
+ } else if (detail.index !== undefined) {
81
+ emit('scroll', getPickerValueFromIndex(props.columns, detail.index));
82
+ }
83
+ }
84
+
85
+ function handleClick() {
86
+ if (!props.disabled) visible.value = true;
87
+ }
88
+
89
+ watch(pickerRef, (element) => {
90
+ boundElement = bindElementEvents(boundElement, element, pickerEventListeners);
91
+ });
92
+
93
+ onBeforeUnmount(() => {
94
+ unbindElementEvents(boundElement, pickerEventListeners);
95
+ });
96
+
97
+ const pickerProps = computed(() => {
98
+ return buildPickerNativeAttrs({
99
+ id: pickerId.value,
100
+ columns: props.columns,
101
+ mode: props.mode,
102
+ start: props.start,
103
+ end: props.end,
104
+ fields: props.fields,
105
+ modelValue: props.modelValue,
106
+ cancelText: props.cancelText,
107
+ cancelTextColor: props.cancelTextColor,
108
+ cancelButtonColor: props.cancelButtonColor,
109
+ confirmText: props.confirmText,
110
+ confirmTextColor: props.confirmTextColor,
111
+ confirmButtonColor: props.confirmButtonColor,
112
+ }, attrs as Record<string, unknown>);
113
+ });
114
+
115
+ const triggerStyle = computed(() => ({
116
+ display: 'flex',
117
+ alignItems: 'center',
118
+ justifyContent: 'space-between',
119
+ padding: '12px 14px',
120
+ backgroundColor: '#fff',
121
+ border: '1px solid #e5e7eb',
122
+ borderRadius: '8px',
123
+ cursor: props.disabled ? 'not-allowed' : 'pointer',
124
+ opacity: props.disabled ? 0.5 : 1,
125
+ width: '100%',
126
+ boxSizing: 'border-box',
127
+ }));
128
+
129
+ defineExpose({ el: pickerRef });
130
+
131
+ const renderPicker = () => visible.value ? h('lx-picker', { ref: pickerRef, ...pickerProps.value }) : null;
132
+ </script>
133
+
134
+ <template>
135
+ <slot :open="handleClick" :disabled="props.disabled">
136
+ <div :class="props.class ?? attrs.class" :style="[triggerStyle, props.style ?? attrs.style]" @click="handleClick">
137
+ <span :style="{ color: modelValue ? '#111' : '#9ca3af' }">{{ displayText || placeholder }}</span>
138
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#9ca3af" stroke-width="2">
139
+ <path d="M6 9l6 6 6-6" />
140
+ </svg>
141
+ </div>
142
+ </slot>
143
+ <renderPicker />
144
+ </template>
@@ -0,0 +1,90 @@
1
+ <script setup lang="ts">
2
+ import { computed, h, onBeforeUnmount, ref, useAttrs, watch } from 'vue';
3
+ import { registerTextareaComponent } from '@lingxia/elements';
4
+ import { buildTextareaNativeAttrs } from '@lingxia/elements';
5
+ import type { LxTextareaProps } from './types.js';
6
+ import {
7
+ bindElementEvents,
8
+ getCustomEventDetail,
9
+ unbindElementEvents,
10
+ } from './text_component_shared.js';
11
+
12
+ const props = withDefaults(defineProps<LxTextareaProps>(), {
13
+ disabled: false,
14
+ });
15
+ const attrs = useAttrs();
16
+
17
+ const emit = defineEmits<{
18
+ 'update:modelValue': [value: string];
19
+ input: [detail: Record<string, unknown>];
20
+ change: [detail: Record<string, unknown>];
21
+ focus: [detail: Record<string, unknown>];
22
+ blur: [detail: Record<string, unknown>];
23
+ confirm: [detail: Record<string, unknown>];
24
+ lineChange: [detail: Record<string, unknown>];
25
+ keyboardHeightChange: [detail: Record<string, unknown>];
26
+ }>();
27
+
28
+ if (typeof window !== 'undefined') {
29
+ registerTextareaComponent();
30
+ }
31
+
32
+ const elementRef = ref<HTMLElement | null>(null);
33
+ let boundElement: HTMLElement | null = null;
34
+
35
+ const textareaEventListeners: Record<string, EventListenerObject> = {
36
+ input: {
37
+ handleEvent: (event: Event) => {
38
+ const detail = getCustomEventDetail<Record<string, unknown>>(event);
39
+ if (typeof detail.value === 'string') {
40
+ emit('update:modelValue', detail.value);
41
+ }
42
+ emit('input', detail);
43
+ },
44
+ },
45
+ change: {
46
+ handleEvent: (event: Event) => emit('change', getCustomEventDetail<Record<string, unknown>>(event)),
47
+ },
48
+ focus: {
49
+ handleEvent: (event: Event) => emit('focus', getCustomEventDetail<Record<string, unknown>>(event)),
50
+ },
51
+ blur: {
52
+ handleEvent: (event: Event) => emit('blur', getCustomEventDetail<Record<string, unknown>>(event)),
53
+ },
54
+ confirm: {
55
+ handleEvent: (event: Event) => emit('confirm', getCustomEventDetail<Record<string, unknown>>(event)),
56
+ },
57
+ linechange: {
58
+ handleEvent: (event: Event) => emit('lineChange', getCustomEventDetail<Record<string, unknown>>(event)),
59
+ },
60
+ keyboardheightchange: {
61
+ handleEvent: (event: Event) => emit('keyboardHeightChange', getCustomEventDetail<Record<string, unknown>>(event)),
62
+ },
63
+ };
64
+
65
+ watch(elementRef, (element) => {
66
+ boundElement = bindElementEvents(boundElement, element, textareaEventListeners);
67
+ });
68
+
69
+ onBeforeUnmount(() => {
70
+ unbindElementEvents(boundElement, textareaEventListeners);
71
+ });
72
+
73
+ const textareaProps = computed<Record<string, unknown>>(() => {
74
+ const result: Record<string, unknown> = buildTextareaNativeAttrs({
75
+ ...props,
76
+ id: props.id ?? (typeof attrs.id === 'string' ? attrs.id : undefined),
77
+ }, attrs as Record<string, unknown>, elementRef.value !== null);
78
+ result.class = props.class ?? attrs.class;
79
+ result.style = props.style ?? attrs.style;
80
+ return result;
81
+ });
82
+
83
+ defineExpose({ el: elementRef });
84
+
85
+ const renderTextarea = () => h('lx-textarea', { ref: elementRef, ...textareaProps.value });
86
+ </script>
87
+
88
+ <template>
89
+ <renderTextarea />
90
+ </template>
@@ -0,0 +1,111 @@
1
+ <script setup lang="ts">
2
+ import { ref, computed, h, onBeforeUnmount, useAttrs, useId, watch } from 'vue';
3
+ import { registerVideoComponent } from '@lingxia/elements';
4
+ import {
5
+ buildVideoNativeAttrs,
6
+ VIDEO_DOM_EVENT_MAP,
7
+ } from '@lingxia/elements';
8
+ import { bindElementEvents, unbindElementEvents } from './text_component_shared.js';
9
+ import type { LxVideoProps } from './types.js';
10
+
11
+ const props = withDefaults(defineProps<LxVideoProps>(), {
12
+ autoplay: false,
13
+ loop: false,
14
+ muted: false,
15
+ controls: true,
16
+ progressBar: true,
17
+ live: false,
18
+ });
19
+ const attrs = useAttrs();
20
+
21
+ const emit = defineEmits<{
22
+ playRequest: [e: Event];
23
+ play: [e: Event];
24
+ playing: [e: Event];
25
+ pause: [e: Event];
26
+ stop: [e: Event];
27
+ ended: [e: Event];
28
+ timeUpdate: [e: Event];
29
+ error: [e: Event];
30
+ loadedMetadata: [e: Event];
31
+ fullscreenChange: [e: Event];
32
+ waiting: [e: Event];
33
+ qualityChange: [e: Event];
34
+ rateChange: [e: Event];
35
+ }>();
36
+
37
+ if (typeof window !== 'undefined') {
38
+ registerVideoComponent();
39
+ }
40
+
41
+ const elementRef = ref<HTMLElement | null>(null);
42
+ const vueId = useId();
43
+ let boundElement: HTMLElement | null = null;
44
+
45
+ const resolvedId = computed(() => props.id || `lx-video-${vueId.replace(/[:]/g, '')}`);
46
+
47
+ const videoEventListeners: Record<string, EventListenerObject> = {
48
+ [VIDEO_DOM_EVENT_MAP.onPlayRequest]: { handleEvent: (event: Event) => emit('playRequest', event) },
49
+ [VIDEO_DOM_EVENT_MAP.onPlay]: { handleEvent: (event: Event) => emit('play', event) },
50
+ [VIDEO_DOM_EVENT_MAP.onPlaying]: { handleEvent: (event: Event) => emit('playing', event) },
51
+ [VIDEO_DOM_EVENT_MAP.onPause]: { handleEvent: (event: Event) => emit('pause', event) },
52
+ [VIDEO_DOM_EVENT_MAP.onStop]: { handleEvent: (event: Event) => emit('stop', event) },
53
+ [VIDEO_DOM_EVENT_MAP.onEnded]: { handleEvent: (event: Event) => emit('ended', event) },
54
+ [VIDEO_DOM_EVENT_MAP.onTimeUpdate]: { handleEvent: (event: Event) => emit('timeUpdate', event) },
55
+ [VIDEO_DOM_EVENT_MAP.onError]: { handleEvent: (event: Event) => emit('error', event) },
56
+ [VIDEO_DOM_EVENT_MAP.onLoadedMetadata]: { handleEvent: (event: Event) => emit('loadedMetadata', event) },
57
+ [VIDEO_DOM_EVENT_MAP.onFullscreenChange]: { handleEvent: (event: Event) => emit('fullscreenChange', event) },
58
+ [VIDEO_DOM_EVENT_MAP.onWaiting]: { handleEvent: (event: Event) => emit('waiting', event) },
59
+ [VIDEO_DOM_EVENT_MAP.onQualityChange]: { handleEvent: (event: Event) => emit('qualityChange', event) },
60
+ [VIDEO_DOM_EVENT_MAP.onRateChange]: { handleEvent: (event: Event) => emit('rateChange', event) },
61
+ };
62
+
63
+ watch(elementRef, (element) => {
64
+ boundElement = bindElementEvents(boundElement, element, videoEventListeners);
65
+ });
66
+
67
+ onBeforeUnmount(() => {
68
+ unbindElementEvents(boundElement, videoEventListeners);
69
+ });
70
+
71
+ watch(
72
+ [elementRef, () => props.rotate, () => props.objectFit],
73
+ () => {
74
+ const el = elementRef.value;
75
+ if (!el) return;
76
+ const rotate = props.rotate;
77
+ if (rotate === undefined || rotate === null || rotate === '') {
78
+ el.removeAttribute('rotate');
79
+ } else {
80
+ el.setAttribute('rotate', String(rotate).trim());
81
+ }
82
+ const objectFit = props.objectFit;
83
+ if (objectFit === undefined || objectFit === null || objectFit === '') {
84
+ el.removeAttribute('object-fit');
85
+ } else {
86
+ el.setAttribute('object-fit', String(objectFit).trim().toLowerCase());
87
+ }
88
+ },
89
+ { immediate: true }
90
+ );
91
+
92
+ const domProps = computed(() => {
93
+ const result = buildVideoNativeAttrs({
94
+ ...props,
95
+ id: resolvedId.value,
96
+ }, attrs as Record<string, unknown>);
97
+ return {
98
+ ...result,
99
+ class: props.class ?? attrs.class,
100
+ style: props.style ?? attrs.style,
101
+ };
102
+ });
103
+
104
+ defineExpose({ el: elementRef });
105
+
106
+ const render = () => h('lx-video', { ref: elementRef, ...domProps.value });
107
+ </script>
108
+
109
+ <template>
110
+ <render />
111
+ </template>
package/dist/hook.d.ts ADDED
@@ -0,0 +1,43 @@
1
+ import { type Ref } from "vue";
2
+ import type { LxChannel, LxBridgeError, LxStream } from "@lingxia/bridge";
3
+ import { type ChannelIn, type ChannelOut, type MethodParams, type ParamsSource, type StreamData, type StreamResult } from "@lingxia/bridge/invocation";
4
+ import { type ActionMap, type Snapshot } from "@lingxia/page-runtime";
5
+ type MethodSource<T> = T | Ref<T>;
6
+ export declare function useLxPage<TData = Snapshot, TActions extends ActionMap = ActionMap>(): {
7
+ data: TData;
8
+ actions: TActions;
9
+ };
10
+ export interface LxStreamOptions<TData, TReduced> {
11
+ params?: unknown | (() => unknown);
12
+ manual?: boolean;
13
+ reduce?: (accumulated: TReduced, chunk: TData) => TReduced;
14
+ initial?: TReduced;
15
+ }
16
+ export interface LxStreamState<TData, TResult = unknown> {
17
+ data: Ref<TData | undefined>;
18
+ result: Ref<TResult | undefined>;
19
+ error: Ref<LxBridgeError | undefined>;
20
+ streaming: Ref<boolean>;
21
+ cancel: () => void;
22
+ start: () => void;
23
+ }
24
+ export declare function useLxStream<TMethod extends (...args: any[]) => LxStream<any, any>, TReduced = StreamData<TMethod>>(method: MethodSource<TMethod>, options?: LxStreamOptions<StreamData<TMethod>, TReduced> & {
25
+ params?: ParamsSource<MethodParams<TMethod>>;
26
+ }): LxStreamState<TReduced extends StreamData<TMethod> ? StreamData<TMethod> : TReduced, StreamResult<TMethod>>;
27
+ export interface LxChannelOptions {
28
+ params?: unknown | (() => unknown);
29
+ manual?: boolean;
30
+ }
31
+ export interface LxChannelState<TData, TOut = TData> {
32
+ last: Ref<TData | undefined>;
33
+ error: Ref<LxBridgeError | undefined>;
34
+ connecting: Ref<boolean>;
35
+ connected: Ref<boolean>;
36
+ send: (payload: TOut) => void;
37
+ close: (code?: string, reason?: string) => void;
38
+ reopen: () => void;
39
+ }
40
+ export declare function useLxChannel<TMethod extends (...args: any[]) => Promise<LxChannel<any, any>>>(method: MethodSource<TMethod>, options?: LxChannelOptions & {
41
+ params?: ParamsSource<MethodParams<TMethod>>;
42
+ }): LxChannelState<ChannelIn<TMethod>, ChannelOut<TMethod>>;
43
+ export {};
package/dist/hook.js ADDED
@@ -0,0 +1,193 @@
1
+ import { onUnmounted, reactive, ref, unref, watch, } from "vue";
2
+ import { getMethodKey, invokeMethod, resolveParams, stableParamKey, toBridgeError, } from "@lingxia/bridge/invocation";
3
+ import { ensurePageBridgeSubscription, getPageActions, subscribePageData, } from "@lingxia/page-runtime";
4
+ function resolveMethod(source) {
5
+ return unref(source);
6
+ }
7
+ const reactiveSnapshot = reactive({});
8
+ let snapshotSubscribed = false;
9
+ function ensureReactiveSnapshot() {
10
+ if (snapshotSubscribed)
11
+ return;
12
+ snapshotSubscribed = true;
13
+ subscribePageData((next) => {
14
+ const normalized = next && typeof next === "object" ? next : {};
15
+ for (const key of Object.keys(reactiveSnapshot)) {
16
+ if (!Object.prototype.hasOwnProperty.call(normalized, key)) {
17
+ delete reactiveSnapshot[key];
18
+ }
19
+ }
20
+ Object.assign(reactiveSnapshot, normalized);
21
+ });
22
+ }
23
+ export function useLxPage() {
24
+ ensurePageBridgeSubscription();
25
+ ensureReactiveSnapshot();
26
+ return { data: reactiveSnapshot, actions: getPageActions() };
27
+ }
28
+ export function useLxStream(method, options) {
29
+ const data = ref((options?.reduce ? options.initial : undefined));
30
+ const result = ref(undefined);
31
+ const error = ref(undefined);
32
+ const streaming = ref(false);
33
+ let handle = null;
34
+ let acc = options?.initial;
35
+ let runId = 0;
36
+ function cancel() {
37
+ runId += 1;
38
+ handle?.cancel();
39
+ handle = null;
40
+ streaming.value = false;
41
+ }
42
+ function start() {
43
+ handle?.cancel();
44
+ const currentRunId = runId + 1;
45
+ runId = currentRunId;
46
+ acc = options?.initial;
47
+ data.value = (options?.reduce ? options.initial : undefined);
48
+ result.value = undefined;
49
+ error.value = undefined;
50
+ streaming.value = true;
51
+ let nextHandle;
52
+ try {
53
+ const params = resolveParams(options?.params);
54
+ nextHandle = invokeMethod(resolveMethod(method), params);
55
+ }
56
+ catch (err) {
57
+ if (runId !== currentRunId)
58
+ return;
59
+ handle = null;
60
+ error.value = toBridgeError(err);
61
+ streaming.value = false;
62
+ return;
63
+ }
64
+ handle = nextHandle;
65
+ nextHandle.on("data", (chunk) => {
66
+ if (runId !== currentRunId)
67
+ return;
68
+ if (options?.reduce) {
69
+ acc = options.reduce(acc, chunk);
70
+ data.value = acc;
71
+ }
72
+ else {
73
+ data.value = chunk;
74
+ }
75
+ });
76
+ nextHandle.on("end", (res) => {
77
+ if (runId !== currentRunId)
78
+ return;
79
+ handle = null;
80
+ result.value = res;
81
+ streaming.value = false;
82
+ });
83
+ nextHandle.on("error", (err) => {
84
+ if (runId !== currentRunId)
85
+ return;
86
+ handle = null;
87
+ error.value = err;
88
+ streaming.value = false;
89
+ });
90
+ }
91
+ watch(() => {
92
+ if (options?.manual)
93
+ return null;
94
+ const resolvedMethod = resolveMethod(method);
95
+ return [
96
+ getMethodKey(resolvedMethod) ?? resolvedMethod,
97
+ stableParamKey(resolveParams(options?.params)),
98
+ ];
99
+ }, () => {
100
+ if (!options?.manual) {
101
+ start();
102
+ }
103
+ }, { immediate: !options?.manual });
104
+ onUnmounted(() => {
105
+ runId += 1;
106
+ handle?.cancel();
107
+ handle = null;
108
+ });
109
+ return { data, result, error, streaming, cancel, start };
110
+ }
111
+ export function useLxChannel(method, options) {
112
+ const last = ref(undefined);
113
+ const error = ref(undefined);
114
+ const connecting = ref(false);
115
+ const connected = ref(false);
116
+ let ch = null;
117
+ let runId = 0;
118
+ function send(payload) {
119
+ ch?.send(payload);
120
+ }
121
+ function close(code, reason) {
122
+ runId += 1;
123
+ ch?.close(code, reason);
124
+ ch = null;
125
+ connecting.value = false;
126
+ connected.value = false;
127
+ }
128
+ function reopen() {
129
+ ch?.close();
130
+ ch = null;
131
+ const thisRunId = ++runId;
132
+ last.value = undefined;
133
+ error.value = undefined;
134
+ connecting.value = true;
135
+ connected.value = false;
136
+ Promise.resolve(invokeMethod(resolveMethod(method), resolveParams(options?.params)))
137
+ .then((nextChannel) => {
138
+ if (runId !== thisRunId) {
139
+ nextChannel.close();
140
+ return;
141
+ }
142
+ ch = nextChannel;
143
+ connecting.value = false;
144
+ connected.value = true;
145
+ nextChannel.on("data", (payload) => {
146
+ if (runId !== thisRunId)
147
+ return;
148
+ last.value = payload;
149
+ });
150
+ nextChannel.on("close", () => {
151
+ if (runId !== thisRunId)
152
+ return;
153
+ ch = null;
154
+ connecting.value = false;
155
+ connected.value = false;
156
+ });
157
+ nextChannel.on("error", (err) => {
158
+ if (runId !== thisRunId)
159
+ return;
160
+ ch = null;
161
+ error.value = err;
162
+ connecting.value = false;
163
+ connected.value = false;
164
+ });
165
+ })
166
+ .catch((err) => {
167
+ if (runId !== thisRunId)
168
+ return;
169
+ error.value = toBridgeError(err);
170
+ connecting.value = false;
171
+ connected.value = false;
172
+ });
173
+ }
174
+ watch(() => {
175
+ if (options?.manual)
176
+ return null;
177
+ const resolvedMethod = resolveMethod(method);
178
+ return [
179
+ getMethodKey(resolvedMethod) ?? resolvedMethod,
180
+ stableParamKey(resolveParams(options?.params)),
181
+ ];
182
+ }, () => {
183
+ if (!options?.manual) {
184
+ reopen();
185
+ }
186
+ }, { immediate: !options?.manual });
187
+ onUnmounted(() => {
188
+ runId += 1;
189
+ ch?.close();
190
+ ch = null;
191
+ });
192
+ return { last, error, connecting, connected, send, close, reopen };
193
+ }
@@ -0,0 +1,7 @@
1
+ export { useLxPage, useLxStream, useLxChannel, type LxStreamOptions, type LxStreamState, type LxChannelOptions, type LxChannelState, } from "./hook.js";
2
+ export { default as LxVideo } from "./LxVideo.vue";
3
+ export { default as LxPicker } from "./LxPicker.vue";
4
+ export { default as LxNavigator } from "./LxNavigator.vue";
5
+ export { default as LxInput } from "./LxInput.vue";
6
+ export { default as LxTextarea } from "./LxTextarea.vue";
7
+ export type { LxVideoProps, LxPickerProps, LxNavigatorProps, LxNavigatorEvent, LxInputProps, LxTextareaProps, } from "./types.js";
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { useLxPage, useLxStream, useLxChannel, } from "./hook.js";
2
+ export { default as LxVideo } from "./LxVideo.vue";
3
+ export { default as LxPicker } from "./LxPicker.vue";
4
+ export { default as LxNavigator } from "./LxNavigator.vue";
5
+ export { default as LxInput } from "./LxInput.vue";
6
+ export { default as LxTextarea } from "./LxTextarea.vue";
@@ -0,0 +1,5 @@
1
+ declare module "*.vue" {
2
+ import type { DefineComponent } from "vue";
3
+ const component: DefineComponent<{}, {}, any>;
4
+ export default component;
5
+ }
@@ -0,0 +1,3 @@
1
+ export declare function getCustomEventDetail<T>(event: Event): T;
2
+ export declare function bindElementEvents(currentBoundElement: HTMLElement | null, nextElement: HTMLElement | null, listeners: Record<string, EventListenerObject>): HTMLElement | null;
3
+ export declare function unbindElementEvents(boundElement: HTMLElement | null, listeners: Record<string, EventListenerObject>): void;
@@ -0,0 +1,25 @@
1
+ export function getCustomEventDetail(event) {
2
+ return (event.detail ?? {});
3
+ }
4
+ export function bindElementEvents(currentBoundElement, nextElement, listeners) {
5
+ if (currentBoundElement && currentBoundElement !== nextElement) {
6
+ for (const [event, listener] of Object.entries(listeners)) {
7
+ currentBoundElement.removeEventListener(event, listener);
8
+ }
9
+ currentBoundElement = null;
10
+ }
11
+ if (nextElement && currentBoundElement !== nextElement) {
12
+ for (const [event, listener] of Object.entries(listeners)) {
13
+ nextElement.addEventListener(event, listener);
14
+ }
15
+ currentBoundElement = nextElement;
16
+ }
17
+ return currentBoundElement;
18
+ }
19
+ export function unbindElementEvents(boundElement, listeners) {
20
+ if (!boundElement)
21
+ return;
22
+ for (const [event, listener] of Object.entries(listeners)) {
23
+ boundElement.removeEventListener(event, listener);
24
+ }
25
+ }
@@ -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 };
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@lingxia/vue",
3
+ "version": "0.5.1",
4
+ "private": false,
5
+ "description": "LingXia Vue runtime and components for lxapp pages",
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
+ "./package.json": "./package.json"
15
+ },
16
+ "scripts": {
17
+ "build": "npm run clean && npm --prefix ../lingxia-elements run build && npm --prefix ../lingxia-page-runtime run build && tsc -p tsconfig.json && mkdir -p dist && cp src/*.vue dist/ && cp src/shims-vue.d.ts dist/shims-vue.d.ts",
18
+ "clean": "rm -rf dist",
19
+ "prepublishOnly": "npm run build"
20
+ },
21
+ "dependencies": {
22
+ "@lingxia/bridge": "0.5.1",
23
+ "@lingxia/elements": "0.5.1",
24
+ "@lingxia/page-runtime": "0.5.1"
25
+ },
26
+ "peerDependencies": {
27
+ "vue": ">=3.5.0"
28
+ },
29
+ "devDependencies": {
30
+ "@lingxia/bridge": "file:../lingxia-bridge",
31
+ "@lingxia/elements": "file:../lingxia-elements",
32
+ "@lingxia/page-runtime": "file:../lingxia-page-runtime",
33
+ "typescript": "^5.7.2",
34
+ "vue": "^3.5.0"
35
+ },
36
+ "files": [
37
+ "dist"
38
+ ],
39
+ "publishConfig": {
40
+ "registry": "https://registry.npmjs.org",
41
+ "access": "public"
42
+ },
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "https://github.com/LingXia-Dev/LingXia.git",
46
+ "directory": "packages/lingxia-vue"
47
+ },
48
+ "license": "MIT"
49
+ }