@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,135 @@
1
+ import { useConfig } from "../config/context.js";
2
+ import { Picker } from "./Picker.js";
3
+ import { _component, bindable, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
4
+ import { computed, createVNode } from "vue";
5
+ //#region src/picker/DatePicker.tsx
6
+ /** 日期的列 */
7
+ /** 共有的属性 */
8
+ /** 未声明的属性落到选择器的根元素上 */
9
+ var daysIn = (year, month) => new Date(year, month, 0).getDate();
10
+ var range = (from, to) => Array.from({ length: to - from + 1 }, (_, index) => from + index);
11
+ var pad = (value) => String(value).padStart(2, "0");
12
+ /** 日期选择器(Picker) */
13
+ var DatePicker = /*#__PURE__*/ _component((_props, _ctx) => {
14
+ const _cache = [];
15
+ const value = bindable(_props, "_value", () => void 0, _ctx.emit);
16
+ const minDate = readonlyProp(_props, "minDate");
17
+ const maxDate = readonlyProp(_props, "maxDate");
18
+ const columns = readonlyProp(_props, "columns", () => [
19
+ "year",
20
+ "month",
21
+ "day"
22
+ ]);
23
+ const onConfirm = readonlyProp(_props, "onConfirm");
24
+ const rest = restProps(_props, _ctx, true);
25
+ const config = useConfig();
26
+ const now = /* @__PURE__ */ new Date();
27
+ const min = computed(() => minDate.value ?? new Date(now.getFullYear() - 10, 0, 1));
28
+ const max = computed(() => maxDate.value ?? new Date(now.getFullYear() + 10, 11, 31));
29
+ /** 把日期限在范围内(只看年月日) */
30
+ const clamp = (date) => {
31
+ const day = new Date(date.getFullYear(), date.getMonth(), date.getDate());
32
+ if (day < min.value) return new Date(min.value.getFullYear(), min.value.getMonth(), min.value.getDate());
33
+ if (day > max.value) return new Date(max.value.getFullYear(), max.value.getMonth(), max.value.getDate());
34
+ return day;
35
+ };
36
+ const current = computed(() => clamp(value.value ?? now));
37
+ /** 各列的选项:月份、日子按当前的年月与范围裁剪 */
38
+ const lists = computed(() => {
39
+ const year = current.value.getFullYear();
40
+ const month = current.value.getMonth() + 1;
41
+ const atMin = (unit) => year === min.value.getFullYear() && (unit === "year" || month === min.value.getMonth() + 1);
42
+ const atMax = (unit) => year === max.value.getFullYear() && (unit === "year" || month === max.value.getMonth() + 1);
43
+ const units = config.lang.dateUnits;
44
+ const all = {
45
+ year: range(min.value.getFullYear(), max.value.getFullYear()).map((n) => ({
46
+ text: `${n}${units.year}`,
47
+ value: n
48
+ })),
49
+ month: range(atMin("year") ? min.value.getMonth() + 1 : 1, atMax("year") ? max.value.getMonth() + 1 : 12).map((n) => ({
50
+ text: `${pad(n)}${units.month}`,
51
+ value: n
52
+ })),
53
+ day: range(atMin("month") ? min.value.getDate() : 1, atMax("month") ? max.value.getDate() : daysIn(year, month)).map((n) => ({
54
+ text: `${pad(n)}${units.day}`,
55
+ value: n
56
+ }))
57
+ };
58
+ return columns.value.map((column) => all[column]);
59
+ });
60
+ const valuesOf = (date) => columns.value.map((column) => column === "year" ? date.getFullYear() : column === "month" ? date.getMonth() + 1 : date.getDate());
61
+ /** 各列的值 → 日期:缺的列取当前的,日子按月份的天数修正 */
62
+ const dateOf = (values) => {
63
+ const pick = (column, fallback) => {
64
+ const position = columns.value.indexOf(column);
65
+ return position >= 0 ? Number(values[position]) : fallback;
66
+ };
67
+ const year = pick("year", current.value.getFullYear());
68
+ const month = pick("month", current.value.getMonth() + 1);
69
+ const day = Math.min(pick("day", current.value.getDate()), daysIn(year, month));
70
+ return clamp(new Date(year, month - 1, day));
71
+ };
72
+ return () => createVNode(Picker, mergeJsxProps(rest, {
73
+ "columns": lists.value,
74
+ "_value": valuesOf(current.value),
75
+ "onChange": _cache[0] || (_cache[0] = ({ values }) => value.value = dateOf(values)),
76
+ "onConfirm": _cache[1] || (_cache[1] = ({ values }) => onConfirm.value?.(dateOf(values)))
77
+ }), null);
78
+ }, {
79
+ name: "DatePicker",
80
+ props: [
81
+ "_value",
82
+ "onUpdate:_value",
83
+ "minDate",
84
+ "maxDate",
85
+ "columns",
86
+ "onConfirm"
87
+ ]
88
+ });
89
+ /** 未声明的属性落到选择器的根元素上 */
90
+ /** 时间选择器(Picker:时、分两列) */
91
+ var TimePicker = /*#__PURE__*/ _component((_props2, _ctx2) => {
92
+ const _cache2 = [];
93
+ const value = bindable(_props2, "_value", () => "00:00", _ctx2.emit);
94
+ const minHour = readonlyProp(_props2, "minHour", 0);
95
+ const maxHour = readonlyProp(_props2, "maxHour", 23);
96
+ const minuteStep = readonlyProp(_props2, "minuteStep", 1);
97
+ const onConfirm = readonlyProp(_props2, "onConfirm");
98
+ const rest = restProps(_props2, _ctx2, true);
99
+ const config = useConfig();
100
+ const lists = computed(() => {
101
+ const units = config.lang.dateUnits;
102
+ return [range(minHour.value, maxHour.value).map((n) => ({
103
+ text: `${pad(n)}${units.hour}`,
104
+ value: n
105
+ })), range(0, Math.floor(59 / minuteStep.value)).map((n) => ({
106
+ text: `${pad(n * minuteStep.value)}${units.minute}`,
107
+ value: n * minuteStep.value
108
+ }))];
109
+ });
110
+ const parts = () => {
111
+ const [hour = "0", minute = "0"] = value.value.split(":");
112
+ return [Number(hour), Math.round(Number(minute) / minuteStep.value) * minuteStep.value];
113
+ };
114
+ const format = (values) => values.map((n) => pad(Number(n))).join(":");
115
+ return () => createVNode(Picker, mergeJsxProps(rest, {
116
+ "columns": lists.value,
117
+ "_value": parts(),
118
+ "onChange": _cache2[0] || (_cache2[0] = ({ values }) => value.value = format(values)),
119
+ "onConfirm": _cache2[1] || (_cache2[1] = ({ values }) => onConfirm.value?.(format(values)))
120
+ }), null);
121
+ }, {
122
+ name: "TimePicker",
123
+ props: [
124
+ "_value",
125
+ "onUpdate:_value",
126
+ "minHour",
127
+ "maxHour",
128
+ "minuteStep",
129
+ "onConfirm"
130
+ ]
131
+ });
132
+ //#endregion
133
+ export { DatePicker, TimePicker };
134
+
135
+ //# sourceMappingURL=DatePicker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DatePicker.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_restProps","_vue_computed","_createVNode","_mergeProps"],"sources":["../../src/picker/DatePicker.tsx"],"sourcesContent":["/**\n * 日期选择器与时间选择器:滚轮选年月日(可以只要年月)、选时分(分钟可以按步长)。建在 Picker 上,常放在底部弹出层里。\n *\n * <DatePicker _value={birthday} minDate={new Date(1950, 0, 1)} maxDate={new Date()} onConfirm={(date) => …} />\n * <DatePicker _value={month} columns={['year', 'month']} />\n * <TimePicker _value={time} minuteStep={15} /> time 是 \"09:30\"\n *\n * 换了年月时日子按那个月的天数修正(3 月 31 日换到 2 月是 2 月 28 / 29 日),超出 minDate、maxDate 的项不出现。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport type { VNodeChild } from 'vue';\nimport { useConfig } from '../config/context';\nimport type { PartProps } from '../utils/props';\nimport { Picker, type PickerOption } from './Picker';\n\n/** 日期的列 */\nexport type DateColumn = 'year' | 'month' | 'day';\n\n/** 共有的属性 */\ninterface PickerShellProps extends Omit<PartProps<'div', 'title'>, 'onChange'> {\n /** 标题 */\n title?: VNodeChild;\n /** 点了取消 */\n onCancel?: () => void;\n /** 可见的行数 */\n visibleItemCount?: number;\n}\n\n/** 未声明的属性落到选择器的根元素上 */\nexport interface DatePickerProps extends PickerShellProps {\n /** 选中的日期;双向绑定写 `_value={x}`。不给时是今天(限在范围内) */\n _value?: Date;\n /** 最早的日期;默认十年前的 1 月 1 日 */\n minDate?: Date;\n /** 最晚的日期;默认十年后的 12 月 31 日 */\n maxDate?: Date;\n /**\n * 显示哪几列\n * @default ['year', 'month', 'day']\n */\n columns?: DateColumn[];\n /** 点了确认 */\n onConfirm?: (date: Date) => void;\n}\n\nconst daysIn = (year: number, month: number) => new Date(year, month, 0).getDate();\nconst range = (from: number, to: number) =>\n Array.from({ length: to - from + 1 }, (_, index) => from + index);\nconst pad = (value: number) => String(value).padStart(2, '0');\n\n/** 日期选择器(Picker) */\nexport const DatePicker = _component(\n ({\n _value: value = _bindable<Date | undefined>(undefined),\n minDate,\n maxDate,\n columns = ['year', 'month', 'day'],\n onConfirm,\n ...rest\n }: DatePickerProps) => {\n const config = useConfig();\n const now = new Date();\n const min = _derived(minDate ?? new Date(now.getFullYear() - 10, 0, 1));\n const max = _derived(maxDate ?? new Date(now.getFullYear() + 10, 11, 31));\n /** 把日期限在范围内(只看年月日) */\n const clamp = (date: Date) => {\n const day = new Date(date.getFullYear(), date.getMonth(), date.getDate());\n if (day < min) return new Date(min.getFullYear(), min.getMonth(), min.getDate());\n if (day > max) return new Date(max.getFullYear(), max.getMonth(), max.getDate());\n return day;\n };\n const current = _derived(clamp(value ?? now));\n\n /** 各列的选项:月份、日子按当前的年月与范围裁剪 */\n const lists = _derived.by(() => {\n const year = current.getFullYear();\n const month = current.getMonth() + 1;\n const atMin = (unit: 'year' | 'month') =>\n year === min.getFullYear() && (unit === 'year' || month === min.getMonth() + 1);\n const atMax = (unit: 'year' | 'month') =>\n year === max.getFullYear() && (unit === 'year' || month === max.getMonth() + 1);\n const units = config.lang.dateUnits;\n const all: Record<DateColumn, PickerOption[]> = {\n year: range(min.getFullYear(), max.getFullYear()).map((n) => ({\n text: `${n}${units.year}`,\n value: n,\n })),\n month: range(\n atMin('year') ? min.getMonth() + 1 : 1,\n atMax('year') ? max.getMonth() + 1 : 12,\n ).map((n) => ({ text: `${pad(n)}${units.month}`, value: n })),\n day: range(\n atMin('month') ? min.getDate() : 1,\n atMax('month') ? max.getDate() : daysIn(year, month),\n ).map((n) => ({ text: `${pad(n)}${units.day}`, value: n })),\n };\n return columns.map((column) => all[column]);\n });\n\n const valuesOf = (date: Date) =>\n columns.map((column) =>\n column === 'year'\n ? date.getFullYear()\n : column === 'month'\n ? date.getMonth() + 1\n : date.getDate(),\n );\n /** 各列的值 → 日期:缺的列取当前的,日子按月份的天数修正 */\n const dateOf = (values: (string | number)[]) => {\n const pick = (column: DateColumn, fallback: number) => {\n const position = columns.indexOf(column);\n return position >= 0 ? Number(values[position]) : fallback;\n };\n const year = pick('year', current.getFullYear());\n const month = pick('month', current.getMonth() + 1);\n const day = Math.min(pick('day', current.getDate()), daysIn(year, month));\n return clamp(new Date(year, month - 1, day));\n };\n\n return () => (\n <Picker\n {...rest}\n columns={lists}\n _value={valuesOf(current)}\n onChange={({ values }) => (value = dateOf(values))}\n onConfirm={({ values }) => onConfirm?.(dateOf(values))}\n />\n );\n },\n);\n\n/** 未声明的属性落到选择器的根元素上 */\nexport interface TimePickerProps extends PickerShellProps {\n /** 选中的时间 \"HH:mm\";双向绑定写 `_value={x}`。不给时是 00:00 */\n _value?: string;\n /**\n * 最小、最大的小时\n * @default 0、23\n */\n minHour?: number;\n maxHour?: number;\n /**\n * 分钟的步长\n * @default 1\n */\n minuteStep?: number;\n /** 点了确认 */\n onConfirm?: (time: string) => void;\n}\n\n/** 时间选择器(Picker:时、分两列) */\nexport const TimePicker = _component(\n ({\n _value: value = _bindable('00:00'),\n minHour = 0,\n maxHour = 23,\n minuteStep = 1,\n onConfirm,\n ...rest\n }: TimePickerProps) => {\n const config = useConfig();\n const lists = _derived.by(() => {\n const units = config.lang.dateUnits;\n return [\n range(minHour, maxHour).map((n) => ({ text: `${pad(n)}${units.hour}`, value: n })),\n range(0, Math.floor(59 / minuteStep)).map((n) => ({\n text: `${pad(n * minuteStep)}${units.minute}`,\n value: n * minuteStep,\n })),\n ];\n });\n const parts = () => {\n const [hour = '0', minute = '0'] = value.split(':');\n return [Number(hour), Math.round(Number(minute) / minuteStep) * minuteStep];\n };\n const format = (values: (string | number)[]) => values.map((n) => pad(Number(n))).join(':');\n return () => (\n <Picker\n {...rest}\n columns={lists}\n _value={parts()}\n onChange={({ values }) => (value = format(values))}\n onConfirm={({ values }) => onConfirm?.(format(values))}\n />\n );\n },\n);\n"],"mappings":"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"}
@@ -0,0 +1,39 @@
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
+ import type { PickerOption } from './PickerColumn';
5
+ /** 未声明的属性落到菜单这一排(div)上 */
6
+ export interface DropdownMenuProps extends PartProps<'div', 'role'> {
7
+ /** 各个 DropdownItem */
8
+ children?: VNodeChild;
9
+ }
10
+ /** DropdownMenu、DropdownItem 的样式 */
11
+ export declare const dropdownStyles: Readonly<{
12
+ arrow: css.CompiledStyles;
13
+ bar: css.CompiledStyles;
14
+ clip: css.CompiledStyles;
15
+ option: css.CompiledStyles;
16
+ overlay: css.CompiledStyles;
17
+ panel: css.CompiledStyles;
18
+ title: css.CompiledStyles;
19
+ titleText: css.CompiledStyles;
20
+ }>;
21
+ /** 下拉菜单(div > 各项的标题;展开的面板在浮层根) */
22
+ export declare const DropdownMenu: import("@zerodep-dev/vue-tsx-pro-tsx").TsxComponent<DropdownMenuProps>;
23
+ /** 未声明的属性落到面板(div)上 */
24
+ export interface DropdownItemProps extends Omit<PartProps<'div', 'title' | 'role'>, 'onChange'> {
25
+ /** 选中的值;双向绑定写 `_value={x}` */
26
+ _value?: string | number;
27
+ /** 选项(与 children 二选一) */
28
+ options?: PickerOption[];
29
+ /** 标题;不给时显示选中的选项 */
30
+ title?: VNodeChild;
31
+ /** 不能展开 */
32
+ disabled?: boolean;
33
+ /** 选了另一项 */
34
+ onChange?: (value: string | number) => void;
35
+ /** 自定义的面板内容(代替选项) */
36
+ children?: VNodeChild;
37
+ }
38
+ /** 下拉菜单的一项:标题由菜单画,这里画展开的面板 */
39
+ export declare const DropdownItem: import("@zerodep-dev/vue-tsx-pro-tsx").TsxComponent<DropdownItemProps>;
@@ -0,0 +1,242 @@
1
+ import { useConfig } from "../config/context.js";
2
+ import { dataAttrs, hasContent, mergeAttrs } from "../utils/props.js";
3
+ import { Icon } from "../base/Icon.js";
4
+ import { useHost } from "../utils/host.js";
5
+ import { usePresence } from "../utils/presence.js";
6
+ import { useLockScroll } from "../overlay/Overlay.js";
7
+ import { css } from "@zerodep-dev/vue-tsx-pro-tsx/styles";
8
+ import { _component, bindable, childrenProp, createContext, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
9
+ import { Teleport, computed, createVNode, onBeforeUnmount, ref, shallowReactive } from "vue";
10
+ import { Check, ChevronDown } from "@lucide/icons";
11
+ //#region src/picker/DropdownMenu.tsx
12
+ /**
13
+ * 下拉菜单:列表上方的一排筛选(排序、分类、价格),点一项从这一排下面展开选项,选了就收起;同一时间只展开一个。
14
+ *
15
+ * <DropdownMenu>
16
+ * <DropdownItem _value={sort} options={[{ text: '综合排序', value: 'default' }, { text: '销量优先', value: 'sales' }]} />
17
+ * <DropdownItem title="筛选">…自定义内容(开关、按钮)…</DropdownItem>
18
+ * </DropdownMenu>
19
+ *
20
+ * 展开的面板与遮罩渲染到浮层根,从这一排的下沿开始(宿主量它的位置),遮罩只盖住下面的部分。
21
+ */
22
+ /** 一项登记给菜单的 */
23
+ /** 菜单提供给各项的 */
24
+ var DropdownContext = createContext("DropdownMenu");
25
+ /** 未声明的属性落到菜单这一排(div)上 */
26
+ /** DropdownMenu、DropdownItem 的样式 */
27
+ var dropdownStyles = css.create({
28
+ bar: {
29
+ position: "relative",
30
+ zIndex: 10,
31
+ display: "flex",
32
+ height: "3em",
33
+ backgroundColor: "_surface",
34
+ boxShadow: "_sm"
35
+ },
36
+ title: {
37
+ flex: "1 1 0",
38
+ minWidth: 0,
39
+ display: "inline-flex",
40
+ alignItems: "center",
41
+ justifyContent: "center",
42
+ gap: "0.2em",
43
+ margin: 0,
44
+ paddingInline: "_sm",
45
+ borderWidth: 0,
46
+ backgroundColor: "transparent",
47
+ color: {
48
+ default: "_text",
49
+ "[aria-expanded=\"true\"]": "_accentText",
50
+ "[disabled]": "_textDisabled"
51
+ },
52
+ fontFamily: "inherit",
53
+ fontSize: "_md",
54
+ whiteSpace: "nowrap",
55
+ cursor: {
56
+ default: "pointer",
57
+ "[disabled]": "not-allowed"
58
+ }
59
+ },
60
+ titleText: {
61
+ overflow: "hidden",
62
+ textOverflow: "ellipsis"
63
+ },
64
+ arrow: {
65
+ flex: "none",
66
+ fontSize: "0.9em",
67
+ transform: {
68
+ default: "none",
69
+ "[data-open]": "rotate(180deg)"
70
+ },
71
+ transitionProperty: "transform",
72
+ transitionDuration: "_md"
73
+ },
74
+ overlay: {
75
+ position: "fixed",
76
+ insetInline: 0,
77
+ insetBlockEnd: 0,
78
+ zIndex: 2e3,
79
+ backgroundColor: "_overlay",
80
+ opacity: {
81
+ default: 0,
82
+ "[data-state=\"open\"]": 1
83
+ },
84
+ transitionProperty: "opacity",
85
+ transitionDuration: "_md"
86
+ },
87
+ clip: {
88
+ position: "fixed",
89
+ insetInline: 0,
90
+ zIndex: 2001,
91
+ overflow: "hidden"
92
+ },
93
+ panel: {
94
+ maxHeight: "60vh",
95
+ overflowY: "auto",
96
+ backgroundColor: "_surface",
97
+ borderEndStartRadius: "_lg",
98
+ borderEndEndRadius: "_lg",
99
+ transform: {
100
+ default: "translateY(-100%)",
101
+ "[data-state=\"open\"]": "none"
102
+ },
103
+ transitionProperty: "transform",
104
+ transitionDuration: "_md",
105
+ transitionTimingFunction: "_standard"
106
+ },
107
+ option: {
108
+ display: "flex",
109
+ alignItems: "center",
110
+ justifyContent: "space-between",
111
+ minHeight: "_control",
112
+ paddingInline: "_lg",
113
+ fontSize: "_md",
114
+ color: {
115
+ default: "_text",
116
+ "[aria-selected=\"true\"]": "_accentText",
117
+ "[data-disabled]": "_textDisabled"
118
+ },
119
+ backgroundColor: {
120
+ default: "transparent",
121
+ ":active:not([data-disabled])": "_fillActive"
122
+ },
123
+ cursor: {
124
+ default: "pointer",
125
+ "[data-disabled]": "not-allowed"
126
+ }
127
+ }
128
+ });
129
+ /** 下拉菜单(div > 各项的标题;展开的面板在浮层根) */
130
+ var DropdownMenu = /*#__PURE__*/ _component((_props, _ctx) => {
131
+ const _cache = [];
132
+ const children = childrenProp(_props);
133
+ const rest = restProps(_props, _ctx, false);
134
+ const host = useHost();
135
+ const entries = shallowReactive([]);
136
+ let open = ref(-1);
137
+ let top = ref(0);
138
+ let bar = null;
139
+ const toggle = async (index) => {
140
+ if (open.value === index) return void (open.value = -1);
141
+ const rect = await host.measure(bar);
142
+ top.value = rect ? rect.top + rect.height : 0;
143
+ open.value = index;
144
+ };
145
+ DropdownContext.provide({
146
+ get open() {
147
+ return open.value;
148
+ },
149
+ get top() {
150
+ return top.value;
151
+ },
152
+ register(entry) {
153
+ entries.push(entry);
154
+ return {
155
+ index: () => entries.indexOf(entry),
156
+ unregister: () => void entries.splice(entries.indexOf(entry), 1)
157
+ };
158
+ },
159
+ close: () => open.value = -1
160
+ });
161
+ return () => createVNode("div", mergeJsxProps(mergeAttrs({ sx: dropdownStyles.bar }, rest, { role: "menubar" }), { "ref": _cache[0] || (_cache[0] = (node) => bar = node) }), [entries.map((entry, index) => createVNode("button", mergeJsxProps(mergeAttrs({ sx: dropdownStyles.title }), {
162
+ "type": "button",
163
+ "aria-expanded": open.value === index ? "true" : "false",
164
+ "aria-haspopup": "true",
165
+ "disabled": entry.disabled,
166
+ "onClick": () => void toggle(index)
167
+ }), [createVNode("span", mergeAttrs({ sx: dropdownStyles.titleText }), [entry.title]), createVNode(Icon, mergeJsxProps({
168
+ "icon": ChevronDown,
169
+ "sx": dropdownStyles.arrow
170
+ }, dataAttrs({ "data-open": open.value === index })), null)])), children.value]);
171
+ }, { name: "DropdownMenu" });
172
+ /** 未声明的属性落到面板(div)上 */
173
+ /** 下拉菜单的一项:标题由菜单画,这里画展开的面板 */
174
+ var DropdownItem = /*#__PURE__*/ _component((_props2, _ctx2) => {
175
+ const _cache2 = [];
176
+ const value = bindable(_props2, "_value", () => void 0, _ctx2.emit);
177
+ const options = readonlyProp(_props2, "options", () => []);
178
+ const title = readonlyProp(_props2, "title");
179
+ const disabled = readonlyProp(_props2, "disabled", false);
180
+ const onChange = readonlyProp(_props2, "onChange");
181
+ const children = childrenProp(_props2);
182
+ const rest = restProps(_props2, _ctx2, false);
183
+ const menu = DropdownContext.inject();
184
+ const host = useHost();
185
+ const config = useConfig();
186
+ const label = computed(() => title.value ?? options.value.find((option) => option.value === value.value)?.text ?? options.value[0]?.text);
187
+ const handle = menu.register({
188
+ get title() {
189
+ return label.value;
190
+ },
191
+ get disabled() {
192
+ return disabled.value;
193
+ }
194
+ });
195
+ onBeforeUnmount(handle.unregister);
196
+ const isOpen = () => menu.open === handle.index();
197
+ const presence = usePresence(isOpen);
198
+ useLockScroll(isOpen);
199
+ const choose = (option) => {
200
+ if (option.disabled) return;
201
+ if (option.value !== value.value) {
202
+ value.value = option.value;
203
+ onChange.value?.(option.value);
204
+ }
205
+ menu.close();
206
+ };
207
+ return () => {
208
+ if (!presence.mounted) return null;
209
+ const state = dataAttrs({ "data-state": presence.visible ? "open" : "closed" });
210
+ return createVNode(Teleport, { "to": host.overlayRoot() }, { default: () => [createVNode("div", mergeJsxProps(mergeAttrs({
211
+ sx: [config.rootSx, dropdownStyles.overlay],
212
+ style: { insetBlockStart: `${menu.top}px` }
213
+ }, state), { "onClick": _cache2[0] || (_cache2[0] = () => menu.close()) }, dataAttrs({ "data-scroll-lock": true })), null), createVNode("div", mergeAttrs({
214
+ sx: [config.rootSx, dropdownStyles.clip],
215
+ style: { insetBlockStart: `${menu.top}px` }
216
+ }), [createVNode("div", mergeAttrs({ sx: dropdownStyles.panel }, rest, {
217
+ role: "menu",
218
+ ...state
219
+ }), [hasContent(children.value) ? children.value : options.value.map((option) => {
220
+ const selected = option.value === value.value;
221
+ return createVNode("div", mergeJsxProps(mergeAttrs({ sx: dropdownStyles.option }), {
222
+ "role": "menuitemradio",
223
+ "aria-checked": selected ? "true" : "false",
224
+ "aria-selected": selected ? "true" : "false"
225
+ }, dataAttrs({ "data-disabled": option.disabled }), { "onClick": () => choose(option) }), [createVNode("span", null, [option.text]), selected && createVNode(Icon, { "icon": Check }, null)]);
226
+ })])])] });
227
+ };
228
+ }, {
229
+ name: "DropdownItem",
230
+ props: [
231
+ "_value",
232
+ "onUpdate:_value",
233
+ "options",
234
+ "title",
235
+ "disabled",
236
+ "onChange"
237
+ ]
238
+ });
239
+ //#endregion
240
+ export { DropdownItem, DropdownMenu, dropdownStyles };
241
+
242
+ //# sourceMappingURL=DropdownMenu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DropdownMenu.js","names":["_vtp_childrenProp","_vtp_restProps","_vue_ref","_createVNode","_mergeProps","_vtp_bindable","_vtp_readonlyProp","_vue_computed"],"sources":["../../src/picker/DropdownMenu.tsx"],"sourcesContent":["/**\n * 下拉菜单:列表上方的一排筛选(排序、分类、价格),点一项从这一排下面展开选项,选了就收起;同一时间只展开一个。\n *\n * <DropdownMenu>\n * <DropdownItem _value={sort} options={[{ text: '综合排序', value: 'default' }, { text: '销量优先', value: 'sales' }]} />\n * <DropdownItem title=\"筛选\">…自定义内容(开关、按钮)…</DropdownItem>\n * </DropdownMenu>\n *\n * 展开的面板与遮罩渲染到浮层根,从这一排的下沿开始(宿主量它的位置),遮罩只盖住下面的部分。\n */\nimport { Check, ChevronDown } from '@lucide/icons';\nimport { _component, createContext } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { Teleport, onBeforeUnmount, shallowReactive, type VNodeChild } from 'vue';\nimport { Icon } from '../base/Icon';\nimport { useConfig } from '../config/context';\nimport { useLockScroll } from '../overlay/Overlay';\nimport { useHost } from '../utils/host';\nimport { usePresence } from '../utils/presence';\nimport { dataAttrs, hasContent, mergeAttrs, type PartProps } from '../utils/props';\nimport type { PickerOption } from './PickerColumn';\n\n/** 一项登记给菜单的 */\ninterface DropdownEntry {\n readonly title: VNodeChild;\n readonly disabled: boolean;\n}\n\n/** 菜单提供给各项的 */\ninterface DropdownState {\n /** 展开的是第几项;-1 是都收着 */\n readonly open: number;\n /** 面板的上沿(px,菜单这一排的下沿) */\n readonly top: number;\n register(entry: DropdownEntry): { index: () => number; unregister: () => void };\n close(): void;\n}\n\nconst DropdownContext = createContext<DropdownState>('DropdownMenu');\n\n/** 未声明的属性落到菜单这一排(div)上 */\nexport interface DropdownMenuProps extends PartProps<'div', 'role'> {\n /** 各个 DropdownItem */\n children?: VNodeChild;\n}\n\n/** DropdownMenu、DropdownItem 的样式 */\nexport const dropdownStyles = css.create({\n bar: {\n position: 'relative',\n zIndex: 10,\n display: 'flex',\n height: '3em',\n backgroundColor: '_surface',\n boxShadow: '_sm',\n },\n title: {\n flex: '1 1 0',\n minWidth: 0,\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: '0.2em',\n margin: 0,\n paddingInline: '_sm',\n borderWidth: 0,\n backgroundColor: 'transparent',\n color: {\n default: '_text',\n '[aria-expanded=\"true\"]': '_accentText',\n '[disabled]': '_textDisabled',\n },\n fontFamily: 'inherit',\n fontSize: '_md',\n whiteSpace: 'nowrap',\n cursor: { default: 'pointer', '[disabled]': 'not-allowed' },\n },\n titleText: { overflow: 'hidden', textOverflow: 'ellipsis' },\n arrow: {\n flex: 'none',\n fontSize: '0.9em',\n transform: { default: 'none', '[data-open]': 'rotate(180deg)' },\n transitionProperty: 'transform',\n transitionDuration: '_md',\n },\n overlay: {\n position: 'fixed',\n insetInline: 0,\n insetBlockEnd: 0,\n zIndex: 2000,\n backgroundColor: '_overlay',\n opacity: { default: 0, '[data-state=\"open\"]': 1 },\n transitionProperty: 'opacity',\n transitionDuration: '_md',\n },\n // 面板外面一层裁掉面板滑出来之前的部分\n clip: { position: 'fixed', insetInline: 0, zIndex: 2001, overflow: 'hidden' },\n panel: {\n maxHeight: '60vh',\n overflowY: 'auto',\n backgroundColor: '_surface',\n borderEndStartRadius: '_lg',\n borderEndEndRadius: '_lg',\n transform: { default: 'translateY(-100%)', '[data-state=\"open\"]': 'none' },\n transitionProperty: 'transform',\n transitionDuration: '_md',\n transitionTimingFunction: '_standard',\n },\n option: {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n minHeight: '_control',\n paddingInline: '_lg',\n fontSize: '_md',\n color: {\n default: '_text',\n '[aria-selected=\"true\"]': '_accentText',\n '[data-disabled]': '_textDisabled',\n },\n backgroundColor: { default: 'transparent', ':active:not([data-disabled])': '_fillActive' },\n cursor: { default: 'pointer', '[data-disabled]': 'not-allowed' },\n },\n});\n\n/** 下拉菜单(div > 各项的标题;展开的面板在浮层根) */\nexport const DropdownMenu = _component(({ children, ...rest }: DropdownMenuProps) => {\n const host = useHost();\n const entries = shallowReactive<DropdownEntry[]>([]);\n let open = _state(-1);\n let top = _state(0);\n let bar: unknown = null;\n const toggle = async (index: number) => {\n if (open === index) return void (open = -1);\n const rect = await host.measure(bar);\n top = rect ? rect.top + rect.height : 0;\n open = index;\n };\n DropdownContext.provide({\n get open() {\n return open;\n },\n get top() {\n return top;\n },\n register(entry) {\n entries.push(entry);\n return {\n index: () => entries.indexOf(entry),\n unregister: () => void entries.splice(entries.indexOf(entry), 1),\n };\n },\n close: () => (open = -1),\n });\n return () => (\n <div\n {...mergeAttrs<'div'>({ sx: dropdownStyles.bar }, rest, { role: 'menubar' })}\n ref={(node: unknown) => (bar = node)}\n >\n {entries.map((entry, index) => (\n <button\n {...mergeAttrs<'button'>({ sx: dropdownStyles.title })}\n type=\"button\"\n aria-expanded={open === index ? 'true' : 'false'}\n aria-haspopup=\"true\"\n disabled={entry.disabled}\n onClick={() => void toggle(index)}\n >\n <span {...mergeAttrs<'span'>({ sx: dropdownStyles.titleText })}>{entry.title}</span>\n <Icon\n icon={ChevronDown}\n sx={dropdownStyles.arrow}\n {...dataAttrs({ 'data-open': open === index })}\n />\n </button>\n ))}\n {children}\n </div>\n );\n});\n\n/** 未声明的属性落到面板(div)上 */\nexport interface DropdownItemProps extends Omit<PartProps<'div', 'title' | 'role'>, 'onChange'> {\n /** 选中的值;双向绑定写 `_value={x}` */\n _value?: string | number;\n /** 选项(与 children 二选一) */\n options?: PickerOption[];\n /** 标题;不给时显示选中的选项 */\n title?: VNodeChild;\n /** 不能展开 */\n disabled?: boolean;\n /** 选了另一项 */\n onChange?: (value: string | number) => void;\n /** 自定义的面板内容(代替选项) */\n children?: VNodeChild;\n}\n\n/** 下拉菜单的一项:标题由菜单画,这里画展开的面板 */\nexport const DropdownItem = _component(\n ({\n _value: value = _bindable<string | number | undefined>(undefined),\n options = [],\n title,\n disabled = false,\n onChange,\n children,\n ...rest\n }: DropdownItemProps) => {\n const menu = DropdownContext.inject();\n const host = useHost();\n const config = useConfig();\n // 标题在这里算好:菜单的渲染只读这个派生值,不去读这一项可双向绑定的 _value\n const label = _derived(\n title ?? options.find((option) => option.value === value)?.text ?? options[0]?.text,\n );\n const handle = menu.register({\n get title() {\n return label;\n },\n get disabled() {\n return disabled;\n },\n });\n onBeforeUnmount(handle.unregister);\n const isOpen = () => menu.open === handle.index();\n const presence = usePresence(isOpen);\n useLockScroll(isOpen);\n\n const choose = (option: PickerOption) => {\n if (option.disabled) return;\n if (option.value !== value) {\n value = option.value;\n onChange?.(option.value);\n }\n menu.close();\n };\n\n return () => {\n if (!presence.mounted) return null;\n const state = dataAttrs({ 'data-state': presence.visible ? 'open' : 'closed' });\n return (\n <Teleport to={host.overlayRoot() as never}>\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [config.rootSx, dropdownStyles.overlay],\n style: { insetBlockStart: `${menu.top}px` },\n },\n state,\n )}\n onClick={() => menu.close()}\n {...dataAttrs({ 'data-scroll-lock': true })}\n />\n <div\n {...mergeAttrs<'div'>({\n sx: [config.rootSx, dropdownStyles.clip],\n style: { insetBlockStart: `${menu.top}px` },\n })}\n >\n <div\n {...mergeAttrs<'div'>({ sx: dropdownStyles.panel }, rest, { role: 'menu', ...state })}\n >\n {hasContent(children)\n ? children\n : options.map((option) => {\n const selected = option.value === value;\n return (\n <div\n {...mergeAttrs<'div'>({ sx: dropdownStyles.option })}\n role=\"menuitemradio\"\n aria-checked={selected ? 'true' : 'false'}\n aria-selected={selected ? 'true' : 'false'}\n {...dataAttrs({ 'data-disabled': option.disabled })}\n onClick={() => choose(option)}\n >\n <span>{option.text}</span>\n {selected && <Icon icon={Check} />}\n </div>\n );\n })}\n </div>\n </div>\n </Teleport>\n );\n };\n },\n);\n"],"mappings":"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"}
@@ -0,0 +1,67 @@
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
+ import { type PickerOption } from './PickerColumn';
5
+ export type { PickerOption } from './PickerColumn';
6
+ /** 选项的值 */
7
+ export type PickerValue = string | number;
8
+ /** 确认、变化时给出的 */
9
+ export interface PickerResult {
10
+ /** 各列选中的值 */
11
+ values: PickerValue[];
12
+ /** 各列选中的选项 */
13
+ options: PickerOption[];
14
+ }
15
+ /** 未声明的属性落到根元素(div)上 */
16
+ export interface PickerProps extends Omit<PartProps<'div', 'title'>, 'onChange'> {
17
+ /** 选项:一列、几列(数组的数组)、联动(选项带 children) */
18
+ columns: PickerOption[] | PickerOption[][];
19
+ /** 各列选中的值;双向绑定写 `_value={x}`(滚动时就写回,确认时 onConfirm) */
20
+ _value?: PickerValue[];
21
+ /** 标题 */
22
+ title?: VNodeChild;
23
+ /**
24
+ * 显示标题栏
25
+ * @default true
26
+ */
27
+ showToolbar?: boolean;
28
+ /** 确认的文字;不给时用语言包 */
29
+ confirmText?: VNodeChild;
30
+ /** 取消的文字;不给时用语言包 */
31
+ cancelText?: VNodeChild;
32
+ /**
33
+ * 可见的行数(奇数)
34
+ * @default 5
35
+ */
36
+ visibleItemCount?: number;
37
+ /** 点了确认 */
38
+ onConfirm?: (result: PickerResult) => void;
39
+ /** 点了取消 */
40
+ onCancel?: () => void;
41
+ /** 滚到另一项 */
42
+ onChange?: (result: PickerResult & {
43
+ columnIndex: number;
44
+ }) => void;
45
+ /** 各列的读屏名称 */
46
+ columnLabels?: string[];
47
+ }
48
+ /** Picker 的样式:root、toolbar、title、action、confirm、columns、mask、indicator */
49
+ export declare const pickerStyles: Readonly<{
50
+ action: css.CompiledStyles;
51
+ columns: css.CompiledStyles;
52
+ confirm: css.CompiledStyles;
53
+ indicator: css.CompiledStyles;
54
+ mask: css.CompiledStyles;
55
+ root: css.CompiledStyles;
56
+ title: css.CompiledStyles;
57
+ toolbar: css.CompiledStyles;
58
+ }>;
59
+ /** 按选中的值算出各列(联动时跟着上一列的选择往下展开)与各列选中的序号 */
60
+ export declare function resolveColumns(columns: PickerOption[] | PickerOption[][], values: PickerValue[]): {
61
+ lists: PickerOption[][];
62
+ indexes: number[];
63
+ options: PickerOption[];
64
+ values: (string | number)[];
65
+ };
66
+ /** 选择器(div > 标题栏 + 各列滚轮) */
67
+ export declare const Picker: import("@zerodep-dev/vue-tsx-pro-tsx").TsxComponent<PickerProps>;