niuma-ui 1.2.5 → 1.2.8

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 (44) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/components/RsAutoComplete.d.ts +39 -0
  3. package/dist/components/RsAutoComplete.impl.js +306 -0
  4. package/dist/components/RsAutoComplete.js +5 -0
  5. package/dist/components/RsCascader.css +81 -0
  6. package/dist/components/RsCascader.d.ts +34 -0
  7. package/dist/components/RsCascader.impl.js +138 -0
  8. package/dist/components/RsCascader.js +8 -0
  9. package/dist/components/RsMentions-1.css +29 -0
  10. package/dist/components/RsMentions.css +19 -0
  11. package/dist/components/RsMentions.d.ts +32 -0
  12. package/dist/components/RsMentions.impl.js +242 -0
  13. package/dist/components/RsMentions.js +10 -0
  14. package/dist/components/RsSelect.css +51 -36
  15. package/dist/components/RsSelect.d.ts +27 -7
  16. package/dist/components/RsSelect.impl.js +145 -30
  17. package/dist/components/RsSelect.js +1 -1
  18. package/dist/components/RsSplitPane.impl.js +6 -2
  19. package/dist/components/RsTabs.d.ts +1 -1
  20. package/dist/components/RsToaster.d.ts +1 -1
  21. package/dist/components/RsTree.d.ts +4 -4
  22. package/dist/components/RsTree.impl.js +1 -1
  23. package/dist/components/RsTreeSelect.css +60 -0
  24. package/dist/components/RsTreeSelect.d.ts +33 -0
  25. package/dist/components/RsTreeSelect.impl.js +145 -0
  26. package/dist/components/RsTreeSelect.js +8 -0
  27. package/dist/components/cascader-utils.d.ts +16 -0
  28. package/dist/components/cascader-utils.js +39 -0
  29. package/dist/components/mentions-utils.d.ts +46 -0
  30. package/dist/components/mentions-utils.js +128 -0
  31. package/dist/components/overlay-utils.d.ts +31 -0
  32. package/dist/components/overlay-utils.js +29 -1
  33. package/dist/components/select-utils.d.ts +15 -0
  34. package/dist/components/select-utils.js +37 -1
  35. package/dist/components/table/RsTableCellEditor.impl.js +7 -1
  36. package/dist/components/use-rs-select.d.ts +5 -2
  37. package/dist/components/use-rs-select.js +31 -19
  38. package/dist/composables/useRsTableShell.js +1 -1
  39. package/dist/index.d.ts +9 -3
  40. package/dist/index.js +6 -0
  41. package/dist/locale/messages.js +6 -0
  42. package/dist/styles.css +160 -0
  43. package/dist/vite-plugins/niuma-ui-host.js +2 -0
  44. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -6,6 +6,33 @@
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.2.8] - 2026-09-01
10
+
11
+ ### 新增
12
+
13
+ - 选择器家族:`RsAutoComplete`、`RsCascader`、`RsTreeSelect`、`RsMentions`。
14
+ - `RsMentions` 按 [WAI-ARIA APG Combobox](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/):浮层贴插入符、Teleport 到 `body`、视口翻转夹边、方向键 / Home / End / Enter / Tab / Esc;测量节点复用,滚动用 rAF。不跟 Ant Mentions 的触发器内输入模型。
15
+ - `RsAutoComplete` 同样按 APG Combobox:建议列表贴输入框、Teleport、`placeAnchoredPopup` 视口翻转夹边并与框等宽。不再走 Reka 未开 popper 的错误落点。输入框与面板跟 `RsSelect` 同一套 token(elevated 表面、大阴影、焦点环、选项 hover);匹配高亮用 span,避免 `<mark>` 被文档样式染成链接色。
16
+ - `RsSelect` 对齐 Ant / Element 的远程 `debounce`、匹配高亮、`variant`、`autoFocus`、`maxTagCount="responsive"`,以及 `focus` / `blur` / `dropdownVisibleChange` / `popupScroll`。面板内搜索与创建保持原交互。
17
+
18
+ ## [1.2.7] - 2026-09-01
19
+
20
+ ### 修复
21
+
22
+ - `RsSelect` 的 `multiple` 不再用泛型 boolean 声明(否则 `:multiple="true"` 可能进 attrs,Combobox 仍按单选)。触发器标签改看 `isMultiple`,空串 / 裸属性也会累加。
23
+ - `RsSelect` 的 v-model 按运行时收 `RsSelectModelValue`(string / number / 数组 / labeled),模板不必再写泛型。
24
+
25
+ ## [1.2.6] - 2026-09-01
26
+
27
+ ### 修复
28
+
29
+ - `niumaUiHost` 不再把 `reka-ui` / `@lucide/vue` / `vue-sonner` 写进宿主 `optimizeDeps.include`。这些包在 pnpm 下只装在 niuma-ui 里,cloud / site 解析不到会告警。
30
+ - `RsSplitPane` 的 `collapse` / `expand` 在尺寸已到位时不再提交新数组、不再重复发事件。
31
+
32
+ ### 变更
33
+
34
+ - `exports` 一律指向 `dist`(含 vite-plugins)。开发仓 `pnpm install` 时若还没有 dist,`prepare` 会编一次;npm 包没有 src,不会重编。`pnpm dev` 联调源码仍由 `niumaUiHost` 改写。
35
+
9
36
  ## [1.2.5] - 2026-08-30
10
37
 
11
38
  ### 变更
@@ -0,0 +1,39 @@
1
+ import type { RsComponentSize } from '../theme/types';
2
+ import { type RsSelectFilterOption, type RsSelectOption } from './select-utils';
3
+ type __VLS_Props = {
4
+ options?: ReadonlyArray<string | RsSelectOption>;
5
+ placeholder?: string;
6
+ disabled?: boolean;
7
+ size?: RsComponentSize;
8
+ allowClear?: boolean;
9
+ filterOption?: RsSelectFilterOption | boolean;
10
+ debounce?: number;
11
+ emptyText?: string;
12
+ };
13
+ type __VLS_ModelProps = {
14
+ modelValue?: string;
15
+ 'open'?: boolean;
16
+ };
17
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
18
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
19
+ search: (query: string) => any;
20
+ select: (value: string) => any;
21
+ clear: () => any;
22
+ "update:modelValue": (value: string) => any;
23
+ "update:open": (value: boolean) => any;
24
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
25
+ onSearch?: ((query: string) => any) | undefined;
26
+ onSelect?: ((value: string) => any) | undefined;
27
+ onClear?: (() => any) | undefined;
28
+ "onUpdate:modelValue"?: ((value: string) => any) | undefined;
29
+ "onUpdate:open"?: ((value: boolean) => any) | undefined;
30
+ }>, {
31
+ disabled: boolean;
32
+ options: ReadonlyArray<string | RsSelectOption>;
33
+ emptyText: string;
34
+ filterOption: RsSelectFilterOption | boolean;
35
+ debounce: number;
36
+ allowClear: boolean;
37
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
38
+ declare const _default: typeof __VLS_export;
39
+ export default _default;
@@ -0,0 +1,306 @@
1
+ import { useRsI18n } from "../composables/useRsI18n.js";
2
+ import RsIcon_default from "./RsIcon.js";
3
+ import { useResolvedRsComponentSize } from "./resolve-size.js";
4
+ import { filterSelectOptions, splitSelectLabelHighlight } from "./select-utils.js";
5
+ import { placeAnchoredPopup, stepEnabledIndex } from "./overlay-utils.js";
6
+ import { Fragment, Teleport, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, mergeModels, nextTick, normalizeClass, normalizeStyle, onMounted, onUnmounted, openBlock, ref, renderList, toDisplayString, unref, useId, useModel, vModelText, watch, withDirectives, withModifiers } from "vue";
7
+ //#region src/components/RsAutoComplete.vue?vue&type=script&setup=true&lang.ts
8
+ var _hoisted_1 = [
9
+ "placeholder",
10
+ "disabled",
11
+ "aria-expanded",
12
+ "aria-controls",
13
+ "aria-activedescendant"
14
+ ];
15
+ var _hoisted_2 = ["aria-label"];
16
+ var _hoisted_3 = [
17
+ "id",
18
+ "aria-label",
19
+ "data-placement"
20
+ ];
21
+ var _hoisted_4 = {
22
+ key: 0,
23
+ class: "rs-auto-complete__empty",
24
+ role: "presentation"
25
+ };
26
+ var _hoisted_5 = [
27
+ "id",
28
+ "aria-selected",
29
+ "aria-disabled",
30
+ "onMousedown"
31
+ ];
32
+ var _hoisted_6 = { class: "rs-auto-complete__label" };
33
+ var _hoisted_7 = {
34
+ key: 0,
35
+ class: "rs-auto-complete__mark"
36
+ };
37
+ var RsAutoComplete_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
38
+ name: "RsAutoComplete",
39
+ inheritAttrs: false,
40
+ __name: "RsAutoComplete",
41
+ props: /*@__PURE__*/ mergeModels({
42
+ options: { default: () => [] },
43
+ placeholder: {},
44
+ disabled: {
45
+ type: Boolean,
46
+ default: false
47
+ },
48
+ size: {},
49
+ allowClear: {
50
+ type: Boolean,
51
+ default: false
52
+ },
53
+ filterOption: {
54
+ type: [Function, Boolean],
55
+ default: true
56
+ },
57
+ debounce: { default: 0 },
58
+ emptyText: { default: "" }
59
+ }, {
60
+ "modelValue": { default: "" },
61
+ "modelModifiers": {},
62
+ "open": {
63
+ type: Boolean,
64
+ default: false
65
+ },
66
+ "openModifiers": {}
67
+ }),
68
+ emits: /*@__PURE__*/ mergeModels([
69
+ "search",
70
+ "select",
71
+ "clear"
72
+ ], ["update:modelValue", "update:open"]),
73
+ setup(__props, { emit: __emit }) {
74
+ const model = useModel(__props, "modelValue");
75
+ const openModel = useModel(__props, "open");
76
+ const props = __props;
77
+ const emit = __emit;
78
+ const { t } = useRsI18n();
79
+ const resolvedSize = useResolvedRsComponentSize(() => props.size);
80
+ const listId = useId();
81
+ const inputRef = ref(null);
82
+ const listRef = ref(null);
83
+ const highlight = ref(0);
84
+ const dismissed = ref(false);
85
+ const searchTimer = ref();
86
+ const popup = ref({
87
+ top: 0,
88
+ left: 0,
89
+ width: 0,
90
+ placement: "bottom"
91
+ });
92
+ const normalized = computed(() => props.options.map((item) => typeof item === "string" ? {
93
+ label: item,
94
+ value: item
95
+ } : item));
96
+ const suggestions = computed(() => {
97
+ if (props.filterOption === false) return normalized.value;
98
+ return filterSelectOptions(normalized.value, model.value, (text, q) => text.toLowerCase().includes(q.toLowerCase()), props.filterOption);
99
+ });
100
+ const open = computed(() => Boolean(openModel.value && !props.disabled && !dismissed.value));
101
+ const activeOption = computed(() => suggestions.value[highlight.value]);
102
+ const activeOptionId = computed(() => open.value && activeOption.value ? `${listId}-${String(activeOption.value.value)}` : void 0);
103
+ watch(model, (query) => {
104
+ if (searchTimer.value) clearTimeout(searchTimer.value);
105
+ const wait = props.debounce ?? 0;
106
+ if (wait <= 0) {
107
+ emit("search", query);
108
+ return;
109
+ }
110
+ searchTimer.value = setTimeout(() => emit("search", query), wait);
111
+ });
112
+ function placePopup() {
113
+ const input = inputRef.value;
114
+ if (!input || !open.value) return;
115
+ const anchor = input.getBoundingClientRect();
116
+ const list = listRef.value?.getBoundingClientRect();
117
+ popup.value = placeAnchoredPopup({
118
+ top: anchor.top,
119
+ left: anchor.left,
120
+ height: anchor.height,
121
+ width: anchor.width
122
+ }, {
123
+ width: anchor.width,
124
+ height: list?.height || Math.min(256, Math.max(1, suggestions.value.length) * 36)
125
+ }, {
126
+ width: window.innerWidth,
127
+ height: window.innerHeight
128
+ });
129
+ }
130
+ let frame = 0;
131
+ function requestPlace() {
132
+ if (frame) return;
133
+ frame = window.requestAnimationFrame(() => {
134
+ frame = 0;
135
+ placePopup();
136
+ });
137
+ }
138
+ function showList() {
139
+ if (props.disabled) return;
140
+ dismissed.value = false;
141
+ openModel.value = true;
142
+ requestPlace();
143
+ }
144
+ function hideList() {
145
+ openModel.value = false;
146
+ }
147
+ function onPick(option) {
148
+ if (option.disabled) return;
149
+ model.value = String(option.value);
150
+ emit("select", String(option.value));
151
+ dismissed.value = true;
152
+ hideList();
153
+ nextTick(() => inputRef.value?.focus());
154
+ }
155
+ function onClear() {
156
+ model.value = "";
157
+ emit("clear");
158
+ showList();
159
+ }
160
+ function onKeydown(event) {
161
+ if (event.key === "Escape") {
162
+ if (!open.value) return;
163
+ event.preventDefault();
164
+ dismissed.value = true;
165
+ hideList();
166
+ return;
167
+ }
168
+ if (!open.value) {
169
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
170
+ event.preventDefault();
171
+ showList();
172
+ }
173
+ return;
174
+ }
175
+ if (event.key === "ArrowDown") {
176
+ event.preventDefault();
177
+ highlight.value = stepEnabledIndex(suggestions.value, highlight.value, 1);
178
+ return;
179
+ }
180
+ if (event.key === "ArrowUp") {
181
+ event.preventDefault();
182
+ highlight.value = stepEnabledIndex(suggestions.value, highlight.value, -1);
183
+ return;
184
+ }
185
+ if (event.key === "Home") {
186
+ event.preventDefault();
187
+ highlight.value = stepEnabledIndex(suggestions.value, -1, 1);
188
+ return;
189
+ }
190
+ if (event.key === "End") {
191
+ event.preventDefault();
192
+ highlight.value = stepEnabledIndex(suggestions.value, suggestions.value.length, -1);
193
+ return;
194
+ }
195
+ if (event.key === "Enter" && activeOption.value) {
196
+ event.preventDefault();
197
+ onPick(activeOption.value);
198
+ return;
199
+ }
200
+ if (event.key === "Tab" && activeOption.value) onPick(activeOption.value);
201
+ }
202
+ function onInput() {
203
+ showList();
204
+ }
205
+ function onFocus() {
206
+ showList();
207
+ }
208
+ function onBlur() {
209
+ window.setTimeout(() => {
210
+ if (document.activeElement === inputRef.value) return;
211
+ hideList();
212
+ }, 0);
213
+ }
214
+ watch(suggestions, (list) => {
215
+ highlight.value = list.findIndex((item) => !item.disabled);
216
+ if (highlight.value < 0) highlight.value = 0;
217
+ requestPlace();
218
+ });
219
+ watch(open, (isOpen) => {
220
+ if (isOpen) nextTick(() => requestPlace());
221
+ }, { immediate: true });
222
+ function onWindowChange() {
223
+ if (open.value) requestPlace();
224
+ }
225
+ onMounted(() => {
226
+ window.addEventListener("resize", onWindowChange);
227
+ window.addEventListener("scroll", onWindowChange, true);
228
+ });
229
+ onUnmounted(() => {
230
+ if (frame) window.cancelAnimationFrame(frame);
231
+ if (searchTimer.value) clearTimeout(searchTimer.value);
232
+ window.removeEventListener("resize", onWindowChange);
233
+ window.removeEventListener("scroll", onWindowChange, true);
234
+ });
235
+ function parts(label) {
236
+ return splitSelectLabelHighlight(label, model.value);
237
+ }
238
+ return (_ctx, _cache) => {
239
+ return openBlock(), createElementBlock("div", { class: normalizeClass(["rs-auto-complete", [`rs-auto-complete--${unref(resolvedSize)}`, { "rs-auto-complete--clearable": __props.allowClear && model.value && !__props.disabled }]]) }, [
240
+ withDirectives(createElementVNode("input", {
241
+ ref_key: "inputRef",
242
+ ref: inputRef,
243
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => model.value = $event),
244
+ class: "rs-auto-complete__input",
245
+ placeholder: __props.placeholder ?? unref(t)("select.placeholder"),
246
+ disabled: __props.disabled,
247
+ autocomplete: "off",
248
+ role: "combobox",
249
+ "aria-autocomplete": "list",
250
+ "aria-haspopup": "listbox",
251
+ "aria-expanded": open.value ? "true" : "false",
252
+ "aria-controls": open.value ? unref(listId) : void 0,
253
+ "aria-activedescendant": activeOptionId.value,
254
+ onKeydown,
255
+ onInput,
256
+ onFocus,
257
+ onBlur
258
+ }, null, 40, _hoisted_1), [[vModelText, model.value]]),
259
+ __props.allowClear && model.value && !__props.disabled ? (openBlock(), createElementBlock("button", {
260
+ key: 0,
261
+ type: "button",
262
+ class: "rs-auto-complete__clear",
263
+ "aria-label": unref(t)("select.clear"),
264
+ onPointerdown: _cache[1] || (_cache[1] = withModifiers(() => {}, ["prevent"])),
265
+ onClick: onClear
266
+ }, [createVNode(RsIcon_default, {
267
+ name: "x",
268
+ size: 14
269
+ })], 40, _hoisted_2)) : createCommentVNode("", true),
270
+ (openBlock(), createBlock(Teleport, { to: "body" }, [open.value ? (openBlock(), createElementBlock("ul", {
271
+ key: 0,
272
+ id: unref(listId),
273
+ ref_key: "listRef",
274
+ ref: listRef,
275
+ class: "rs-auto-complete__list",
276
+ role: "listbox",
277
+ "aria-label": unref(t)("autocomplete.suggestions"),
278
+ "data-placement": popup.value.placement,
279
+ style: normalizeStyle({
280
+ position: "fixed",
281
+ top: `${popup.value.top}px`,
282
+ left: `${popup.value.left}px`,
283
+ width: `${popup.value.width}px`
284
+ })
285
+ }, [!suggestions.value.length ? (openBlock(), createElementBlock("li", _hoisted_4, toDisplayString(__props.emptyText || unref(t)("select.empty")), 1)) : createCommentVNode("", true), (openBlock(true), createElementBlock(Fragment, null, renderList(suggestions.value, (opt, index) => {
286
+ return openBlock(), createElementBlock("li", {
287
+ id: `${unref(listId)}-${String(opt.value)}`,
288
+ key: String(opt.value),
289
+ role: "option",
290
+ class: normalizeClass(["rs-auto-complete__item", {
291
+ "rs-auto-complete__item--disabled": opt.disabled,
292
+ "rs-auto-complete__item--active": index === highlight.value
293
+ }]),
294
+ "aria-selected": index === highlight.value,
295
+ "aria-disabled": opt.disabled ? "true" : void 0,
296
+ onMousedown: withModifiers(($event) => onPick(opt), ["prevent"])
297
+ }, [createElementVNode("span", _hoisted_6, [(openBlock(true), createElementBlock(Fragment, null, renderList(parts(opt.label), (part, i) => {
298
+ return openBlock(), createElementBlock(Fragment, { key: i }, [part.highlight ? (openBlock(), createElementBlock("span", _hoisted_7, toDisplayString(part.text), 1)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createTextVNode(toDisplayString(part.text), 1)], 64))], 64);
299
+ }), 128))])], 42, _hoisted_5);
300
+ }), 128))], 12, _hoisted_3)) : createCommentVNode("", true)]))
301
+ ], 2);
302
+ };
303
+ }
304
+ });
305
+ //#endregion
306
+ export { RsAutoComplete_vue_vue_type_script_setup_true_lang_default as default };
@@ -0,0 +1,5 @@
1
+ import RsAutoComplete_vue_vue_type_script_setup_true_lang_default from "./RsAutoComplete.impl.js";
2
+ //#region src/components/RsAutoComplete.vue
3
+ var RsAutoComplete_default = RsAutoComplete_vue_vue_type_script_setup_true_lang_default;
4
+ //#endregion
5
+ export { RsAutoComplete_default as default };
@@ -0,0 +1,81 @@
1
+
2
+ .rs-cascader[data-v-332e1018] {
3
+ display: inline-flex;
4
+ align-items: center;
5
+ gap: var(--rs-space-sm);
6
+ box-sizing: border-box;
7
+ width: 100%;
8
+ min-width: 12rem;
9
+ height: var(--rs-control-height-md);
10
+ padding: 0 var(--rs-space-md);
11
+ border: 1px solid var(--rs-input-border, var(--rs-border));
12
+ border-radius: var(--rs-radius-sm);
13
+ background: var(--rs-input-bg);
14
+ color: var(--rs-text);
15
+ font-size: var(--rs-font-size-sm);
16
+ cursor: pointer;
17
+ }
18
+ .rs-cascader--sm[data-v-332e1018] {
19
+ height: var(--rs-control-height-sm);
20
+ }
21
+ .rs-cascader--lg[data-v-332e1018] {
22
+ height: var(--rs-control-height-lg);
23
+ }
24
+ .rs-cascader--disabled[data-v-332e1018] {
25
+ opacity: 0.38;
26
+ cursor: not-allowed;
27
+ }
28
+ .rs-cascader__placeholder[data-v-332e1018] {
29
+ flex: 1;
30
+ color: var(--rs-muted);
31
+ text-align: left;
32
+ }
33
+ .rs-cascader__value[data-v-332e1018] {
34
+ flex: 1;
35
+ overflow: hidden;
36
+ text-align: left;
37
+ text-overflow: ellipsis;
38
+ white-space: nowrap;
39
+ }
40
+ .rs-cascader__clear[data-v-332e1018],
41
+ .rs-cascader__icon[data-v-332e1018] {
42
+ color: var(--rs-muted);
43
+ }
44
+ .rs-cascader__clear[data-v-332e1018] {
45
+ border: 0;
46
+ background: transparent;
47
+ cursor: pointer;
48
+ }
49
+ .rs-cascader__panel[data-v-332e1018] {
50
+ display: flex;
51
+ min-height: 8rem;
52
+ max-height: 16rem;
53
+ }
54
+ .rs-cascader__col[data-v-332e1018] {
55
+ min-width: 8rem;
56
+ margin: 0;
57
+ padding: var(--rs-space-xs);
58
+ overflow: auto;
59
+ border-right: 1px solid var(--rs-border);
60
+ list-style: none;
61
+ }
62
+ .rs-cascader__col[data-v-332e1018]:last-child {
63
+ border-right: 0;
64
+ }
65
+ .rs-cascader__item[data-v-332e1018] {
66
+ display: flex;
67
+ align-items: center;
68
+ justify-content: space-between;
69
+ gap: var(--rs-space-sm);
70
+ padding: var(--rs-space-xs) var(--rs-space-sm);
71
+ border-radius: var(--rs-radius-xs);
72
+ cursor: pointer;
73
+ }
74
+ .rs-cascader__item--active[data-v-332e1018],
75
+ .rs-cascader__item[data-v-332e1018]:hover:not(.rs-cascader__item--disabled) {
76
+ background: var(--rs-surface-hover);
77
+ }
78
+ .rs-cascader__item--disabled[data-v-332e1018] {
79
+ opacity: 0.4;
80
+ cursor: not-allowed;
81
+ }
@@ -0,0 +1,34 @@
1
+ import type { RsComponentSize } from '../theme/types';
2
+ import { type RsCascaderExpandTrigger, type RsCascaderOption, type RsCascaderPath } from './cascader-utils';
3
+ type __VLS_Props = {
4
+ options: readonly RsCascaderOption[];
5
+ placeholder?: string;
6
+ disabled?: boolean;
7
+ allowClear?: boolean;
8
+ changeOnSelect?: boolean;
9
+ expandTrigger?: RsCascaderExpandTrigger;
10
+ separator?: string;
11
+ size?: RsComponentSize;
12
+ };
13
+ type __VLS_ModelProps = {
14
+ modelValue?: RsCascaderPath;
15
+ 'open'?: boolean;
16
+ };
17
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
18
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
19
+ change: (path: RsCascaderPath) => any;
20
+ "update:modelValue": (value: RsCascaderPath) => any;
21
+ "update:open": (value: boolean) => any;
22
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
23
+ onChange?: ((path: RsCascaderPath) => any) | undefined;
24
+ "onUpdate:modelValue"?: ((value: RsCascaderPath) => any) | undefined;
25
+ "onUpdate:open"?: ((value: boolean) => any) | undefined;
26
+ }>, {
27
+ disabled: boolean;
28
+ separator: string;
29
+ allowClear: boolean;
30
+ changeOnSelect: boolean;
31
+ expandTrigger: RsCascaderExpandTrigger;
32
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
33
+ declare const _default: typeof __VLS_export;
34
+ export default _default;
@@ -0,0 +1,138 @@
1
+ import { useRsI18n } from "../composables/useRsI18n.js";
2
+ import RsIcon_default from "./RsIcon.js";
3
+ import { useResolvedRsComponentSize } from "./resolve-size.js";
4
+ import RsPopover_default from "./RsPopover.js";
5
+ import { cascaderColumns, cascaderDisplay, isCascaderLeaf } from "./cascader-utils.js";
6
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, mergeModels, normalizeClass, openBlock, renderList, toDisplayString, unref, useModel, withCtx, withModifiers } from "vue";
7
+ //#region src/components/RsCascader.vue?vue&type=script&setup=true&lang.ts
8
+ var _hoisted_1 = ["disabled"];
9
+ var _hoisted_2 = {
10
+ key: 0,
11
+ class: "rs-cascader__value"
12
+ };
13
+ var _hoisted_3 = {
14
+ key: 1,
15
+ class: "rs-cascader__placeholder"
16
+ };
17
+ var _hoisted_4 = ["aria-label"];
18
+ var _hoisted_5 = ["onClick", "onMouseenter"];
19
+ var RsCascader_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
20
+ name: "RsCascader",
21
+ __name: "RsCascader",
22
+ props: /*@__PURE__*/ mergeModels({
23
+ options: {},
24
+ placeholder: {},
25
+ disabled: {
26
+ type: Boolean,
27
+ default: false
28
+ },
29
+ allowClear: {
30
+ type: Boolean,
31
+ default: false
32
+ },
33
+ changeOnSelect: {
34
+ type: Boolean,
35
+ default: false
36
+ },
37
+ expandTrigger: { default: "click" },
38
+ separator: { default: " / " },
39
+ size: {}
40
+ }, {
41
+ "modelValue": { default: () => [] },
42
+ "modelModifiers": {},
43
+ "open": {
44
+ type: Boolean,
45
+ default: false
46
+ },
47
+ "openModifiers": {}
48
+ }),
49
+ emits: /*@__PURE__*/ mergeModels(["change"], ["update:modelValue", "update:open"]),
50
+ setup(__props, { emit: __emit }) {
51
+ const model = useModel(__props, "modelValue");
52
+ const open = useModel(__props, "open");
53
+ const props = __props;
54
+ const emit = __emit;
55
+ const { t } = useRsI18n();
56
+ const resolvedSize = useResolvedRsComponentSize(() => props.size);
57
+ const columns = computed(() => cascaderColumns(props.options, model.value));
58
+ const display = computed(() => cascaderDisplay(props.options, model.value, props.separator));
59
+ function isActive(depth, value) {
60
+ return String(model.value[depth] ?? "") === String(value);
61
+ }
62
+ function pick(depth, option) {
63
+ if (option.disabled) return;
64
+ const next = [...model.value.slice(0, depth), option.value];
65
+ model.value = next;
66
+ emit("change", next);
67
+ if (isCascaderLeaf(option) || props.changeOnSelect && props.expandTrigger === "click") {
68
+ if (isCascaderLeaf(option)) open.value = false;
69
+ }
70
+ }
71
+ function onHover(depth, option) {
72
+ if (props.expandTrigger !== "hover" || option.disabled) return;
73
+ if (!isCascaderLeaf(option)) model.value = [...model.value.slice(0, depth), option.value];
74
+ }
75
+ function onClear() {
76
+ model.value = [];
77
+ emit("change", []);
78
+ }
79
+ return (_ctx, _cache) => {
80
+ return openBlock(), createBlock(RsPopover_default, {
81
+ open: open.value,
82
+ "onUpdate:open": _cache[1] || (_cache[1] = ($event) => open.value = $event),
83
+ side: "bottom",
84
+ align: "start",
85
+ width: "auto"
86
+ }, {
87
+ content: withCtx(() => [createElementVNode("div", {
88
+ class: "rs-cascader__panel",
89
+ onMousedown: _cache[0] || (_cache[0] = withModifiers(() => {}, ["prevent"]))
90
+ }, [(openBlock(true), createElementBlock(Fragment, null, renderList(columns.value, (col, depth) => {
91
+ return openBlock(), createElementBlock("ul", {
92
+ key: depth,
93
+ class: "rs-cascader__col"
94
+ }, [(openBlock(true), createElementBlock(Fragment, null, renderList(col, (opt) => {
95
+ return openBlock(), createElementBlock("li", {
96
+ key: String(opt.value),
97
+ class: normalizeClass(["rs-cascader__item", {
98
+ "rs-cascader__item--active": isActive(depth, opt.value),
99
+ "rs-cascader__item--disabled": opt.disabled
100
+ }]),
101
+ onClick: ($event) => pick(depth, opt),
102
+ onMouseenter: ($event) => onHover(depth, opt)
103
+ }, [createElementVNode("span", null, toDisplayString(opt.label), 1), !unref(isCascaderLeaf)(opt) ? (openBlock(), createBlock(RsIcon_default, {
104
+ key: 0,
105
+ name: "chevron-right",
106
+ size: 12
107
+ })) : createCommentVNode("", true)], 42, _hoisted_5);
108
+ }), 128))]);
109
+ }), 128))], 32)]),
110
+ default: withCtx(() => [createElementVNode("button", {
111
+ type: "button",
112
+ class: normalizeClass(["rs-cascader", [`rs-cascader--${unref(resolvedSize)}`, { "rs-cascader--disabled": __props.disabled }]]),
113
+ disabled: __props.disabled
114
+ }, [
115
+ display.value ? (openBlock(), createElementBlock("span", _hoisted_2, toDisplayString(display.value), 1)) : (openBlock(), createElementBlock("span", _hoisted_3, toDisplayString(__props.placeholder ?? unref(t)("select.placeholder")), 1)),
116
+ __props.allowClear && display.value && !__props.disabled ? (openBlock(), createElementBlock("button", {
117
+ key: 2,
118
+ type: "button",
119
+ class: "rs-cascader__clear",
120
+ "aria-label": unref(t)("select.clear"),
121
+ onClick: withModifiers(onClear, ["stop"])
122
+ }, [createVNode(RsIcon_default, {
123
+ name: "x",
124
+ size: 12
125
+ })], 8, _hoisted_4)) : createCommentVNode("", true),
126
+ createVNode(RsIcon_default, {
127
+ name: "chevron-down",
128
+ size: 14,
129
+ class: "rs-cascader__icon"
130
+ })
131
+ ], 10, _hoisted_1)]),
132
+ _: 1
133
+ }, 8, ["open"]);
134
+ };
135
+ }
136
+ });
137
+ //#endregion
138
+ export { RsCascader_vue_vue_type_script_setup_true_lang_default as default };
@@ -0,0 +1,8 @@
1
+ import './RsCascader.css'
2
+ import _plugin_vue_export_helper_default from "../_virtual/_plugin-vue_export-helper.js";
3
+ import RsCascader_vue_vue_type_script_setup_true_lang_default from "./RsCascader.impl.js";
4
+
5
+ //#region src/components/RsCascader.vue
6
+ var RsCascader_default = /*#__PURE__*/ _plugin_vue_export_helper_default(RsCascader_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-332e1018"]]);
7
+ //#endregion
8
+ export { RsCascader_default as default };
@@ -0,0 +1,29 @@
1
+
2
+ .rs-mentions__list {
3
+ position: fixed;
4
+ z-index: var(--rs-z-dropdown, 80);
5
+ box-sizing: border-box;
6
+ min-width: 10rem;
7
+ max-height: 12rem;
8
+ margin: 0;
9
+ padding: var(--rs-space-xs);
10
+ overflow: auto;
11
+ border: 1px solid var(--rs-border);
12
+ border-radius: var(--rs-radius-sm);
13
+ background: var(--rs-surface);
14
+ box-shadow: var(--rs-shadow-md);
15
+ list-style: none;
16
+ }
17
+ .rs-mentions__item {
18
+ padding: var(--rs-space-xs) var(--rs-space-sm);
19
+ border-radius: var(--rs-radius-xs);
20
+ cursor: pointer;
21
+ }
22
+ .rs-mentions__item--active,
23
+ .rs-mentions__item:hover:not(.rs-mentions__item--disabled) {
24
+ background: var(--rs-surface-hover);
25
+ }
26
+ .rs-mentions__item--disabled {
27
+ opacity: 0.4;
28
+ cursor: not-allowed;
29
+ }