@via-profit/ui-kit 0.2.1-alpha.1 → 0.2.2
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 +33 -6
- package/Autocomplete/AutocompleteContainer.js +48 -36
- package/Autocomplete/AutocompleteItem.d.ts +1 -1
- package/Autocomplete/AutocompleteTextField.d.ts +3 -0
- package/Autocomplete/AutocompleteTextField.js +63 -1
- package/Autocomplete/OverrideMenuList.d.ts +4 -0
- package/Autocomplete/OverrideMenuList.js +39 -0
- package/Autocomplete/context.d.ts +9 -1
- package/Autocomplete/context.js +15 -1
- package/Autocomplete/index.js +9 -10
- package/Autocomplete2/Autocomplete2.d.ts +12 -0
- package/Autocomplete2/Autocomplete2.js +83 -0
- package/Autocomplete2/Autocomplete2TextField.d.ts +7 -0
- package/Autocomplete2/Autocomplete2TextField.js +39 -0
- package/Autocomplete2/index.d.ts +2 -0
- package/Autocomplete2/index.js +7 -0
- package/Avatar/index.js +1 -1
- package/Badge/BadgeDeleteButton.js +6 -6
- package/Badge/BadgeOutlined.js +2 -2
- package/Badge/BadgeStandard.js +1 -1
- package/Button/ButtonOutlined.js +1 -1
- package/Button/ButtonPlain.js +1 -1
- package/Button/ButtonStandard.js +4 -11
- package/Calendar/CalendarCell.js +6 -2
- package/Calendar/CalendarComponent.d.ts +226 -0
- package/Calendar/CalendarComponent.js +572 -0
- package/Calendar/CalendarControlButton.js +16 -9
- package/Calendar/CalendarDayBadge.js +1 -1
- package/Calendar/CalendarEmptyCell.d.ts +2 -2
- package/Calendar/CalendarEmptyCell.js +8 -3
- package/Calendar/CalendarHeading.js +2 -1
- package/Calendar/CalendarSubheading.js +3 -2
- package/Calendar/CalendarWeekDayCell.d.ts +10 -0
- package/Calendar/CalendarWeekDayCell.js +32 -0
- package/Calendar/CalendarWeekDayWeekNumber.d.ts +7 -0
- package/Calendar/CalendarWeekDayWeekNumber.js +38 -0
- package/Calendar/CalendarWeekRow.d.ts +2 -2
- package/Calendar/CalendarWeekRow.js +2 -1
- package/Calendar/CalendarWeekRowButton.d.ts +13 -0
- package/Calendar/CalendarWeekRowButton.js +31 -0
- package/Calendar/CalendarYearsSelector.d.ts +39 -1
- package/Calendar/CalendarYearsSelector.js +52 -16
- package/Calendar/IconChevronDown.d.ts +3 -0
- package/Calendar/IconChevronDown.js +31 -0
- package/Calendar/index.d.ts +7 -224
- package/Calendar/index.js +19 -281
- package/Calendar/use-calendar.d.ts +1 -1
- package/Color/Color.d.ts +138 -0
- package/Color/Color.js +324 -0
- package/Color/ColorGenerator.d.ts +29 -0
- package/Color/ColorGenerator.js +141 -0
- package/Color/ColorParser.d.ts +20 -0
- package/Color/ColorParser.js +284 -0
- package/Color/index.d.ts +4 -122
- package/Color/index.js +18 -473
- package/CountryFlags/SwiperOverview.d.ts +257 -0
- package/CountryFlags/SwiperOverview.js +524 -0
- package/DatePicker/index.d.ts +2 -17
- package/DatePicker/index.js +47 -61
- package/DatePicker/useDatePickerFormat.js +5 -5
- package/ErrorBoundary/Fallback.d.ts +3 -0
- package/ErrorBoundary/Fallback.js +51 -0
- package/ErrorBoundary/RenderError.d.ts +6 -0
- package/ErrorBoundary/RenderError.js +80 -0
- package/ErrorBoundary/index.d.ts +7 -11
- package/ErrorBoundary/index.js +4 -51
- package/LoadingIndicator/Spinner.d.ts +5 -3
- package/LoadingIndicator/Spinner.js +61 -32
- package/LoadingIndicator/index.d.ts +3 -3
- package/LoadingIndicator/index.js +15 -2
- package/MaskedField/index.d.ts +2 -1
- package/MaskedField/index.js +52 -43
- package/MaskedField/useMasked.js +7 -5
- package/Menu/AutoHeightWrapper.d.ts +6 -0
- package/Menu/AutoHeightWrapper.js +43 -0
- package/Menu/MeasuredItem.d.ts +10 -0
- package/Menu/MeasuredItem.js +51 -0
- package/Menu/MenuContainer.d.ts +51 -18
- package/Menu/MenuContainer.js +19 -12
- package/Menu/MenuItem.d.ts +24 -7
- package/Menu/MenuItem.js +13 -6
- package/Menu/MenuList.d.ts +4 -2
- package/Menu/MenuList.js +26 -6
- package/Menu/VirtualizedItem.d.ts +22 -0
- package/Menu/VirtualizedItem.js +69 -0
- package/Menu/VirtualizedList.d.ts +18 -0
- package/Menu/VirtualizedList.js +28 -0
- package/Menu/VirtualizedListComponent.d.ts +20 -0
- package/Menu/VirtualizedListComponent.js +305 -0
- package/Menu/index.d.ts +3 -1
- package/Menu/index.js +2 -0
- package/Popper/Popper.d.ts +7 -80
- package/Popper/Popper.js +26 -12
- package/Popper/PopperContainer.js +1 -1
- package/Popper/usePopprer.d.ts +88 -3
- package/Popper/usePopprer.js +240 -65
- package/Selectbox/SelectboxButton.d.ts +5 -0
- package/Selectbox/SelectboxButton.js +48 -8
- package/Selectbox/SelectboxButtonWrapper.d.ts +2 -0
- package/Selectbox/SelectboxButtonWrapper.js +1 -17
- package/Selectbox/SelectboxMenu.d.ts +12 -0
- package/Selectbox/SelectboxMenu.js +50 -0
- package/Selectbox/index.d.ts +5 -5
- package/Selectbox/index.js +115 -97
- package/Surface/index.js +1 -1
- package/Swiper/Swiper.d.ts +57 -0
- package/Swiper/Swiper.js +412 -0
- package/Swiper/SwiperContainer.d.ts +7 -0
- package/Swiper/SwiperContainer.js +15 -0
- package/Swiper/SwiperSlide.d.ts +12 -0
- package/Swiper/SwiperSlide.js +58 -0
- package/Swiper/SwiperSlidesRenderer.d.ts +11 -0
- package/Swiper/SwiperSlidesRenderer.js +74 -0
- package/Swiper/SwiperTrack.d.ts +18 -0
- package/Swiper/SwiperTrack.js +55 -0
- package/Swiper/SwiperWrapper.d.ts +16 -0
- package/Swiper/SwiperWrapper.js +60 -0
- package/Swiper/index.d.ts +7 -0
- package/Swiper/index.js +26 -0
- package/Switch/SwitchDot.js +1 -1
- package/Switch/SwitchTrack.js +1 -1
- package/TextField/TextFieldContainer.d.ts +2 -2
- package/TextField/TextFieldContainer.js +5 -3
- package/TextField/TextFieldErrorText.d.ts +2 -2
- package/TextField/TextFieldErrorText.js +5 -4
- package/TextField/TextFieldIconWrapper.d.ts +3 -3
- package/TextField/TextFieldIconWrapper.js +5 -3
- package/TextField/TextFieldInput.d.ts +2 -2
- package/TextField/TextFieldInput.js +6 -4
- package/TextField/TextFieldInputWrapper.d.ts +2 -2
- package/TextField/TextFieldInputWrapper.js +5 -3
- package/TextField/TextFieldLabel.d.ts +2 -2
- package/TextField/TextFieldLabel.js +5 -3
- package/TextField/TextFieldLabelAsterisk.d.ts +2 -2
- package/TextField/TextFieldLabelAsterisk.js +5 -3
- package/TextField/index.d.ts +8 -1
- package/TextField/index.js +49 -22
- package/ThemeProvider/createTheme.js +2 -2
- package/package.json +1 -1
- package/Calendar/reducer.d.ts +0 -17
- package/Calendar/reducer.js +0 -50
- /package/Calendar/{IconChevronRight.d.ts → CalendarIconNext.d.ts} +0 -0
- /package/Calendar/{IconChevronRight.js → CalendarIconNext.js} +0 -0
|
@@ -21,7 +21,7 @@ const StyledContainer = styled_1.default.div `
|
|
|
21
21
|
z-index: ${({ theme, $zIndex, $disablePortal }) => typeof $zIndex !== 'undefined' ? $zIndex : $disablePortal ? undefined : theme.zIndex.modal};
|
|
22
22
|
`;
|
|
23
23
|
const PopperContainer = (props, ref) => {
|
|
24
|
-
const { children, disablePortal, zIndex, positionStrategy = '
|
|
24
|
+
const { children, disablePortal, zIndex, positionStrategy = 'fixed' } = props, nativeProps = __rest(props, ["children", "disablePortal", "zIndex", "positionStrategy"]);
|
|
25
25
|
return (react_1.default.createElement(StyledContainer, Object.assign({ "$disablePortal": disablePortal, "$zIndex": zIndex, "$positionStrategy": positionStrategy }, nativeProps, { ref: ref }), children));
|
|
26
26
|
};
|
|
27
27
|
exports.default = react_1.default.forwardRef(PopperContainer);
|
package/Popper/usePopprer.d.ts
CHANGED
|
@@ -1,8 +1,83 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { AnchorPos } from './Popper';
|
|
3
2
|
import { PositionStrategy } from './PopperContainer';
|
|
3
|
+
/**
|
|
4
|
+
* Base directions for popper positioning.
|
|
5
|
+
* These are the primary axes around which the popper can be placed.
|
|
6
|
+
*
|
|
7
|
+
* @default 'bottom' (when used as anchorPos)
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* type Direction = 'top' | 'bottom' | 'left' | 'right';
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export type Direction = 'top' | 'left' | 'right' | 'bottom';
|
|
14
|
+
/**
|
|
15
|
+
* Auto placement modifiers that enable intelligent positioning.
|
|
16
|
+
* The popper will automatically choose the best placement that fits in the viewport.
|
|
17
|
+
* - `auto`: Dynamically selects the optimal direction
|
|
18
|
+
* - `auto-top`, `auto-bottom`, `auto-left`, `auto-right`: Auto placement with priority given to the specified direction
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```tsx
|
|
22
|
+
* // Will try to place on top first, but will flip if needed
|
|
23
|
+
* <Popper anchorPos="auto-top">...</Popper>
|
|
24
|
+
*
|
|
25
|
+
* // Will try all directions equally
|
|
26
|
+
* <Popper anchorPos="auto">...</Popper>
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export type AutoModifier = 'auto' | 'auto-top' | 'auto-bottom' | 'auto-left' | 'auto-right';
|
|
30
|
+
/**
|
|
31
|
+
* Precise placement modifiers for fine-tuned positioning.
|
|
32
|
+
* These combine a base direction with specific alignment options:
|
|
33
|
+
* - `start`/`end`: Aligns to the beginning or end of the anchor (useful for RTL layouts)
|
|
34
|
+
* - `left`/`right`: Horizontal alignment for vertical placements
|
|
35
|
+
* - `top`/`bottom`: Vertical alignment for horizontal placements
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```tsx
|
|
39
|
+
* // Corner placements
|
|
40
|
+
* <Popper anchorPos="top-left">Aligns to top-left corner</Popper>
|
|
41
|
+
*
|
|
42
|
+
* // Logical placements (RTL-friendly)
|
|
43
|
+
* <Popper anchorPos="bottom-start">Aligns to bottom edge, start side</Popper>
|
|
44
|
+
*
|
|
45
|
+
* // Side alignments
|
|
46
|
+
* <Popper anchorPos="left-top">Aligns to left side, top edge</Popper>
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export type Modifier = 'top-start' | 'top-end' | 'top-left' | 'top-right' | 'bottom-end' | 'bottom-start' | 'bottom-right' | 'bottom-left' | 'left-top' | 'left-bottom' | 'right-top' | 'right-bottom' | 'bottom-fill' | 'top-fill';
|
|
50
|
+
/**
|
|
51
|
+
* Available placement options for the popper.
|
|
52
|
+
* Combines basic directions, precise modifiers, and auto-placement options
|
|
53
|
+
* to provide full control over popper positioning.
|
|
54
|
+
*
|
|
55
|
+
* Categories:
|
|
56
|
+
* - **Basic directions**: `top`, `bottom`, `left`, `right` - Centered placement
|
|
57
|
+
* - **Corner placements**: `top-left`, `top-right`, `bottom-left`, `bottom-right` - Aligned to corners
|
|
58
|
+
* - **Logical placements**: `top-start`, `top-end`, `bottom-start`, `bottom-end` - RTL-aware alignment
|
|
59
|
+
* - **Side alignments**: `left-top`, `left-bottom`, `right-top`, `right-bottom` - Edge-aligned
|
|
60
|
+
* - **Auto placements**: `auto`, `auto-top`, `auto-bottom`, `auto-left`, `auto-right` - Intelligent positioning
|
|
61
|
+
*
|
|
62
|
+
* @default 'bottom'
|
|
63
|
+
* @example
|
|
64
|
+
* ```tsx
|
|
65
|
+
* // Basic centered placement
|
|
66
|
+
* <Popper anchorPos="bottom">Centered below anchor</Popper>
|
|
67
|
+
*
|
|
68
|
+
* // Corner placement
|
|
69
|
+
* <Popper anchorPos="top-right">Attached to top-right corner</Popper>
|
|
70
|
+
*
|
|
71
|
+
* // Auto placement with priority
|
|
72
|
+
* <Popper anchorPos="auto-left">Try left first, then find best fit</Popper>
|
|
73
|
+
*
|
|
74
|
+
* // RTL-aware placement
|
|
75
|
+
* <Popper anchorPos="top-start">Respects text direction</Popper>
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export type AnchorPos = Direction | Modifier | AutoModifier;
|
|
4
79
|
export interface UsePopperProps {
|
|
5
|
-
readonly anchorElement
|
|
80
|
+
readonly anchorElement: HTMLElement | null;
|
|
6
81
|
readonly anchorPos?: AnchorPos;
|
|
7
82
|
readonly positionStrategy?: PositionStrategy;
|
|
8
83
|
readonly autoFlip?: boolean;
|
|
@@ -16,10 +91,20 @@ export interface UsePopperResult {
|
|
|
16
91
|
readonly popperRef: React.MutableRefObject<HTMLDivElement | null>;
|
|
17
92
|
readonly isVisible: boolean;
|
|
18
93
|
readonly calculatePosition: () => void;
|
|
19
|
-
readonly
|
|
94
|
+
readonly scrollableAncestor: HTMLElement | Window | null;
|
|
20
95
|
readonly setActualPlacement: React.Dispatch<React.SetStateAction<AnchorPos>>;
|
|
21
96
|
readonly setStyle: React.Dispatch<React.SetStateAction<React.CSSProperties | null>>;
|
|
22
97
|
readonly setIsVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
|
23
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Finds the closest scrollable ancestor of an element.
|
|
101
|
+
* A scrollable element is one that has overflow set to auto, scroll, or overlay
|
|
102
|
+
* and has content that overflows, allowing scrolling.
|
|
103
|
+
*
|
|
104
|
+
* @param element - The element to start searching from
|
|
105
|
+
* @param includeHidden - Whether to consider elements with overflow: hidden
|
|
106
|
+
* @returns The closest scrollable ancestor or window if none found
|
|
107
|
+
*/
|
|
108
|
+
export declare const findScrollableAncestor: (element: HTMLElement | null, includeHidden?: boolean) => HTMLElement | Window | null;
|
|
24
109
|
export declare const usePopper: ({ anchorElement, anchorPos, positionStrategy, autoFlip, offset, viewportMargin, isOpen, }: UsePopperProps) => UsePopperResult;
|
|
25
110
|
export default usePopper;
|
package/Popper/usePopprer.js
CHANGED
|
@@ -3,14 +3,60 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.usePopper = void 0;
|
|
6
|
+
exports.usePopper = exports.findScrollableAncestor = void 0;
|
|
7
7
|
const react_1 = __importDefault(require("react"));
|
|
8
|
-
|
|
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 = ({ anchorElement, anchorPos = 'auto', positionStrategy = 'fixed', autoFlip = true, offset = 0, viewportMargin = 10, isOpen = false, }) => {
|
|
9
48
|
const [actualPlacement, setActualPlacement] = react_1.default.useState(anchorPos);
|
|
10
49
|
const [style, setStyle] = react_1.default.useState(null);
|
|
11
50
|
const [isVisible, setIsVisible] = react_1.default.useState(false);
|
|
12
51
|
const popperRef = react_1.default.useRef(null);
|
|
13
|
-
//
|
|
52
|
+
// Finding the nearest scrollable ancestor
|
|
53
|
+
const scrollableAncestor = react_1.default.useMemo(() => {
|
|
54
|
+
if (!anchorElement || typeof window === 'undefined') {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
return (0, exports.findScrollableAncestor)(anchorElement);
|
|
58
|
+
}, [anchorElement]);
|
|
59
|
+
// Synchronizing actualPlacement with anchorPos
|
|
14
60
|
react_1.default.useEffect(() => {
|
|
15
61
|
if (actualPlacement !== anchorPos) {
|
|
16
62
|
setActualPlacement(anchorPos);
|
|
@@ -21,11 +67,16 @@ const usePopper = ({ anchorElement, anchorPos = 'auto', positionStrategy = 'abso
|
|
|
21
67
|
const [direction, modifier = ''] = place.split('-');
|
|
22
68
|
let left = anchorRect.left;
|
|
23
69
|
let top = anchorRect.top;
|
|
24
|
-
|
|
70
|
+
let width = undefined;
|
|
71
|
+
// Calculating the position relative to the viewport
|
|
25
72
|
switch (direction) {
|
|
26
73
|
case 'top':
|
|
27
74
|
top = anchorRect.top - popperRect.height - offset;
|
|
28
|
-
if (modifier === '
|
|
75
|
+
if (modifier === 'fill') {
|
|
76
|
+
left = anchorRect.left;
|
|
77
|
+
width = anchorRect.width;
|
|
78
|
+
}
|
|
79
|
+
else if (modifier === 'left' || modifier === 'start') {
|
|
29
80
|
left = anchorRect.left;
|
|
30
81
|
}
|
|
31
82
|
else if (modifier === 'right' || modifier === 'end') {
|
|
@@ -37,7 +88,11 @@ const usePopper = ({ anchorElement, anchorPos = 'auto', positionStrategy = 'abso
|
|
|
37
88
|
break;
|
|
38
89
|
case 'bottom':
|
|
39
90
|
top = anchorRect.bottom + offset;
|
|
40
|
-
if (modifier === '
|
|
91
|
+
if (modifier === 'fill') {
|
|
92
|
+
left = anchorRect.left;
|
|
93
|
+
width = anchorRect.width;
|
|
94
|
+
}
|
|
95
|
+
else if (modifier === 'left' || modifier === 'start') {
|
|
41
96
|
left = anchorRect.left;
|
|
42
97
|
}
|
|
43
98
|
else if (modifier === 'right' || modifier === 'end') {
|
|
@@ -75,31 +130,82 @@ const usePopper = ({ anchorElement, anchorPos = 'auto', positionStrategy = 'abso
|
|
|
75
130
|
left = anchorRect.left + (anchorRect.width - popperRect.width) / 2;
|
|
76
131
|
top = anchorRect.bottom + offset;
|
|
77
132
|
}
|
|
78
|
-
//
|
|
133
|
+
// Returning the viewport coordinates for verification or the final coordinates
|
|
79
134
|
if (forViewportCheck) {
|
|
80
|
-
return { left, top };
|
|
135
|
+
return { left, top, width };
|
|
81
136
|
}
|
|
82
|
-
//
|
|
137
|
+
// For the final positioning, we take into account the strategy
|
|
83
138
|
if (positionStrategy === 'absolute') {
|
|
84
139
|
return {
|
|
85
140
|
left: left + window.scrollX,
|
|
86
141
|
top: top + window.scrollY,
|
|
142
|
+
width,
|
|
87
143
|
};
|
|
88
144
|
}
|
|
89
|
-
return { left, top };
|
|
145
|
+
return { left, top, width };
|
|
90
146
|
}, [offset, positionStrategy]);
|
|
147
|
+
// const clampPopperToContainer = React.useCallback(
|
|
148
|
+
// (style: { left: number; top: number; width?: number }, _popperRect: DOMRect) => style,
|
|
149
|
+
// [],
|
|
150
|
+
// );
|
|
151
|
+
const clampPopperToContainer = react_1.default.useCallback((style, popperRect) => {
|
|
152
|
+
// Если нет scrollableAncestor, используем document.documentElement как fallback
|
|
153
|
+
const container = scrollableAncestor instanceof HTMLElement ? scrollableAncestor : document.documentElement;
|
|
154
|
+
const containerRect = container.getBoundingClientRect();
|
|
155
|
+
// Базовые границы контейнера с учетом отступа
|
|
156
|
+
let minLeft = containerRect.left + viewportMargin;
|
|
157
|
+
let maxLeft = containerRect.right - popperRect.width - viewportMargin;
|
|
158
|
+
let minTop = containerRect.top + viewportMargin;
|
|
159
|
+
let maxTop = containerRect.bottom - popperRect.height - viewportMargin;
|
|
160
|
+
// Для fixed позиционирования добавляем ограничения viewport
|
|
161
|
+
if (positionStrategy === 'fixed') {
|
|
162
|
+
// Границы viewport с учетом отступа
|
|
163
|
+
const viewportMinLeft = viewportMargin;
|
|
164
|
+
const viewportMaxLeft = window.innerWidth - popperRect.width - viewportMargin;
|
|
165
|
+
const viewportMinTop = viewportMargin;
|
|
166
|
+
const viewportMaxTop = window.innerHeight - popperRect.height - viewportMargin;
|
|
167
|
+
// Берем пересечение границ контейнера и viewport
|
|
168
|
+
minLeft = Math.max(minLeft, viewportMinLeft);
|
|
169
|
+
maxLeft = Math.min(maxLeft, viewportMaxLeft);
|
|
170
|
+
minTop = Math.max(minTop, viewportMinTop);
|
|
171
|
+
maxTop = Math.min(maxTop, viewportMaxTop);
|
|
172
|
+
}
|
|
173
|
+
// Убеждаемся, что границы валидны (min не больше max)
|
|
174
|
+
const validMinLeft = Math.min(minLeft, maxLeft);
|
|
175
|
+
const validMaxLeft = Math.max(minLeft, maxLeft);
|
|
176
|
+
const validMinTop = Math.min(minTop, maxTop);
|
|
177
|
+
const validMaxTop = Math.max(minTop, maxTop);
|
|
178
|
+
// Ограничиваем позицию
|
|
179
|
+
const clampedLeft = Math.max(validMinLeft, Math.min(style.left, validMaxLeft));
|
|
180
|
+
const clampedTop = Math.max(validMinTop, Math.min(style.top, validMaxTop));
|
|
181
|
+
return {
|
|
182
|
+
left: clampedLeft,
|
|
183
|
+
top: clampedTop,
|
|
184
|
+
width: style.width,
|
|
185
|
+
};
|
|
186
|
+
}, [positionStrategy, scrollableAncestor, viewportMargin]);
|
|
91
187
|
const checkIfViewportFits = react_1.default.useCallback((style, popperRect) => {
|
|
92
188
|
const left = style.left;
|
|
93
189
|
const top = style.top;
|
|
94
190
|
const right = left + popperRect.width;
|
|
95
191
|
const bottom = top + popperRect.height;
|
|
192
|
+
if (positionStrategy === 'absolute' && scrollableAncestor instanceof HTMLElement) {
|
|
193
|
+
const containerRect = scrollableAncestor.getBoundingClientRect();
|
|
194
|
+
const relativeLeft = left - containerRect.left;
|
|
195
|
+
const relativeTop = top - containerRect.top;
|
|
196
|
+
const relativeRight = relativeLeft + popperRect.width;
|
|
197
|
+
const relativeBottom = relativeTop + popperRect.height;
|
|
198
|
+
const isWithinHorizontal = relativeLeft >= viewportMargin && relativeRight <= containerRect.width - viewportMargin;
|
|
199
|
+
const isWithinVertical = relativeTop >= viewportMargin && relativeBottom <= containerRect.height - viewportMargin;
|
|
200
|
+
return isWithinHorizontal && isWithinVertical;
|
|
201
|
+
}
|
|
96
202
|
const viewportWidth = window.innerWidth;
|
|
97
203
|
const viewportHeight = window.innerHeight;
|
|
98
204
|
const isWithinHorizontal = left >= viewportMargin && right <= viewportWidth - viewportMargin;
|
|
99
205
|
const isWithinVertical = top >= viewportMargin && bottom <= viewportHeight - viewportMargin;
|
|
100
206
|
const hasValidPosition = left >= 0 && top >= 0;
|
|
101
207
|
return hasValidPosition && isWithinHorizontal && isWithinVertical;
|
|
102
|
-
}, [viewportMargin]);
|
|
208
|
+
}, [viewportMargin, scrollableAncestor, positionStrategy]);
|
|
103
209
|
const placementsOrder = react_1.default.useMemo(() => {
|
|
104
210
|
const basePlacements = {
|
|
105
211
|
top: ['top', 'top-left', 'top-right', 'top-start', 'top-end'],
|
|
@@ -116,7 +222,7 @@ const usePopper = ({ anchorElement, anchorPos = 'auto', positionStrategy = 'abso
|
|
|
116
222
|
const order = {};
|
|
117
223
|
allPlacements.forEach(placement => {
|
|
118
224
|
const [direction] = placement.split('-');
|
|
119
|
-
//
|
|
225
|
+
// Priority: same direction > opposite > the rest
|
|
120
226
|
const opposite = direction === 'top'
|
|
121
227
|
? 'bottom'
|
|
122
228
|
: direction === 'bottom'
|
|
@@ -132,10 +238,11 @@ const usePopper = ({ anchorElement, anchorPos = 'auto', positionStrategy = 'abso
|
|
|
132
238
|
...(direction !== 'left' && direction !== 'right' ? basePlacements.right : []),
|
|
133
239
|
];
|
|
134
240
|
});
|
|
241
|
+
// console.log(order);
|
|
135
242
|
return order;
|
|
136
243
|
}, []);
|
|
137
244
|
const getPreferredPlacements = react_1.default.useCallback((preferredPlacement, anchorRect, popperRect) => {
|
|
138
|
-
//
|
|
245
|
+
// Check the auto placements
|
|
139
246
|
if (preferredPlacement.startsWith('auto')) {
|
|
140
247
|
const [, preferredDirection = 'bottom'] = preferredPlacement.split('-');
|
|
141
248
|
const allPlacements = [
|
|
@@ -156,7 +263,7 @@ const usePopper = ({ anchorElement, anchorPos = 'auto', positionStrategy = 'abso
|
|
|
156
263
|
'right-top',
|
|
157
264
|
'right-bottom',
|
|
158
265
|
];
|
|
159
|
-
//
|
|
266
|
+
// Sorting placements
|
|
160
267
|
const sortedPlacements = [...allPlacements].sort((a, b) => {
|
|
161
268
|
const aDir = a.split('-')[0];
|
|
162
269
|
const bDir = b.split('-')[0];
|
|
@@ -184,7 +291,7 @@ const usePopper = ({ anchorElement, anchorPos = 'auto', positionStrategy = 'abso
|
|
|
184
291
|
return {
|
|
185
292
|
found: true,
|
|
186
293
|
placement: place,
|
|
187
|
-
style: finalStyle,
|
|
294
|
+
style: clampPopperToContainer(finalStyle, popperRect),
|
|
188
295
|
};
|
|
189
296
|
}
|
|
190
297
|
}
|
|
@@ -199,9 +306,50 @@ const usePopper = ({ anchorElement, anchorPos = 'auto', positionStrategy = 'abso
|
|
|
199
306
|
return {
|
|
200
307
|
found: false,
|
|
201
308
|
placement: preferredPlacement,
|
|
202
|
-
style: finalStyle,
|
|
309
|
+
style: clampPopperToContainer(finalStyle, popperRect),
|
|
203
310
|
};
|
|
204
311
|
}
|
|
312
|
+
/**
|
|
313
|
+
* bottom-fill, top-fill
|
|
314
|
+
*/
|
|
315
|
+
if (preferredPlacement.endsWith('fill')) {
|
|
316
|
+
const [preferredDirection = 'bottom'] = preferredPlacement.split('-');
|
|
317
|
+
const allPlacements = ['top-fill', 'bottom-fill'];
|
|
318
|
+
const sortedPlacements = [...allPlacements].sort((a, b) => {
|
|
319
|
+
const aDir = a.split('-')[0];
|
|
320
|
+
const bDir = b.split('-')[0];
|
|
321
|
+
if (aDir === preferredDirection && bDir !== preferredDirection)
|
|
322
|
+
return -1;
|
|
323
|
+
if (aDir !== preferredDirection && bDir === preferredDirection)
|
|
324
|
+
return 1;
|
|
325
|
+
return 0;
|
|
326
|
+
});
|
|
327
|
+
for (const place of sortedPlacements) {
|
|
328
|
+
const viewportStyle = getPositionStyle({
|
|
329
|
+
place,
|
|
330
|
+
anchorRect,
|
|
331
|
+
popperRect,
|
|
332
|
+
forViewportCheck: true,
|
|
333
|
+
});
|
|
334
|
+
const fits = checkIfViewportFits(viewportStyle, popperRect);
|
|
335
|
+
if (fits) {
|
|
336
|
+
const finalStyle = getPositionStyle({
|
|
337
|
+
place,
|
|
338
|
+
anchorRect,
|
|
339
|
+
popperRect,
|
|
340
|
+
forViewportCheck: false,
|
|
341
|
+
});
|
|
342
|
+
return {
|
|
343
|
+
found: true,
|
|
344
|
+
placement: place,
|
|
345
|
+
style: clampPopperToContainer(finalStyle, popperRect),
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Other
|
|
352
|
+
*/
|
|
205
353
|
const order = placementsOrder[preferredPlacement] || placementsOrder.bottom;
|
|
206
354
|
for (const place of order) {
|
|
207
355
|
const viewportStyle = getPositionStyle({
|
|
@@ -221,7 +369,7 @@ const usePopper = ({ anchorElement, anchorPos = 'auto', positionStrategy = 'abso
|
|
|
221
369
|
return {
|
|
222
370
|
found: true,
|
|
223
371
|
placement: place,
|
|
224
|
-
style: finalStyle,
|
|
372
|
+
style: clampPopperToContainer(finalStyle, popperRect),
|
|
225
373
|
};
|
|
226
374
|
}
|
|
227
375
|
}
|
|
@@ -234,73 +382,52 @@ const usePopper = ({ anchorElement, anchorPos = 'auto', positionStrategy = 'abso
|
|
|
234
382
|
return {
|
|
235
383
|
found: false,
|
|
236
384
|
placement: preferredPlacement,
|
|
237
|
-
style: finalStyle,
|
|
385
|
+
style: clampPopperToContainer(finalStyle, popperRect),
|
|
238
386
|
};
|
|
239
|
-
}, [autoFlip, getPositionStyle, checkIfViewportFits, placementsOrder]);
|
|
387
|
+
}, [autoFlip, getPositionStyle, checkIfViewportFits, clampPopperToContainer, placementsOrder]);
|
|
240
388
|
const calculatePosition = react_1.default.useCallback(() => {
|
|
241
389
|
if (!anchorElement || !popperRef.current) {
|
|
242
390
|
return;
|
|
243
391
|
}
|
|
244
392
|
const anchorRect = anchorElement.getBoundingClientRect();
|
|
245
393
|
const popperRect = popperRef.current.getBoundingClientRect();
|
|
246
|
-
const
|
|
247
|
-
if (
|
|
248
|
-
setActualPlacement(
|
|
249
|
-
setStyle(placements.style);
|
|
394
|
+
const { style, placement, found } = getPreferredPlacements(anchorPos, anchorRect, popperRect);
|
|
395
|
+
if (found) {
|
|
396
|
+
setActualPlacement(placement);
|
|
250
397
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
return 'top center';
|
|
265
|
-
}, []);
|
|
266
|
-
// Очистка при закрытии
|
|
398
|
+
setStyle(style);
|
|
399
|
+
}, [anchorElement, anchorPos, getPreferredPlacements]);
|
|
400
|
+
// const getTransformOrigin = React.useCallback((placement: AnchorPos): string => {
|
|
401
|
+
// if (placement.startsWith('top')) return 'bottom center';
|
|
402
|
+
// if (placement.startsWith('bottom')) return 'top center';
|
|
403
|
+
// if (placement.startsWith('left')) return 'right center';
|
|
404
|
+
// if (placement.startsWith('right')) return 'left center';
|
|
405
|
+
//
|
|
406
|
+
// return 'top center';
|
|
407
|
+
// }, []);
|
|
408
|
+
/**
|
|
409
|
+
* Open / Close popper logic
|
|
410
|
+
*/
|
|
267
411
|
react_1.default.useEffect(() => {
|
|
268
412
|
if (!isOpen) {
|
|
269
413
|
setIsVisible(false);
|
|
270
414
|
setStyle(null);
|
|
271
415
|
}
|
|
272
416
|
else if (anchorElement && isOpen && popperRef.current) {
|
|
273
|
-
|
|
274
|
-
const popperRect = popperRef.current.getBoundingClientRect();
|
|
275
|
-
const placements = getPreferredPlacements(actualPlacement, anchorRect, popperRect);
|
|
276
|
-
if (placements.found) {
|
|
277
|
-
setActualPlacement(placements.placement);
|
|
278
|
-
setStyle(placements.style);
|
|
279
|
-
}
|
|
280
|
-
else {
|
|
281
|
-
setStyle(placements.style);
|
|
282
|
-
}
|
|
417
|
+
calculatePosition();
|
|
283
418
|
setIsVisible(true);
|
|
284
419
|
}
|
|
285
|
-
}, [actualPlacement, anchorElement, getPreferredPlacements, isOpen]);
|
|
286
|
-
|
|
420
|
+
}, [actualPlacement, anchorElement, getPreferredPlacements, isOpen, calculatePosition]);
|
|
421
|
+
/**
|
|
422
|
+
* The anchor element changes
|
|
423
|
+
*/
|
|
287
424
|
react_1.default.useEffect(() => {
|
|
288
|
-
|
|
289
|
-
if (isOpen) {
|
|
290
|
-
calculatePosition();
|
|
291
|
-
}
|
|
292
|
-
};
|
|
293
|
-
window.addEventListener('scroll', handleEvent);
|
|
294
|
-
return () => {
|
|
295
|
-
window.removeEventListener('scroll', handleEvent);
|
|
296
|
-
};
|
|
297
|
-
}, [calculatePosition, isOpen]);
|
|
298
|
-
// Отслеживание мутаций anchor элемента
|
|
299
|
-
react_1.default.useEffect(() => {
|
|
300
|
-
if (!isOpen || !anchorElement)
|
|
425
|
+
if (!isOpen || !anchorElement) {
|
|
301
426
|
return;
|
|
427
|
+
}
|
|
302
428
|
const observer = new MutationObserver(() => {
|
|
303
429
|
window.requestAnimationFrame(() => {
|
|
430
|
+
// console.log('The anchor element was change');
|
|
304
431
|
calculatePosition();
|
|
305
432
|
});
|
|
306
433
|
});
|
|
@@ -314,13 +441,61 @@ const usePopper = ({ anchorElement, anchorPos = 'auto', positionStrategy = 'abso
|
|
|
314
441
|
observer.disconnect();
|
|
315
442
|
};
|
|
316
443
|
}, [isOpen, anchorElement, calculatePosition]);
|
|
444
|
+
/**
|
|
445
|
+
* On scroll - recalculate position
|
|
446
|
+
*/
|
|
447
|
+
react_1.default.useEffect(() => {
|
|
448
|
+
if (!isOpen || !scrollableAncestor) {
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
const handleScroll = () => {
|
|
452
|
+
window.requestAnimationFrame(() => {
|
|
453
|
+
calculatePosition();
|
|
454
|
+
});
|
|
455
|
+
};
|
|
456
|
+
// parent scroll listener
|
|
457
|
+
if (scrollableAncestor instanceof HTMLElement) {
|
|
458
|
+
scrollableAncestor.addEventListener('scroll', handleScroll, { passive: true });
|
|
459
|
+
}
|
|
460
|
+
else if (scrollableAncestor === window) {
|
|
461
|
+
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
462
|
+
}
|
|
463
|
+
// eslint-disable-next-line consistent-return
|
|
464
|
+
return () => {
|
|
465
|
+
if (scrollableAncestor instanceof HTMLElement) {
|
|
466
|
+
scrollableAncestor.removeEventListener('scroll', handleScroll);
|
|
467
|
+
}
|
|
468
|
+
else if (scrollableAncestor === window) {
|
|
469
|
+
window.removeEventListener('scroll', handleScroll);
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
}, [isOpen, scrollableAncestor, calculatePosition]);
|
|
473
|
+
/**
|
|
474
|
+
* On window resize - recalculate position
|
|
475
|
+
*/
|
|
476
|
+
react_1.default.useEffect(() => {
|
|
477
|
+
if (!isOpen) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
const handleResize = () => {
|
|
481
|
+
window.requestAnimationFrame(() => {
|
|
482
|
+
calculatePosition();
|
|
483
|
+
});
|
|
484
|
+
};
|
|
485
|
+
// parent scroll listener
|
|
486
|
+
window.addEventListener('resize', handleResize);
|
|
487
|
+
// eslint-disable-next-line consistent-return
|
|
488
|
+
return () => {
|
|
489
|
+
window.removeEventListener('resize', handleResize);
|
|
490
|
+
};
|
|
491
|
+
}, [calculatePosition, isOpen]);
|
|
317
492
|
return {
|
|
318
493
|
actualPlacement,
|
|
319
494
|
style,
|
|
320
495
|
popperRef,
|
|
321
496
|
isVisible,
|
|
497
|
+
scrollableAncestor,
|
|
322
498
|
calculatePosition,
|
|
323
|
-
getTransformOrigin,
|
|
324
499
|
setActualPlacement,
|
|
325
500
|
setStyle,
|
|
326
501
|
setIsVisible,
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { ButtonProps } from '../Button';
|
|
3
|
+
import { AnchorPos } from '../Menu';
|
|
3
4
|
export type SelectboxButtonProps = Omit<ButtonProps, 'type'> & {
|
|
4
5
|
readonly fullWidth?: boolean;
|
|
5
6
|
readonly error?: boolean;
|
|
7
|
+
readonly anchorPos?: AnchorPos;
|
|
8
|
+
readonly isOpen?: boolean;
|
|
6
9
|
};
|
|
7
10
|
declare const _default: React.ForwardRefExoticComponent<Omit<ButtonProps, "type"> & {
|
|
8
11
|
readonly fullWidth?: boolean | undefined;
|
|
9
12
|
readonly error?: boolean | undefined;
|
|
13
|
+
readonly anchorPos?: import("../Popper").AnchorPos | undefined;
|
|
14
|
+
readonly isOpen?: boolean | undefined;
|
|
10
15
|
} & React.RefAttributes<HTMLButtonElement>>;
|
|
11
16
|
export default _default;
|
|
@@ -41,25 +41,65 @@ const React = __importStar(require("react"));
|
|
|
41
41
|
const styled_1 = __importDefault(require("@emotion/styled"));
|
|
42
42
|
const Button_1 = __importDefault(require("../Button"));
|
|
43
43
|
const ButtonTextWrapper_1 = __importDefault(require("../Button/ButtonTextWrapper"));
|
|
44
|
+
const react_1 = require("@emotion/react");
|
|
44
45
|
const StyledSelectboxButton = (0, styled_1.default)(Button_1.default) `
|
|
45
46
|
flex: 1;
|
|
46
47
|
padding: 1em 1.2em;
|
|
47
48
|
font-size: 1em;
|
|
48
49
|
background: none;
|
|
49
|
-
border-radius: inherit;
|
|
50
|
-
margin: 0;
|
|
51
|
-
border: 0;
|
|
52
50
|
width: 100%;
|
|
51
|
+
border-width: 1px;
|
|
52
|
+
border-style: solid;
|
|
53
|
+
box-shadow: none;
|
|
54
|
+
&:hover {
|
|
55
|
+
box-shadow: none;
|
|
56
|
+
}
|
|
57
|
+
border-color: ${({ theme }) => theme.isDark
|
|
58
|
+
? theme.color.textPrimary.darken(100).toString()
|
|
59
|
+
: theme.color.textPrimary.lighten(150).toString()};
|
|
60
|
+
${props => props.$error &&
|
|
61
|
+
(0, react_1.css) `
|
|
62
|
+
border-color: ${props.theme.color.error.toString()};
|
|
63
|
+
color: ${props.theme.color.error.toString()};
|
|
64
|
+
&:focus {
|
|
65
|
+
border-color: ${props.theme.color.error.lighten(0.6).toString()};
|
|
66
|
+
}
|
|
67
|
+
`}
|
|
68
|
+
transition: none;
|
|
69
|
+
${({ theme, $isOpen, $anchorPos }) => $isOpen &&
|
|
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
|
+
`};
|
|
53
95
|
`;
|
|
54
96
|
const StyledButtonTextWrapper = (0, styled_1.default)(ButtonTextWrapper_1.default) `
|
|
55
97
|
flex: 1;
|
|
56
98
|
`;
|
|
57
99
|
const SelectboxButton = (props, ref) => {
|
|
58
|
-
const { children, fullWidth, error } = props, nativeProps = __rest(props, ["children", "fullWidth", "error"]);
|
|
59
|
-
return (React.createElement(StyledSelectboxButton, Object.assign({ "$fullWidth": fullWidth, "$error": error }, nativeProps, { overrides: {
|
|
60
|
-
TextWrapper:
|
|
61
|
-
return React.createElement(StyledButtonTextWrapper, Object.assign({}, p, { ref: r }));
|
|
62
|
-
}),
|
|
100
|
+
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, "$anchorPos": anchorPos, overrides: {
|
|
102
|
+
TextWrapper: StyledButtonTextWrapper,
|
|
63
103
|
}, ref: ref }), children));
|
|
64
104
|
};
|
|
65
105
|
exports.default = React.forwardRef(SelectboxButton);
|
|
@@ -2,9 +2,11 @@ import * as React from 'react';
|
|
|
2
2
|
export type SelectboxButtonWrapperProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
3
3
|
readonly fullWidth?: boolean;
|
|
4
4
|
readonly error?: boolean;
|
|
5
|
+
readonly isOpen: boolean;
|
|
5
6
|
};
|
|
6
7
|
declare const _default: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & {
|
|
7
8
|
readonly fullWidth?: boolean | undefined;
|
|
8
9
|
readonly error?: boolean | undefined;
|
|
10
|
+
readonly isOpen: boolean;
|
|
9
11
|
} & React.RefAttributes<HTMLDivElement>>;
|
|
10
12
|
export default _default;
|