zartui 3.1.3 → 3.1.5
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/README.md +25 -0
- package/es/date-time-picker/DateTimePickerWrapper.mjs +6 -4
- package/es/date-time-picker/utils.d.ts +1 -0
- package/es/date-time-picker/utils.mjs +11 -1
- package/es/index.d.ts +2 -1
- package/es/index.mjs +4 -1
- package/es/lazyload/vue-lazyload/index.d.ts +55 -55
- package/es/radio-picker/RadioPicker.d.ts +207 -0
- package/es/radio-picker/RadioPicker.mjs +290 -0
- package/es/radio-picker/RadioPickerColumn.d.ts +70 -0
- package/es/radio-picker/RadioPickerColumn.mjs +237 -0
- package/es/radio-picker/RadioPickerToolbar.d.ts +18 -0
- package/es/radio-picker/RadioPickerToolbar.mjs +54 -0
- package/es/radio-picker/index.css +1 -0
- package/es/radio-picker/index.d.ts +136 -0
- package/es/radio-picker/index.mjs +10 -0
- package/es/radio-picker/style/index.d.ts +1 -0
- package/es/radio-picker/style/index.mjs +16 -0
- package/es/radio-picker/types.d.ts +44 -0
- package/es/radio-picker/types.mjs +0 -0
- package/es/radio-picker/utils.d.ts +13 -0
- package/es/radio-picker/utils.mjs +80 -0
- package/es/utils/debounce.d.ts +46 -0
- package/es/utils/debounce.mjs +65 -0
- package/es/vue-sfc-shim.d.ts +6 -6
- package/es/vue-tsx-shim.d.ts +23 -23
- package/lib/date-time-picker/DateTimePickerWrapper.js +5 -3
- package/lib/date-time-picker/utils.d.ts +1 -0
- package/lib/date-time-picker/utils.js +10 -0
- package/lib/index.css +1 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.js +4 -1
- package/lib/lazyload/vue-lazyload/index.d.ts +55 -55
- package/lib/radio-picker/RadioPicker.d.ts +207 -0
- package/lib/radio-picker/RadioPicker.js +319 -0
- package/lib/radio-picker/RadioPickerColumn.d.ts +70 -0
- package/lib/radio-picker/RadioPickerColumn.js +266 -0
- package/lib/radio-picker/RadioPickerToolbar.d.ts +18 -0
- package/lib/radio-picker/RadioPickerToolbar.js +73 -0
- package/lib/radio-picker/index.css +1 -0
- package/lib/radio-picker/index.d.ts +136 -0
- package/lib/radio-picker/index.js +39 -0
- package/lib/radio-picker/style/index.d.ts +1 -0
- package/lib/radio-picker/style/index.js +16 -0
- package/lib/radio-picker/types.d.ts +44 -0
- package/lib/radio-picker/types.js +15 -0
- package/lib/radio-picker/utils.d.ts +13 -0
- package/lib/radio-picker/utils.js +99 -0
- package/lib/utils/debounce.d.ts +46 -0
- package/lib/utils/debounce.js +84 -0
- package/lib/vue-sfc-shim.d.ts +6 -6
- package/lib/vue-tsx-shim.d.ts +23 -23
- package/lib/web-types.json +1 -1
- package/lib/zartui.cjs.js +1676 -999
- package/lib/zartui.es.js +1676 -999
- package/lib/zartui.js +1682 -1005
- package/lib/zartui.min.js +1 -1
- package/package.json +67 -67
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { mergeProps as _mergeProps, createVNode as _createVNode } from "vue";
|
|
2
|
+
import { computed, defineComponent, nextTick, ref, watch } from "vue";
|
|
3
|
+
import { HAPTICS_FEEDBACK, extend, isSameValue, makeArrayProp, makeNumericProp, makeStringProp, pick, preventDefault, truthProp, unitToPx } from "../utils/index.mjs";
|
|
4
|
+
import { assignDefaultFields, bem, findOptionByValue, formatCascadeColumns, getColumnsType, getFirstEnabledOption, isOptionExist, name } from "./utils.mjs";
|
|
5
|
+
import { useChildren, useEventListener } from "@zartui/use";
|
|
6
|
+
import { useExpose } from "../composables/use-expose.mjs";
|
|
7
|
+
import { Divider } from "../divider/index.mjs";
|
|
8
|
+
import { Loading } from "../loading/index.mjs";
|
|
9
|
+
import Search from "../search/index.mjs";
|
|
10
|
+
import Column, { PICKER_KEY } from "./RadioPickerColumn.mjs";
|
|
11
|
+
import Toolbar, { pickerToolbarPropKeys, pickerToolbarProps, pickerToolbarSlots } from "./RadioPickerToolbar.mjs";
|
|
12
|
+
import Popup from "../popup/index.mjs";
|
|
13
|
+
import { useDebounceFn } from "../utils/debounce.mjs";
|
|
14
|
+
const RADIO_PICKER_TITLE_HEIGHT = 44;
|
|
15
|
+
const RADIO_PICKER_SEARCH_HEIGHT = 60;
|
|
16
|
+
const pickerSharedProps = extend({
|
|
17
|
+
loading: Boolean,
|
|
18
|
+
readonly: Boolean,
|
|
19
|
+
allowHtml: Boolean,
|
|
20
|
+
showToolbar: truthProp,
|
|
21
|
+
showTitle: truthProp,
|
|
22
|
+
swipeDuration: makeNumericProp(1e3),
|
|
23
|
+
title: makeStringProp("\u8BF7\u9009\u62E9")
|
|
24
|
+
}, pickerToolbarProps);
|
|
25
|
+
const radioPickerProps = extend({}, pickerSharedProps, {
|
|
26
|
+
columns: makeArrayProp(),
|
|
27
|
+
modelValue: makeArrayProp(),
|
|
28
|
+
columnsFieldNames: Object,
|
|
29
|
+
// 占满半屏,这里的高度是减去了title与底部按钮的内容高度
|
|
30
|
+
wrapHeight: makeNumericProp("40vh"),
|
|
31
|
+
showPicker: Boolean,
|
|
32
|
+
popup: truthProp,
|
|
33
|
+
searchable: Boolean,
|
|
34
|
+
autoSearch: truthProp,
|
|
35
|
+
searchPlaceholder: makeStringProp("\u8BF7\u641C\u7D22")
|
|
36
|
+
});
|
|
37
|
+
var stdin_default = defineComponent({
|
|
38
|
+
name,
|
|
39
|
+
props: radioPickerProps,
|
|
40
|
+
emits: ["confirm", "cancel", "change", "clickOption", "update:modelValue", "update:showPicker"],
|
|
41
|
+
setup(props, {
|
|
42
|
+
emit,
|
|
43
|
+
slots
|
|
44
|
+
}) {
|
|
45
|
+
const columnsRef = ref();
|
|
46
|
+
const selectedValues = ref(props.modelValue.slice(0));
|
|
47
|
+
const showPicker = ref(props.showPicker);
|
|
48
|
+
const {
|
|
49
|
+
children,
|
|
50
|
+
linkChildren
|
|
51
|
+
} = useChildren(PICKER_KEY);
|
|
52
|
+
const searchVal = ref("");
|
|
53
|
+
const autoSearchVal = ref("");
|
|
54
|
+
linkChildren();
|
|
55
|
+
const fields = computed(() => assignDefaultFields(props.columnsFieldNames));
|
|
56
|
+
const wrapHeight = computed(() => {
|
|
57
|
+
const basicContentHeight = unitToPx(props.wrapHeight);
|
|
58
|
+
let contentHeight = basicContentHeight;
|
|
59
|
+
if (props.showTitle) {
|
|
60
|
+
if (props.searchable) {
|
|
61
|
+
contentHeight = basicContentHeight - RADIO_PICKER_SEARCH_HEIGHT;
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
contentHeight = basicContentHeight + RADIO_PICKER_TITLE_HEIGHT;
|
|
65
|
+
if (props.searchable) {
|
|
66
|
+
contentHeight -= RADIO_PICKER_SEARCH_HEIGHT;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return contentHeight;
|
|
70
|
+
});
|
|
71
|
+
const columnsType = computed(() => getColumnsType(props.columns, fields.value));
|
|
72
|
+
const currentColumns = computed(() => {
|
|
73
|
+
const {
|
|
74
|
+
columns
|
|
75
|
+
} = props;
|
|
76
|
+
switch (columnsType.value) {
|
|
77
|
+
case "multiple":
|
|
78
|
+
return columns;
|
|
79
|
+
case "cascade":
|
|
80
|
+
return formatCascadeColumns(columns, fields.value, selectedValues);
|
|
81
|
+
default:
|
|
82
|
+
return [columns];
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
const hasOptions = computed(() => currentColumns.value.some((options) => options.length));
|
|
86
|
+
const selectedOptions = computed(() => currentColumns.value.map((options, index) => findOptionByValue(options, selectedValues.value[index], fields.value)));
|
|
87
|
+
watch(showPicker, (newValue) => {
|
|
88
|
+
if (newValue !== props.showPicker) {
|
|
89
|
+
emit("update:showPicker", newValue);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
watch(() => props.showPicker, (newValue) => {
|
|
93
|
+
if (newValue !== showPicker.value) {
|
|
94
|
+
showPicker.value = newValue;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
const setValue = (index, value) => {
|
|
98
|
+
if (selectedValues.value[index] !== value) {
|
|
99
|
+
const newValues = selectedValues.value.slice(0);
|
|
100
|
+
newValues[index] = value;
|
|
101
|
+
selectedValues.value = newValues;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
const getEventParams = () => ({
|
|
105
|
+
selectedValues: selectedValues.value.slice(0),
|
|
106
|
+
selectedOptions: selectedOptions.value
|
|
107
|
+
});
|
|
108
|
+
const onChange = (value, columnIndex) => {
|
|
109
|
+
setValue(columnIndex, value);
|
|
110
|
+
if (columnsType.value === "cascade") {
|
|
111
|
+
selectedValues.value.forEach((value2, index) => {
|
|
112
|
+
const options = currentColumns.value[index];
|
|
113
|
+
if (!isOptionExist(options, value2, fields.value)) {
|
|
114
|
+
setValue(index, options.length ? options[0][fields.value.value] : void 0);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
nextTick(() => {
|
|
119
|
+
emit("change", extend({
|
|
120
|
+
columnIndex
|
|
121
|
+
}, getEventParams()));
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
const onClickOption = (currentOption, columnIndex) => emit("clickOption", extend({
|
|
125
|
+
columnIndex,
|
|
126
|
+
currentOption
|
|
127
|
+
}, getEventParams()));
|
|
128
|
+
const confirm = () => {
|
|
129
|
+
children.forEach((child) => child.stopMomentum());
|
|
130
|
+
const params = getEventParams();
|
|
131
|
+
emit("confirm", params);
|
|
132
|
+
return params;
|
|
133
|
+
};
|
|
134
|
+
const cancel = () => {
|
|
135
|
+
emit("update:showPicker", false);
|
|
136
|
+
emit("cancel", getEventParams());
|
|
137
|
+
};
|
|
138
|
+
const updateShow = (value) => emit("update:showPicker", value);
|
|
139
|
+
const renderColumnItems = () => currentColumns.value.map((options, columnIndex) => _createVNode(Column, {
|
|
140
|
+
"value": selectedValues.value[columnIndex],
|
|
141
|
+
"fields": fields.value,
|
|
142
|
+
"options": options,
|
|
143
|
+
"readonly": props.readonly,
|
|
144
|
+
"allowHtml": props.allowHtml,
|
|
145
|
+
"wrapHeight": wrapHeight.value,
|
|
146
|
+
"swipeDuration": props.swipeDuration,
|
|
147
|
+
"autoSearchVal": autoSearchVal.value,
|
|
148
|
+
"onChange": (value) => onChange(value, columnIndex),
|
|
149
|
+
"onClickOption": (option) => onClickOption(option, columnIndex)
|
|
150
|
+
}, {
|
|
151
|
+
option: slots.option,
|
|
152
|
+
searchEmpty: slots["search-empty"]
|
|
153
|
+
}));
|
|
154
|
+
const renderMask = (maskHeight) => {
|
|
155
|
+
if (hasOptions.value) {
|
|
156
|
+
const maskStyle = {
|
|
157
|
+
backgroundSize: `100% ${(maskHeight - wrapHeight.value) / 2}px`
|
|
158
|
+
};
|
|
159
|
+
return [_createVNode("div", {
|
|
160
|
+
"class": bem("mask"),
|
|
161
|
+
"style": maskStyle
|
|
162
|
+
}, null)];
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
const handleSearchInput = () => {
|
|
166
|
+
if (props.autoSearch) {
|
|
167
|
+
return useDebounceFn((val) => {
|
|
168
|
+
searchVal.value = val;
|
|
169
|
+
autoSearchVal.value = searchVal.value;
|
|
170
|
+
}, 500);
|
|
171
|
+
}
|
|
172
|
+
return (val) => {
|
|
173
|
+
searchVal.value = val;
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
const handleSearch = () => {
|
|
177
|
+
if (!props.autoSearch) {
|
|
178
|
+
autoSearchVal.value = searchVal.value;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
const renderSearch = () => {
|
|
182
|
+
if (props.searchable) {
|
|
183
|
+
return _createVNode("div", {
|
|
184
|
+
"class": bem("search")
|
|
185
|
+
}, [_createVNode(Search, {
|
|
186
|
+
"show-action": !props.autoSearch,
|
|
187
|
+
"modelValue": searchVal.value,
|
|
188
|
+
"placeholder": props.searchPlaceholder,
|
|
189
|
+
"onUpdate:modelValue": handleSearchInput(),
|
|
190
|
+
"onSearch": handleSearch,
|
|
191
|
+
"onClear": handleSearch
|
|
192
|
+
}, null)]);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
const renderColumns = () => {
|
|
196
|
+
const columnsStyle = {
|
|
197
|
+
height: `${wrapHeight.value}px`
|
|
198
|
+
};
|
|
199
|
+
return _createVNode("div", {
|
|
200
|
+
"ref": columnsRef,
|
|
201
|
+
"class": bem("columns"),
|
|
202
|
+
"style": columnsStyle
|
|
203
|
+
}, [renderColumnItems(), renderMask(wrapHeight.value)]);
|
|
204
|
+
};
|
|
205
|
+
const renderToolbar = () => {
|
|
206
|
+
if (props.showToolbar) {
|
|
207
|
+
return _createVNode(Toolbar, _mergeProps(pick(props, pickerToolbarPropKeys), {
|
|
208
|
+
"onConfirm": confirm,
|
|
209
|
+
"onCancel": cancel
|
|
210
|
+
}), pick(slots, pickerToolbarSlots));
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
const renderTitleBar = () => {
|
|
214
|
+
if (props.showTitle) {
|
|
215
|
+
if (slots["title"]) {
|
|
216
|
+
return slots["title"]();
|
|
217
|
+
}
|
|
218
|
+
return _createVNode("div", {
|
|
219
|
+
"class": [bem("title"), HAPTICS_FEEDBACK]
|
|
220
|
+
}, [props.title]);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
const topDivider = () => props.showTitle ? _createVNode(Divider, {
|
|
224
|
+
"style": "margin: 0"
|
|
225
|
+
}, null) : null;
|
|
226
|
+
const buttomDivider = () => props.showToolbar ? _createVNode(Divider, {
|
|
227
|
+
"style": "margin: 0"
|
|
228
|
+
}, null) : null;
|
|
229
|
+
watch(currentColumns, (columns) => {
|
|
230
|
+
columns.forEach((options, index) => {
|
|
231
|
+
if (options.length && !isOptionExist(options, selectedValues.value[index], fields.value)) {
|
|
232
|
+
setValue(index, getFirstEnabledOption(options)[fields.value.value]);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}, {
|
|
236
|
+
immediate: true
|
|
237
|
+
});
|
|
238
|
+
let lastEmittedModelValue;
|
|
239
|
+
watch(() => props.modelValue, (newValues) => {
|
|
240
|
+
if (!isSameValue(newValues, selectedValues.value) && !isSameValue(newValues, lastEmittedModelValue)) {
|
|
241
|
+
selectedValues.value = newValues.slice(0);
|
|
242
|
+
lastEmittedModelValue = newValues.slice(0);
|
|
243
|
+
}
|
|
244
|
+
}, {
|
|
245
|
+
deep: true
|
|
246
|
+
});
|
|
247
|
+
watch(selectedValues, (newValues) => {
|
|
248
|
+
if (!isSameValue(newValues, props.modelValue)) {
|
|
249
|
+
lastEmittedModelValue = newValues.slice(0);
|
|
250
|
+
emit("update:modelValue", lastEmittedModelValue);
|
|
251
|
+
}
|
|
252
|
+
}, {
|
|
253
|
+
immediate: true
|
|
254
|
+
});
|
|
255
|
+
useEventListener("touchmove", preventDefault, {
|
|
256
|
+
target: columnsRef
|
|
257
|
+
});
|
|
258
|
+
const getSelectedOptions = () => selectedOptions.value;
|
|
259
|
+
useExpose({
|
|
260
|
+
confirm,
|
|
261
|
+
getSelectedOptions
|
|
262
|
+
});
|
|
263
|
+
const renderPicker = () => {
|
|
264
|
+
var _a, _b;
|
|
265
|
+
return _createVNode("div", {
|
|
266
|
+
"class": bem()
|
|
267
|
+
}, [renderTitleBar(), props.loading ? _createVNode(Loading, {
|
|
268
|
+
"class": bem("loading")
|
|
269
|
+
}, null) : null, topDivider(), renderSearch(), (_a = slots["columns-top"]) == null ? void 0 : _a.call(slots), renderColumns(), (_b = slots["columns-bottom"]) == null ? void 0 : _b.call(slots), buttomDivider(), renderToolbar()]);
|
|
270
|
+
};
|
|
271
|
+
return () => {
|
|
272
|
+
if (props.popup) {
|
|
273
|
+
return _createVNode(Popup, {
|
|
274
|
+
"show": showPicker.value,
|
|
275
|
+
"onUpdate:show": [($event) => showPicker.value = $event, updateShow],
|
|
276
|
+
"round": true,
|
|
277
|
+
"position": "bottom"
|
|
278
|
+
}, {
|
|
279
|
+
default: renderPicker
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
return renderPicker();
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
export {
|
|
287
|
+
stdin_default as default,
|
|
288
|
+
pickerSharedProps,
|
|
289
|
+
radioPickerProps
|
|
290
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { InjectionKey, PropType } from 'vue';
|
|
2
|
+
import type { PickerColumnProvide, PickerFieldNames, RadioPickerOption } from './types';
|
|
3
|
+
export declare const PICKER_KEY: InjectionKey<PickerColumnProvide>;
|
|
4
|
+
declare const _default: import("vue").DefineComponent<{
|
|
5
|
+
value: (NumberConstructor | StringConstructor)[];
|
|
6
|
+
unit: {
|
|
7
|
+
type: PropType<string>;
|
|
8
|
+
default: string;
|
|
9
|
+
};
|
|
10
|
+
fields: {
|
|
11
|
+
type: PropType<Required<PickerFieldNames>>;
|
|
12
|
+
required: true;
|
|
13
|
+
};
|
|
14
|
+
options: {
|
|
15
|
+
type: PropType<RadioPickerOption[]>;
|
|
16
|
+
default: () => RadioPickerOption[];
|
|
17
|
+
};
|
|
18
|
+
readonly: BooleanConstructor;
|
|
19
|
+
allowHtml: BooleanConstructor;
|
|
20
|
+
wrapHeight: {
|
|
21
|
+
type: NumberConstructor;
|
|
22
|
+
required: true;
|
|
23
|
+
};
|
|
24
|
+
swipeDuration: {
|
|
25
|
+
type: (NumberConstructor | StringConstructor)[];
|
|
26
|
+
required: true;
|
|
27
|
+
};
|
|
28
|
+
autoSearchVal: {
|
|
29
|
+
type: PropType<string>;
|
|
30
|
+
default: string;
|
|
31
|
+
};
|
|
32
|
+
}, () => JSX.Element, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("change" | "clickOption")[], "change" | "clickOption", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
|
|
33
|
+
value: (NumberConstructor | StringConstructor)[];
|
|
34
|
+
unit: {
|
|
35
|
+
type: PropType<string>;
|
|
36
|
+
default: string;
|
|
37
|
+
};
|
|
38
|
+
fields: {
|
|
39
|
+
type: PropType<Required<PickerFieldNames>>;
|
|
40
|
+
required: true;
|
|
41
|
+
};
|
|
42
|
+
options: {
|
|
43
|
+
type: PropType<RadioPickerOption[]>;
|
|
44
|
+
default: () => RadioPickerOption[];
|
|
45
|
+
};
|
|
46
|
+
readonly: BooleanConstructor;
|
|
47
|
+
allowHtml: BooleanConstructor;
|
|
48
|
+
wrapHeight: {
|
|
49
|
+
type: NumberConstructor;
|
|
50
|
+
required: true;
|
|
51
|
+
};
|
|
52
|
+
swipeDuration: {
|
|
53
|
+
type: (NumberConstructor | StringConstructor)[];
|
|
54
|
+
required: true;
|
|
55
|
+
};
|
|
56
|
+
autoSearchVal: {
|
|
57
|
+
type: PropType<string>;
|
|
58
|
+
default: string;
|
|
59
|
+
};
|
|
60
|
+
}>> & {
|
|
61
|
+
onChange?: ((...args: any[]) => any) | undefined;
|
|
62
|
+
onClickOption?: ((...args: any[]) => any) | undefined;
|
|
63
|
+
}, {
|
|
64
|
+
readonly: boolean;
|
|
65
|
+
options: RadioPickerOption[];
|
|
66
|
+
allowHtml: boolean;
|
|
67
|
+
unit: string;
|
|
68
|
+
autoSearchVal: string;
|
|
69
|
+
}, {}>;
|
|
70
|
+
export default _default;
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { createTextVNode as _createTextVNode, createVNode as _createVNode } from "vue";
|
|
2
|
+
import { computed, defineComponent, ref, watch } from "vue";
|
|
3
|
+
import RadioGroup from "../radio-group/RadioGroup.mjs";
|
|
4
|
+
import Radio from "../radio/Radio.mjs";
|
|
5
|
+
import { clamp, createNamespace, makeArrayProp, makeRequiredProp, makeStringProp, numericProp, preventDefault } from "../utils/index.mjs";
|
|
6
|
+
import { findIndexOfEnabledOption, getElementTranslateY } from "./utils.mjs";
|
|
7
|
+
import { useEventListener, useParent } from "@zartui/use";
|
|
8
|
+
import { useExpose } from "../composables/use-expose.mjs";
|
|
9
|
+
import { useTouch } from "../composables/use-touch.mjs";
|
|
10
|
+
import Empty from "../empty/Empty.mjs";
|
|
11
|
+
const DEFAULT_DURATION = 200;
|
|
12
|
+
const MOMENTUM_TIME = 300;
|
|
13
|
+
const MOMENTUM_DISTANCE = 15;
|
|
14
|
+
const [name, bem] = createNamespace("radio-picker-column");
|
|
15
|
+
const PICKER_KEY = Symbol(name);
|
|
16
|
+
var stdin_default = defineComponent({
|
|
17
|
+
name,
|
|
18
|
+
props: {
|
|
19
|
+
value: numericProp,
|
|
20
|
+
unit: makeStringProp(""),
|
|
21
|
+
fields: makeRequiredProp(Object),
|
|
22
|
+
options: makeArrayProp(),
|
|
23
|
+
readonly: Boolean,
|
|
24
|
+
allowHtml: Boolean,
|
|
25
|
+
wrapHeight: makeRequiredProp(Number),
|
|
26
|
+
swipeDuration: makeRequiredProp(numericProp),
|
|
27
|
+
autoSearchVal: makeStringProp("")
|
|
28
|
+
},
|
|
29
|
+
emits: ["change", "clickOption"],
|
|
30
|
+
setup(props, {
|
|
31
|
+
emit,
|
|
32
|
+
slots
|
|
33
|
+
}) {
|
|
34
|
+
let moving;
|
|
35
|
+
let startOffset;
|
|
36
|
+
let touchStartTime;
|
|
37
|
+
let momentumOffset;
|
|
38
|
+
let transitionEndTrigger;
|
|
39
|
+
const root = ref();
|
|
40
|
+
const wrapper = ref();
|
|
41
|
+
const currentOffset = ref(0);
|
|
42
|
+
const currentDuration = ref(0);
|
|
43
|
+
const touch = useTouch();
|
|
44
|
+
const baseOffset = () => 0;
|
|
45
|
+
const setRangeOffset = (distance) => {
|
|
46
|
+
const maxOffsetVal = wrapper.value.clientHeight - props.wrapHeight;
|
|
47
|
+
currentOffset.value = clamp(distance, -maxOffsetVal, 0);
|
|
48
|
+
};
|
|
49
|
+
const isSearchEmpty = computed(() => {
|
|
50
|
+
return !props.options.some((option) => {
|
|
51
|
+
const text = option[props.fields.text];
|
|
52
|
+
return text == null ? void 0 : text.includes(props.autoSearchVal);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
watch(() => props.autoSearchVal, () => {
|
|
56
|
+
currentOffset.value = baseOffset();
|
|
57
|
+
}, {
|
|
58
|
+
immediate: true
|
|
59
|
+
});
|
|
60
|
+
const updateValueByIndex = (index) => {
|
|
61
|
+
const enabledIndex = findIndexOfEnabledOption(props.options, index);
|
|
62
|
+
const trigger = () => {
|
|
63
|
+
const value = props.options[enabledIndex][props.fields.value];
|
|
64
|
+
if (value !== props.value) {
|
|
65
|
+
emit("change", value);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
if (moving) {
|
|
69
|
+
transitionEndTrigger = trigger;
|
|
70
|
+
} else {
|
|
71
|
+
trigger();
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const isReadonly = () => props.readonly || !props.options.length;
|
|
75
|
+
const onClickOption = (index) => {
|
|
76
|
+
if (moving || isReadonly()) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
transitionEndTrigger = null;
|
|
80
|
+
currentDuration.value = DEFAULT_DURATION;
|
|
81
|
+
updateValueByIndex(index);
|
|
82
|
+
emit("clickOption", props.options[index]);
|
|
83
|
+
};
|
|
84
|
+
const momentum = (distance, duration) => {
|
|
85
|
+
const speed = Math.abs(distance / duration);
|
|
86
|
+
distance = currentOffset.value + speed / 3e-3 * (distance < 0 ? -1 : 1);
|
|
87
|
+
setRangeOffset(distance);
|
|
88
|
+
currentDuration.value = +props.swipeDuration;
|
|
89
|
+
};
|
|
90
|
+
const stopMomentum = () => {
|
|
91
|
+
moving = false;
|
|
92
|
+
currentDuration.value = 0;
|
|
93
|
+
if (transitionEndTrigger) {
|
|
94
|
+
transitionEndTrigger();
|
|
95
|
+
transitionEndTrigger = null;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
const onTouchStart = (event) => {
|
|
99
|
+
if (isReadonly()) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
touch.start(event);
|
|
103
|
+
if (moving && wrapper.value) {
|
|
104
|
+
const translateY = getElementTranslateY(wrapper.value);
|
|
105
|
+
setRangeOffset(Math.min(0, translateY - baseOffset()));
|
|
106
|
+
}
|
|
107
|
+
currentDuration.value = 0;
|
|
108
|
+
startOffset = currentOffset.value;
|
|
109
|
+
touchStartTime = Date.now();
|
|
110
|
+
momentumOffset = startOffset;
|
|
111
|
+
transitionEndTrigger = null;
|
|
112
|
+
};
|
|
113
|
+
const onTouchMove = (event) => {
|
|
114
|
+
if (isReadonly()) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
touch.move(event);
|
|
118
|
+
if (touch.isVertical()) {
|
|
119
|
+
moving = true;
|
|
120
|
+
preventDefault(event, true);
|
|
121
|
+
}
|
|
122
|
+
setRangeOffset(startOffset + touch.deltaY.value);
|
|
123
|
+
const now = Date.now();
|
|
124
|
+
if (now - touchStartTime > MOMENTUM_TIME) {
|
|
125
|
+
touchStartTime = now;
|
|
126
|
+
momentumOffset = currentOffset.value;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const onTouchEnd = () => {
|
|
130
|
+
if (isReadonly()) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const distance = currentOffset.value - momentumOffset;
|
|
134
|
+
const duration = Date.now() - touchStartTime;
|
|
135
|
+
const startMomentum = duration < MOMENTUM_TIME && Math.abs(distance) > MOMENTUM_DISTANCE;
|
|
136
|
+
if (startMomentum) {
|
|
137
|
+
momentum(distance, duration);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
currentDuration.value = DEFAULT_DURATION;
|
|
141
|
+
setTimeout(() => {
|
|
142
|
+
moving = false;
|
|
143
|
+
}, 0);
|
|
144
|
+
};
|
|
145
|
+
const transSearchedText = (text) => {
|
|
146
|
+
if (!text)
|
|
147
|
+
return text;
|
|
148
|
+
const regex = new RegExp(props.autoSearchVal, "gi");
|
|
149
|
+
const replaced = text.replace(regex, "<span style='color: var(--zt-radio-picker-search-radio-label-match-color);'>$&</span>");
|
|
150
|
+
return replaced;
|
|
151
|
+
};
|
|
152
|
+
const renderOptions = () => {
|
|
153
|
+
return props.options.map((option, index) => {
|
|
154
|
+
let text = option[props.fields.text];
|
|
155
|
+
const {
|
|
156
|
+
disabled
|
|
157
|
+
} = option;
|
|
158
|
+
const value = option[props.fields.value];
|
|
159
|
+
const isSearchMatch = text == null ? void 0 : text.includes(props.autoSearchVal);
|
|
160
|
+
let isSearchedTrans = false;
|
|
161
|
+
if (isSearchMatch && props.autoSearchVal) {
|
|
162
|
+
text = transSearchedText(text);
|
|
163
|
+
isSearchedTrans = true;
|
|
164
|
+
}
|
|
165
|
+
const data = {
|
|
166
|
+
tabindex: disabled ? -1 : 0,
|
|
167
|
+
class: [bem("item", {
|
|
168
|
+
"not-match": !isSearchMatch
|
|
169
|
+
}), option.className],
|
|
170
|
+
disabled,
|
|
171
|
+
onClick: () => {
|
|
172
|
+
if (!disabled) {
|
|
173
|
+
return onClickOption(index);
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
name: value
|
|
177
|
+
};
|
|
178
|
+
const childData = {
|
|
179
|
+
[props.allowHtml || isSearchedTrans ? "innerHTML" : "textContent"]: text
|
|
180
|
+
};
|
|
181
|
+
return _createVNode(Radio, data, {
|
|
182
|
+
default: () => [slots.option ? slots.option({
|
|
183
|
+
option,
|
|
184
|
+
searchValue: props.autoSearchVal
|
|
185
|
+
}) : _createVNode("div", childData, null)]
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
};
|
|
189
|
+
useParent(PICKER_KEY);
|
|
190
|
+
useExpose({
|
|
191
|
+
stopMomentum
|
|
192
|
+
});
|
|
193
|
+
useEventListener("touchmove", onTouchMove, {
|
|
194
|
+
target: root
|
|
195
|
+
});
|
|
196
|
+
const renderSearchEmpty = () => {
|
|
197
|
+
var _a;
|
|
198
|
+
if (isSearchEmpty.value) {
|
|
199
|
+
if (slots.searchEmpty) {
|
|
200
|
+
return (_a = slots.searchEmpty) == null ? void 0 : _a.call(slots);
|
|
201
|
+
}
|
|
202
|
+
return _createVNode(Empty, {
|
|
203
|
+
"class": bem("search-empty"),
|
|
204
|
+
"image": "no-search-result"
|
|
205
|
+
}, {
|
|
206
|
+
description: () => {
|
|
207
|
+
return _createVNode("span", null, [_createTextVNode("\u6682\u65E0\u641C\u7D22\u7ED3\u679C")]);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
return () => _createVNode("div", {
|
|
213
|
+
"ref": root,
|
|
214
|
+
"class": bem(),
|
|
215
|
+
"onTouchstartPassive": onTouchStart,
|
|
216
|
+
"onTouchend": onTouchEnd,
|
|
217
|
+
"onTouchcancel": onTouchEnd
|
|
218
|
+
}, [_createVNode("ul", {
|
|
219
|
+
"ref": wrapper,
|
|
220
|
+
"style": {
|
|
221
|
+
transform: `translate3d(0, ${currentOffset.value + baseOffset()}px, 0)`,
|
|
222
|
+
transitionDuration: `${currentDuration.value}ms`,
|
|
223
|
+
transitionProperty: currentDuration.value ? "all" : "none"
|
|
224
|
+
},
|
|
225
|
+
"class": bem("wrapper"),
|
|
226
|
+
"onTransitionend": stopMomentum
|
|
227
|
+
}, [_createVNode(RadioGroup, {
|
|
228
|
+
"modelValue": props.value
|
|
229
|
+
}, {
|
|
230
|
+
default: () => [renderOptions()]
|
|
231
|
+
})]), renderSearchEmpty()]);
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
export {
|
|
235
|
+
PICKER_KEY,
|
|
236
|
+
stdin_default as default
|
|
237
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const pickerToolbarProps: {
|
|
2
|
+
cancelButtonText: StringConstructor;
|
|
3
|
+
confirmButtonText: StringConstructor;
|
|
4
|
+
};
|
|
5
|
+
export declare const pickerToolbarSlots: string[];
|
|
6
|
+
export type PickerToolbarPropKeys = Array<keyof typeof pickerToolbarProps>;
|
|
7
|
+
export declare const pickerToolbarPropKeys: PickerToolbarPropKeys;
|
|
8
|
+
declare const _default: import("vue").DefineComponent<{
|
|
9
|
+
cancelButtonText: StringConstructor;
|
|
10
|
+
confirmButtonText: StringConstructor;
|
|
11
|
+
}, () => JSX.Element, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("cancel" | "confirm")[], "cancel" | "confirm", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
|
|
12
|
+
cancelButtonText: StringConstructor;
|
|
13
|
+
confirmButtonText: StringConstructor;
|
|
14
|
+
}>> & {
|
|
15
|
+
onCancel?: ((...args: any[]) => any) | undefined;
|
|
16
|
+
onConfirm?: ((...args: any[]) => any) | undefined;
|
|
17
|
+
}, {}, {}>;
|
|
18
|
+
export default _default;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createVNode as _createVNode } from "vue";
|
|
2
|
+
import { defineComponent } from "vue";
|
|
3
|
+
import { Button } from "../button/index.mjs";
|
|
4
|
+
import { HAPTICS_FEEDBACK, createNamespace } from "../utils/index.mjs";
|
|
5
|
+
import { bem, t } from "./utils.mjs";
|
|
6
|
+
const [name] = createNamespace("picker-toolbar");
|
|
7
|
+
const pickerToolbarProps = {
|
|
8
|
+
cancelButtonText: String,
|
|
9
|
+
confirmButtonText: String
|
|
10
|
+
};
|
|
11
|
+
const pickerToolbarSlots = ["cancel", "confirm", "title", "toolbar"];
|
|
12
|
+
const pickerToolbarPropKeys = Object.keys(pickerToolbarProps);
|
|
13
|
+
var stdin_default = defineComponent({
|
|
14
|
+
name,
|
|
15
|
+
props: pickerToolbarProps,
|
|
16
|
+
emits: ["confirm", "cancel"],
|
|
17
|
+
setup(props, {
|
|
18
|
+
emit,
|
|
19
|
+
slots
|
|
20
|
+
}) {
|
|
21
|
+
const onCancel = () => emit("cancel");
|
|
22
|
+
const onConfirm = () => emit("confirm");
|
|
23
|
+
const renderCancel = () => {
|
|
24
|
+
const text = props.cancelButtonText || t("cancel");
|
|
25
|
+
return _createVNode(Button, {
|
|
26
|
+
"type": "default",
|
|
27
|
+
"hairline": true,
|
|
28
|
+
"class": bem("cancel"),
|
|
29
|
+
"onClick": onCancel
|
|
30
|
+
}, {
|
|
31
|
+
default: () => [slots.cancel ? slots.cancel() : text]
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
const renderConfirm = () => {
|
|
35
|
+
const text = props.confirmButtonText || t("confirm");
|
|
36
|
+
return _createVNode(Button, {
|
|
37
|
+
"type": "primary",
|
|
38
|
+
"class": [bem("confirm"), HAPTICS_FEEDBACK],
|
|
39
|
+
"onClick": onConfirm
|
|
40
|
+
}, {
|
|
41
|
+
default: () => [slots.confirm ? slots.confirm() : text]
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
return () => _createVNode("div", {
|
|
45
|
+
"class": bem("toolbar")
|
|
46
|
+
}, [slots.toolbar ? slots.toolbar() : [renderCancel(), renderConfirm()]]);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
export {
|
|
50
|
+
stdin_default as default,
|
|
51
|
+
pickerToolbarPropKeys,
|
|
52
|
+
pickerToolbarProps,
|
|
53
|
+
pickerToolbarSlots
|
|
54
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--zt-radio-picker-cancel-margin: 8px 4px 8px 16px;--zt-radio-picker-confirm-margin: 8px 16px 8px 4px;--zt-radio-picker-title-height: 44px;--zt-radio-picker-search-padding-top-bottom: 12px;--zt-radio-picker-search-radio-label-match-color: var(--zt-blue);--zt-radio-picker-title-border-radius: 16px 16px 0 0;--zt-radio-picker-frame-background-color: var(--zt-gray-a04);--zt-radio-picker-cancel-background-color: var(--zt-white);--zt-radio-picker-title-text-color: var(--zt-gray-a4);--zt-radio-picker-background: var(--zt-background-2);--zt-radio-picker-toolbar-height: 60px;--zt-radio-picker-title-font-size: var(--zt-font-size-md);--zt-radio-picker-title-background: var(--zt-white);--zt-radio-picker-title-line-height: var(--zt-line-height-md);--zt-radio-picker-action-padding: 0 var(--zt-padding-md);--zt-radio-picker-action-font-size: var(--zt-font-size-lg);--zt-radio-picker-confirm-action-color: var(--zt-white);--zt-radio-picker-cancel-action-color: var(--zt-text-color);--zt-radio-picker-option-font-size: var(--zt-font-size-lg);--zt-radio-picker-option-radio-label-margin: 8px;--zt-radio-picker-option-padding: 12px var(--zt-padding-md);--zt-radio-picker-option-text-color: var(--zt-text-color);--zt-radio-picker-option-disabled-opacity: .3;--zt-radio-picker-loading-icon-color: var(--zt-primary-color);--zt-radio-picker-loading-mask-color: rgba(255, 255, 255, .9);--zt-radio-picker-mask-color: linear-gradient(180deg, rgba(255, 255, 255, .9), rgba(255, 255, 255, .4)), linear-gradient(0deg, rgba(255, 255, 255, .9), rgba(255, 255, 255, .4));--zt-radio-picker-unit-font-size: var(--zt-font-size-md)}.zt-theme-dark{--zt-radio-picker-selected-background: rgba(255, 255, 255, .08);--zt-radio-picker-title-background: var(--zt-background-2);--zt-radio-picker-title-text-color: var(--zt-gray-default);--zt-radio-picker-mask-color: linear-gradient(180deg, rgba(0, 0, 0, .6), rgba(0, 0, 0, .1)), linear-gradient(0deg, rgba(0, 0, 0, .6), rgba(0, 0, 0, .1))}.zt-radio-picker{position:relative;background:var(--zt-radio-picker-background);-webkit-user-select:none;user-select:none}.zt-radio-picker__toolbar{display:flex;align-items:center;justify-content:space-between;height:var(--zt-radio-picker-toolbar-height)}.zt-radio-picker__cancel,.zt-radio-picker__confirm{width:50%}.zt-radio-picker__cancel{background:var(--zt-button-default-background);margin:var(--zt-radio-picker-cancel-margin)}.zt-radio-picker__confirm{margin:var(--zt-radio-picker-confirm-margin)}.zt-radio-picker__title{display:flex;justify-content:center;align-items:center;width:100%;height:var(--zt-radio-picker-title-height);border-radius:var(--zt-radio-picker-title-border-radius);font-size:var(--zt-radio-picker-title-font-size);background:var(--zt-radio-picker-title-background);color:var(--zt-radio-picker-title-text-color)}.zt-radio-picker__columns{position:relative;display:flex;cursor:-webkit-grab;cursor:grab}.zt-radio-picker__search{padding-top:var(--zt-radio-picker-search-padding-top-bottom);padding-bottom:var(--zt-radio-picker-search-padding-top-bottom)}.zt-radio-picker__search .zt-search{padding-top:0;padding-bottom:0}.zt-radio-picker__loading{position:absolute;top:0;right:0;bottom:0;left:0;z-index:3;display:flex;align-items:center;justify-content:center;color:var(--zt-radio-picker-loading-icon-color);background:var(--zt-radio-picker-loading-mask-color)}.zt-radio-picker__frame{position:absolute;top:50%;z-index:2;transform:translateY(-50%);pointer-events:none;width:100%;background:var(--zt-radio-picker-frame-background-color)}.zt-radio-picker__mask{position:absolute;top:0;left:0;z-index:1;width:100%;height:100%;background-image:var(--zt-radio-picker-mask-color);background-repeat:no-repeat;background-position:top,bottom;transform:translateZ(0);pointer-events:none}.zt-radio-picker-column{position:relative;flex:1;overflow:hidden;font-size:var(--zt-radio-picker-option-font-size);background:var(--zt-radio-picker-background)}.zt-radio-picker-column__wrapper{transition-timing-function:cubic-bezier(.23,1,.68,1)}.zt-radio-picker-column__item{display:flex;align-items:center;justify-content:flex-start;color:var(--zt-radio-picker-option-text-color)}.zt-radio-picker-column__item--not-match{display:none}.zt-radio-picker-column__item.zt-radio{padding:var(--zt-radio-picker-option-padding)}.zt-radio-picker-column__item.zt-radio:after{left:calc(var(--zt-radio-size) + var(--zt-radio-label-margin) + var(--zt-padding-md))}.zt-radio-picker-column__item.zt-radio .zt-radio__label{margin-left:var(--zt-radio-picker-option-radio-label-margin)}.zt-radio-picker-column__search-empty .zt-empty__description{padding:unset}
|