niuma-ui 1.2.6 → 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 (42) hide show
  1. package/CHANGELOG.md +16 -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/RsTabs.d.ts +1 -1
  19. package/dist/components/RsToaster.d.ts +1 -1
  20. package/dist/components/RsTree.d.ts +4 -4
  21. package/dist/components/RsTree.impl.js +1 -1
  22. package/dist/components/RsTreeSelect.css +60 -0
  23. package/dist/components/RsTreeSelect.d.ts +33 -0
  24. package/dist/components/RsTreeSelect.impl.js +145 -0
  25. package/dist/components/RsTreeSelect.js +8 -0
  26. package/dist/components/cascader-utils.d.ts +16 -0
  27. package/dist/components/cascader-utils.js +39 -0
  28. package/dist/components/mentions-utils.d.ts +46 -0
  29. package/dist/components/mentions-utils.js +128 -0
  30. package/dist/components/overlay-utils.d.ts +31 -0
  31. package/dist/components/overlay-utils.js +29 -1
  32. package/dist/components/select-utils.d.ts +15 -0
  33. package/dist/components/select-utils.js +37 -1
  34. package/dist/components/table/RsTableCellEditor.impl.js +7 -1
  35. package/dist/components/use-rs-select.d.ts +5 -2
  36. package/dist/components/use-rs-select.js +31 -19
  37. package/dist/composables/useRsTableShell.js +1 -1
  38. package/dist/index.d.ts +9 -3
  39. package/dist/index.js +6 -0
  40. package/dist/locale/messages.js +6 -0
  41. package/dist/styles.css +160 -0
  42. package/package.json +1 -1
@@ -0,0 +1,8 @@
1
+ import './RsTreeSelect.css'
2
+ import _plugin_vue_export_helper_default from "../_virtual/_plugin-vue_export-helper.js";
3
+ import RsTreeSelect_vue_vue_type_script_setup_true_lang_default from "./RsTreeSelect.impl.js";
4
+
5
+ //#region src/components/RsTreeSelect.vue
6
+ var RsTreeSelect_default = /*#__PURE__*/ _plugin_vue_export_helper_default(RsTreeSelect_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-a057dd37"]]);
7
+ //#endregion
8
+ export { RsTreeSelect_default as default };
@@ -0,0 +1,16 @@
1
+ import type { RsSelectValue } from './select-utils';
2
+ /** 级联选项,对齐 Ant Cascader options */
3
+ export interface RsCascaderOption {
4
+ label: string;
5
+ value: RsSelectValue;
6
+ disabled?: boolean;
7
+ children?: RsCascaderOption[];
8
+ isLeaf?: boolean;
9
+ }
10
+ export type RsCascaderPath = RsSelectValue[];
11
+ export type RsCascaderExpandTrigger = 'click' | 'hover';
12
+ export declare function findCascaderOption(options: readonly RsCascaderOption[], value: RsSelectValue): RsCascaderOption | undefined;
13
+ export declare function cascaderColumns(options: readonly RsCascaderOption[], path: RsCascaderPath): RsCascaderOption[][];
14
+ export declare function cascaderLabels(options: readonly RsCascaderOption[], path: RsCascaderPath): string[];
15
+ export declare function cascaderDisplay(options: readonly RsCascaderOption[], path: RsCascaderPath, separator?: string): string;
16
+ export declare function isCascaderLeaf(option: RsCascaderOption): boolean;
@@ -0,0 +1,39 @@
1
+ //#region src/components/cascader-utils.ts
2
+ function findCascaderOption(options, value) {
3
+ return options.find((item) => String(item.value) === String(value));
4
+ }
5
+ function cascaderColumns(options, path) {
6
+ const columns = [options.slice()];
7
+ let level = options;
8
+ for (const token of path) {
9
+ const children = findCascaderOption(level, token)?.children;
10
+ if (!children?.length) break;
11
+ columns.push(children);
12
+ level = children;
13
+ }
14
+ return columns;
15
+ }
16
+ function cascaderLabels(options, path) {
17
+ const labels = [];
18
+ let level = options;
19
+ for (const token of path) {
20
+ const current = findCascaderOption(level, token);
21
+ if (!current) {
22
+ labels.push(String(token));
23
+ break;
24
+ }
25
+ labels.push(current.label);
26
+ level = current.children ?? [];
27
+ }
28
+ return labels;
29
+ }
30
+ function cascaderDisplay(options, path, separator = " / ") {
31
+ return cascaderLabels(options, path).join(separator);
32
+ }
33
+ function isCascaderLeaf(option) {
34
+ if (option.isLeaf === true) return true;
35
+ if (option.isLeaf === false) return false;
36
+ return !option.children?.length;
37
+ }
38
+ //#endregion
39
+ export { cascaderColumns, cascaderDisplay, cascaderLabels, findCascaderOption, isCascaderLeaf };
@@ -0,0 +1,46 @@
1
+ export interface RsMentionOption {
2
+ label: string;
3
+ value: string;
4
+ disabled?: boolean;
5
+ }
6
+ export interface RsMentionActive {
7
+ prefix: string;
8
+ query: string;
9
+ start: number;
10
+ end: number;
11
+ }
12
+ export declare function resolveMentionPrefixes(prefix: string | readonly string[]): string[];
13
+ /** 光标前最近一个前缀 + 查询词(不含空格) */
14
+ export declare function findActiveMention(text: string, cursor: number, prefixes: readonly string[], split?: string): RsMentionActive | null;
15
+ export declare function applyMention(text: string, active: RsMentionActive, value: string, split?: string): {
16
+ text: string;
17
+ cursor: number;
18
+ };
19
+ export interface RsMentionCaretBox {
20
+ top: number;
21
+ left: number;
22
+ height: number;
23
+ }
24
+ export interface RsMentionPopupBox {
25
+ top: number;
26
+ left: number;
27
+ placement: 'top' | 'bottom';
28
+ }
29
+ export declare function stepMentionIndex(options: readonly RsMentionOption[], current: number, delta: 1 | -1): number;
30
+ /** 视口内避让:下边不够就翻到上方,左右夹进窗口。 */
31
+ export declare function placeMentionPopup(caret: RsMentionCaretBox, popup: {
32
+ width: number;
33
+ height: number;
34
+ }, viewport: {
35
+ width: number;
36
+ height: number;
37
+ }, gap?: number): RsMentionPopupBox;
38
+ export interface RsTextareaCaretMeter {
39
+ measure: (textarea: HTMLTextAreaElement, index: number) => RsMentionCaretBox;
40
+ dispose: () => void;
41
+ }
42
+ /** 复用隐藏镜像节点,避免每次按键 insert/remove 逼 reflow。 */
43
+ export declare function createTextareaCaretMeter(): RsTextareaCaretMeter;
44
+ /** 相对 textarea 内容区的光标坐标(已扣滚动)。测试 / 单次调用用;热路径请复用 meter。 */
45
+ export declare function measureTextareaCaret(textarea: HTMLTextAreaElement, index: number): RsMentionCaretBox;
46
+ export declare function filterMentionOptions(options: readonly RsMentionOption[], query: string): RsMentionOption[];
@@ -0,0 +1,128 @@
1
+ import { placeAnchoredPopup, stepEnabledIndex } from "./overlay-utils.js";
2
+ //#region src/components/mentions-utils.ts
3
+ function resolveMentionPrefixes(prefix) {
4
+ return (Array.isArray(prefix) ? prefix : [prefix]).map(String).filter(Boolean);
5
+ }
6
+ /** 光标前最近一个前缀 + 查询词(不含空格) */
7
+ function findActiveMention(text, cursor, prefixes, split = " ") {
8
+ const head = text.slice(0, Math.max(0, cursor));
9
+ let found = null;
10
+ for (const token of prefixes) {
11
+ const at = head.lastIndexOf(token);
12
+ if (at < 0) continue;
13
+ const after = head.slice(at + token.length);
14
+ if (after.includes(split) || after.includes("\n")) continue;
15
+ if (!found || at > found.start) found = {
16
+ prefix: token,
17
+ query: after,
18
+ start: at,
19
+ end: cursor
20
+ };
21
+ }
22
+ return found;
23
+ }
24
+ function applyMention(text, active, value, split = " ") {
25
+ const insert = `${active.prefix}${value}${split}`;
26
+ return {
27
+ text: `${text.slice(0, active.start)}${insert}${text.slice(active.end)}`,
28
+ cursor: active.start + insert.length
29
+ };
30
+ }
31
+ function stepMentionIndex(options, current, delta) {
32
+ return stepEnabledIndex(options, current, delta);
33
+ }
34
+ /** 视口内避让:下边不够就翻到上方,左右夹进窗口。 */
35
+ function placeMentionPopup(caret, popup, viewport, gap = 4) {
36
+ const box = placeAnchoredPopup(caret, popup, viewport, gap);
37
+ return {
38
+ top: box.top,
39
+ left: box.left,
40
+ placement: box.placement
41
+ };
42
+ }
43
+ var MIRROR_STYLE_KEYS = [
44
+ "boxSizing",
45
+ "width",
46
+ "height",
47
+ "overflowX",
48
+ "overflowY",
49
+ "borderTopWidth",
50
+ "borderRightWidth",
51
+ "borderBottomWidth",
52
+ "borderLeftWidth",
53
+ "paddingTop",
54
+ "paddingRight",
55
+ "paddingBottom",
56
+ "paddingLeft",
57
+ "fontStyle",
58
+ "fontVariant",
59
+ "fontWeight",
60
+ "fontStretch",
61
+ "fontSize",
62
+ "fontFamily",
63
+ "letterSpacing",
64
+ "textIndent",
65
+ "textTransform",
66
+ "wordSpacing",
67
+ "tabSize",
68
+ "lineHeight",
69
+ "whiteSpace",
70
+ "wordWrap"
71
+ ];
72
+ /** 复用隐藏镜像节点,避免每次按键 insert/remove 逼 reflow。 */
73
+ function createTextareaCaretMeter() {
74
+ let mirror = null;
75
+ let marker = null;
76
+ function ensure() {
77
+ if (mirror && marker) return {
78
+ mirror,
79
+ marker
80
+ };
81
+ mirror = document.createElement("div");
82
+ mirror.setAttribute("aria-hidden", "true");
83
+ const s = mirror.style;
84
+ s.position = "absolute";
85
+ s.visibility = "hidden";
86
+ s.pointerEvents = "none";
87
+ s.whiteSpace = "pre-wrap";
88
+ s.wordWrap = "break-word";
89
+ s.top = "0";
90
+ s.left = "-9999px";
91
+ marker = document.createElement("span");
92
+ marker.textContent = ".";
93
+ document.body.appendChild(mirror);
94
+ return {
95
+ mirror,
96
+ marker
97
+ };
98
+ }
99
+ function measure(textarea, index) {
100
+ const { mirror: box, marker: caret } = ensure();
101
+ const style = window.getComputedStyle(textarea);
102
+ for (const key of MIRROR_STYLE_KEYS) box.style.setProperty(key.replace(/[A-Z]/g, (ch) => `-${ch.toLowerCase()}`), style[key]);
103
+ const text = textarea.value.slice(0, Math.max(0, index));
104
+ box.textContent = text.endsWith("\n") ? `${text}\u00a0` : text;
105
+ box.appendChild(caret);
106
+ return {
107
+ top: caret.offsetTop - textarea.scrollTop,
108
+ left: caret.offsetLeft - textarea.scrollLeft,
109
+ height: caret.offsetHeight || parseFloat(style.lineHeight) || 16
110
+ };
111
+ }
112
+ function dispose() {
113
+ mirror?.remove();
114
+ mirror = null;
115
+ marker = null;
116
+ }
117
+ return {
118
+ measure,
119
+ dispose
120
+ };
121
+ }
122
+ function filterMentionOptions(options, query) {
123
+ const q = query.trim().toLowerCase();
124
+ if (!q) return options.filter((item) => !item.disabled);
125
+ return options.filter((item) => !item.disabled && (item.label.toLowerCase().includes(q) || item.value.toLowerCase().includes(q)));
126
+ }
127
+ //#endregion
128
+ export { applyMention, createTextareaCaretMeter, filterMentionOptions, findActiveMention, placeMentionPopup, resolveMentionPrefixes, stepMentionIndex };
@@ -21,3 +21,34 @@ export declare const RS_TOAST_DEFAULT_POSITION: RsToastPosition;
21
21
  export declare const RS_TOAST_DEFAULT_GAP = 4;
22
22
  export declare const rsToastPositions: readonly ["top-center", "top-left", "top-right", "bottom-center", "bottom-left", "bottom-right"];
23
23
  export declare function rsFeedbackIconClass(tone: RsFeedbackTone): string;
24
+ /** 视口内锚点矩形(输入框、插入符等)。 */
25
+ export interface RsOverlayAnchorBox {
26
+ top: number;
27
+ left: number;
28
+ height: number;
29
+ width?: number;
30
+ }
31
+ export interface RsOverlayPopupSize {
32
+ width: number;
33
+ height: number;
34
+ }
35
+ export interface RsOverlayViewport {
36
+ width: number;
37
+ height: number;
38
+ }
39
+ /** 锚点浮层最终盒子。placement 供翻转后的样式钩子使用。 */
40
+ export interface RsOverlayBox {
41
+ top: number;
42
+ left: number;
43
+ width: number;
44
+ placement: 'top' | 'bottom';
45
+ }
46
+ /**
47
+ * 把浮层贴到锚点:优先下方,视口不够则翻到上方,左右夹进窗口。
48
+ * 对齐 WAI-ARIA APG 浮层惯例与 CSS overflow 避让,不跟某一家组件库的 API。
49
+ */
50
+ export declare function placeAnchoredPopup(anchor: RsOverlayAnchorBox, popup: RsOverlayPopupSize, viewport: RsOverlayViewport, gap?: number): RsOverlayBox;
51
+ /** 在可选项里按方向跳过 disabled,供 combobox / listbox 方向键与 Home / End。 */
52
+ export declare function stepEnabledIndex<T extends {
53
+ disabled?: boolean;
54
+ }>(options: readonly T[], current: number, delta: 1 | -1): number;
@@ -13,5 +13,33 @@ var rsToastPositions = [
13
13
  function rsFeedbackIconClass(tone) {
14
14
  return `rs-feedback-icon rs-feedback-icon--${tone}`;
15
15
  }
16
+ /**
17
+ * 把浮层贴到锚点:优先下方,视口不够则翻到上方,左右夹进窗口。
18
+ * 对齐 WAI-ARIA APG 浮层惯例与 CSS overflow 避让,不跟某一家组件库的 API。
19
+ */
20
+ function placeAnchoredPopup(anchor, popup, viewport, gap = 4) {
21
+ const below = anchor.top + anchor.height + gap;
22
+ const above = anchor.top - popup.height - gap;
23
+ const placement = below + popup.height <= viewport.height || above < gap ? "bottom" : "top";
24
+ const top = placement === "bottom" ? below : Math.max(gap, above);
25
+ const width = Math.min(popup.width, Math.max(0, viewport.width - gap * 2));
26
+ const maxLeft = Math.max(gap, viewport.width - width - gap);
27
+ return {
28
+ top,
29
+ left: Math.min(Math.max(gap, anchor.left), maxLeft),
30
+ width,
31
+ placement
32
+ };
33
+ }
34
+ /** 在可选项里按方向跳过 disabled,供 combobox / listbox 方向键与 Home / End。 */
35
+ function stepEnabledIndex(options, current, delta) {
36
+ if (!options.length) return 0;
37
+ let index = current;
38
+ for (let n = 0; n < options.length; n += 1) {
39
+ index = (index + delta + options.length) % options.length;
40
+ if (!options[index]?.disabled) return index;
41
+ }
42
+ return Math.max(0, current);
43
+ }
16
44
  //#endregion
17
- export { RS_TOAST_DEFAULT_GAP, RS_TOAST_DEFAULT_POSITION, rsFeedbackIconClass, rsToastPositions };
45
+ export { RS_TOAST_DEFAULT_GAP, RS_TOAST_DEFAULT_POSITION, placeAnchoredPopup, rsFeedbackIconClass, rsToastPositions, stepEnabledIndex };
@@ -3,6 +3,14 @@ export type RsSelectValue = string | number;
3
3
  export type RsSelectOptionFilterProp = string;
4
4
  export type RsSelectPlacement = 'top' | 'bottom' | 'left' | 'right';
5
5
  export type RsSelectStatus = 'error' | 'warning' | '';
6
+ /** 对齐 Ant Design 5 Select variant;搜索仍在面板内,不改触发器打字。 */
7
+ export type RsSelectVariant = 'outlined' | 'filled' | 'borderless';
8
+ /** 多选折叠:数字为上限;responsive 按触发器宽度收。 */
9
+ export type RsSelectMaxTagCount = number | 'responsive';
10
+ export declare function splitSelectLabelHighlight(label: string, keyword: string): Array<{
11
+ text: string;
12
+ highlight: boolean;
13
+ }>;
6
14
  /** 自定义过滤(对齐 Ant Design filterOption)。返回 false 则隐藏该项。 */
7
15
  export type RsSelectFilterOption = (query: string, option: RsSelectOption) => boolean;
8
16
  /** 过滤后排序(对齐 Ant Design filterSort) */
@@ -41,6 +49,7 @@ export type RsSelectModelValue = RsSelectValue | RsSelectValue[] | RsSelectLabel
41
49
  * 按泛型收窄后的 v-model。
42
50
  * 默认单选、值为 string:宿主 `@update:model-value="(v: string) => void"` 可直接赋值。
43
51
  * `multiple` / `labelInValue` 为字面量 true 时收成数组或 labeled;为 `boolean` 时保留联合。
52
+ * 组件本体的 defineModel 用 RsSelectModelValue(multiple / labelInValue 是运行时 boolean)。
44
53
  */
45
54
  export type RsSelectResolvedModel<Value extends RsSelectValue = string, Multiple extends boolean = false, LabelInValue extends boolean = false> = LabelInValue extends true ? Multiple extends true ? RsSelectLabeledValue[] : Multiple extends false ? RsSelectLabeledValue | '' : RsSelectLabeledValue | RsSelectLabeledValue[] | '' : Multiple extends true ? Value[] : Multiple extends false ? Value | '' : Value | Value[] | '';
46
55
  /**
@@ -48,6 +57,12 @@ export type RsSelectResolvedModel<Value extends RsSelectValue = string, Multiple
48
57
  * 选项若传入 value: '',对内映射为此哨兵,避免崩溃;对外读写仍为 ''。
49
58
  */
50
59
  export declare const RS_SELECT_EMPTY_VALUE = "__rs_select_empty__";
60
+ /**
61
+ * Vue 对泛型 boolean prop 不会按 Boolean 收口。
62
+ * 模板写 `multiple` 时运行时可能是 '';个别版本还会落到 attrs 变成 'true' / 'multiple'。
63
+ * Boolean('') 为 false,会把多选打成单选。
64
+ */
65
+ export declare function isSelectMultiple(value: unknown): boolean;
51
66
  /** 业务 value → ComboboxItem token(空串走哨兵;数字转为十进制字符串) */
52
67
  export declare function toComboboxValue(value: RsSelectValue): string;
53
68
  /** ComboboxItem token → 业务字符串(仅还原空串哨兵,不恢复 number) */
@@ -1,9 +1,45 @@
1
1
  //#region src/components/select-utils.ts
2
+ function splitSelectLabelHighlight(label, keyword) {
3
+ const query = keyword.trim();
4
+ if (!query) return [{
5
+ text: label,
6
+ highlight: false
7
+ }];
8
+ const lowerLabel = label.toLowerCase();
9
+ const lowerQuery = query.toLowerCase();
10
+ const index = lowerLabel.indexOf(lowerQuery);
11
+ if (index < 0) return [{
12
+ text: label,
13
+ highlight: false
14
+ }];
15
+ const parts = [];
16
+ if (index > 0) parts.push({
17
+ text: label.slice(0, index),
18
+ highlight: false
19
+ });
20
+ parts.push({
21
+ text: label.slice(index, index + query.length),
22
+ highlight: true
23
+ });
24
+ if (index + query.length < label.length) parts.push({
25
+ text: label.slice(index + query.length),
26
+ highlight: false
27
+ });
28
+ return parts;
29
+ }
2
30
  /**
3
31
  * Reka ComboboxItem 禁止 value 为空串(空串表示未选中 / placeholder)。
4
32
  * 选项若传入 value: '',对内映射为此哨兵,避免崩溃;对外读写仍为 ''。
5
33
  */
6
34
  var RS_SELECT_EMPTY_VALUE = "__rs_select_empty__";
35
+ /**
36
+ * Vue 对泛型 boolean prop 不会按 Boolean 收口。
37
+ * 模板写 `multiple` 时运行时可能是 '';个别版本还会落到 attrs 变成 'true' / 'multiple'。
38
+ * Boolean('') 为 false,会把多选打成单选。
39
+ */
40
+ function isSelectMultiple(value) {
41
+ return value === true || value === "" || value === "true" || value === "multiple";
42
+ }
7
43
  /** 业务 value → ComboboxItem token(空串走哨兵;数字转为十进制字符串) */
8
44
  function toComboboxValue(value) {
9
45
  return value === "" ? RS_SELECT_EMPTY_VALUE : String(value);
@@ -208,4 +244,4 @@ function splitByTokenSeparators(raw, separators) {
208
244
  return raw.split(found);
209
245
  }
210
246
  //#endregion
211
- export { RS_SELECT_EMPTY_VALUE, buildOptionDisabledMap, buildOptionLabelMap, comboboxBindingToTokens, filterSelectOptions, findSelectOption, flattenSelectOptions, flattenSelectValues, fromComboboxValue, isSelectLabeledValue, isSelectOptionGroup, normalizeSelectOptions, optionDisplayLabel, packSelectModel, restoreSelectValue, restoreSelectValues, selectModelEntries, sortSelectOptions, splitByTokenSeparators, toComboboxValue, toLabeledValue, toSelectedTokens, unwrapSelectEntry };
247
+ export { RS_SELECT_EMPTY_VALUE, buildOptionDisabledMap, buildOptionLabelMap, comboboxBindingToTokens, filterSelectOptions, findSelectOption, flattenSelectOptions, flattenSelectValues, fromComboboxValue, isSelectLabeledValue, isSelectMultiple, isSelectOptionGroup, normalizeSelectOptions, optionDisplayLabel, packSelectModel, restoreSelectValue, restoreSelectValues, selectModelEntries, sortSelectOptions, splitByTokenSeparators, splitSelectLabelHighlight, toComboboxValue, toLabeledValue, toSelectedTokens, unwrapSelectEntry };
@@ -1,6 +1,7 @@
1
1
  import { useRsI18n } from "../../composables/useRsI18n.js";
2
2
  import RsInput_default from "../RsInput.js";
3
3
  import RsInputNumber_default from "../RsInputNumber.js";
4
+ import { unwrapSelectEntry } from "../select-utils.js";
4
5
  import RsSelect_default from "../RsSelect.js";
5
6
  import RsDatePicker_default from "../RsDatePicker.js";
6
7
  import { applyFocusMode, isNullDraft, nullToEditText, resolveCellEditorInputType, usesOverlayEditor } from "./table-edit-utils.js";
@@ -194,8 +195,13 @@ var RsTableCellEditor_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*
194
195
  emit("commit");
195
196
  }
196
197
  }
198
+ function selectDraftTokens(value) {
199
+ if (Array.isArray(value)) return value.map((item) => unwrapSelectEntry(item)).filter((item) => item !== null).map(String);
200
+ const raw = unwrapSelectEntry(value);
201
+ return raw == null ? "" : String(raw);
202
+ }
197
203
  function onSelectUpdate(value) {
198
- const tokens = Array.isArray(value) ? value.map(String) : String(value);
204
+ const tokens = selectDraftTokens(value);
199
205
  if (tokens === "" || Array.isArray(tokens) && tokens.length === 0) {
200
206
  if (!props.editorOptions?.clearable) return;
201
207
  model.value = props.allowNull ? nullToEditText() : "";
@@ -15,7 +15,9 @@ export interface RsSelectEngineProps {
15
15
  remote?: boolean;
16
16
  virtual?: boolean;
17
17
  virtualThreshold?: number;
18
- maxTagCount?: number;
18
+ maxTagCount?: number | 'responsive';
19
+ /** 远程 @search 防抖毫秒;0 立即发 */
20
+ debounce?: number;
19
21
  maxTagPlaceholder?: string | ((omitted: number) => string);
20
22
  maxTagTooltip?: boolean;
21
23
  maxTagTextLength?: number;
@@ -40,12 +42,13 @@ export type RsSelectEngineEmit = {
40
42
  * Select 过滤、选中、多选 tag、creatable / 分隔符提交。
41
43
  * 组件模板只消费返回值;表单校验仍留在 SFC。
42
44
  */
43
- export declare function useRsSelect(props: RsSelectEngineProps, model: ModelRef<RsSelectModelValue>, open: ModelRef<boolean>, searchQuery: ModelRef<string>, emit: RsSelectEngineEmit, t: RsTranslateFn): {
45
+ export declare function useRsSelect<T extends RsSelectModelValue = RsSelectModelValue>(props: RsSelectEngineProps, model: ModelRef<T>, open: ModelRef<boolean>, searchQuery: ModelRef<string>, emit: RsSelectEngineEmit, t: RsTranslateFn): {
44
46
  normalizedOptions: import("vue").ComputedRef<import("./select-utils").RsSelectOptions>;
45
47
  resolvedPlaceholder: import("vue").ComputedRef<string>;
46
48
  resolvedSearchPlaceholder: import("vue").ComputedRef<string>;
47
49
  resolvedEmptyText: import("vue").ComputedRef<string>;
48
50
  resolvedLoadingText: import("vue").ComputedRef<string>;
51
+ isMultiple: import("vue").ComputedRef<boolean>;
49
52
  isSearchable: import("vue").ComputedRef<boolean>;
50
53
  labelMap: import("vue").ComputedRef<Map<string, string>>;
51
54
  useVirtual: import("vue").ComputedRef<boolean>;
@@ -1,6 +1,6 @@
1
1
  import { useFilter } from "./reka.js";
2
- import { buildOptionDisabledMap, buildOptionLabelMap, comboboxBindingToTokens, filterSelectOptions, findSelectOption, flattenSelectOptions, flattenSelectValues, fromComboboxValue, isSelectLabeledValue, normalizeSelectOptions, optionDisplayLabel, packSelectModel, restoreSelectValue, restoreSelectValues, selectModelEntries, sortSelectOptions, splitByTokenSeparators, toComboboxValue, toSelectedTokens } from "./select-utils.js";
3
- import { computed, nextTick, watch } from "vue";
2
+ import { buildOptionDisabledMap, buildOptionLabelMap, comboboxBindingToTokens, filterSelectOptions, findSelectOption, flattenSelectOptions, flattenSelectValues, fromComboboxValue, isSelectLabeledValue, isSelectMultiple, normalizeSelectOptions, optionDisplayLabel, packSelectModel, restoreSelectValue, restoreSelectValues, selectModelEntries, sortSelectOptions, splitByTokenSeparators, toComboboxValue, toSelectedTokens } from "./select-utils.js";
3
+ import { computed, nextTick, onUnmounted, watch } from "vue";
4
4
  //#region src/components/use-rs-select.ts
5
5
  /**
6
6
  * Select 过滤、选中、多选 tag、creatable / 分隔符提交。
@@ -8,6 +8,7 @@ import { computed, nextTick, watch } from "vue";
8
8
  */
9
9
  function useRsSelect(props, model, open, searchQuery, emit, t) {
10
10
  const { contains } = useFilter({ sensitivity: "base" });
11
+ const isMultiple = computed(() => isSelectMultiple(props.multiple));
11
12
  const normalizedOptions = computed(() => normalizeSelectOptions(props.options, props.fieldNames));
12
13
  const resolvedPlaceholder = computed(() => props.placeholder ?? t("select.placeholder"));
13
14
  const resolvedSearchPlaceholder = computed(() => props.searchPlaceholder ?? t("select.searchPlaceholder"));
@@ -46,11 +47,12 @@ function useRsSelect(props, model, open, searchQuery, emit, t) {
46
47
  if (!canCreate.value) return values;
47
48
  return [createValue.value, ...values.filter((item) => String(item) !== createValue.value)];
48
49
  });
49
- const selectedValues = computed(() => toSelectedTokens(model.value, Boolean(props.multiple)));
50
+ const selectedValues = computed(() => toSelectedTokens(model.value, isMultiple.value));
50
51
  const hasValue = computed(() => selectedValues.value.length > 0);
51
52
  const visibleTagTokens = computed(() => {
52
53
  const all = selectedValues.value;
53
- if (!props.multiple || props.maxTagCount == null || all.length <= props.maxTagCount) return all;
54
+ if (!isMultiple.value || props.maxTagCount == null || props.maxTagCount === "responsive") return all;
55
+ if (all.length <= props.maxTagCount) return all;
54
56
  return all.slice(0, Math.max(0, props.maxTagCount));
55
57
  });
56
58
  const omittedTagCount = computed(() => Math.max(0, selectedValues.value.length - visibleTagTokens.value.length));
@@ -82,7 +84,7 @@ function useRsSelect(props, model, open, searchQuery, emit, t) {
82
84
  if (!max || max < 1 || label.length <= max) return label;
83
85
  return `${label.slice(0, max)}…`;
84
86
  }
85
- const atMultipleLimit = computed(() => Boolean(props.multiple) && props.multipleLimit != null && props.multipleLimit > 0 && selectedValues.value.length >= props.multipleLimit);
87
+ const atMultipleLimit = computed(() => isMultiple.value && props.multipleLimit != null && props.multipleLimit > 0 && selectedValues.value.length >= props.multipleLimit);
86
88
  function isOptionLimited(option) {
87
89
  if (!atMultipleLimit.value) return false;
88
90
  return !selectedValues.value.includes(toComboboxValue(option.value));
@@ -93,11 +95,11 @@ function useRsSelect(props, model, open, searchQuery, emit, t) {
93
95
  return option ? isOptionLimited(option) : false;
94
96
  }
95
97
  const singleDisplayLabel = computed(() => {
96
- if (props.multiple || !hasValue.value) return "";
98
+ if (isMultiple.value || !hasValue.value) return "";
97
99
  return tokenLabel(selectedValues.value[0]);
98
100
  });
99
101
  function writeTokens(tokens) {
100
- model.value = packSelectModel(restoreSelectValues(tokens, normalizedOptions.value), normalizedOptions.value, Boolean(props.multiple), Boolean(props.labelInValue));
102
+ model.value = packSelectModel(restoreSelectValues(tokens, normalizedOptions.value), normalizedOptions.value, isMultiple.value, Boolean(props.labelInValue));
101
103
  }
102
104
  function emitSelectionDiff(prev, next) {
103
105
  const prevSet = new Set(prev);
@@ -113,12 +115,12 @@ function useRsSelect(props, model, open, searchQuery, emit, t) {
113
115
  }
114
116
  const comboboxModel = computed({
115
117
  get() {
116
- if (props.multiple) return selectedValues.value.map((token) => token);
118
+ if (isMultiple.value) return selectedValues.value;
117
119
  return hasValue.value ? selectedValues.value[0] : void 0;
118
120
  },
119
121
  set(value) {
120
122
  const prev = selectedValues.value;
121
- const tokens = comboboxBindingToTokens(value, Boolean(props.multiple));
123
+ const tokens = comboboxBindingToTokens(value, isMultiple.value);
122
124
  emitSelectionDiff(prev, tokens);
123
125
  writeTokens(tokens);
124
126
  if (props.autoClearSearchValue) searchQuery.value = "";
@@ -132,7 +134,7 @@ function useRsSelect(props, model, open, searchQuery, emit, t) {
132
134
  if (!next) return;
133
135
  const values = currentValues();
134
136
  if (values.some((item) => String(item) === next)) return;
135
- if (props.multiple) {
137
+ if (isMultiple.value) {
136
138
  if (atMultipleLimit.value) return;
137
139
  const prev = selectedValues.value;
138
140
  const packed = packSelectModel([...values, next], normalizedOptions.value, true, Boolean(props.labelInValue));
@@ -147,7 +149,7 @@ function useRsSelect(props, model, open, searchQuery, emit, t) {
147
149
  if (props.autoClearSearchValue) searchQuery.value = "";
148
150
  }
149
151
  function consumeTokenSeparators(raw) {
150
- if (!props.multiple && !props.creatable) return;
152
+ if (!isMultiple.value && !props.creatable) return;
151
153
  const parts = splitByTokenSeparators(raw, props.tokenSeparators ?? []);
152
154
  if (!parts) return;
153
155
  const rest = parts.pop() ?? "";
@@ -159,26 +161,35 @@ function useRsSelect(props, model, open, searchQuery, emit, t) {
159
161
  const prev = selectedValues.value;
160
162
  const key = toComboboxValue(matched.value);
161
163
  if (prev.includes(key)) continue;
162
- if (props.multiple && atMultipleLimit.value) continue;
163
- const nextTokens = props.multiple ? [...prev, key] : [key];
164
+ if (isMultiple.value && atMultipleLimit.value) continue;
165
+ const nextTokens = isMultiple.value ? [...prev, key] : [key];
164
166
  emitSelectionDiff(prev, nextTokens);
165
167
  writeTokens(nextTokens);
166
- if (!props.multiple) open.value = false;
168
+ if (!isMultiple.value) open.value = false;
167
169
  } else if (props.creatable) commitCreatedValue(token);
168
170
  }
169
171
  if (rest !== raw) searchQuery.value = rest;
170
172
  }
173
+ let searchTimer;
171
174
  watch(searchQuery, (query) => {
172
- if (props.remote) emit("search", query);
175
+ if (props.remote) {
176
+ const wait = props.debounce ?? 0;
177
+ if (searchTimer) clearTimeout(searchTimer);
178
+ if (wait <= 0) emit("search", query);
179
+ else searchTimer = setTimeout(() => emit("search", query), wait);
180
+ }
173
181
  if (props.tokenSeparators?.length) consumeTokenSeparators(query);
174
182
  });
183
+ onUnmounted(() => {
184
+ if (searchTimer) clearTimeout(searchTimer);
185
+ });
175
186
  watch(open, async (isOpen) => {
176
187
  if (isOpen) {
177
188
  const pending = searchQuery.value;
178
189
  await nextTick();
179
190
  await nextTick();
180
191
  if (pending) searchQuery.value = pending;
181
- else if (props.fillSearchWithValue && !props.multiple && hasValue.value) searchQuery.value = singleDisplayLabel.value;
192
+ else if (props.fillSearchWithValue && !isMultiple.value && hasValue.value) searchQuery.value = singleDisplayLabel.value;
182
193
  else searchQuery.value = "";
183
194
  return;
184
195
  }
@@ -196,19 +207,19 @@ function useRsSelect(props, model, open, searchQuery, emit, t) {
196
207
  event.stopPropagation();
197
208
  emitSelectionDiff(selectedValues.value, []);
198
209
  emit("clear");
199
- model.value = props.multiple ? [] : "";
210
+ model.value = isMultiple.value ? [] : "";
200
211
  }
201
212
  function removeTag(value, event) {
202
213
  event.preventDefault();
203
214
  event.stopPropagation();
204
- if (!props.multiple) return;
215
+ if (!isMultiple.value) return;
205
216
  const prev = selectedValues.value;
206
217
  const tokens = prev.filter((item) => item !== value);
207
218
  emitSelectionDiff(prev, tokens);
208
219
  writeTokens(tokens);
209
220
  }
210
221
  function setValue(value) {
211
- if (props.multiple) {
222
+ if (isMultiple.value) {
212
223
  writeTokens(toSelectedTokens(Array.isArray(value) ? value : value !== "" && value != null ? [value] : [], true));
213
224
  return;
214
225
  }
@@ -229,6 +240,7 @@ function useRsSelect(props, model, open, searchQuery, emit, t) {
229
240
  resolvedSearchPlaceholder,
230
241
  resolvedEmptyText,
231
242
  resolvedLoadingText,
243
+ isMultiple,
232
244
  isSearchable,
233
245
  labelMap,
234
246
  useVirtual,
@@ -1,10 +1,10 @@
1
+ import { resolveVirtualListHeight } from "../components/virtual-list-utils.js";
1
2
  import { resolveScrollWidth, selectRowKeys } from "../components/table-utils.js";
2
3
  import { RS_TABLE_PREFIX_COL_WIDTH, measureRsTablePrefixWidth, useRsTableColumnVirtual } from "./useRsTableColumnVirtual.js";
3
4
  import { useRsTableColumnLayout } from "./useRsTableColumnLayout.js";
4
5
  import { useRsTableColumnResize } from "./useRsTableColumnResize.js";
5
6
  import { useRsTableContextMenu } from "./useRsTableContextMenu.js";
6
7
  import { useRsTableInteraction } from "./useRsTableInteraction.js";
7
- import { resolveVirtualListHeight } from "../components/virtual-list-utils.js";
8
8
  import { computed } from "vue";
9
9
  //#region src/composables/useRsTableShell.ts
10
10
  /**
package/dist/index.d.ts CHANGED
@@ -35,6 +35,10 @@ export { default as RsLabel } from './components/RsLabel.js';
35
35
  export { default as RsLink } from './components/RsLink.js';
36
36
  export { default as RsMenu } from './components/RsMenu.js';
37
37
  export { default as RsSelect } from './components/RsSelect.js';
38
+ export { default as RsAutoComplete } from './components/RsAutoComplete.js';
39
+ export { default as RsCascader } from './components/RsCascader.js';
40
+ export { default as RsTreeSelect } from './components/RsTreeSelect.js';
41
+ export { default as RsMentions } from './components/RsMentions.js';
38
42
  export { default as RsScrollbar } from './components/RsScrollbar.js';
39
43
  export { default as RsAvatar } from './components/RsAvatar.js';
40
44
  export { default as RsCard } from './components/RsCard.js';
@@ -100,10 +104,12 @@ export type { RsContextMenuItem } from './components/context-menu-utils';
100
104
  export type { RsDropdownItem, RsDropdownItemGroup, RsDropdownItems } from './components/dropdown-utils';
101
105
  export type { RsMenuItem, RsMenuItemGroup, RsMenuItems } from './components/menu-utils';
102
106
  export type { RsScrollbarOrientation, RsScrollbarType } from './components/scrollbar-utils';
103
- export type { RsSelectFieldNames, RsSelectFilterOption, RsSelectFilterSort, RsSelectGetPopupContainer, RsSelectLabeledValue, RsSelectModelValue, RsSelectResolvedModel, RsSelectOption, RsSelectOptionFilterProp, RsSelectOptionGroup, RsSelectOptionInput, RsSelectOptions, RsSelectOptionsInput, RsSelectPlacement, RsSelectStatus, RsSelectValue, } from './components/select-utils';
107
+ export type { RsSelectFieldNames, RsSelectFilterOption, RsSelectFilterSort, RsSelectGetPopupContainer, RsSelectLabeledValue, RsSelectModelValue, RsSelectResolvedModel, RsSelectOption, RsSelectOptionFilterProp, RsSelectOptionGroup, RsSelectOptionInput, RsSelectOptions, RsSelectOptionsInput, RsSelectPlacement, RsSelectStatus, RsSelectMaxTagCount, RsSelectVariant, RsSelectValue, } from './components/select-utils';
108
+ export type { RsCascaderExpandTrigger, RsCascaderOption, RsCascaderPath, } from './components/cascader-utils';
109
+ export type { RsMentionActive, RsMentionCaretBox, RsMentionOption, RsMentionPopupBox, } from './components/mentions-utils';
104
110
  export { RS_SELECT_EMPTY_VALUE, fromComboboxValue, isSelectLabeledValue, normalizeSelectOptions, optionDisplayLabel, packSelectModel, restoreSelectValue, toComboboxValue, unwrapSelectEntry, } from './components/select-utils';
105
- export type { RsFeedbackTone, RsToastPosition, RsToastType } from './components/overlay-utils';
106
- export { RS_TOAST_DEFAULT_GAP, RS_TOAST_DEFAULT_POSITION, rsToastPositions, rsFeedbackIconClass } from './components/overlay-utils';
111
+ export type { RsFeedbackTone, RsToastPosition, RsToastType, RsOverlayAnchorBox, RsOverlayBox } from './components/overlay-utils';
112
+ export { RS_TOAST_DEFAULT_GAP, RS_TOAST_DEFAULT_POSITION, rsToastPositions, rsFeedbackIconClass, placeAnchoredPopup, stepEnabledIndex } from './components/overlay-utils';
107
113
  export type { RsFormContext, RsFormErrorRender, RsFormErrorRenderContext, RsFormFieldExpose, RsFormItemContext, RsFormListContext, RsFormListField, RsFormListOperations, RsFormFieldValidationResult, RsFormGap, RsFormLabelAlign, RsFormLabelPosition, RsFormMaxWidth, RsFormSize, RsFormValidateStatus, RsFormValidationResult, } from './components/form-utils';
108
114
  export { cloneFormFieldValue, isRsFormItemBoundControl, provideRsFormItemContext, provideRsFormListContext, RS_FORM_INJECTION_KEY, RS_FORM_ITEM_INJECTION_KEY, RS_FORM_LIST_INJECTION_KEY, resolveFieldRules, useRsFormContext, useRsFormField, useRsFormItemContext, useRsFormListContext, } from './components/form-utils';
109
115
  export type { RsFormNamePath } from './components/form-path';