adata-ui 2.1.41-beta.2 → 2.1.41-beta.21

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 (628) hide show
  1. package/-error.vue +170 -170
  2. package/.editorconfig +12 -12
  3. package/.gitlab-ci.yml +16 -16
  4. package/.nuxtrc +1 -1
  5. package/.nvmrc +1 -1
  6. package/.playground/app.config.ts +5 -5
  7. package/.playground/app.vue +102 -102
  8. package/.playground/nuxt.config.ts +4 -4
  9. package/CLAUDE.md +63 -63
  10. package/README.md +75 -75
  11. package/app.config.ts +28 -28
  12. package/app.vue +8 -8
  13. package/assets/styles/main.css +12 -12
  14. package/assets/styles/theme.css +590 -583
  15. package/assets/styles/ui.css +247 -247
  16. package/components/elements/README.md +1 -1
  17. package/components/elements/a-select-row/ASelectRowV2.vue +210 -210
  18. package/components/elements/a-select-row/index.vue +139 -139
  19. package/components/elements/accordion/AAccordion.vue +199 -199
  20. package/components/elements/accordion/AAccordionGroup/AAccordionGroup.vue +145 -145
  21. package/components/elements/accordion/AAccordionGroup/types.ts +20 -20
  22. package/components/elements/accordion/AAccordionGroup/ui.config.ts +22 -22
  23. package/components/elements/accordion/types.ts +14 -14
  24. package/components/elements/accordion/ui.config.ts +22 -22
  25. package/components/elements/alert/AAlert.vue +52 -52
  26. package/components/elements/banner/ABanner.vue +67 -49
  27. package/components/elements/bg-block/ABgBlock.vue +11 -11
  28. package/components/elements/button/AButton.vue +191 -191
  29. package/components/elements/button/AButtonV2.vue +179 -179
  30. package/components/elements/button/types.ts +24 -24
  31. package/components/elements/button-login/index.vue +19 -19
  32. package/components/elements/calendar/ACalendar.vue +83 -83
  33. package/components/elements/carousel/ACarousel.vue +159 -159
  34. package/components/elements/carousel/config.ui.ts +15 -15
  35. package/components/elements/carousel/types.ts +18 -18
  36. package/components/elements/chip-wrapper/AChipWrapper.vue +81 -81
  37. package/components/elements/chip-wrapper/types.ts +12 -12
  38. package/components/elements/chips/AChips.vue +33 -33
  39. package/components/elements/companies/AOurClients.vue +47 -47
  40. package/components/elements/count-up/ACountUp.vue +54 -54
  41. package/components/elements/curve-block/ACurveBlock.vue +259 -259
  42. package/components/elements/curve-block-v2/ACurveBlockV2.vue +122 -122
  43. package/components/elements/digit-badge/ADigitBadge.vue +86 -86
  44. package/components/elements/empty-box/empty-box-V2.vue +92 -92
  45. package/components/elements/error-template/index.vue +121 -116
  46. package/components/elements/feature-description/AFeatureDescription.vue +112 -112
  47. package/components/elements/illustrations/noAccess.vue +71 -71
  48. package/components/elements/infinite-carousel/AInfiniteCarousel.vue +85 -85
  49. package/components/elements/infinite-scroll/AInfiniteScroll.vue +33 -33
  50. package/components/elements/leave-note/ALeaveNote.vue +102 -102
  51. package/components/elements/pagination/APagination.vue +110 -110
  52. package/components/elements/photos-animation/APhotosAnimation.vue +85 -85
  53. package/components/elements/section-heading/ASectionHeading.vue +35 -35
  54. package/components/elements/segmented/ASegmentedV2.vue +154 -154
  55. package/components/elements/select/ACascadeSelectV2.vue +710 -0
  56. package/components/elements/select/AGroupSelectV2.vue +592 -0
  57. package/components/elements/select/ASelect.vue +448 -448
  58. package/components/elements/select/ASelectMobile.vue +186 -186
  59. package/components/elements/select/ASelectV2.vue +513 -580
  60. package/components/elements/select/cascade-utils.ts +115 -0
  61. package/components/elements/select/types.ts +27 -0
  62. package/components/elements/select/ui/cascade-drawer.vue +146 -0
  63. package/components/elements/select/ui/cascade-panel.vue +237 -0
  64. package/components/elements/select/ui/select-button.vue +112 -112
  65. package/components/elements/select/ui/select-list.vue +99 -99
  66. package/components/elements/select/ui/select-trigger.vue +167 -0
  67. package/components/elements/show-more/AShowMore.vue +35 -35
  68. package/components/elements/show-more/AShowMoreV2.vue +26 -26
  69. package/components/elements/skeleton/ASkeleton.vue +44 -44
  70. package/components/elements/slider/ASlider.vue +175 -175
  71. package/components/elements/slider/ASliderItem.vue +31 -31
  72. package/components/elements/slider/ASliderV2.vue +182 -182
  73. package/components/elements/slider/types.ts +21 -21
  74. package/components/elements/star-rating/AStarRating.vue +189 -189
  75. package/components/elements/star-rating/types.ts +7 -7
  76. package/components/elements/star-rating/ui/Star.vue +74 -74
  77. package/components/elements/status-badge/AStatusBadge.vue +160 -125
  78. package/components/elements/step-slider/AStepSliderColCard.vue +266 -270
  79. package/components/elements/step-slider/AStepSliderColNumber.vue +199 -199
  80. package/components/elements/step-slider/AStepSliderRowCard.vue +197 -0
  81. package/components/elements/table/ATable.vue +93 -93
  82. package/components/elements/table/table.config.ts +32 -32
  83. package/components/elements/tree-select/ATreeSelect.vue +157 -157
  84. package/components/elements/tree-select/TreeService.ts +184 -184
  85. package/components/elements/tree-select/components/ATreeSelectNode.vue +104 -104
  86. package/components/elements/tree-select/types.ts +63 -63
  87. package/components/elements/tree-select/utils.ts +30 -30
  88. package/components/elements/tree-select-mobile/ATreeSelectMobile.vue +79 -79
  89. package/components/elements/tree-select-mobile/components/ATreeSelectNodesMobile.vue +175 -175
  90. package/components/elements/tree-select-modal/ATreeSelectModal.vue +217 -217
  91. package/components/features/color-mode/AColorMode.client.vue +74 -74
  92. package/components/features/dropdown/ADropdown.vue +127 -127
  93. package/components/features/dropdown/ADropdownV2.vue +140 -140
  94. package/components/features/go-top/GoTop.vue +67 -63
  95. package/components/features/lang-switcher/lang-switcher.vue +160 -158
  96. package/components/features/payment/banner/BasicPlusLimitBanner.vue +84 -84
  97. package/components/features/payment/banner/PaymentBanner.vue +88 -88
  98. package/components/features/payment/banner/UpSellBanner.vue +105 -105
  99. package/components/features/payment/process/PaymentKaspiQrSidePanel.vue +180 -180
  100. package/components/features/payment/process/PaymentKaspiRedirectSidePanel.vue +123 -123
  101. package/components/features/payment/process/PaymentMethodSidePanel.vue +138 -138
  102. package/components/features/payment/process/PaymentProcess.vue +162 -158
  103. package/components/features/payment/process/PaymentTopUpSidePanel.vue +134 -119
  104. package/components/features/pk-mobile-services/APkMobileServices.vue +107 -107
  105. package/components/features//321/201hange-version/AChangeVersion.vue +20 -20
  106. package/components/forms/README.md +1 -1
  107. package/components/forms/checkbox/ACheckbox.vue +149 -149
  108. package/components/forms/checkbox/ACheckboxV2.vue +229 -229
  109. package/components/forms/feedback-form/FeedbackForm.vue +177 -177
  110. package/components/forms/feedback-form/FeedbackFormV2.vue +183 -186
  111. package/components/forms/input/AInputV2.vue +580 -560
  112. package/components/forms/input/button/AInputButton.vue +53 -53
  113. package/components/forms/input/date/AInputDate.vue +217 -217
  114. package/components/forms/input/password/AInputPassword.vue +59 -59
  115. package/components/forms/input/password/AInputPasswordV2.vue +72 -0
  116. package/components/forms/input/standard/AInputStandard.vue +424 -424
  117. package/components/forms/input/textarea/ATextarea.vue +207 -207
  118. package/components/forms/input/textarea/ATextareaV2.vue +253 -253
  119. package/components/forms/password/APasswordRequirements.vue +50 -0
  120. package/components/forms/radio-button/ARadioButton.vue +95 -95
  121. package/components/forms/request-demo/ARequestDemo.vue +108 -108
  122. package/components/forms/toggle/AToggle.vue +157 -157
  123. package/components/forms/toggle/AToggleV2.vue +72 -72
  124. package/components/forms/toggle/types.ts +16 -16
  125. package/components/modals/AConfirmationEmail.vue +40 -40
  126. package/components/modals/AboutTariffModal.vue +115 -115
  127. package/components/modals/AnotherDeviceModal.vue +65 -65
  128. package/components/modals/ApplicationAcceptedModal.vue +45 -45
  129. package/components/modals/ConnectingTariffModal.vue +60 -60
  130. package/components/modals/ContactsMobileModel.vue +182 -182
  131. package/components/modals/ContentNavigationModal.vue +100 -100
  132. package/components/modals/Insufficient-funds-modal.vue +62 -62
  133. package/components/modals/LimitReachedModal.vue +44 -44
  134. package/components/modals/NoAccessModal.vue +39 -39
  135. package/components/modals/PaymentMethodModal.vue +101 -101
  136. package/components/modals/RedirectsBanksModal.vue +69 -69
  137. package/components/modals/ReplenishModal.vue +132 -132
  138. package/components/modals/ReportBugConfirmModal.vue +32 -32
  139. package/components/modals/ReportBugModal.vue +172 -172
  140. package/components/modals/Resend.vue +81 -81
  141. package/components/modals/SubmitApplicationModal.vue +126 -126
  142. package/components/modals/id/AuthModal.vue +72 -72
  143. package/components/modals/id/IdAutoLogoutModal.vue +44 -44
  144. package/components/modals/id/IdBanner.vue +41 -41
  145. package/components/modals/id/IdConfirmAccountOtpModal.vue +176 -182
  146. package/components/modals/id/IdConfirmSuccessfulModal.vue +44 -41
  147. package/components/modals/id/IdLoginModal.vue +370 -336
  148. package/components/modals/id/IdModals.vue +171 -125
  149. package/components/modals/id/IdNewPasswordModal.vue +172 -129
  150. package/components/modals/id/IdOtpInput.vue +156 -156
  151. package/components/modals/id/IdPasswordSuccessfulModal.vue +25 -25
  152. package/components/modals/id/IdRecoveryModal.vue +133 -117
  153. package/components/modals/id/IdRegistrationModal.vue +260 -215
  154. package/components/modals/id/IdResetPasswordOtpModal.vue +145 -159
  155. package/components/modals/id/IdTwoFactorModal.vue +111 -126
  156. package/components/navigation/README.md +1 -1
  157. package/components/navigation/bottom-navigation/ABottomNavigation.vue +112 -106
  158. package/components/navigation/bottom-navigation/types.ts +6 -0
  159. package/components/navigation/breadcrumbs/ABreadcrumbs.vue +128 -128
  160. package/components/navigation/breadcrumbs/types.ts +8 -8
  161. package/components/navigation/footer/AFooter.vue +283 -283
  162. package/components/navigation/footer/ui/a-footer-accordion.vue +118 -118
  163. package/components/navigation/header/AHeader.vue +241 -237
  164. package/components/navigation/header/AlmatyContacts.vue +145 -145
  165. package/components/navigation/header/AstanaContacts.vue +67 -67
  166. package/components/navigation/header/CardGallery.vue +252 -252
  167. package/components/navigation/header/ContactMenu.vue +48 -48
  168. package/components/navigation/header/HeaderLink.vue +250 -250
  169. package/components/navigation/header/HeaderUsage.vue +139 -139
  170. package/components/navigation/header/ListItem.vue +32 -32
  171. package/components/navigation/header/NavCard.vue +42 -42
  172. package/components/navigation/header/NavList.vue +73 -73
  173. package/components/navigation/header/NotificationsMenu.vue +50 -50
  174. package/components/navigation/header/ProductMenu.vue +53 -101
  175. package/components/navigation/header/ProfileMenu.vue +168 -168
  176. package/components/navigation/header/SystemNotification.vue +108 -108
  177. package/components/navigation/header/types.ts +28 -28
  178. package/components/navigation/line-tabs/ALineTabs.vue +112 -112
  179. package/components/navigation/line-tabs/types.ts +21 -21
  180. package/components/navigation/mobile-navigation/AMobileNavigation.vue +129 -112
  181. package/components/navigation/pill-tabs/APillTabs.vue +179 -179
  182. package/components/navigation/pill-tabs/APillTabsV2.vue +241 -241
  183. package/components/navigation/pill-tabs/types.ts +24 -24
  184. package/components/navigation/side-navigation/ASideNavigation.vue +406 -406
  185. package/components/overlays/README.md +1 -1
  186. package/components/overlays/drawer/ADrawerV2.vue +342 -335
  187. package/components/overlays/modal/AModal.vue +335 -335
  188. package/components/overlays/modal/AModalV2.vue +401 -400
  189. package/components/overlays/side-panel/ASidePanel.vue +445 -445
  190. package/components/overlays/sideover/ASlideover.vue +351 -351
  191. package/components/overlays/tooltip/ATooltip.vue +238 -238
  192. package/components/overlays/tooltip/ATooltipV2.vue +226 -226
  193. package/components/overlays/tooltip/types.ts +26 -26
  194. package/components/overlays/tooltip/useTooltipTrigger.ts +101 -101
  195. package/components/transitions/ATransitionHeight.vue +67 -67
  196. package/components/utils/index.ts +19 -19
  197. package/components/utils/removeTrailingSlash.ts +7 -7
  198. package/composables/highlight.ts +19 -19
  199. package/composables/modalsState.ts +8 -8
  200. package/composables/projectState.ts +2 -2
  201. package/composables/useAccessTokenCookie.ts +25 -25
  202. package/composables/useActiveNavigation.ts +84 -84
  203. package/composables/useAdaptive.ts +46 -46
  204. package/composables/useBuyTariffs.ts +91 -91
  205. package/composables/useCarouselScroll.ts +49 -49
  206. package/composables/useChipOverflow.ts +92 -92
  207. package/composables/useHeaderNavigationLinks.ts +535 -519
  208. package/composables/useIdModals.ts +36 -36
  209. package/composables/usePayment.ts +81 -78
  210. package/composables/useUIValidation.ts +16 -16
  211. package/composables/useUrls.ts +23 -23
  212. package/eslint.config.mjs +58 -58
  213. package/i18n/i18n.config.ts +21 -21
  214. package/i18n.config.ts +21 -21
  215. package/icons/adata-logo.vue +17 -17
  216. package/icons/ai/ai-diamond-unfilled.vue +26 -0
  217. package/icons/ai/ai-diamond.vue +11 -0
  218. package/icons/ai-assistant.vue +13 -13
  219. package/icons/akimat.vue +20 -20
  220. package/icons/archive.vue +16 -16
  221. package/icons/arrow/arrow-bottom-left-on-square.vue +5 -5
  222. package/icons/arrow/arrow-circle-down.vue +16 -16
  223. package/icons/arrow/arrow-circle-reset.vue +16 -16
  224. package/icons/arrow/arrow-corner.vue +9 -9
  225. package/icons/arrow/arrow-down-left.vue +6 -0
  226. package/icons/arrow/arrow-graph-down.vue +13 -13
  227. package/icons/arrow/arrow-graph-up.vue +14 -14
  228. package/icons/arrow/arrow-left-right.vue +8 -8
  229. package/icons/arrow/arrow-left.vue +14 -14
  230. package/icons/arrow/arrow-right.vue +14 -14
  231. package/icons/arrow/arrow-square-down.vue +15 -15
  232. package/icons/arrow/arrow-top-right-on-square.vue +6 -6
  233. package/icons/arrow/arrow-up-right.vue +6 -0
  234. package/icons/arrow/arrows-in.vue +11 -11
  235. package/icons/arrow/arrows-out.vue +11 -11
  236. package/icons/arrow/arrows-resize-in.vue +38 -38
  237. package/icons/arrow/arrows-resize-out.vue +17 -17
  238. package/icons/arrow-currency-gray.vue +5 -5
  239. package/icons/arrow-currency-green.vue +5 -5
  240. package/icons/arrow-currency-red.vue +5 -5
  241. package/icons/arrow-side-up.vue +6 -6
  242. package/icons/avatar.vue +12 -12
  243. package/icons/bank.vue +5 -5
  244. package/icons/bar-chart-3.vue +3 -3
  245. package/icons/bill.vue +3 -3
  246. package/icons/block.vue +16 -16
  247. package/icons/book-open.vue +3 -3
  248. package/icons/bookmark/bookmark-filled.vue +19 -19
  249. package/icons/bookmark/bookmark.vue +5 -5
  250. package/icons/bot.vue +52 -52
  251. package/icons/braces.vue +3 -3
  252. package/icons/briefcase.vue +14 -14
  253. package/icons/browsers/browser-duck.vue +65 -65
  254. package/icons/browsers/browser-google.vue +30 -30
  255. package/icons/browsers/browser-yandex.vue +13 -13
  256. package/icons/building-org.vue +15 -0
  257. package/icons/building-vector.vue +7 -7
  258. package/icons/building.vue +9 -9
  259. package/icons/calculator.vue +7 -7
  260. package/icons/calendar.vue +24 -24
  261. package/icons/calendar2.vue +23 -0
  262. package/icons/car-avto.vue +19 -0
  263. package/icons/car-front.vue +21 -0
  264. package/icons/car-side.vue +16 -16
  265. package/icons/car.vue +7 -7
  266. package/icons/cart.vue +15 -15
  267. package/icons/chart-bar.vue +5 -5
  268. package/icons/chart-no-axes-column.vue +3 -3
  269. package/icons/chart-pie-piece.vue +6 -0
  270. package/icons/chart-pie.vue +16 -16
  271. package/icons/check/check-circle.vue +6 -6
  272. package/icons/check/check-outline.vue +3 -3
  273. package/icons/check/check-shield.vue +14 -14
  274. package/icons/check/check.vue +17 -17
  275. package/icons/check/checkmark-circle.vue +6 -6
  276. package/icons/check-sb.vue +7 -7
  277. package/icons/checkbox/checkbox-active.vue +17 -17
  278. package/icons/checkbox/checkbox-empty.vue +10 -10
  279. package/icons/checkbox/checkbox-intermediate.vue +7 -7
  280. package/icons/chevron/chevron-down-outline.vue +3 -3
  281. package/icons/chevron/chevron-down.vue +5 -5
  282. package/icons/chevron/chevron-left.vue +5 -5
  283. package/icons/chevron/chevron-right.vue +5 -5
  284. package/icons/chevron/chevron-up.vue +5 -5
  285. package/icons/chevron/double-chevron-right.vue +12 -12
  286. package/icons/clipboard-check.vue +18 -0
  287. package/icons/clipboard-list.vue +3 -3
  288. package/icons/clipboard-text.vue +7 -7
  289. package/icons/clock-outline.vue +3 -3
  290. package/icons/clock-rotate-ccw.vue +7 -0
  291. package/icons/clock-time.vue +24 -24
  292. package/icons/clock.vue +25 -25
  293. package/icons/close.vue +16 -16
  294. package/icons/cloud/cloud-upload.vue +3 -3
  295. package/icons/code.vue +9 -9
  296. package/icons/cog.vue +29 -0
  297. package/icons/company/company-bazis.vue +21 -21
  298. package/icons/company/company-bereke.vue +25 -25
  299. package/icons/company/company-bigroup.vue +16 -16
  300. package/icons/company/company-erg.vue +17 -17
  301. package/icons/company/company-forte.vue +21 -21
  302. package/icons/company/company-halyk.vue +15 -15
  303. package/icons/company/company-jusan.vue +31 -31
  304. package/icons/company/company-kaspi.vue +14 -14
  305. package/icons/company/company-kazzinc.vue +22 -22
  306. package/icons/company/company-mycar.vue +13 -13
  307. package/icons/company/company-samruk.vue +21 -21
  308. package/icons/company-egov-small.vue +15 -15
  309. package/icons/company.vue +8 -8
  310. package/icons/compare-outlined.vue +8 -8
  311. package/icons/copy.vue +7 -7
  312. package/icons/credit-card.vue +17 -0
  313. package/icons/crown.vue +6 -0
  314. package/icons/currency/currency-dollar.vue +16 -16
  315. package/icons/currency/currency-down.vue +22 -22
  316. package/icons/currency/currency-eur.vue +57 -57
  317. package/icons/currency/currency-rub.vue +7 -7
  318. package/icons/currency/currency-tenge.vue +9 -9
  319. package/icons/currency/currency-usd.vue +221 -221
  320. package/icons/currency/currency-yuan.vue +25 -25
  321. package/icons/database.vue +3 -3
  322. package/icons/default-avatar.vue +20 -20
  323. package/icons/delete.vue +7 -7
  324. package/icons/document.vue +5 -5
  325. package/icons/dots-horizontal.vue +31 -31
  326. package/icons/download-line.vue +13 -13
  327. package/icons/download-outline.vue +3 -3
  328. package/icons/download-rounded.vue +15 -15
  329. package/icons/download.vue +11 -11
  330. package/icons/edit.vue +7 -7
  331. package/icons/education.vue +16 -16
  332. package/icons/egov-small.vue +7 -7
  333. package/icons/egov.vue +41 -41
  334. package/icons/elicense.vue +38 -38
  335. package/icons/excel-icon.vue +14 -14
  336. package/icons/expand-window.vue +7 -7
  337. package/icons/eye-closed.vue +26 -26
  338. package/icons/eye-open.vue +7 -7
  339. package/icons/eye-opened.vue +23 -23
  340. package/icons/factory.vue +7 -0
  341. package/icons/favourite-bookmark-outline.vue +17 -17
  342. package/icons/file/file-check-corner.vue +3 -3
  343. package/icons/file/file-scan.vue +5 -0
  344. package/icons/file/file-spreadsheet.vue +3 -3
  345. package/icons/file/file.vue +16 -16
  346. package/icons/file/files.vue +16 -16
  347. package/icons/file/folder.vue +15 -0
  348. package/icons/file-download.vue +8 -0
  349. package/icons/file-json.vue +3 -3
  350. package/icons/file-pen.vue +31 -31
  351. package/icons/file-search.vue +3 -3
  352. package/icons/file-stack.vue +31 -31
  353. package/icons/file-text.vue +3 -3
  354. package/icons/file-types/excel.vue +131 -131
  355. package/icons/file-types/html.vue +13 -13
  356. package/icons/file-types/jpeg.vue +16 -16
  357. package/icons/file-types/pdf.vue +11 -11
  358. package/icons/file-types/png.vue +11 -11
  359. package/icons/file-types/rar.vue +11 -11
  360. package/icons/file-types/word.vue +50 -50
  361. package/icons/file-types/zip.vue +11 -11
  362. package/icons/filter.vue +5 -5
  363. package/icons/filter2.vue +22 -0
  364. package/icons/flag.vue +7 -7
  365. package/icons/flask-conical.vue +3 -3
  366. package/icons/folder-open.vue +3 -3
  367. package/icons/funnel.vue +5 -0
  368. package/icons/gauge.vue +17 -17
  369. package/icons/gift-outline.vue +3 -3
  370. package/icons/gift.vue +8 -8
  371. package/icons/git-compare.vue +38 -38
  372. package/icons/git-fork.vue +3 -3
  373. package/icons/globe-2.vue +3 -3
  374. package/icons/globe.vue +16 -16
  375. package/icons/google.vue +19 -41
  376. package/icons/gradiend-icons/files-gradient.vue +22 -22
  377. package/icons/gradiend-icons/hammer-gradient.vue +62 -62
  378. package/icons/graduation-cap.vue +31 -31
  379. package/icons/graph-loading.vue +34 -34
  380. package/icons/graph-pie.vue +14 -14
  381. package/icons/hand/hand-thumb-up-filled.vue +5 -5
  382. package/icons/hand/hand-thumb-up.vue +5 -5
  383. package/icons/hand-with-phone/hand-with-phone-dark.vue +52 -52
  384. package/icons/hand-with-phone/hand-with-phone-light.vue +52 -52
  385. package/icons/handshake.vue +5 -5
  386. package/icons/hcheck.vue +14 -14
  387. package/icons/hdocument.vue +7 -7
  388. package/icons/headset.vue +6 -6
  389. package/icons/heart-pulse.vue +6 -0
  390. package/icons/help-question-circle.vue +26 -26
  391. package/icons/history.vue +5 -5
  392. package/icons/home.vue +14 -14
  393. package/icons/horizontal-more.vue +13 -13
  394. package/icons/hot-line.vue +6 -6
  395. package/icons/hummer.vue +16 -16
  396. package/icons/id.vue +7 -7
  397. package/icons/in-row-type.vue +46 -46
  398. package/icons/in-table-type.vue +29 -29
  399. package/icons/info/info-circle.vue +29 -29
  400. package/icons/invoice.vue +8 -8
  401. package/icons/judge.vue +14 -14
  402. package/icons/kaspi-qr.vue +13 -13
  403. package/icons/key-round.vue +3 -3
  404. package/icons/landmark.vue +3 -3
  405. package/icons/layers.vue +3 -3
  406. package/icons/letter/letter-a.vue +8 -8
  407. package/icons/letter/letter-b.vue +6 -6
  408. package/icons/letter/letter-c.vue +6 -6
  409. package/icons/letter/letter-d.vue +6 -6
  410. package/icons/letter/letter-e.vue +6 -6
  411. package/icons/link-chain.vue +28 -28
  412. package/icons/link-url.vue +3 -3
  413. package/icons/link.vue +7 -7
  414. package/icons/linkedin.vue +24 -24
  415. package/icons/list-checks.vue +3 -0
  416. package/icons/list-tree.vue +3 -3
  417. package/icons/loader-circle.vue +27 -27
  418. package/icons/location.vue +8 -8
  419. package/icons/lock.vue +5 -5
  420. package/icons/log-out.vue +21 -0
  421. package/icons/logo.vue +15 -15
  422. package/icons/logout.vue +16 -16
  423. package/icons/magnify/magnifying-glass-minus.vue +8 -8
  424. package/icons/magnify/magnifying-glass-plus.vue +9 -9
  425. package/icons/magnify/magnifying-glass.vue +9 -9
  426. package/icons/mail-question-mark.vue +8 -8
  427. package/icons/mail.vue +7 -7
  428. package/icons/mailru.vue +19 -34
  429. package/icons/main-filter.vue +10 -5
  430. package/icons/map/map-pin-filled.vue +7 -7
  431. package/icons/map/map-pin-rect.vue +15 -15
  432. package/icons/map/map-pin.vue +7 -7
  433. package/icons/map-marker-start.vue +7 -7
  434. package/icons/map-paper.vue +5 -5
  435. package/icons/medal.vue +7 -7
  436. package/icons/menu-filled.vue +20 -20
  437. package/icons/menu.vue +8 -8
  438. package/icons/message/message-square.vue +3 -3
  439. package/icons/message/message.vue +12 -12
  440. package/icons/message-dotted.vue +30 -30
  441. package/icons/messages-square.vue +3 -3
  442. package/icons/minus/minus-circle.vue +10 -10
  443. package/icons/money.vue +5 -5
  444. package/icons/monitoring.vue +10 -10
  445. package/icons/moon.vue +8 -8
  446. package/icons/more.vue +29 -29
  447. package/icons/network.vue +3 -3
  448. package/icons/newspaper.vue +3 -3
  449. package/icons/note-pencil.vue +14 -14
  450. package/icons/note.vue +14 -14
  451. package/icons/notification-error.vue +17 -17
  452. package/icons/notification.vue +16 -16
  453. package/icons/paperclip.vue +7 -7
  454. package/icons/payment/payment-card.vue +6 -6
  455. package/icons/payment/payment-kaspi.vue +11 -11
  456. package/icons/person-vector.vue +7 -7
  457. package/icons/person-with-briefcase.vue +10 -10
  458. package/icons/person.vue +22 -22
  459. package/icons/phone-call.vue +6 -6
  460. package/icons/phone-filled.vue +5 -5
  461. package/icons/phone-reversed.vue +3 -3
  462. package/icons/phone.vue +7 -7
  463. package/icons/plus/plus-circle.vue +8 -8
  464. package/icons/plus/plus.vue +13 -13
  465. package/icons/profile.vue +5 -5
  466. package/icons/qr-code.vue +25 -0
  467. package/icons/radio/radio-check.vue +8 -8
  468. package/icons/radio/radio-empty.vue +10 -10
  469. package/icons/receipt-text.vue +19 -0
  470. package/icons/receipt.vue +8 -8
  471. package/icons/recycle.vue +10 -0
  472. package/icons/refresh-cw.vue +8 -0
  473. package/icons/reply.vue +15 -0
  474. package/icons/robot-outline.vue +3 -3
  475. package/icons/robot.vue +14 -14
  476. package/icons/route.vue +7 -7
  477. package/icons/sanctions.vue +8 -8
  478. package/icons/scales/scale-outline.vue +3 -3
  479. package/icons/scales/scale.vue +16 -16
  480. package/icons/scales/scales.vue +5 -5
  481. package/icons/scales/standing-scales.vue +15 -15
  482. package/icons/scan-search.vue +3 -3
  483. package/icons/search-outline.vue +3 -3
  484. package/icons/search.vue +27 -27
  485. package/icons/send.vue +3 -3
  486. package/icons/settings.vue +26 -26
  487. package/icons/share/share-alt.vue +5 -5
  488. package/icons/share/share.vue +14 -14
  489. package/icons/shield-alert.vue +31 -31
  490. package/icons/shield-check.vue +20 -0
  491. package/icons/shield-verified.vue +14 -14
  492. package/icons/ship.vue +8 -8
  493. package/icons/shuffle.vue +3 -0
  494. package/icons/siren.vue +23 -0
  495. package/icons/socials/face-book.vue +15 -15
  496. package/icons/socials/facebook-color.vue +16 -16
  497. package/icons/socials/globe-color.vue +16 -16
  498. package/icons/socials/instagram-color.vue +46 -46
  499. package/icons/socials/instagram.vue +19 -19
  500. package/icons/socials/linkedin-color.vue +14 -14
  501. package/icons/socials/telegram-color.vue +16 -16
  502. package/icons/socials/telegram.vue +15 -15
  503. package/icons/socials/tik-tok.vue +15 -15
  504. package/icons/socials/tiktok-color.vue +14 -14
  505. package/icons/socials/vk-color.vue +16 -16
  506. package/icons/socials/youtube-color.vue +16 -16
  507. package/icons/socials/youtube.vue +16 -16
  508. package/icons/sort/sort-asc.vue +15 -15
  509. package/icons/sort/sort-desc.vue +15 -15
  510. package/icons/sparkles.vue +3 -3
  511. package/icons/splitting-arrows.vue +8 -8
  512. package/icons/star/star-filled.vue +41 -41
  513. package/icons/star/star-half-filled.vue +21 -21
  514. package/icons/star/star.vue +25 -25
  515. package/icons/store.vue +7 -0
  516. package/icons/sun.vue +25 -25
  517. package/icons/sviazi.vue +5 -5
  518. package/icons/tag.vue +30 -30
  519. package/icons/target.vue +7 -0
  520. package/icons/tasks.vue +10 -10
  521. package/icons/tender-search.vue +11 -11
  522. package/icons/terminal.vue +3 -3
  523. package/icons/toasts/check-circle-toast.vue +6 -6
  524. package/icons/toasts/warning-triangle-toast.vue +8 -8
  525. package/icons/trash.vue +13 -13
  526. package/icons/trees.vue +8 -0
  527. package/icons/triangle-alert.vue +3 -3
  528. package/icons/triangle.vue +10 -10
  529. package/icons/trophy.vue +10 -0
  530. package/icons/truck.vue +7 -7
  531. package/icons/upload.vue +16 -0
  532. package/icons/user-plus.vue +3 -0
  533. package/icons/user-square.vue +14 -14
  534. package/icons/user-x.vue +11 -0
  535. package/icons/user-xmark.vue +10 -0
  536. package/icons/user.vue +22 -22
  537. package/icons/users-focus.vue +8 -8
  538. package/icons/users-outline.vue +3 -3
  539. package/icons/users-round.vue +3 -3
  540. package/icons/users-three.vue +7 -7
  541. package/icons/users.vue +8 -8
  542. package/icons/view-eye-open.vue +13 -13
  543. package/icons/wallet.vue +6 -0
  544. package/icons/warning/warning-circle.vue +29 -29
  545. package/icons/warning/warning-triangle-filled.vue +12 -12
  546. package/icons/warning/warning-triangle.vue +28 -28
  547. package/icons/webhook.vue +3 -3
  548. package/icons/whatsapp.vue +8 -8
  549. package/icons/work-bag.vue +11 -11
  550. package/icons/work-case.vue +9 -9
  551. package/icons/work-search.vue +10 -10
  552. package/icons/work.vue +5 -5
  553. package/icons/workflow.vue +3 -3
  554. package/icons/x-mark.vue +15 -15
  555. package/icons/x-outline.vue +3 -3
  556. package/icons/yandex.vue +19 -29
  557. package/icons/zap.vue +3 -3
  558. package/illustrations/address-location.vue +38 -38
  559. package/illustrations/award-badge.vue +105 -105
  560. package/illustrations/ball-with-chain.vue +120 -120
  561. package/illustrations/bill.vue +133 -133
  562. package/illustrations/buildings.vue +82 -82
  563. package/illustrations/calendar.vue +156 -156
  564. package/illustrations/chains.vue +152 -152
  565. package/illustrations/coin-percent.vue +126 -126
  566. package/illustrations/coins-stack.vue +202 -202
  567. package/illustrations/delete-dark.vue +32 -32
  568. package/illustrations/delete.vue +33 -33
  569. package/illustrations/doc-with-stamp.vue +126 -126
  570. package/illustrations/document.vue +64 -64
  571. package/illustrations/door.vue +85 -85
  572. package/illustrations/empty-badge-check.vue +20 -20
  573. package/illustrations/empty-ban.vue +18 -18
  574. package/illustrations/empty-box.vue +77 -77
  575. package/illustrations/empty-filter.vue +20 -20
  576. package/illustrations/empty-inbox.vue +19 -19
  577. package/illustrations/empty-scale.vue +22 -22
  578. package/illustrations/empty-search-x.vue +21 -21
  579. package/illustrations/empty-shopping-cart.vue +21 -21
  580. package/illustrations/empty-users-round.vue +20 -20
  581. package/illustrations/empty-wallet.vue +127 -127
  582. package/illustrations/graph-in-coin.vue +119 -119
  583. package/illustrations/hammer.vue +156 -156
  584. package/illustrations/hand-cash.vue +108 -108
  585. package/illustrations/info.vue +52 -52
  586. package/illustrations/mail-active.vue +86 -86
  587. package/illustrations/mail.vue +68 -68
  588. package/illustrations/no-access.vue +92 -92
  589. package/illustrations/notes-search.vue +20 -0
  590. package/illustrations/notes-sticky.vue +20 -0
  591. package/illustrations/ok.vue +90 -90
  592. package/illustrations/people-group.vue +237 -237
  593. package/illustrations/person-with-phone.vue +187 -187
  594. package/illustrations/person.vue +159 -159
  595. package/illustrations/phone-check.vue +110 -110
  596. package/illustrations/phone-payment-method.vue +223 -223
  597. package/illustrations/scheme-notes.vue +21 -0
  598. package/illustrations/scheme-waypoints.vue +25 -0
  599. package/illustrations/search.vue +14 -14
  600. package/illustrations/shield-user.vue +94 -94
  601. package/illustrations/stop-hand.vue +84 -84
  602. package/illustrations/stop-sign.vue +34 -34
  603. package/illustrations/suit.vue +97 -97
  604. package/illustrations/suitcase.vue +105 -105
  605. package/illustrations/terminal.vue +100 -100
  606. package/illustrations/trash-can.vue +66 -66
  607. package/illustrations/turn-on-tariff.vue +38 -38
  608. package/illustrations/two-persons.vue +169 -169
  609. package/lang/en.ts +911 -877
  610. package/lang/kk.ts +913 -879
  611. package/lang/ru.ts +913 -879
  612. package/layouts/default.vue +13 -13
  613. package/nuxt.config.ts +119 -95
  614. package/package.json +64 -64
  615. package/plugins/maska.ts +4 -4
  616. package/plugins/toast.client.ts +41 -41
  617. package/public/kaspi/logo.svg +4 -4
  618. package/server/middleware/color-mode-cookie.ts +58 -0
  619. package/shared/constans/pages.ts +96 -96
  620. package/tsconfig.json +6 -6
  621. package/utils/apiErrorMessage.ts +22 -0
  622. package/utils/getMaxZindex.ts +25 -25
  623. package/utils/localizedNavigation.ts +36 -36
  624. package/utils/passwordRules.ts +20 -0
  625. package/utils/toCssSize.ts +11 -0
  626. package/utils/twMerge.ts +34 -34
  627. package/vitest.config.ts +14 -14
  628. package/.claude/settings.local.json +0 -19
@@ -0,0 +1,710 @@
1
+ <script generic="T" lang="ts" setup>
2
+ import type { CascadeEntry, CascadeKeys, CascadeMode } from './types'
3
+ import { toCssSize } from '#adata-ui/utils/toCssSize'
4
+ import { autoUpdate, flip, size as floatingSize, offset, shift, useFloating } from '@floating-ui/vue'
5
+ import { onClickOutside, useMediaQuery, useToggle, useWindowSize } from '@vueuse/core'
6
+ import { isEqual } from 'lodash-es'
7
+ import { buildCascadeIndex, isDisabled, isLeaf, nodeKey, nodeLabel, pathLabel, resolvePanels } from './cascade-utils'
8
+
9
+ interface Props {
10
+ label: string
11
+ options: T[]
12
+ required?: boolean
13
+ keyToShow?: string
14
+ keyToSelect?: string
15
+ childrenKey?: string
16
+ disabledKey?: string
17
+ /** `true` — в v-model лежит ключ листа, `false` — сам объект листа. */
18
+ getOnlyKey?: boolean
19
+ clearable?: boolean
20
+ disabled?: boolean
21
+ size?: 'sm' | 'md'
22
+ error?: string | string[]
23
+ startIcon?: object
24
+ buttonBg?: string
25
+ /** Куда растут подпанели. `auto` — вправо, а если не влезает, влево. */
26
+ panelsSide?: 'auto' | 'right' | 'left'
27
+ /** Принудительный режим раскрытия вместо авто-выбора по ширине экрана. */
28
+ submenuTrigger?: 'auto' | 'hover' | 'click'
29
+ hoverOpenDelay?: number
30
+ hoverCloseDelay?: number
31
+ /** Ширина колонки. По умолчанию — ширина триггера. */
32
+ panelWidth?: number | string
33
+ /** Нижняя граница ширины колонки — когда триггер узкий, а подписи длинные. */
34
+ panelMinWidth?: number | string
35
+ panelMaxHeight?: number | string
36
+ /** Подпись первой колонки. По умолчанию — `label`. */
37
+ panelTitle?: string
38
+ /** Показывать в триггере весь путь («Кварталы / 1 квартал 2026»). */
39
+ showPathInTrigger?: boolean
40
+ drawerTitle?: string
41
+ /** Фиксированная общая высота мобильного дровера («70dvh»). */
42
+ drawerHeight?: number | string
43
+ drawerMaxHeight?: number | string
44
+ backLabel?: string
45
+ emptyLabel?: string
46
+ mobileBreakpoint?: number
47
+ tabletBreakpoint?: number
48
+ teleport?: boolean
49
+ /** Слой выпадашки и мобильного дровера. */
50
+ zIndex?: number | string
51
+ }
52
+
53
+ interface Emits {
54
+ (e: 'onChange', option: T, path: T[]): void
55
+ (e: 'onClear'): void
56
+ }
57
+
58
+ /**
59
+ * Каскадный селект: опция-родитель раскрывает своих детей отдельной колонкой сбоку.
60
+ *
61
+ * Три режима, выбираются по ширине экрана (или принудительно через `submenuTrigger`):
62
+ * • `columns-hover` (десктоп) — подпанель по наведению;
63
+ * • `columns-click` (планшет) — подпанель по клику, у дочерней колонки есть «Назад»;
64
+ * • `drawer` (мобильный) — `a-drawer-v2` с drill-down по одной колонке.
65
+ *
66
+ * Выбирается ТОЛЬКО лист: узел с детьми лишь раскрывает ветку и значение не меняет.
67
+ */
68
+ defineOptions({ name: 'ACascadeSelectV2' })
69
+
70
+ const props = withDefaults(defineProps<Props>(), {
71
+ required: false,
72
+ keyToShow: 'name',
73
+ keyToSelect: 'id',
74
+ childrenKey: 'children',
75
+ disabledKey: undefined,
76
+ getOnlyKey: false,
77
+ clearable: true,
78
+ disabled: false,
79
+ size: 'sm',
80
+ error: undefined,
81
+ startIcon: undefined,
82
+ buttonBg: '',
83
+ panelsSide: 'auto',
84
+ submenuTrigger: 'auto',
85
+ hoverOpenDelay: 120,
86
+ hoverCloseDelay: 200,
87
+ panelWidth: undefined,
88
+ panelMinWidth: undefined,
89
+ panelMaxHeight: undefined,
90
+ panelTitle: undefined,
91
+ showPathInTrigger: false,
92
+ drawerTitle: undefined,
93
+ drawerHeight: undefined,
94
+ drawerMaxHeight: undefined,
95
+ backLabel: 'Назад',
96
+ emptyLabel: 'Ничего не найдено',
97
+ mobileBreakpoint: 768,
98
+ tabletBreakpoint: 1024,
99
+ teleport: true,
100
+ zIndex: 10000,
101
+ })
102
+
103
+ const emits = defineEmits<Emits>()
104
+
105
+ defineSlots<{
106
+ 'option'?: (props: { option: T, level: number, selected: boolean, hasChildren: boolean, active: boolean }) => unknown
107
+ 'single-selected'?: (props: { value: T, path: T[] }) => unknown
108
+ 'empty'?: () => unknown
109
+ }>()
110
+
111
+ const modelValue = defineModel<T | unknown | null>({ required: true })
112
+ const isOpen = defineModel<boolean>('open', { default: false })
113
+
114
+ const keys = computed<CascadeKeys>(() => ({
115
+ keyToShow: props.keyToShow,
116
+ keyToSelect: props.keyToSelect,
117
+ childrenKey: props.childrenKey,
118
+ disabledKey: props.disabledKey,
119
+ }))
120
+
121
+ /* Один обход дерева на смену options: дальше все выборки по ключу — O(1). */
122
+ const index = computed(() => buildCascadeIndex(props.options, keys.value))
123
+
124
+ const selectedKey = computed<unknown>(() => {
125
+ const value = modelValue.value
126
+ if (value == null || value === '') return undefined
127
+ return props.getOnlyKey ? value : nodeKey(value as T, keys.value)
128
+ })
129
+
130
+ const selectedEntry = computed<CascadeEntry<T> | null>(() =>
131
+ selectedKey.value === undefined ? null : index.value.get(selectedKey.value) ?? null,
132
+ )
133
+
134
+ /** Ключи предков выбранного листа — ими раскрываем ветку при открытии. */
135
+ const selectedAncestorKeys = computed<unknown[]>(() =>
136
+ selectedEntry.value ? selectedEntry.value.keyPath.slice(0, -1) : [],
137
+ )
138
+
139
+ // -- Навигация -------------------------------------------------------------
140
+
141
+ /** Цепочка ключей раскрытых родителей: activePath[i] раскрыт из колонки i. */
142
+ const activePath = ref<unknown[]>([])
143
+
144
+ const resolved = computed(() => resolvePanels(props.options, activePath.value, index.value, keys.value))
145
+ const panels = computed<T[][]>(() => resolved.value.panels)
146
+ /** Путь, обрезанный до реально разрешимого: options могли смениться под нами. */
147
+ const resolvedPath = computed<unknown[]>(() => resolved.value.keyPath)
148
+
149
+ // -- Режим -----------------------------------------------------------------
150
+
151
+ const isMobileViewport = useMediaQuery(computed(() => `(max-width: ${props.mobileBreakpoint - 1}px)`))
152
+ const isTabletViewport = useMediaQuery(
153
+ computed(() => `(min-width: ${props.mobileBreakpoint}px) and (max-width: ${props.tabletBreakpoint - 1}px)`),
154
+ )
155
+
156
+ const mode = computed<CascadeMode>(() => {
157
+ if (isMobileViewport.value) return 'drawer'
158
+ if (props.submenuTrigger === 'hover') return 'columns-hover'
159
+ if (props.submenuTrigger === 'click') return 'columns-click'
160
+ return isTabletViewport.value ? 'columns-click' : 'columns-hover'
161
+ })
162
+
163
+ const isDrawerMode = computed(() => mode.value === 'drawer')
164
+
165
+ /**
166
+ * Планшет и дровер показывают одну колонку — текущую: вернуться к родителю есть чем
167
+ * («Назад»), а вторая колонка только съедала бы ширину. Десктоп показывает все.
168
+ * Абсолютный `level` сохраняем, чтобы срез не ломал индексацию путей и рефов.
169
+ */
170
+ const panelViews = computed<{ options: T[], level: number }[]>(() => {
171
+ const all = panels.value.map((options, level) => ({ options, level }))
172
+ if (isDrawerMode.value || mode.value === 'columns-click') return all.slice(-1)
173
+ return all
174
+ })
175
+
176
+ /* undefined доезжает до a-cascade-drawer и там подхватывает его собственные дефолты. */
177
+ const drawerHeightStyle = computed(() => toCssSize(props.drawerHeight))
178
+ const drawerMaxHeightStyle = computed(() => toCssSize(props.drawerMaxHeight))
179
+
180
+ const drawerView = computed(() => panelViews.value[0] ?? { options: props.options, level: 0 })
181
+ const drawerParentLabel = computed(() => {
182
+ const level = drawerView.value.level
183
+ if (level === 0) return ''
184
+ const entry = index.value.get(resolvedPath.value[level - 1])
185
+ return entry ? nodeLabel(entry.node, keys.value) : ''
186
+ })
187
+
188
+ // -- Позиционирование ------------------------------------------------------
189
+
190
+ const wrapper = ref<HTMLElement | null>(null)
191
+ const triggerRef = ref<{ el: HTMLButtonElement | null, valueEl: HTMLElement | null } | null>(null)
192
+ const reference = computed<HTMLElement | null>(() => triggerRef.value?.el ?? null)
193
+ const floating = ref<HTMLElement | null>(null)
194
+
195
+ const panelsDir = ref<'right' | 'left'>('right')
196
+
197
+ const autoMaxHeight = ref('320px')
198
+ /* Ширина триггера, снятая floating-ui: по ней колонки выравниваются с полем, как в ASelectV2. */
199
+ const autoPanelWidth = ref<string>()
200
+
201
+ const panelMaxHeightStyle = computed(() => toCssSize(props.panelMaxHeight) ?? autoMaxHeight.value)
202
+ const panelWidthStyle = computed(() => toCssSize(props.panelWidth) ?? autoPanelWidth.value)
203
+ const panelMinWidthStyle = computed(() => toCssSize(props.panelMinWidth))
204
+
205
+ function pxOf(value?: string): number {
206
+ const match = /^([\d.]+)px$/.exec(value ?? '')
207
+ return match ? Number.parseFloat(match[1]) : 0
208
+ }
209
+
210
+ /** Фактическая ширина колонки в пикселях; 0 — размер задан нечисловой единицей, мерить нечем. */
211
+ const panelWidthPx = computed(() => {
212
+ const width = pxOf(panelWidthStyle.value)
213
+ if (!width) return 0
214
+ /* min-width может оказаться больше ширины триггера — тогда колонка шире, чем кажется. */
215
+ return Math.max(width, pxOf(panelMinWidthStyle.value))
216
+ })
217
+
218
+ const { width: viewportWidth } = useWindowSize()
219
+
220
+ /**
221
+ * Колонки фиксированной ширины перестают влезать в экран уже на третьем уровне,
222
+ * а `shift` умеет только сдвигать. Поэтому режем лишние слева — тем же приёмом,
223
+ * что планшет (`slice(-2)`) и дровер (`slice(-1)`).
224
+ */
225
+ const visiblePanelViews = computed(() => {
226
+ const all = panelViews.value
227
+ const panelPx = panelWidthPx.value
228
+ if (isDrawerMode.value || !panelPx || !viewportWidth.value) return all
229
+
230
+ const gap = 8 /* gap-2 между колонками */
231
+ const edges = 16 /* по 8px от краёв — столько же просит shift({ padding: 8 }) */
232
+ const fits = Math.max(1, Math.floor((viewportWidth.value - edges + gap) / (panelPx + gap)))
233
+ return all.length > fits ? all.slice(-fits) : all
234
+ })
235
+
236
+ /**
237
+ * «Назад» нужен на самой мелкой видимой колонке: если срез (планшетный или по ширине экрана)
238
+ * спрятал родителя, вернуться к нему больше нечем. На остальных колонках он был бы дублем.
239
+ */
240
+ function showBackFor(level: number) {
241
+ if (level === 0) return false
242
+ return level === (visiblePanelViews.value[0]?.level ?? 0)
243
+ }
244
+
245
+ const { x, y, strategy, update } = useFloating(reference, floating, {
246
+ /* Колонки растут вправо от левого края триггера; в зеркальном режиме — влево от правого. */
247
+ placement: computed(() => (panelsDir.value === 'left' ? 'bottom-end' : 'bottom-start')),
248
+ strategy: 'fixed',
249
+ middleware: computed(() => [
250
+ offset(6),
251
+ flip({ fallbackPlacements: panelsDir.value === 'left' ? ['top-end'] : ['top-start'] }),
252
+ shift({ padding: 8 }),
253
+ floatingSize({
254
+ /* Пишем в ref только на смену значения: apply зовётся на каждый кадр autoUpdate. */
255
+ apply({ availableHeight, rects }) {
256
+ const nextHeight = `${Math.max(160, Math.min(availableHeight, 360))}px`
257
+ if (nextHeight !== autoMaxHeight.value) autoMaxHeight.value = nextHeight
258
+
259
+ const nextWidth = `${Math.round(rects.reference.width)}px`
260
+ if (nextWidth !== autoPanelWidth.value) autoPanelWidth.value = nextWidth
261
+ },
262
+ }),
263
+ ]),
264
+ })
265
+
266
+ /* Только на смену: запись меняет placement, тот дёргает update — иначе петля. */
267
+ function setDirection(next: 'right' | 'left') {
268
+ if (next !== panelsDir.value) panelsDir.value = next
269
+ }
270
+
271
+ /**
272
+ * Сторону выбираем один раз на изменение набора колонок, а не внутри middleware:
273
+ * измерение в `apply` меняло бы placement, тот — измерение, и получалась бы петля.
274
+ */
275
+ function measureDirection() {
276
+ if (props.panelsSide !== 'auto') {
277
+ setDirection(props.panelsSide)
278
+ return
279
+ }
280
+ const rect = reference.value?.getBoundingClientRect()
281
+ const el = floating.value
282
+ if (!rect || !el) return
283
+ const needed = el.scrollWidth
284
+ const spaceRight = window.innerWidth - rect.left - 8
285
+ const spaceLeft = rect.right - 8
286
+ setDirection(needed > spaceRight && spaceLeft > spaceRight ? 'left' : 'right')
287
+ }
288
+
289
+ watch(() => props.panelsSide, () => measureDirection(), { immediate: true })
290
+ watch([visiblePanelViews, isOpen], () => {
291
+ if (!isOpen.value || isDrawerMode.value) return
292
+ nextTick(measureDirection)
293
+ })
294
+
295
+ let cleanup: (() => void) | null = null
296
+ watch([isOpen, isDrawerMode], () => {
297
+ cleanup?.()
298
+ cleanup = null
299
+ if (!isOpen.value || isDrawerMode.value) return
300
+ nextTick(() => {
301
+ if (reference.value && floating.value) {
302
+ cleanup = autoUpdate(reference.value, floating.value, () => {
303
+ update()
304
+ measureDirection()
305
+ })
306
+ }
307
+ })
308
+ })
309
+
310
+ // -- Таймеры hover-intent (по образцу useTooltipTrigger) --------------------
311
+
312
+ let openTimer: ReturnType<typeof setTimeout> | null = null
313
+ let collapseTimer: ReturnType<typeof setTimeout> | null = null
314
+
315
+ function clearOpenTimer() {
316
+ if (openTimer) {
317
+ clearTimeout(openTimer)
318
+ openTimer = null
319
+ }
320
+ }
321
+
322
+ function clearCollapseTimer() {
323
+ if (collapseTimer) {
324
+ clearTimeout(collapseTimer)
325
+ collapseTimer = null
326
+ }
327
+ }
328
+
329
+ onUnmounted(() => {
330
+ clearOpenTimer()
331
+ clearCollapseTimer()
332
+ cleanup?.()
333
+ })
334
+
335
+ // -- Действия --------------------------------------------------------------
336
+
337
+ const toggleOpen = useToggle(isOpen)
338
+
339
+ function openBranch(level: number, node: T) {
340
+ activePath.value = [...resolvedPath.value.slice(0, level), nodeKey(node, keys.value)]
341
+ }
342
+
343
+ function collapseTo(level: number) {
344
+ activePath.value = resolvedPath.value.slice(0, level)
345
+ }
346
+
347
+ function selectLeaf(node: T) {
348
+ const entry = index.value.get(nodeKey(node, keys.value))
349
+ modelValue.value = props.getOnlyKey ? nodeKey(node, keys.value) : node
350
+ emits('onChange', node, entry?.path ?? [node])
351
+ toggleOpen(false)
352
+ }
353
+
354
+ function activate(level: number, node: T) {
355
+ if (isDisabled(node, keys.value)) return
356
+ clearOpenTimer()
357
+ clearCollapseTimer()
358
+ if (isLeaf(node, keys.value)) {
359
+ selectLeaf(node)
360
+ return
361
+ }
362
+ openBranch(level, node)
363
+ focusRow(level + 1, firstEnabledKey(level + 1))
364
+ }
365
+
366
+ function goBackFrom(level: number) {
367
+ activePath.value = resolvedPath.value.slice(0, Math.max(0, level - 1))
368
+ }
369
+
370
+ function goBack() {
371
+ goBackFrom(drawerView.value.level)
372
+ }
373
+
374
+ function onClear() {
375
+ modelValue.value = null
376
+ activePath.value = []
377
+ emits('onClear')
378
+ }
379
+
380
+ defineExpose({ onClear })
381
+
382
+ // -- Hover -----------------------------------------------------------------
383
+
384
+ function onRowHover(level: number, node: T) {
385
+ if (mode.value !== 'columns-hover') return
386
+ clearCollapseTimer()
387
+ clearOpenTimer()
388
+ openTimer = setTimeout(() => {
389
+ openTimer = null
390
+ if (isDisabled(node, keys.value)) return
391
+ if (isLeaf(node, keys.value)) collapseTo(level)
392
+ else openBranch(level, node)
393
+ }, props.hoverOpenDelay)
394
+ }
395
+
396
+ function onPanelsEnter() {
397
+ clearCollapseTimer()
398
+ }
399
+
400
+ /* Уводя мышь со всего каскада, схлопываем его до ветки выбранного значения. */
401
+ function onPanelsLeave() {
402
+ clearOpenTimer()
403
+ if (mode.value !== 'columns-hover') return
404
+ clearCollapseTimer()
405
+ collapseTimer = setTimeout(() => {
406
+ collapseTimer = null
407
+ activePath.value = selectedAncestorKeys.value
408
+ }, props.hoverCloseDelay)
409
+ }
410
+
411
+ // -- Клавиатура ------------------------------------------------------------
412
+
413
+ const focused = ref<{ level: number, key: unknown } | null>(null)
414
+ const panelRefs = ref<Record<number, { focusItem: (key: unknown) => void } | null>>({})
415
+
416
+ function setPanelRef(el: unknown, level: number) {
417
+ if (el == null) delete panelRefs.value[level]
418
+ else panelRefs.value[level] = el as { focusItem: (key: unknown) => void }
419
+ }
420
+
421
+ function enabledRows(level: number): T[] {
422
+ return (panels.value[level] ?? []).filter(node => !isDisabled(node, keys.value))
423
+ }
424
+
425
+ function firstEnabledKey(level: number): unknown {
426
+ const rows = enabledRows(level)
427
+ return rows.length ? nodeKey(rows[0], keys.value) : undefined
428
+ }
429
+
430
+ function focusRow(level: number, key: unknown) {
431
+ if (key === undefined || isDrawerMode.value) return
432
+ focused.value = { level, key }
433
+ nextTick(() => panelRefs.value[level]?.focusItem(key))
434
+ }
435
+
436
+ function nodeByKey(level: number, key: unknown): T | undefined {
437
+ return (panels.value[level] ?? []).find(node => isEqual(nodeKey(node, keys.value), key))
438
+ }
439
+
440
+ function moveWithin(level: number, delta: number) {
441
+ const rows = enabledRows(level)
442
+ if (!rows.length) return
443
+ const current = rows.findIndex(node => isEqual(nodeKey(node, keys.value), focused.value?.key))
444
+ const next = current === -1
445
+ ? (delta > 0 ? 0 : rows.length - 1)
446
+ : (current + delta + rows.length) % rows.length
447
+ focusRow(level, nodeKey(rows[next], keys.value))
448
+ }
449
+
450
+ function focusInitial() {
451
+ if (isDrawerMode.value) return
452
+ const entry = selectedEntry.value
453
+ if (entry) {
454
+ focusRow(entry.level, selectedKey.value)
455
+ return
456
+ }
457
+ focusRow(0, firstEnabledKey(0))
458
+ }
459
+
460
+ function closeAndRestoreFocus() {
461
+ toggleOpen(false)
462
+ nextTick(() => reference.value?.focus())
463
+ }
464
+
465
+ function onKeydown(event: KeyboardEvent) {
466
+ if (props.disabled) return
467
+
468
+ if (!isOpen.value) {
469
+ if (event.key === 'ArrowDown') {
470
+ event.preventDefault()
471
+ toggleOpen(true)
472
+ }
473
+ return
474
+ }
475
+
476
+ if (event.key === 'Escape') {
477
+ event.preventDefault()
478
+ closeAndRestoreFocus()
479
+ return
480
+ }
481
+
482
+ if (event.key === 'Tab') {
483
+ toggleOpen(false)
484
+ return
485
+ }
486
+
487
+ if (isDrawerMode.value) return
488
+
489
+ const level = focused.value?.level ?? 0
490
+
491
+ switch (event.key) {
492
+ case 'ArrowDown':
493
+ event.preventDefault()
494
+ moveWithin(level, 1)
495
+ break
496
+ case 'ArrowUp':
497
+ event.preventDefault()
498
+ moveWithin(level, -1)
499
+ break
500
+ case 'Home':
501
+ event.preventDefault()
502
+ focusRow(level, firstEnabledKey(level))
503
+ break
504
+ case 'End': {
505
+ event.preventDefault()
506
+ const rows = enabledRows(level)
507
+ if (rows.length) focusRow(level, nodeKey(rows[rows.length - 1], keys.value))
508
+ break
509
+ }
510
+ case 'ArrowRight': {
511
+ const node = focused.value ? nodeByKey(level, focused.value.key) : undefined
512
+ if (!node || isLeaf(node, keys.value)) break
513
+ event.preventDefault()
514
+ openBranch(level, node)
515
+ focusRow(level + 1, firstEnabledKey(level + 1))
516
+ break
517
+ }
518
+ case 'ArrowLeft': {
519
+ if (level === 0) break
520
+ event.preventDefault()
521
+ const parentKey = resolvedPath.value[level - 1]
522
+ goBackFrom(level)
523
+ focusRow(level - 1, parentKey)
524
+ break
525
+ }
526
+ }
527
+ }
528
+
529
+ // -- Открытие / закрытие ---------------------------------------------------
530
+
531
+ watch(isOpen, (open) => {
532
+ clearOpenTimer()
533
+ clearCollapseTimer()
534
+ if (open) {
535
+ activePath.value = selectedAncestorKeys.value
536
+ /* Ширину берём с триггера сразу: иначе колонка мигнёт фолбэком min-w-45 и прыгнет. */
537
+ const triggerWidth = reference.value?.offsetWidth
538
+ if (triggerWidth) autoPanelWidth.value = `${Math.round(triggerWidth)}px`
539
+ nextTick(focusInitial)
540
+ }
541
+ else {
542
+ focused.value = null
543
+ }
544
+ })
545
+
546
+ onClickOutside(floating, () => {
547
+ if (isOpen.value && !isDrawerMode.value) toggleOpen(false)
548
+ }, { ignore: [reference] })
549
+
550
+ // -- Отображение в триггере ------------------------------------------------
551
+
552
+ const displayValue = computed(() => {
553
+ const entry = selectedEntry.value
554
+ if (!entry) return ''
555
+ return props.showPathInTrigger
556
+ ? pathLabel(entry.path, keys.value)
557
+ : nodeLabel(entry.node, keys.value)
558
+ })
559
+
560
+ const rootPanelTitle = computed(() => props.panelTitle ?? props.label)
561
+ </script>
562
+
563
+ <template>
564
+ <div
565
+ ref="wrapper"
566
+ class="cascade-select relative w-full text-body-md"
567
+ @keydown="onKeydown"
568
+ >
569
+ <a-select-trigger
570
+ ref="triggerRef"
571
+ :label="label"
572
+ :required="required"
573
+ :size="size"
574
+ :disabled="disabled"
575
+ :error="error"
576
+ :is-open="isOpen"
577
+ :start-icon="startIcon"
578
+ :button-bg="buttonBg"
579
+ :clearable="clearable"
580
+ :show-clear="clearable && !!selectedEntry"
581
+ floating-label
582
+ :has-value="!!selectedEntry"
583
+ @toggle="toggleOpen()"
584
+ @clear="onClear"
585
+ >
586
+ <span v-if="selectedEntry" class="block truncate leading-tight">
587
+ <slot
588
+ name="single-selected"
589
+ :value="selectedEntry.node"
590
+ :path="selectedEntry.path"
591
+ >{{ displayValue }}</slot>
592
+ </span>
593
+ <span v-else class="text-secondary">&#8203;</span>
594
+ </a-select-trigger>
595
+
596
+ <client-only>
597
+ <teleport
598
+ to="body"
599
+ :disabled="!teleport"
600
+ >
601
+ <transition
602
+ enter-active-class="cascade-select__enter-active"
603
+ enter-from-class="cascade-select__enter-from"
604
+ enter-to-class="cascade-select__enter-to"
605
+ leave-active-class="cascade-select__leave-active"
606
+ leave-from-class="cascade-select__leave-from"
607
+ leave-to-class="cascade-select__leave-to"
608
+ >
609
+ <div
610
+ v-if="isOpen && !isDrawerMode"
611
+ ref="floating"
612
+ class="cascade-select__panels flex items-start gap-2"
613
+ :class="{ 'flex-row-reverse': panelsDir === 'left' }"
614
+ :style="{
615
+ position: strategy,
616
+ left: x != null ? `${x}px` : '',
617
+ top: y != null ? `${y}px` : '',
618
+ zIndex,
619
+ }"
620
+ @pointerenter="onPanelsEnter"
621
+ @pointerleave="onPanelsLeave"
622
+ @keydown="onKeydown"
623
+ >
624
+ <a-cascade-panel
625
+ v-for="view in visiblePanelViews"
626
+ :key="view.level"
627
+ :ref="(el: any) => setPanelRef(el, view.level)"
628
+ :options="view.options"
629
+ :keys="keys"
630
+ :level="view.level"
631
+ :active-key="resolvedPath[view.level]"
632
+ :selected-key="selectedKey"
633
+ :focused-key="focused?.level === view.level ? focused?.key : undefined"
634
+ :title="view.level === 0 ? rootPanelTitle : ''"
635
+ :show-back="showBackFor(view.level)"
636
+ :mirrored="panelsDir === 'left'"
637
+ :back-label="backLabel"
638
+ :width="panelWidthStyle"
639
+ :min-width="panelMinWidthStyle"
640
+ :max-height="panelMaxHeightStyle"
641
+ :empty-label="emptyLabel"
642
+ @activate="activate(view.level, $event)"
643
+ @hover="onRowHover(view.level, $event)"
644
+ @back="goBackFrom(view.level)"
645
+ >
646
+ <template #option="slotProps">
647
+ <slot name="option" v-bind="slotProps" />
648
+ </template>
649
+ <template #empty>
650
+ <slot name="empty" />
651
+ </template>
652
+ </a-cascade-panel>
653
+ </div>
654
+ </transition>
655
+ </teleport>
656
+
657
+ <a-cascade-drawer
658
+ v-if="isDrawerMode"
659
+ v-model="isOpen"
660
+ :options="drawerView.options"
661
+ :keys="keys"
662
+ :level="drawerView.level"
663
+ :title="drawerTitle ?? label"
664
+ :parent-label="drawerParentLabel"
665
+ :back-label="backLabel"
666
+ :selected-key="selectedKey"
667
+ :height="drawerHeightStyle"
668
+ :max-height="drawerMaxHeightStyle"
669
+ :z-index="zIndex"
670
+ @activate="activate(drawerView.level, $event)"
671
+ @back="goBack"
672
+ >
673
+ <template #option="slotProps">
674
+ <slot name="option" v-bind="slotProps" />
675
+ </template>
676
+ <template #empty>
677
+ <slot name="empty" />
678
+ </template>
679
+ </a-cascade-drawer>
680
+ </client-only>
681
+ </div>
682
+ </template>
683
+
684
+ <style scoped>
685
+ .cascade-select__enter-active,
686
+ .cascade-select__leave-active {
687
+ transition:
688
+ opacity 180ms cubic-bezier(0.22, 1, 0.36, 1),
689
+ transform 180ms cubic-bezier(0.22, 1, 0.36, 1);
690
+ }
691
+
692
+ .cascade-select__enter-from,
693
+ .cascade-select__leave-to {
694
+ opacity: 0;
695
+ transform: translateY(-4px);
696
+ }
697
+
698
+ .cascade-select__enter-to,
699
+ .cascade-select__leave-from {
700
+ opacity: 1;
701
+ transform: translateY(0);
702
+ }
703
+
704
+ @media (prefers-reduced-motion: reduce) {
705
+ .cascade-select__enter-active,
706
+ .cascade-select__leave-active {
707
+ transition: none;
708
+ }
709
+ }
710
+ </style>