@via-profit/ui-kit 0.2.2 → 0.2.4
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/Autocomplete/AutocompleteContainer.d.ts +5 -4
- package/Autocomplete/AutocompleteContainer.js +128 -142
- package/Autocomplete/AutocompleteItem.js +1 -1
- package/Autocomplete/context.d.ts +3 -5
- package/Autocomplete/context.js +22 -14
- package/Highlighted/index.js +10 -5
- package/Highlighted/splitTextIntoParts.d.ts +5 -0
- package/Highlighted/splitTextIntoParts.js +46 -0
- package/Menu/MenuContainer.d.ts +24 -2
- package/Menu/MenuContainer.js +193 -242
- package/Menu/MenuList.d.ts +1 -0
- package/Menu/MenuList.js +15 -16
- package/Popper/Popper.d.ts +3 -2
- package/Popper/Popper.js +9 -7
- package/Popper/PopperContainer.d.ts +0 -1
- package/Popper/PopperContainer.js +8 -3
- package/Popper/index.d.ts +1 -1
- package/Popper/index.js +1 -1
- package/Popper/usePopper.d.ts +111 -0
- package/Popper/usePopper.js +489 -0
- package/Selectbox/SelectboxButton.js +2 -27
- package/Selectbox/SelectboxMenu.d.ts +0 -2
- package/Selectbox/SelectboxMenu.js +13 -5
- package/Selectbox/index.d.ts +5 -3
- package/Selectbox/index.js +4 -4
- package/Swiper/Swiper.d.ts +0 -2
- package/Swiper/Swiper.js +1 -18
- package/package.json +1 -1
package/Menu/MenuContainer.js
CHANGED
|
@@ -32,116 +32,105 @@ const MenuList_1 = __importDefault(require("./MenuList"));
|
|
|
32
32
|
const Popper_1 = __importDefault(require("../Popper"));
|
|
33
33
|
const ClickOutside_1 = __importDefault(require("../ClickOutside"));
|
|
34
34
|
const onRequestCloseDefault = () => undefined;
|
|
35
|
+
const isEqual = (a, b) => {
|
|
36
|
+
if (a === b) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
const keysA = Object.keys(a);
|
|
43
|
+
const keysB = Object.keys(b);
|
|
44
|
+
if (keysA.length !== keysB.length)
|
|
45
|
+
return false;
|
|
46
|
+
return keysA.every(key => Object.prototype.hasOwnProperty.call(b, key) && a[key] === b[key]);
|
|
47
|
+
};
|
|
35
48
|
const MenuContainer = react_1.default.forwardRef((props, ref) => {
|
|
36
|
-
const { items, value, anchorElement = null, overrides, children, closeOutsideClick = true, isOpen = false, anchorPos = 'bottom', positionStrategy, autoFlip, viewportMargin, offset, multiple = false, autofocus = true, closeOnSelect = !multiple, onRequestClose = onRequestCloseDefault, onAnchorPosChanged, zIndex, onSelectItem, getOptionSelected, } = props;
|
|
37
|
-
const [actualPlacement, setActualPlacement] =
|
|
49
|
+
const { items, value, anchorElement = null, overrides, children, closeOutsideClick = true, isOpen = false, anchorPos = 'bottom', alternativePlacements = ['bottom', 'top'], positionStrategy, autoFlip, viewportMargin, offset, multiple = false, autofocus = true, closeOnSelect = !multiple, onRequestClose = onRequestCloseDefault, onAnchorPosChanged, zIndex, onSelectItem, getOptionSelected, maxWidth, } = props;
|
|
50
|
+
// const [actualPlacement, setActualPlacement] = React.useState(anchorPos);
|
|
51
|
+
const [currentAnchorElement, setAnchorElement] = react_1.default.useState(anchorElement);
|
|
38
52
|
const overridesMap = react_1.default.useMemo(() => ({
|
|
39
53
|
List: (overrides === null || overrides === void 0 ? void 0 : overrides.List) || MenuList_1.default,
|
|
40
54
|
Popper: (overrides === null || overrides === void 0 ? void 0 : overrides.Popper) || Popper_1.default,
|
|
41
|
-
}), [overrides]);
|
|
42
|
-
const getSelectedIndexes = react_1.default.useCallback(() => {
|
|
43
|
-
const idx = new Set();
|
|
44
|
-
if (value !== null) {
|
|
45
|
-
if (typeof getOptionSelected === 'function') {
|
|
46
|
-
if (multiple) {
|
|
47
|
-
value.forEach(v => {
|
|
48
|
-
idx.add(items.findIndex(item => getOptionSelected({ item, value: v })));
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
idx.add(items.findIndex(item => getOptionSelected({ item, value: value })));
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
if (multiple) {
|
|
57
|
-
value.forEach(v => {
|
|
58
|
-
idx.add(items.findIndex(item => JSON.stringify(item) === JSON.stringify(v)));
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
idx.add(items.findIndex(item => JSON.stringify(item) === JSON.stringify(value)));
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return [...idx];
|
|
67
|
-
}, [getOptionSelected, items, multiple, value]);
|
|
68
|
-
const [currentAnchorElement, setAnchorElement] = react_1.default.useState(anchorElement);
|
|
69
|
-
const isOpenRef = react_1.default.useRef(isOpen);
|
|
55
|
+
}), [overrides === null || overrides === void 0 ? void 0 : overrides.List, overrides === null || overrides === void 0 ? void 0 : overrides.Popper]);
|
|
70
56
|
const menuListRef = react_1.default.useRef(null);
|
|
71
57
|
const menuPopperRef = react_1.default.useRef(null);
|
|
58
|
+
const isOpenRef = react_1.default.useRef(isOpen);
|
|
59
|
+
const focusTimeoutRef = react_1.default.useRef();
|
|
72
60
|
const { dispatch, state: { selectedIndexes, markedIndex, hoveredIndex }, } = (0, context_1.default)();
|
|
73
61
|
const selectedIndexesRef = react_1.default.useRef(selectedIndexes);
|
|
74
|
-
|
|
62
|
+
// Оптимизированная функция получения выбранных индексов
|
|
63
|
+
const getSelectedIndexes = react_1.default.useCallback(() => {
|
|
64
|
+
if (value === null || (multiple && value.length === 0)) {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
const indexes = [];
|
|
68
|
+
const compareFunc = getOptionSelected
|
|
69
|
+
? (item, val) => getOptionSelected({ item, value: val })
|
|
70
|
+
: (item, val) => isEqual(item, val);
|
|
71
|
+
if (multiple) {
|
|
72
|
+
const valueArray = value;
|
|
73
|
+
items.forEach((item, index) => {
|
|
74
|
+
if (valueArray.some(val => compareFunc(item, val))) {
|
|
75
|
+
indexes.push(index);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
const singleValue = value;
|
|
81
|
+
const index = items.findIndex(item => compareFunc(item, singleValue));
|
|
82
|
+
if (index !== -1) {
|
|
83
|
+
indexes.push(index);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return indexes;
|
|
87
|
+
}, [items, value, multiple, getOptionSelected]);
|
|
75
88
|
const scrollToIndex = react_1.default.useCallback((index) => {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
89
|
+
if (index === -1) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const container = menuListRef.current;
|
|
93
|
+
const option = container === null || container === void 0 ? void 0 : container.children[index];
|
|
94
|
+
if (container && option) {
|
|
95
|
+
const containerRect = container.getBoundingClientRect();
|
|
96
|
+
const optionRect = option.getBoundingClientRect();
|
|
97
|
+
const scrollOffset = optionRect.top - containerRect.top + container.scrollTop;
|
|
98
|
+
container.scrollTo({
|
|
99
|
+
top: scrollOffset,
|
|
100
|
+
behavior: 'instant'
|
|
82
101
|
});
|
|
83
102
|
}
|
|
84
103
|
}, []);
|
|
85
|
-
react_1.default.useEffect(() => {
|
|
86
|
-
let timeout;
|
|
87
|
-
const recalc = () => {
|
|
88
|
-
if (timeout) {
|
|
89
|
-
clearTimeout(timeout);
|
|
90
|
-
}
|
|
91
|
-
timeout = setTimeout(() => {
|
|
92
|
-
if (anchorElement) {
|
|
93
|
-
// calculateElementPos();
|
|
94
|
-
}
|
|
95
|
-
}, 300);
|
|
96
|
-
};
|
|
97
|
-
window.addEventListener('resize', recalc);
|
|
98
|
-
window.addEventListener('scroll', recalc);
|
|
99
|
-
recalc();
|
|
100
|
-
return () => {
|
|
101
|
-
window.removeEventListener('resize', recalc);
|
|
102
|
-
window.removeEventListener('scroll', recalc);
|
|
103
|
-
};
|
|
104
|
-
}, [anchorElement, isOpen]);
|
|
105
|
-
/**
|
|
106
|
-
* Scroll to first of selected item
|
|
107
|
-
*/
|
|
108
104
|
const scrollToFirstSelected = react_1.default.useCallback(() => {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
dispatch({
|
|
113
|
-
|
|
114
|
-
payload: {
|
|
115
|
-
markedIndex: index,
|
|
116
|
-
},
|
|
117
|
-
});
|
|
118
|
-
scrollToIndex(index);
|
|
105
|
+
if (selectedIndexes.length === 0)
|
|
106
|
+
return;
|
|
107
|
+
const firstSelectedIndex = Math.min(...selectedIndexes);
|
|
108
|
+
dispatch((0, context_1.actionSetmenuState)({ markedIndex: firstSelectedIndex }));
|
|
109
|
+
scrollToIndex(firstSelectedIndex);
|
|
119
110
|
}, [dispatch, selectedIndexes, scrollToIndex]);
|
|
120
|
-
/**
|
|
121
|
-
* Select specified item by index
|
|
122
|
-
*/
|
|
123
111
|
const selectItem = react_1.default.useCallback((index) => {
|
|
112
|
+
if (index < 0 || index >= items.length)
|
|
113
|
+
return;
|
|
124
114
|
const item = items[index];
|
|
125
|
-
if (!item)
|
|
115
|
+
if (!item)
|
|
126
116
|
return;
|
|
127
|
-
}
|
|
128
117
|
if (typeof onSelectItem === 'function') {
|
|
129
|
-
// For multiple
|
|
130
118
|
if (multiple) {
|
|
131
|
-
|
|
119
|
+
// Для multiple режима
|
|
120
|
+
const selectedSet = new Set();
|
|
132
121
|
if (value !== null) {
|
|
133
|
-
value.forEach(v =>
|
|
122
|
+
value.forEach(v => selectedSet.add(v));
|
|
134
123
|
}
|
|
135
124
|
if (selectedIndexes.includes(index)) {
|
|
136
|
-
|
|
125
|
+
selectedSet.delete(item);
|
|
137
126
|
}
|
|
138
|
-
|
|
139
|
-
|
|
127
|
+
else {
|
|
128
|
+
selectedSet.add(item);
|
|
140
129
|
}
|
|
141
|
-
onSelectItem(Array.from(
|
|
130
|
+
onSelectItem(Array.from(selectedSet));
|
|
142
131
|
}
|
|
143
132
|
else {
|
|
144
|
-
//
|
|
133
|
+
// Для single режима
|
|
145
134
|
onSelectItem(item);
|
|
146
135
|
}
|
|
147
136
|
if (closeOnSelect) {
|
|
@@ -149,232 +138,194 @@ const MenuContainer = react_1.default.forwardRef((props, ref) => {
|
|
|
149
138
|
}
|
|
150
139
|
}
|
|
151
140
|
}, [onSelectItem, onRequestClose, selectedIndexes, closeOnSelect, items, value, multiple]);
|
|
152
|
-
/**
|
|
153
|
-
* Highlight specified item by index
|
|
154
|
-
*/
|
|
155
141
|
const highlightIndex = react_1.default.useCallback((index) => {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
highlightIndex(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}, [highlightIndex, scrollToIndex, items.length]);
|
|
179
|
-
const selectHightlightedItem = react_1.default.useCallback(() => {
|
|
180
|
-
if (markedIndex > -1) {
|
|
142
|
+
const validIndex = Math.max(-1, Math.min(index, items.length - 1));
|
|
143
|
+
if (validIndex !== markedIndex) {
|
|
144
|
+
dispatch((0, context_1.actionSetmenuState)({ markedIndex: validIndex }));
|
|
145
|
+
scrollToIndex(validIndex);
|
|
146
|
+
}
|
|
147
|
+
}, [dispatch, items.length, markedIndex, scrollToIndex]);
|
|
148
|
+
const highlightPrevItem = react_1.default.useCallback(() => {
|
|
149
|
+
const newIndex = markedIndex - 1;
|
|
150
|
+
highlightIndex(newIndex >= 0 ? newIndex : 0);
|
|
151
|
+
}, [markedIndex, highlightIndex]);
|
|
152
|
+
const highlightNextItem = react_1.default.useCallback(() => {
|
|
153
|
+
const newIndex = markedIndex + 1;
|
|
154
|
+
highlightIndex(newIndex < items.length ? newIndex : items.length - 1);
|
|
155
|
+
}, [markedIndex, items.length, highlightIndex]);
|
|
156
|
+
const highlightFirstItem = react_1.default.useCallback(() => {
|
|
157
|
+
highlightIndex(0);
|
|
158
|
+
}, [highlightIndex]);
|
|
159
|
+
const highlightLastItem = react_1.default.useCallback(() => {
|
|
160
|
+
highlightIndex(items.length - 1);
|
|
161
|
+
}, [highlightIndex, items.length]);
|
|
162
|
+
const selectHighlightedItem = react_1.default.useCallback(() => {
|
|
163
|
+
if (markedIndex > -1 && markedIndex < items.length) {
|
|
181
164
|
selectItem(markedIndex);
|
|
182
165
|
}
|
|
183
|
-
}, [markedIndex, selectItem]);
|
|
184
|
-
|
|
185
|
-
* API
|
|
186
|
-
*/
|
|
166
|
+
}, [markedIndex, selectItem, items.length]);
|
|
167
|
+
// API ref
|
|
187
168
|
react_1.default.useImperativeHandle(ref, () => ({
|
|
188
169
|
focus: () => { var _a; return (_a = menuListRef.current) === null || _a === void 0 ? void 0 : _a.focus(); },
|
|
189
170
|
scrollToIndex: (idx) => scrollToIndex(idx),
|
|
190
171
|
highlightIndex: (idx) => highlightIndex(idx),
|
|
191
|
-
highlightPrevItem: () =>
|
|
192
|
-
highlightNextItem: () =>
|
|
193
|
-
highlightFirstItem: () =>
|
|
194
|
-
highlightLastItem: () =>
|
|
172
|
+
highlightPrevItem: () => highlightPrevItem(),
|
|
173
|
+
highlightNextItem: () => highlightNextItem(),
|
|
174
|
+
highlightFirstItem: () => highlightFirstItem(),
|
|
175
|
+
highlightLastItem: () => highlightLastItem(),
|
|
195
176
|
scrollToFirstSelected: () => scrollToFirstSelected(),
|
|
196
|
-
selectHighlightedItem: () =>
|
|
177
|
+
selectHighlightedItem: () => selectHighlightedItem(),
|
|
197
178
|
selectItem: (idx) => selectItem(idx),
|
|
198
179
|
getListElement: () => menuListRef.current,
|
|
199
|
-
setActualPlacement: placement => setActualPlacement(placement),
|
|
180
|
+
// setActualPlacement: placement => setActualPlacement(placement),
|
|
200
181
|
}), [
|
|
201
182
|
scrollToIndex,
|
|
202
183
|
highlightIndex,
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
184
|
+
highlightPrevItem,
|
|
185
|
+
highlightNextItem,
|
|
186
|
+
highlightFirstItem,
|
|
187
|
+
highlightLastItem,
|
|
188
|
+
selectHighlightedItem,
|
|
208
189
|
scrollToFirstSelected,
|
|
209
190
|
selectItem,
|
|
210
191
|
]);
|
|
211
|
-
|
|
212
|
-
* Update achnor
|
|
213
|
-
*/
|
|
192
|
+
// Обновление anchor элемента
|
|
214
193
|
react_1.default.useEffect(() => {
|
|
215
194
|
if (anchorElement !== currentAnchorElement) {
|
|
216
195
|
setAnchorElement(anchorElement);
|
|
217
196
|
}
|
|
218
197
|
}, [anchorElement, currentAnchorElement]);
|
|
219
|
-
|
|
220
|
-
* Keyboard events of menu list container.
|
|
221
|
-
* Note: Moved selection up and down while keys pressed
|
|
222
|
-
*/
|
|
198
|
+
// Обработчик клавиатуры
|
|
223
199
|
const listKeydownEvent = react_1.default.useCallback((event) => {
|
|
224
200
|
switch (event.code) {
|
|
225
201
|
case 'Enter':
|
|
226
202
|
case 'NumpadEnter':
|
|
227
|
-
case 'Tab':
|
|
228
203
|
event.preventDefault();
|
|
229
|
-
if (markedIndex > -1) {
|
|
204
|
+
if (markedIndex > -1 && markedIndex < items.length) {
|
|
230
205
|
selectItem(markedIndex);
|
|
231
206
|
}
|
|
232
207
|
break;
|
|
233
208
|
case 'ArrowUp':
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
hightlightPrevItem();
|
|
237
|
-
}
|
|
209
|
+
event.preventDefault();
|
|
210
|
+
highlightPrevItem();
|
|
238
211
|
break;
|
|
239
212
|
case 'ArrowDown':
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
hightlightNextItem();
|
|
243
|
-
}
|
|
213
|
+
event.preventDefault();
|
|
214
|
+
highlightNextItem();
|
|
244
215
|
break;
|
|
245
216
|
case 'Home':
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
hightlightFirstItem();
|
|
249
|
-
}
|
|
217
|
+
event.preventDefault();
|
|
218
|
+
highlightFirstItem();
|
|
250
219
|
break;
|
|
251
220
|
case 'End':
|
|
252
|
-
{
|
|
253
|
-
event.preventDefault();
|
|
254
|
-
hightlightLastItem();
|
|
255
|
-
}
|
|
256
|
-
break;
|
|
257
|
-
case 'PageUp':
|
|
258
|
-
case 'PageDown':
|
|
259
|
-
case 'Space':
|
|
260
221
|
event.preventDefault();
|
|
222
|
+
highlightLastItem();
|
|
261
223
|
break;
|
|
262
224
|
case 'Escape':
|
|
263
225
|
event.preventDefault();
|
|
264
226
|
onRequestClose(event);
|
|
265
227
|
break;
|
|
266
228
|
default:
|
|
267
|
-
// do nothing
|
|
268
229
|
break;
|
|
269
230
|
}
|
|
270
231
|
}, [
|
|
271
232
|
markedIndex,
|
|
272
|
-
|
|
233
|
+
items.length,
|
|
273
234
|
selectItem,
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
235
|
+
highlightPrevItem,
|
|
236
|
+
highlightNextItem,
|
|
237
|
+
highlightFirstItem,
|
|
238
|
+
highlightLastItem,
|
|
239
|
+
onRequestClose,
|
|
278
240
|
]);
|
|
241
|
+
// Управление открытием/закрытием меню
|
|
279
242
|
react_1.default.useEffect(() => {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
return () => {
|
|
295
|
-
window.document.removeEventListener('resize', windowResizeEvent);
|
|
296
|
-
};
|
|
297
|
-
}, [isOpen, onRequestClose, anchorElement, closeOutsideClick]);
|
|
298
|
-
/**
|
|
299
|
-
* Toggle menu open
|
|
300
|
-
*/
|
|
301
|
-
react_1.default.useEffect(() => {
|
|
302
|
-
if (isOpenRef.current !== isOpen) {
|
|
303
|
-
isOpenRef.current = Boolean(isOpen);
|
|
304
|
-
// setMenuOpen(Boolean(isOpen));
|
|
305
|
-
if (!isOpen) {
|
|
306
|
-
onRequestClose();
|
|
307
|
-
}
|
|
308
|
-
if (isOpen) {
|
|
309
|
-
scrollToFirstSelected();
|
|
310
|
-
if (autofocus) {
|
|
311
|
-
setTimeout(() => {
|
|
312
|
-
var _a;
|
|
313
|
-
(_a = menuListRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
314
|
-
}, 15);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
// Reset indexes
|
|
318
|
-
if (!isOpen) {
|
|
319
|
-
// listVirtRef.current?.scrollToIndex(0);
|
|
320
|
-
dispatch((0, context_1.actionSetmenuState)({ markedIndex: -1, hoveredIndex: -1 }));
|
|
243
|
+
if (isOpenRef.current === isOpen)
|
|
244
|
+
return;
|
|
245
|
+
isOpenRef.current = isOpen;
|
|
246
|
+
if (!isOpen) {
|
|
247
|
+
onRequestClose();
|
|
248
|
+
dispatch((0, context_1.actionSetmenuState)({ markedIndex: -1, hoveredIndex: -1 }));
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
// При открытии
|
|
252
|
+
scrollToFirstSelected();
|
|
253
|
+
if (autofocus) {
|
|
254
|
+
// Очищаем предыдущий таймаут
|
|
255
|
+
if (focusTimeoutRef.current) {
|
|
256
|
+
clearTimeout(focusTimeoutRef.current);
|
|
321
257
|
}
|
|
258
|
+
focusTimeoutRef.current = setTimeout(() => {
|
|
259
|
+
var _a;
|
|
260
|
+
(_a = menuListRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
261
|
+
}, 15);
|
|
322
262
|
}
|
|
323
|
-
}, [
|
|
324
|
-
|
|
325
|
-
* Mark selected items by values
|
|
326
|
-
*/
|
|
263
|
+
}, [isOpen, autofocus, dispatch, onRequestClose, scrollToFirstSelected]);
|
|
264
|
+
// Обновление выбранных индексов
|
|
327
265
|
react_1.default.useEffect(() => {
|
|
328
266
|
const indexes = getSelectedIndexes();
|
|
329
|
-
//
|
|
330
|
-
|
|
331
|
-
selectedIndexesRef.current.
|
|
267
|
+
// Сравниваем массивы эффективно
|
|
268
|
+
const hasChanged = selectedIndexesRef.current.length !== indexes.length ||
|
|
269
|
+
selectedIndexesRef.current.some((value, idx) => value !== indexes[idx]);
|
|
270
|
+
if (hasChanged) {
|
|
332
271
|
selectedIndexesRef.current = indexes;
|
|
333
272
|
dispatch({
|
|
334
273
|
type: 'setMenuState',
|
|
335
|
-
payload: {
|
|
336
|
-
selectedIndexes: indexes,
|
|
337
|
-
},
|
|
274
|
+
payload: { selectedIndexes: indexes },
|
|
338
275
|
});
|
|
339
276
|
}
|
|
340
|
-
}, [
|
|
341
|
-
const itemClickHandler = react_1.default.useCallback((index) => () =>
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
const itemMouseLeaveHandler = react_1.default.useCallback((index, hoveredIndex) => () => {
|
|
345
|
-
if (index === hoveredIndex) {
|
|
277
|
+
}, [getSelectedIndexes, dispatch]);
|
|
278
|
+
const itemClickHandler = react_1.default.useCallback((index) => () => selectItem(index), [selectItem]);
|
|
279
|
+
const itemMouseEnterHandler = react_1.default.useCallback((index) => () => {
|
|
280
|
+
if (index !== hoveredIndex) {
|
|
346
281
|
dispatch((0, context_1.actionSetmenuState)({
|
|
347
|
-
hoveredIndex:
|
|
282
|
+
hoveredIndex: index,
|
|
348
283
|
markedIndex: -1,
|
|
349
284
|
}));
|
|
350
285
|
}
|
|
351
|
-
}, [dispatch]);
|
|
352
|
-
const
|
|
353
|
-
if (index
|
|
286
|
+
}, [dispatch, hoveredIndex]);
|
|
287
|
+
const itemMouseLeaveHandler = react_1.default.useCallback((index) => () => {
|
|
288
|
+
if (index === hoveredIndex) {
|
|
354
289
|
dispatch((0, context_1.actionSetmenuState)({
|
|
355
|
-
hoveredIndex:
|
|
290
|
+
hoveredIndex: -1,
|
|
356
291
|
markedIndex: -1,
|
|
357
292
|
}));
|
|
358
293
|
}
|
|
359
|
-
}, [dispatch]);
|
|
294
|
+
}, [dispatch, hoveredIndex]);
|
|
295
|
+
const onAnchorPosChangedMemo = react_1.default.useCallback((newPlacement) => {
|
|
296
|
+
if (typeof onAnchorPosChanged === 'function') {
|
|
297
|
+
onAnchorPosChanged(newPlacement);
|
|
298
|
+
}
|
|
299
|
+
}, [onAnchorPosChanged]);
|
|
300
|
+
const renderedChildren = react_1.default.useMemo(() => items.map((item, index) => children({ item, index }, {
|
|
301
|
+
key: index,
|
|
302
|
+
onMouseEnter: itemMouseEnterHandler(index),
|
|
303
|
+
onMouseLeave: itemMouseLeaveHandler(index),
|
|
304
|
+
onClick: itemClickHandler(index),
|
|
305
|
+
selected: selectedIndexes.includes(index),
|
|
306
|
+
hovered: hoveredIndex === index || markedIndex === index,
|
|
307
|
+
})), [
|
|
308
|
+
items,
|
|
309
|
+
children,
|
|
310
|
+
selectedIndexes,
|
|
311
|
+
hoveredIndex,
|
|
312
|
+
markedIndex,
|
|
313
|
+
itemMouseEnterHandler,
|
|
314
|
+
itemMouseLeaveHandler,
|
|
315
|
+
itemClickHandler,
|
|
316
|
+
]);
|
|
317
|
+
// Cleanup on unmount
|
|
318
|
+
react_1.default.useEffect(() => () => {
|
|
319
|
+
if (focusTimeoutRef.current) {
|
|
320
|
+
clearTimeout(focusTimeoutRef.current);
|
|
321
|
+
}
|
|
322
|
+
}, []);
|
|
323
|
+
// Мемоизация компонентов
|
|
324
|
+
const PopperComponent = overridesMap.Popper;
|
|
325
|
+
const ListComponent = overridesMap.List;
|
|
360
326
|
return (react_1.default.createElement(ClickOutside_1.default, { onOutsideClick: onRequestClose, mouseEvent: isOpen && closeOutsideClick ? 'onMouseDown' : false },
|
|
361
|
-
react_1.default.createElement(
|
|
362
|
-
|
|
363
|
-
if (typeof onAnchorPosChanged === 'function') {
|
|
364
|
-
onAnchorPosChanged(newPlacement);
|
|
365
|
-
}
|
|
366
|
-
}, positionStrategy: positionStrategy, autoFlip: autoFlip, viewportMargin: viewportMargin, offset: offset },
|
|
367
|
-
react_1.default.createElement(overridesMap.List, { anchorPos: actualPlacement, isOpen: Boolean(isOpen), ref: menuListRef, onKeyDown: listKeydownEvent }, items.map((item, index) => children({
|
|
368
|
-
item,
|
|
369
|
-
index,
|
|
370
|
-
}, {
|
|
371
|
-
key: index,
|
|
372
|
-
onMouseEnter: itemMouseEnterHandler(index, hoveredIndex),
|
|
373
|
-
onMouseLeave: itemMouseLeaveHandler(index, hoveredIndex),
|
|
374
|
-
onClick: itemClickHandler(index),
|
|
375
|
-
selected: selectedIndexes.includes(index),
|
|
376
|
-
hovered: hoveredIndex === index || markedIndex === index,
|
|
377
|
-
}))))));
|
|
327
|
+
react_1.default.createElement(PopperComponent, { isOpen: Boolean(isOpen), ref: menuPopperRef, zIndex: zIndex, anchorPos: anchorPos, alternativePlacements: alternativePlacements, anchorElement: anchorElement, onAnchorPosChanged: onAnchorPosChangedMemo, positionStrategy: positionStrategy, autoFlip: autoFlip, viewportMargin: viewportMargin, offset: offset },
|
|
328
|
+
react_1.default.createElement(ListComponent, { anchorPos: anchorPos, isOpen: Boolean(isOpen), ref: menuListRef, maxWidth: maxWidth, onKeyDown: listKeydownEvent }, renderedChildren))));
|
|
378
329
|
});
|
|
379
330
|
MenuContainer.displayName = 'MenuContainer';
|
|
380
331
|
exports.default = MenuContainer;
|
package/Menu/MenuList.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { AnchorPos } from '../Popper';
|
|
|
3
3
|
export interface MenuListProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
4
4
|
readonly isOpen: boolean;
|
|
5
5
|
readonly anchorPos: AnchorPos;
|
|
6
|
+
readonly maxWidth?: number | string;
|
|
6
7
|
}
|
|
7
8
|
export declare const MenuList: React.ForwardRefExoticComponent<MenuListProps & React.RefAttributes<HTMLDivElement>>;
|
|
8
9
|
export default MenuList;
|
package/Menu/MenuList.js
CHANGED
|
@@ -27,6 +27,19 @@ const StyledMenuList = styled_1.default.div `
|
|
|
27
27
|
border-radius: ${({ theme }) => theme.shape.radiusFactor * 2}em;
|
|
28
28
|
box-shadow: 0 0.5em 1.5em ${({ theme }) => theme.color.surface.darken(50).alpha(0.6).toString()};
|
|
29
29
|
|
|
30
|
+
${({ $maxWidth }) => {
|
|
31
|
+
if (typeof $maxWidth === 'number') {
|
|
32
|
+
return (0, react_2.css) `
|
|
33
|
+
max-width: ${$maxWidth}px;
|
|
34
|
+
`;
|
|
35
|
+
}
|
|
36
|
+
if (typeof $maxWidth === 'string') {
|
|
37
|
+
return (0, react_2.css) `
|
|
38
|
+
max-width: ${$maxWidth};
|
|
39
|
+
`;
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}};
|
|
30
43
|
&:focus {
|
|
31
44
|
outline-style: solid;
|
|
32
45
|
outline-width: 0.14em;
|
|
@@ -64,24 +77,10 @@ const StyledMenuList = styled_1.default.div `
|
|
|
64
77
|
overflow-y: auto;
|
|
65
78
|
padding: 0.4em;
|
|
66
79
|
max-height: 18em;
|
|
67
|
-
min-width: 10em;
|
|
68
|
-
|
|
69
|
-
max-width: 100%;
|
|
70
|
-
|
|
71
|
-
${({ $fixedWidth }) => $fixedWidth &&
|
|
72
|
-
(0, react_2.css) `
|
|
73
|
-
min-width: 10em;
|
|
74
|
-
max-width: 16em;
|
|
75
|
-
`}
|
|
76
|
-
|
|
77
|
-
${({ $anchorPos, theme }) => $anchorPos === 'top-fill' &&
|
|
78
|
-
(0, react_2.css) `
|
|
79
|
-
box-shadow: 0 -0.5em 1.5em ${theme.color.surface.darken(50).alpha(0.6).toString()};
|
|
80
|
-
`};
|
|
81
80
|
`;
|
|
82
81
|
exports.MenuList = react_1.default.forwardRef((props, ref) => {
|
|
83
|
-
const { isOpen, anchorPos, children } = props, nativeProps = __rest(props, ["isOpen", "anchorPos", "children"]);
|
|
84
|
-
return (react_1.default.createElement(StyledMenuList, Object.assign({ tabIndex: -1, "$isOpen": isOpen, "$
|
|
82
|
+
const { isOpen, anchorPos, maxWidth, children } = props, nativeProps = __rest(props, ["isOpen", "anchorPos", "maxWidth", "children"]);
|
|
83
|
+
return (react_1.default.createElement(StyledMenuList, Object.assign({ tabIndex: -1, "$isOpen": isOpen, "$maxWidth": maxWidth, "$anchorPos": anchorPos }, nativeProps, { ref: ref }), children));
|
|
85
84
|
});
|
|
86
85
|
exports.MenuList.displayName = 'MenuList';
|
|
87
86
|
exports.default = exports.MenuList;
|
package/Popper/Popper.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { PopperContainerProps, PositionStrategy } from './PopperContainer';
|
|
3
|
-
import { AnchorPos } from './
|
|
4
|
-
export * from './
|
|
3
|
+
import { AnchorPos } from './usePopper';
|
|
4
|
+
export * from './usePopper';
|
|
5
5
|
export interface PopperProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
6
6
|
/**
|
|
7
7
|
* Controls the visibility of the popper.
|
|
@@ -38,6 +38,7 @@ export interface PopperProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
38
38
|
* ```
|
|
39
39
|
*/
|
|
40
40
|
readonly anchorPos?: AnchorPos;
|
|
41
|
+
readonly alternativePlacements?: readonly AnchorPos[];
|
|
41
42
|
readonly onAnchorPosChanged?: (anchorPos: AnchorPos) => void;
|
|
42
43
|
/**
|
|
43
44
|
* The z-index value for the popper container.
|