@alfalab/core-components-select 19.1.10 → 19.2.0-alfasans

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 (257) hide show
  1. package/components/arrow/index.css +4 -4
  2. package/components/arrow/index.module.css.js +1 -1
  3. package/components/base-checkmark/index.css +9 -9
  4. package/components/base-checkmark/index.module.css.js +1 -1
  5. package/components/base-option/index.css +19 -17
  6. package/components/base-option/index.module.css.js +1 -1
  7. package/components/base-select/Component.js +36 -49
  8. package/components/base-select/Component.js.map +1 -1
  9. package/components/base-select/index.css +70 -20
  10. package/components/base-select/index.module.css.js +1 -1
  11. package/components/base-select/index.module.css.js.map +1 -1
  12. package/components/base-select/mobile.css +12 -11
  13. package/components/base-select/mobile.module.css.js +1 -1
  14. package/components/base-select/mobile.module.css.js.map +1 -1
  15. package/components/checkmark/index.css +11 -11
  16. package/components/checkmark/index.module.css.js +1 -1
  17. package/components/checkmark-mobile/index.css +6 -6
  18. package/components/checkmark-mobile/index.module.css.js +1 -1
  19. package/components/clear-button/index.css +4 -4
  20. package/components/clear-button/index.module.css.js +1 -1
  21. package/components/field/index.css +22 -18
  22. package/components/field/index.module.css.js +1 -1
  23. package/components/footer/index.css +4 -4
  24. package/components/footer/index.module.css.js +1 -1
  25. package/components/optgroup/index.css +8 -7
  26. package/components/optgroup/index.module.css.js +1 -1
  27. package/components/option/desktop/index.css +28 -22
  28. package/components/option/desktop/index.module.css.js +1 -1
  29. package/components/option/desktop/index.module.css.js.map +1 -1
  30. package/components/option/mobile/index.css +25 -19
  31. package/components/option/mobile/index.module.css.js +1 -1
  32. package/components/option/mobile/index.module.css.js.map +1 -1
  33. package/components/options-list/Component.d.ts +4 -0
  34. package/components/options-list/Component.js +32 -53
  35. package/components/options-list/Component.js.map +1 -1
  36. package/components/options-list/index.css +13 -33
  37. package/components/options-list/index.module.css.js +1 -1
  38. package/components/options-list/index.module.css.js.map +1 -1
  39. package/components/search/index.css +1 -1
  40. package/components/search/index.module.css.js +1 -1
  41. package/components/virtual-options-list/Component.d.ts +4 -0
  42. package/components/virtual-options-list/Component.js +61 -48
  43. package/components/virtual-options-list/Component.js.map +1 -1
  44. package/cssm/components/base-option/index.module.css +5 -3
  45. package/cssm/components/base-select/Component.js +36 -49
  46. package/cssm/components/base-select/Component.js.map +1 -1
  47. package/cssm/components/base-select/index.module.css +61 -11
  48. package/cssm/components/base-select/mobile.module.css +1 -0
  49. package/cssm/components/field/index.module.css +9 -5
  50. package/cssm/components/optgroup/index.module.css +4 -3
  51. package/cssm/components/option/desktop/index.module.css +9 -3
  52. package/cssm/components/option/mobile/index.module.css +9 -3
  53. package/cssm/components/options-list/Component.d.ts +4 -0
  54. package/cssm/components/options-list/Component.js +32 -53
  55. package/cssm/components/options-list/Component.js.map +1 -1
  56. package/cssm/components/options-list/index.module.css +6 -26
  57. package/cssm/components/virtual-options-list/Component.d.ts +4 -0
  58. package/cssm/components/virtual-options-list/Component.js +61 -48
  59. package/cssm/components/virtual-options-list/Component.js.map +1 -1
  60. package/cssm/presets/useSelectWithApply/hook.d.ts +12 -0
  61. package/cssm/presets/useSelectWithApply/options-list-with-apply/Component.d.ts +4 -0
  62. package/cssm/presets/useSelectWithApply/options-list-with-apply/footer/index.module.css +0 -5
  63. package/cssm/presets/useSelectWithApply/options-list-with-apply/header/index.module.css +9 -6
  64. package/cssm/shared/index.js +0 -1
  65. package/cssm/shared/index.js.map +1 -1
  66. package/cssm/typings.d.ts +21 -1
  67. package/cssm/utils.d.ts +1 -6
  68. package/cssm/utils.js +19 -28
  69. package/cssm/utils.js.map +1 -1
  70. package/esm/components/arrow/index.css +4 -4
  71. package/esm/components/arrow/index.module.css.js +1 -1
  72. package/esm/components/base-checkmark/index.css +9 -9
  73. package/esm/components/base-checkmark/index.module.css.js +1 -1
  74. package/esm/components/base-option/index.css +19 -17
  75. package/esm/components/base-option/index.module.css.js +1 -1
  76. package/esm/components/base-select/Component.js +37 -50
  77. package/esm/components/base-select/Component.js.map +1 -1
  78. package/esm/components/base-select/index.css +70 -20
  79. package/esm/components/base-select/index.module.css.js +1 -1
  80. package/esm/components/base-select/index.module.css.js.map +1 -1
  81. package/esm/components/base-select/mobile.css +12 -11
  82. package/esm/components/base-select/mobile.module.css.js +1 -1
  83. package/esm/components/base-select/mobile.module.css.js.map +1 -1
  84. package/esm/components/checkmark/index.css +11 -11
  85. package/esm/components/checkmark/index.module.css.js +1 -1
  86. package/esm/components/checkmark-mobile/index.css +6 -6
  87. package/esm/components/checkmark-mobile/index.module.css.js +1 -1
  88. package/esm/components/clear-button/index.css +4 -4
  89. package/esm/components/clear-button/index.module.css.js +1 -1
  90. package/esm/components/field/index.css +22 -18
  91. package/esm/components/field/index.module.css.js +1 -1
  92. package/esm/components/footer/index.css +4 -4
  93. package/esm/components/footer/index.module.css.js +1 -1
  94. package/esm/components/optgroup/index.css +8 -7
  95. package/esm/components/optgroup/index.module.css.js +1 -1
  96. package/esm/components/option/desktop/index.css +28 -22
  97. package/esm/components/option/desktop/index.module.css.js +1 -1
  98. package/esm/components/option/desktop/index.module.css.js.map +1 -1
  99. package/esm/components/option/mobile/index.css +25 -19
  100. package/esm/components/option/mobile/index.module.css.js +1 -1
  101. package/esm/components/option/mobile/index.module.css.js.map +1 -1
  102. package/esm/components/options-list/Component.d.ts +4 -0
  103. package/esm/components/options-list/Component.js +33 -54
  104. package/esm/components/options-list/Component.js.map +1 -1
  105. package/esm/components/options-list/index.css +13 -33
  106. package/esm/components/options-list/index.module.css.js +1 -1
  107. package/esm/components/options-list/index.module.css.js.map +1 -1
  108. package/esm/components/search/index.css +1 -1
  109. package/esm/components/search/index.module.css.js +1 -1
  110. package/esm/components/virtual-options-list/Component.d.ts +4 -0
  111. package/esm/components/virtual-options-list/Component.js +63 -49
  112. package/esm/components/virtual-options-list/Component.js.map +1 -1
  113. package/esm/mobile/modal.mobile.css +1 -1
  114. package/esm/mobile/modal.mobile.module.css.js +1 -1
  115. package/esm/presets/useSelectWithApply/hook.d.ts +12 -0
  116. package/esm/presets/useSelectWithApply/options-list-with-apply/Component.d.ts +4 -0
  117. package/esm/presets/useSelectWithApply/options-list-with-apply/footer/index.css +2 -7
  118. package/esm/presets/useSelectWithApply/options-list-with-apply/footer/index.module.css.js +1 -1
  119. package/esm/presets/useSelectWithApply/options-list-with-apply/footer/index.module.css.js.map +1 -1
  120. package/esm/presets/useSelectWithApply/options-list-with-apply/header/index.css +12 -9
  121. package/esm/presets/useSelectWithApply/options-list-with-apply/header/index.module.css.js +1 -1
  122. package/esm/presets/useSelectWithApply/options-list-with-apply/header/index.module.css.js.map +1 -1
  123. package/esm/presets/useSelectWithLoading/index.css +1 -1
  124. package/esm/presets/useSelectWithLoading/index.module.css.js +1 -1
  125. package/esm/shared/index.js +1 -1
  126. package/esm/typings.d.ts +21 -1
  127. package/esm/utils.d.ts +1 -6
  128. package/esm/utils.js +21 -29
  129. package/esm/utils.js.map +1 -1
  130. package/mobile/modal.mobile.css +1 -1
  131. package/mobile/modal.mobile.module.css.js +1 -1
  132. package/modern/components/arrow/index.css +4 -4
  133. package/modern/components/arrow/index.module.css.js +1 -1
  134. package/modern/components/base-checkmark/index.css +9 -9
  135. package/modern/components/base-checkmark/index.module.css.js +1 -1
  136. package/modern/components/base-option/index.css +19 -17
  137. package/modern/components/base-option/index.module.css.js +1 -1
  138. package/modern/components/base-select/Component.js +34 -47
  139. package/modern/components/base-select/Component.js.map +1 -1
  140. package/modern/components/base-select/index.css +70 -20
  141. package/modern/components/base-select/index.module.css.js +1 -1
  142. package/modern/components/base-select/index.module.css.js.map +1 -1
  143. package/modern/components/base-select/mobile.css +12 -11
  144. package/modern/components/base-select/mobile.module.css.js +1 -1
  145. package/modern/components/base-select/mobile.module.css.js.map +1 -1
  146. package/modern/components/checkmark/index.css +11 -11
  147. package/modern/components/checkmark/index.module.css.js +1 -1
  148. package/modern/components/checkmark-mobile/index.css +6 -6
  149. package/modern/components/checkmark-mobile/index.module.css.js +1 -1
  150. package/modern/components/clear-button/index.css +4 -4
  151. package/modern/components/clear-button/index.module.css.js +1 -1
  152. package/modern/components/field/index.css +22 -18
  153. package/modern/components/field/index.module.css.js +1 -1
  154. package/modern/components/footer/index.css +4 -4
  155. package/modern/components/footer/index.module.css.js +1 -1
  156. package/modern/components/optgroup/index.css +8 -7
  157. package/modern/components/optgroup/index.module.css.js +1 -1
  158. package/modern/components/option/desktop/index.css +28 -22
  159. package/modern/components/option/desktop/index.module.css.js +1 -1
  160. package/modern/components/option/desktop/index.module.css.js.map +1 -1
  161. package/modern/components/option/mobile/index.css +25 -19
  162. package/modern/components/option/mobile/index.module.css.js +1 -1
  163. package/modern/components/option/mobile/index.module.css.js.map +1 -1
  164. package/modern/components/options-list/Component.d.ts +4 -0
  165. package/modern/components/options-list/Component.js +29 -49
  166. package/modern/components/options-list/Component.js.map +1 -1
  167. package/modern/components/options-list/index.css +13 -33
  168. package/modern/components/options-list/index.module.css.js +1 -1
  169. package/modern/components/options-list/index.module.css.js.map +1 -1
  170. package/modern/components/search/index.css +1 -1
  171. package/modern/components/search/index.module.css.js +1 -1
  172. package/modern/components/virtual-options-list/Component.d.ts +4 -0
  173. package/modern/components/virtual-options-list/Component.js +59 -45
  174. package/modern/components/virtual-options-list/Component.js.map +1 -1
  175. package/modern/mobile/modal.mobile.css +1 -1
  176. package/modern/mobile/modal.mobile.module.css.js +1 -1
  177. package/modern/presets/useSelectWithApply/hook.d.ts +12 -0
  178. package/modern/presets/useSelectWithApply/options-list-with-apply/Component.d.ts +4 -0
  179. package/modern/presets/useSelectWithApply/options-list-with-apply/footer/index.css +2 -7
  180. package/modern/presets/useSelectWithApply/options-list-with-apply/footer/index.module.css.js +1 -1
  181. package/modern/presets/useSelectWithApply/options-list-with-apply/footer/index.module.css.js.map +1 -1
  182. package/modern/presets/useSelectWithApply/options-list-with-apply/header/index.css +12 -9
  183. package/modern/presets/useSelectWithApply/options-list-with-apply/header/index.module.css.js +1 -1
  184. package/modern/presets/useSelectWithApply/options-list-with-apply/header/index.module.css.js.map +1 -1
  185. package/modern/presets/useSelectWithLoading/index.css +1 -1
  186. package/modern/presets/useSelectWithLoading/index.module.css.js +1 -1
  187. package/modern/shared/index.js +1 -1
  188. package/modern/typings.d.ts +21 -1
  189. package/modern/utils.d.ts +1 -6
  190. package/modern/utils.js +21 -29
  191. package/modern/utils.js.map +1 -1
  192. package/moderncssm/components/base-option/index.module.css +2 -1
  193. package/moderncssm/components/base-select/Component.js +34 -47
  194. package/moderncssm/components/base-select/Component.js.map +1 -1
  195. package/moderncssm/components/base-select/index.module.css +67 -9
  196. package/moderncssm/components/base-select/mobile.module.css +1 -0
  197. package/moderncssm/components/field/index.module.css +6 -3
  198. package/moderncssm/components/optgroup/index.module.css +1 -1
  199. package/moderncssm/components/option/desktop/index.module.css +5 -1
  200. package/moderncssm/components/option/mobile/index.module.css +5 -1
  201. package/moderncssm/components/options-list/Component.d.ts +4 -0
  202. package/moderncssm/components/options-list/Component.js +29 -49
  203. package/moderncssm/components/options-list/Component.js.map +1 -1
  204. package/moderncssm/components/options-list/index.module.css +7 -32
  205. package/moderncssm/components/virtual-options-list/Component.d.ts +4 -0
  206. package/moderncssm/components/virtual-options-list/Component.js +59 -45
  207. package/moderncssm/components/virtual-options-list/Component.js.map +1 -1
  208. package/moderncssm/presets/useSelectWithApply/hook.d.ts +12 -0
  209. package/moderncssm/presets/useSelectWithApply/options-list-with-apply/Component.d.ts +4 -0
  210. package/moderncssm/presets/useSelectWithApply/options-list-with-apply/footer/index.module.css +0 -4
  211. package/moderncssm/presets/useSelectWithApply/options-list-with-apply/header/index.module.css +8 -6
  212. package/moderncssm/shared/index.js +1 -1
  213. package/moderncssm/typings.d.ts +21 -1
  214. package/moderncssm/utils.d.ts +1 -6
  215. package/moderncssm/utils.js +21 -29
  216. package/moderncssm/utils.js.map +1 -1
  217. package/package.json +15 -16
  218. package/presets/useSelectWithApply/hook.d.ts +12 -0
  219. package/presets/useSelectWithApply/options-list-with-apply/Component.d.ts +4 -0
  220. package/presets/useSelectWithApply/options-list-with-apply/footer/index.css +2 -7
  221. package/presets/useSelectWithApply/options-list-with-apply/footer/index.module.css.js +1 -1
  222. package/presets/useSelectWithApply/options-list-with-apply/footer/index.module.css.js.map +1 -1
  223. package/presets/useSelectWithApply/options-list-with-apply/header/index.css +12 -9
  224. package/presets/useSelectWithApply/options-list-with-apply/header/index.module.css.js +1 -1
  225. package/presets/useSelectWithApply/options-list-with-apply/header/index.module.css.js.map +1 -1
  226. package/presets/useSelectWithLoading/index.css +1 -1
  227. package/presets/useSelectWithLoading/index.module.css.js +1 -1
  228. package/shared/index.js +0 -1
  229. package/shared/index.js.map +1 -1
  230. package/src/components/base-select/Component.tsx +41 -55
  231. package/src/components/base-select/index.module.css +60 -8
  232. package/src/components/base-select/mobile.module.css +1 -0
  233. package/src/components/option/index.module.css +4 -0
  234. package/src/components/options-list/Component.tsx +54 -97
  235. package/src/components/options-list/index.module.css +7 -31
  236. package/src/components/virtual-options-list/Component.tsx +95 -72
  237. package/src/presets/useSelectWithApply/options-list-with-apply/footer/index.module.css +0 -4
  238. package/src/presets/useSelectWithApply/options-list-with-apply/header/index.module.css +4 -4
  239. package/src/typings.ts +26 -0
  240. package/src/utils.ts +30 -34
  241. package/src/vars.css +3 -0
  242. package/typings.d.ts +21 -1
  243. package/utils.d.ts +1 -6
  244. package/utils.js +19 -28
  245. package/utils.js.map +1 -1
  246. package/components/virtual-options-list/index.css +0 -83
  247. package/components/virtual-options-list/index.module.css.js +0 -8
  248. package/components/virtual-options-list/index.module.css.js.map +0 -1
  249. package/cssm/components/virtual-options-list/index.module.css +0 -83
  250. package/esm/components/virtual-options-list/index.css +0 -83
  251. package/esm/components/virtual-options-list/index.module.css.js +0 -6
  252. package/esm/components/virtual-options-list/index.module.css.js.map +0 -1
  253. package/modern/components/virtual-options-list/index.css +0 -83
  254. package/modern/components/virtual-options-list/index.module.css.js +0 -6
  255. package/modern/components/virtual-options-list/index.module.css.js.map +0 -1
  256. package/moderncssm/components/virtual-options-list/index.module.css +0 -81
  257. package/src/components/virtual-options-list/index.module.css +0 -77
@@ -23,13 +23,10 @@
23
23
 
24
24
  .optionsListWrapper {
25
25
  position: relative;
26
- padding-top: var(--select-options-list-top-padding);
27
- padding-bottom: var(--select-options-list-bottom-padding);
28
26
  box-shadow: var(--select-options-list-box-shadow);
29
27
  border-radius: var(--select-options-list-border-radius);
30
28
  background: var(--select-option-background);
31
29
  overflow: hidden;
32
- will-change: transform;
33
30
  }
34
31
 
35
32
  .optionsListBorder {
@@ -67,14 +64,69 @@
67
64
  }
68
65
 
69
66
  .search {
70
- margin: var(--gap-12) var(--gap-12) var(--gap-0);
67
+ margin: calc(var(--gap-12) - var(--select-options-list-top-padding))
68
+ calc(var(--gap-12) - var(--select-options-list-right-padding)) var(--gap-0)
69
+ calc(var(--gap-12) - var(--select-options-list-left-padding));
71
70
 
72
- &.size-72 {
73
- margin-left: var(--gap-16);
74
- margin-right: var(--gap-16);
71
+ &:last-child {
72
+ margin-bottom: calc(var(--gap-12) - var(--select-options-list-bottom-padding));
73
+ }
74
+ }
75
+
76
+ .option {
77
+ border-radius: var(--select-option-border-radius);
78
+ }
79
+
80
+ .list {
81
+ display: inline-block;
82
+ }
83
+
84
+ .matchContent {
85
+ .scrollbar[data-simplebar] {
86
+ .scrollable {
87
+ display: flex;
88
+ }
89
+
90
+ .content {
91
+ flex-grow: 1;
92
+ }
93
+
94
+ .list {
95
+ display: inline-block;
96
+ min-width: 100%;
97
+ }
75
98
  }
76
99
 
100
+ *:not(&) .list {
101
+ min-width: 100%;
102
+ max-width: 100%;
103
+ }
104
+ }
105
+
106
+ .scrollbar {
107
+ padding-right: var(--select-options-list-right-padding);
108
+ padding-left: var(--select-options-list-left-padding);
109
+ &:first-child {
110
+ .list:before {
111
+ content: '';
112
+ display: block;
113
+ padding-top: var(--select-options-list-top-padding);
114
+ }
115
+ }
77
116
  &:last-child {
78
- margin-bottom: var(--gap-12);
117
+ .list:after {
118
+ content: '';
119
+ display: block;
120
+ padding-top: var(--select-options-list-bottom-padding);
121
+ }
122
+ }
123
+ }
124
+
125
+ :global(.simplebar-track):global(.simplebar-vertical) {
126
+ .scrollbar:first-child & {
127
+ top: var(--select-options-list-scrollbar-gap);
128
+ }
129
+ .scrollbar:last-child & {
130
+ bottom: var(--select-options-list-scrollbar-gap);
79
131
  }
80
132
  }
@@ -31,6 +31,7 @@
31
31
 
32
32
  .scrollbar {
33
33
  flex: 1;
34
+ overflow: visible;
34
35
  }
35
36
 
36
37
  .search {
@@ -37,6 +37,10 @@
37
37
  }
38
38
  }
39
39
 
40
+ .size-40 {
41
+ min-height: var(--size-xs-height);
42
+ }
43
+
40
44
  .size-48 {
41
45
  min-height: var(--size-s-height);
42
46
  }
@@ -1,8 +1,8 @@
1
- import React, { forwardRef, useCallback, useRef, useState } from 'react';
1
+ import React, { forwardRef, useRef, useState } from 'react';
2
2
  import mergeRefs from 'react-merge-refs';
3
3
  import cn from 'classnames';
4
4
 
5
- import { Scrollbar } from '@alfalab/core-components-scrollbar';
5
+ import { ScrollbarPrivate } from '@alfalab/core-components-scrollbar-private';
6
6
 
7
7
  import { DEFAULT_VISIBLE_OPTIONS } from '../../consts';
8
8
  import { useNativeScrollbar } from '../../hooks/use-native-scrollbar';
@@ -40,7 +40,6 @@ export const OptionsList = forwardRef<HTMLDivElement, OptionsListProps>(
40
40
  header,
41
41
  footer,
42
42
  showFooter = true,
43
- optionsListWidth,
44
43
  nativeScrollbar: nativeScrollbarProp,
45
44
  flatOptions = [],
46
45
  setHighlightedIndex,
@@ -50,9 +49,24 @@ export const OptionsList = forwardRef<HTMLDivElement, OptionsListProps>(
50
49
  multiple,
51
50
  limitDynamicOptionGroupSize = false,
52
51
  client,
52
+ scrollableNodeClassName,
53
+ contentNodeClassName,
54
+ listNodeClassName,
53
55
  },
54
56
  ref,
55
57
  ) => {
58
+ const actualOptionsCount = limitDynamicOptionGroupSize && options.length > 0;
59
+ const listRef = useRef<HTMLDivElement>(null);
60
+ const scrollableNodeRef = useRef<HTMLDivElement>(null);
61
+ const [, maxHeight] = useVisibleOptions({
62
+ visibleOptions,
63
+ listRef,
64
+ open,
65
+ options,
66
+ actualOptionsCount,
67
+ size: actualOptionsCount ? size : undefined,
68
+ });
69
+ const noOptions = options.length === 0;
56
70
  const [scrollTop, setScrollTop] = useState(true);
57
71
  const [scrollBottom, setScrollBottom] = useState(false);
58
72
 
@@ -61,27 +75,22 @@ export const OptionsList = forwardRef<HTMLDivElement, OptionsListProps>(
61
75
  client,
62
76
  });
63
77
 
64
- const handleScroll = useCallback(
65
- (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
66
- const scrolledToHeader = event.currentTarget.scrollTop <= 0;
67
- const scrolledToFooter =
68
- event.currentTarget.scrollHeight - event.currentTarget.offsetHeight <=
69
- event.currentTarget.scrollTop;
78
+ const handleScroll: React.MouseEventHandler<HTMLDivElement> = (event) => {
79
+ const scrolledToHeader = event.currentTarget.scrollTop <= 0;
80
+ const scrolledToFooter =
81
+ event.currentTarget.scrollHeight - event.currentTarget.offsetHeight <=
82
+ event.currentTarget.scrollTop;
70
83
 
71
- setScrollTop(scrolledToHeader);
72
- setScrollBottom(scrolledToFooter);
84
+ setScrollTop(scrolledToHeader);
85
+ setScrollBottom(scrolledToFooter);
73
86
 
74
- onScroll?.(event);
75
- },
76
- [onScroll],
77
- );
87
+ onScroll?.(event);
88
+ };
78
89
 
79
90
  const renderOption = (option: OptionShape, index: number) => (
80
91
  <Option key={option.key} {...getOptionProps(option, index)} />
81
92
  );
82
93
 
83
- const listRef = useRef<HTMLDivElement>(null);
84
- const scrollbarRef = useRef<HTMLDivElement>(null);
85
94
  const counter = createCounter();
86
95
  const renderGroup = (group: GroupShape) => {
87
96
  const groupSelectedItems = selectedItems?.filter(({ key: selectedItemKey }) =>
@@ -116,89 +125,10 @@ export const OptionsList = forwardRef<HTMLDivElement, OptionsListProps>(
116
125
  );
117
126
  };
118
127
 
119
- const actualOptionsCount = limitDynamicOptionGroupSize && options.length > 0;
120
-
121
- const [measured, maxHeight] = useVisibleOptions({
122
- visibleOptions,
123
- listRef,
124
- open,
125
- options,
126
- actualOptionsCount,
127
- size: actualOptionsCount
128
- ? (() => {
129
- switch (typeof size) {
130
- case 'string':
131
- throw new Error(
132
- 'OptionsList with `limitDynamicOptionGroupSize` enabled needs a `size` with number type',
133
- );
134
- default:
135
- return size;
136
- }
137
- })()
138
- : undefined,
139
- });
140
-
141
128
  if (options.length === 0 && !emptyPlaceholder && !header && !footer) {
142
129
  return null;
143
130
  }
144
131
 
145
- const renderListItems = () => (
146
- <React.Fragment>
147
- {options.map((option) =>
148
- isGroup(option) ? renderGroup(option) : renderOption(option, counter()),
149
- )}
150
-
151
- {emptyPlaceholder && options.length === 0 && (
152
- <div className={styles.emptyPlaceholder}>{emptyPlaceholder}</div>
153
- )}
154
- </React.Fragment>
155
- );
156
-
157
- const renderWithCustomScrollbar = () => {
158
- const scrollableNodeProps = {
159
- onScroll: handleScroll,
160
- 'data-test-id': dataTestId,
161
- ref: ref as React.RefObject<HTMLDivElement>,
162
- };
163
-
164
- return (
165
- <Scrollbar
166
- className={cn(styles.scrollable, scrollbarClassName)}
167
- verticalBarClassName={cn(styles.verticalBar, {
168
- [styles.verticalBarWithHeader]: Boolean(header),
169
- [styles.verticalBarWithFooter]: Boolean(footer),
170
- })}
171
- ref={scrollbarRef}
172
- style={{ maxHeight }}
173
- horizontalAutoStretch={optionsListWidth === 'content'}
174
- scrollableNodeProps={scrollableNodeProps}
175
- contentNodeProps={{ ref: listRef }}
176
- maskProps={{
177
- /*
178
- * Для корректного подсчета высоты опций(иначе для optionsListWidth: 'field'
179
- * высота опции всегда будет равна высоте одной строчки)
180
- */
181
- className: cn({
182
- [styles.mask]: optionsListWidth === 'content' && !measured,
183
- }),
184
- }}
185
- >
186
- {renderListItems()}
187
- </Scrollbar>
188
- );
189
- };
190
-
191
- const renderWithNativeScrollbar = () => (
192
- <div
193
- className={cn(styles.scrollable, scrollbarClassName)}
194
- ref={mergeRefs([listRef, ref])}
195
- onScroll={handleScroll}
196
- style={{ maxHeight }}
197
- >
198
- {renderListItems()}
199
- </div>
200
- );
201
-
202
132
  const resetHighlightedIndex = () => setHighlightedIndex?.(-1);
203
133
 
204
134
  return (
@@ -217,7 +147,34 @@ export const OptionsList = forwardRef<HTMLDivElement, OptionsListProps>(
217
147
  </div>
218
148
  )}
219
149
 
220
- {nativeScrollbar ? renderWithNativeScrollbar() : renderWithCustomScrollbar()}
150
+ {!noOptions && (
151
+ <ScrollbarPrivate
152
+ native={nativeScrollbar}
153
+ className={scrollbarClassName}
154
+ style={{ maxHeight }}
155
+ scrollableNodeProps={{
156
+ ref: mergeRefs([scrollableNodeRef, ref]),
157
+ onScroll: handleScroll,
158
+ className: scrollableNodeClassName,
159
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
160
+ // @ts-expect-error
161
+ 'data-test-id': nativeScrollbar ? undefined : dataTestId,
162
+ }}
163
+ contentNodeProps={{ className: contentNodeClassName }}
164
+ >
165
+ <div className={listNodeClassName} ref={listRef}>
166
+ {options.map((option) =>
167
+ isGroup(option)
168
+ ? renderGroup(option)
169
+ : renderOption(option, counter()),
170
+ )}
171
+ </div>
172
+ </ScrollbarPrivate>
173
+ )}
174
+
175
+ {emptyPlaceholder && noOptions && (
176
+ <div className={styles.emptyPlaceholder}>{emptyPlaceholder}</div>
177
+ )}
221
178
 
222
179
  {showFooter && footer && (
223
180
  <div
@@ -6,37 +6,11 @@
6
6
  outline: none;
7
7
  box-sizing: border-box;
8
8
  position: relative;
9
-
10
- & .scrollable {
11
- overflow: auto;
12
- width: 100%;
13
-
14
- & .verticalBar.verticalBar {
15
- top: var(--select-options-list-scrollbar-gap);
16
- height: calc(100% - 2 * var(--select-options-list-scrollbar-gap));
17
-
18
- &.verticalBarWithHeader {
19
- top: 0;
20
- height: calc(100% - var(--select-options-list-scrollbar-gap));
21
- }
22
-
23
- &.verticalBarWithFooter {
24
- top: var(--select-options-list-scrollbar-gap);
25
- height: calc(100% - var(--select-options-list-scrollbar-gap));
26
- }
27
-
28
- &.verticalBarWithHeader.verticalBarWithFooter {
29
- top: 0;
30
- height: 100%;
31
- }
32
- }
33
- }
34
9
  }
35
10
 
36
11
  .optionsListHeader {
37
12
  box-sizing: border-box;
38
13
  border-bottom: 1px solid transparent;
39
- transition: border-color 0.2s ease;
40
14
 
41
15
  &.headerHighlighted {
42
16
  border-bottom-color: var(--color-light-neutral-300);
@@ -48,22 +22,24 @@
48
22
  bottom: var(--gap-0);
49
23
  box-sizing: border-box;
50
24
  border-top: 1px solid transparent;
51
- transition: border-color 0.2s ease;
52
25
 
53
26
  &.withBorder {
54
27
  border-top-color: var(--color-light-neutral-300);
55
28
  }
56
29
  }
57
30
 
58
- .mask {
59
- min-width: 4000px;
60
- }
61
-
62
31
  .emptyPlaceholder {
63
32
  padding: var(--gap-16) var(--gap-12);
64
33
  color: var(--select-options-list-empty-placeholder-color);
34
+ flex: 1; /* needed in mobile */
65
35
  }
66
36
 
37
+ .size-64 .emptyPlaceholder,
67
38
  .size-72 .emptyPlaceholder {
68
39
  padding: var(--gap-24) var(--gap-16);
69
40
  }
41
+
42
+ .list {
43
+ will-change: transform;
44
+ height: auto;
45
+ }
@@ -1,17 +1,24 @@
1
- import React, { forwardRef, useEffect, useMemo, useRef, useState } from 'react';
2
- import mergeRefs from 'react-merge-refs';
1
+ import React, {
2
+ forwardRef,
3
+ useEffect,
4
+ useImperativeHandle,
5
+ useMemo,
6
+ useRef,
7
+ useState,
8
+ } from 'react';
3
9
  import { useVirtual } from 'react-virtual';
4
10
  import cn from 'classnames';
5
11
 
6
- import { Scrollbar } from '@alfalab/core-components-scrollbar';
12
+ import { ScrollbarPrivate } from '@alfalab/core-components-scrollbar-private';
13
+ import { getElementWindow } from '@alfalab/core-components-shared';
7
14
 
8
15
  import { DEFAULT_VISIBLE_OPTIONS } from '../../consts';
9
16
  import { useNativeScrollbar } from '../../hooks/use-native-scrollbar';
10
17
  import { type GroupShape, type OptionShape, type OptionsListProps } from '../../typings';
11
- import { isGroup, lastIndexOf, usePrevious, useVirtualVisibleOptions } from '../../utils';
18
+ import { isGroup, usePrevious, useVirtualVisibleOptions } from '../../utils';
12
19
  import { Optgroup as DefaultOptgroup } from '../optgroup';
13
20
 
14
- import styles from './index.module.css';
21
+ import styles from '../options-list/index.module.css';
15
22
 
16
23
  export const VirtualOptionsList = forwardRef<HTMLDivElement, OptionsListProps>(
17
24
  (
@@ -33,7 +40,6 @@ export const VirtualOptionsList = forwardRef<HTMLDivElement, OptionsListProps>(
33
40
  header,
34
41
  footer,
35
42
  showFooter = true,
36
- optionsListWidth,
37
43
  onScroll,
38
44
  nativeScrollbar: nativeScrollbarProp,
39
45
  setHighlightedIndex,
@@ -43,18 +49,43 @@ export const VirtualOptionsList = forwardRef<HTMLDivElement, OptionsListProps>(
43
49
  multiple,
44
50
  scrollbarClassName,
45
51
  client,
52
+ scrollableNodeClassName,
53
+ contentNodeClassName,
54
+ listNodeClassName,
55
+ footerClassName,
56
+ ctrlRef,
46
57
  },
47
58
  ref,
48
59
  ) => {
49
60
  const listRef = useRef<HTMLDivElement>(null);
50
- const parentRef = useRef<HTMLDivElement>(null);
51
- const scrollbarRef = useRef<HTMLDivElement>(null);
61
+ const scrollableNodeRef = useRef<HTMLDivElement>(null);
62
+ const noOptions = options.length === 0;
63
+ const [scrollTop, setScrollTop] = useState(true);
64
+ const [scrollBottom, setScrollBottom] = useState(false);
52
65
  const [visibleOptionsInvalidateKey, setVisibleOptionsInvalidateKey] = useState('');
53
66
  const prevHighlightedIndex = usePrevious(highlightedIndex) || -1;
54
67
 
55
68
  const rowVirtualizer = useVirtual({
69
+ scrollToFn(offset, defaultScrollToFn) {
70
+ let delta = 0;
71
+ const list = listRef.current;
72
+
73
+ if (list) {
74
+ const win = getElementWindow(list);
75
+
76
+ delta = ['::before', '::after']
77
+ .map((pseudo) => {
78
+ const { paddingTop } = win.getComputedStyle(list, pseudo);
79
+
80
+ return parseFloat(paddingTop) || 0;
81
+ })
82
+ .reduce((a, b) => a + b);
83
+ }
84
+
85
+ defaultScrollToFn?.(offset === 0 ? offset : offset + delta);
86
+ },
56
87
  size: flatOptions.length,
57
- parentRef: (ref || parentRef) as React.RefObject<HTMLDivElement>,
88
+ parentRef: (ref as React.RefObject<HTMLDivElement>) || scrollableNodeRef,
58
89
  overscan: 15,
59
90
  });
60
91
 
@@ -71,21 +102,13 @@ export const VirtualOptionsList = forwardRef<HTMLDivElement, OptionsListProps>(
71
102
  // eslint-disable-next-line react-hooks/exhaustive-deps
72
103
  }, [open]);
73
104
 
74
- // Скролл к пункту, которого нет на экране
75
- useEffect(() => {
76
- if (highlightedIndex === -1) return;
77
-
78
- if (!rowVirtualizer.virtualItems.some((option) => option.index === highlightedIndex)) {
79
- rowVirtualizer.scrollToIndex(highlightedIndex, { align: 'end' });
80
- }
81
- // eslint-disable-next-line react-hooks/exhaustive-deps
82
- }, [highlightedIndex]);
105
+ useImperativeHandle(ctrlRef, () => rowVirtualizer, [rowVirtualizer]);
83
106
 
84
107
  // Циклическая навигация
85
108
  useEffect(() => {
86
109
  const notDisabled = (option: OptionShape) => !option.disabled;
87
110
  const firstNonDisabled = flatOptions.findIndex(notDisabled);
88
- const lastNonDisabled = lastIndexOf(flatOptions, notDisabled);
111
+ const lastNonDisabled = flatOptions.findLastIndex(notDisabled);
89
112
 
90
113
  if (
91
114
  prevHighlightedIndex <= firstNonDisabled &&
@@ -115,11 +138,10 @@ export const VirtualOptionsList = forwardRef<HTMLDivElement, OptionsListProps>(
115
138
  // eslint-disable-next-line react-hooks/exhaustive-deps
116
139
  }, [rowVirtualizer.virtualItems.length, flatOptions]);
117
140
 
118
- useVirtualVisibleOptions({
141
+ const maxHeight = useVirtualVisibleOptions({
119
142
  visibleOptions,
120
143
  invalidate: visibleOptionsInvalidateKey,
121
144
  listRef,
122
- styleTargetRef: nativeScrollbar ? parentRef : scrollbarRef,
123
145
  open,
124
146
  });
125
147
 
@@ -139,6 +161,18 @@ export const VirtualOptionsList = forwardRef<HTMLDivElement, OptionsListProps>(
139
161
  }, {});
140
162
  }, [options]);
141
163
 
164
+ const handleScroll: React.MouseEventHandler<HTMLDivElement> = (event) => {
165
+ const scrolledToHeader = event.currentTarget.scrollTop <= 0;
166
+ const scrolledToFooter =
167
+ event.currentTarget.scrollHeight - event.currentTarget.offsetHeight <=
168
+ event.currentTarget.scrollTop;
169
+
170
+ setScrollTop(scrolledToHeader);
171
+ setScrollBottom(scrolledToFooter);
172
+
173
+ onScroll?.(event);
174
+ };
175
+
142
176
  const renderList = () =>
143
177
  rowVirtualizer.virtualItems.map((virtualRow) => {
144
178
  const option = flatOptions[virtualRow.index];
@@ -177,16 +211,7 @@ export const VirtualOptionsList = forwardRef<HTMLDivElement, OptionsListProps>(
177
211
  };
178
212
 
179
213
  return (
180
- <div
181
- key={virtualRow.index}
182
- ref={virtualRow.measureRef}
183
- className={cn(styles.virtualRow, {
184
- [styles.highlighted]: highlightedIndex === virtualRow.index,
185
- })}
186
- style={{
187
- transform: `translateY(${virtualRow.start}px)`,
188
- }}
189
- >
214
+ <div key={virtualRow.index} ref={virtualRow.measureRef}>
190
215
  {renderGroup()}
191
216
  {!isGroup(option) && (
192
217
  <Option {...getOptionProps(option, virtualRow.index)} />
@@ -195,72 +220,70 @@ export const VirtualOptionsList = forwardRef<HTMLDivElement, OptionsListProps>(
195
220
  );
196
221
  });
197
222
 
198
- const contentNodeProps = {
199
- className: styles.inner,
200
- style: { height: `${rowVirtualizer.totalSize}px` },
201
- ref: listRef,
202
- };
203
-
204
- const renderWithCustomScrollbar = () => (
205
- <Scrollbar
206
- className={cn(styles.scrollable, scrollbarClassName)}
207
- ref={scrollbarRef}
208
- horizontalAutoStretch={optionsListWidth === 'content'}
209
- scrollableNodeProps={{ onScroll, ref: parentRef }}
210
- contentNodeProps={contentNodeProps}
211
- >
212
- {renderList()}
213
- </Scrollbar>
214
- );
215
-
216
- const renderWithNativeScrollbar = () => {
217
- if (visibleOptions) {
218
- return (
219
- <div
220
- className={cn(styles.scrollable, scrollbarClassName)}
221
- ref={mergeRefs([parentRef, ref])}
222
- onScroll={onScroll}
223
- >
224
- <div {...contentNodeProps}>{renderList()}</div>
225
- </div>
226
- );
227
- }
228
-
229
- return <div {...contentNodeProps}>{renderList()}</div>;
230
- };
231
-
232
223
  const resetHighlightedIndex = () => setHighlightedIndex?.(-1);
233
224
 
234
225
  if (options.length === 0 && !emptyPlaceholder) {
235
226
  return null;
236
227
  }
237
228
 
229
+ const [firstVirualRow] = rowVirtualizer.virtualItems;
230
+
238
231
  return (
239
232
  <div
240
- className={cn(styles.virtualOptionsList, styles[`size-${size}`], className)}
241
- data-test-id={dataTestId}
233
+ {...(nativeScrollbar && { 'data-test-id': dataTestId })}
234
+ className={cn(styles.optionsList, styles[`size-${size}`], className)}
242
235
  >
243
236
  {header && (
244
237
  <div
245
- className={styles.virtualOptionsListHeader}
238
+ className={cn(styles.optionsListHeader, {
239
+ [styles.headerHighlighted]: !scrollTop,
240
+ })}
246
241
  onMouseEnter={resetHighlightedIndex}
247
242
  >
248
243
  {header}
249
244
  </div>
250
245
  )}
251
246
 
252
- {nativeScrollbar ? renderWithNativeScrollbar() : renderWithCustomScrollbar()}
247
+ {!noOptions && (
248
+ <ScrollbarPrivate
249
+ native={nativeScrollbar}
250
+ className={scrollbarClassName}
251
+ style={{ maxHeight }}
252
+ scrollableNodeProps={{
253
+ ref: scrollableNodeRef,
254
+ onScroll: handleScroll,
255
+ className: scrollableNodeClassName,
256
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
257
+ // @ts-expect-error
258
+ 'data-test-id': nativeScrollbar ? undefined : dataTestId,
259
+ }}
260
+ contentNodeProps={{
261
+ className: contentNodeClassName,
262
+ style: { minHeight: rowVirtualizer.totalSize },
263
+ }}
264
+ >
265
+ <div
266
+ className={cn(styles.list, listNodeClassName)}
267
+ ref={listRef}
268
+ style={{ transform: `translateY(${firstVirualRow?.start ?? 0}px)` }}
269
+ >
270
+ {renderList()}
271
+ </div>
272
+ </ScrollbarPrivate>
273
+ )}
253
274
 
254
- {emptyPlaceholder && options.length === 0 && (
275
+ {emptyPlaceholder && noOptions && (
255
276
  <div className={styles.emptyPlaceholder}>{emptyPlaceholder}</div>
256
277
  )}
257
278
 
258
279
  {showFooter && footer && (
259
280
  <div
260
281
  onMouseEnter={resetHighlightedIndex}
261
- className={cn(styles.virtualOptionsListFooter, {
282
+ className={cn(styles.optionsListFooter, footerClassName, {
262
283
  [styles.withBorder]:
263
- visibleOptions && flatOptions.length > visibleOptions,
284
+ visibleOptions &&
285
+ flatOptions.length > visibleOptions &&
286
+ !scrollBottom,
264
287
  })}
265
288
  >
266
289
  {footer}
@@ -6,10 +6,6 @@
6
6
  padding: var(--gap-12);
7
7
  outline: none;
8
8
 
9
- &.size-72 {
10
- padding: var(--gap-12) var(--gap-16);
11
- }
12
-
13
9
  & > .button + .button {
14
10
  margin-left: var(--select-option-list-footer-button-gap);
15
11
  }
@@ -1,10 +1,10 @@
1
1
  @import '@alfalab/core-components-vars/src/index.css';
2
+ @import '../../../../vars.css';
2
3
 
3
4
  .desktop {
4
- padding: var(--gap-12);
5
- &.size-72 {
6
- padding-left: var(--gap-16);
7
- }
5
+ padding: calc(var(--gap-12) + var(--select-options-list-top-padding))
6
+ calc(var(--gap-12) + var(--select-options-list-right-padding)) var(--gap-12)
7
+ calc(var(--gap-12) + var(--select-options-list-left-padding));
8
8
  }
9
9
 
10
10
  .mobile {