@rainbow-oh/yee-c 0.6.4 → 0.8.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.
- package/dist/cjs/Button/style/index.less +5 -4
- package/dist/cjs/Drawer/drawer.js +1 -1
- package/dist/cjs/Drawer/interface.d.ts +1 -1
- package/dist/cjs/Drawer/style/index.less +1 -0
- package/dist/cjs/Form/form-store.d.ts +2 -1
- package/dist/cjs/Form/form-store.js +32 -17
- package/dist/cjs/List/demo/virtual.d.ts +3 -0
- package/dist/cjs/List/demo/virtual.js +53 -0
- package/dist/cjs/List/hooks/useListKeyboard.d.ts +28 -0
- package/dist/cjs/List/hooks/useListKeyboard.js +188 -0
- package/dist/cjs/List/interface.d.ts +29 -2
- package/dist/cjs/List/list.js +141 -21
- package/dist/cjs/List/style/index.less +24 -0
- package/dist/cjs/Select/demo/columns.d.ts +3 -0
- package/dist/cjs/Select/demo/columns.js +62 -0
- package/dist/cjs/Select/hooks/useSelectKeyboard.d.ts +13 -1
- package/dist/cjs/Select/hooks/useSelectKeyboard.js +81 -10
- package/dist/cjs/Select/interface.d.ts +30 -3
- package/dist/cjs/Select/option.js +4 -3
- package/dist/cjs/Select/options.js +90 -8
- package/dist/cjs/Select/select.js +32 -8
- package/dist/cjs/Select/style/index.less +14 -0
- package/dist/cjs/Selector/interface.d.ts +4 -0
- package/dist/cjs/Selector/selector.js +5 -3
- package/dist/cjs/Table/hooks/useColumns.js +1 -1
- package/dist/cjs/Table/interface.d.ts +1 -1
- package/dist/cjs/Table/style/index.less +35 -11
- package/dist/cjs/Table/table.js +57 -7
- package/dist/cjs/Table/tbody/cell.js +1 -1
- package/dist/cjs/Table/util/header.d.ts +3 -1
- package/dist/cjs/Table/util/header.js +30 -3
- package/dist/cjs/Tree/demo/draggable.js +2 -0
- package/dist/cjs/Tree/interface.d.ts +46 -23
- package/dist/cjs/Tree/tree-node.d.ts +1 -1
- package/dist/cjs/Tree/tree-node.js +30 -23
- package/dist/cjs/Tree/tree.d.ts +13 -12
- package/dist/cjs/Tree/tree.js +452 -227
- package/dist/cjs/Tree/utils/tree-tools.d.ts +43 -10
- package/dist/cjs/Tree/utils/tree-tools.js +129 -91
- package/dist/cjs/locale/context.d.ts +5 -4
- package/dist/cjs/locale/context.js +15 -8
- package/dist/cjs/locale/interface.d.ts +20 -0
- package/dist/cjs/locale/util.d.ts +3 -3
- package/dist/cjs/style/color.css +3 -0
- package/dist/cjs/style/size.css +2 -1
- package/dist/cjs/style/themes.css +1 -1
- package/dist/cjs/style/variables.css +6 -2
- package/dist/cjs/utils/isEqual.js +5 -7
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/Button/style/index.less +5 -4
- package/dist/esm/Drawer/drawer.js +2 -1
- package/dist/esm/Drawer/interface.d.ts +1 -1
- package/dist/esm/Drawer/style/index.less +1 -0
- package/dist/esm/Form/form-store.d.ts +2 -1
- package/dist/esm/Form/form-store.js +40 -19
- package/dist/esm/List/demo/virtual.d.ts +3 -0
- package/dist/esm/List/demo/virtual.js +22 -0
- package/dist/esm/List/hooks/useListKeyboard.d.ts +28 -0
- package/dist/esm/List/hooks/useListKeyboard.js +173 -0
- package/dist/esm/List/interface.d.ts +29 -2
- package/dist/esm/List/list.js +171 -14
- package/dist/esm/List/style/index.less +24 -0
- package/dist/esm/Select/demo/columns.d.ts +3 -0
- package/dist/esm/Select/demo/columns.js +36 -0
- package/dist/esm/Select/hooks/useSelectKeyboard.d.ts +13 -1
- package/dist/esm/Select/hooks/useSelectKeyboard.js +91 -14
- package/dist/esm/Select/interface.d.ts +30 -3
- package/dist/esm/Select/option.js +6 -2
- package/dist/esm/Select/options.js +114 -32
- package/dist/esm/Select/select.js +40 -11
- package/dist/esm/Select/style/index.less +14 -0
- package/dist/esm/Selector/interface.d.ts +4 -0
- package/dist/esm/Selector/selector.js +10 -4
- package/dist/esm/Table/hooks/useColumns.js +1 -1
- package/dist/esm/Table/interface.d.ts +1 -1
- package/dist/esm/Table/style/index.less +35 -11
- package/dist/esm/Table/table.js +54 -2
- package/dist/esm/Table/tbody/cell.js +1 -1
- package/dist/esm/Table/util/header.d.ts +3 -1
- package/dist/esm/Table/util/header.js +30 -2
- package/dist/esm/Tree/demo/draggable.js +2 -0
- package/dist/esm/Tree/interface.d.ts +46 -23
- package/dist/esm/Tree/tree-node.d.ts +1 -1
- package/dist/esm/Tree/tree-node.js +26 -31
- package/dist/esm/Tree/tree.d.ts +13 -12
- package/dist/esm/Tree/tree.js +572 -163
- package/dist/esm/Tree/utils/tree-tools.d.ts +43 -10
- package/dist/esm/Tree/utils/tree-tools.js +219 -104
- package/dist/esm/locale/context.d.ts +5 -4
- package/dist/esm/locale/context.js +25 -9
- package/dist/esm/locale/interface.d.ts +20 -0
- package/dist/esm/locale/util.d.ts +3 -3
- package/dist/esm/style/color.css +3 -0
- package/dist/esm/style/size.css +2 -1
- package/dist/esm/style/themes.css +1 -1
- package/dist/esm/style/variables.css +6 -2
- package/dist/esm/utils/isEqual.js +5 -7
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/umd/yee-c.min.css +4 -4
- package/dist/umd/yee-c.min.js +1 -1
- package/package.json +1 -1
- package/src/Button/style/index.less +5 -4
- package/src/Drawer/drawer.tsx +1 -1
- package/src/Drawer/interface.ts +1 -0
- package/src/Drawer/style/index.less +1 -0
- package/src/Form/form-store.ts +42 -15
- package/src/List/demo/virtual.tsx +20 -0
- package/src/List/hooks/useListKeyboard.ts +235 -0
- package/src/List/index.en-US.md +6 -0
- package/src/List/index.zh-CN.md +6 -0
- package/src/List/interface.ts +29 -2
- package/src/List/list.tsx +157 -19
- package/src/List/style/index.less +24 -0
- package/src/Select/demo/columns.tsx +35 -0
- package/src/Select/hooks/useSelectKeyboard.ts +112 -15
- package/src/Select/index.en-US.md +4 -0
- package/src/Select/index.zh-CN.md +4 -0
- package/src/Select/interface.ts +31 -4
- package/src/Select/option.tsx +9 -3
- package/src/Select/options.tsx +119 -26
- package/src/Select/select.tsx +41 -8
- package/src/Select/style/index.less +14 -0
- package/src/Selector/interface.ts +4 -0
- package/src/Selector/selector.tsx +8 -3
- package/src/Table/hooks/useColumns.ts +1 -1
- package/src/Table/interface.ts +1 -1
- package/src/Table/style/index.less +35 -11
- package/src/Table/table.tsx +60 -1
- package/src/Table/tbody/cell.tsx +1 -1
- package/src/Table/util/header.ts +48 -2
- package/src/Tree/demo/draggable.tsx +2 -0
- package/src/Tree/interface.ts +46 -23
- package/src/Tree/tree-node.tsx +26 -29
- package/src/Tree/tree.tsx +573 -274
- package/src/Tree/utils/tree-tools.ts +231 -125
- package/src/locale/_README.md +22 -13
- package/src/locale/context.tsx +33 -16
- package/src/locale/interface.ts +24 -0
- package/src/locale/util.ts +6 -6
- package/src/style/color.css +3 -0
- package/src/style/size.css +2 -1
- package/src/utils/isEqual.ts +5 -7
- package/src/version.ts +1 -1
|
@@ -6,18 +6,42 @@
|
|
|
6
6
|
list-style: none;
|
|
7
7
|
background-color: var(--yee-list-bg-color);
|
|
8
8
|
|
|
9
|
+
&:focus {
|
|
10
|
+
outline: none;
|
|
11
|
+
}
|
|
12
|
+
|
|
9
13
|
&-item {
|
|
10
14
|
padding: var(--yee-list-item-padding);
|
|
11
15
|
|
|
12
16
|
&:hover:not(&-disabled) {
|
|
13
17
|
background-color: var(--yee-list-item-bg-hover-color);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
&-disabled {
|
|
14
21
|
cursor: not-allowed;
|
|
22
|
+
opacity: 0.5;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
&-clickable:not(&-disabled) {
|
|
26
|
+
cursor: pointer;
|
|
15
27
|
}
|
|
16
28
|
|
|
17
29
|
&-focused {
|
|
18
30
|
background-color: var(--yee-list-item-bg-hover-color);
|
|
19
31
|
}
|
|
20
32
|
}
|
|
33
|
+
|
|
34
|
+
// Multi-column grid layout (non-virtual mode).
|
|
35
|
+
// Column count driven by CSS custom property --yee-list-columns set inline.
|
|
36
|
+
&-grid {
|
|
37
|
+
display: grid;
|
|
38
|
+
grid-template-columns: repeat(var(--yee-list-columns, 1), 1fr);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Virtual multi-column: each row is already a grid via inline style.
|
|
42
|
+
&-grid-row {
|
|
43
|
+
align-items: center;
|
|
44
|
+
}
|
|
21
45
|
}
|
|
22
46
|
|
|
23
47
|
@{pls}-bordered {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
|
|
29
|
+
// src/Select/demo/columns.tsx
|
|
30
|
+
var columns_exports = {};
|
|
31
|
+
__export(columns_exports, {
|
|
32
|
+
default: () => columns_default
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(columns_exports);
|
|
35
|
+
var import_yee_c = require("../..");
|
|
36
|
+
var import_react = __toESM(require("react"));
|
|
37
|
+
var options = Array.from({ length: 24 }, (_, i) => ({
|
|
38
|
+
label: `Option ${i + 1}`,
|
|
39
|
+
value: `opt-${i + 1}`
|
|
40
|
+
}));
|
|
41
|
+
var columns_default = () => {
|
|
42
|
+
return /* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 24 } }, /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("p", null, "3 columns:"), /* @__PURE__ */ import_react.default.createElement(
|
|
43
|
+
import_yee_c.Select,
|
|
44
|
+
{
|
|
45
|
+
columns: 3,
|
|
46
|
+
popupWidth: 360,
|
|
47
|
+
options,
|
|
48
|
+
placeholder: "Select an option",
|
|
49
|
+
style: { width: 200 }
|
|
50
|
+
}
|
|
51
|
+
)), /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("p", null, "4 columns with multiple selection:"), /* @__PURE__ */ import_react.default.createElement(
|
|
52
|
+
import_yee_c.Select,
|
|
53
|
+
{
|
|
54
|
+
columns: 4,
|
|
55
|
+
popupWidth: 480,
|
|
56
|
+
mode: "multiple",
|
|
57
|
+
options,
|
|
58
|
+
placeholder: "Select options",
|
|
59
|
+
style: { width: 300 }
|
|
60
|
+
}
|
|
61
|
+
)));
|
|
62
|
+
};
|
|
@@ -7,6 +7,18 @@ interface UseSelectKeyboardParams {
|
|
|
7
7
|
}>;
|
|
8
8
|
open: boolean;
|
|
9
9
|
selectedKeys: Array<string | number>;
|
|
10
|
+
/**
|
|
11
|
+
* Number of columns in multi-column mode.
|
|
12
|
+
* Affects ArrowLeft/ArrowRight and ArrowUp/ArrowDown behavior.
|
|
13
|
+
* @default 1
|
|
14
|
+
*/
|
|
15
|
+
columns?: number;
|
|
16
|
+
/**
|
|
17
|
+
* Whether loose value matching is enabled.
|
|
18
|
+
* Affects initial focus resolution when selectedKeys types differ from option values.
|
|
19
|
+
* @default false
|
|
20
|
+
*/
|
|
21
|
+
looseMatch?: boolean;
|
|
10
22
|
onSelect: (value: string | number) => void;
|
|
11
23
|
onClose?: () => void;
|
|
12
24
|
onOpenChange?: (open: boolean) => void;
|
|
@@ -18,7 +30,7 @@ interface UseSelectKeyboardParams {
|
|
|
18
30
|
*/
|
|
19
31
|
scrollToIndex?: (index: number) => void;
|
|
20
32
|
}
|
|
21
|
-
export default function useSelectKeyboard({ options, open, selectedKeys, onSelect, onClose, onOpenChange, containerRef, scrollToIndex, }: UseSelectKeyboardParams): {
|
|
33
|
+
export default function useSelectKeyboard({ options, open, selectedKeys, columns, looseMatch, onSelect, onClose, onOpenChange, containerRef, scrollToIndex, }: UseSelectKeyboardParams): {
|
|
22
34
|
focusedKey: string | number;
|
|
23
35
|
onKeyDown: (event: React.KeyboardEvent) => void;
|
|
24
36
|
};
|
|
@@ -38,6 +38,8 @@ function useSelectKeyboard({
|
|
|
38
38
|
options,
|
|
39
39
|
open,
|
|
40
40
|
selectedKeys,
|
|
41
|
+
columns = 1,
|
|
42
|
+
looseMatch = false,
|
|
41
43
|
onSelect,
|
|
42
44
|
onClose,
|
|
43
45
|
onOpenChange,
|
|
@@ -80,18 +82,39 @@ function useSelectKeyboard({
|
|
|
80
82
|
const optionElements = container.querySelectorAll('[role="option"]');
|
|
81
83
|
const targetElement = optionElements[index];
|
|
82
84
|
if (targetElement) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
const containerRect = container.getBoundingClientRect();
|
|
86
|
+
const targetRect = targetElement.getBoundingClientRect();
|
|
87
|
+
if (targetRect.bottom > containerRect.bottom) {
|
|
88
|
+
container.scrollTop += targetRect.bottom - containerRect.bottom;
|
|
89
|
+
} else if (targetRect.top < containerRect.top) {
|
|
90
|
+
container.scrollTop -= containerRect.top - targetRect.top;
|
|
91
|
+
}
|
|
86
92
|
}
|
|
87
93
|
};
|
|
88
94
|
const handleArrowUp = () => {
|
|
95
|
+
var _a;
|
|
89
96
|
const focusedIndex = getFocusedIndex();
|
|
90
97
|
let nextIndex;
|
|
91
|
-
if (
|
|
92
|
-
|
|
98
|
+
if (columns > 1) {
|
|
99
|
+
if (focusedIndex === -1) {
|
|
100
|
+
nextIndex = findNextEnabledIndex(options.length - 1, -1);
|
|
101
|
+
} else {
|
|
102
|
+
const target = focusedIndex - columns;
|
|
103
|
+
if (target >= 0) {
|
|
104
|
+
nextIndex = options[target].disabled ? findNextEnabledIndex(target, -1) : target;
|
|
105
|
+
} else {
|
|
106
|
+
const lastRowStart = Math.floor((options.length - 1) / columns) * columns;
|
|
107
|
+
const col = focusedIndex % columns;
|
|
108
|
+
const wrapTarget = Math.min(lastRowStart + col, options.length - 1);
|
|
109
|
+
nextIndex = ((_a = options[wrapTarget]) == null ? void 0 : _a.disabled) ? findNextEnabledIndex(wrapTarget, -1) : wrapTarget;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
93
112
|
} else {
|
|
94
|
-
|
|
113
|
+
if (focusedIndex === -1) {
|
|
114
|
+
nextIndex = findNextEnabledIndex(options.length - 1, -1);
|
|
115
|
+
} else {
|
|
116
|
+
nextIndex = findNextEnabledIndex(focusedIndex, -1);
|
|
117
|
+
}
|
|
95
118
|
}
|
|
96
119
|
if (nextIndex !== -1) {
|
|
97
120
|
const nextValue = options[nextIndex].value;
|
|
@@ -100,12 +123,28 @@ function useSelectKeyboard({
|
|
|
100
123
|
}
|
|
101
124
|
};
|
|
102
125
|
const handleArrowDown = () => {
|
|
126
|
+
var _a;
|
|
103
127
|
const focusedIndex = getFocusedIndex();
|
|
104
128
|
let nextIndex;
|
|
105
|
-
if (
|
|
106
|
-
|
|
129
|
+
if (columns > 1) {
|
|
130
|
+
if (focusedIndex === -1) {
|
|
131
|
+
nextIndex = findNextEnabledIndex(0, 1);
|
|
132
|
+
} else {
|
|
133
|
+
const target = focusedIndex + columns;
|
|
134
|
+
if (target < options.length) {
|
|
135
|
+
nextIndex = options[target].disabled ? findNextEnabledIndex(target, 1) : target;
|
|
136
|
+
} else {
|
|
137
|
+
const col = focusedIndex % columns;
|
|
138
|
+
const wrapTarget = col;
|
|
139
|
+
nextIndex = ((_a = options[wrapTarget]) == null ? void 0 : _a.disabled) ? findNextEnabledIndex(wrapTarget, 1) : wrapTarget;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
107
142
|
} else {
|
|
108
|
-
|
|
143
|
+
if (focusedIndex === -1) {
|
|
144
|
+
nextIndex = findNextEnabledIndex(0, 1);
|
|
145
|
+
} else {
|
|
146
|
+
nextIndex = findNextEnabledIndex(focusedIndex, 1);
|
|
147
|
+
}
|
|
109
148
|
}
|
|
110
149
|
if (nextIndex !== -1) {
|
|
111
150
|
const nextValue = options[nextIndex].value;
|
|
@@ -113,6 +152,26 @@ function useSelectKeyboard({
|
|
|
113
152
|
scrollOptionIntoView(nextIndex);
|
|
114
153
|
}
|
|
115
154
|
};
|
|
155
|
+
const handleArrowLeft = () => {
|
|
156
|
+
const focusedIndex = getFocusedIndex();
|
|
157
|
+
if (focusedIndex <= 0)
|
|
158
|
+
return;
|
|
159
|
+
const nextIndex = findNextEnabledIndex(focusedIndex, -1);
|
|
160
|
+
if (nextIndex !== -1) {
|
|
161
|
+
setFocusedKey(options[nextIndex].value);
|
|
162
|
+
scrollOptionIntoView(nextIndex);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
const handleArrowRight = () => {
|
|
166
|
+
const focusedIndex = getFocusedIndex();
|
|
167
|
+
if (focusedIndex >= options.length - 1)
|
|
168
|
+
return;
|
|
169
|
+
const nextIndex = findNextEnabledIndex(focusedIndex, 1);
|
|
170
|
+
if (nextIndex !== -1) {
|
|
171
|
+
setFocusedKey(options[nextIndex].value);
|
|
172
|
+
scrollOptionIntoView(nextIndex);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
116
175
|
const handleEnter = () => {
|
|
117
176
|
const currentFocusedKey = latestFocusedKey.current;
|
|
118
177
|
if (!currentFocusedKey)
|
|
@@ -142,6 +201,18 @@ function useSelectKeyboard({
|
|
|
142
201
|
event.preventDefault();
|
|
143
202
|
handleArrowDown();
|
|
144
203
|
break;
|
|
204
|
+
case "ArrowLeft":
|
|
205
|
+
if (columns > 1) {
|
|
206
|
+
event.preventDefault();
|
|
207
|
+
handleArrowLeft();
|
|
208
|
+
}
|
|
209
|
+
break;
|
|
210
|
+
case "ArrowRight":
|
|
211
|
+
if (columns > 1) {
|
|
212
|
+
event.preventDefault();
|
|
213
|
+
handleArrowRight();
|
|
214
|
+
}
|
|
215
|
+
break;
|
|
145
216
|
case "Enter":
|
|
146
217
|
event.preventDefault();
|
|
147
218
|
handleEnter();
|
|
@@ -158,7 +229,7 @@ function useSelectKeyboard({
|
|
|
158
229
|
let focusedValue = "";
|
|
159
230
|
if ((selectedKeys == null ? void 0 : selectedKeys.length) > 0) {
|
|
160
231
|
const firstSelected = options == null ? void 0 : options.find(
|
|
161
|
-
(opt) => opt.value === selectedKeys[0]
|
|
232
|
+
(opt) => looseMatch ? String(opt.value) === String(selectedKeys[0]) : opt.value === selectedKeys[0]
|
|
162
233
|
);
|
|
163
234
|
if (firstSelected && !firstSelected.disabled) {
|
|
164
235
|
focusedValue = firstSelected.value;
|
|
@@ -126,10 +126,27 @@ export interface SelectProps extends DataAttributeProps, Pick<SelectorProps, 'si
|
|
|
126
126
|
* @default 200
|
|
127
127
|
*/
|
|
128
128
|
listHeight?: number;
|
|
129
|
+
/**
|
|
130
|
+
* Number of columns in the dropdown panel.
|
|
131
|
+
* When greater than 1, options are displayed in a grid layout.
|
|
132
|
+
* @default 1
|
|
133
|
+
*/
|
|
134
|
+
columns?: number;
|
|
135
|
+
/**
|
|
136
|
+
* Fixed width of the dropdown popup in pixels.
|
|
137
|
+
* When set, the popup will not stretch to match the trigger width.
|
|
138
|
+
* Useful with multi-column layout to provide enough horizontal space.
|
|
139
|
+
*/
|
|
140
|
+
popupWidth?: number;
|
|
141
|
+
/**
|
|
142
|
+
* Enable loose value matching. When enabled, value comparison uses
|
|
143
|
+
* `String(a) === String(b)` instead of strict equality (`===`).
|
|
144
|
+
* Resolves type mismatch when options use string values but the
|
|
145
|
+
* controlled value is a number (or vice versa).
|
|
146
|
+
* @default false
|
|
147
|
+
*/
|
|
148
|
+
looseMatch?: boolean;
|
|
129
149
|
}
|
|
130
|
-
/**
|
|
131
|
-
* Props for the internal Option component (renders a single dropdown option).
|
|
132
|
-
*/
|
|
133
150
|
export interface OptionProps {
|
|
134
151
|
/**
|
|
135
152
|
* Display label
|
|
@@ -165,6 +182,7 @@ export interface OptionsContextValue {
|
|
|
165
182
|
selectedKeys: Array<string | number>;
|
|
166
183
|
focusedKey: string | number;
|
|
167
184
|
multiple: boolean;
|
|
185
|
+
looseMatch: boolean;
|
|
168
186
|
onSelect: (key: string | number, e?: React.MouseEvent | React.KeyboardEvent) => void;
|
|
169
187
|
}
|
|
170
188
|
/**
|
|
@@ -223,6 +241,15 @@ export interface OptionsProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
|
|
|
223
241
|
virtualApiRef?: React.MutableRefObject<{
|
|
224
242
|
scrollToIndex: (index: number) => void;
|
|
225
243
|
} | null>;
|
|
244
|
+
/**
|
|
245
|
+
* Number of columns in the dropdown grid layout.
|
|
246
|
+
* @default 1
|
|
247
|
+
*/
|
|
248
|
+
columns?: number;
|
|
249
|
+
/**
|
|
250
|
+
* Whether loose value matching is enabled
|
|
251
|
+
*/
|
|
252
|
+
looseMatch?: boolean;
|
|
226
253
|
/**
|
|
227
254
|
* Callback when an option is selected
|
|
228
255
|
*/
|
|
@@ -38,20 +38,21 @@ var import_react = __toESM(require("react"));
|
|
|
38
38
|
var import_options = require("./options");
|
|
39
39
|
var Option = (props) => {
|
|
40
40
|
const { label, value, disabled, title, dataTestId, style } = props;
|
|
41
|
-
const { prefixCls, selectedKeys, focusedKey, multiple, onSelect } = (0, import_react.useContext)(import_options.OptionsCtx);
|
|
41
|
+
const { prefixCls, selectedKeys, focusedKey, multiple, looseMatch, onSelect } = (0, import_react.useContext)(import_options.OptionsCtx);
|
|
42
42
|
const handleClick = (e) => {
|
|
43
43
|
if (!disabled) {
|
|
44
44
|
onSelect(value, e);
|
|
45
45
|
}
|
|
46
46
|
};
|
|
47
47
|
const htmlTitle = title ? title : typeof label === "string" ? label : void 0;
|
|
48
|
-
const selected = selectedKeys.includes(value);
|
|
48
|
+
const selected = looseMatch ? selectedKeys.some((k) => String(k) === String(value)) : selectedKeys.includes(value);
|
|
49
|
+
const focused = looseMatch ? String(focusedKey) === String(value) : focusedKey === value;
|
|
49
50
|
return /* @__PURE__ */ import_react.default.createElement(
|
|
50
51
|
"div",
|
|
51
52
|
{
|
|
52
53
|
className: (0, import_clsx.default)(`${prefixCls}-option`, {
|
|
53
54
|
[`${prefixCls}-option-selected`]: selected,
|
|
54
|
-
[`${prefixCls}-option-focused`]:
|
|
55
|
+
[`${prefixCls}-option-focused`]: focused,
|
|
55
56
|
[`${prefixCls}-option-disabled`]: disabled
|
|
56
57
|
}),
|
|
57
58
|
"aria-label": "option",
|
|
@@ -87,6 +87,8 @@ var Options = (0, import_react.forwardRef)(
|
|
|
87
87
|
itemHeight = 32,
|
|
88
88
|
listHeight = 200,
|
|
89
89
|
virtualApiRef,
|
|
90
|
+
columns = 1,
|
|
91
|
+
looseMatch = false,
|
|
90
92
|
onSelect
|
|
91
93
|
} = _a, rest = __objRest(_a, [
|
|
92
94
|
"prefixCls",
|
|
@@ -101,10 +103,14 @@ var Options = (0, import_react.forwardRef)(
|
|
|
101
103
|
"itemHeight",
|
|
102
104
|
"listHeight",
|
|
103
105
|
"virtualApiRef",
|
|
106
|
+
"columns",
|
|
107
|
+
"looseMatch",
|
|
104
108
|
"onSelect"
|
|
105
109
|
]);
|
|
110
|
+
const multiColumn = columns > 1;
|
|
106
111
|
const rootRef = (0, import_react.useRef)(null);
|
|
107
112
|
(0, import_react.useImperativeHandle)(ref, () => rootRef.current, []);
|
|
113
|
+
const rowCount = multiColumn ? Math.ceil(options.length / columns) : options.length;
|
|
108
114
|
const {
|
|
109
115
|
start,
|
|
110
116
|
end,
|
|
@@ -114,19 +120,24 @@ var Options = (0, import_react.forwardRef)(
|
|
|
114
120
|
onScroll,
|
|
115
121
|
scrollToIndex
|
|
116
122
|
} = (0, import_useVirtualList.default)({
|
|
117
|
-
itemCount:
|
|
123
|
+
itemCount: virtual ? rowCount : 0,
|
|
118
124
|
itemHeight
|
|
119
125
|
});
|
|
120
126
|
(0, import_react.useEffect)(() => {
|
|
121
127
|
if (!virtual || !virtualApiRef)
|
|
122
128
|
return;
|
|
123
|
-
virtualApiRef.current = {
|
|
129
|
+
virtualApiRef.current = {
|
|
130
|
+
scrollToIndex: (flatIndex) => {
|
|
131
|
+
const rowIndex = multiColumn ? Math.floor(flatIndex / columns) : flatIndex;
|
|
132
|
+
scrollToIndex(rowIndex);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
124
135
|
return () => {
|
|
125
136
|
if (virtualApiRef.current) {
|
|
126
137
|
virtualApiRef.current = null;
|
|
127
138
|
}
|
|
128
139
|
};
|
|
129
|
-
}, [virtual, virtualApiRef, scrollToIndex]);
|
|
140
|
+
}, [virtual, virtualApiRef, scrollToIndex, multiColumn, columns]);
|
|
130
141
|
(0, import_react.useEffect)(() => {
|
|
131
142
|
if (virtual) {
|
|
132
143
|
scrollToIndex(0);
|
|
@@ -141,7 +152,8 @@ var Options = (0, import_react.forwardRef)(
|
|
|
141
152
|
if (focusedIndex < 0)
|
|
142
153
|
return;
|
|
143
154
|
if (virtual) {
|
|
144
|
-
|
|
155
|
+
const rowIndex = multiColumn ? Math.floor(focusedIndex / columns) : focusedIndex;
|
|
156
|
+
scrollToIndex(rowIndex);
|
|
145
157
|
return;
|
|
146
158
|
}
|
|
147
159
|
const container = rootRef.current;
|
|
@@ -150,17 +162,83 @@ var Options = (0, import_react.forwardRef)(
|
|
|
150
162
|
const optionElements = container.querySelectorAll('[role="option"]');
|
|
151
163
|
const targetElement = optionElements[focusedIndex];
|
|
152
164
|
if (targetElement) {
|
|
153
|
-
|
|
165
|
+
const containerRect = container.getBoundingClientRect();
|
|
166
|
+
const targetRect = targetElement.getBoundingClientRect();
|
|
167
|
+
if (targetRect.bottom > containerRect.bottom) {
|
|
168
|
+
container.scrollTop += targetRect.bottom - containerRect.bottom;
|
|
169
|
+
} else if (targetRect.top < containerRect.top) {
|
|
170
|
+
container.scrollTop -= containerRect.top - targetRect.top;
|
|
171
|
+
}
|
|
154
172
|
}
|
|
155
|
-
}, [focusedKey, virtual, options, scrollToIndex, rootRef]);
|
|
173
|
+
}, [focusedKey, virtual, options, scrollToIndex, rootRef, multiColumn, columns]);
|
|
156
174
|
const isEmpty = options.length === 0;
|
|
157
175
|
const ctxValue = {
|
|
158
176
|
prefixCls,
|
|
159
177
|
selectedKeys,
|
|
160
178
|
focusedKey,
|
|
161
179
|
multiple,
|
|
180
|
+
looseMatch,
|
|
162
181
|
onSelect
|
|
163
182
|
};
|
|
183
|
+
if (virtual && multiColumn && !isEmpty) {
|
|
184
|
+
const visibleRows = [];
|
|
185
|
+
for (let rowIdx = start; rowIdx < end; rowIdx++) {
|
|
186
|
+
const rowOptions = [];
|
|
187
|
+
for (let col = 0; col < columns; col++) {
|
|
188
|
+
const flatIdx = rowIdx * columns + col;
|
|
189
|
+
if (flatIdx < options.length) {
|
|
190
|
+
rowOptions.push({ option: options[flatIdx], flatIdx });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
visibleRows.push({ rowIdx, rowOptions });
|
|
194
|
+
}
|
|
195
|
+
return /* @__PURE__ */ import_react.default.createElement(
|
|
196
|
+
"div",
|
|
197
|
+
__spreadProps(__spreadValues({}, rest), {
|
|
198
|
+
className: (0, import_clsx.default)(
|
|
199
|
+
`${prefixCls}-popup`,
|
|
200
|
+
`${prefixCls}-popup-virtual`,
|
|
201
|
+
popupClassName
|
|
202
|
+
),
|
|
203
|
+
style: popupStyle,
|
|
204
|
+
ref: rootRef,
|
|
205
|
+
tabIndex: -1
|
|
206
|
+
}),
|
|
207
|
+
/* @__PURE__ */ import_react.default.createElement(OptionsCtx.Provider, { value: ctxValue }, /* @__PURE__ */ import_react.default.createElement(
|
|
208
|
+
"div",
|
|
209
|
+
{
|
|
210
|
+
className: `${prefixCls}-popup-viewport`,
|
|
211
|
+
ref: viewportRef,
|
|
212
|
+
onScroll,
|
|
213
|
+
style: { maxHeight: listHeight, overflowY: "auto" }
|
|
214
|
+
},
|
|
215
|
+
/* @__PURE__ */ import_react.default.createElement("div", { style: { height: totalHeight, position: "relative" } }, visibleRows.map(({ rowIdx, rowOptions }, ri) => /* @__PURE__ */ import_react.default.createElement(
|
|
216
|
+
"div",
|
|
217
|
+
{
|
|
218
|
+
key: rowIdx,
|
|
219
|
+
className: `${prefixCls}-popup-grid-row`,
|
|
220
|
+
style: {
|
|
221
|
+
position: "absolute",
|
|
222
|
+
top: 0,
|
|
223
|
+
left: 0,
|
|
224
|
+
right: 0,
|
|
225
|
+
height: itemHeight,
|
|
226
|
+
transform: `translateY(${offsetY + ri * itemHeight}px)`,
|
|
227
|
+
display: "grid",
|
|
228
|
+
gridTemplateColumns: `repeat(${columns}, 1fr)`
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
rowOptions.map(({ option, flatIdx }) => /* @__PURE__ */ import_react.default.createElement(
|
|
232
|
+
import_option.default,
|
|
233
|
+
__spreadProps(__spreadValues({}, option), {
|
|
234
|
+
key: option.value,
|
|
235
|
+
dataTestId: `${dataTestId}-${flatIdx}`
|
|
236
|
+
})
|
|
237
|
+
))
|
|
238
|
+
)))
|
|
239
|
+
))
|
|
240
|
+
);
|
|
241
|
+
}
|
|
164
242
|
if (virtual && !isEmpty) {
|
|
165
243
|
const visibleOptions = options.slice(start, end);
|
|
166
244
|
return /* @__PURE__ */ import_react.default.createElement(
|
|
@@ -204,8 +282,12 @@ var Options = (0, import_react.forwardRef)(
|
|
|
204
282
|
return /* @__PURE__ */ import_react.default.createElement(
|
|
205
283
|
"div",
|
|
206
284
|
__spreadProps(__spreadValues({}, rest), {
|
|
207
|
-
className: (0, import_clsx.default)(
|
|
208
|
-
|
|
285
|
+
className: (0, import_clsx.default)(
|
|
286
|
+
`${prefixCls}-popup`,
|
|
287
|
+
{ [`${prefixCls}-popup-grid`]: multiColumn },
|
|
288
|
+
popupClassName
|
|
289
|
+
),
|
|
290
|
+
style: __spreadValues(__spreadValues({}, popupStyle), multiColumn ? { "--yee-select-columns": columns } : void 0),
|
|
209
291
|
ref: rootRef,
|
|
210
292
|
tabIndex: -1
|
|
211
293
|
}),
|
|
@@ -70,6 +70,16 @@ var import_mergeContextToProps = __toESM(require("../utils/mergeContextToProps")
|
|
|
70
70
|
var import_useSelectKeyboard = __toESM(require("./hooks/useSelectKeyboard"));
|
|
71
71
|
var import_options = __toESM(require("./options"));
|
|
72
72
|
var import_style = require("./style/index.less");
|
|
73
|
+
function matchValue(a, b, loose) {
|
|
74
|
+
if (loose)
|
|
75
|
+
return String(a) === String(b);
|
|
76
|
+
return a === b;
|
|
77
|
+
}
|
|
78
|
+
function keysIncludes(keys, val, loose) {
|
|
79
|
+
if (loose)
|
|
80
|
+
return keys.some((k) => String(k) === String(val));
|
|
81
|
+
return keys.includes(val);
|
|
82
|
+
}
|
|
73
83
|
var Select = (baseprops) => {
|
|
74
84
|
const { select } = (0, import_react.useContext)(import_Config_Provider.GlobalContext);
|
|
75
85
|
const props = (0, import_mergeContextToProps.default)(baseprops, select);
|
|
@@ -87,6 +97,9 @@ var Select = (baseprops) => {
|
|
|
87
97
|
virtual,
|
|
88
98
|
itemHeight,
|
|
89
99
|
listHeight,
|
|
100
|
+
columns = 1,
|
|
101
|
+
popupWidth,
|
|
102
|
+
looseMatch = false,
|
|
90
103
|
orphanClassName,
|
|
91
104
|
orphanStyle
|
|
92
105
|
} = _a, rest = __objRest(_a, [
|
|
@@ -103,6 +116,9 @@ var Select = (baseprops) => {
|
|
|
103
116
|
"virtual",
|
|
104
117
|
"itemHeight",
|
|
105
118
|
"listHeight",
|
|
119
|
+
"columns",
|
|
120
|
+
"popupWidth",
|
|
121
|
+
"looseMatch",
|
|
106
122
|
"orphanClassName",
|
|
107
123
|
"orphanStyle"
|
|
108
124
|
]);
|
|
@@ -139,11 +155,11 @@ var Select = (baseprops) => {
|
|
|
139
155
|
return [];
|
|
140
156
|
}
|
|
141
157
|
return keys.map((key) => {
|
|
142
|
-
const opt = options.find((o) => o.value
|
|
158
|
+
const opt = options.find((o) => matchValue(o.value, key, looseMatch));
|
|
143
159
|
return opt != null ? opt : { value: key, label: String(key) };
|
|
144
160
|
});
|
|
145
161
|
}
|
|
146
|
-
return (_a2 = options.find((opt) => opt.value
|
|
162
|
+
return (_a2 = options.find((opt) => matchValue(opt.value, keys, looseMatch))) != null ? _a2 : {
|
|
147
163
|
value: keys,
|
|
148
164
|
label: String(keys)
|
|
149
165
|
};
|
|
@@ -156,8 +172,8 @@ var Select = (baseprops) => {
|
|
|
156
172
|
setSearchValue("");
|
|
157
173
|
setOpen(false);
|
|
158
174
|
} else {
|
|
159
|
-
if (mergedValue
|
|
160
|
-
keys = mergedValue.filter((k) => k
|
|
175
|
+
if (keysIncludes(mergedValue, key, looseMatch)) {
|
|
176
|
+
keys = mergedValue.filter((k) => !matchValue(k, key, looseMatch));
|
|
161
177
|
} else {
|
|
162
178
|
keys = [...mergedValue, key];
|
|
163
179
|
}
|
|
@@ -174,7 +190,9 @@ var Select = (baseprops) => {
|
|
|
174
190
|
onChange == null ? void 0 : onChange(single ? "" : [], single ? void 0 : []);
|
|
175
191
|
};
|
|
176
192
|
const handleRemove = (option) => {
|
|
177
|
-
const keys = mergedValue.filter(
|
|
193
|
+
const keys = mergedValue.filter(
|
|
194
|
+
(k) => !matchValue(k, option.value, looseMatch)
|
|
195
|
+
);
|
|
178
196
|
const filteredOpts2 = getOptions(keys);
|
|
179
197
|
setMergedValue(keys);
|
|
180
198
|
const key = single ? keys[0] : keys;
|
|
@@ -203,6 +221,8 @@ var Select = (baseprops) => {
|
|
|
203
221
|
options: filteredOpts,
|
|
204
222
|
open,
|
|
205
223
|
selectedKeys: mergedValue,
|
|
224
|
+
columns,
|
|
225
|
+
looseMatch,
|
|
206
226
|
onSelect: handleSelect,
|
|
207
227
|
onClose: () => setOpen(false),
|
|
208
228
|
onOpenChange: handleOpenChange,
|
|
@@ -223,7 +243,9 @@ var Select = (baseprops) => {
|
|
|
223
243
|
virtual,
|
|
224
244
|
itemHeight,
|
|
225
245
|
listHeight,
|
|
226
|
-
virtualApiRef
|
|
246
|
+
virtualApiRef,
|
|
247
|
+
columns,
|
|
248
|
+
looseMatch
|
|
227
249
|
}
|
|
228
250
|
);
|
|
229
251
|
return /* @__PURE__ */ import_react.default.createElement(
|
|
@@ -231,7 +253,8 @@ var Select = (baseprops) => {
|
|
|
231
253
|
__spreadProps(__spreadValues({}, rest), {
|
|
232
254
|
placement,
|
|
233
255
|
popup,
|
|
234
|
-
stretch: "width",
|
|
256
|
+
stretch: popupWidth ? void 0 : "width",
|
|
257
|
+
popupStyle: popupWidth ? { width: popupWidth } : void 0,
|
|
235
258
|
open: disabled ? false : open,
|
|
236
259
|
onOpenChange: handleOpenChange
|
|
237
260
|
}),
|
|
@@ -249,7 +272,8 @@ var Select = (baseprops) => {
|
|
|
249
272
|
onSearch: handleSearch,
|
|
250
273
|
onKeyDown,
|
|
251
274
|
orphanClassName,
|
|
252
|
-
orphanStyle
|
|
275
|
+
orphanStyle,
|
|
276
|
+
looseMatch
|
|
253
277
|
})
|
|
254
278
|
)
|
|
255
279
|
);
|
|
@@ -54,6 +54,20 @@
|
|
|
54
54
|
align-items: center;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
// Multi-column grid layout (non-virtual mode).
|
|
58
|
+
// The column count is driven by the CSS custom property --yee-select-columns
|
|
59
|
+
// set inline by the Options component.
|
|
60
|
+
&-popup-grid {
|
|
61
|
+
display: grid;
|
|
62
|
+
grid-template-columns: repeat(var(--yee-select-columns, 1), 1fr);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Virtual multi-column: each row container is already a grid via inline style;
|
|
66
|
+
// options inside it should fill cells evenly and center vertically.
|
|
67
|
+
&-popup-grid-row {
|
|
68
|
+
align-items: center;
|
|
69
|
+
}
|
|
70
|
+
|
|
57
71
|
&-empty {
|
|
58
72
|
padding: var(--yee-select-option-padding);
|
|
59
73
|
}
|