@manohub/ui 0.6.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 (169) hide show
  1. package/README.md +141 -0
  2. package/dist/components/avatar/index.d.ts +283 -0
  3. package/dist/components/avatar/index.js +167 -0
  4. package/dist/components/badge/index.d.ts +132 -0
  5. package/dist/components/badge/index.js +99 -0
  6. package/dist/components/button/index.d.ts +578 -0
  7. package/dist/components/button/index.js +191 -0
  8. package/dist/components/capsule/index.d.ts +161 -0
  9. package/dist/components/capsule/index.js +101 -0
  10. package/dist/components/card/index.d.ts +456 -0
  11. package/dist/components/card/index.js +231 -0
  12. package/dist/components/checkbox/index.d.ts +331 -0
  13. package/dist/components/checkbox/index.js +165 -0
  14. package/dist/components/code/index.d.ts +134 -0
  15. package/dist/components/code/index.js +117 -0
  16. package/dist/components/code-editor/index.d.ts +163 -0
  17. package/dist/components/code-editor/index.js +98 -0
  18. package/dist/components/collapse/index.d.ts +125 -0
  19. package/dist/components/collapse/index.js +95 -0
  20. package/dist/components/control-size.d.ts +8 -0
  21. package/dist/components/dialog/index.d.ts +224 -0
  22. package/dist/components/dialog/index.js +194 -0
  23. package/dist/components/divider/index.d.ts +64 -0
  24. package/dist/components/divider/index.js +62 -0
  25. package/dist/components/drawer/index.d.ts +226 -0
  26. package/dist/components/drawer/index.js +190 -0
  27. package/dist/components/filter/index.d.ts +391 -0
  28. package/dist/components/filter/index.js +203 -0
  29. package/dist/components/form/index.d.ts +704 -0
  30. package/dist/components/form/index.js +329 -0
  31. package/dist/components/input/index.d.ts +980 -0
  32. package/dist/components/input/index.js +334 -0
  33. package/dist/components/layout/index.d.ts +162 -0
  34. package/dist/components/layout/index.js +100 -0
  35. package/dist/components/list-view/index.d.ts +563 -0
  36. package/dist/components/list-view/index.js +221 -0
  37. package/dist/components/loading/index.d.ts +77 -0
  38. package/dist/components/loading/index.js +40 -0
  39. package/dist/components/nav/index.d.ts +293 -0
  40. package/dist/components/nav/index.js +164 -0
  41. package/dist/components/notice/index.d.ts +137 -0
  42. package/dist/components/notice/index.js +101 -0
  43. package/dist/components/notification/index.d.ts +118 -0
  44. package/dist/components/notification/index.js +99 -0
  45. package/dist/components/page/index.d.ts +276 -0
  46. package/dist/components/page/index.js +351 -0
  47. package/dist/components/page/split-resize.d.ts +42 -0
  48. package/dist/components/page/split-resize.js +36 -0
  49. package/dist/components/pagination/index.d.ts +171 -0
  50. package/dist/components/pagination/index.js +226 -0
  51. package/dist/components/panel/index.d.ts +401 -0
  52. package/dist/components/panel/index.js +193 -0
  53. package/dist/components/part-padding.d.ts +27 -0
  54. package/dist/components/part-padding.js +23 -0
  55. package/dist/components/query-state/index.d.ts +207 -0
  56. package/dist/components/query-state/index.js +181 -0
  57. package/dist/components/radio/index.d.ts +473 -0
  58. package/dist/components/radio/index.js +250 -0
  59. package/dist/components/search/index.d.ts +167 -0
  60. package/dist/components/search/index.js +105 -0
  61. package/dist/components/select/index.d.ts +252 -0
  62. package/dist/components/select/index.js +281 -0
  63. package/dist/components/skeleton/index.d.ts +88 -0
  64. package/dist/components/skeleton/index.js +56 -0
  65. package/dist/components/steps/index.d.ts +199 -0
  66. package/dist/components/steps/index.js +117 -0
  67. package/dist/components/switch/index.d.ts +150 -0
  68. package/dist/components/switch/index.js +103 -0
  69. package/dist/components/table/index.d.ts +351 -0
  70. package/dist/components/table/index.js +286 -0
  71. package/dist/components/tabs/index.d.ts +179 -0
  72. package/dist/components/tabs/index.js +153 -0
  73. package/dist/components/tabset/index.d.ts +105 -0
  74. package/dist/components/tabset/index.js +68 -0
  75. package/dist/components/tag/index.d.ts +203 -0
  76. package/dist/components/tag/index.js +144 -0
  77. package/dist/components/toast/index.d.ts +271 -0
  78. package/dist/components/toast/index.js +116 -0
  79. package/dist/components/tooltip/index.d.ts +126 -0
  80. package/dist/components/tooltip/index.js +97 -0
  81. package/dist/components/tree/index.d.ts +317 -0
  82. package/dist/components/tree/index.js +251 -0
  83. package/dist/components/upload/index.d.ts +384 -0
  84. package/dist/components/upload/index.js +172 -0
  85. package/dist/index.d.ts +63 -0
  86. package/dist/index.js +102 -0
  87. package/dist/services/base.d.ts +21 -0
  88. package/dist/services/base.js +42 -0
  89. package/dist/services/dialog.d.ts +41 -0
  90. package/dist/services/dialog.js +83 -0
  91. package/dist/services/index.d.ts +12 -0
  92. package/dist/services/loading.d.ts +21 -0
  93. package/dist/services/loading.js +41 -0
  94. package/dist/services/toast.d.ts +40 -0
  95. package/dist/services/toast.js +74 -0
  96. package/dist/styles/components/avatar.css +128 -0
  97. package/dist/styles/components/avatar.tokens.css +24 -0
  98. package/dist/styles/components/badge.css +237 -0
  99. package/dist/styles/components/button.css +454 -0
  100. package/dist/styles/components/button.tokens.css +56 -0
  101. package/dist/styles/components/capsule.css +120 -0
  102. package/dist/styles/components/capsule.tokens.css +27 -0
  103. package/dist/styles/components/card.css +192 -0
  104. package/dist/styles/components/checkbox.css +145 -0
  105. package/dist/styles/components/checkbox.tokens.css +26 -0
  106. package/dist/styles/components/code-editor.css +91 -0
  107. package/dist/styles/components/code-editor.tokens.css +24 -0
  108. package/dist/styles/components/code.css +153 -0
  109. package/dist/styles/components/code.tokens.css +29 -0
  110. package/dist/styles/components/collapse.css +62 -0
  111. package/dist/styles/components/collapse.tokens.css +20 -0
  112. package/dist/styles/components/dialog.css +106 -0
  113. package/dist/styles/components/dialog.tokens.css +24 -0
  114. package/dist/styles/components/divider.css +71 -0
  115. package/dist/styles/components/divider.tokens.css +21 -0
  116. package/dist/styles/components/drawer.css +143 -0
  117. package/dist/styles/components/drawer.tokens.css +22 -0
  118. package/dist/styles/components/filter.css +41 -0
  119. package/dist/styles/components/filter.tokens.css +14 -0
  120. package/dist/styles/components/form.css +220 -0
  121. package/dist/styles/components/form.tokens.css +15 -0
  122. package/dist/styles/components/input.css +445 -0
  123. package/dist/styles/components/input.tokens.css +41 -0
  124. package/dist/styles/components/layout.css +198 -0
  125. package/dist/styles/components/list-view.css +244 -0
  126. package/dist/styles/components/loading.css +76 -0
  127. package/dist/styles/components/loading.tokens.css +23 -0
  128. package/dist/styles/components/nav.css +48 -0
  129. package/dist/styles/components/notice.css +153 -0
  130. package/dist/styles/components/notice.tokens.css +17 -0
  131. package/dist/styles/components/notification.css +159 -0
  132. package/dist/styles/components/notification.tokens.css +37 -0
  133. package/dist/styles/components/page.css +473 -0
  134. package/dist/styles/components/pagination.css +297 -0
  135. package/dist/styles/components/pagination.tokens.css +32 -0
  136. package/dist/styles/components/panel.css +77 -0
  137. package/dist/styles/components/query-state.css +88 -0
  138. package/dist/styles/components/radio.css +271 -0
  139. package/dist/styles/components/radio.tokens.css +26 -0
  140. package/dist/styles/components/search.css +30 -0
  141. package/dist/styles/components/select.css +242 -0
  142. package/dist/styles/components/select.tokens.css +33 -0
  143. package/dist/styles/components/skeleton.css +60 -0
  144. package/dist/styles/components/skeleton.tokens.css +27 -0
  145. package/dist/styles/components/steps.css +157 -0
  146. package/dist/styles/components/steps.tokens.css +42 -0
  147. package/dist/styles/components/switch.css +165 -0
  148. package/dist/styles/components/switch.tokens.css +26 -0
  149. package/dist/styles/components/table.css +439 -0
  150. package/dist/styles/components/table.tokens.css +41 -0
  151. package/dist/styles/components/tabs.css +234 -0
  152. package/dist/styles/components/tabs.tokens.css +30 -0
  153. package/dist/styles/components/tag.css +200 -0
  154. package/dist/styles/components/tag.tokens.css +30 -0
  155. package/dist/styles/components/toast.css +150 -0
  156. package/dist/styles/components/toast.tokens.css +26 -0
  157. package/dist/styles/components/tooltip.css +89 -0
  158. package/dist/styles/components/tooltip.tokens.css +25 -0
  159. package/dist/styles/components/tree.css +139 -0
  160. package/dist/styles/components/tree.tokens.css +20 -0
  161. package/dist/styles/components/upload.css +150 -0
  162. package/dist/styles/components/upload.tokens.css +27 -0
  163. package/dist/styles/index.css +149 -0
  164. package/dist/styles/operations/collapse.css +84 -0
  165. package/dist/styles/operations/popup.css +189 -0
  166. package/dist/styles/operations/tabset.css +57 -0
  167. package/dist/utils/state-illustration.d.ts +31 -0
  168. package/dist/utils/state-illustration.js +156 -0
  169. package/package.json +62 -0
@@ -0,0 +1,252 @@
1
+ import { type PropType, type SlotsType, type VNodeChild } from 'vue';
2
+ import type { ControlSize } from '../control-size';
3
+ import { type InputStatus } from '../input';
4
+ export interface SelectOption {
5
+ label: string;
6
+ value: string | number;
7
+ disabled?: boolean;
8
+ /** 分组标题用的标记(内部判别用,消费方不必写) */
9
+ options?: never;
10
+ }
11
+ /** 分组选项(`{ label: '进行中', options: [...] }`,组内选项与平铺选项同构) */
12
+ export interface SelectOptionGroup {
13
+ label: string;
14
+ options: SelectOption[];
15
+ }
16
+ export type SelectItem = SelectOption | SelectOptionGroup;
17
+ export type SelectValue = string | number | Array<string | number>;
18
+ /**
19
+ * 选择器(**自绘**,不封装 farris)。
20
+ *
21
+ * ## 触发器复用的是输入框的「触发器形态」(不自绘框)
22
+ *
23
+ * 触发器由 `<Input as="button">` 渲染 —— 框 / 内距 / 边 / 圆角 / 字号 / hover / 聚焦 / 禁用 /
24
+ * 校验色(红·黄)/ **内建清除位** / **右侧图标位**全部来自输入面与它的表壳,本件只提供值 / 标签的排版。
25
+ * 好处是「像输入框」这件事只剩一份规格(改输入框即自动跟随),且清除与右侧图标不必再自绘一套。
26
+ * 三条落点各有其处:`class` → 表壳(显式锚 + 本件对壳的微调)、`controlClass` → 壳内控件(本件排版类)、
27
+ * `controlAttrs` → 控件上的原生语义与键盘契约(`popovertarget` / `aria-*` / `keydown`)。
28
+ * 触发器仍是原生 `<button>`(`popovertarget` 只认 button),故零 JS 开合与隐式锚路径不变;
29
+ * 面板改挂 `mh-popup--anchored`:锚显式钉在**表壳**上 —— 否则隐式锚会缩成壳内控件,
30
+ * 面板不再与整件同宽(`anchor-size(width)` 取的是锚的宽)。
31
+ *
32
+ * ## 浮层边界(与「popup 只给零 JS 面」的取舍一致)
33
+ *
34
+ * 面板走**原生 popover**:`<button popovertarget>` + `<div popover>`,开合 / Esc / 点外部关闭 /
35
+ * 顶层渲染 / 定位档全部由浏览器与 `styles/operations/popup.css` 承担,本件**不实现**第二套浮层行为
36
+ * (不做 JS 定位、不做 resize 监听)。本件只补原生做不到的:选中值回填、多选数组的增删与收纳、
37
+ * 搜索过滤与「创建」项、以及键盘的下拉导航(↑↓ 移动高亮行)。
38
+ *
39
+ * ## 状态三源等价
40
+ *
41
+ * 选中行 = `[data-state='selected']`(组件下发)/ 原生 `input:checked`(静态页零 JS 路径)/
42
+ * `.is-selected`(规格冻结档),样式层只写一份声明。
43
+ *
44
+ * @example 单选
45
+ * <Select options={opts} modelValue={v} placeholder="请选择" onChange={setV} />
46
+ *
47
+ * @example 多选(超出 maxTagCount 收纳成「其他 n 项」)
48
+ * <Select multiple maxTagCount={2} options={opts} modelValue={list} onChange={setList} />
49
+ *
50
+ * @example 分组 + 搜索 + 可创建
51
+ * <Select searchable allowCreate multiple options={grouped} modelValue={list} onChange={setList} />
52
+ */
53
+ export declare const Select: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
54
+ /** 选项表(平铺项与分组项可混用) */
55
+ options: {
56
+ type: PropType<SelectItem[]>;
57
+ default: () => never[];
58
+ };
59
+ /** 受控值(多选时是数组) */
60
+ modelValue: {
61
+ type: PropType<SelectValue | undefined>;
62
+ default: undefined;
63
+ };
64
+ /** 非受控初始值 */
65
+ defaultValue: {
66
+ type: PropType<SelectValue | undefined>;
67
+ default: undefined;
68
+ };
69
+ placeholder: {
70
+ type: StringConstructor;
71
+ default: string;
72
+ };
73
+ /** 多选(值变数组,选中项渲染成标签) */
74
+ multiple: {
75
+ type: BooleanConstructor;
76
+ default: boolean;
77
+ };
78
+ /** 多选时最多显示几个标签,其余收纳(0 = 不限) */
79
+ maxTagCount: {
80
+ type: NumberConstructor;
81
+ default: number;
82
+ };
83
+ /** 收纳标签的文案(`{count}` 会被替换成条数);缺省「其他 n 项」 */
84
+ maxTagPlaceholder: {
85
+ type: StringConstructor;
86
+ default: undefined;
87
+ };
88
+ /** 搜索:`true` 强制开;不给时选项多于 8 条自动开 */
89
+ searchable: {
90
+ type: BooleanConstructor;
91
+ default: boolean;
92
+ };
93
+ /** 自定义过滤(缺省按 `label.includes(keyword)`) */
94
+ filterOption: {
95
+ type: PropType<(option: SelectOption, keyword: string) => boolean>;
96
+ default: undefined;
97
+ };
98
+ /** 可创建:搜索词无命中时出现「创建 xxx」(仅多选有意义) */
99
+ allowCreate: {
100
+ type: BooleanConstructor;
101
+ default: boolean;
102
+ };
103
+ clearable: {
104
+ type: BooleanConstructor;
105
+ default: boolean;
106
+ };
107
+ disabled: {
108
+ type: BooleanConstructor;
109
+ default: boolean;
110
+ };
111
+ /** 尺寸档(24 / 28 / 32) */
112
+ size: {
113
+ type: PropType<ControlSize>;
114
+ default: string;
115
+ };
116
+ /** 校验状态(红 / 黄;与 Input 同一词表) */
117
+ status: {
118
+ type: PropType<InputStatus>;
119
+ default: undefined;
120
+ };
121
+ /** 加载中(面板里出一行提示,不渲染选项) */
122
+ loading: {
123
+ type: BooleanConstructor;
124
+ default: boolean;
125
+ };
126
+ emptyText: {
127
+ type: StringConstructor;
128
+ default: string;
129
+ };
130
+ /** 值变更回调;参数为新值(多选为数组) */
131
+ onChange: {
132
+ type: PropType<(v: SelectValue) => void>;
133
+ default: undefined;
134
+ };
135
+ /** 搜索词变更(用于远程搜索:拿到关键词自己去换 options) */
136
+ onSearch: {
137
+ type: PropType<(keyword: string) => void>;
138
+ default: undefined;
139
+ };
140
+ }>, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
141
+ 'update:modelValue': (v: SelectValue) => true;
142
+ }, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
143
+ /** 选项表(平铺项与分组项可混用) */
144
+ options: {
145
+ type: PropType<SelectItem[]>;
146
+ default: () => never[];
147
+ };
148
+ /** 受控值(多选时是数组) */
149
+ modelValue: {
150
+ type: PropType<SelectValue | undefined>;
151
+ default: undefined;
152
+ };
153
+ /** 非受控初始值 */
154
+ defaultValue: {
155
+ type: PropType<SelectValue | undefined>;
156
+ default: undefined;
157
+ };
158
+ placeholder: {
159
+ type: StringConstructor;
160
+ default: string;
161
+ };
162
+ /** 多选(值变数组,选中项渲染成标签) */
163
+ multiple: {
164
+ type: BooleanConstructor;
165
+ default: boolean;
166
+ };
167
+ /** 多选时最多显示几个标签,其余收纳(0 = 不限) */
168
+ maxTagCount: {
169
+ type: NumberConstructor;
170
+ default: number;
171
+ };
172
+ /** 收纳标签的文案(`{count}` 会被替换成条数);缺省「其他 n 项」 */
173
+ maxTagPlaceholder: {
174
+ type: StringConstructor;
175
+ default: undefined;
176
+ };
177
+ /** 搜索:`true` 强制开;不给时选项多于 8 条自动开 */
178
+ searchable: {
179
+ type: BooleanConstructor;
180
+ default: boolean;
181
+ };
182
+ /** 自定义过滤(缺省按 `label.includes(keyword)`) */
183
+ filterOption: {
184
+ type: PropType<(option: SelectOption, keyword: string) => boolean>;
185
+ default: undefined;
186
+ };
187
+ /** 可创建:搜索词无命中时出现「创建 xxx」(仅多选有意义) */
188
+ allowCreate: {
189
+ type: BooleanConstructor;
190
+ default: boolean;
191
+ };
192
+ clearable: {
193
+ type: BooleanConstructor;
194
+ default: boolean;
195
+ };
196
+ disabled: {
197
+ type: BooleanConstructor;
198
+ default: boolean;
199
+ };
200
+ /** 尺寸档(24 / 28 / 32) */
201
+ size: {
202
+ type: PropType<ControlSize>;
203
+ default: string;
204
+ };
205
+ /** 校验状态(红 / 黄;与 Input 同一词表) */
206
+ status: {
207
+ type: PropType<InputStatus>;
208
+ default: undefined;
209
+ };
210
+ /** 加载中(面板里出一行提示,不渲染选项) */
211
+ loading: {
212
+ type: BooleanConstructor;
213
+ default: boolean;
214
+ };
215
+ emptyText: {
216
+ type: StringConstructor;
217
+ default: string;
218
+ };
219
+ /** 值变更回调;参数为新值(多选为数组) */
220
+ onChange: {
221
+ type: PropType<(v: SelectValue) => void>;
222
+ default: undefined;
223
+ };
224
+ /** 搜索词变更(用于远程搜索:拿到关键词自己去换 options) */
225
+ onSearch: {
226
+ type: PropType<(keyword: string) => void>;
227
+ default: undefined;
228
+ };
229
+ }>> & Readonly<{
230
+ "onUpdate:modelValue"?: ((v: SelectValue) => any) | undefined;
231
+ }>, {
232
+ size: ControlSize;
233
+ multiple: boolean;
234
+ disabled: boolean;
235
+ onChange: (v: SelectValue) => void;
236
+ loading: boolean;
237
+ options: SelectItem[];
238
+ defaultValue: SelectValue | undefined;
239
+ placeholder: string;
240
+ modelValue: SelectValue | undefined;
241
+ status: InputStatus;
242
+ clearable: boolean;
243
+ onSearch: (keyword: string) => void;
244
+ maxTagCount: number;
245
+ maxTagPlaceholder: string;
246
+ searchable: boolean;
247
+ filterOption: (option: SelectOption, keyword: string) => boolean;
248
+ allowCreate: boolean;
249
+ emptyText: string;
250
+ }, SlotsType<{
251
+ empty?: () => VNodeChild;
252
+ }>, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -0,0 +1,281 @@
1
+ import { defineComponent, ref, computed, createVNode, Fragment, mergeProps, createTextVNode } from "vue";
2
+ import { Icon } from "@manohub/icon";
3
+ import { Input } from "../input/index.js";
4
+ const isGroup = (item) => Array.isArray(item.options);
5
+ const Select = /* @__PURE__ */ defineComponent({
6
+ name: "Select",
7
+ inheritAttrs: false,
8
+ props: {
9
+ /** 选项表(平铺项与分组项可混用) */
10
+ options: {
11
+ type: Array,
12
+ default: () => []
13
+ },
14
+ /** 受控值(多选时是数组) */
15
+ modelValue: {
16
+ type: [String, Number, Array],
17
+ default: void 0
18
+ },
19
+ /** 非受控初始值 */
20
+ defaultValue: {
21
+ type: [String, Number, Array],
22
+ default: void 0
23
+ },
24
+ placeholder: {
25
+ type: String,
26
+ default: "请选择"
27
+ },
28
+ /** 多选(值变数组,选中项渲染成标签) */
29
+ multiple: {
30
+ type: Boolean,
31
+ default: false
32
+ },
33
+ /** 多选时最多显示几个标签,其余收纳(0 = 不限) */
34
+ maxTagCount: {
35
+ type: Number,
36
+ default: 3
37
+ },
38
+ /** 收纳标签的文案(`{count}` 会被替换成条数);缺省「其他 n 项」 */
39
+ maxTagPlaceholder: {
40
+ type: String,
41
+ default: void 0
42
+ },
43
+ /** 搜索:`true` 强制开;不给时选项多于 8 条自动开 */
44
+ searchable: {
45
+ type: Boolean,
46
+ default: false
47
+ },
48
+ /** 自定义过滤(缺省按 `label.includes(keyword)`) */
49
+ filterOption: {
50
+ type: Function,
51
+ default: void 0
52
+ },
53
+ /** 可创建:搜索词无命中时出现「创建 xxx」(仅多选有意义) */
54
+ allowCreate: {
55
+ type: Boolean,
56
+ default: false
57
+ },
58
+ clearable: {
59
+ type: Boolean,
60
+ default: false
61
+ },
62
+ disabled: {
63
+ type: Boolean,
64
+ default: false
65
+ },
66
+ /** 尺寸档(24 / 28 / 32) */
67
+ size: {
68
+ type: String,
69
+ default: "md"
70
+ },
71
+ /** 校验状态(红 / 黄;与 Input 同一词表) */
72
+ status: {
73
+ type: String,
74
+ default: void 0
75
+ },
76
+ /** 加载中(面板里出一行提示,不渲染选项) */
77
+ loading: {
78
+ type: Boolean,
79
+ default: false
80
+ },
81
+ emptyText: {
82
+ type: String,
83
+ default: "暂无数据"
84
+ },
85
+ /** 值变更回调;参数为新值(多选为数组) */
86
+ onChange: {
87
+ type: Function,
88
+ default: void 0
89
+ },
90
+ /** 搜索词变更(用于远程搜索:拿到关键词自己去换 options) */
91
+ onSearch: {
92
+ type: Function,
93
+ default: void 0
94
+ }
95
+ },
96
+ emits: {
97
+ "update:modelValue": (v) => true
98
+ },
99
+ slots: Object,
100
+ setup(props, {
101
+ emit,
102
+ slots,
103
+ attrs
104
+ }) {
105
+ const panelId = `mh-select-${Math.random().toString(36).slice(2, 10)}`;
106
+ const panelRef = ref(null);
107
+ const inner = ref(props.defaultValue);
108
+ const keyword = ref("");
109
+ const current = computed(() => props.modelValue ?? inner.value);
110
+ const selectedValues = computed(() => {
111
+ const value = current.value;
112
+ if (value === void 0 || value === null || value === "") return [];
113
+ return Array.isArray(value) ? value : [value];
114
+ });
115
+ const flatOptions = computed(() => {
116
+ const list = [];
117
+ for (const item of props.options) {
118
+ if (isGroup(item)) list.push(...item.options);
119
+ else list.push(item);
120
+ }
121
+ return list;
122
+ });
123
+ const selectedOptions = computed(() => flatOptions.value.filter((option) => selectedValues.value.includes(option.value)));
124
+ const showSearch = computed(() => props.searchable || flatOptions.value.length > 8);
125
+ const matched = (option, kw) => props.filterOption ? props.filterOption(option, kw) : option.label.includes(kw);
126
+ const visibleOptions = computed(() => {
127
+ const kw = keyword.value;
128
+ if (!kw) return props.options;
129
+ const result = [];
130
+ for (const item of props.options) {
131
+ if (isGroup(item)) {
132
+ const kept = item.options.filter((option) => matched(option, kw));
133
+ if (kept.length) result.push({
134
+ label: item.label,
135
+ options: kept
136
+ });
137
+ } else if (matched(item, kw)) {
138
+ result.push(item);
139
+ }
140
+ }
141
+ return result;
142
+ });
143
+ const creatable = computed(() => props.allowCreate && props.multiple && !!keyword.value && !flatOptions.value.some((option) => option.label === keyword.value));
144
+ const visibleTags = computed(() => props.maxTagCount > 0 ? selectedOptions.value.slice(0, props.maxTagCount) : selectedOptions.value);
145
+ const foldedCount = computed(() => selectedOptions.value.length - visibleTags.value.length);
146
+ const foldText = computed(() => props.maxTagPlaceholder ? props.maxTagPlaceholder.replace("{count}", String(foldedCount.value)) : `其他 ${foldedCount.value} 项`);
147
+ const commit = (value) => {
148
+ if (props.modelValue === void 0) inner.value = value;
149
+ emit("update:modelValue", value);
150
+ props.onChange?.(value);
151
+ };
152
+ const hidePanel = () => panelRef.value?.hidePopover?.();
153
+ const pick = (option) => {
154
+ if (option.disabled) return;
155
+ if (props.multiple) {
156
+ const next = selectedValues.value.includes(option.value) ? selectedValues.value.filter((value) => value !== option.value) : [...selectedValues.value, option.value];
157
+ commit(next);
158
+ return;
159
+ }
160
+ commit(option.value);
161
+ hidePanel();
162
+ };
163
+ const createFromKeyword = () => {
164
+ const value = keyword.value.trim();
165
+ if (!value) return;
166
+ commit([...selectedValues.value, value]);
167
+ keyword.value = "";
168
+ };
169
+ const clear = () => commit(props.multiple ? [] : "");
170
+ const optionEls = () => panelRef.value ? [...panelRef.value.querySelectorAll(".mh-select__option")].filter((el) => !el.disabled) : [];
171
+ const moveFocus = (step) => {
172
+ const items = optionEls();
173
+ if (!items.length) return;
174
+ const at = items.indexOf(document.activeElement);
175
+ const next = at < 0 ? step > 0 ? 0 : items.length - 1 : (at + step + items.length) % items.length;
176
+ items[next].focus();
177
+ };
178
+ const onTriggerKeydown = (event) => {
179
+ if (event.key === "Delete" && props.clearable) clear();
180
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
181
+ event.preventDefault();
182
+ if (!props.disabled) {
183
+ panelRef.value?.showPopover?.();
184
+ requestAnimationFrame(() => moveFocus(event.key === "ArrowDown" ? 0 : -1));
185
+ }
186
+ }
187
+ };
188
+ const onPanelKeydown = (event) => {
189
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
190
+ event.preventDefault();
191
+ moveFocus(event.key === "ArrowDown" ? 1 : -1);
192
+ }
193
+ };
194
+ const renderOption = (option) => createVNode("button", {
195
+ "key": String(option.value),
196
+ "type": "button",
197
+ "class": ["mh-select__option", option.disabled ? "is-disabled" : ""].filter(Boolean).join(" "),
198
+ "data-state": selectedValues.value.includes(option.value) ? "selected" : "unselected",
199
+ "disabled": option.disabled,
200
+ "role": "option",
201
+ "aria-selected": selectedValues.value.includes(option.value) ? "true" : "false",
202
+ "onClick": () => pick(option)
203
+ }, [option.label]);
204
+ return () => {
205
+ const hasValue = selectedValues.value.length > 0;
206
+ return createVNode("div", {
207
+ "class": ["mh-popup-host", attrs.class].filter(Boolean).join(" "),
208
+ "style": attrs.style
209
+ }, [createVNode(Input, {
210
+ "as": "button",
211
+ "size": props.size,
212
+ "status": props.status,
213
+ "disabled": props.disabled,
214
+ "clearable": props.clearable,
215
+ "hasValue": hasValue,
216
+ "class": "mh-popup-anchor mh-select-host",
217
+ "controlClass": ["mh-select", props.multiple ? "mh-select--multiple" : "", props.size === "md" ? "" : `mh-select--${props.size}`].filter(Boolean).join(" "),
218
+ "controlAttrs": {
219
+ popovertarget: panelId,
220
+ "aria-haspopup": "listbox",
221
+ "aria-expanded": "false",
222
+ onKeydown: onTriggerKeydown
223
+ },
224
+ "onChange": clear
225
+ }, {
226
+ default: () => props.multiple ? createVNode(Fragment, null, [visibleTags.value.map((option) => createVNode("span", {
227
+ "key": String(option.value),
228
+ "class": "mh-select__chip"
229
+ }, [option.label])), foldedCount.value > 0 ? createVNode("span", {
230
+ "class": "mh-select__chip mh-select__chip--fold"
231
+ }, [foldText.value]) : null, !hasValue ? createVNode("span", {
232
+ "class": "mh-select__value mh-select__value--placeholder"
233
+ }, [props.placeholder]) : null]) : createVNode("span", {
234
+ "class": ["mh-select__value", hasValue ? "" : "mh-select__value--placeholder"].filter(Boolean).join(" ")
235
+ }, [selectedOptions.value[0]?.label ?? props.placeholder]),
236
+ /* 箭头沿用输入框的右侧图标位(本次不开放自定义入口) */
237
+ suffix: () => createVNode(Icon, {
238
+ "name": "chevron-down",
239
+ "class": "mh-select__caret mh-popup-caret"
240
+ }, null)
241
+ }), createVNode("div", mergeProps({
242
+ "ref": panelRef,
243
+ "id": panelId
244
+ }, {
245
+ popover: "auto"
246
+ }, {
247
+ "class": "mh-popup mh-select__panel mh-popup--anchored",
248
+ "data-side": "bottom",
249
+ "data-align": "start",
250
+ "role": "listbox",
251
+ "onKeydown": onPanelKeydown
252
+ }), [showSearch.value ? createVNode("div", {
253
+ "class": "mh-select__search"
254
+ }, [createVNode("input", {
255
+ "class": "mh-input",
256
+ "value": keyword.value,
257
+ "placeholder": "搜索",
258
+ "onInput": (event) => {
259
+ keyword.value = event.target.value;
260
+ props.onSearch?.(keyword.value);
261
+ }
262
+ }, null)]) : null, props.loading ? createVNode("span", {
263
+ "class": "mh-select__option mh-select__option--hint"
264
+ }, [createTextVNode("加载中…")]) : visibleOptions.value.length === 0 && !creatable.value ? slots.empty ? slots.empty() : createVNode("span", {
265
+ "class": "mh-select__option mh-select__option--empty"
266
+ }, [props.emptyText]) : createVNode(Fragment, null, [visibleOptions.value.map((item, index) => isGroup(item) ? createVNode("div", {
267
+ "key": `group-${index}`,
268
+ "class": "mh-select__group"
269
+ }, [createVNode("span", {
270
+ "class": "mh-select__group-label"
271
+ }, [item.label]), item.options.map((option) => renderOption(option))]) : renderOption(item)), creatable.value ? createVNode("button", {
272
+ "type": "button",
273
+ "class": "mh-select__option mh-select__add",
274
+ "onClick": createFromKeyword
275
+ }, [createTextVNode("创建「"), keyword.value.trim(), createTextVNode("」")]) : null])])]);
276
+ };
277
+ }
278
+ });
279
+ export {
280
+ Select
281
+ };
@@ -0,0 +1,88 @@
1
+ import { type PropType, type SlotsType, type VNodeChild } from 'vue';
2
+ export interface SkeletonProps {
3
+ /** 行数(缺省 1;行间距由样式层的 `--ui-skeleton-gap` 给) */
4
+ rows?: number;
5
+ /** 块状档:给一段区间的占位(高由内容/`height` 决定,不再按行高定尺) */
6
+ block?: boolean;
7
+ /** 行宽(缺省 100%;传数字按 px) */
8
+ width?: string | number;
9
+ /** 行高(缺省与行高一致;传数字按 px) */
10
+ height?: string | number;
11
+ /** 胶囊档:圆角取满(标签 / 头像 / 次按钮的占位) */
12
+ round?: boolean;
13
+ /** 是否开启微光动画(缺省开;静态摆样、截图与打印时关掉) */
14
+ active?: boolean;
15
+ }
16
+ /**
17
+ * 骨架屏(骨架行 / 列表骨架 / 块状骨架)。
18
+ *
19
+ * 微光动画与色值在本包**只有一份**(`styles/components/skeleton.css` 的 `--ui-skeleton-*` 与
20
+ * `@keyframes mh-skeleton-shimmer`),`QueryState` 的加载态复用它 —— 别处不要再写一份
21
+ * (两处各写一份必然漂,同一屏会出现两种呼吸节奏)。
22
+ *
23
+ * @example 列表骨架(5 行)
24
+ * <Skeleton :rows="5" />
25
+ *
26
+ * @example 卡片体占位
27
+ * <Skeleton block height={120} />
28
+ */
29
+ export declare const Skeleton: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
30
+ rows: {
31
+ type: NumberConstructor;
32
+ default: number;
33
+ };
34
+ block: {
35
+ type: BooleanConstructor;
36
+ default: boolean;
37
+ };
38
+ width: {
39
+ type: PropType<string | number>;
40
+ default: undefined;
41
+ };
42
+ height: {
43
+ type: PropType<string | number>;
44
+ default: undefined;
45
+ };
46
+ round: {
47
+ type: BooleanConstructor;
48
+ default: boolean;
49
+ };
50
+ active: {
51
+ type: BooleanConstructor;
52
+ default: boolean;
53
+ };
54
+ }>, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
55
+ rows: {
56
+ type: NumberConstructor;
57
+ default: number;
58
+ };
59
+ block: {
60
+ type: BooleanConstructor;
61
+ default: boolean;
62
+ };
63
+ width: {
64
+ type: PropType<string | number>;
65
+ default: undefined;
66
+ };
67
+ height: {
68
+ type: PropType<string | number>;
69
+ default: undefined;
70
+ };
71
+ round: {
72
+ type: BooleanConstructor;
73
+ default: boolean;
74
+ };
75
+ active: {
76
+ type: BooleanConstructor;
77
+ default: boolean;
78
+ };
79
+ }>> & Readonly<{}>, {
80
+ round: boolean;
81
+ block: boolean;
82
+ active: boolean;
83
+ width: string | number;
84
+ height: string | number;
85
+ rows: number;
86
+ }, SlotsType<{
87
+ default?: () => VNodeChild;
88
+ }>, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -0,0 +1,56 @@
1
+ import { defineComponent, createVNode } from "vue";
2
+ const Skeleton = /* @__PURE__ */ defineComponent({
3
+ name: "Skeleton",
4
+ inheritAttrs: false,
5
+ props: {
6
+ rows: {
7
+ type: Number,
8
+ default: 1
9
+ },
10
+ block: {
11
+ type: Boolean,
12
+ default: false
13
+ },
14
+ width: {
15
+ type: [String, Number],
16
+ default: void 0
17
+ },
18
+ height: {
19
+ type: [String, Number],
20
+ default: void 0
21
+ },
22
+ round: {
23
+ type: Boolean,
24
+ default: false
25
+ },
26
+ active: {
27
+ type: Boolean,
28
+ default: true
29
+ }
30
+ },
31
+ slots: Object,
32
+ setup(props, {
33
+ attrs
34
+ }) {
35
+ const px = (value) => value === void 0 ? void 0 : typeof value === "number" ? `${value}px` : value;
36
+ return () => {
37
+ const style = {};
38
+ if (px(props.width)) style.width = px(props.width);
39
+ if (px(props.height)) style.height = px(props.height);
40
+ const count = props.block ? 1 : Math.max(1, Math.floor(props.rows));
41
+ return createVNode("div", {
42
+ "class": ["mh-skeleton-list", props.active ? "" : "mh-skeleton-list--static", attrs.class].filter(Boolean).join(" "),
43
+ "style": attrs.style
44
+ }, [Array.from({
45
+ length: count
46
+ }, (_, index) => createVNode("div", {
47
+ "key": index,
48
+ "class": ["mh-skeleton", props.block ? "mh-skeleton--block" : "", props.round ? "mh-skeleton--round" : ""].filter(Boolean).join(" "),
49
+ "style": style
50
+ }, null))]);
51
+ };
52
+ }
53
+ });
54
+ export {
55
+ Skeleton
56
+ };