@zerodep-dev/vue-tsx-pro-mobile 0.0.0-stage → 0.4.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/LICENSE +21 -0
- package/README.md +29 -2
- package/dist/area/data.d.ts +10 -0
- package/dist/area/data.js +3899 -0
- package/dist/area/data.js.map +1 -0
- package/dist/area/index.d.ts +22 -0
- package/dist/area/index.js +41 -0
- package/dist/area/index.js.map +1 -0
- package/dist/base/Button.d.ts +170 -0
- package/dist/base/Button.js +537 -0
- package/dist/base/Button.js.map +1 -0
- package/dist/base/Icon.d.ts +38 -0
- package/dist/base/Icon.js +65 -0
- package/dist/base/Icon.js.map +1 -0
- package/dist/capability/PrivacyPrompt.d.ts +1 -0
- package/dist/capability/PrivacyPrompt.js +73 -0
- package/dist/capability/PrivacyPrompt.js.map +1 -0
- package/dist/capability/types.d.ts +92 -0
- package/dist/capability/usePermission.d.ts +23 -0
- package/dist/capability/usePermission.js +52 -0
- package/dist/capability/usePermission.js.map +1 -0
- package/dist/capability/useStorage.d.ts +8 -0
- package/dist/capability/useStorage.js +19 -0
- package/dist/capability/useStorage.js.map +1 -0
- package/dist/capability/web.d.ts +18 -0
- package/dist/capability/web.js +109 -0
- package/dist/capability/web.js.map +1 -0
- package/dist/capacitor/index.d.ts +9 -0
- package/dist/capacitor/index.js +143 -0
- package/dist/capacitor/index.js.map +1 -0
- package/dist/config/ConfigProvider.d.ts +37 -0
- package/dist/config/ConfigProvider.js +64 -0
- package/dist/config/ConfigProvider.js.map +1 -0
- package/dist/config/context.d.ts +16 -0
- package/dist/config/context.js +20 -0
- package/dist/config/context.js.map +1 -0
- package/dist/config/lang.d.ts +85 -0
- package/dist/config/lang.js +138 -0
- package/dist/config/lang.js.map +1 -0
- package/dist/display/Badge.d.ts +39 -0
- package/dist/display/Badge.js +108 -0
- package/dist/display/Badge.js.map +1 -0
- package/dist/display/Cell.d.ts +92 -0
- package/dist/display/Cell.js +204 -0
- package/dist/display/Cell.js.map +1 -0
- package/dist/display/Collapse.d.ts +47 -0
- package/dist/display/Collapse.js +173 -0
- package/dist/display/Collapse.js.map +1 -0
- package/dist/display/Empty.d.ts +42 -0
- package/dist/display/Empty.js +79 -0
- package/dist/display/Empty.js.map +1 -0
- package/dist/display/Grid.d.ts +49 -0
- package/dist/display/Grid.js +136 -0
- package/dist/display/Grid.js.map +1 -0
- package/dist/display/Image.d.ts +70 -0
- package/dist/display/Image.js +138 -0
- package/dist/display/Image.js.map +1 -0
- package/dist/display/IndexBar.d.ts +34 -0
- package/dist/display/IndexBar.js +158 -0
- package/dist/display/IndexBar.js.map +1 -0
- package/dist/display/List.d.ts +34 -0
- package/dist/display/List.js +105 -0
- package/dist/display/List.js.map +1 -0
- package/dist/display/NoticeBar.d.ts +57 -0
- package/dist/display/NoticeBar.js +172 -0
- package/dist/display/NoticeBar.js.map +1 -0
- package/dist/display/Steps.d.ts +59 -0
- package/dist/display/Steps.js +158 -0
- package/dist/display/Steps.js.map +1 -0
- package/dist/display/SwipeCell.d.ts +37 -0
- package/dist/display/SwipeCell.js +147 -0
- package/dist/display/SwipeCell.js.map +1 -0
- package/dist/display/Swiper.d.ts +47 -0
- package/dist/display/Swiper.js +173 -0
- package/dist/display/Swiper.js.map +1 -0
- package/dist/display/Tag.d.ts +99 -0
- package/dist/display/Tag.js +320 -0
- package/dist/display/Tag.js.map +1 -0
- package/dist/feedback/Dialog.d.ts +79 -0
- package/dist/feedback/Dialog.js +186 -0
- package/dist/feedback/Dialog.js.map +1 -0
- package/dist/feedback/FeedbackOutlet.d.ts +2 -0
- package/dist/feedback/FeedbackOutlet.js +31 -0
- package/dist/feedback/FeedbackOutlet.js.map +1 -0
- package/dist/feedback/Loading.d.ts +26 -0
- package/dist/feedback/Loading.js +68 -0
- package/dist/feedback/Loading.js.map +1 -0
- package/dist/feedback/Notify.d.ts +48 -0
- package/dist/feedback/Notify.js +116 -0
- package/dist/feedback/Notify.js.map +1 -0
- package/dist/feedback/PullRefresh.d.ts +58 -0
- package/dist/feedback/PullRefresh.js +178 -0
- package/dist/feedback/PullRefresh.js.map +1 -0
- package/dist/feedback/Skeleton.d.ts +56 -0
- package/dist/feedback/Skeleton.js +99 -0
- package/dist/feedback/Skeleton.js.map +1 -0
- package/dist/feedback/Toast.d.ts +89 -0
- package/dist/feedback/Toast.js +186 -0
- package/dist/feedback/Toast.js.map +1 -0
- package/dist/form/Checkbox.d.ts +52 -0
- package/dist/form/Checkbox.js +107 -0
- package/dist/form/Checkbox.js.map +1 -0
- package/dist/form/Field.d.ts +91 -0
- package/dist/form/Field.js +296 -0
- package/dist/form/Field.js.map +1 -0
- package/dist/form/Form.d.ts +50 -0
- package/dist/form/Form.js +96 -0
- package/dist/form/Form.js.map +1 -0
- package/dist/form/FormItem.d.ts +57 -0
- package/dist/form/FormItem.js +142 -0
- package/dist/form/FormItem.js.map +1 -0
- package/dist/form/Radio.d.ts +41 -0
- package/dist/form/Radio.js +85 -0
- package/dist/form/Radio.js.map +1 -0
- package/dist/form/Rate.d.ts +45 -0
- package/dist/form/Rate.js +162 -0
- package/dist/form/Rate.js.map +1 -0
- package/dist/form/Search.d.ts +50 -0
- package/dist/form/Search.js +152 -0
- package/dist/form/Search.js.map +1 -0
- package/dist/form/Slider.d.ts +43 -0
- package/dist/form/Slider.js +213 -0
- package/dist/form/Slider.js.map +1 -0
- package/dist/form/Stepper.d.ts +61 -0
- package/dist/form/Stepper.js +222 -0
- package/dist/form/Stepper.js.map +1 -0
- package/dist/form/Switch.d.ts +33 -0
- package/dist/form/Switch.js +116 -0
- package/dist/form/Switch.js.map +1 -0
- package/dist/form/Uploader.d.ts +67 -0
- package/dist/form/Uploader.js +207 -0
- package/dist/form/Uploader.js.map +1 -0
- package/dist/form/choice.d.ts +42 -0
- package/dist/form/choice.js +109 -0
- package/dist/form/choice.js.map +1 -0
- package/dist/form/context.d.ts +83 -0
- package/dist/form/context.js +89 -0
- package/dist/form/context.js.map +1 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +59 -0
- package/dist/layout/Divider.d.ts +34 -0
- package/dist/layout/Divider.js +55 -0
- package/dist/layout/Divider.js.map +1 -0
- package/dist/layout/Space.d.ts +66 -0
- package/dist/layout/Space.js +65 -0
- package/dist/layout/Space.js.map +1 -0
- package/dist/navigation/BackTop.d.ts +29 -0
- package/dist/navigation/BackTop.js +89 -0
- package/dist/navigation/BackTop.js.map +1 -0
- package/dist/navigation/NavBar.d.ts +46 -0
- package/dist/navigation/NavBar.js +141 -0
- package/dist/navigation/NavBar.js.map +1 -0
- package/dist/navigation/Sticky.d.ts +19 -0
- package/dist/navigation/Sticky.js +28 -0
- package/dist/navigation/Sticky.js.map +1 -0
- package/dist/navigation/TabBar.d.ts +54 -0
- package/dist/navigation/TabBar.js +138 -0
- package/dist/navigation/TabBar.js.map +1 -0
- package/dist/navigation/Tabs.d.ts +45 -0
- package/dist/navigation/Tabs.js +206 -0
- package/dist/navigation/Tabs.js.map +1 -0
- package/dist/overlay/ActionSheet.d.ts +80 -0
- package/dist/overlay/ActionSheet.js +164 -0
- package/dist/overlay/ActionSheet.js.map +1 -0
- package/dist/overlay/Overlay.d.ts +35 -0
- package/dist/overlay/Overlay.js +85 -0
- package/dist/overlay/Overlay.js.map +1 -0
- package/dist/overlay/Popup.d.ts +76 -0
- package/dist/overlay/Popup.js +191 -0
- package/dist/overlay/Popup.js.map +1 -0
- package/dist/picker/Calendar.d.ts +69 -0
- package/dist/picker/Calendar.js +213 -0
- package/dist/picker/Calendar.js.map +1 -0
- package/dist/picker/Cascader.d.ts +50 -0
- package/dist/picker/Cascader.js +199 -0
- package/dist/picker/Cascader.js.map +1 -0
- package/dist/picker/DatePicker.d.ts +52 -0
- package/dist/picker/DatePicker.js +135 -0
- package/dist/picker/DatePicker.js.map +1 -0
- package/dist/picker/DropdownMenu.d.ts +39 -0
- package/dist/picker/DropdownMenu.js +242 -0
- package/dist/picker/DropdownMenu.js.map +1 -0
- package/dist/picker/Picker.d.ts +67 -0
- package/dist/picker/Picker.js +185 -0
- package/dist/picker/Picker.js.map +1 -0
- package/dist/picker/PickerColumn.d.ts +36 -0
- package/dist/picker/PickerColumn.js +175 -0
- package/dist/picker/PickerColumn.js.map +1 -0
- package/dist/theme/dark.d.ts +121 -0
- package/dist/theme/dark.js +77 -0
- package/dist/theme/dark.js.map +1 -0
- package/dist/theme/divider.d.ts +6 -0
- package/dist/theme/divider.js +18 -0
- package/dist/theme/divider.js.map +1 -0
- package/dist/theme/token-names.d.ts +1389 -0
- package/dist/theme/tokens.d.ts +549 -0
- package/dist/theme/tokens.js +312 -0
- package/dist/theme/tokens.js.map +1 -0
- package/dist/utils/host.d.ts +97 -0
- package/dist/utils/host.js +98 -0
- package/dist/utils/host.js.map +1 -0
- package/dist/utils/presence.d.ts +11 -0
- package/dist/utils/presence.js +34 -0
- package/dist/utils/presence.js.map +1 -0
- package/dist/utils/props.d.ts +38 -0
- package/dist/utils/props.js +115 -0
- package/dist/utils/props.js.map +1 -0
- package/dist/utils/scales.d.ts +48 -0
- package/package.json +106 -3
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { dataAttrs, mergeAttrs } from "../utils/props.js";
|
|
2
|
+
import { Icon } from "../base/Icon.js";
|
|
3
|
+
import { useHost } from "../utils/host.js";
|
|
4
|
+
import { useForm } from "./context.js";
|
|
5
|
+
import { css } from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
6
|
+
import { _component, bindable, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
7
|
+
import { createVNode } from "vue";
|
|
8
|
+
import { Star } from "@lucide/icons";
|
|
9
|
+
//#region src/form/Rate.tsx
|
|
10
|
+
/**
|
|
11
|
+
* 评分:一排星,点一颗或在上面滑动选分数,可以有半颗。只读时用来展示评分。
|
|
12
|
+
*
|
|
13
|
+
* <Rate _value={score} />
|
|
14
|
+
* <Rate _value={4.5} allowHalf readonly />
|
|
15
|
+
* <Rate _value={mood} icon={Heart} tone="danger" count={3} />
|
|
16
|
+
*/
|
|
17
|
+
/** 未声明的属性落到根元素(div)上 */
|
|
18
|
+
/** Rate 的样式:root、item、void、各色调、half */
|
|
19
|
+
var rateStyles = css.create({
|
|
20
|
+
root: {
|
|
21
|
+
display: "inline-flex",
|
|
22
|
+
alignItems: "center",
|
|
23
|
+
gap: "0.25em",
|
|
24
|
+
fontSize: "1.25em",
|
|
25
|
+
verticalAlign: "middle",
|
|
26
|
+
userSelect: "none",
|
|
27
|
+
cursor: {
|
|
28
|
+
default: "pointer",
|
|
29
|
+
"[data-readonly]": "default",
|
|
30
|
+
"[data-disabled]": "not-allowed"
|
|
31
|
+
},
|
|
32
|
+
opacity: {
|
|
33
|
+
default: 1,
|
|
34
|
+
"[data-disabled]": "_disabled"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
item: {
|
|
38
|
+
position: "relative",
|
|
39
|
+
display: "inline-flex"
|
|
40
|
+
},
|
|
41
|
+
void: { color: "_fillActive" },
|
|
42
|
+
warning: { color: "_warning" },
|
|
43
|
+
danger: { color: "_danger" },
|
|
44
|
+
accent: { color: "_accent" },
|
|
45
|
+
half: {
|
|
46
|
+
position: "absolute",
|
|
47
|
+
insetBlock: 0,
|
|
48
|
+
insetInlineStart: 0,
|
|
49
|
+
width: "50%",
|
|
50
|
+
overflow: "hidden"
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
/** 评分(div[role=slider] > 各颗星) */
|
|
54
|
+
var Rate = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
55
|
+
const value = bindable(_props, "_value", () => 0, _ctx.emit);
|
|
56
|
+
const count = readonlyProp(_props, "count", 5);
|
|
57
|
+
const allowHalf = readonlyProp(_props, "allowHalf", false);
|
|
58
|
+
const readonly = readonlyProp(_props, "readonly", false);
|
|
59
|
+
const disabled = readonlyProp(_props, "disabled");
|
|
60
|
+
const icon = readonlyProp(_props, "icon", () => Star);
|
|
61
|
+
const tone = readonlyProp(_props, "tone", "warning");
|
|
62
|
+
const onChange = readonlyProp(_props, "onChange");
|
|
63
|
+
const propsItem = readonlyProp(_props, "propsItem");
|
|
64
|
+
const rest = restProps(_props, _ctx, true);
|
|
65
|
+
const host = useHost();
|
|
66
|
+
const form = useForm();
|
|
67
|
+
const items = [];
|
|
68
|
+
let rects = [];
|
|
69
|
+
const isDisabled = () => disabled.value ?? form?.disabled ?? false;
|
|
70
|
+
const interactive = () => !readonly.value && !form?.readonly && !isDisabled();
|
|
71
|
+
const set = (next) => {
|
|
72
|
+
if (next === value.value) return;
|
|
73
|
+
value.value = next;
|
|
74
|
+
onChange.value?.(next);
|
|
75
|
+
};
|
|
76
|
+
/** 横坐标落在哪颗星的哪一半:分数 */
|
|
77
|
+
const scoreAt = (x) => {
|
|
78
|
+
let score = 0;
|
|
79
|
+
rects.forEach((rect, index) => {
|
|
80
|
+
if (!rect || x < rect.left) return;
|
|
81
|
+
score = allowHalf.value && x < rect.left + rect.width / 2 ? index + .5 : index + 1;
|
|
82
|
+
});
|
|
83
|
+
return score;
|
|
84
|
+
};
|
|
85
|
+
const measure = async () => {
|
|
86
|
+
rects = await Promise.all(items.slice(0, count.value).map((item) => host.measure(item)));
|
|
87
|
+
};
|
|
88
|
+
const pointX = (event) => {
|
|
89
|
+
const touch = event.changedTouches?.[0];
|
|
90
|
+
return touch ? touch.clientX : event.clientX;
|
|
91
|
+
};
|
|
92
|
+
return () => {
|
|
93
|
+
const off = isDisabled();
|
|
94
|
+
return createVNode("div", mergeAttrs({ sx: [rateStyles.root, rateStyles[tone.value]] }, rest, {
|
|
95
|
+
role: "slider",
|
|
96
|
+
tabindex: interactive() ? 0 : void 0,
|
|
97
|
+
"aria-valuemin": 0,
|
|
98
|
+
"aria-valuemax": count.value,
|
|
99
|
+
"aria-valuenow": value.value,
|
|
100
|
+
"aria-readonly": readonly.value ? "true" : void 0,
|
|
101
|
+
"aria-disabled": off ? "true" : void 0,
|
|
102
|
+
onTouchstart: () => interactive() && void measure(),
|
|
103
|
+
onTouchmove: (event) => {
|
|
104
|
+
if (!interactive() || !rects.length) return;
|
|
105
|
+
const score = scoreAt(pointX(event));
|
|
106
|
+
if (score > 0) set(score);
|
|
107
|
+
},
|
|
108
|
+
onClick: (event) => {
|
|
109
|
+
if (!interactive()) return;
|
|
110
|
+
const x = pointX(event);
|
|
111
|
+
const pick = () => {
|
|
112
|
+
const score = scoreAt(x);
|
|
113
|
+
set(score === value.value ? 0 : score);
|
|
114
|
+
rects = [];
|
|
115
|
+
};
|
|
116
|
+
if (rects.length) pick();
|
|
117
|
+
else measure().then(pick);
|
|
118
|
+
},
|
|
119
|
+
onKeydown: (event) => {
|
|
120
|
+
if (!interactive()) return;
|
|
121
|
+
const delta = allowHalf.value ? .5 : 1;
|
|
122
|
+
if (event.key === "ArrowRight" || event.key === "ArrowUp") set(Math.min(count.value, value.value + delta));
|
|
123
|
+
else if (event.key === "ArrowLeft" || event.key === "ArrowDown") set(Math.max(0, value.value - delta));
|
|
124
|
+
else return;
|
|
125
|
+
event.preventDefault();
|
|
126
|
+
},
|
|
127
|
+
...dataAttrs({
|
|
128
|
+
"data-touch-move": interactive() || void 0,
|
|
129
|
+
"data-readonly": readonly.value,
|
|
130
|
+
"data-disabled": off
|
|
131
|
+
})
|
|
132
|
+
}), [Array.from({ length: count.value }, (_, index) => {
|
|
133
|
+
const fill = Math.min(1, Math.max(0, value.value - index));
|
|
134
|
+
return createVNode("span", mergeJsxProps(mergeAttrs({ sx: rateStyles.item }, propsItem.value), { "ref": (node) => items[index] = node }), [createVNode(Icon, {
|
|
135
|
+
"icon": icon.value,
|
|
136
|
+
"filled": true,
|
|
137
|
+
"sx": fill === 1 ? void 0 : rateStyles.void
|
|
138
|
+
}, null), fill > 0 && fill < 1 && createVNode("span", mergeAttrs({ sx: rateStyles.half }), [createVNode(Icon, {
|
|
139
|
+
"icon": icon.value,
|
|
140
|
+
"filled": true
|
|
141
|
+
}, null)])]);
|
|
142
|
+
})]);
|
|
143
|
+
};
|
|
144
|
+
}, {
|
|
145
|
+
name: "Rate",
|
|
146
|
+
props: [
|
|
147
|
+
"_value",
|
|
148
|
+
"onUpdate:_value",
|
|
149
|
+
"count",
|
|
150
|
+
"allowHalf",
|
|
151
|
+
"readonly",
|
|
152
|
+
"disabled",
|
|
153
|
+
"icon",
|
|
154
|
+
"tone",
|
|
155
|
+
"onChange",
|
|
156
|
+
"propsItem"
|
|
157
|
+
]
|
|
158
|
+
});
|
|
159
|
+
//#endregion
|
|
160
|
+
export { Rate, rateStyles };
|
|
161
|
+
|
|
162
|
+
//# sourceMappingURL=Rate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Rate.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_restProps","_createVNode","_mergeProps"],"sources":["../../src/form/Rate.tsx"],"sourcesContent":["/**\n * 评分:一排星,点一颗或在上面滑动选分数,可以有半颗。只读时用来展示评分。\n *\n * <Rate _value={score} />\n * <Rate _value={4.5} allowHalf readonly />\n * <Rate _value={mood} icon={Heart} tone=\"danger\" count={3} />\n */\nimport { Star } from '@lucide/icons';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { Icon, type IconData } from '../base/Icon';\nimport { useHost, type HostRect } from '../utils/host';\nimport { dataAttrs, mergeAttrs, type PartProps } from '../utils/props';\nimport { useForm } from './context';\n\n/** 未声明的属性落到根元素(div)上 */\nexport interface RateProps extends Omit<PartProps<'div', 'role' | 'tabindex'>, 'onChange'> {\n /** 分数;双向绑定写 `_value={x}` */\n _value?: number;\n /**\n * 几颗星\n * @default 5\n */\n count?: number;\n /** 可以选半颗 */\n allowHalf?: boolean;\n /** 只读:只展示 */\n readonly?: boolean;\n /** 禁用 */\n disabled?: boolean;\n /**\n * 图标\n * @default Star\n */\n icon?: IconData;\n /**\n * 选中部分的色调\n * @default 'warning'\n */\n tone?: 'warning' | 'danger' | 'accent';\n /** 分数变了 */\n onChange?: (value: number) => void;\n /** 每颗星的部件属性 */\n propsItem?: PartProps<'span'>;\n}\n\n/** Rate 的样式:root、item、void、各色调、half */\nexport const rateStyles = css.create({\n root: {\n display: 'inline-flex',\n alignItems: 'center',\n gap: '0.25em',\n fontSize: '1.25em',\n verticalAlign: 'middle',\n userSelect: 'none',\n cursor: { default: 'pointer', '[data-readonly]': 'default', '[data-disabled]': 'not-allowed' },\n opacity: { default: 1, '[data-disabled]': '_disabled' },\n },\n item: { position: 'relative', display: 'inline-flex' },\n void: { color: '_fillActive' },\n warning: { color: '_warning' },\n danger: { color: '_danger' },\n accent: { color: '_accent' },\n // 半颗:选中色的星叠在上面,只露出左半\n half: {\n position: 'absolute',\n insetBlock: 0,\n insetInlineStart: 0,\n width: '50%',\n overflow: 'hidden',\n },\n});\n\n/** 评分(div[role=slider] > 各颗星) */\nexport const Rate = _component(\n ({\n _value: value = _bindable(0),\n count = 5,\n allowHalf = false,\n readonly = false,\n disabled,\n icon = Star,\n tone = 'warning',\n onChange,\n propsItem,\n ...rest\n }: RateProps) => {\n const host = useHost();\n const form = useForm();\n const items: unknown[] = [];\n let rects: (HostRect | null)[] = [];\n const isDisabled = () => disabled ?? form?.disabled ?? false;\n const interactive = () => !readonly && !form?.readonly && !isDisabled();\n const set = (next: number) => {\n if (next === value) return;\n value = next;\n onChange?.(next);\n };\n /** 横坐标落在哪颗星的哪一半:分数 */\n const scoreAt = (x: number) => {\n let score = 0;\n rects.forEach((rect, index) => {\n if (!rect || x < rect.left) return;\n const half = allowHalf && x < rect.left + rect.width / 2;\n score = half ? index + 0.5 : index + 1;\n });\n return score;\n };\n const measure = async () => {\n rects = await Promise.all(items.slice(0, count).map((item) => host.measure(item)));\n };\n const pointX = (event: Event) => {\n const touch = (event as TouchEvent).changedTouches?.[0];\n return touch ? touch.clientX : (event as MouseEvent).clientX;\n };\n\n return () => {\n const off = isDisabled();\n return (\n <div\n {...mergeAttrs<'div'>({ sx: [rateStyles.root, rateStyles[tone]] }, rest, {\n role: 'slider',\n tabindex: interactive() ? 0 : undefined,\n 'aria-valuemin': 0,\n 'aria-valuemax': count,\n 'aria-valuenow': value,\n 'aria-readonly': readonly ? 'true' : undefined,\n 'aria-disabled': off ? 'true' : undefined,\n onTouchstart: () => interactive() && void measure(),\n onTouchmove: (event: TouchEvent) => {\n if (!interactive() || !rects.length) return;\n const score = scoreAt(pointX(event));\n // 滑到最左边也至少一颗:清零只能点同一颗(见 onClick)\n if (score > 0) set(score);\n },\n onClick: (event: Event) => {\n if (!interactive()) return;\n const x = pointX(event);\n const pick = () => {\n const score = scoreAt(x);\n // 再点一下当前的分数:清零\n set(score === value ? 0 : score);\n // 量到的位置只用这一次:页面滚动、布局变了下次重新量\n rects = [];\n };\n // 鼠标点的(没有按下时量过):先量\n if (rects.length) pick();\n else void measure().then(pick);\n },\n onKeydown: (event: KeyboardEvent) => {\n if (!interactive()) return;\n const delta = allowHalf ? 0.5 : 1;\n if (event.key === 'ArrowRight' || event.key === 'ArrowUp')\n set(Math.min(count, value + delta));\n else if (event.key === 'ArrowLeft' || event.key === 'ArrowDown')\n set(Math.max(0, value - delta));\n else return;\n event.preventDefault();\n },\n ...dataAttrs({\n // 小程序:收 touchmove(在星上滑动选分数)\n 'data-touch-move': interactive() || undefined,\n 'data-readonly': readonly,\n 'data-disabled': off,\n }),\n })}\n >\n {Array.from({ length: count }, (_, index) => {\n const fill = Math.min(1, Math.max(0, value - index));\n return (\n <span\n {...mergeAttrs<'span'>({ sx: rateStyles.item }, propsItem)}\n ref={(node: unknown) => (items[index] = node)}\n >\n <Icon icon={icon} filled sx={fill === 1 ? undefined : rateStyles.void} />\n {fill > 0 && fill < 1 && (\n <span {...mergeAttrs<'span'>({ sx: rateStyles.half })}>\n <Icon icon={icon} filled />\n </span>\n )}\n </span>\n );\n })}\n </div>\n );\n };\n },\n);\n"],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';
|
|
2
|
+
import type { VNodeChild } from 'vue';
|
|
3
|
+
import { type PartProps } from '../utils/props';
|
|
4
|
+
/** 输入框的属性由组件管理 */
|
|
5
|
+
export type SearchInputManaged = 'value' | 'type' | 'disabled' | 'placeholder' | 'enterkeyhint';
|
|
6
|
+
/** 未声明的属性落到根元素(div)上 */
|
|
7
|
+
export interface SearchProps extends Omit<PartProps<'div', 'role'>, 'onInput'> {
|
|
8
|
+
/** 搜索的文字;双向绑定写 `_value={x}` */
|
|
9
|
+
_value?: string;
|
|
10
|
+
/** 占位文字;不给时用语言包的“搜索” */
|
|
11
|
+
placeholder?: string;
|
|
12
|
+
/**
|
|
13
|
+
* 形状:方角或胶囊
|
|
14
|
+
* @default 'round'
|
|
15
|
+
*/
|
|
16
|
+
shape?: 'round' | 'square';
|
|
17
|
+
/**
|
|
18
|
+
* 有内容时显示清除按钮
|
|
19
|
+
* @default true
|
|
20
|
+
*/
|
|
21
|
+
clearable?: boolean;
|
|
22
|
+
/** 右边显示“取消” */
|
|
23
|
+
showAction?: boolean;
|
|
24
|
+
/** 右边的内容;不给时是语言包的“取消” */
|
|
25
|
+
action?: VNodeChild;
|
|
26
|
+
/** 禁用 */
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
/** 按了键盘的搜索键 */
|
|
29
|
+
onSearch?: (value: string) => void;
|
|
30
|
+
/** 点了取消:默认清空文字 */
|
|
31
|
+
onCancel?: () => void;
|
|
32
|
+
/** 点了清除按钮 */
|
|
33
|
+
onClear?: () => void;
|
|
34
|
+
/** 输入框的部件属性 */
|
|
35
|
+
propsInput?: PartProps<'input', SearchInputManaged>;
|
|
36
|
+
/** 灰底的输入区的部件属性 */
|
|
37
|
+
propsBox?: PartProps<'div'>;
|
|
38
|
+
/** 取消按钮的部件属性 */
|
|
39
|
+
propsAction?: PartProps<'button'>;
|
|
40
|
+
}
|
|
41
|
+
/** Search 的样式:root、box、square、icon、action */
|
|
42
|
+
export declare const searchStyles: Readonly<{
|
|
43
|
+
action: css.CompiledStyles;
|
|
44
|
+
box: css.CompiledStyles;
|
|
45
|
+
icon: css.CompiledStyles;
|
|
46
|
+
root: css.CompiledStyles;
|
|
47
|
+
square: css.CompiledStyles;
|
|
48
|
+
}>;
|
|
49
|
+
/** 搜索框(div > 输入区 + 取消) */
|
|
50
|
+
export declare const Search: import("@zerodep-dev/vue-tsx-pro-tsx").TsxComponent<SearchProps>;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { useConfig } from "../config/context.js";
|
|
2
|
+
import { dataAttrs, mergeAttrs } from "../utils/props.js";
|
|
3
|
+
import { Icon } from "../base/Icon.js";
|
|
4
|
+
import { fieldStyles } from "./Field.js";
|
|
5
|
+
import { css } from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
6
|
+
import { _component, bindable, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
7
|
+
import { createVNode } from "vue";
|
|
8
|
+
import { CircleX, Search } from "@lucide/icons";
|
|
9
|
+
//#region src/form/Search.tsx
|
|
10
|
+
/**
|
|
11
|
+
* 搜索框:圆角的灰底输入框,前面放大镜,有内容时可清除,右边可带“取消”。按键盘的搜索键时 onSearch。
|
|
12
|
+
*
|
|
13
|
+
* <Search _value={keyword} onSearch={(text) => load(text)} />
|
|
14
|
+
* <Search _value={keyword} showAction onCancel={() => back()} placeholder="搜索商品" />
|
|
15
|
+
*/
|
|
16
|
+
/** 输入框的属性由组件管理 */
|
|
17
|
+
/** 未声明的属性落到根元素(div)上 */
|
|
18
|
+
/** Search 的样式:root、box、square、icon、action */
|
|
19
|
+
var searchStyles = css.create({
|
|
20
|
+
root: {
|
|
21
|
+
boxSizing: "border-box",
|
|
22
|
+
display: "flex",
|
|
23
|
+
alignItems: "center",
|
|
24
|
+
gap: "_md",
|
|
25
|
+
paddingBlock: "0.55em",
|
|
26
|
+
paddingInline: "_md",
|
|
27
|
+
fontSize: "_md",
|
|
28
|
+
lineHeight: "1.5",
|
|
29
|
+
backgroundColor: "_surface"
|
|
30
|
+
},
|
|
31
|
+
box: {
|
|
32
|
+
flex: "1 1 auto",
|
|
33
|
+
minWidth: 0,
|
|
34
|
+
display: "flex",
|
|
35
|
+
alignItems: "center",
|
|
36
|
+
gap: "_sm",
|
|
37
|
+
height: "2.3em",
|
|
38
|
+
paddingInline: "0.8em",
|
|
39
|
+
borderRadius: "_full",
|
|
40
|
+
backgroundColor: "_fill",
|
|
41
|
+
opacity: {
|
|
42
|
+
default: 1,
|
|
43
|
+
"[data-disabled]": "_disabled"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
square: { borderRadius: "_sm" },
|
|
47
|
+
icon: {
|
|
48
|
+
flex: "none",
|
|
49
|
+
fontSize: "1.1em",
|
|
50
|
+
color: "_textMuted"
|
|
51
|
+
},
|
|
52
|
+
action: {
|
|
53
|
+
flex: "none",
|
|
54
|
+
padding: 0,
|
|
55
|
+
margin: 0,
|
|
56
|
+
borderWidth: 0,
|
|
57
|
+
backgroundColor: "transparent",
|
|
58
|
+
color: {
|
|
59
|
+
default: "_text",
|
|
60
|
+
":active": "_textMuted"
|
|
61
|
+
},
|
|
62
|
+
fontFamily: "inherit",
|
|
63
|
+
fontSize: "inherit",
|
|
64
|
+
lineHeight: "inherit",
|
|
65
|
+
cursor: "pointer"
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
/** 搜索框(div > 输入区 + 取消) */
|
|
69
|
+
var Search$1 = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
70
|
+
const _cache = [];
|
|
71
|
+
const value = bindable(_props, "_value", () => "", _ctx.emit);
|
|
72
|
+
const placeholder = readonlyProp(_props, "placeholder");
|
|
73
|
+
const shape = readonlyProp(_props, "shape", "round");
|
|
74
|
+
const clearable = readonlyProp(_props, "clearable", true);
|
|
75
|
+
const showAction = readonlyProp(_props, "showAction", false);
|
|
76
|
+
const action = readonlyProp(_props, "action");
|
|
77
|
+
const disabled = readonlyProp(_props, "disabled", false);
|
|
78
|
+
const onSearch = readonlyProp(_props, "onSearch");
|
|
79
|
+
const onCancel = readonlyProp(_props, "onCancel");
|
|
80
|
+
const onClear = readonlyProp(_props, "onClear");
|
|
81
|
+
const propsInput = readonlyProp(_props, "propsInput");
|
|
82
|
+
const propsBox = readonlyProp(_props, "propsBox");
|
|
83
|
+
const propsAction = readonlyProp(_props, "propsAction");
|
|
84
|
+
const rest = restProps(_props, _ctx, true);
|
|
85
|
+
const config = useConfig();
|
|
86
|
+
let input = null;
|
|
87
|
+
let composing = false;
|
|
88
|
+
return () => createVNode("div", mergeAttrs({ sx: searchStyles.root }, rest, { role: "search" }), [createVNode("div", mergeAttrs({ sx: [searchStyles.box, shape.value === "square" && searchStyles.square] }, propsBox.value, dataAttrs({ "data-disabled": disabled.value })), [
|
|
89
|
+
createVNode(Icon, {
|
|
90
|
+
"icon": Search,
|
|
91
|
+
"sx": searchStyles.icon
|
|
92
|
+
}, null),
|
|
93
|
+
createVNode("input", mergeJsxProps(mergeAttrs({ sx: fieldStyles.input }, propsInput.value, {
|
|
94
|
+
value: value.value,
|
|
95
|
+
type: "text",
|
|
96
|
+
enterkeyhint: "search",
|
|
97
|
+
disabled: disabled.value,
|
|
98
|
+
placeholder: placeholder.value ?? config.lang.search,
|
|
99
|
+
onInput: (event) => {
|
|
100
|
+
if (!composing) value.value = event.target.value;
|
|
101
|
+
},
|
|
102
|
+
onCompositionstart: () => composing = true,
|
|
103
|
+
onCompositionend: (event) => {
|
|
104
|
+
composing = false;
|
|
105
|
+
value.value = event.target.value;
|
|
106
|
+
},
|
|
107
|
+
onKeydown: (event) => {
|
|
108
|
+
if (event.key !== "Enter") return;
|
|
109
|
+
event.preventDefault();
|
|
110
|
+
onSearch.value?.(value.value);
|
|
111
|
+
}
|
|
112
|
+
}), { "ref": _cache[0] || (_cache[0] = (node) => input = node) }), null),
|
|
113
|
+
clearable.value && !disabled.value && value.value !== "" && createVNode("button", mergeJsxProps(mergeAttrs({ sx: fieldStyles.clear }), {
|
|
114
|
+
"type": "button",
|
|
115
|
+
"aria-label": config.lang.clear,
|
|
116
|
+
"onMousedown": _cache[1] || (_cache[1] = (event) => event.preventDefault()),
|
|
117
|
+
"onClick": _cache[2] || (_cache[2] = () => {
|
|
118
|
+
value.value = "";
|
|
119
|
+
onClear.value?.();
|
|
120
|
+
input?.focus();
|
|
121
|
+
})
|
|
122
|
+
}), [createVNode(Icon, { "icon": CircleX }, null)])
|
|
123
|
+
]), showAction.value && createVNode("button", mergeAttrs({ sx: searchStyles.action }, propsAction.value, {
|
|
124
|
+
type: "button",
|
|
125
|
+
onClick: () => {
|
|
126
|
+
value.value = "";
|
|
127
|
+
onCancel.value?.();
|
|
128
|
+
}
|
|
129
|
+
}), [action.value ?? config.lang.cancel])]);
|
|
130
|
+
}, {
|
|
131
|
+
name: "Search",
|
|
132
|
+
props: [
|
|
133
|
+
"_value",
|
|
134
|
+
"onUpdate:_value",
|
|
135
|
+
"placeholder",
|
|
136
|
+
"shape",
|
|
137
|
+
"clearable",
|
|
138
|
+
"showAction",
|
|
139
|
+
"action",
|
|
140
|
+
"disabled",
|
|
141
|
+
"onSearch",
|
|
142
|
+
"onCancel",
|
|
143
|
+
"onClear",
|
|
144
|
+
"propsInput",
|
|
145
|
+
"propsBox",
|
|
146
|
+
"propsAction"
|
|
147
|
+
]
|
|
148
|
+
});
|
|
149
|
+
//#endregion
|
|
150
|
+
export { Search$1 as Search, searchStyles };
|
|
151
|
+
|
|
152
|
+
//# sourceMappingURL=Search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Search.js","names":["Search","_vtp_bindable","_vtp_readonlyProp","_vtp_restProps","_createVNode","SearchIcon","_mergeProps"],"sources":["../../src/form/Search.tsx"],"sourcesContent":["/**\n * 搜索框:圆角的灰底输入框,前面放大镜,有内容时可清除,右边可带“取消”。按键盘的搜索键时 onSearch。\n *\n * <Search _value={keyword} onSearch={(text) => load(text)} />\n * <Search _value={keyword} showAction onCancel={() => back()} placeholder=\"搜索商品\" />\n */\nimport { CircleX, Search as SearchIcon } from '@lucide/icons';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { VNodeChild } from 'vue';\nimport { Icon } from '../base/Icon';\nimport { useConfig } from '../config/context';\nimport { dataAttrs, mergeAttrs, type PartProps } from '../utils/props';\nimport { fieldStyles } from './Field';\n\n/** 输入框的属性由组件管理 */\nexport type SearchInputManaged = 'value' | 'type' | 'disabled' | 'placeholder' | 'enterkeyhint';\n\n/** 未声明的属性落到根元素(div)上 */\nexport interface SearchProps extends Omit<PartProps<'div', 'role'>, 'onInput'> {\n /** 搜索的文字;双向绑定写 `_value={x}` */\n _value?: string;\n /** 占位文字;不给时用语言包的“搜索” */\n placeholder?: string;\n /**\n * 形状:方角或胶囊\n * @default 'round'\n */\n shape?: 'round' | 'square';\n /**\n * 有内容时显示清除按钮\n * @default true\n */\n clearable?: boolean;\n /** 右边显示“取消” */\n showAction?: boolean;\n /** 右边的内容;不给时是语言包的“取消” */\n action?: VNodeChild;\n /** 禁用 */\n disabled?: boolean;\n /** 按了键盘的搜索键 */\n onSearch?: (value: string) => void;\n /** 点了取消:默认清空文字 */\n onCancel?: () => void;\n /** 点了清除按钮 */\n onClear?: () => void;\n /** 输入框的部件属性 */\n propsInput?: PartProps<'input', SearchInputManaged>;\n /** 灰底的输入区的部件属性 */\n propsBox?: PartProps<'div'>;\n /** 取消按钮的部件属性 */\n propsAction?: PartProps<'button'>;\n}\n\n/** Search 的样式:root、box、square、icon、action */\nexport const searchStyles = css.create({\n root: {\n boxSizing: 'border-box',\n display: 'flex',\n alignItems: 'center',\n gap: '_md',\n paddingBlock: '0.55em',\n paddingInline: '_md',\n fontSize: '_md',\n lineHeight: '1.5',\n backgroundColor: '_surface',\n },\n box: {\n flex: '1 1 auto',\n minWidth: 0,\n display: 'flex',\n alignItems: 'center',\n gap: '_sm',\n height: '2.3em',\n paddingInline: '0.8em',\n borderRadius: '_full',\n backgroundColor: '_fill',\n opacity: { default: 1, '[data-disabled]': '_disabled' },\n },\n square: { borderRadius: '_sm' },\n icon: { flex: 'none', fontSize: '1.1em', color: '_textMuted' },\n action: {\n flex: 'none',\n padding: 0,\n margin: 0,\n borderWidth: 0,\n backgroundColor: 'transparent',\n color: { default: '_text', ':active': '_textMuted' },\n fontFamily: 'inherit',\n fontSize: 'inherit',\n lineHeight: 'inherit',\n cursor: 'pointer',\n },\n});\n\n/** 搜索框(div > 输入区 + 取消) */\nexport const Search = _component(\n ({\n _value: value = _bindable(''),\n placeholder,\n shape = 'round',\n clearable = true,\n showAction = false,\n action,\n disabled = false,\n onSearch,\n onCancel,\n onClear,\n propsInput,\n propsBox,\n propsAction,\n ...rest\n }: SearchProps) => {\n const config = useConfig();\n let input: HTMLInputElement | null = null;\n let composing = false;\n return () => (\n <div {...mergeAttrs<'div'>({ sx: searchStyles.root }, rest, { role: 'search' })}>\n <div\n {...mergeAttrs<'div'>(\n { sx: [searchStyles.box, shape === 'square' && searchStyles.square] },\n propsBox,\n dataAttrs({ 'data-disabled': disabled }),\n )}\n >\n <Icon icon={SearchIcon} sx={searchStyles.icon} />\n <input\n {...mergeAttrs<'input'>({ sx: fieldStyles.input }, propsInput, {\n value,\n // 不用 type=search:WebKit 会再画一个自带的清除按钮\n type: 'text',\n enterkeyhint: 'search',\n disabled,\n placeholder: placeholder ?? config.lang.search,\n onInput: (event: Event) => {\n if (!composing) value = (event.target as HTMLInputElement).value;\n },\n onCompositionstart: () => (composing = true),\n onCompositionend: (event: Event) => {\n composing = false;\n value = (event.target as HTMLInputElement).value;\n },\n onKeydown: (event: KeyboardEvent) => {\n if (event.key !== 'Enter') return;\n event.preventDefault();\n onSearch?.(value);\n },\n })}\n ref={(node: unknown) => (input = node as HTMLInputElement | null)}\n />\n {clearable && !disabled && value !== '' && (\n <button\n {...mergeAttrs<'button'>({ sx: fieldStyles.clear })}\n type=\"button\"\n aria-label={config.lang.clear}\n onMousedown={(event: Event) => event.preventDefault()}\n onClick={() => {\n value = '';\n onClear?.();\n input?.focus();\n }}\n >\n <Icon icon={CircleX} />\n </button>\n )}\n </div>\n {showAction && (\n <button\n {...mergeAttrs<'button'>({ sx: searchStyles.action }, propsAction, {\n type: 'button',\n onClick: () => {\n value = '';\n onCancel?.();\n },\n })}\n >\n {action ?? config.lang.cancel}\n </button>\n )}\n </div>\n );\n },\n);\n"],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';
|
|
2
|
+
import { type PartProps } from '../utils/props';
|
|
3
|
+
/** 一个数,或区间的两端 */
|
|
4
|
+
export type SliderValue = number | [number, number];
|
|
5
|
+
/** 未声明的属性落到根元素(div)上 */
|
|
6
|
+
export interface SliderProps extends Omit<PartProps<'div'>, 'onChange'> {
|
|
7
|
+
/** 当前的值;双向绑定写 `_value={x}`。数组时是区间 */
|
|
8
|
+
_value?: SliderValue;
|
|
9
|
+
/**
|
|
10
|
+
* 最小值
|
|
11
|
+
* @default 0
|
|
12
|
+
*/
|
|
13
|
+
min?: number;
|
|
14
|
+
/**
|
|
15
|
+
* 最大值
|
|
16
|
+
* @default 100
|
|
17
|
+
*/
|
|
18
|
+
max?: number;
|
|
19
|
+
/**
|
|
20
|
+
* 步长
|
|
21
|
+
* @default 1
|
|
22
|
+
*/
|
|
23
|
+
step?: number;
|
|
24
|
+
/** 禁用 */
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
/** 只读 */
|
|
27
|
+
readonly?: boolean;
|
|
28
|
+
/** 松手(或点了轨道、按了方向键)之后 */
|
|
29
|
+
onChange?: (value: SliderValue) => void;
|
|
30
|
+
/** 轨道的部件属性 */
|
|
31
|
+
propsTrack?: PartProps<'div'>;
|
|
32
|
+
/** 滑块(两个时都用)的部件属性 */
|
|
33
|
+
propsThumb?: PartProps<'div'>;
|
|
34
|
+
}
|
|
35
|
+
/** Slider 的样式:root、track、fill、thumb */
|
|
36
|
+
export declare const sliderStyles: Readonly<{
|
|
37
|
+
fill: css.CompiledStyles;
|
|
38
|
+
root: css.CompiledStyles;
|
|
39
|
+
thumb: css.CompiledStyles;
|
|
40
|
+
track: css.CompiledStyles;
|
|
41
|
+
}>;
|
|
42
|
+
/** 滑块(div > 轨道:已选的一段 + 滑块) */
|
|
43
|
+
export declare const Slider: import("@zerodep-dev/vue-tsx-pro-tsx").TsxComponent<SliderProps>;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { dataAttrs, mergeAttrs } from "../utils/props.js";
|
|
2
|
+
import { useHost } from "../utils/host.js";
|
|
3
|
+
import { useForm } from "./context.js";
|
|
4
|
+
import { css } from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
5
|
+
import { _component, bindable, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
6
|
+
import { createVNode, ref } from "vue";
|
|
7
|
+
//#region src/form/Slider.tsx
|
|
8
|
+
/** 一个数,或区间的两端 */
|
|
9
|
+
/** 未声明的属性落到根元素(div)上 */
|
|
10
|
+
/** Slider 的样式:root、track、fill、thumb */
|
|
11
|
+
var sliderStyles = css.create({
|
|
12
|
+
root: {
|
|
13
|
+
position: "relative",
|
|
14
|
+
display: "flex",
|
|
15
|
+
alignItems: "center",
|
|
16
|
+
height: "2em",
|
|
17
|
+
paddingInline: "0.75em",
|
|
18
|
+
touchAction: "pan-y",
|
|
19
|
+
userSelect: "none",
|
|
20
|
+
opacity: {
|
|
21
|
+
default: 1,
|
|
22
|
+
"[data-disabled]": "_disabled"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
track: {
|
|
26
|
+
position: "relative",
|
|
27
|
+
flex: "1 1 auto",
|
|
28
|
+
height: "0.25em",
|
|
29
|
+
borderRadius: "_full",
|
|
30
|
+
backgroundColor: "_fillActive"
|
|
31
|
+
},
|
|
32
|
+
fill: {
|
|
33
|
+
position: "absolute",
|
|
34
|
+
insetBlock: 0,
|
|
35
|
+
borderRadius: "_full",
|
|
36
|
+
backgroundColor: "_accent"
|
|
37
|
+
},
|
|
38
|
+
thumb: {
|
|
39
|
+
boxSizing: "border-box",
|
|
40
|
+
position: "absolute",
|
|
41
|
+
insetBlockStart: "50%",
|
|
42
|
+
width: "1.5em",
|
|
43
|
+
height: "1.5em",
|
|
44
|
+
borderRadius: "_full",
|
|
45
|
+
borderWidth: "_xs",
|
|
46
|
+
borderStyle: "solid",
|
|
47
|
+
borderColor: "_border",
|
|
48
|
+
backgroundColor: "#fff",
|
|
49
|
+
boxShadow: "_sm",
|
|
50
|
+
transform: "translate(-50%, -50%)",
|
|
51
|
+
cursor: {
|
|
52
|
+
default: "grab",
|
|
53
|
+
"[data-disabled]": "not-allowed"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
var LOCK_DISTANCE = 6;
|
|
58
|
+
/** 滑块(div > 轨道:已选的一段 + 滑块) */
|
|
59
|
+
var Slider = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
60
|
+
const _cache = [];
|
|
61
|
+
const value = bindable(_props, "_value", () => 0, _ctx.emit);
|
|
62
|
+
const min = readonlyProp(_props, "min", 0);
|
|
63
|
+
const max = readonlyProp(_props, "max", 100);
|
|
64
|
+
const step = readonlyProp(_props, "step", 1);
|
|
65
|
+
const disabled = readonlyProp(_props, "disabled");
|
|
66
|
+
const readonly = readonlyProp(_props, "readonly", false);
|
|
67
|
+
const onChange = readonlyProp(_props, "onChange");
|
|
68
|
+
const propsTrack = readonlyProp(_props, "propsTrack");
|
|
69
|
+
const propsThumb = readonlyProp(_props, "propsThumb");
|
|
70
|
+
const rest = restProps(_props, _ctx, true);
|
|
71
|
+
const host = useHost();
|
|
72
|
+
const form = useForm();
|
|
73
|
+
let track = null;
|
|
74
|
+
let rect = null;
|
|
75
|
+
let horizontal = ref(false);
|
|
76
|
+
let start = {
|
|
77
|
+
x: 0,
|
|
78
|
+
y: 0
|
|
79
|
+
};
|
|
80
|
+
let direction = null;
|
|
81
|
+
let active = 0;
|
|
82
|
+
const interactive = () => !(disabled.value ?? form?.disabled) && !readonly.value && !form?.readonly;
|
|
83
|
+
const ends = () => Array.isArray(value.value) ? value.value : [min.value, value.value];
|
|
84
|
+
const percent = (number) => (number - min.value) / (max.value - min.value || 1) * 100;
|
|
85
|
+
const snap = (number) => {
|
|
86
|
+
const stepped = Math.round((number - min.value) / step.value) * step.value + min.value;
|
|
87
|
+
return Number(Math.min(max.value, Math.max(min.value, stepped)).toFixed(10));
|
|
88
|
+
};
|
|
89
|
+
const valueAt = (x) => rect ? snap(min.value + (x - rect.left) / (rect.width || 1) * (max.value - min.value)) : null;
|
|
90
|
+
/** 把一端改成 next:区间两端交叉时换成拖另一端 */
|
|
91
|
+
const setEnd = (index, next) => {
|
|
92
|
+
if (!Array.isArray(value.value)) return void (value.value = next);
|
|
93
|
+
const pair = [...value.value];
|
|
94
|
+
pair[index] = next;
|
|
95
|
+
if (pair[0] > pair[1]) {
|
|
96
|
+
pair.reverse();
|
|
97
|
+
active = 1 - index;
|
|
98
|
+
}
|
|
99
|
+
value.value = pair;
|
|
100
|
+
};
|
|
101
|
+
const nearest = (number) => {
|
|
102
|
+
if (!Array.isArray(value.value)) return 0;
|
|
103
|
+
return Math.abs(number - value.value[0]) <= Math.abs(number - value.value[1]) ? 0 : 1;
|
|
104
|
+
};
|
|
105
|
+
const pointX = (event) => event.changedTouches?.[0]?.clientX ?? event.clientX;
|
|
106
|
+
const onTouchstart = (event) => {
|
|
107
|
+
if (!interactive()) return;
|
|
108
|
+
const touch = event.touches[0];
|
|
109
|
+
start = {
|
|
110
|
+
x: touch.clientX,
|
|
111
|
+
y: touch.clientY
|
|
112
|
+
};
|
|
113
|
+
direction = null;
|
|
114
|
+
host.measure(track).then((measured) => rect = measured);
|
|
115
|
+
};
|
|
116
|
+
const onTouchmove = (event) => {
|
|
117
|
+
if (!interactive() || !rect) return;
|
|
118
|
+
const touch = event.touches[0];
|
|
119
|
+
if (!direction) {
|
|
120
|
+
const dx = touch.clientX - start.x;
|
|
121
|
+
const dy = touch.clientY - start.y;
|
|
122
|
+
if (Math.abs(dx) < LOCK_DISTANCE && Math.abs(dy) < LOCK_DISTANCE) return;
|
|
123
|
+
direction = Math.abs(dx) > Math.abs(dy) ? "h" : "v";
|
|
124
|
+
horizontal.value = direction === "h";
|
|
125
|
+
if (horizontal.value) active = nearest(valueAt(start.x) ?? min.value);
|
|
126
|
+
}
|
|
127
|
+
if (direction !== "h") return;
|
|
128
|
+
const next = valueAt(touch.clientX);
|
|
129
|
+
if (next !== null) setEnd(active, next);
|
|
130
|
+
};
|
|
131
|
+
const onTouchend = () => {
|
|
132
|
+
if (horizontal.value) onChange.value?.(value.value);
|
|
133
|
+
horizontal.value = false;
|
|
134
|
+
};
|
|
135
|
+
const onClick = (event) => {
|
|
136
|
+
if (!interactive()) return;
|
|
137
|
+
const x = pointX(event);
|
|
138
|
+
host.measure(track).then((measured) => {
|
|
139
|
+
rect = measured;
|
|
140
|
+
const next = valueAt(x);
|
|
141
|
+
if (next === null) return;
|
|
142
|
+
active = nearest(next);
|
|
143
|
+
setEnd(active, next);
|
|
144
|
+
onChange.value?.(value.value);
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
const onKeydown = (index, event) => {
|
|
148
|
+
if (!interactive()) return;
|
|
149
|
+
const delta = {
|
|
150
|
+
ArrowRight: step.value,
|
|
151
|
+
ArrowUp: step.value,
|
|
152
|
+
ArrowLeft: -step.value,
|
|
153
|
+
ArrowDown: -step.value
|
|
154
|
+
}[event.key];
|
|
155
|
+
if (delta === void 0) return;
|
|
156
|
+
event.preventDefault();
|
|
157
|
+
setEnd(index, snap(ends()[index] + delta));
|
|
158
|
+
onChange.value?.(value.value);
|
|
159
|
+
};
|
|
160
|
+
return () => {
|
|
161
|
+
const off = !interactive();
|
|
162
|
+
const [low, high] = ends();
|
|
163
|
+
const range = Array.isArray(value.value);
|
|
164
|
+
const thumbs = range ? [low, high] : [high];
|
|
165
|
+
return createVNode("div", mergeAttrs({ sx: sliderStyles.root }, rest, {
|
|
166
|
+
onTouchstart,
|
|
167
|
+
onTouchmove,
|
|
168
|
+
onTouchend,
|
|
169
|
+
onTouchcancel: onTouchend,
|
|
170
|
+
onClick,
|
|
171
|
+
...dataAttrs({
|
|
172
|
+
"data-touch-move": horizontal.value ? "catch" : true,
|
|
173
|
+
"data-disabled": disabled.value ?? form?.disabled
|
|
174
|
+
})
|
|
175
|
+
}), [createVNode("div", mergeJsxProps(mergeAttrs({ sx: sliderStyles.track }, propsTrack.value), { "ref": _cache[0] || (_cache[0] = (node) => track = node) }), [createVNode("div", mergeAttrs({
|
|
176
|
+
sx: sliderStyles.fill,
|
|
177
|
+
style: {
|
|
178
|
+
insetInlineStart: `${range ? percent(low) : 0}%`,
|
|
179
|
+
width: `${percent(high) - (range ? percent(low) : 0)}%`
|
|
180
|
+
}
|
|
181
|
+
}), null), thumbs.map((number, index) => createVNode("div", mergeAttrs({
|
|
182
|
+
sx: sliderStyles.thumb,
|
|
183
|
+
style: { insetInlineStart: `${percent(number)}%` }
|
|
184
|
+
}, propsThumb.value, {
|
|
185
|
+
role: "slider",
|
|
186
|
+
tabindex: off ? void 0 : 0,
|
|
187
|
+
"aria-valuemin": min.value,
|
|
188
|
+
"aria-valuemax": max.value,
|
|
189
|
+
"aria-valuenow": number,
|
|
190
|
+
"aria-disabled": off ? "true" : void 0,
|
|
191
|
+
onKeydown: (event) => onKeydown(range ? index : 1, event),
|
|
192
|
+
...dataAttrs({ "data-disabled": off })
|
|
193
|
+
}), null))])]);
|
|
194
|
+
};
|
|
195
|
+
}, {
|
|
196
|
+
name: "Slider",
|
|
197
|
+
props: [
|
|
198
|
+
"_value",
|
|
199
|
+
"onUpdate:_value",
|
|
200
|
+
"min",
|
|
201
|
+
"max",
|
|
202
|
+
"step",
|
|
203
|
+
"disabled",
|
|
204
|
+
"readonly",
|
|
205
|
+
"onChange",
|
|
206
|
+
"propsTrack",
|
|
207
|
+
"propsThumb"
|
|
208
|
+
]
|
|
209
|
+
});
|
|
210
|
+
//#endregion
|
|
211
|
+
export { Slider, sliderStyles };
|
|
212
|
+
|
|
213
|
+
//# sourceMappingURL=Slider.js.map
|