@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,178 @@
1
+ import { useConfig } from "../config/context.js";
2
+ import { dataAttrs, mergeAttrs } from "../utils/props.js";
3
+ import { Loading } from "./Loading.js";
4
+ import { useHost } from "../utils/host.js";
5
+ import { css } from "@zerodep-dev/vue-tsx-pro-tsx/styles";
6
+ import { _component, bindable, childrenProp, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
7
+ import { createVNode, onBeforeUnmount, ref, watch } from "vue";
8
+ //#region src/feedback/PullRefresh.tsx
9
+ /** 下拉的阶段 */
10
+ /** 下拉的阶段 */
11
+ /** 未声明的属性落到根元素(div)上 */
12
+ /** PullRefresh 的样式:root、track、dragging、head */
13
+ var pullRefreshStyles = css.create({
14
+ root: {
15
+ overflow: "hidden",
16
+ userSelect: "none"
17
+ },
18
+ track: {
19
+ position: "relative",
20
+ transitionProperty: "transform",
21
+ transitionDuration: "_md",
22
+ transitionTimingFunction: "_standard"
23
+ },
24
+ dragging: { transitionDuration: "0s" },
25
+ head: {
26
+ position: "absolute",
27
+ insetInline: 0,
28
+ insetBlockEnd: "100%",
29
+ display: "flex",
30
+ alignItems: "center",
31
+ justifyContent: "center",
32
+ overflow: "hidden",
33
+ fontSize: "_sm",
34
+ color: "_textMuted"
35
+ }
36
+ });
37
+ var LOCK_DISTANCE = 8;
38
+ /** 拉的手感:提示区高度以内跟手,再往下越拉越沉 */
39
+ function damp(dy, head) {
40
+ if (dy <= head) return dy;
41
+ if (dy <= head * 2) return head + (dy - head) / 2;
42
+ return head * 1.5 + (dy - head * 2) / 4;
43
+ }
44
+ /** 下拉刷新(div > 滑动层:提示区 + 内容) */
45
+ var PullRefresh = /*#__PURE__*/ _component((_props, _ctx) => {
46
+ const refreshing = bindable(_props, "_refreshing", () => false, _ctx.emit);
47
+ const onRefresh = readonlyProp(_props, "onRefresh");
48
+ const disabled = readonlyProp(_props, "disabled", false);
49
+ const headHeight = readonlyProp(_props, "headHeight", 50);
50
+ const pullingText = readonlyProp(_props, "pullingText");
51
+ const loosingText = readonlyProp(_props, "loosingText");
52
+ const loadingText = readonlyProp(_props, "loadingText");
53
+ const successText = readonlyProp(_props, "successText");
54
+ const successDuration = readonlyProp(_props, "successDuration", 500);
55
+ const head = readonlyProp(_props, "head");
56
+ const children = childrenProp(_props);
57
+ const propsHead = readonlyProp(_props, "propsHead");
58
+ const rest = restProps(_props, _ctx, false);
59
+ const host = useHost();
60
+ const config = useConfig();
61
+ let drag = ref(null);
62
+ let pulling = ref(false);
63
+ let succeeded = ref(false);
64
+ let start = {
65
+ x: 0,
66
+ y: 0
67
+ };
68
+ let direction = null;
69
+ let tracking = false;
70
+ let successTimer;
71
+ const status = () => {
72
+ if (refreshing.value) return "loading";
73
+ if (succeeded.value) return "success";
74
+ if (drag.value === null || drag.value === 0) return "normal";
75
+ return drag.value >= headHeight.value ? "loosing" : "pulling";
76
+ };
77
+ watch(() => refreshing.value, (now, before) => {
78
+ if (now || !before || successText.value === void 0) return;
79
+ succeeded.value = true;
80
+ clearTimeout(successTimer);
81
+ successTimer = setTimeout(() => succeeded.value = false, successDuration.value);
82
+ });
83
+ onBeforeUnmount(() => clearTimeout(successTimer));
84
+ const refresh = async () => {
85
+ refreshing.value = true;
86
+ const pending = onRefresh.value?.();
87
+ if (!(pending instanceof Promise)) return;
88
+ try {
89
+ await pending;
90
+ } finally {
91
+ refreshing.value = false;
92
+ }
93
+ };
94
+ const onTouchstart = (event) => {
95
+ tracking = !disabled.value && !refreshing.value && !succeeded.value && host.scrollTop() <= 0;
96
+ if (!tracking) return;
97
+ const touch = event.touches[0];
98
+ start = {
99
+ x: touch.clientX,
100
+ y: touch.clientY
101
+ };
102
+ direction = null;
103
+ };
104
+ const onTouchmove = (event) => {
105
+ if (!tracking) return;
106
+ const touch = event.touches[0];
107
+ const dx = touch.clientX - start.x;
108
+ const dy = touch.clientY - start.y;
109
+ if (!direction) {
110
+ if (Math.abs(dx) < LOCK_DISTANCE && Math.abs(dy) < LOCK_DISTANCE) return;
111
+ direction = dy > 0 && dy > Math.abs(dx) ? "v" : "h";
112
+ if (direction === "h") return void (tracking = false);
113
+ pulling.value = true;
114
+ }
115
+ if (event.cancelable) event.preventDefault();
116
+ drag.value = damp(Math.max(0, dy), headHeight.value);
117
+ };
118
+ const onTouchend = () => {
119
+ tracking = false;
120
+ pulling.value = false;
121
+ if (drag.value === null) return;
122
+ const loosing = drag.value >= headHeight.value;
123
+ drag.value = null;
124
+ if (loosing) refresh();
125
+ };
126
+ const text = (current) => {
127
+ switch (current) {
128
+ case "pulling": return pullingText.value ?? config.lang.pulling;
129
+ case "loosing": return loosingText.value ?? config.lang.loosing;
130
+ case "loading": return createVNode(Loading, null, {
131
+ default: () => [loadingText.value ?? config.lang.refreshing],
132
+ $stable: true
133
+ });
134
+ case "success": return successText.value;
135
+ default: return null;
136
+ }
137
+ };
138
+ return () => {
139
+ const current = status();
140
+ const distance = drag.value ?? (current === "loading" || current === "success" ? headHeight.value : 0);
141
+ return createVNode("div", mergeAttrs({ sx: pullRefreshStyles.root }, rest, {
142
+ onTouchstart,
143
+ onTouchmove,
144
+ onTouchend,
145
+ onTouchcancel: onTouchend,
146
+ ...dataAttrs({
147
+ "data-status": current,
148
+ "data-touch-move": pulling.value ? "catch" : true
149
+ })
150
+ }), [createVNode("div", mergeAttrs({
151
+ sx: [pullRefreshStyles.track, drag.value !== null && pullRefreshStyles.dragging],
152
+ style: { transform: distance ? `translateY(${distance}px)` : "none" }
153
+ }), [createVNode("div", mergeAttrs({
154
+ sx: pullRefreshStyles.head,
155
+ style: { height: `${headHeight.value}px` }
156
+ }, propsHead.value, { "aria-live": "polite" }), [head.value ? head.value(current, distance) : text(current)]), children.value])]);
157
+ };
158
+ }, {
159
+ name: "PullRefresh",
160
+ props: [
161
+ "_refreshing",
162
+ "onUpdate:_refreshing",
163
+ "onRefresh",
164
+ "disabled",
165
+ "headHeight",
166
+ "pullingText",
167
+ "loosingText",
168
+ "loadingText",
169
+ "successText",
170
+ "successDuration",
171
+ "head",
172
+ "propsHead"
173
+ ]
174
+ });
175
+ //#endregion
176
+ export { PullRefresh, pullRefreshStyles };
177
+
178
+ //# sourceMappingURL=PullRefresh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PullRefresh.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_vue_ref","_createVNode"],"sources":["../../src/feedback/PullRefresh.tsx"],"sourcesContent":["/**\n * 下拉刷新:页面在顶部时往下拉,露出提示(下拉刷新 → 松手刷新 → 加载中),松手后调用 onRefresh。\n *\n * <PullRefresh _refreshing={loading} onRefresh={reload}>\n * <List … />\n * </PullRefresh>\n *\n * onRefresh 返回 Promise 时自动在完成后收起;不返回时由使用方把 _refreshing 改回 false。\n * 只在页面滚到顶时接管下拉;拉动期间挡住页面自己的滚动与回弹(H5 取消 touchmove,小程序换成 catch)。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { onBeforeUnmount, watch, type VNodeChild } from 'vue';\nimport { useConfig } from '../config/context';\nimport { useHost } from '../utils/host';\nimport { dataAttrs, mergeAttrs, type PartProps } from '../utils/props';\nimport { Loading } from './Loading';\n\n/** 下拉的阶段 */\nexport interface PullRefreshStatuses {\n /** 没在拉 */\n normal: unknown;\n /** 拉了但还不够,松手会弹回 */\n pulling: unknown;\n /** 拉够了,松手就刷新 */\n loosing: unknown;\n /** 刷新中 */\n loading: unknown;\n /** 刷新完了,显示一会儿成功的提示 */\n success: unknown;\n}\n\n/** 下拉的阶段 */\nexport type PullRefreshStatus = keyof PullRefreshStatuses;\n\n/** 未声明的属性落到根元素(div)上 */\nexport interface PullRefreshProps extends PartProps<'div'> {\n /** 是否在刷新;双向绑定写 `_refreshing={x}` */\n _refreshing?: boolean;\n /** 松手刷新;返回 Promise 时完成后自动收起 */\n onRefresh?: () => void | Promise<unknown>;\n /** 不能下拉 */\n disabled?: boolean;\n /**\n * 顶部提示区的高度(px),也是拉到多少算\"拉够了\"\n * @default 50\n */\n headHeight?: number;\n /** 各阶段的提示文字;不给时用语言包 */\n pullingText?: VNodeChild;\n loosingText?: VNodeChild;\n loadingText?: VNodeChild;\n /** 刷新完显示的文字;不给时直接收起 */\n successText?: VNodeChild;\n /**\n * 成功提示显示多久(ms)\n * @default 500\n */\n successDuration?: number;\n /** 自定义提示区的内容(代替各阶段的文字) */\n head?: (status: PullRefreshStatus, distance: number) => VNodeChild;\n /** 内容 */\n children?: VNodeChild;\n /** 提示区的部件属性 */\n propsHead?: PartProps<'div'>;\n}\n\n/** PullRefresh 的样式:root、track、dragging、head */\nexport const pullRefreshStyles = css.create({\n root: { overflow: 'hidden', userSelect: 'none' },\n track: {\n position: 'relative',\n transitionProperty: 'transform',\n transitionDuration: '_md',\n transitionTimingFunction: '_standard',\n },\n dragging: { transitionDuration: '0s' },\n head: {\n position: 'absolute',\n insetInline: 0,\n insetBlockEnd: '100%',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n overflow: 'hidden',\n fontSize: '_sm',\n color: '_textMuted',\n },\n});\n\n// 判定横竖的移动距离(px)\nconst LOCK_DISTANCE = 8;\n\n/** 拉的手感:提示区高度以内跟手,再往下越拉越沉 */\nfunction damp(dy: number, head: number) {\n if (dy <= head) return dy;\n if (dy <= head * 2) return head + (dy - head) / 2;\n return head * 1.5 + (dy - head * 2) / 4;\n}\n\n/** 下拉刷新(div > 滑动层:提示区 + 内容) */\nexport const PullRefresh = _component(\n ({\n _refreshing: refreshing = _bindable(false),\n onRefresh,\n disabled = false,\n headHeight = 50,\n pullingText,\n loosingText,\n loadingText,\n successText,\n successDuration = 500,\n head,\n children,\n propsHead,\n ...rest\n }: PullRefreshProps) => {\n const host = useHost();\n const config = useConfig();\n // 拉动中的距离(null 是没在拉);刷新完的成功提示\n let drag = _state<number | null>(null);\n let pulling = _state(false);\n let succeeded = _state(false);\n let start = { x: 0, y: 0 };\n let direction: 'h' | 'v' | null = null;\n let tracking = false;\n let successTimer: ReturnType<typeof setTimeout> | undefined;\n\n const status = (): PullRefreshStatus => {\n if (refreshing) return 'loading';\n if (succeeded) return 'success';\n if (drag === null || drag === 0) return 'normal';\n return drag >= headHeight ? 'loosing' : 'pulling';\n };\n\n // 刷新结束:有成功提示时先停一会儿再收起\n watch(\n () => refreshing,\n (now, before) => {\n if (now || !before || successText === undefined) return;\n succeeded = true;\n clearTimeout(successTimer);\n successTimer = setTimeout(() => (succeeded = false), successDuration);\n },\n );\n onBeforeUnmount(() => clearTimeout(successTimer));\n\n const refresh = async () => {\n refreshing = true;\n const pending = onRefresh?.();\n if (!(pending instanceof Promise)) return;\n try {\n await pending;\n } finally {\n refreshing = false;\n }\n };\n\n const onTouchstart = (event: TouchEvent) => {\n // 只在页面滚到顶、没在刷新时接管\n tracking = !disabled && !refreshing && !succeeded && host.scrollTop() <= 0;\n if (!tracking) return;\n const touch = event.touches[0]!;\n start = { x: touch.clientX, y: touch.clientY };\n direction = null;\n };\n const onTouchmove = (event: TouchEvent) => {\n if (!tracking) return;\n const touch = event.touches[0]!;\n const dx = touch.clientX - start.x;\n const dy = touch.clientY - start.y;\n if (!direction) {\n if (Math.abs(dx) < LOCK_DISTANCE && Math.abs(dy) < LOCK_DISTANCE) return;\n direction = dy > 0 && dy > Math.abs(dx) ? 'v' : 'h';\n // 往上推、横滑:交给页面\n if (direction === 'h') return void (tracking = false);\n pulling = true;\n }\n // 挡住浏览器自己的下拉(刷新整页、回弹)\n if (event.cancelable) event.preventDefault();\n drag = damp(Math.max(0, dy), headHeight);\n };\n const onTouchend = () => {\n tracking = false;\n pulling = false;\n if (drag === null) return;\n const loosing = drag >= headHeight;\n drag = null;\n if (loosing) void refresh();\n };\n\n const text = (current: PullRefreshStatus): VNodeChild => {\n switch (current) {\n case 'pulling':\n return pullingText ?? config.lang.pulling;\n case 'loosing':\n return loosingText ?? config.lang.loosing;\n case 'loading':\n return <Loading>{loadingText ?? config.lang.refreshing}</Loading>;\n case 'success':\n return successText;\n default:\n return null;\n }\n };\n\n return () => {\n const current = status();\n const distance = drag ?? (current === 'loading' || current === 'success' ? headHeight : 0);\n return (\n <div\n {...mergeAttrs<'div'>({ sx: pullRefreshStyles.root }, rest, {\n onTouchstart,\n onTouchmove,\n onTouchend,\n onTouchcancel: onTouchend,\n ...dataAttrs({\n 'data-status': current,\n // 小程序:收 touchmove;拉动期间 catch,页面不跟着滚\n 'data-touch-move': pulling ? 'catch' : true,\n }),\n })}\n >\n <div\n {...mergeAttrs<'div'>({\n sx: [pullRefreshStyles.track, drag !== null && pullRefreshStyles.dragging],\n style: { transform: distance ? `translateY(${distance}px)` : 'none' },\n })}\n >\n <div\n {...mergeAttrs<'div'>(\n { sx: pullRefreshStyles.head, style: { height: `${headHeight}px` } },\n propsHead,\n {\n 'aria-live': 'polite',\n },\n )}\n >\n {head ? head(current, distance) : text(current)}\n </div>\n {children}\n </div>\n </div>\n );\n };\n },\n);\n"],"mappings":"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"}
@@ -0,0 +1,56 @@
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
+ /** 未声明的属性落到占位的根元素(div)上 */
5
+ export interface SkeletonProps extends PartProps<'div', 'role' | 'aria-busy' | 'title'> {
6
+ /**
7
+ * 是否显示占位;false 时显示 children
8
+ * @default true
9
+ */
10
+ loading?: boolean;
11
+ /**
12
+ * 文字的行数
13
+ * @default 3
14
+ */
15
+ rows?: number;
16
+ /**
17
+ * 每行的宽度;给数组时按行取,最后一行默认 60%
18
+ * @default '100%'
19
+ */
20
+ rowWidth?: string | string[];
21
+ /** 显示标题的块 */
22
+ title?: boolean;
23
+ /** 显示头像的块 */
24
+ avatar?: boolean;
25
+ /**
26
+ * 头像的形状
27
+ * @default 'round'
28
+ */
29
+ avatarShape?: 'round' | 'square';
30
+ /**
31
+ * 呼吸动画
32
+ * @default true
33
+ */
34
+ animate?: boolean;
35
+ /** 加载完显示的内容 */
36
+ children?: VNodeChild;
37
+ /** 头像块的部件属性 */
38
+ propsAvatar?: PartProps<'div'>;
39
+ /** 标题块的部件属性 */
40
+ propsTitle?: PartProps<'div'>;
41
+ /** 每行文字块的部件属性 */
42
+ propsRow?: PartProps<'div'>;
43
+ }
44
+ /** Skeleton 的样式:root、animate、avatar、square、body、block、title、row */
45
+ export declare const skeletonStyles: Readonly<{
46
+ animate: css.CompiledStyles;
47
+ avatar: css.CompiledStyles;
48
+ block: css.CompiledStyles;
49
+ body: css.CompiledStyles;
50
+ root: css.CompiledStyles;
51
+ row: css.CompiledStyles;
52
+ square: css.CompiledStyles;
53
+ title: css.CompiledStyles;
54
+ }>;
55
+ /** 骨架屏(div[aria-busy] > 头像 + 标题 + 文字行);loading 为 false 时只渲染 children */
56
+ export declare const Skeleton: import("@zerodep-dev/vue-tsx-pro-tsx").TsxComponent<SkeletonProps>;
@@ -0,0 +1,99 @@
1
+ import { mergeAttrs } from "../utils/props.js";
2
+ import { css } from "@zerodep-dev/vue-tsx-pro-tsx/styles";
3
+ import { _component, childrenProp, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
4
+ import { createVNode } from "vue";
5
+ //#region src/feedback/Skeleton.tsx
6
+ /** 未声明的属性落到占位的根元素(div)上 */
7
+ var pulse = css.keyframes({ "50%": { opacity: .5 } });
8
+ /** Skeleton 的样式:root、animate、avatar、square、body、block、title、row */
9
+ var skeletonStyles = css.create({
10
+ root: {
11
+ display: "flex",
12
+ gap: "_lg",
13
+ paddingBlock: "_sm",
14
+ paddingInline: "_lg"
15
+ },
16
+ animate: {
17
+ animationName: pulse,
18
+ animationDuration: "1.4s",
19
+ animationTimingFunction: "_standard",
20
+ animationIterationCount: {
21
+ default: "infinite",
22
+ _reducedMotion: 0
23
+ }
24
+ },
25
+ avatar: {
26
+ flex: "none",
27
+ width: "2.5em",
28
+ height: "2.5em",
29
+ borderRadius: "_full"
30
+ },
31
+ square: { borderRadius: "_md" },
32
+ body: {
33
+ flex: "1 1 auto",
34
+ minWidth: 0,
35
+ display: "flex",
36
+ flexDirection: "column",
37
+ gap: "0.85em"
38
+ },
39
+ block: {
40
+ backgroundColor: "_fill",
41
+ borderRadius: "_sm"
42
+ },
43
+ title: {
44
+ width: "40%",
45
+ height: "1.15em"
46
+ },
47
+ row: { height: "1em" }
48
+ });
49
+ /** 某一行的宽度:数组按行取;没给时最后一行(不止一行时)短一些,看起来像一段文字的结尾 */
50
+ function widthOf(rowWidth, index, rows) {
51
+ if (Array.isArray(rowWidth)) return rowWidth[index] ?? "100%";
52
+ if (rowWidth) return rowWidth;
53
+ return rows > 1 && index === rows - 1 ? "60%" : "100%";
54
+ }
55
+ /** 骨架屏(div[aria-busy] > 头像 + 标题 + 文字行);loading 为 false 时只渲染 children */
56
+ var Skeleton = /*#__PURE__*/ _component((_props, _ctx) => {
57
+ const loading = readonlyProp(_props, "loading", true);
58
+ const rows = readonlyProp(_props, "rows", 3);
59
+ const rowWidth = readonlyProp(_props, "rowWidth");
60
+ const title = readonlyProp(_props, "title", false);
61
+ const avatar = readonlyProp(_props, "avatar", false);
62
+ const avatarShape = readonlyProp(_props, "avatarShape", "round");
63
+ const animate = readonlyProp(_props, "animate", true);
64
+ const children = childrenProp(_props);
65
+ const propsAvatar = readonlyProp(_props, "propsAvatar");
66
+ const propsTitle = readonlyProp(_props, "propsTitle");
67
+ const propsRow = readonlyProp(_props, "propsRow");
68
+ const rest = restProps(_props, _ctx, false);
69
+ return () => {
70
+ if (!loading.value) return children.value;
71
+ const block = (sx) => [
72
+ skeletonStyles.block,
73
+ sx,
74
+ animate.value && skeletonStyles.animate
75
+ ];
76
+ return createVNode("div", mergeAttrs({ sx: skeletonStyles.root }, rest, { "aria-busy": "true" }), [avatar.value && createVNode("div", mergeAttrs({ sx: block([skeletonStyles.avatar, avatarShape.value === "square" && skeletonStyles.square]) }, propsAvatar.value), null), createVNode("div", mergeAttrs({ sx: skeletonStyles.body }), [title.value && createVNode("div", mergeAttrs({ sx: block(skeletonStyles.title) }, propsTitle.value), null), Array.from({ length: rows.value }, (_, index) => createVNode("div", mergeAttrs({
77
+ sx: block(skeletonStyles.row),
78
+ style: { width: widthOf(rowWidth.value, index, rows.value) }
79
+ }, propsRow.value), null))])]);
80
+ };
81
+ }, {
82
+ name: "Skeleton",
83
+ props: [
84
+ "loading",
85
+ "rows",
86
+ "rowWidth",
87
+ "title",
88
+ "avatar",
89
+ "avatarShape",
90
+ "animate",
91
+ "propsAvatar",
92
+ "propsTitle",
93
+ "propsRow"
94
+ ]
95
+ });
96
+ //#endregion
97
+ export { Skeleton, skeletonStyles };
98
+
99
+ //# sourceMappingURL=Skeleton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Skeleton.js","names":["_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_createVNode"],"sources":["../../src/feedback/Skeleton.tsx"],"sourcesContent":["/**\n * 骨架屏:内容加载出来之前,用灰色的块占住标题、头像、几行文字的位置,加载完换成真实内容。\n * 只用于整块内容(卡片、列表项、详情页)的占位;按钮、开关这类小组件的加载用 Loading。\n *\n * <Skeleton loading={pending} avatar title rows={3}>\n * <ArticleCard … />\n * </Skeleton>\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { css, type SxStyles } from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { VNodeChild } from 'vue';\nimport { mergeAttrs, type PartProps } from '../utils/props';\n\n/** 未声明的属性落到占位的根元素(div)上 */\nexport interface SkeletonProps extends PartProps<'div', 'role' | 'aria-busy' | 'title'> {\n /**\n * 是否显示占位;false 时显示 children\n * @default true\n */\n loading?: boolean;\n /**\n * 文字的行数\n * @default 3\n */\n rows?: number;\n /**\n * 每行的宽度;给数组时按行取,最后一行默认 60%\n * @default '100%'\n */\n rowWidth?: string | string[];\n /** 显示标题的块 */\n title?: boolean;\n /** 显示头像的块 */\n avatar?: boolean;\n /**\n * 头像的形状\n * @default 'round'\n */\n avatarShape?: 'round' | 'square';\n /**\n * 呼吸动画\n * @default true\n */\n animate?: boolean;\n /** 加载完显示的内容 */\n children?: VNodeChild;\n /** 头像块的部件属性 */\n propsAvatar?: PartProps<'div'>;\n /** 标题块的部件属性 */\n propsTitle?: PartProps<'div'>;\n /** 每行文字块的部件属性 */\n propsRow?: PartProps<'div'>;\n}\n\nconst pulse = css.keyframes({ '50%': { opacity: 0.5 } });\n\n/** Skeleton 的样式:root、animate、avatar、square、body、block、title、row */\nexport const skeletonStyles = css.create({\n root: { display: 'flex', gap: '_lg', paddingBlock: '_sm', paddingInline: '_lg' },\n animate: {\n animationName: pulse,\n animationDuration: '1.4s',\n animationTimingFunction: '_standard',\n // 减少动态效果时不闪\n animationIterationCount: { default: 'infinite', _reducedMotion: 0 },\n },\n avatar: { flex: 'none', width: '2.5em', height: '2.5em', borderRadius: '_full' },\n square: { borderRadius: '_md' },\n body: { flex: '1 1 auto', minWidth: 0, display: 'flex', flexDirection: 'column', gap: '0.85em' },\n block: { backgroundColor: '_fill', borderRadius: '_sm' },\n title: { width: '40%', height: '1.15em' },\n row: { height: '1em' },\n});\n\n/** 某一行的宽度:数组按行取;没给时最后一行(不止一行时)短一些,看起来像一段文字的结尾 */\nfunction widthOf(rowWidth: string | string[] | undefined, index: number, rows: number) {\n if (Array.isArray(rowWidth)) return rowWidth[index] ?? '100%';\n if (rowWidth) return rowWidth;\n return rows > 1 && index === rows - 1 ? '60%' : '100%';\n}\n\n/** 骨架屏(div[aria-busy] > 头像 + 标题 + 文字行);loading 为 false 时只渲染 children */\nexport const Skeleton = _component(\n ({\n loading = true,\n rows = 3,\n rowWidth,\n title = false,\n avatar = false,\n avatarShape = 'round',\n animate = true,\n children,\n propsAvatar,\n propsTitle,\n propsRow,\n ...rest\n }: SkeletonProps) =>\n () => {\n if (!loading) return children;\n const block = (sx: SxStyles): SxStyles => [\n skeletonStyles.block,\n sx,\n animate && skeletonStyles.animate,\n ];\n return (\n <div {...mergeAttrs<'div'>({ sx: skeletonStyles.root }, rest, { 'aria-busy': 'true' })}>\n {avatar && (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: block([\n skeletonStyles.avatar,\n avatarShape === 'square' && skeletonStyles.square,\n ]),\n },\n propsAvatar,\n )}\n />\n )}\n <div {...mergeAttrs<'div'>({ sx: skeletonStyles.body })}>\n {title && (\n <div {...mergeAttrs<'div'>({ sx: block(skeletonStyles.title) }, propsTitle)} />\n )}\n {Array.from({ length: rows }, (_, index) => (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: block(skeletonStyles.row),\n style: { width: widthOf(rowWidth, index, rows) },\n },\n propsRow,\n )}\n />\n ))}\n </div>\n </div>\n );\n },\n);\n"],"mappings":";;;;;;AAsDA,IAAM,QAAQ,IAAI,UAAU,EAAE,OAAO,EAAE,SAAS,GAAI,EAAE,CAAC;;AAGvD,IAAa,iBAAiB,IAAI,OAAO;CACvC,MAAM;EAAE,SAAS;EAAQ,KAAK;EAAO,cAAc;EAAO,eAAe;CAAM;CAC/E,SAAS;EACP,eAAe;EACf,mBAAmB;EACnB,yBAAyB;EAEzB,yBAAyB;GAAE,SAAS;GAAY,gBAAgB;EAAE;CACpE;CACA,QAAQ;EAAE,MAAM;EAAQ,OAAO;EAAS,QAAQ;EAAS,cAAc;CAAQ;CAC/E,QAAQ,EAAE,cAAc,MAAM;CAC9B,MAAM;EAAE,MAAM;EAAY,UAAU;EAAG,SAAS;EAAQ,eAAe;EAAU,KAAK;CAAS;CAC/F,OAAO;EAAE,iBAAiB;EAAS,cAAc;CAAM;CACvD,OAAO;EAAE,OAAO;EAAO,QAAQ;CAAS;CACxC,KAAK,EAAE,QAAQ,MAAM;AACvB,CAAC;;AAGD,SAAS,QAAQ,UAAyC,OAAe,MAAc;CACrF,IAAI,MAAM,QAAQ,QAAQ,GAAG,OAAO,SAAS,UAAU;CACvD,IAAI,UAAU,OAAO;CACrB,OAAO,OAAO,KAAK,UAAU,OAAO,IAAI,QAAQ;AAClD;;AAGA,IAAa,WAAW,0BACtB,QAAA,SAAA;CAAA,MAAA,UAAAA,aAAA,QAAA,WACY,IAAI;CAAA,MAAA,OAAAA,aAAA,QAAA,QACP,CAAC;CAAA,MAAA,WAAAA,aAAA,QAAA,UAAA;CAAA,MAAA,QAAAA,aAAA,QAAA,SAEA,KAAK;CAAA,MAAA,SAAAA,aAAA,QAAA,UACJ,KAAK;CAAA,MAAA,cAAAA,aAAA,QAAA,eACA,OAAO;CAAA,MAAA,UAAAA,aAAA,QAAA,WACX,IAAI;CAAA,MAAA,WAAAC,aAAA,MAAA;CAAA,MAAA,cAAAD,aAAA,QAAA,aAAA;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAAA,WAAAA,aAAA,QAAA,UAAA;CAAA,MAKX,OAAIE,UAAA,QAAA,MAAA,KAAA;CAAA,aAED;EACJ,IAAI,CAAA,QAAA,OAAU,OAAA,SAAA;EACd,MAAM,SAAS,OAA2B;GACxC,eAAe;GACf;GACA,QAAA,SAAW,eAAe;EAAO;EAEnC,OAAAC,YAAA,OACW,WAAkB,EAAE,IAAI,eAAe,KAAK,GAAG,MAAM,EAAE,aAAa,OAAO,CAAC,GAAC,CACnF,OAAA,SAAAA,YAAA,OAEO,WACF,EACE,IAAI,MAAM,CACR,eAAe,QACf,YAAA,UAAgB,YAAY,eAAe,MAAM,CAClD,EACH,GAAC,YAAA,KAEH,GAAC,IAAA,GAEJA,YAAA,OACQ,WAAkB,EAAE,IAAI,eAAe,KAAK,CAAC,GAAC,CACpD,MAAA,SAAAA,YAAA,OACU,WAAkB,EAAE,IAAI,MAAM,eAAe,KAAK,EAAE,GAAC,WAAA,KAAY,GAAC,IAAA,GAE5E,MAAM,KAAK,EAAE,QAAM,KAAA,MAAO,IAAI,GAAG,UAAKA,YAAA,OAE/B,WACF;GACE,IAAI,MAAM,eAAe,GAAG;GAC5B,OAAO,EAAE,OAAO,QAAO,SAAA,OAAW,OAAK,KAAA,KAAM,EAAE;EACjD,GAAC,SAAA,KAEH,GAAC,IAAA,CAEJ,CAAC,CAAA,CAAA,CAAA;CAIV;AAAC,GAAA;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACL"}
@@ -0,0 +1,89 @@
1
+ import { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';
2
+ import { type VNodeChild } from 'vue';
3
+ import { type IconData } from '../base/Icon';
4
+ import { type PartProps } from '../utils/props';
5
+ /** 轻提示的类型 */
6
+ export interface ToastTypes {
7
+ /** 只有文字 */
8
+ text: unknown;
9
+ /** 加载中:旋转指示,不自动关闭 */
10
+ loading: unknown;
11
+ /** 成功:对勾 */
12
+ success: unknown;
13
+ /** 失败:叉 */
14
+ fail: unknown;
15
+ }
16
+ /** 显示的位置 */
17
+ export interface ToastPositions {
18
+ /** 屏幕中间 */
19
+ middle: unknown;
20
+ /** 靠上 */
21
+ top: unknown;
22
+ /** 靠下 */
23
+ bottom: unknown;
24
+ }
25
+ /** 轻提示的选项(组件的 props 与 showToast 的参数共用) */
26
+ export interface ToastOptions {
27
+ /** 文字 */
28
+ message?: VNodeChild;
29
+ /**
30
+ * 类型
31
+ * @default 'text'
32
+ */
33
+ type?: keyof ToastTypes;
34
+ /** 自定义图标(代替类型自带的) */
35
+ icon?: IconData;
36
+ /**
37
+ * 显示多久(ms);0 表示不自动关闭。加载中默认不自动关闭
38
+ * @default 2000
39
+ */
40
+ duration?: number;
41
+ /**
42
+ * 位置
43
+ * @default 'middle'
44
+ */
45
+ position?: keyof ToastPositions;
46
+ /** 显示期间挡住页面的点击(加载中常用) */
47
+ forbidClick?: boolean;
48
+ /** 关闭之后 */
49
+ onClose?: () => void;
50
+ }
51
+ /** 未声明的属性落到提示框(div)上 */
52
+ export interface ToastProps extends ToastOptions, PartProps<'div', 'role'> {
53
+ /** 是否显示;双向绑定写 `_open={x}`,到时间自动关闭时写回 false */
54
+ _open?: boolean;
55
+ /** 文字(与 message 二选一) */
56
+ children?: VNodeChild;
57
+ }
58
+ /** Toast 的样式:root、withIcon、各位置、icon、blocker */
59
+ export declare const toastStyles: Readonly<{
60
+ blocker: css.CompiledStyles;
61
+ bottom: css.CompiledStyles;
62
+ icon: css.CompiledStyles;
63
+ middle: css.CompiledStyles;
64
+ root: css.CompiledStyles;
65
+ top: css.CompiledStyles;
66
+ withIcon: css.CompiledStyles;
67
+ }>;
68
+ /** 轻提示(div[role=status],渲染到浮层根) */
69
+ export declare const Toast: import("@zerodep-dev/vue-tsx-pro-tsx").TsxComponent<ToastProps>;
70
+ /** 命令式轻提示的状态:ConfigProvider 里的出口按它渲染 */
71
+ export declare const toastState: import("vue").ShallowReactive<{
72
+ open: boolean;
73
+ options: ToastOptions;
74
+ }>;
75
+ /** 命令式轻提示的控制器 */
76
+ export interface ToastHandle {
77
+ /** 关闭这个提示(已经被后一个替换时什么也不做) */
78
+ close(): void;
79
+ }
80
+ /** 显示一个轻提示;同一时间只有一个,后一个替换前一个 */
81
+ export declare function showToast(options: ToastOptions | string): ToastHandle;
82
+ /** 加载中的轻提示:不自动关闭,默认挡住点击;用返回的 close() 关 */
83
+ export declare const showLoadingToast: (options?: ToastOptions | string) => ToastHandle;
84
+ /** 成功的轻提示 */
85
+ export declare const showSuccessToast: (options: ToastOptions | string) => ToastHandle;
86
+ /** 失败的轻提示 */
87
+ export declare const showFailToast: (options: ToastOptions | string) => ToastHandle;
88
+ /** 关闭当前的轻提示 */
89
+ export declare function closeToast(): void;
@@ -0,0 +1,186 @@
1
+ import { useConfig } from "../config/context.js";
2
+ import { dataAttrs, mergeAttrs } from "../utils/props.js";
3
+ import { Icon } from "../base/Icon.js";
4
+ import { Loading } from "./Loading.js";
5
+ import { useHost } from "../utils/host.js";
6
+ import { usePresence } from "../utils/presence.js";
7
+ import { css } from "@zerodep-dev/vue-tsx-pro-tsx/styles";
8
+ import { _component, bindable, childrenProp, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
9
+ import { Teleport, createVNode, onBeforeUnmount, shallowReactive, watch } from "vue";
10
+ import { CircleCheck, CircleX } from "@lucide/icons";
11
+ //#region src/feedback/Toast.tsx
12
+ /**
13
+ * 轻提示:屏幕中间(或上、下)的一小块黑底文字,过一会儿自己消失。有文字、加载中、成功、失败四种。
14
+ *
15
+ * showToast('已复制');
16
+ * const toast = showLoadingToast('提交中…'); … toast.close();
17
+ * showSuccessToast('保存成功');
18
+ * <Toast _open={show} type="fail">网络不太好</Toast>
19
+ *
20
+ * 命令式的写法渲染在 ConfigProvider 里(页面要包一层);同一时间只有一个,后一个替换前一个。
21
+ */
22
+ /** 轻提示的类型 */
23
+ /** 显示的位置 */
24
+ /** 轻提示的选项(组件的 props 与 showToast 的参数共用) */
25
+ /** 未声明的属性落到提示框(div)上 */
26
+ /** Toast 的样式:root、withIcon、各位置、icon、blocker */
27
+ var toastStyles = css.create({
28
+ root: {
29
+ boxSizing: "border-box",
30
+ position: "fixed",
31
+ insetInlineStart: "50%",
32
+ zIndex: 3e3,
33
+ display: "flex",
34
+ flexDirection: "column",
35
+ alignItems: "center",
36
+ gap: "_sm",
37
+ maxWidth: "70%",
38
+ paddingBlock: "0.6em",
39
+ paddingInline: "1em",
40
+ borderRadius: "_lg",
41
+ backgroundColor: "_inverseSurface",
42
+ color: "_inverseText",
43
+ fontSize: "_md",
44
+ lineHeight: "1.4",
45
+ textAlign: "center",
46
+ overflowWrap: "anywhere",
47
+ pointerEvents: "none",
48
+ opacity: {
49
+ default: 0,
50
+ "[data-state=\"open\"]": 1
51
+ },
52
+ transitionProperty: "opacity, transform",
53
+ transitionDuration: "_md"
54
+ },
55
+ withIcon: {
56
+ minWidth: "7.5em",
57
+ minHeight: "7.5em",
58
+ justifyContent: "center",
59
+ padding: "_lg"
60
+ },
61
+ middle: {
62
+ insetBlockStart: "50%",
63
+ transform: {
64
+ default: "translate(-50%, -50%) scale(0.92)",
65
+ "[data-state=\"open\"]": "translate(-50%, -50%)"
66
+ }
67
+ },
68
+ top: {
69
+ insetBlockStart: "20%",
70
+ transform: "translateX(-50%)"
71
+ },
72
+ bottom: {
73
+ insetBlockEnd: "20%",
74
+ transform: "translateX(-50%)"
75
+ },
76
+ icon: { fontSize: "2.5em" },
77
+ blocker: {
78
+ position: "fixed",
79
+ inset: 0,
80
+ zIndex: 3e3
81
+ }
82
+ });
83
+ var ICONS = {
84
+ success: CircleCheck,
85
+ fail: CircleX
86
+ };
87
+ /** 轻提示(div[role=status],渲染到浮层根) */
88
+ var Toast = /*#__PURE__*/ _component((_props, _ctx) => {
89
+ const open = bindable(_props, "_open", () => false, _ctx.emit);
90
+ const message = readonlyProp(_props, "message");
91
+ const type = readonlyProp(_props, "type", "text");
92
+ const icon = readonlyProp(_props, "icon");
93
+ const duration = readonlyProp(_props, "duration");
94
+ const position = readonlyProp(_props, "position", "middle");
95
+ const forbidClick = readonlyProp(_props, "forbidClick", false);
96
+ const onClose = readonlyProp(_props, "onClose");
97
+ const children = childrenProp(_props);
98
+ const rest = restProps(_props, _ctx, false);
99
+ const host = useHost();
100
+ const config = useConfig();
101
+ const presence = usePresence(() => open.value, 240);
102
+ let timer;
103
+ const schedule = () => {
104
+ clearTimeout(timer);
105
+ const wait = duration.value ?? (type.value === "loading" ? 0 : 2e3);
106
+ if (open.value && wait > 0) timer = setTimeout(() => {
107
+ open.value = false;
108
+ onClose.value?.();
109
+ }, wait);
110
+ };
111
+ watch(() => [
112
+ open.value,
113
+ message.value,
114
+ type.value,
115
+ duration.value
116
+ ], schedule, { immediate: true });
117
+ onBeforeUnmount(() => clearTimeout(timer));
118
+ return () => {
119
+ if (!presence.mounted) return null;
120
+ const glyph = type.value === "loading" ? createVNode(Loading, { "size": 36 }, null) : icon.value || type.value !== "text" ? createVNode(Icon, {
121
+ "icon": icon.value ?? ICONS[type.value],
122
+ "sx": toastStyles.icon
123
+ }, null) : null;
124
+ return createVNode(Teleport, { "to": host.overlayRoot() }, { default: () => [forbidClick.value && createVNode("div", mergeAttrs({ sx: toastStyles.blocker }), null), createVNode("div", mergeAttrs({ sx: [
125
+ config.rootSx,
126
+ toastStyles.root,
127
+ toastStyles[position.value],
128
+ glyph && toastStyles.withIcon
129
+ ] }, rest, {
130
+ role: "status",
131
+ ...dataAttrs({ "data-state": presence.visible ? "open" : "closed" })
132
+ }), [glyph, message.value ?? children.value])] });
133
+ };
134
+ }, {
135
+ name: "Toast",
136
+ props: [
137
+ "_open",
138
+ "onUpdate:_open",
139
+ "message",
140
+ "type",
141
+ "icon",
142
+ "duration",
143
+ "position",
144
+ "forbidClick",
145
+ "onClose"
146
+ ]
147
+ });
148
+ /** 命令式轻提示的状态:ConfigProvider 里的出口按它渲染 */
149
+ var toastState = shallowReactive({
150
+ open: false,
151
+ options: {}
152
+ });
153
+ /** 命令式轻提示的控制器 */
154
+ /** 显示一个轻提示;同一时间只有一个,后一个替换前一个 */
155
+ function showToast(options) {
156
+ const next = typeof options === "string" ? { message: options } : options;
157
+ toastState.options = next;
158
+ toastState.open = true;
159
+ return { close() {
160
+ if (toastState.options === next) toastState.open = false;
161
+ } };
162
+ }
163
+ /** 加载中的轻提示:不自动关闭,默认挡住点击;用返回的 close() 关 */
164
+ var showLoadingToast = (options = {}) => showToast({
165
+ forbidClick: true,
166
+ ...typeof options === "string" ? { message: options } : options,
167
+ type: "loading"
168
+ });
169
+ /** 成功的轻提示 */
170
+ var showSuccessToast = (options) => showToast({
171
+ ...typeof options === "string" ? { message: options } : options,
172
+ type: "success"
173
+ });
174
+ /** 失败的轻提示 */
175
+ var showFailToast = (options) => showToast({
176
+ ...typeof options === "string" ? { message: options } : options,
177
+ type: "fail"
178
+ });
179
+ /** 关闭当前的轻提示 */
180
+ function closeToast() {
181
+ toastState.open = false;
182
+ }
183
+ //#endregion
184
+ export { Toast, closeToast, showFailToast, showLoadingToast, showSuccessToast, showToast, toastState, toastStyles };
185
+
186
+ //# sourceMappingURL=Toast.js.map