@cloud-ru/ds-fields 1.0.0

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 (314) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/LICENSE +201 -0
  3. package/README.md +888 -0
  4. package/dist/cjs/components/FieldColor/FieldColor.d.ts +48 -0
  5. package/dist/cjs/components/FieldColor/FieldColor.js +147 -0
  6. package/dist/cjs/components/FieldColor/index.d.ts +1 -0
  7. package/dist/cjs/components/FieldColor/index.js +17 -0
  8. package/dist/cjs/components/FieldColor/styles.module.css +79 -0
  9. package/dist/cjs/components/FieldColor/utils.d.ts +7 -0
  10. package/dist/cjs/components/FieldColor/utils.js +19 -0
  11. package/dist/cjs/components/FieldCombo/FieldCombo.d.ts +91 -0
  12. package/dist/cjs/components/FieldCombo/FieldCombo.js +200 -0
  13. package/dist/cjs/components/FieldCombo/index.d.ts +1 -0
  14. package/dist/cjs/components/FieldCombo/index.js +17 -0
  15. package/dist/cjs/components/FieldDate/FieldDate.d.ts +2 -0
  16. package/dist/cjs/components/FieldDate/FieldDate.js +369 -0
  17. package/dist/cjs/components/FieldDate/constants.d.ts +10 -0
  18. package/dist/cjs/components/FieldDate/constants.js +13 -0
  19. package/dist/cjs/components/FieldDate/index.d.ts +3 -0
  20. package/dist/cjs/components/FieldDate/index.js +19 -0
  21. package/dist/cjs/components/FieldDate/mask.d.ts +20 -0
  22. package/dist/cjs/components/FieldDate/mask.js +117 -0
  23. package/dist/cjs/components/FieldDate/styles.module.css +43 -0
  24. package/dist/cjs/components/FieldDate/types.d.ts +100 -0
  25. package/dist/cjs/components/FieldDate/types.js +2 -0
  26. package/dist/cjs/components/FieldDate/utils.d.ts +3 -0
  27. package/dist/cjs/components/FieldDate/utils.js +8 -0
  28. package/dist/cjs/components/FieldSecure/FieldSecure.d.ts +64 -0
  29. package/dist/cjs/components/FieldSecure/FieldSecure.js +158 -0
  30. package/dist/cjs/components/FieldSecure/index.d.ts +1 -0
  31. package/dist/cjs/components/FieldSecure/index.js +17 -0
  32. package/dist/cjs/components/FieldSelect/FieldSelect.d.ts +2 -0
  33. package/dist/cjs/components/FieldSelect/FieldSelect.js +360 -0
  34. package/dist/cjs/components/FieldSelect/constants.d.ts +4 -0
  35. package/dist/cjs/components/FieldSelect/constants.js +7 -0
  36. package/dist/cjs/components/FieldSelect/index.d.ts +3 -0
  37. package/dist/cjs/components/FieldSelect/index.js +19 -0
  38. package/dist/cjs/components/FieldSelect/styles.module.css +92 -0
  39. package/dist/cjs/components/FieldSelect/types.d.ts +197 -0
  40. package/dist/cjs/components/FieldSelect/types.js +2 -0
  41. package/dist/cjs/components/FieldSelect/utils.d.ts +19 -0
  42. package/dist/cjs/components/FieldSelect/utils.js +104 -0
  43. package/dist/cjs/components/FieldSlider/FieldSlider.d.ts +92 -0
  44. package/dist/cjs/components/FieldSlider/FieldSlider.js +196 -0
  45. package/dist/cjs/components/FieldSlider/index.d.ts +2 -0
  46. package/dist/cjs/components/FieldSlider/index.js +18 -0
  47. package/dist/cjs/components/FieldSlider/styles.module.css +44 -0
  48. package/dist/cjs/components/FieldSlider/types.d.ts +4 -0
  49. package/dist/cjs/components/FieldSlider/types.js +2 -0
  50. package/dist/cjs/components/FieldSlider/utils/generateAllowedValues.d.ts +1 -0
  51. package/dist/cjs/components/FieldSlider/utils/generateAllowedValues.js +16 -0
  52. package/dist/cjs/components/FieldSlider/utils/getClosestMark.d.ts +4 -0
  53. package/dist/cjs/components/FieldSlider/utils/getClosestMark.js +16 -0
  54. package/dist/cjs/components/FieldSlider/utils/getMarkLabel.d.ts +2 -0
  55. package/dist/cjs/components/FieldSlider/utils/getMarkLabel.js +7 -0
  56. package/dist/cjs/components/FieldSlider/utils/getTextFieldValue.d.ts +2 -0
  57. package/dist/cjs/components/FieldSlider/utils/getTextFieldValue.js +9 -0
  58. package/dist/cjs/components/FieldSlider/utils/index.d.ts +5 -0
  59. package/dist/cjs/components/FieldSlider/utils/index.js +21 -0
  60. package/dist/cjs/components/FieldSlider/utils/isMarkObject.d.ts +6 -0
  61. package/dist/cjs/components/FieldSlider/utils/isMarkObject.js +6 -0
  62. package/dist/cjs/components/FieldStepper/FieldStepper.d.ts +81 -0
  63. package/dist/cjs/components/FieldStepper/FieldStepper.js +160 -0
  64. package/dist/cjs/components/FieldStepper/index.d.ts +1 -0
  65. package/dist/cjs/components/FieldStepper/index.js +17 -0
  66. package/dist/cjs/components/FieldStepper/styles.module.css +78 -0
  67. package/dist/cjs/components/FieldStepper/utils.d.ts +14 -0
  68. package/dist/cjs/components/FieldStepper/utils.js +45 -0
  69. package/dist/cjs/components/FieldText/FieldText.d.ts +11 -0
  70. package/dist/cjs/components/FieldText/FieldText.js +14 -0
  71. package/dist/cjs/components/FieldText/index.d.ts +1 -0
  72. package/dist/cjs/components/FieldText/index.js +17 -0
  73. package/dist/cjs/components/FieldTextArea/FieldTextArea.d.ts +95 -0
  74. package/dist/cjs/components/FieldTextArea/FieldTextArea.js +114 -0
  75. package/dist/cjs/components/FieldTextArea/index.d.ts +1 -0
  76. package/dist/cjs/components/FieldTextArea/index.js +17 -0
  77. package/dist/cjs/components/FieldTextArea/styles.module.css +123 -0
  78. package/dist/cjs/components/FieldTime/FieldTime.d.ts +74 -0
  79. package/dist/cjs/components/FieldTime/FieldTime.js +167 -0
  80. package/dist/cjs/components/FieldTime/index.d.ts +2 -0
  81. package/dist/cjs/components/FieldTime/index.js +17 -0
  82. package/dist/cjs/components/FieldTime/mask.d.ts +3 -0
  83. package/dist/cjs/components/FieldTime/mask.js +24 -0
  84. package/dist/cjs/components/FieldTime/styles.module.css +3 -0
  85. package/dist/cjs/components/index.d.ts +14 -0
  86. package/dist/cjs/components/index.js +32 -0
  87. package/dist/cjs/components/shared/helperComponents/FieldElementButton/FieldElementButton.d.ts +35 -0
  88. package/dist/cjs/components/shared/helperComponents/FieldElementButton/FieldElementButton.js +47 -0
  89. package/dist/cjs/components/shared/helperComponents/FieldElementButton/FieldElementButtonList.d.ts +26 -0
  90. package/dist/cjs/components/shared/helperComponents/FieldElementButton/FieldElementButtonList.js +24 -0
  91. package/dist/cjs/components/shared/helperComponents/FieldElementButton/constants.d.ts +9 -0
  92. package/dist/cjs/components/shared/helperComponents/FieldElementButton/constants.js +12 -0
  93. package/dist/cjs/components/shared/helperComponents/FieldElementButton/index.d.ts +4 -0
  94. package/dist/cjs/components/shared/helperComponents/FieldElementButton/index.js +20 -0
  95. package/dist/cjs/components/shared/helperComponents/FieldElementButton/styles.module.css +204 -0
  96. package/dist/cjs/components/shared/helperComponents/FieldElementButton/types.d.ts +4 -0
  97. package/dist/cjs/components/shared/helperComponents/FieldElementButton/types.js +2 -0
  98. package/dist/cjs/components/shared/helperComponents/FieldShell/FieldShell.d.ts +43 -0
  99. package/dist/cjs/components/shared/helperComponents/FieldShell/FieldShell.js +22 -0
  100. package/dist/cjs/components/shared/helperComponents/FieldShell/index.d.ts +1 -0
  101. package/dist/cjs/components/shared/helperComponents/FieldShell/index.js +17 -0
  102. package/dist/cjs/components/shared/helperComponents/FieldShell/styles.module.css +11 -0
  103. package/dist/cjs/components/shared/hooks/useCopyButton.d.ts +20 -0
  104. package/dist/cjs/components/shared/hooks/useCopyButton.js +58 -0
  105. package/dist/cjs/components/shared/index.d.ts +4 -0
  106. package/dist/cjs/components/shared/index.js +20 -0
  107. package/dist/cjs/components/shared/styles.module.css +673 -0
  108. package/dist/cjs/components/shared/utils.d.ts +69 -0
  109. package/dist/cjs/components/shared/utils.js +104 -0
  110. package/dist/cjs/constants.d.ts +61 -0
  111. package/dist/cjs/constants.js +76 -0
  112. package/dist/cjs/hooks/index.d.ts +1 -0
  113. package/dist/cjs/hooks/index.js +17 -0
  114. package/dist/cjs/hooks/useAdaptiveAutoFocus.d.ts +7 -0
  115. package/dist/cjs/hooks/useAdaptiveAutoFocus.js +10 -0
  116. package/dist/cjs/index.d.ts +3 -0
  117. package/dist/cjs/index.js +19 -0
  118. package/dist/cjs/package.json +3 -0
  119. package/dist/cjs/segments/index.d.ts +2 -0
  120. package/dist/cjs/segments/index.js +18 -0
  121. package/dist/cjs/segments/segments.d.ts +22 -0
  122. package/dist/cjs/segments/segments.js +46 -0
  123. package/dist/cjs/segments/useSegmentedMask.d.ts +43 -0
  124. package/dist/cjs/segments/useSegmentedMask.js +282 -0
  125. package/dist/cjs/style.css +1350 -0
  126. package/dist/esm/components/FieldColor/FieldColor.d.ts +48 -0
  127. package/dist/esm/components/FieldColor/FieldColor.js +141 -0
  128. package/dist/esm/components/FieldColor/index.d.ts +1 -0
  129. package/dist/esm/components/FieldColor/index.js +1 -0
  130. package/dist/esm/components/FieldColor/styles.module.css +79 -0
  131. package/dist/esm/components/FieldColor/utils.d.ts +7 -0
  132. package/dist/esm/components/FieldColor/utils.js +16 -0
  133. package/dist/esm/components/FieldCombo/FieldCombo.d.ts +91 -0
  134. package/dist/esm/components/FieldCombo/FieldCombo.js +194 -0
  135. package/dist/esm/components/FieldCombo/index.d.ts +1 -0
  136. package/dist/esm/components/FieldCombo/index.js +1 -0
  137. package/dist/esm/components/FieldDate/FieldDate.d.ts +2 -0
  138. package/dist/esm/components/FieldDate/FieldDate.js +363 -0
  139. package/dist/esm/components/FieldDate/constants.d.ts +10 -0
  140. package/dist/esm/components/FieldDate/constants.js +10 -0
  141. package/dist/esm/components/FieldDate/index.d.ts +3 -0
  142. package/dist/esm/components/FieldDate/index.js +3 -0
  143. package/dist/esm/components/FieldDate/mask.d.ts +20 -0
  144. package/dist/esm/components/FieldDate/mask.js +111 -0
  145. package/dist/esm/components/FieldDate/styles.module.css +43 -0
  146. package/dist/esm/components/FieldDate/types.d.ts +100 -0
  147. package/dist/esm/components/FieldDate/types.js +1 -0
  148. package/dist/esm/components/FieldDate/utils.d.ts +3 -0
  149. package/dist/esm/components/FieldDate/utils.js +5 -0
  150. package/dist/esm/components/FieldSecure/FieldSecure.d.ts +64 -0
  151. package/dist/esm/components/FieldSecure/FieldSecure.js +152 -0
  152. package/dist/esm/components/FieldSecure/index.d.ts +1 -0
  153. package/dist/esm/components/FieldSecure/index.js +1 -0
  154. package/dist/esm/components/FieldSelect/FieldSelect.d.ts +2 -0
  155. package/dist/esm/components/FieldSelect/FieldSelect.js +354 -0
  156. package/dist/esm/components/FieldSelect/constants.d.ts +4 -0
  157. package/dist/esm/components/FieldSelect/constants.js +4 -0
  158. package/dist/esm/components/FieldSelect/index.d.ts +3 -0
  159. package/dist/esm/components/FieldSelect/index.js +3 -0
  160. package/dist/esm/components/FieldSelect/styles.module.css +92 -0
  161. package/dist/esm/components/FieldSelect/types.d.ts +197 -0
  162. package/dist/esm/components/FieldSelect/types.js +1 -0
  163. package/dist/esm/components/FieldSelect/utils.d.ts +19 -0
  164. package/dist/esm/components/FieldSelect/utils.js +93 -0
  165. package/dist/esm/components/FieldSlider/FieldSlider.d.ts +92 -0
  166. package/dist/esm/components/FieldSlider/FieldSlider.js +190 -0
  167. package/dist/esm/components/FieldSlider/index.d.ts +2 -0
  168. package/dist/esm/components/FieldSlider/index.js +2 -0
  169. package/dist/esm/components/FieldSlider/styles.module.css +44 -0
  170. package/dist/esm/components/FieldSlider/types.d.ts +4 -0
  171. package/dist/esm/components/FieldSlider/types.js +1 -0
  172. package/dist/esm/components/FieldSlider/utils/generateAllowedValues.d.ts +1 -0
  173. package/dist/esm/components/FieldSlider/utils/generateAllowedValues.js +13 -0
  174. package/dist/esm/components/FieldSlider/utils/getClosestMark.d.ts +4 -0
  175. package/dist/esm/components/FieldSlider/utils/getClosestMark.js +13 -0
  176. package/dist/esm/components/FieldSlider/utils/getMarkLabel.d.ts +2 -0
  177. package/dist/esm/components/FieldSlider/utils/getMarkLabel.js +4 -0
  178. package/dist/esm/components/FieldSlider/utils/getTextFieldValue.d.ts +2 -0
  179. package/dist/esm/components/FieldSlider/utils/getTextFieldValue.js +6 -0
  180. package/dist/esm/components/FieldSlider/utils/index.d.ts +5 -0
  181. package/dist/esm/components/FieldSlider/utils/index.js +5 -0
  182. package/dist/esm/components/FieldSlider/utils/isMarkObject.d.ts +6 -0
  183. package/dist/esm/components/FieldSlider/utils/isMarkObject.js +3 -0
  184. package/dist/esm/components/FieldStepper/FieldStepper.d.ts +81 -0
  185. package/dist/esm/components/FieldStepper/FieldStepper.js +154 -0
  186. package/dist/esm/components/FieldStepper/index.d.ts +1 -0
  187. package/dist/esm/components/FieldStepper/index.js +1 -0
  188. package/dist/esm/components/FieldStepper/styles.module.css +78 -0
  189. package/dist/esm/components/FieldStepper/utils.d.ts +14 -0
  190. package/dist/esm/components/FieldStepper/utils.js +38 -0
  191. package/dist/esm/components/FieldText/FieldText.d.ts +11 -0
  192. package/dist/esm/components/FieldText/FieldText.js +11 -0
  193. package/dist/esm/components/FieldText/index.d.ts +1 -0
  194. package/dist/esm/components/FieldText/index.js +1 -0
  195. package/dist/esm/components/FieldTextArea/FieldTextArea.d.ts +95 -0
  196. package/dist/esm/components/FieldTextArea/FieldTextArea.js +108 -0
  197. package/dist/esm/components/FieldTextArea/index.d.ts +1 -0
  198. package/dist/esm/components/FieldTextArea/index.js +1 -0
  199. package/dist/esm/components/FieldTextArea/styles.module.css +123 -0
  200. package/dist/esm/components/FieldTime/FieldTime.d.ts +74 -0
  201. package/dist/esm/components/FieldTime/FieldTime.js +161 -0
  202. package/dist/esm/components/FieldTime/index.d.ts +2 -0
  203. package/dist/esm/components/FieldTime/index.js +1 -0
  204. package/dist/esm/components/FieldTime/mask.d.ts +3 -0
  205. package/dist/esm/components/FieldTime/mask.js +20 -0
  206. package/dist/esm/components/FieldTime/styles.module.css +3 -0
  207. package/dist/esm/components/index.d.ts +14 -0
  208. package/dist/esm/components/index.js +12 -0
  209. package/dist/esm/components/shared/helperComponents/FieldElementButton/FieldElementButton.d.ts +35 -0
  210. package/dist/esm/components/shared/helperComponents/FieldElementButton/FieldElementButton.js +41 -0
  211. package/dist/esm/components/shared/helperComponents/FieldElementButton/FieldElementButtonList.d.ts +26 -0
  212. package/dist/esm/components/shared/helperComponents/FieldElementButton/FieldElementButtonList.js +21 -0
  213. package/dist/esm/components/shared/helperComponents/FieldElementButton/constants.d.ts +9 -0
  214. package/dist/esm/components/shared/helperComponents/FieldElementButton/constants.js +9 -0
  215. package/dist/esm/components/shared/helperComponents/FieldElementButton/index.d.ts +4 -0
  216. package/dist/esm/components/shared/helperComponents/FieldElementButton/index.js +4 -0
  217. package/dist/esm/components/shared/helperComponents/FieldElementButton/styles.module.css +204 -0
  218. package/dist/esm/components/shared/helperComponents/FieldElementButton/types.d.ts +4 -0
  219. package/dist/esm/components/shared/helperComponents/FieldElementButton/types.js +1 -0
  220. package/dist/esm/components/shared/helperComponents/FieldShell/FieldShell.d.ts +43 -0
  221. package/dist/esm/components/shared/helperComponents/FieldShell/FieldShell.js +16 -0
  222. package/dist/esm/components/shared/helperComponents/FieldShell/index.d.ts +1 -0
  223. package/dist/esm/components/shared/helperComponents/FieldShell/index.js +1 -0
  224. package/dist/esm/components/shared/helperComponents/FieldShell/styles.module.css +11 -0
  225. package/dist/esm/components/shared/hooks/useCopyButton.d.ts +20 -0
  226. package/dist/esm/components/shared/hooks/useCopyButton.js +55 -0
  227. package/dist/esm/components/shared/index.d.ts +4 -0
  228. package/dist/esm/components/shared/index.js +4 -0
  229. package/dist/esm/components/shared/styles.module.css +673 -0
  230. package/dist/esm/components/shared/utils.d.ts +69 -0
  231. package/dist/esm/components/shared/utils.js +91 -0
  232. package/dist/esm/constants.d.ts +61 -0
  233. package/dist/esm/constants.js +73 -0
  234. package/dist/esm/hooks/index.d.ts +1 -0
  235. package/dist/esm/hooks/index.js +1 -0
  236. package/dist/esm/hooks/useAdaptiveAutoFocus.d.ts +7 -0
  237. package/dist/esm/hooks/useAdaptiveAutoFocus.js +7 -0
  238. package/dist/esm/index.d.ts +3 -0
  239. package/dist/esm/index.js +3 -0
  240. package/dist/esm/segments/index.d.ts +2 -0
  241. package/dist/esm/segments/index.js +2 -0
  242. package/dist/esm/segments/segments.d.ts +22 -0
  243. package/dist/esm/segments/segments.js +43 -0
  244. package/dist/esm/segments/useSegmentedMask.d.ts +43 -0
  245. package/dist/esm/segments/useSegmentedMask.js +279 -0
  246. package/dist/esm/style.css +1350 -0
  247. package/dist/tsconfig.cjs.tsbuildinfo +1 -0
  248. package/dist/tsconfig.esm.tsbuildinfo +1 -0
  249. package/package.json +62 -0
  250. package/src/components/FieldColor/FieldColor.tsx +426 -0
  251. package/src/components/FieldColor/index.ts +1 -0
  252. package/src/components/FieldColor/styles.module.scss +120 -0
  253. package/src/components/FieldColor/utils.ts +17 -0
  254. package/src/components/FieldCombo/FieldCombo.tsx +495 -0
  255. package/src/components/FieldCombo/index.ts +1 -0
  256. package/src/components/FieldDate/FieldDate.tsx +658 -0
  257. package/src/components/FieldDate/constants.ts +11 -0
  258. package/src/components/FieldDate/index.ts +3 -0
  259. package/src/components/FieldDate/mask.ts +123 -0
  260. package/src/components/FieldDate/styles.module.scss +56 -0
  261. package/src/components/FieldDate/types.ts +124 -0
  262. package/src/components/FieldDate/utils.ts +7 -0
  263. package/src/components/FieldSecure/FieldSecure.tsx +398 -0
  264. package/src/components/FieldSecure/index.ts +1 -0
  265. package/src/components/FieldSelect/FieldSelect.tsx +689 -0
  266. package/src/components/FieldSelect/constants.ts +4 -0
  267. package/src/components/FieldSelect/index.ts +3 -0
  268. package/src/components/FieldSelect/styles.module.scss +104 -0
  269. package/src/components/FieldSelect/types.ts +212 -0
  270. package/src/components/FieldSelect/utils.ts +134 -0
  271. package/src/components/FieldSlider/FieldSlider.tsx +502 -0
  272. package/src/components/FieldSlider/index.ts +2 -0
  273. package/src/components/FieldSlider/styles.module.scss +47 -0
  274. package/src/components/FieldSlider/types.ts +5 -0
  275. package/src/components/FieldSlider/utils/generateAllowedValues.ts +13 -0
  276. package/src/components/FieldSlider/utils/getClosestMark.ts +21 -0
  277. package/src/components/FieldSlider/utils/getMarkLabel.ts +7 -0
  278. package/src/components/FieldSlider/utils/getTextFieldValue.ts +8 -0
  279. package/src/components/FieldSlider/utils/index.ts +5 -0
  280. package/src/components/FieldSlider/utils/isMarkObject.ts +7 -0
  281. package/src/components/FieldStepper/FieldStepper.tsx +457 -0
  282. package/src/components/FieldStepper/index.ts +1 -0
  283. package/src/components/FieldStepper/styles.module.scss +98 -0
  284. package/src/components/FieldStepper/utils.ts +44 -0
  285. package/src/components/FieldText/FieldText.tsx +27 -0
  286. package/src/components/FieldText/index.ts +1 -0
  287. package/src/components/FieldTextArea/FieldTextArea.tsx +374 -0
  288. package/src/components/FieldTextArea/index.ts +1 -0
  289. package/src/components/FieldTextArea/styles.module.scss +192 -0
  290. package/src/components/FieldTime/FieldTime.tsx +401 -0
  291. package/src/components/FieldTime/index.ts +2 -0
  292. package/src/components/FieldTime/mask.ts +22 -0
  293. package/src/components/FieldTime/styles.module.scss +3 -0
  294. package/src/components/index.ts +14 -0
  295. package/src/components/shared/helperComponents/FieldElementButton/FieldElementButton.tsx +127 -0
  296. package/src/components/shared/helperComponents/FieldElementButton/FieldElementButtonList.tsx +72 -0
  297. package/src/components/shared/helperComponents/FieldElementButton/constants.ts +10 -0
  298. package/src/components/shared/helperComponents/FieldElementButton/index.ts +4 -0
  299. package/src/components/shared/helperComponents/FieldElementButton/styles.module.scss +137 -0
  300. package/src/components/shared/helperComponents/FieldElementButton/types.ts +6 -0
  301. package/src/components/shared/helperComponents/FieldShell/FieldShell.tsx +100 -0
  302. package/src/components/shared/helperComponents/FieldShell/index.ts +1 -0
  303. package/src/components/shared/helperComponents/FieldShell/styles.module.scss +13 -0
  304. package/src/components/shared/hooks/useCopyButton.tsx +103 -0
  305. package/src/components/shared/index.ts +4 -0
  306. package/src/components/shared/styles.module.scss +409 -0
  307. package/src/components/shared/utils.ts +128 -0
  308. package/src/constants.ts +73 -0
  309. package/src/hooks/index.ts +1 -0
  310. package/src/hooks/useAdaptiveAutoFocus.ts +16 -0
  311. package/src/index.ts +3 -0
  312. package/src/segments/index.ts +2 -0
  313. package/src/segments/segments.ts +74 -0
  314. package/src/segments/useSegmentedMask.ts +361 -0
@@ -0,0 +1,689 @@
1
+ import { ChevronDownSVG } from '@cloud-ru/ds-icons/interface/system';
2
+ import { InputPrivate, useButtonNavigation, useClearButton } from '@cloud-ru/ds-input-private';
3
+ import { Droplist, ItemId } from '@cloud-ru/ds-list';
4
+ import { Tag } from '@cloud-ru/ds-tag';
5
+ import { useLayoutEffect } from '@cloud-ru/ds-utils';
6
+ import cn from 'classnames';
7
+ import mergeRefs from 'merge-refs';
8
+ import {
9
+ CSSProperties,
10
+ FocusEvent,
11
+ forwardRef,
12
+ KeyboardEvent,
13
+ MouseEvent,
14
+ useCallback,
15
+ useEffect,
16
+ useMemo,
17
+ useRef,
18
+ useState,
19
+ } from 'react';
20
+
21
+ // Минимальная ширина поискового input'а в режиме чипов: при пустом вводе input «схлопывается»
22
+ // к этому значению и прижимается к последнему чипу (паритет с легаси BASE_MIN_WIDTH).
23
+ const SEARCH_INPUT_PLUG_MIN_WIDTH = 4;
24
+
25
+ import { FieldDecorator, SIZE, VALIDATION_STATE } from '@cloud-ru/ds-field-decorator';
26
+
27
+ import { TEST_IDS } from '../../constants';
28
+ import { useAdaptiveAutoFocus } from '../../hooks';
29
+ import {
30
+ copyTextToClipboard,
31
+ getAcrylicProps,
32
+ preventSlotMouseDown,
33
+ stopSlotClickPropagation,
34
+ toInputSize,
35
+ useCopyButton,
36
+ } from '../shared';
37
+ import fieldStyles from '../shared/styles.module.scss';
38
+ import styles from './styles.module.scss';
39
+ import { FieldSelectProps } from './types';
40
+ import { extractAppearance, extractLabel, filterItems, findItem, isMultiple, TAG_SIZE_MAP } from './utils';
41
+
42
+ export const FieldSelect = forwardRef<HTMLInputElement, FieldSelectProps>(function FieldSelect(props, ref) {
43
+ const {
44
+ label = '',
45
+ labelTooltip,
46
+ caption,
47
+ hint,
48
+ error,
49
+ validationState = VALIDATION_STATE.Default,
50
+ showHintIcon,
51
+ length,
52
+ required,
53
+ size = SIZE.M,
54
+ className,
55
+ fieldClassName,
56
+ items,
57
+ pinTop,
58
+ pinBottom,
59
+ placeholder = '',
60
+ iconBefore,
61
+ prefix,
62
+ postfix,
63
+ disabled,
64
+ readonly: readOnly,
65
+ background = true,
66
+ open: openProp,
67
+ onOpenChange,
68
+ placement,
69
+ widthStrategy = 'eq',
70
+ searchable = true,
71
+ search,
72
+ autocomplete = false,
73
+ addOptionByEnter = false,
74
+ resetSearchOnOptionSelection = true,
75
+ showClearButton = true,
76
+ showCopyButton = true,
77
+ onCopyButtonClick,
78
+ id,
79
+ name,
80
+ autoFocus,
81
+ layoutPresets,
82
+ onFocus: onFocusProp,
83
+ onBlur: onBlurProp,
84
+ enableFuzzySearch = true,
85
+ selectedOptionFormatter,
86
+ footer,
87
+ footerActiveElementsRefs,
88
+ loading,
89
+ noDataState,
90
+ noResultsState,
91
+ errorDataState,
92
+ dataError,
93
+ dataFiltered,
94
+ virtualized,
95
+ scrollToSelectedItem,
96
+ limitedScrollHeight,
97
+ untouchableScrollbars,
98
+ closeOnPopstate,
99
+ onKeyDown: onKeyDownProp,
100
+ 'data-test-id': dataTestId = TEST_IDS.fieldSelect,
101
+ } = props;
102
+ const multiple = isMultiple(props);
103
+ const chips = multiple ? (props.chips ?? true) : false;
104
+ const removeByBackspace = multiple ? (props.removeByBackspace ?? true) : false;
105
+ const closeDroplistOnItemClick = props.closeDroplistOnItemClick ?? !multiple;
106
+
107
+ const resolvedAutoFocus = useAdaptiveAutoFocus(autoFocus, layoutPresets);
108
+
109
+ const inputRef = useRef<HTMLInputElement>(null);
110
+ // Реальный <input>: `inputRef` уходит в Droplist как `triggerElemRef`, а `PopoverPrivate`
111
+ // перезаписывает его `.current` своей span-обёрткой (см. MR!101). Отдельный ref на сам input
112
+ // нужен для `useButtonNavigation` (курсор/фокус) и возврата фокуса после очистки.
113
+ const inputElementRef = useRef<HTMLInputElement>(null);
114
+ const clearButtonRef = useRef<HTMLButtonElement>(null);
115
+ const copyButtonRef = useRef<HTMLButtonElement>(null);
116
+ // Контейнер чипов+input (замер доступной ширины) и скрытый плаг (замер ширины введённого текста)
117
+ // для расчёта minWidth поискового input'а в режиме чипов — см. searchInput ниже.
118
+ const contentRef = useRef<HTMLDivElement>(null);
119
+ const inputPlugRef = useRef<HTMLSpanElement>(null);
120
+ const [chipInputMinWidth, setChipInputMinWidth] = useState<number | undefined>(undefined);
121
+ const [hover, setHover] = useState(false);
122
+ const [focusVisible, setFocusVisible] = useState(false);
123
+ const [openLocal, setOpenLocal] = useState(false);
124
+ const open = openProp ?? openLocal;
125
+
126
+ const [singleLocal, setSingleLocal] = useState<ItemId | undefined>(
127
+ !multiple ? (props.defaultValue as ItemId | undefined) : undefined,
128
+ );
129
+ const [multipleLocal, setMultipleLocal] = useState<ItemId[]>(
130
+ // Array.isArray-страховка: при multiple defaultValue обязан быть массивом по типам,
131
+ // но «расхлябанный» вызов (например, Storybook-spread, где single-default остаётся
132
+ // строкой при переключении selection) не должен ронять компонент на `.map` строки.
133
+ multiple && Array.isArray(props.defaultValue) ? (props.defaultValue as ItemId[]) : [],
134
+ );
135
+ const singleValue = useMemo<ItemId | undefined>(
136
+ () => (!multiple ? ((props.value as ItemId | undefined) ?? singleLocal) : undefined),
137
+ [multiple, props.value, singleLocal],
138
+ );
139
+ const multipleValue = useMemo<ItemId[]>(() => {
140
+ if (!multiple) {
141
+ return [];
142
+ }
143
+ return Array.isArray(props.value) ? (props.value as ItemId[]) : multipleLocal;
144
+ }, [multiple, props.value, multipleLocal]);
145
+
146
+ const effectiveValidationState = useMemo(
147
+ () => (error ? VALIDATION_STATE.Error : validationState),
148
+ [error, validationState],
149
+ );
150
+
151
+ const allItems = useMemo(() => [...(pinTop ?? []), ...items, ...(pinBottom ?? [])], [items, pinTop, pinBottom]);
152
+
153
+ const selectedPairs = useMemo(() => {
154
+ if (!multiple || multipleValue.length === 0) {
155
+ return [];
156
+ }
157
+
158
+ // Неизвестное значение (ещё не загружены опции) показываем по его id, не теряем (паритет с легаси).
159
+ return multipleValue.map(id => {
160
+ const item = findItem(allItems, id);
161
+
162
+ return {
163
+ id,
164
+ label: item ? extractLabel(item) : String(id),
165
+ disabled: Boolean(item?.disabled),
166
+ appearance: extractAppearance(item),
167
+ };
168
+ });
169
+ }, [allItems, multiple, multipleValue]);
170
+
171
+ const formatPair = useCallback(
172
+ (pair: { id: ItemId; label: string }) => (selectedOptionFormatter ? selectedOptionFormatter(pair) : pair.label),
173
+ [selectedOptionFormatter],
174
+ );
175
+
176
+ const selectedLabel = useMemo(() => {
177
+ if (!multiple) {
178
+ if (singleValue === undefined) {
179
+ return '';
180
+ }
181
+
182
+ const item = findItem(allItems, singleValue);
183
+ const label = item ? extractLabel(item) : String(singleValue);
184
+
185
+ return formatPair({ id: singleValue, label });
186
+ }
187
+
188
+ if (chips) {
189
+ return '';
190
+ }
191
+
192
+ if ('formatSelected' in props && props.formatSelected) {
193
+ return props.formatSelected(selectedPairs);
194
+ }
195
+
196
+ return selectedPairs.map(formatPair).join(', ');
197
+ }, [allItems, multiple, singleValue, selectedPairs, chips, props, formatPair]);
198
+
199
+ // Строка поиска: controlled через `search.value`, иначе локальный state. Auto-sync к выбранному
200
+ // значению делаем через СТАБИЛЬНЫЙ setLocalInput, а не через setInputValue в deps эффекта —
201
+ // иначе нестабильная ссылка сеттера зацикливает эффект (сбрасывала бы значение постоянно).
202
+ const [localInput, setLocalInput] = useState(search?.defaultValue ?? selectedLabel);
203
+ const inputValue = search?.value !== undefined ? search.value : localInput;
204
+ const [typing, setTyping] = useState(false);
205
+
206
+ const setInputValue = useCallback(
207
+ (next: string) => {
208
+ if (search?.value === undefined) {
209
+ setLocalInput(next);
210
+ }
211
+ search?.onChange?.(next);
212
+ },
213
+ [search],
214
+ );
215
+
216
+ useEffect(() => {
217
+ if (!typing && resetSearchOnOptionSelection && search?.value === undefined) {
218
+ setLocalInput(selectedLabel);
219
+ }
220
+ }, [selectedLabel, typing, resetSearchOnOptionSelection, search]);
221
+
222
+ // autocomplete — клиентскую фильтрацию не делаем, список берётся из items как есть (серверный поиск).
223
+ const filteredItems = useMemo(() => {
224
+ if (!searchable || autocomplete || !typing) {
225
+ return items;
226
+ }
227
+
228
+ return filterItems(items, inputValue, enableFuzzySearch);
229
+ }, [searchable, autocomplete, typing, items, inputValue, enableFuzzySearch]);
230
+
231
+ const filteredPinTop = useMemo(() => {
232
+ if (!searchable || autocomplete || !typing || !pinTop) {
233
+ return pinTop;
234
+ }
235
+
236
+ return filterItems(pinTop, inputValue, enableFuzzySearch);
237
+ }, [searchable, autocomplete, typing, pinTop, inputValue, enableFuzzySearch]);
238
+
239
+ const filteredPinBottom = useMemo(() => {
240
+ if (!searchable || autocomplete || !typing || !pinBottom) {
241
+ return pinBottom;
242
+ }
243
+
244
+ return filterItems(pinBottom, inputValue, enableFuzzySearch);
245
+ }, [searchable, autocomplete, typing, pinBottom, inputValue, enableFuzzySearch]);
246
+
247
+ const handleOpenChange = useCallback(
248
+ (next: boolean) => {
249
+ // Дроплист открывается кликом по полю (Droplist дёргает onOpenChange(true)).
250
+ // readonly/disabled поле открывать нельзя — блокируем открытие, закрытие разрешаем.
251
+ if (next && (disabled || readOnly)) return;
252
+ if (openProp === undefined) {
253
+ setOpenLocal(next);
254
+ }
255
+
256
+ onOpenChange?.(next);
257
+
258
+ if (!next) {
259
+ setTyping(false);
260
+
261
+ if (resetSearchOnOptionSelection) {
262
+ setInputValue(selectedLabel);
263
+ }
264
+ }
265
+ },
266
+ [openProp, onOpenChange, disabled, readOnly, resetSearchOnOptionSelection, setInputValue, selectedLabel],
267
+ );
268
+
269
+ const handleSingleChange = (next: ItemId | undefined) => {
270
+ if (multiple) {
271
+ return;
272
+ }
273
+
274
+ if (props.value === undefined) {
275
+ setSingleLocal(next);
276
+ }
277
+
278
+ props.onChange?.(next);
279
+ setTyping(false);
280
+ };
281
+
282
+ const handleMultipleChange = (next: ItemId[]) => {
283
+ if (!multiple) {
284
+ return;
285
+ }
286
+
287
+ if (props.value === undefined) {
288
+ setMultipleLocal(next);
289
+ }
290
+
291
+ props.onChange?.(next);
292
+
293
+ if (searchable) {
294
+ setTyping(false);
295
+
296
+ if (resetSearchOnOptionSelection) {
297
+ setInputValue('');
298
+ }
299
+ }
300
+ };
301
+
302
+ const handleInputChange = (next: string) => {
303
+ setInputValue(next);
304
+ setTyping(true);
305
+
306
+ if (!open) {
307
+ handleOpenChange(true);
308
+ }
309
+ };
310
+
311
+ const handleClear = useCallback(() => {
312
+ if (multiple) {
313
+ // Отключённые выбранные значения остаются при очистке (паритет с легаси).
314
+ const keepDisabled = selectedPairs.filter(pair => pair.disabled).map(pair => pair.id);
315
+
316
+ if (props.value === undefined) {
317
+ setMultipleLocal(keepDisabled);
318
+ }
319
+
320
+ props.onChange?.(keepDisabled);
321
+ } else {
322
+ if (props.value === undefined) {
323
+ setSingleLocal(undefined);
324
+ }
325
+
326
+ props.onChange?.(undefined);
327
+ }
328
+
329
+ setInputValue('');
330
+ setTyping(false);
331
+ inputElementRef.current?.focus();
332
+
333
+ // required → переоткрыть дроплист после очистки (паритет с легаси onClear).
334
+ if (required && !open) {
335
+ handleOpenChange(true);
336
+ }
337
+ }, [multiple, props, selectedPairs, setInputValue, required, open, handleOpenChange]);
338
+
339
+ const removeChip = useCallback(
340
+ (id: ItemId) => {
341
+ if (!multiple) {
342
+ return;
343
+ }
344
+
345
+ // Нельзя удалить отключённый чип (паритет с легаси).
346
+ if (selectedPairs.find(pair => pair.id === id)?.disabled) {
347
+ return;
348
+ }
349
+
350
+ const next = multipleValue.filter(v => v !== id);
351
+
352
+ if (props.value === undefined) {
353
+ setMultipleLocal(next);
354
+ }
355
+
356
+ props.onChange?.(next);
357
+ },
358
+ [multiple, multipleValue, props, selectedPairs],
359
+ );
360
+
361
+ const handleRemoveChip = useCallback(
362
+ (id: ItemId) => (e: MouseEvent<HTMLButtonElement>) => {
363
+ e.stopPropagation();
364
+ removeChip(id);
365
+ },
366
+ [removeChip],
367
+ );
368
+
369
+ const hasValue = multiple ? multipleValue.length > 0 : singleValue !== undefined;
370
+ const showClear = Boolean(showClearButton && hasValue && !disabled && !readOnly);
371
+ const showCopy = Boolean(showCopyButton && hasValue && readOnly && !disabled);
372
+ const valueToCopy = multiple ? selectedPairs.map(formatPair).join(', ') : selectedLabel;
373
+
374
+ const onCopy = useCallback(async () => {
375
+ const copied = await copyTextToClipboard(valueToCopy);
376
+ if (copied) {
377
+ onCopyButtonClick?.();
378
+ }
379
+ return copied;
380
+ }, [onCopyButtonClick, valueToCopy]);
381
+
382
+ // Clear/Copy как roving-postfix: ArrowRight из input (курсор в конце или readonly) уводит на
383
+ // кнопку, ArrowLeft — обратно в поле. Паритет с FieldCombo. `inputElementRef` — реальный input,
384
+ // т.к. `inputRef` клобберится popover'ом.
385
+ const clearButtonSettings = useClearButton({
386
+ clearButtonRef,
387
+ showClearButton: showClear,
388
+ size: toInputSize(size),
389
+ onClear: handleClear,
390
+ onDown: preventSlotMouseDown,
391
+ disabled: Boolean(disabled || readOnly),
392
+ dataTestId: TEST_IDS.fieldSelectClear,
393
+ });
394
+
395
+ const copyButtonSettings = useCopyButton({
396
+ copyButtonRef,
397
+ showCopyButton: showCopy,
398
+ size: toInputSize(size),
399
+ onCopy,
400
+ disabled: Boolean(disabled),
401
+ dataTestId: TEST_IDS.fieldSelectCopy,
402
+ });
403
+
404
+ const { postfixButtons, inputTabIndex, onInputKeyDown } = useButtonNavigation({
405
+ inputRef: inputElementRef,
406
+ postfixButtons: useMemo(() => [clearButtonSettings, copyButtonSettings], [clearButtonSettings, copyButtonSettings]),
407
+ readonly: !searchable || Boolean(readOnly),
408
+ submitKeys: ['Enter', 'Space', 'Tab'],
409
+ });
410
+
411
+ const handleTriggerKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
412
+ // Пробрасываем consumer onKeyDown до внутренней обработки (паритет с легаси useHandleOnKeyDown).
413
+ onKeyDownProp?.(event);
414
+
415
+ // Roving-навигация по clear/copy (ArrowRight/ArrowLeft). ArrowDown/Enter ниже не перехватываются.
416
+ onInputKeyDown(event);
417
+
418
+ if (disabled || readOnly) {
419
+ return;
420
+ }
421
+
422
+ if (event.key === 'ArrowDown' && !open) {
423
+ event.preventDefault();
424
+ handleOpenChange(true);
425
+ }
426
+
427
+ if (event.key === 'Escape' && open) {
428
+ event.preventDefault();
429
+ handleOpenChange(false);
430
+ }
431
+
432
+ if (addOptionByEnter && event.key === 'Enter' && inputValue !== '') {
433
+ // Зафиксировать введённый текст как новый выбор (создание опции «на лету»).
434
+ event.preventDefault();
435
+
436
+ if (multiple) {
437
+ if (!multipleValue.includes(inputValue)) {
438
+ handleMultipleChange([...multipleValue, inputValue]);
439
+ }
440
+ } else {
441
+ handleSingleChange(inputValue);
442
+ }
443
+
444
+ return;
445
+ }
446
+
447
+ if (event.key === 'Enter' && !open) {
448
+ event.preventDefault();
449
+ handleOpenChange(true);
450
+ }
451
+
452
+ if (event.key === 'Backspace' && removeByBackspace && multiple && inputValue === '' && multipleValue.length > 0) {
453
+ event.preventDefault();
454
+ const last = multipleValue[multipleValue.length - 1];
455
+ removeChip(last);
456
+ }
457
+ };
458
+
459
+ const handleTriggerMouseEnter = useCallback(() => {
460
+ if (!readOnly && !disabled) {
461
+ setHover(true);
462
+ }
463
+ }, [readOnly, disabled]);
464
+
465
+ const handleTriggerMouseLeave = useCallback(() => {
466
+ setHover(false);
467
+ }, []);
468
+
469
+ const handleTriggerClick = useCallback(() => {
470
+ if (disabled || readOnly) {
471
+ return;
472
+ }
473
+
474
+ if (!open) {
475
+ handleOpenChange(true);
476
+ }
477
+
478
+ inputElementRef.current?.focus();
479
+ }, [disabled, readOnly, open, handleOpenChange]);
480
+
481
+ const handleInputFocus = useCallback(
482
+ (event: FocusEvent<HTMLInputElement>) => {
483
+ setFocusVisible(event.target.matches(':focus-visible'));
484
+
485
+ if (searchable) {
486
+ event.target.select();
487
+ }
488
+
489
+ onFocusProp?.(event);
490
+ },
491
+ [searchable, onFocusProp],
492
+ );
493
+
494
+ const handleInputBlur = useCallback(
495
+ (event: FocusEvent<HTMLInputElement>) => {
496
+ setFocusVisible(false);
497
+ onBlurProp?.(event);
498
+ },
499
+ [onBlurProp],
500
+ );
501
+
502
+ const droplistSelection = multiple
503
+ ? { mode: 'multiple' as const, value: multipleValue, onChange: handleMultipleChange }
504
+ : { mode: 'single' as const, value: singleValue, onChange: handleSingleChange };
505
+
506
+ const hasChips = chips && selectedPairs.length > 0;
507
+
508
+ // minWidth поискового input'а в режиме чипов: ширина введённого текста (скрытый `.inputPlug`),
509
+ // но не больше ширины строки чипов (`contentRef`). Замер в layout-эффекте (до paint, без мигания):
510
+ // пустой ввод → ~4px (input прижат к последнему чипу), длинный ввод → перенос на новую строку
511
+ // через flex-wrap. Без чипов minWidth не задаётся (input занимает всю строку как single-select).
512
+ useLayoutEffect(() => {
513
+ if (!hasChips) {
514
+ setChipInputMinWidth(undefined);
515
+ return;
516
+ }
517
+ const content = contentRef.current?.clientWidth ?? SEARCH_INPUT_PLUG_MIN_WIDTH;
518
+ const plug = inputPlugRef.current?.clientWidth ?? SEARCH_INPUT_PLUG_MIN_WIDTH;
519
+ setChipInputMinWidth(Math.min(content, Math.max(plug, SEARCH_INPUT_PLUG_MIN_WIDTH)));
520
+ }, [hasChips, inputValue, selectedPairs.length]);
521
+
522
+ const searchInputField = (
523
+ <InputPrivate
524
+ ref={mergeRefs(ref, inputRef, inputElementRef)}
525
+ className={cn(fieldStyles.fieldInput, !searchable && styles.readonlyCursor)}
526
+ value={inputValue}
527
+ onChange={searchable ? handleInputChange : undefined}
528
+ placeholder={selectedLabel ? '' : placeholder}
529
+ disabled={disabled}
530
+ readonly={!searchable || readOnly}
531
+ id={id}
532
+ name={name}
533
+ autoFocus={resolvedAutoFocus}
534
+ tabIndex={inputTabIndex}
535
+ onFocus={handleInputFocus}
536
+ onBlur={handleInputBlur}
537
+ onKeyDown={handleTriggerKeyDown}
538
+ data-test-id={TEST_IDS.fieldSelectInput}
539
+ />
540
+ );
541
+
542
+ // В режиме чипов input заворачивается в `.inputWrapper` с динамической minWidth (флоу за чипами);
543
+ // без чипов — обычный `inputArea` на всю строку.
544
+ const searchInput = hasChips ? (
545
+ <div className={styles.inputWrapper} style={{ minWidth: chipInputMinWidth } as CSSProperties}>
546
+ {searchInputField}
547
+ </div>
548
+ ) : (
549
+ <div className={fieldStyles.inputArea}>{searchInputField}</div>
550
+ );
551
+
552
+ const trigger = (
553
+ // Семантика: combobox-обёртка с InputPrivate внутри (input = focusable element).
554
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions
555
+ <div
556
+ className={cn(fieldStyles.fieldWrapper, styles.trigger, fieldClassName)}
557
+ data-size={size}
558
+ data-validation-state={effectiveValidationState}
559
+ data-disabled={disabled || undefined}
560
+ data-readonly={readOnly || undefined}
561
+ data-withbackground={background || undefined}
562
+ data-focusvisible={focusVisible || open || undefined}
563
+ data-hover={!readOnly && !disabled && hover ? true : undefined}
564
+ data-test-id={dataTestId}
565
+ onMouseEnter={handleTriggerMouseEnter}
566
+ onMouseLeave={handleTriggerMouseLeave}
567
+ onClick={handleTriggerClick}
568
+ >
569
+ <div className={fieldStyles.backgroundWrapper}>
570
+ <div
571
+ className={fieldStyles.materialLayer}
572
+ {...getAcrylicProps({
573
+ validationState: effectiveValidationState,
574
+ disabled,
575
+ readonly: readOnly,
576
+ hover,
577
+ focusVisible: focusVisible || open,
578
+ })}
579
+ >
580
+ <div className={fieldStyles.acrylicBg} aria-hidden />
581
+ </div>
582
+ <div className={fieldStyles.borderStateLayer} data-state='borderOnBackground' />
583
+ <div className={fieldStyles.focusLayer} />
584
+ </div>
585
+ <div className={fieldStyles.fieldContainer}>
586
+ <div className={fieldStyles.contentWrapper}>
587
+ {iconBefore && <div className={fieldStyles.iconSlot}>{iconBefore}</div>}
588
+ <div className={fieldStyles.inputLine}>
589
+ {prefix && <span className={fieldStyles.prefix}>{prefix}</span>}
590
+ {/* Чипы и поисковый input живут в одном wrap-контейнере (паритет с легаси
591
+ FieldSelect .contentWrapper): input течёт сразу за последним чипом и
592
+ переносится на новую строку только когда не влезает. */}
593
+ {hasChips ? (
594
+ <div className={styles.chipsRow} ref={contentRef} data-test-id={TEST_IDS.fieldSelectChips}>
595
+ {selectedPairs.map(pair => (
596
+ <Tag
597
+ key={String(pair.id)}
598
+ label={formatPair(pair)}
599
+ size={TAG_SIZE_MAP[size]}
600
+ appearance={pair.appearance ?? 'neutral'}
601
+ onDelete={disabled || readOnly || pair.disabled ? undefined : handleRemoveChip(pair.id)}
602
+ />
603
+ ))}
604
+ {searchInput}
605
+ {/* Скрытый плаг: рендерит введённый текст той же типографикой, замеряем его ширину
606
+ для minWidth поискового input'а (см. layout-эффект выше). aria-hidden — вне дерева. */}
607
+ <span ref={inputPlugRef} className={styles.inputPlug} aria-hidden>
608
+ {inputValue}
609
+ </span>
610
+ </div>
611
+ ) : (
612
+ searchInput
613
+ )}
614
+ {/* Clear/Copy — roving-postfix через useButtonNavigation (ArrowRight/ArrowLeft).
615
+ Слот-обёртка гасит всплытие mousedown/click к Droplist-триггеру; test-id и onClick
616
+ живут на <Button> внутри (адресуемый = интерактивный). */}
617
+ {postfixButtons && (
618
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions
619
+ <span
620
+ className={fieldStyles.postfixButtonsSlot}
621
+ onMouseDown={preventSlotMouseDown}
622
+ onClick={stopSlotClickPropagation}
623
+ >
624
+ {postfixButtons}
625
+ </span>
626
+ )}
627
+ {postfix && <span className={fieldStyles.postfix}>{postfix}</span>}
628
+ </div>
629
+ <div className={fieldStyles.iconSlot}>
630
+ <span className={styles.chevron} data-open={open || undefined} aria-hidden>
631
+ <ChevronDownSVG />
632
+ </span>
633
+ </div>
634
+ </div>
635
+ </div>
636
+ </div>
637
+ );
638
+
639
+ return (
640
+ <FieldDecorator
641
+ className={className}
642
+ label={label}
643
+ labelTooltip={labelTooltip}
644
+ caption={caption}
645
+ hint={hint}
646
+ error={error}
647
+ size={size}
648
+ validationState={validationState}
649
+ showHintIcon={showHintIcon}
650
+ length={length}
651
+ required={required}
652
+ labelFor={id}
653
+ disabled={disabled}
654
+ readonly={readOnly}
655
+ >
656
+ <Droplist
657
+ items={filteredItems}
658
+ label={label}
659
+ pinTop={filteredPinTop}
660
+ pinBottom={filteredPinBottom}
661
+ trigger='click'
662
+ placement={placement}
663
+ widthStrategy={widthStrategy}
664
+ triggerElemRef={inputRef}
665
+ size={size}
666
+ open={open}
667
+ onOpenChange={handleOpenChange}
668
+ selection={droplistSelection}
669
+ closeDroplistOnItemClick={closeDroplistOnItemClick}
670
+ footer={footer}
671
+ footerActiveElementsRefs={footerActiveElementsRefs}
672
+ loading={loading}
673
+ noDataState={noDataState}
674
+ noResultsState={noResultsState}
675
+ errorDataState={errorDataState}
676
+ dataError={dataError}
677
+ dataFiltered={dataFiltered ?? (searchable && typing && inputValue !== '')}
678
+ virtualized={virtualized}
679
+ scroll
680
+ scrollToSelectedItem={scrollToSelectedItem}
681
+ limitedScrollHeight={limitedScrollHeight}
682
+ untouchableScrollbars={untouchableScrollbars}
683
+ closeOnPopstate={closeOnPopstate}
684
+ >
685
+ {trigger}
686
+ </Droplist>
687
+ </FieldDecorator>
688
+ );
689
+ });
@@ -0,0 +1,4 @@
1
+ export const SELECTION_MODE = {
2
+ Single: 'single',
3
+ Multiple: 'multiple',
4
+ } as const;
@@ -0,0 +1,3 @@
1
+ export * from './FieldSelect';
2
+ export * from './constants';
3
+ export * from './types';