@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.
Files changed (143) hide show
  1. package/Autocomplete/AutocompleteContainer.d.ts +33 -6
  2. package/Autocomplete/AutocompleteContainer.js +48 -36
  3. package/Autocomplete/AutocompleteItem.d.ts +1 -1
  4. package/Autocomplete/AutocompleteTextField.d.ts +3 -0
  5. package/Autocomplete/AutocompleteTextField.js +63 -1
  6. package/Autocomplete/OverrideMenuList.d.ts +4 -0
  7. package/Autocomplete/OverrideMenuList.js +39 -0
  8. package/Autocomplete/context.d.ts +9 -1
  9. package/Autocomplete/context.js +15 -1
  10. package/Autocomplete/index.js +9 -10
  11. package/Autocomplete2/Autocomplete2.d.ts +12 -0
  12. package/Autocomplete2/Autocomplete2.js +83 -0
  13. package/Autocomplete2/Autocomplete2TextField.d.ts +7 -0
  14. package/Autocomplete2/Autocomplete2TextField.js +39 -0
  15. package/Autocomplete2/index.d.ts +2 -0
  16. package/Autocomplete2/index.js +7 -0
  17. package/Avatar/index.js +1 -1
  18. package/Badge/BadgeDeleteButton.js +6 -6
  19. package/Badge/BadgeOutlined.js +2 -2
  20. package/Badge/BadgeStandard.js +1 -1
  21. package/Button/ButtonOutlined.js +1 -1
  22. package/Button/ButtonPlain.js +1 -1
  23. package/Button/ButtonStandard.js +4 -11
  24. package/Calendar/CalendarCell.js +6 -2
  25. package/Calendar/CalendarComponent.d.ts +226 -0
  26. package/Calendar/CalendarComponent.js +572 -0
  27. package/Calendar/CalendarControlButton.js +16 -9
  28. package/Calendar/CalendarDayBadge.js +1 -1
  29. package/Calendar/CalendarEmptyCell.d.ts +2 -2
  30. package/Calendar/CalendarEmptyCell.js +8 -3
  31. package/Calendar/CalendarHeading.js +2 -1
  32. package/Calendar/CalendarSubheading.js +3 -2
  33. package/Calendar/CalendarWeekDayCell.d.ts +10 -0
  34. package/Calendar/CalendarWeekDayCell.js +32 -0
  35. package/Calendar/CalendarWeekDayWeekNumber.d.ts +7 -0
  36. package/Calendar/CalendarWeekDayWeekNumber.js +38 -0
  37. package/Calendar/CalendarWeekRow.d.ts +2 -2
  38. package/Calendar/CalendarWeekRow.js +2 -1
  39. package/Calendar/CalendarWeekRowButton.d.ts +13 -0
  40. package/Calendar/CalendarWeekRowButton.js +31 -0
  41. package/Calendar/CalendarYearsSelector.d.ts +39 -1
  42. package/Calendar/CalendarYearsSelector.js +52 -16
  43. package/Calendar/IconChevronDown.d.ts +3 -0
  44. package/Calendar/IconChevronDown.js +31 -0
  45. package/Calendar/index.d.ts +7 -224
  46. package/Calendar/index.js +19 -281
  47. package/Calendar/use-calendar.d.ts +1 -1
  48. package/Color/Color.d.ts +138 -0
  49. package/Color/Color.js +324 -0
  50. package/Color/ColorGenerator.d.ts +29 -0
  51. package/Color/ColorGenerator.js +141 -0
  52. package/Color/ColorParser.d.ts +20 -0
  53. package/Color/ColorParser.js +284 -0
  54. package/Color/index.d.ts +4 -122
  55. package/Color/index.js +18 -473
  56. package/CountryFlags/SwiperOverview.d.ts +257 -0
  57. package/CountryFlags/SwiperOverview.js +524 -0
  58. package/DatePicker/index.d.ts +2 -17
  59. package/DatePicker/index.js +47 -61
  60. package/DatePicker/useDatePickerFormat.js +5 -5
  61. package/ErrorBoundary/Fallback.d.ts +3 -0
  62. package/ErrorBoundary/Fallback.js +51 -0
  63. package/ErrorBoundary/RenderError.d.ts +6 -0
  64. package/ErrorBoundary/RenderError.js +80 -0
  65. package/ErrorBoundary/index.d.ts +7 -11
  66. package/ErrorBoundary/index.js +4 -51
  67. package/LoadingIndicator/Spinner.d.ts +5 -3
  68. package/LoadingIndicator/Spinner.js +61 -32
  69. package/LoadingIndicator/index.d.ts +3 -3
  70. package/LoadingIndicator/index.js +15 -2
  71. package/MaskedField/index.d.ts +2 -1
  72. package/MaskedField/index.js +52 -43
  73. package/MaskedField/useMasked.js +7 -5
  74. package/Menu/AutoHeightWrapper.d.ts +6 -0
  75. package/Menu/AutoHeightWrapper.js +43 -0
  76. package/Menu/MeasuredItem.d.ts +10 -0
  77. package/Menu/MeasuredItem.js +51 -0
  78. package/Menu/MenuContainer.d.ts +51 -18
  79. package/Menu/MenuContainer.js +19 -12
  80. package/Menu/MenuItem.d.ts +24 -7
  81. package/Menu/MenuItem.js +13 -6
  82. package/Menu/MenuList.d.ts +4 -2
  83. package/Menu/MenuList.js +26 -6
  84. package/Menu/VirtualizedItem.d.ts +22 -0
  85. package/Menu/VirtualizedItem.js +69 -0
  86. package/Menu/VirtualizedList.d.ts +18 -0
  87. package/Menu/VirtualizedList.js +28 -0
  88. package/Menu/VirtualizedListComponent.d.ts +20 -0
  89. package/Menu/VirtualizedListComponent.js +305 -0
  90. package/Menu/index.d.ts +3 -1
  91. package/Menu/index.js +2 -0
  92. package/Popper/Popper.d.ts +7 -80
  93. package/Popper/Popper.js +26 -12
  94. package/Popper/PopperContainer.js +1 -1
  95. package/Popper/usePopprer.d.ts +88 -3
  96. package/Popper/usePopprer.js +240 -65
  97. package/Selectbox/SelectboxButton.d.ts +5 -0
  98. package/Selectbox/SelectboxButton.js +48 -8
  99. package/Selectbox/SelectboxButtonWrapper.d.ts +2 -0
  100. package/Selectbox/SelectboxButtonWrapper.js +1 -17
  101. package/Selectbox/SelectboxMenu.d.ts +12 -0
  102. package/Selectbox/SelectboxMenu.js +50 -0
  103. package/Selectbox/index.d.ts +5 -5
  104. package/Selectbox/index.js +115 -97
  105. package/Surface/index.js +1 -1
  106. package/Swiper/Swiper.d.ts +57 -0
  107. package/Swiper/Swiper.js +412 -0
  108. package/Swiper/SwiperContainer.d.ts +7 -0
  109. package/Swiper/SwiperContainer.js +15 -0
  110. package/Swiper/SwiperSlide.d.ts +12 -0
  111. package/Swiper/SwiperSlide.js +58 -0
  112. package/Swiper/SwiperSlidesRenderer.d.ts +11 -0
  113. package/Swiper/SwiperSlidesRenderer.js +74 -0
  114. package/Swiper/SwiperTrack.d.ts +18 -0
  115. package/Swiper/SwiperTrack.js +55 -0
  116. package/Swiper/SwiperWrapper.d.ts +16 -0
  117. package/Swiper/SwiperWrapper.js +60 -0
  118. package/Swiper/index.d.ts +7 -0
  119. package/Swiper/index.js +26 -0
  120. package/Switch/SwitchDot.js +1 -1
  121. package/Switch/SwitchTrack.js +1 -1
  122. package/TextField/TextFieldContainer.d.ts +2 -2
  123. package/TextField/TextFieldContainer.js +5 -3
  124. package/TextField/TextFieldErrorText.d.ts +2 -2
  125. package/TextField/TextFieldErrorText.js +5 -4
  126. package/TextField/TextFieldIconWrapper.d.ts +3 -3
  127. package/TextField/TextFieldIconWrapper.js +5 -3
  128. package/TextField/TextFieldInput.d.ts +2 -2
  129. package/TextField/TextFieldInput.js +6 -4
  130. package/TextField/TextFieldInputWrapper.d.ts +2 -2
  131. package/TextField/TextFieldInputWrapper.js +5 -3
  132. package/TextField/TextFieldLabel.d.ts +2 -2
  133. package/TextField/TextFieldLabel.js +5 -3
  134. package/TextField/TextFieldLabelAsterisk.d.ts +2 -2
  135. package/TextField/TextFieldLabelAsterisk.js +5 -3
  136. package/TextField/index.d.ts +8 -1
  137. package/TextField/index.js +49 -22
  138. package/ThemeProvider/createTheme.js +2 -2
  139. package/package.json +1 -1
  140. package/Calendar/reducer.d.ts +0 -17
  141. package/Calendar/reducer.js +0 -50
  142. /package/Calendar/{IconChevronRight.d.ts → CalendarIconNext.d.ts} +0 -0
  143. /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 = 'absolute' } = props, nativeProps = __rest(props, ["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);
@@ -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?: HTMLElement | null;
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 getTransformOrigin: (placement: AnchorPos) => string;
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;
@@ -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
- const usePopper = ({ anchorElement, anchorPos = 'auto', positionStrategy = 'absolute', autoFlip = true, offset = 0, viewportMargin = 30, isOpen = false, }) => {
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
- // Синхронизация actualPlacement с anchorPos
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
- // Вычисляем позицию относительно viewport
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 === 'left' || modifier === 'start') {
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 === 'left' || modifier === 'start') {
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
- // Возвращаем viewport координаты для проверки или финальные координаты
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
- // Обработка auto placement
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 placements = getPreferredPlacements(actualPlacement, anchorRect, popperRect);
247
- if (placements.found) {
248
- setActualPlacement(placements.placement);
249
- setStyle(placements.style);
394
+ const { style, placement, found } = getPreferredPlacements(anchorPos, anchorRect, popperRect);
395
+ if (found) {
396
+ setActualPlacement(placement);
250
397
  }
251
- else {
252
- setStyle(placements.style);
253
- }
254
- }, [actualPlacement, anchorElement, getPreferredPlacements]);
255
- const getTransformOrigin = react_1.default.useCallback((placement) => {
256
- if (placement.startsWith('top'))
257
- return 'bottom center';
258
- if (placement.startsWith('bottom'))
259
- return 'top center';
260
- if (placement.startsWith('left'))
261
- return 'right center';
262
- if (placement.startsWith('right'))
263
- return 'left center';
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
- const anchorRect = anchorElement.getBoundingClientRect();
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
- const handleEvent = () => {
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: React.forwardRef(function SelectboxButtonTextWrapper(p, r) {
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;