@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.
Files changed (209) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +29 -2
  3. package/dist/area/data.d.ts +10 -0
  4. package/dist/area/data.js +3899 -0
  5. package/dist/area/data.js.map +1 -0
  6. package/dist/area/index.d.ts +22 -0
  7. package/dist/area/index.js +41 -0
  8. package/dist/area/index.js.map +1 -0
  9. package/dist/base/Button.d.ts +170 -0
  10. package/dist/base/Button.js +537 -0
  11. package/dist/base/Button.js.map +1 -0
  12. package/dist/base/Icon.d.ts +38 -0
  13. package/dist/base/Icon.js +65 -0
  14. package/dist/base/Icon.js.map +1 -0
  15. package/dist/capability/PrivacyPrompt.d.ts +1 -0
  16. package/dist/capability/PrivacyPrompt.js +73 -0
  17. package/dist/capability/PrivacyPrompt.js.map +1 -0
  18. package/dist/capability/types.d.ts +92 -0
  19. package/dist/capability/usePermission.d.ts +23 -0
  20. package/dist/capability/usePermission.js +52 -0
  21. package/dist/capability/usePermission.js.map +1 -0
  22. package/dist/capability/useStorage.d.ts +8 -0
  23. package/dist/capability/useStorage.js +19 -0
  24. package/dist/capability/useStorage.js.map +1 -0
  25. package/dist/capability/web.d.ts +18 -0
  26. package/dist/capability/web.js +109 -0
  27. package/dist/capability/web.js.map +1 -0
  28. package/dist/capacitor/index.d.ts +9 -0
  29. package/dist/capacitor/index.js +143 -0
  30. package/dist/capacitor/index.js.map +1 -0
  31. package/dist/config/ConfigProvider.d.ts +37 -0
  32. package/dist/config/ConfigProvider.js +64 -0
  33. package/dist/config/ConfigProvider.js.map +1 -0
  34. package/dist/config/context.d.ts +16 -0
  35. package/dist/config/context.js +20 -0
  36. package/dist/config/context.js.map +1 -0
  37. package/dist/config/lang.d.ts +85 -0
  38. package/dist/config/lang.js +138 -0
  39. package/dist/config/lang.js.map +1 -0
  40. package/dist/display/Badge.d.ts +39 -0
  41. package/dist/display/Badge.js +108 -0
  42. package/dist/display/Badge.js.map +1 -0
  43. package/dist/display/Cell.d.ts +92 -0
  44. package/dist/display/Cell.js +204 -0
  45. package/dist/display/Cell.js.map +1 -0
  46. package/dist/display/Collapse.d.ts +47 -0
  47. package/dist/display/Collapse.js +173 -0
  48. package/dist/display/Collapse.js.map +1 -0
  49. package/dist/display/Empty.d.ts +42 -0
  50. package/dist/display/Empty.js +79 -0
  51. package/dist/display/Empty.js.map +1 -0
  52. package/dist/display/Grid.d.ts +49 -0
  53. package/dist/display/Grid.js +136 -0
  54. package/dist/display/Grid.js.map +1 -0
  55. package/dist/display/Image.d.ts +70 -0
  56. package/dist/display/Image.js +138 -0
  57. package/dist/display/Image.js.map +1 -0
  58. package/dist/display/IndexBar.d.ts +34 -0
  59. package/dist/display/IndexBar.js +158 -0
  60. package/dist/display/IndexBar.js.map +1 -0
  61. package/dist/display/List.d.ts +34 -0
  62. package/dist/display/List.js +105 -0
  63. package/dist/display/List.js.map +1 -0
  64. package/dist/display/NoticeBar.d.ts +57 -0
  65. package/dist/display/NoticeBar.js +172 -0
  66. package/dist/display/NoticeBar.js.map +1 -0
  67. package/dist/display/Steps.d.ts +59 -0
  68. package/dist/display/Steps.js +158 -0
  69. package/dist/display/Steps.js.map +1 -0
  70. package/dist/display/SwipeCell.d.ts +37 -0
  71. package/dist/display/SwipeCell.js +147 -0
  72. package/dist/display/SwipeCell.js.map +1 -0
  73. package/dist/display/Swiper.d.ts +47 -0
  74. package/dist/display/Swiper.js +173 -0
  75. package/dist/display/Swiper.js.map +1 -0
  76. package/dist/display/Tag.d.ts +99 -0
  77. package/dist/display/Tag.js +320 -0
  78. package/dist/display/Tag.js.map +1 -0
  79. package/dist/feedback/Dialog.d.ts +79 -0
  80. package/dist/feedback/Dialog.js +186 -0
  81. package/dist/feedback/Dialog.js.map +1 -0
  82. package/dist/feedback/FeedbackOutlet.d.ts +2 -0
  83. package/dist/feedback/FeedbackOutlet.js +31 -0
  84. package/dist/feedback/FeedbackOutlet.js.map +1 -0
  85. package/dist/feedback/Loading.d.ts +26 -0
  86. package/dist/feedback/Loading.js +68 -0
  87. package/dist/feedback/Loading.js.map +1 -0
  88. package/dist/feedback/Notify.d.ts +48 -0
  89. package/dist/feedback/Notify.js +116 -0
  90. package/dist/feedback/Notify.js.map +1 -0
  91. package/dist/feedback/PullRefresh.d.ts +58 -0
  92. package/dist/feedback/PullRefresh.js +178 -0
  93. package/dist/feedback/PullRefresh.js.map +1 -0
  94. package/dist/feedback/Skeleton.d.ts +56 -0
  95. package/dist/feedback/Skeleton.js +99 -0
  96. package/dist/feedback/Skeleton.js.map +1 -0
  97. package/dist/feedback/Toast.d.ts +89 -0
  98. package/dist/feedback/Toast.js +186 -0
  99. package/dist/feedback/Toast.js.map +1 -0
  100. package/dist/form/Checkbox.d.ts +52 -0
  101. package/dist/form/Checkbox.js +107 -0
  102. package/dist/form/Checkbox.js.map +1 -0
  103. package/dist/form/Field.d.ts +91 -0
  104. package/dist/form/Field.js +296 -0
  105. package/dist/form/Field.js.map +1 -0
  106. package/dist/form/Form.d.ts +50 -0
  107. package/dist/form/Form.js +96 -0
  108. package/dist/form/Form.js.map +1 -0
  109. package/dist/form/FormItem.d.ts +57 -0
  110. package/dist/form/FormItem.js +142 -0
  111. package/dist/form/FormItem.js.map +1 -0
  112. package/dist/form/Radio.d.ts +41 -0
  113. package/dist/form/Radio.js +85 -0
  114. package/dist/form/Radio.js.map +1 -0
  115. package/dist/form/Rate.d.ts +45 -0
  116. package/dist/form/Rate.js +162 -0
  117. package/dist/form/Rate.js.map +1 -0
  118. package/dist/form/Search.d.ts +50 -0
  119. package/dist/form/Search.js +152 -0
  120. package/dist/form/Search.js.map +1 -0
  121. package/dist/form/Slider.d.ts +43 -0
  122. package/dist/form/Slider.js +213 -0
  123. package/dist/form/Slider.js.map +1 -0
  124. package/dist/form/Stepper.d.ts +61 -0
  125. package/dist/form/Stepper.js +222 -0
  126. package/dist/form/Stepper.js.map +1 -0
  127. package/dist/form/Switch.d.ts +33 -0
  128. package/dist/form/Switch.js +116 -0
  129. package/dist/form/Switch.js.map +1 -0
  130. package/dist/form/Uploader.d.ts +67 -0
  131. package/dist/form/Uploader.js +207 -0
  132. package/dist/form/Uploader.js.map +1 -0
  133. package/dist/form/choice.d.ts +42 -0
  134. package/dist/form/choice.js +109 -0
  135. package/dist/form/choice.js.map +1 -0
  136. package/dist/form/context.d.ts +83 -0
  137. package/dist/form/context.js +89 -0
  138. package/dist/form/context.js.map +1 -0
  139. package/dist/index.d.ts +66 -0
  140. package/dist/index.js +59 -0
  141. package/dist/layout/Divider.d.ts +34 -0
  142. package/dist/layout/Divider.js +55 -0
  143. package/dist/layout/Divider.js.map +1 -0
  144. package/dist/layout/Space.d.ts +66 -0
  145. package/dist/layout/Space.js +65 -0
  146. package/dist/layout/Space.js.map +1 -0
  147. package/dist/navigation/BackTop.d.ts +29 -0
  148. package/dist/navigation/BackTop.js +89 -0
  149. package/dist/navigation/BackTop.js.map +1 -0
  150. package/dist/navigation/NavBar.d.ts +46 -0
  151. package/dist/navigation/NavBar.js +141 -0
  152. package/dist/navigation/NavBar.js.map +1 -0
  153. package/dist/navigation/Sticky.d.ts +19 -0
  154. package/dist/navigation/Sticky.js +28 -0
  155. package/dist/navigation/Sticky.js.map +1 -0
  156. package/dist/navigation/TabBar.d.ts +54 -0
  157. package/dist/navigation/TabBar.js +138 -0
  158. package/dist/navigation/TabBar.js.map +1 -0
  159. package/dist/navigation/Tabs.d.ts +45 -0
  160. package/dist/navigation/Tabs.js +206 -0
  161. package/dist/navigation/Tabs.js.map +1 -0
  162. package/dist/overlay/ActionSheet.d.ts +80 -0
  163. package/dist/overlay/ActionSheet.js +164 -0
  164. package/dist/overlay/ActionSheet.js.map +1 -0
  165. package/dist/overlay/Overlay.d.ts +35 -0
  166. package/dist/overlay/Overlay.js +85 -0
  167. package/dist/overlay/Overlay.js.map +1 -0
  168. package/dist/overlay/Popup.d.ts +76 -0
  169. package/dist/overlay/Popup.js +191 -0
  170. package/dist/overlay/Popup.js.map +1 -0
  171. package/dist/picker/Calendar.d.ts +69 -0
  172. package/dist/picker/Calendar.js +213 -0
  173. package/dist/picker/Calendar.js.map +1 -0
  174. package/dist/picker/Cascader.d.ts +50 -0
  175. package/dist/picker/Cascader.js +199 -0
  176. package/dist/picker/Cascader.js.map +1 -0
  177. package/dist/picker/DatePicker.d.ts +52 -0
  178. package/dist/picker/DatePicker.js +135 -0
  179. package/dist/picker/DatePicker.js.map +1 -0
  180. package/dist/picker/DropdownMenu.d.ts +39 -0
  181. package/dist/picker/DropdownMenu.js +242 -0
  182. package/dist/picker/DropdownMenu.js.map +1 -0
  183. package/dist/picker/Picker.d.ts +67 -0
  184. package/dist/picker/Picker.js +185 -0
  185. package/dist/picker/Picker.js.map +1 -0
  186. package/dist/picker/PickerColumn.d.ts +36 -0
  187. package/dist/picker/PickerColumn.js +175 -0
  188. package/dist/picker/PickerColumn.js.map +1 -0
  189. package/dist/theme/dark.d.ts +121 -0
  190. package/dist/theme/dark.js +77 -0
  191. package/dist/theme/dark.js.map +1 -0
  192. package/dist/theme/divider.d.ts +6 -0
  193. package/dist/theme/divider.js +18 -0
  194. package/dist/theme/divider.js.map +1 -0
  195. package/dist/theme/token-names.d.ts +1389 -0
  196. package/dist/theme/tokens.d.ts +549 -0
  197. package/dist/theme/tokens.js +312 -0
  198. package/dist/theme/tokens.js.map +1 -0
  199. package/dist/utils/host.d.ts +97 -0
  200. package/dist/utils/host.js +98 -0
  201. package/dist/utils/host.js.map +1 -0
  202. package/dist/utils/presence.d.ts +11 -0
  203. package/dist/utils/presence.js +34 -0
  204. package/dist/utils/presence.js.map +1 -0
  205. package/dist/utils/props.d.ts +38 -0
  206. package/dist/utils/props.js +115 -0
  207. package/dist/utils/props.js.map +1 -0
  208. package/dist/utils/scales.d.ts +48 -0
  209. 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