@via-profit/ui-kit 0.2.2 → 0.2.3
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 +522 -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
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.usePopper = exports.findScrollableAncestor = void 0;
|
|
7
|
+
const react_1 = __importDefault(require("react"));
|
|
8
|
+
/**
|
|
9
|
+
* Finds the closest scrollable ancestor of an element.
|
|
10
|
+
* A scrollable element is one that has overflow set to auto, scroll, or overlay
|
|
11
|
+
* and has content that overflows, allowing scrolling.
|
|
12
|
+
*
|
|
13
|
+
* @param element - The element to start searching from
|
|
14
|
+
* @param includeHidden - Whether to consider elements with overflow: hidden
|
|
15
|
+
* @returns The closest scrollable ancestor or window if none found
|
|
16
|
+
*/
|
|
17
|
+
const findScrollableAncestor = (element, includeHidden = false) => {
|
|
18
|
+
if (!element) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
// Helper to check if an element is scrollable
|
|
22
|
+
const isScrollable = (el) => {
|
|
23
|
+
const style = window.getComputedStyle(el);
|
|
24
|
+
const overflow = style.overflow + style.overflowY + style.overflowX;
|
|
25
|
+
// Check if element has overflow that enables scrolling
|
|
26
|
+
const hasOverflow = overflow.includes('auto') ||
|
|
27
|
+
overflow.includes('scroll') ||
|
|
28
|
+
(includeHidden && overflow.includes('hidden'));
|
|
29
|
+
if (!hasOverflow) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
// Check if element actually has scrollable content
|
|
33
|
+
return el.scrollHeight > el.clientHeight || el.scrollWidth > el.clientWidth;
|
|
34
|
+
};
|
|
35
|
+
// Start from parent (skip the element itself)
|
|
36
|
+
let parent = element.parentElement;
|
|
37
|
+
while (parent) {
|
|
38
|
+
if (isScrollable(parent)) {
|
|
39
|
+
return parent;
|
|
40
|
+
}
|
|
41
|
+
parent = parent.parentElement;
|
|
42
|
+
}
|
|
43
|
+
// If no scrollable ancestor found, return window (which is scrollable)
|
|
44
|
+
return window;
|
|
45
|
+
};
|
|
46
|
+
exports.findScrollableAncestor = findScrollableAncestor;
|
|
47
|
+
const usePopper = (props) => {
|
|
48
|
+
const { anchorElement, anchorPos = 'auto', positionStrategy = 'fixed', autoFlip = false, offset = 0, viewportMargin = 10, isOpen = false, alternativePlacements, } = props;
|
|
49
|
+
const isMountedRef = react_1.default.useRef(false);
|
|
50
|
+
const isPositioningRef = react_1.default.useRef(false);
|
|
51
|
+
const [actualPlacement, setActualPlacement] = react_1.default.useState(anchorPos);
|
|
52
|
+
const [style, setStyle] = react_1.default.useState(null);
|
|
53
|
+
const [isVisible, setIsVisible] = react_1.default.useState(false);
|
|
54
|
+
const popperRef = react_1.default.useRef(null);
|
|
55
|
+
// Finding the nearest scrollable ancestor
|
|
56
|
+
const scrollableAncestor = react_1.default.useMemo(() => {
|
|
57
|
+
if (!anchorElement || typeof window === 'undefined') {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
return (0, exports.findScrollableAncestor)(anchorElement);
|
|
61
|
+
}, [anchorElement]);
|
|
62
|
+
// Synchronizing actualPlacement with anchorPos
|
|
63
|
+
react_1.default.useEffect(() => {
|
|
64
|
+
if (actualPlacement !== anchorPos) {
|
|
65
|
+
setActualPlacement(anchorPos);
|
|
66
|
+
}
|
|
67
|
+
}, [actualPlacement, anchorPos]);
|
|
68
|
+
const getPositionStyle = react_1.default.useCallback((props) => {
|
|
69
|
+
var _a;
|
|
70
|
+
const { place, anchorRect, popperRect, forViewportCheck = false } = props;
|
|
71
|
+
const [direction, modifier = ''] = place.split('-');
|
|
72
|
+
let left = anchorRect.left;
|
|
73
|
+
let top = anchorRect.top;
|
|
74
|
+
let width = undefined;
|
|
75
|
+
// Calculating the position relative to the viewport
|
|
76
|
+
switch (direction) {
|
|
77
|
+
case 'top':
|
|
78
|
+
top = anchorRect.top - popperRect.height - offset;
|
|
79
|
+
if (modifier === 'fill') {
|
|
80
|
+
left = anchorRect.left;
|
|
81
|
+
width = anchorRect.width;
|
|
82
|
+
}
|
|
83
|
+
else if (modifier === 'left' || modifier === 'start') {
|
|
84
|
+
left = anchorRect.left;
|
|
85
|
+
}
|
|
86
|
+
else if (modifier === 'right' || modifier === 'end') {
|
|
87
|
+
left = anchorRect.right - popperRect.width;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
left = anchorRect.left + (anchorRect.width - popperRect.width) / 2;
|
|
91
|
+
}
|
|
92
|
+
break;
|
|
93
|
+
case 'bottom':
|
|
94
|
+
top = anchorRect.bottom + offset;
|
|
95
|
+
if (modifier === 'fill') {
|
|
96
|
+
left = anchorRect.left;
|
|
97
|
+
width = anchorRect.width;
|
|
98
|
+
}
|
|
99
|
+
else if (modifier === 'left' || modifier === 'start') {
|
|
100
|
+
left = anchorRect.left;
|
|
101
|
+
}
|
|
102
|
+
else if (modifier === 'right' || modifier === 'end') {
|
|
103
|
+
left = anchorRect.right - popperRect.width;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
left = anchorRect.left + (anchorRect.width - popperRect.width) / 2;
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
case 'left':
|
|
110
|
+
left = anchorRect.left - popperRect.width - offset;
|
|
111
|
+
if (modifier === 'top' || modifier === 'start') {
|
|
112
|
+
top = anchorRect.top;
|
|
113
|
+
}
|
|
114
|
+
else if (modifier === 'bottom' || modifier === 'end') {
|
|
115
|
+
top = anchorRect.bottom - popperRect.height;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
top = anchorRect.top + (anchorRect.height - popperRect.height) / 2;
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
case 'right':
|
|
122
|
+
left = anchorRect.right + offset;
|
|
123
|
+
if (modifier === 'top' || modifier === 'start') {
|
|
124
|
+
top = anchorRect.top;
|
|
125
|
+
}
|
|
126
|
+
else if (modifier === 'bottom' || modifier === 'end') {
|
|
127
|
+
top = anchorRect.bottom - popperRect.height;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
top = anchorRect.top + (anchorRect.height - popperRect.height) / 2;
|
|
131
|
+
}
|
|
132
|
+
break;
|
|
133
|
+
default:
|
|
134
|
+
left = anchorRect.left + (anchorRect.width - popperRect.width) / 2;
|
|
135
|
+
top = anchorRect.bottom + offset;
|
|
136
|
+
}
|
|
137
|
+
// Returning the viewport coordinates for verification or the final coordinates
|
|
138
|
+
if (forViewportCheck) {
|
|
139
|
+
return { left, top, width };
|
|
140
|
+
}
|
|
141
|
+
// For the final positioning, we take into account the strategy
|
|
142
|
+
if (positionStrategy === 'absolute') {
|
|
143
|
+
// Для absolute позиционирования нужно учитывать:
|
|
144
|
+
// 1. Прокрутку страницы (scrollX/scrollY)
|
|
145
|
+
// 2. Позицию относительно ближайшего positioned ancestor
|
|
146
|
+
// Получаем offset родителя с позиционированием
|
|
147
|
+
let offsetParent = ((_a = popperRef.current) === null || _a === void 0 ? void 0 : _a.offsetParent) || null;
|
|
148
|
+
// Если нет offsetParent или это body, используем document.documentElement
|
|
149
|
+
if (!offsetParent || offsetParent === document.body) {
|
|
150
|
+
offsetParent = document.documentElement;
|
|
151
|
+
}
|
|
152
|
+
const offsetParentRect = offsetParent.getBoundingClientRect();
|
|
153
|
+
// Вычисляем позицию относительно offsetParent
|
|
154
|
+
// left/top уже относительно viewport, вычитаем позицию offsetParent
|
|
155
|
+
let relativeLeft = left - offsetParentRect.left;
|
|
156
|
+
let relativeTop = top - offsetParentRect.top;
|
|
157
|
+
// Добавляем прокрутку offsetParent (если он скроллится)
|
|
158
|
+
if (offsetParent !== document.documentElement && offsetParent !== document.body) {
|
|
159
|
+
relativeLeft += offsetParent.scrollLeft || 0;
|
|
160
|
+
relativeTop += offsetParent.scrollTop || 0;
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
// Для document.documentElement используем window.scroll
|
|
164
|
+
relativeLeft += window.scrollX;
|
|
165
|
+
relativeTop += window.scrollY;
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
left: relativeLeft,
|
|
169
|
+
top: relativeTop,
|
|
170
|
+
width,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return { left, top, width };
|
|
174
|
+
}, [offset, positionStrategy]);
|
|
175
|
+
const clampPopperToContainer = react_1.default.useCallback((style, popperRect) => {
|
|
176
|
+
// Если нет scrollableAncestor, используем document.documentElement как fallback
|
|
177
|
+
const container = scrollableAncestor instanceof HTMLElement ? scrollableAncestor : document.documentElement;
|
|
178
|
+
const containerRect = container.getBoundingClientRect();
|
|
179
|
+
// Базовые границы контейнера с учетом отступа
|
|
180
|
+
let minLeft = containerRect.left + viewportMargin;
|
|
181
|
+
let maxLeft = containerRect.right - popperRect.width - viewportMargin;
|
|
182
|
+
let minTop = containerRect.top + viewportMargin;
|
|
183
|
+
let maxTop = containerRect.bottom - popperRect.height - viewportMargin;
|
|
184
|
+
// Для fixed позиционирования добавляем ограничения viewport
|
|
185
|
+
if (positionStrategy === 'fixed') {
|
|
186
|
+
// Границы viewport с учетом отступа
|
|
187
|
+
const viewportMinLeft = viewportMargin;
|
|
188
|
+
const viewportMaxLeft = window.innerWidth - popperRect.width - viewportMargin;
|
|
189
|
+
const viewportMinTop = viewportMargin;
|
|
190
|
+
const viewportMaxTop = window.innerHeight - popperRect.height - viewportMargin;
|
|
191
|
+
// Берем пересечение границ контейнера и viewport
|
|
192
|
+
minLeft = Math.max(minLeft, viewportMinLeft);
|
|
193
|
+
maxLeft = Math.min(maxLeft, viewportMaxLeft);
|
|
194
|
+
minTop = Math.max(minTop, viewportMinTop);
|
|
195
|
+
maxTop = Math.min(maxTop, viewportMaxTop);
|
|
196
|
+
}
|
|
197
|
+
// Убеждаемся, что границы валидны (min не больше max)
|
|
198
|
+
const validMinLeft = Math.min(minLeft, maxLeft);
|
|
199
|
+
const validMaxLeft = Math.max(minLeft, maxLeft);
|
|
200
|
+
const validMinTop = Math.min(minTop, maxTop);
|
|
201
|
+
const validMaxTop = Math.max(minTop, maxTop);
|
|
202
|
+
// Ограничиваем позицию
|
|
203
|
+
const clampedLeft = Math.max(validMinLeft, Math.min(style.left, validMaxLeft));
|
|
204
|
+
const clampedTop = Math.max(validMinTop, Math.min(style.top, validMaxTop));
|
|
205
|
+
return {
|
|
206
|
+
left: clampedLeft,
|
|
207
|
+
top: clampedTop,
|
|
208
|
+
width: style.width,
|
|
209
|
+
};
|
|
210
|
+
}, [positionStrategy, scrollableAncestor, viewportMargin]);
|
|
211
|
+
const checkIfViewportFits = react_1.default.useCallback((style, popperRect) => {
|
|
212
|
+
const left = style.left;
|
|
213
|
+
const top = style.top;
|
|
214
|
+
const right = left + popperRect.width;
|
|
215
|
+
const bottom = top + popperRect.height;
|
|
216
|
+
if (positionStrategy === 'absolute' && scrollableAncestor instanceof HTMLElement) {
|
|
217
|
+
const containerRect = scrollableAncestor.getBoundingClientRect();
|
|
218
|
+
const relativeLeft = left - containerRect.left;
|
|
219
|
+
const relativeTop = top - containerRect.top;
|
|
220
|
+
const relativeRight = relativeLeft + popperRect.width;
|
|
221
|
+
const relativeBottom = relativeTop + popperRect.height;
|
|
222
|
+
const isWithinHorizontal = relativeLeft >= viewportMargin && relativeRight <= containerRect.width - viewportMargin;
|
|
223
|
+
const isWithinVertical = relativeTop >= viewportMargin && relativeBottom <= containerRect.height - viewportMargin;
|
|
224
|
+
return isWithinHorizontal && isWithinVertical;
|
|
225
|
+
}
|
|
226
|
+
const viewportWidth = window.innerWidth;
|
|
227
|
+
const viewportHeight = window.innerHeight;
|
|
228
|
+
const isWithinHorizontal = left >= viewportMargin && right <= viewportWidth - viewportMargin;
|
|
229
|
+
const isWithinVertical = top >= viewportMargin && bottom <= viewportHeight - viewportMargin;
|
|
230
|
+
const hasValidPosition = left >= 0 && top >= 0;
|
|
231
|
+
return hasValidPosition && isWithinHorizontal && isWithinVertical;
|
|
232
|
+
}, [viewportMargin, scrollableAncestor, positionStrategy]);
|
|
233
|
+
const placementsOrder = react_1.default.useMemo(() => {
|
|
234
|
+
const basePlacements = {
|
|
235
|
+
top: ['top', 'top-left', 'top-right', 'top-start', 'top-end'],
|
|
236
|
+
bottom: ['bottom', 'bottom-left', 'bottom-right', 'bottom-start', 'bottom-end'],
|
|
237
|
+
left: ['left', 'left-top', 'left-bottom'],
|
|
238
|
+
right: ['right', 'right-top', 'right-bottom'],
|
|
239
|
+
};
|
|
240
|
+
const allPlacements = [
|
|
241
|
+
...basePlacements.top,
|
|
242
|
+
...basePlacements.bottom,
|
|
243
|
+
...basePlacements.left,
|
|
244
|
+
...basePlacements.right,
|
|
245
|
+
];
|
|
246
|
+
const order = {};
|
|
247
|
+
allPlacements.forEach(placement => {
|
|
248
|
+
const [direction] = placement.split('-');
|
|
249
|
+
// Priority: same direction > opposite > the rest
|
|
250
|
+
const opposite = direction === 'top'
|
|
251
|
+
? 'bottom'
|
|
252
|
+
: direction === 'bottom'
|
|
253
|
+
? 'top'
|
|
254
|
+
: direction === 'left'
|
|
255
|
+
? 'right'
|
|
256
|
+
: 'left';
|
|
257
|
+
order[placement] = [
|
|
258
|
+
placement,
|
|
259
|
+
...basePlacements[direction].filter(p => p !== placement),
|
|
260
|
+
...(basePlacements[opposite] || []),
|
|
261
|
+
...(direction !== 'left' && direction !== 'right' ? basePlacements.left : []),
|
|
262
|
+
...(direction !== 'left' && direction !== 'right' ? basePlacements.right : []),
|
|
263
|
+
];
|
|
264
|
+
});
|
|
265
|
+
// console.log(order);
|
|
266
|
+
return order;
|
|
267
|
+
}, []);
|
|
268
|
+
// #region Get Preferred Placements
|
|
269
|
+
const getPreferredPlacements = react_1.default.useCallback((preferredPlacement, anchorRect, popperRect) => {
|
|
270
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
271
|
+
const tryPlacement = (placement) => {
|
|
272
|
+
const viewportStyle = getPositionStyle({
|
|
273
|
+
place: placement,
|
|
274
|
+
anchorRect,
|
|
275
|
+
popperRect,
|
|
276
|
+
forViewportCheck: true,
|
|
277
|
+
});
|
|
278
|
+
if (!checkIfViewportFits(viewportStyle, popperRect)) {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
const finalStyle = getPositionStyle({
|
|
282
|
+
place: placement,
|
|
283
|
+
anchorRect,
|
|
284
|
+
popperRect,
|
|
285
|
+
forViewportCheck: false,
|
|
286
|
+
});
|
|
287
|
+
return {
|
|
288
|
+
found: true,
|
|
289
|
+
placement,
|
|
290
|
+
style: clampPopperToContainer(finalStyle, popperRect),
|
|
291
|
+
};
|
|
292
|
+
};
|
|
293
|
+
const fallback = (placement) => {
|
|
294
|
+
const finalStyle = getPositionStyle({
|
|
295
|
+
place: placement,
|
|
296
|
+
anchorRect,
|
|
297
|
+
popperRect,
|
|
298
|
+
forViewportCheck: false,
|
|
299
|
+
});
|
|
300
|
+
return {
|
|
301
|
+
found: false,
|
|
302
|
+
placement,
|
|
303
|
+
style: clampPopperToContainer(finalStyle, popperRect),
|
|
304
|
+
};
|
|
305
|
+
};
|
|
306
|
+
const isAuto = preferredPlacement.startsWith('auto');
|
|
307
|
+
const isFill = preferredPlacement.endsWith('fill');
|
|
308
|
+
// -------------------------------------------------------
|
|
309
|
+
// 1. EXPLICIT (top, bottom, left, right, top-left, ...)
|
|
310
|
+
// -------------------------------------------------------
|
|
311
|
+
if (!isAuto && !isFill) {
|
|
312
|
+
if (!autoFlip) {
|
|
313
|
+
return (_a = tryPlacement(preferredPlacement)) !== null && _a !== void 0 ? _a : fallback(preferredPlacement);
|
|
314
|
+
}
|
|
315
|
+
// Формируем порядок без дубликатов
|
|
316
|
+
const order = Array.from(new Set([
|
|
317
|
+
preferredPlacement,
|
|
318
|
+
...((_b = alternativePlacements === null || alternativePlacements === void 0 ? void 0 : alternativePlacements.filter(p => !p.startsWith('auto') && !p.endsWith('fill'))) !== null && _b !== void 0 ? _b : []),
|
|
319
|
+
...((_c = placementsOrder[preferredPlacement]) !== null && _c !== void 0 ? _c : []),
|
|
320
|
+
]));
|
|
321
|
+
for (const p of order) {
|
|
322
|
+
const r = tryPlacement(p);
|
|
323
|
+
if (r)
|
|
324
|
+
return r;
|
|
325
|
+
}
|
|
326
|
+
return fallback(preferredPlacement);
|
|
327
|
+
}
|
|
328
|
+
// -------------------------------------------------------
|
|
329
|
+
// 2. FILL (bottom-fill, top-fill)
|
|
330
|
+
// -------------------------------------------------------
|
|
331
|
+
if (isFill) {
|
|
332
|
+
const [dir] = preferredPlacement.split('-');
|
|
333
|
+
if (!autoFlip) {
|
|
334
|
+
return (_d = tryPlacement(preferredPlacement)) !== null && _d !== void 0 ? _d : fallback(preferredPlacement);
|
|
335
|
+
}
|
|
336
|
+
const opposite = dir === 'bottom' ? 'top-fill' : 'bottom-fill';
|
|
337
|
+
// Формируем порядок: fill сначала, потом обычные позиции из альтернатив, потом дефолтные
|
|
338
|
+
const order = Array.from(new Set([
|
|
339
|
+
preferredPlacement,
|
|
340
|
+
opposite,
|
|
341
|
+
// Обычные позиции из alternativePlacements (не auto и не fill)
|
|
342
|
+
...((_e = alternativePlacements === null || alternativePlacements === void 0 ? void 0 : alternativePlacements.filter(p => !p.startsWith('auto') && !p.endsWith('fill'))) !== null && _e !== void 0 ? _e : []),
|
|
343
|
+
// Обычные позиции из дефолтного порядка (не fill)
|
|
344
|
+
...((_g = (_f = placementsOrder[preferredPlacement]) === null || _f === void 0 ? void 0 : _f.filter(p => !p.endsWith('fill'))) !== null && _g !== void 0 ? _g : []),
|
|
345
|
+
]));
|
|
346
|
+
for (const p of order) {
|
|
347
|
+
const r = tryPlacement(p);
|
|
348
|
+
if (r)
|
|
349
|
+
return r;
|
|
350
|
+
}
|
|
351
|
+
return fallback(preferredPlacement);
|
|
352
|
+
}
|
|
353
|
+
// -------------------------------------------------------
|
|
354
|
+
// 3. AUTO
|
|
355
|
+
// -------------------------------------------------------
|
|
356
|
+
if (isAuto) {
|
|
357
|
+
// Если autoFlip выключен - просто используем дефолтное направление
|
|
358
|
+
if (!autoFlip) {
|
|
359
|
+
const [, dir = 'bottom'] = preferredPlacement.split('-');
|
|
360
|
+
const defaultPlacement = dir;
|
|
361
|
+
return (_h = tryPlacement(defaultPlacement)) !== null && _h !== void 0 ? _h : fallback(defaultPlacement);
|
|
362
|
+
}
|
|
363
|
+
const [, dir = 'bottom'] = preferredPlacement.split('-');
|
|
364
|
+
const base = (alternativePlacements === null || alternativePlacements === void 0 ? void 0 : alternativePlacements.length)
|
|
365
|
+
? alternativePlacements
|
|
366
|
+
: [
|
|
367
|
+
'bottom',
|
|
368
|
+
'bottom-start',
|
|
369
|
+
'bottom-end',
|
|
370
|
+
'bottom-left',
|
|
371
|
+
'bottom-right',
|
|
372
|
+
'top',
|
|
373
|
+
'top-left',
|
|
374
|
+
'top-right',
|
|
375
|
+
'top-start',
|
|
376
|
+
'top-end',
|
|
377
|
+
'left',
|
|
378
|
+
'left-top',
|
|
379
|
+
'left-bottom',
|
|
380
|
+
'right',
|
|
381
|
+
'right-top',
|
|
382
|
+
'right-bottom',
|
|
383
|
+
];
|
|
384
|
+
const sorted = [...base].sort((a, b) => {
|
|
385
|
+
const ad = a.split('-')[0];
|
|
386
|
+
const bd = b.split('-')[0];
|
|
387
|
+
if (ad === dir && bd !== dir)
|
|
388
|
+
return -1;
|
|
389
|
+
if (ad !== dir && bd === dir)
|
|
390
|
+
return 1;
|
|
391
|
+
return 0;
|
|
392
|
+
});
|
|
393
|
+
for (const p of sorted) {
|
|
394
|
+
const r = tryPlacement(p);
|
|
395
|
+
if (r)
|
|
396
|
+
return r;
|
|
397
|
+
}
|
|
398
|
+
return fallback(dir);
|
|
399
|
+
}
|
|
400
|
+
// -------------------------------------------------------
|
|
401
|
+
// 4. SUPER FALLBACK
|
|
402
|
+
// -------------------------------------------------------
|
|
403
|
+
return fallback('bottom');
|
|
404
|
+
}, [
|
|
405
|
+
autoFlip,
|
|
406
|
+
getPositionStyle,
|
|
407
|
+
checkIfViewportFits,
|
|
408
|
+
clampPopperToContainer,
|
|
409
|
+
placementsOrder,
|
|
410
|
+
alternativePlacements,
|
|
411
|
+
]);
|
|
412
|
+
// #region Calculate Position
|
|
413
|
+
const calculatePosition = react_1.default.useCallback(() => {
|
|
414
|
+
if (!anchorElement || !popperRef.current) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
const anchorRect = anchorElement.getBoundingClientRect();
|
|
418
|
+
const popperRect = popperRef.current.getBoundingClientRect();
|
|
419
|
+
const { style, placement, found } = getPreferredPlacements(anchorPos, anchorRect, popperRect);
|
|
420
|
+
if (found) {
|
|
421
|
+
setActualPlacement(placement);
|
|
422
|
+
}
|
|
423
|
+
setStyle(style);
|
|
424
|
+
}, [anchorElement, anchorPos, getPreferredPlacements]);
|
|
425
|
+
/**
|
|
426
|
+
* Open / Close popper logic
|
|
427
|
+
*/
|
|
428
|
+
react_1.default.useEffect(() => {
|
|
429
|
+
if (!isOpen) {
|
|
430
|
+
setIsVisible(false);
|
|
431
|
+
setStyle(null);
|
|
432
|
+
}
|
|
433
|
+
else if (anchorElement && isOpen && popperRef.current) {
|
|
434
|
+
calculatePosition();
|
|
435
|
+
setIsVisible(true);
|
|
436
|
+
}
|
|
437
|
+
}, [anchorElement, getPreferredPlacements, isOpen, calculatePosition]);
|
|
438
|
+
/**
|
|
439
|
+
* The anchor element changes
|
|
440
|
+
*/
|
|
441
|
+
react_1.default.useEffect(() => {
|
|
442
|
+
if (!isOpen || !anchorElement) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
const observer = new MutationObserver(() => {
|
|
446
|
+
window.requestAnimationFrame(() => {
|
|
447
|
+
// console.log('The anchor element was change');
|
|
448
|
+
calculatePosition();
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
observer.observe(anchorElement, {
|
|
452
|
+
attributes: true,
|
|
453
|
+
childList: true,
|
|
454
|
+
subtree: true,
|
|
455
|
+
});
|
|
456
|
+
// eslint-disable-next-line consistent-return
|
|
457
|
+
return () => {
|
|
458
|
+
observer.disconnect();
|
|
459
|
+
};
|
|
460
|
+
}, [isOpen, anchorElement, calculatePosition]);
|
|
461
|
+
/**
|
|
462
|
+
* On scroll - recalculate position
|
|
463
|
+
*/
|
|
464
|
+
react_1.default.useEffect(() => {
|
|
465
|
+
if (!isOpen || !scrollableAncestor) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const handleScroll = () => {
|
|
469
|
+
window.requestAnimationFrame(() => {
|
|
470
|
+
calculatePosition();
|
|
471
|
+
});
|
|
472
|
+
};
|
|
473
|
+
// parent scroll listener
|
|
474
|
+
if (scrollableAncestor instanceof HTMLElement) {
|
|
475
|
+
scrollableAncestor.addEventListener('scroll', handleScroll, { passive: true });
|
|
476
|
+
}
|
|
477
|
+
else if (scrollableAncestor === window) {
|
|
478
|
+
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
479
|
+
}
|
|
480
|
+
// eslint-disable-next-line consistent-return
|
|
481
|
+
return () => {
|
|
482
|
+
if (scrollableAncestor instanceof HTMLElement) {
|
|
483
|
+
scrollableAncestor.removeEventListener('scroll', handleScroll);
|
|
484
|
+
}
|
|
485
|
+
else if (scrollableAncestor === window) {
|
|
486
|
+
window.removeEventListener('scroll', handleScroll);
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
}, [isOpen, scrollableAncestor, calculatePosition]);
|
|
490
|
+
/**
|
|
491
|
+
* On window resize - recalculate position
|
|
492
|
+
*/
|
|
493
|
+
react_1.default.useEffect(() => {
|
|
494
|
+
if (!isOpen) {
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
const handleResize = () => {
|
|
498
|
+
window.requestAnimationFrame(() => {
|
|
499
|
+
calculatePosition();
|
|
500
|
+
});
|
|
501
|
+
};
|
|
502
|
+
// parent scroll listener
|
|
503
|
+
window.addEventListener('resize', handleResize);
|
|
504
|
+
// eslint-disable-next-line consistent-return
|
|
505
|
+
return () => {
|
|
506
|
+
window.removeEventListener('resize', handleResize);
|
|
507
|
+
};
|
|
508
|
+
}, [calculatePosition, isOpen]);
|
|
509
|
+
return {
|
|
510
|
+
actualPlacement,
|
|
511
|
+
style,
|
|
512
|
+
popperRef,
|
|
513
|
+
isVisible,
|
|
514
|
+
scrollableAncestor,
|
|
515
|
+
calculatePosition,
|
|
516
|
+
setActualPlacement,
|
|
517
|
+
setStyle,
|
|
518
|
+
setIsVisible,
|
|
519
|
+
};
|
|
520
|
+
};
|
|
521
|
+
exports.usePopper = usePopper;
|
|
522
|
+
exports.default = exports.usePopper;
|
|
@@ -66,39 +66,14 @@ const StyledSelectboxButton = (0, styled_1.default)(Button_1.default) `
|
|
|
66
66
|
}
|
|
67
67
|
`}
|
|
68
68
|
transition: none;
|
|
69
|
-
|
|
70
|
-
$anchorPos &&
|
|
71
|
-
['bottom-fill', 'top-fill'].includes($anchorPos) &&
|
|
72
|
-
(0, react_1.css) `
|
|
73
|
-
border-width: 1px;
|
|
74
|
-
border-style: solid;
|
|
75
|
-
border-color: ${theme.isDark
|
|
76
|
-
? theme.color.textPrimary.darken(100).toString()
|
|
77
|
-
: theme.color.textPrimary.lighten(150).toString()};
|
|
78
|
-
`};
|
|
79
|
-
|
|
80
|
-
${({ $anchorPos, $isOpen }) => $isOpen &&
|
|
81
|
-
$anchorPos === 'bottom-fill' &&
|
|
82
|
-
(0, react_1.css) `
|
|
83
|
-
border-bottom-left-radius: 0;
|
|
84
|
-
border-bottom-right-radius: 0;
|
|
85
|
-
border-bottom: 0;
|
|
86
|
-
`};
|
|
87
|
-
|
|
88
|
-
${({ $anchorPos, $isOpen }) => $isOpen &&
|
|
89
|
-
$anchorPos === 'top-fill' &&
|
|
90
|
-
(0, react_1.css) `
|
|
91
|
-
border-top-left-radius: 0;
|
|
92
|
-
border-top-right-radius: 0;
|
|
93
|
-
border-top: 0;
|
|
94
|
-
`};
|
|
69
|
+
|
|
95
70
|
`;
|
|
96
71
|
const StyledButtonTextWrapper = (0, styled_1.default)(ButtonTextWrapper_1.default) `
|
|
97
72
|
flex: 1;
|
|
98
73
|
`;
|
|
99
74
|
const SelectboxButton = (props, ref) => {
|
|
100
75
|
const { children, fullWidth, error, anchorPos, isOpen } = props, nativeProps = __rest(props, ["children", "fullWidth", "error", "anchorPos", "isOpen"]);
|
|
101
|
-
return (React.createElement(StyledSelectboxButton, Object.assign({ "$fullWidth": fullWidth, "$error": error }, nativeProps, { "$isOpen": isOpen,
|
|
76
|
+
return (React.createElement(StyledSelectboxButton, Object.assign({ "$fullWidth": fullWidth, "$error": error }, nativeProps, { "$isOpen": isOpen, overrides: {
|
|
102
77
|
TextWrapper: StyledButtonTextWrapper,
|
|
103
78
|
}, ref: ref }), children));
|
|
104
79
|
};
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { MenuProps, MenuRef } from '../Menu';
|
|
3
|
-
import { AnchorPos } from '../Popper';
|
|
4
3
|
import { OnChange } from 'Selectbox';
|
|
5
4
|
export interface SelectbosMenuProps<T, Multiple extends boolean | undefined = undefined> extends MenuProps<T, Multiple> {
|
|
6
|
-
readonly actualPlacement: AnchorPos;
|
|
7
5
|
readonly onChange?: OnChange<T, Multiple>;
|
|
8
6
|
}
|
|
9
7
|
declare const _default: <T, Multiple extends boolean | undefined = undefined>(props: SelectbosMenuProps<T, Multiple> & {
|
|
@@ -22,15 +22,25 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
22
22
|
__setModuleDefault(result, mod);
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
26
|
+
var t = {};
|
|
27
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
28
|
+
t[p] = s[p];
|
|
29
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
30
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
31
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
32
|
+
t[p[i]] = s[p[i]];
|
|
33
|
+
}
|
|
34
|
+
return t;
|
|
35
|
+
};
|
|
25
36
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
37
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
38
|
};
|
|
28
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
40
|
const React = __importStar(require("react"));
|
|
30
41
|
const Menu_1 = __importDefault(require("../Menu"));
|
|
31
|
-
const OverrideMenuList_1 = __importDefault(require("../Autocomplete/OverrideMenuList"));
|
|
32
42
|
const SelectboxMenu = React.forwardRef((props, ref) => {
|
|
33
|
-
const {
|
|
43
|
+
const { multiple, items, value, isOpen, anchorPos, anchorElement, getOptionSelected, onChange, onRequestClose, children } = props, restMenuProps = __rest(props, ["multiple", "items", "value", "isOpen", "anchorPos", "anchorElement", "getOptionSelected", "onChange", "onRequestClose", "children"]);
|
|
34
44
|
const handleSelectItem = React.useCallback((item) => {
|
|
35
45
|
onChange === null || onChange === void 0 ? void 0 : onChange(item);
|
|
36
46
|
}, [onChange]);
|
|
@@ -42,9 +52,7 @@ const SelectboxMenu = React.forwardRef((props, ref) => {
|
|
|
42
52
|
anchorElement === null || anchorElement === void 0 ? void 0 : anchorElement.focus();
|
|
43
53
|
}
|
|
44
54
|
}, [onRequestClose, anchorElement]);
|
|
45
|
-
return (React.createElement(Menu_1.default, { ref: ref, offset: 0, anchorPos:
|
|
46
|
-
List: OverrideMenuList_1.default,
|
|
47
|
-
}, closeOnSelect: !multiple, onRequestClose: handleRequestClose }, ({ index, item }, itemProps) => children({ index, item: item }, itemProps)));
|
|
55
|
+
return (React.createElement(Menu_1.default, Object.assign({ ref: ref, offset: 0, anchorPos: anchorPos, multiple: multiple, items: items, value: value, isOpen: isOpen && items.length > 0, autofocus: true, anchorElement: anchorElement, closeOutsideClick: false, getOptionSelected: getOptionSelected, onSelectItem: handleSelectItem, closeOnSelect: !multiple, onRequestClose: handleRequestClose }, restMenuProps), ({ index, item }, itemProps) => children({ index, item: item }, itemProps)));
|
|
48
56
|
});
|
|
49
57
|
SelectboxMenu.displayName = 'SelectboxMenu';
|
|
50
58
|
exports.default = React.memo(SelectboxMenu);
|
package/Selectbox/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { GetOptionSelected, OnRequestClose, Value } from '../Menu';
|
|
3
|
-
import type {
|
|
3
|
+
import type { MenuItemProps } from '../Menu/MenuItem';
|
|
4
4
|
import SelectboxItem from '../Menu/MenuItem';
|
|
5
5
|
import { SelectboxButtonProps } from './SelectboxButton';
|
|
6
6
|
import { SelectboxChevronIconProps } from './SelectboxChevronIcon';
|
|
7
|
-
import { AnchorPos } from '../Popper';
|
|
7
|
+
import { AnchorPos, PositionStrategy } from '../Popper';
|
|
8
8
|
import { TextFieldLabelProps } from '../TextField/TextFieldLabel';
|
|
9
9
|
import { TextFieldLabelAsteriskProps } from '../TextField/TextFieldLabelAsterisk';
|
|
10
10
|
import { TextFieldErrorTextProps } from '../TextField/TextFieldErrorText';
|
|
@@ -36,6 +36,8 @@ export interface SelectboxProps<T, Multiple extends boolean | undefined = undefi
|
|
|
36
36
|
* Default: `bottom`
|
|
37
37
|
*/
|
|
38
38
|
readonly anchorPos?: AnchorPos;
|
|
39
|
+
readonly positionStrategy?: PositionStrategy;
|
|
40
|
+
readonly autoFlip?: boolean;
|
|
39
41
|
/**
|
|
40
42
|
* Text field loading state\
|
|
41
43
|
* If `true` then text field has been contained the loading indicator, otherwise - nop
|
|
@@ -154,7 +156,7 @@ export interface SelectboxOverrides {
|
|
|
154
156
|
export type Children<T> = (data: {
|
|
155
157
|
item: T;
|
|
156
158
|
index: number;
|
|
157
|
-
}, itemProps:
|
|
159
|
+
}, itemProps: MenuItemProps) => React.ReactNode;
|
|
158
160
|
export type ItemToString<T, Multiple extends boolean | undefined = undefined> = (item: Multiple extends undefined ? T : readonly T[]) => string;
|
|
159
161
|
export type OnChange<T, Multiple extends boolean | undefined = undefined> = (item: Value<T, Multiple>) => void;
|
|
160
162
|
declare const _default: <T, Multiple extends boolean | undefined = undefined>(props: SelectboxProps<T, Multiple> & {
|