@feugene/granularity 0.31.0 → 0.31.1

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 (265) hide show
  1. package/README.md +0 -1
  2. package/dist/chunks/adapter-CAOxmn2G.js.map +1 -1
  3. package/dist/chunks/{config-BHcS5Cwm.js → config-87fszqnD.js} +1 -1
  4. package/dist/chunks/{config-BHcS5Cwm.js.map → config-87fszqnD.js.map} +1 -1
  5. package/dist/chunks/config-A-bBiotF.js.map +1 -1
  6. package/dist/chunks/config-B3J0alyY.js.map +1 -1
  7. package/dist/chunks/config-BhfNgyWL.js.map +1 -1
  8. package/dist/chunks/config-Bjn30smH.js.map +1 -1
  9. package/dist/chunks/config-BqT6PMzO.js.map +1 -1
  10. package/dist/chunks/config-C0Ea9ePP.js.map +1 -1
  11. package/dist/chunks/config-C0Eu8jpG.js.map +1 -1
  12. package/dist/chunks/config-CDat1wXf.js.map +1 -1
  13. package/dist/chunks/config-CQLNjc9X2.js.map +1 -1
  14. package/dist/chunks/config-CbEbLyDE.js.map +1 -1
  15. package/dist/chunks/{config-jyr6AIZt2.js → config-CuQmUkMw2.js} +2 -2
  16. package/dist/chunks/{config-jyr6AIZt2.js.map → config-CuQmUkMw2.js.map} +1 -1
  17. package/dist/chunks/config-DeyT9dJl.js.map +1 -1
  18. package/dist/chunks/config-DnF472Ox.js.map +1 -1
  19. package/dist/chunks/config-DwABJHEo.js.map +1 -1
  20. package/dist/chunks/{config-tiUtEn4Q.js → config-E1cTyRVV.js} +1 -1
  21. package/dist/chunks/{config-tiUtEn4Q.js.map → config-E1cTyRVV.js.map} +1 -1
  22. package/dist/chunks/config-MmPym-Z72.js.map +1 -1
  23. package/dist/chunks/config-T3-DMXYm.js.map +1 -1
  24. package/dist/chunks/config-Yy67uWHY.js.map +1 -1
  25. package/dist/chunks/config-_jbgsFpw.js.map +1 -1
  26. package/dist/chunks/config-g8-JGZ_Y.js.map +1 -1
  27. package/dist/chunks/config-mlheIMtV.js.map +1 -1
  28. package/dist/chunks/config-rLkc9Zn2.js.map +1 -1
  29. package/dist/chunks/config-uzUuosv7.js.map +1 -1
  30. package/dist/chunks/context-CTbuwcYs.js.map +1 -1
  31. package/dist/chunks/deltaTone-B5OoS1vo.js.map +1 -1
  32. package/dist/chunks/es-Do2CNrC2.js.map +1 -1
  33. package/dist/chunks/fileValidation-D5TFBhbv.js.map +1 -1
  34. package/dist/chunks/focusables-BkzK42SQ.js.map +1 -1
  35. package/dist/chunks/{generated-DJ2dJ__C.js → generated-CSyPsWtM.js} +1 -1
  36. package/dist/chunks/{generated-DJ2dJ__C.js.map → generated-CSyPsWtM.js.map} +1 -1
  37. package/dist/chunks/grBadgeStyles-D8Mz73B5.js.map +1 -1
  38. package/dist/chunks/{grButtonStyles-B_-YRE__.js → grButtonStyles-B3wRWW_i.js} +4 -4
  39. package/dist/chunks/grButtonStyles-B3wRWW_i.js.map +1 -0
  40. package/dist/chunks/grLinkStyles-FME_qMpi.js.map +1 -1
  41. package/dist/chunks/grLoadingStyles-CnQaf5ry.js.map +1 -1
  42. package/dist/chunks/{grRadioStyles-D4gnU65n.js → grRadioStyles-DGL2QLy6.js} +1 -1
  43. package/dist/chunks/{grRadioStyles-D4gnU65n.js.map → grRadioStyles-DGL2QLy6.js.map} +1 -1
  44. package/dist/chunks/grSelectStyles-Cdtd10eY.js.map +1 -1
  45. package/dist/chunks/{grSliderStyles-B5uNxNJq.js → grSliderStyles-kaz6VXZ3.js} +9 -1
  46. package/dist/chunks/grSliderStyles-kaz6VXZ3.js.map +1 -0
  47. package/dist/chunks/{granular-provider-yJNX1g1G.js → granular-provider-Da2CJO6j.js} +3 -3
  48. package/dist/chunks/{granular-provider-yJNX1g1G.js.map → granular-provider-Da2CJO6j.js.map} +1 -1
  49. package/dist/chunks/granularityI18n-BC5zsj_d.js.map +1 -1
  50. package/dist/chunks/icon-Dag2tIMs.js.map +1 -1
  51. package/dist/chunks/inert-DIHCCn_1.js.map +1 -1
  52. package/dist/chunks/keyboard-DE2yhz_b.js.map +1 -1
  53. package/dist/chunks/numericStep-Bq2lj4ew.js.map +1 -1
  54. package/dist/chunks/optionFilter-DYw9XPPx.js.map +1 -1
  55. package/dist/chunks/overlayStack-C8cfsqXa.js.map +1 -1
  56. package/dist/chunks/portalRoot-CVhCgdG6.js.map +1 -1
  57. package/dist/chunks/renamedProp-BwcN-SJg.js.map +1 -1
  58. package/dist/chunks/{safelist-BOq5Li7p.js → safelist-DxkMj5IW.js} +1 -1
  59. package/dist/chunks/{safelist-BOq5Li7p.js.map → safelist-DxkMj5IW.js.map} +1 -1
  60. package/dist/chunks/slotNodes-DxNXpC7v.js.map +1 -1
  61. package/dist/chunks/useControlAddons-asUr10Pf.js.map +1 -1
  62. package/dist/chunks/useControlledOpen-DV3SqLTP.js.map +1 -1
  63. package/dist/chunks/useDragSort-0l47h4yb.js.map +1 -1
  64. package/dist/chunks/useFilePreviews-DnwgdyBs.js.map +1 -1
  65. package/dist/chunks/useFocusWithin-BwHE0rzm.js.map +1 -1
  66. package/dist/chunks/useModalOverlay-Cv5hF63W.js.map +1 -1
  67. package/dist/chunks/usePortalTarget-C0p-mhaM.js.map +1 -1
  68. package/dist/chunks/validation-5HhpanPP.js.map +1 -1
  69. package/dist/components/GrAlert/chunks/GrAlert-FAVf-ksH.js.map +1 -1
  70. package/dist/components/GrAutocomplete/chunks/{GrAutocomplete-X6RlmiJQ.js → GrAutocomplete-rYEY15UI.js} +1 -1
  71. package/dist/components/GrAutocomplete/chunks/GrAutocomplete-rYEY15UI.js.map +1 -0
  72. package/dist/components/GrAutocomplete/index.js +1 -1
  73. package/dist/components/GrAutocomplete/styles.css +1 -1
  74. package/dist/components/GrAvatar/chunks/GrAvatar-CNmLmgcr.js.map +1 -1
  75. package/dist/components/GrBadge/chunks/{GrBadge-BsoocpF-.js → GrBadge-D2MfThPz.js} +1 -1
  76. package/dist/components/GrBadge/chunks/GrBadge-D2MfThPz.js.map +1 -0
  77. package/dist/components/GrBadge/index.js +1 -1
  78. package/dist/components/GrBadge/styles.css +1 -1
  79. package/dist/components/GrBadgeWrap/chunks/GrBadgeWrap-B-A3PRkE.js.map +1 -1
  80. package/dist/components/GrBottomNav/chunks/GrBottomNav-wtFatLjB.js.map +1 -1
  81. package/dist/components/GrBreadcrumbs/chunks/GrBreadcrumbs-DRDNan_n.js.map +1 -1
  82. package/dist/components/GrButton/chunks/{GrButton-Bw2AH4uX.js → GrButton-7GP0zObj.js} +1 -1
  83. package/dist/components/GrButton/chunks/GrButton-7GP0zObj.js.map +1 -0
  84. package/dist/components/GrButton/index.js +3 -3
  85. package/dist/components/GrButtonGroup/chunks/GrButtonGroup-CKyiAV8K.js.map +1 -1
  86. package/dist/components/GrCard/chunks/GrCard-BttXz1pQ.js.map +1 -1
  87. package/dist/components/GrCheckbox/chunks/GrCheckbox-CEcHIsXM.js.map +1 -1
  88. package/dist/components/GrCheckboxGroup/chunks/GrCheckboxGroup-BOwrVcFr.js.map +1 -1
  89. package/dist/components/GrChip/chunks/GrChip-C0vU69tf.js.map +1 -1
  90. package/dist/components/GrChipGroup/chunks/GrChipGroup-C78Vcwos.js.map +1 -1
  91. package/dist/components/GrCodeBlock/chunks/{GrCodeBlock-Dkvkr91t.js → GrCodeBlock-Dp0eEBTZ.js} +1 -1
  92. package/dist/components/GrCodeBlock/chunks/GrCodeBlock-Dp0eEBTZ.js.map +1 -0
  93. package/dist/components/GrCodeBlock/index.js +1 -1
  94. package/dist/components/GrCollapse/chunks/GrCollapse-BlW6ybdL.js.map +1 -1
  95. package/dist/components/GrColorPicker/chunks/{GrColorPicker-C7ifyQ0R.js → GrColorPicker-B20gQ6AR.js} +1 -1
  96. package/dist/components/GrColorPicker/chunks/GrColorPicker-B20gQ6AR.js.map +1 -0
  97. package/dist/components/GrColorPicker/index.js +1 -1
  98. package/dist/components/GrCommandPalette/chunks/{GrCommandPalette-C5abL2Y7.js → GrCommandPalette-Cd0Yo3Dw.js} +1 -1
  99. package/dist/components/GrCommandPalette/chunks/GrCommandPalette-Cd0Yo3Dw.js.map +1 -0
  100. package/dist/components/GrCommandPalette/index.js +1 -1
  101. package/dist/components/GrCommandPalette/styles.css +1 -1
  102. package/dist/components/GrConfigProvider/chunks/GrConfigProvider-BF7R3hgg.js.map +1 -1
  103. package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-B9mIU78E.js → GrConfirmDialog-DOsbRhoZ.js} +3 -3
  104. package/dist/components/GrConfirmDialog/chunks/GrConfirmDialog-DOsbRhoZ.js.map +1 -0
  105. package/dist/components/GrConfirmDialog/index.js +1 -1
  106. package/dist/components/GrContextMenu/chunks/GrContextMenu-_FKwQvuQ.js.map +1 -1
  107. package/dist/components/GrDataTable/chunks/{GrDataTable-C8a9_nCg.js → GrDataTable-CAGgRk27.js} +5 -3
  108. package/dist/components/GrDataTable/chunks/GrDataTable-CAGgRk27.js.map +1 -0
  109. package/dist/components/GrDataTable/index.js +1 -1
  110. package/dist/components/GrDelta/chunks/GrDelta-BZwkgQW-.js.map +1 -1
  111. package/dist/components/GrDescriptionList/chunks/GrDescriptionList-BMeCgFV3.js.map +1 -1
  112. package/dist/components/GrDialog/chunks/{GrDialog-bOUDYfmh.js → GrDialog-S1Vl-OhX.js} +1 -1
  113. package/dist/components/GrDialog/chunks/{GrDialog-bOUDYfmh.js.map → GrDialog-S1Vl-OhX.js.map} +1 -1
  114. package/dist/components/GrDialog/index.js +1 -1
  115. package/dist/components/GrDialogService/chunks/{GrDialogService-6s2cQ-_6.js → GrDialogService-Cuhc4YLp.js} +4 -4
  116. package/dist/components/GrDialogService/chunks/GrDialogService-Cuhc4YLp.js.map +1 -0
  117. package/dist/components/GrDialogService/index.js +1 -1
  118. package/dist/components/GrDivider/chunks/GrDivider-BXavGcMX.js.map +1 -1
  119. package/dist/components/GrDrawer/chunks/{GrDrawer-B7PiAcbU.js → GrDrawer-C0YqVE9e.js} +1 -1
  120. package/dist/components/GrDrawer/chunks/GrDrawer-C0YqVE9e.js.map +1 -0
  121. package/dist/components/GrDrawer/index.js +1 -1
  122. package/dist/components/GrDropdown/chunks/GrDropdown-13Wt9eB_.js.map +1 -1
  123. package/dist/components/GrDropdownMenu/chunks/GrDropdownMenu-GOzuAXsi.js.map +1 -1
  124. package/dist/components/GrDropdownMenu/chunks/GrDropdownMenuList-B33ZzwfB.js.map +1 -1
  125. package/dist/components/GrFilePreview/chunks/GrFilePreview-C9Z7hMK8.js.map +1 -1
  126. package/dist/components/GrFileUpload/chunks/GrFileUpload-NsHkRRIa.js.map +1 -1
  127. package/dist/components/GrForm/chunks/GrForm-CrnfCsgj.js.map +1 -1
  128. package/dist/components/GrFormField/chunks/GrFormField-DSylU7IU.js.map +1 -1
  129. package/dist/components/GrFormFile/chunks/{GrFormFile-CYtXU9HV.js → GrFormFile-B1jGZyIj.js} +2 -2
  130. package/dist/components/GrFormFile/chunks/GrFormFile-B1jGZyIj.js.map +1 -0
  131. package/dist/components/GrFormFile/index.js +1 -1
  132. package/dist/components/GrFormFile/styles.css +1 -1
  133. package/dist/components/GrIcon/chunks/GrIcon-Dmf0c9ba.js.map +1 -1
  134. package/dist/components/GrImageViewer/chunks/GrImageViewer-CyVN6Zpc.js.map +1 -1
  135. package/dist/components/GrInput/chunks/GrInput-BLywRZUN.js.map +1 -1
  136. package/dist/components/GrInputTag/chunks/GrInputTag-Cj4A9Xgs.js.map +1 -1
  137. package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-Cy2bknHO.js → GrJsonViewer-BCZ7lE2k.js} +2 -2
  138. package/dist/components/GrJsonViewer/chunks/GrJsonViewer-BCZ7lE2k.js.map +1 -0
  139. package/dist/components/GrJsonViewer/index.js +1 -1
  140. package/dist/components/GrKbd/chunks/GrKbd-D2ga1H9i.js.map +1 -1
  141. package/dist/components/GrLink/chunks/GrLink-DYXw3W9w.js.map +1 -1
  142. package/dist/components/GrList/chunks/{GrList-DmT4qzLO.js → GrList-B_CRJB4c.js} +1 -1
  143. package/dist/components/GrList/chunks/GrList-B_CRJB4c.js.map +1 -0
  144. package/dist/components/GrList/index.js +1 -1
  145. package/dist/components/GrList/styles.css +1 -1
  146. package/dist/components/GrModal/chunks/GrModal-CCoWZuIl.js.map +1 -1
  147. package/dist/components/GrNavbar/chunks/{GrNavbar-C5EjMKaC.js → GrNavbar-_rtHKwTf.js} +1 -1
  148. package/dist/components/GrNavbar/chunks/{GrNavbar-C5EjMKaC.js.map → GrNavbar-_rtHKwTf.js.map} +1 -1
  149. package/dist/components/GrNavbar/index.js +1 -1
  150. package/dist/components/GrNumberInput/chunks/GrNumberInput-DQ59YICu.js.map +1 -1
  151. package/dist/components/GrPagination/chunks/{GrPagination-Cw3R21dp.js → GrPagination-B_kAhoAV.js} +2 -2
  152. package/dist/components/GrPagination/chunks/{GrPagination-Cw3R21dp.js.map → GrPagination-B_kAhoAV.js.map} +1 -1
  153. package/dist/components/GrPagination/index.js +1 -1
  154. package/dist/components/GrPopover/chunks/GrPopover-CofG-Tfe.js.map +1 -1
  155. package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-DLcl1xiq.js → GrPromptDialog-BuJQMZLM.js} +4 -4
  156. package/dist/components/GrPromptDialog/chunks/GrPromptDialog-BuJQMZLM.js.map +1 -0
  157. package/dist/components/GrPromptDialog/index.js +1 -1
  158. package/dist/components/GrRadio/chunks/{GrRadio-C_rESc5g.js → GrRadio-Aq3v8XgG.js} +1 -1
  159. package/dist/components/GrRadio/chunks/GrRadio-Aq3v8XgG.js.map +1 -0
  160. package/dist/components/GrRadio/index.js +2 -2
  161. package/dist/components/GrRadioGroup/chunks/{GrRadioGroup-CdQO4G-U.js → GrRadioGroup-y9LVdTWh.js} +1 -1
  162. package/dist/components/GrRadioGroup/chunks/GrRadioGroup-y9LVdTWh.js.map +1 -0
  163. package/dist/components/GrRadioGroup/index.js +1 -1
  164. package/dist/components/GrRating/chunks/GrRating-IEiTOdnF.js.map +1 -1
  165. package/dist/components/GrResponseErrorBanner/chunks/{GrResponseErrorBanner-C_skObtv.js → GrResponseErrorBanner-CBVRpgIG.js} +1 -1
  166. package/dist/components/GrResponseErrorBanner/chunks/{GrResponseErrorBanner-C_skObtv.js.map → GrResponseErrorBanner-CBVRpgIG.js.map} +1 -1
  167. package/dist/components/GrResponseErrorBanner/chunks/{GrResponseErrorBanner-DudGc9LO.js → GrResponseErrorBanner-DMeOWlzj.js} +1 -1
  168. package/dist/components/GrResponseErrorBanner/chunks/GrResponseErrorBanner-DMeOWlzj.js.map +1 -0
  169. package/dist/components/GrResponseErrorBanner/index.js +2 -2
  170. package/dist/components/GrSegmented/chunks/GrSegmented-CB-VR-yL.js.map +1 -1
  171. package/dist/components/GrSelect/chunks/{GrSelect-BiUrSSxc.js → GrSelect-gCeRFgQo.js} +1 -1
  172. package/dist/components/GrSelect/chunks/GrSelect-gCeRFgQo.js.map +1 -0
  173. package/dist/components/GrSelect/index.js +1 -1
  174. package/dist/components/GrSelect/styles.css +1 -1
  175. package/dist/components/GrSidebar/chunks/{GrSidebar-B8EQIlfW.js → GrSidebar-alkwBUzL.js} +1 -1
  176. package/dist/components/GrSidebar/chunks/GrSidebar-alkwBUzL.js.map +1 -0
  177. package/dist/components/GrSidebar/index.js +1 -1
  178. package/dist/components/GrSkeleton/chunks/GrSkeleton-CgfXxPQ2.js.map +1 -1
  179. package/dist/components/GrSlider/chunks/{GrSlider-DJfeEl4h.js → GrSlider-DTTrZTHK.js} +3 -3
  180. package/dist/components/GrSlider/chunks/GrSlider-DTTrZTHK.js.map +1 -0
  181. package/dist/components/GrSlider/index.js +2 -2
  182. package/dist/components/GrSortableList/chunks/GrSortableList-HyKX--rO.js.map +1 -1
  183. package/dist/components/GrSplitter/chunks/GrSplitter-MltjaADn.js.map +1 -1
  184. package/dist/components/GrStatistic/chunks/GrStatistic-qHVasK37.js.map +1 -1
  185. package/dist/components/GrSteps/chunks/GrSteps-C2jxpZoN.js.map +1 -1
  186. package/dist/components/GrSwitch/chunks/{GrSwitch-g_OOSkh7.js → GrSwitch-7Oe5QfQZ.js} +1 -1
  187. package/dist/components/GrSwitch/chunks/GrSwitch-7Oe5QfQZ.js.map +1 -0
  188. package/dist/components/GrSwitch/index.js +1 -1
  189. package/dist/components/GrTabPanels/chunks/GrTabPanels-BZQS8-JM.js.map +1 -1
  190. package/dist/components/GrTable/chunks/GrTable-Zcdz2St_.js.map +1 -1
  191. package/dist/components/GrTextarea/chunks/{GrTextarea-KREK75Bu.js → GrTextarea-DinXG12-.js} +10 -10
  192. package/dist/components/GrTextarea/chunks/GrTextarea-DinXG12-.js.map +1 -0
  193. package/dist/components/GrTextarea/index.js +1 -1
  194. package/dist/components/GrTimeline/chunks/GrTimeline-BiHpArsg.js.map +1 -1
  195. package/dist/components/GrToaster/chunks/{GrToaster-Dp7-4B8k.js → GrToaster-COXIBKzq.js} +2 -2
  196. package/dist/components/GrToaster/chunks/GrToaster-COXIBKzq.js.map +1 -0
  197. package/dist/components/GrToaster/index.js +1 -1
  198. package/dist/components/GrToaster/styles.css +1 -1
  199. package/dist/components/GrTooltip/chunks/GrTooltip-uYETdbCD.js.map +1 -1
  200. package/dist/components/GrTree/chunks/{GrTree-DC89I4AP.js → GrTree-Ci6CZfKR.js} +5 -5
  201. package/dist/components/GrTree/chunks/GrTree-Ci6CZfKR.js.map +1 -0
  202. package/dist/components/GrTree/index.js +1 -1
  203. package/dist/components/GrTree/styles.css +1 -1
  204. package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-iJnMl2t9.js → GrTreeSelect-BHwgGJcT.js} +1 -1
  205. package/dist/components/GrTreeSelect/chunks/GrTreeSelect-BHwgGJcT.js.map +1 -0
  206. package/dist/components/GrTreeSelect/index.js +1 -1
  207. package/dist/composables/useAnnouncer.js.map +1 -1
  208. package/dist/composables/useComboboxNavigation.js.map +1 -1
  209. package/dist/composables/useDragGesture.js.map +1 -1
  210. package/dist/composables/useFocusTrap.js.map +1 -1
  211. package/dist/composables/useOverlayLayer.js.map +1 -1
  212. package/dist/composables/useRovingFocus.js.map +1 -1
  213. package/dist/composables/useTheme.js.map +1 -1
  214. package/dist/composables/useToast.js.map +1 -1
  215. package/dist/composables/useVirtualList.js.map +1 -1
  216. package/dist/directives/autofocus.js.map +1 -1
  217. package/dist/directives/autosize.js.map +1 -1
  218. package/dist/directives/clickOutside.js.map +1 -1
  219. package/dist/directives/dropzone.js.map +1 -1
  220. package/dist/directives/hotkey.js.map +1 -1
  221. package/dist/directives/loading.js.map +1 -1
  222. package/dist/granular-provider-node.js +1 -1
  223. package/dist/granular-provider.js +1 -1
  224. package/dist/index.js +33 -33
  225. package/dist/testing.js +2 -2
  226. package/dist/testing.js.map +1 -1
  227. package/dist/theme-apply.js.map +1 -1
  228. package/dist/theme.js +1 -1
  229. package/dist/theme.js.map +1 -1
  230. package/dist/tokens.js +1 -1
  231. package/dist/types/components/GrCodeBlock/tokenizeJson.d.ts +1 -3
  232. package/dist/types/components/GrImageViewer/GrImageViewer.vue.d.ts +1 -1
  233. package/dist/types/components/GrInput/GrInput.vue.d.ts +24 -24
  234. package/dist/types/components/GrSlider/grSliderStyles.d.ts +9 -1
  235. package/dist/types/components/GrTabPanels/GrTabPanel.vue.d.ts +3 -3
  236. package/dist/vue/index.js.map +1 -1
  237. package/dist/web-types.json +1 -1
  238. package/package.json +3 -3
  239. package/dist/chunks/grButtonStyles-B_-YRE__.js.map +0 -1
  240. package/dist/chunks/grSliderStyles-B5uNxNJq.js.map +0 -1
  241. package/dist/components/GrAutocomplete/chunks/GrAutocomplete-X6RlmiJQ.js.map +0 -1
  242. package/dist/components/GrBadge/chunks/GrBadge-BsoocpF-.js.map +0 -1
  243. package/dist/components/GrButton/chunks/GrButton-Bw2AH4uX.js.map +0 -1
  244. package/dist/components/GrCodeBlock/chunks/GrCodeBlock-Dkvkr91t.js.map +0 -1
  245. package/dist/components/GrColorPicker/chunks/GrColorPicker-C7ifyQ0R.js.map +0 -1
  246. package/dist/components/GrCommandPalette/chunks/GrCommandPalette-C5abL2Y7.js.map +0 -1
  247. package/dist/components/GrConfirmDialog/chunks/GrConfirmDialog-B9mIU78E.js.map +0 -1
  248. package/dist/components/GrDataTable/chunks/GrDataTable-C8a9_nCg.js.map +0 -1
  249. package/dist/components/GrDialogService/chunks/GrDialogService-6s2cQ-_6.js.map +0 -1
  250. package/dist/components/GrDrawer/chunks/GrDrawer-B7PiAcbU.js.map +0 -1
  251. package/dist/components/GrFormFile/chunks/GrFormFile-CYtXU9HV.js.map +0 -1
  252. package/dist/components/GrJsonViewer/chunks/GrJsonViewer-Cy2bknHO.js.map +0 -1
  253. package/dist/components/GrList/chunks/GrList-DmT4qzLO.js.map +0 -1
  254. package/dist/components/GrPromptDialog/chunks/GrPromptDialog-DLcl1xiq.js.map +0 -1
  255. package/dist/components/GrRadio/chunks/GrRadio-C_rESc5g.js.map +0 -1
  256. package/dist/components/GrRadioGroup/chunks/GrRadioGroup-CdQO4G-U.js.map +0 -1
  257. package/dist/components/GrResponseErrorBanner/chunks/GrResponseErrorBanner-DudGc9LO.js.map +0 -1
  258. package/dist/components/GrSelect/chunks/GrSelect-BiUrSSxc.js.map +0 -1
  259. package/dist/components/GrSidebar/chunks/GrSidebar-B8EQIlfW.js.map +0 -1
  260. package/dist/components/GrSlider/chunks/GrSlider-DJfeEl4h.js.map +0 -1
  261. package/dist/components/GrSwitch/chunks/GrSwitch-g_OOSkh7.js.map +0 -1
  262. package/dist/components/GrTextarea/chunks/GrTextarea-KREK75Bu.js.map +0 -1
  263. package/dist/components/GrToaster/chunks/GrToaster-Dp7-4B8k.js.map +0 -1
  264. package/dist/components/GrTree/chunks/GrTree-DC89I4AP.js.map +0 -1
  265. package/dist/components/GrTreeSelect/chunks/GrTreeSelect-iJnMl2t9.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"numericStep-Bq2lj4ew.js","names":[],"sources":["../../src/components/shared/numericStep.ts"],"sourcesContent":["/**\n * Крупный шаг клавиатуры: `PageUp` и `PageDown`.\n *\n * Десять обычных шагов либо десятая часть диапазона — что крупнее. Правило\n * одно на слайдер и числовое поле: у обоих `PageUp` обязан двигать значение\n * заметно, но кратно шагу, иначе значение уедет мимо сетки.\n *\n * Границы необязательны, потому что у поля их может не быть вовсе — без них\n * диапазона не существует и остаются те же десять шагов. Компонент, у которого\n * границы обязательны, но могут выродиться (`max <= min` у слайдера),\n * подставляет сюда уже нормализованный размах: так обе трактовки вырожденного\n * случая сохраняются без ветвления внутри формулы.\n */\nexport function bigStep(step: number, min?: number, max?: number): number {\n const base = step * 10\n if (min === undefined || max === undefined) return base\n\n const span = max - min\n const tenth = Math.round((span / 10) / step) * step\n\n return Math.max(base, tenth || step)\n}\n"],"mappings":";;;;;;;;;;;;;;AAaA,SAAgB,QAAQ,MAAc,KAAc,KAAsB;CACxE,MAAM,OAAO,OAAO;CACpB,IAAI,QAAQ,KAAA,KAAa,QAAQ,KAAA,GAAW,OAAO;CAEnD,MAAM,OAAO,MAAM;CACnB,MAAM,QAAQ,KAAK,MAAO,OAAO,KAAM,IAAI,IAAI;CAE/C,OAAO,KAAK,IAAI,MAAM,SAAS,IAAI;AACrC"}
1
+ {"version":3,"file":"numericStep-Bq2lj4ew.js","names":[],"sources":["../../src/components/shared/numericStep.ts"],"sourcesContent":["/**\n * Крупный шаг клавиатуры: `PageUp` и `PageDown`.\n *\n * Десять обычных шагов либо десятая часть диапазона — что крупнее. Правило\n * одно на слайдер и числовое поле: у обоих `PageUp` обязан двигать значение\n * заметно, но кратно шагу, иначе значение уедет мимо сетки.\n *\n * Границы необязательны, потому что у поля их может не быть вовсе — без них\n * диапазона не существует и остаются те же десять шагов. Компонент, у которого\n * границы обязательны, но могут выродиться (`max <= min` у слайдера),\n * подставляет сюда уже нормализованный размах: так обе трактовки вырожденного\n * случая сохраняются без ветвления внутри формулы.\n */\nexport function bigStep(step: number, min?: number, max?: number): number {\n const base = step * 10\n if (min === undefined || max === undefined)\n return base\n\n const span = max - min\n const tenth = Math.round((span / 10) / step) * step\n\n return Math.max(base, tenth || step)\n}\n"],"mappings":";;;;;;;;;;;;;;AAaA,SAAgB,QAAQ,MAAc,KAAc,KAAsB;CACxE,MAAM,OAAO,OAAO;CACpB,IAAI,QAAQ,KAAA,KAAa,QAAQ,KAAA,GAC/B,OAAO;CAET,MAAM,OAAO,MAAM;CACnB,MAAM,QAAQ,KAAK,MAAO,OAAO,KAAM,IAAI,IAAI;CAE/C,OAAO,KAAK,IAAI,MAAM,SAAS,IAAI;AACrC"}
@@ -1 +1 @@
1
- {"version":3,"file":"optionFilter-DYw9XPPx.js","names":[],"sources":["../../src/components/shared/optionFilter.ts"],"sourcesContent":["/**\n * Подстрочный поиск по опциям — один на пакет.\n *\n * Тот же предикат был написан трижды, и у каждой копии был свой контракт\n * нормализации: `GrSelect` лоукейсил запрос в вызывающем коде, `GrAutocomplete`\n * внутри матчера, `GrCommandPalette` — внутри своего. Расхождение такого рода\n * не видно на глаз и всплывает как «в одном компоненте регистр учитывается, в\n * другом нет».\n *\n * Нормализация здесь ровно одна (`normalizeOptionQuery`), а компонент выбирает,\n * что ему нужно: готовый плоский список (`filterOptions`) или голый предикат\n * (`matchesOptionQuery`) — `GrSelect` фильтрует внутри обхода групп и списка не\n * имеет.\n */\n\n/** Запрос в сравнимом виде: без краевых пробелов и в нижнем регистре. */\nexport function normalizeOptionQuery(raw: string): string {\n return raw.trim().toLowerCase()\n}\n\n/**\n * Ядро матча: подстрока в любом из полей. `undefined` в наборе пропускается —\n * у команды палитры может не быть ни описания, ни группы.\n *\n * `needle` ожидается уже нормализованным: нормализовать на каждый элемент\n * списка значило бы делать одну и ту же работу n раз.\n */\nexport function matchesQueryParts(parts: readonly (string | undefined)[], needle: string): boolean {\n if (!needle) return true\n\n return parts.some(part => part !== undefined && part.toLowerCase().includes(needle))\n}\n\n/** Опция в терминах поиска: метка видна пользователю, значение — то, что он мог запомнить. */\nexport interface OptionLike {\n label: string\n value: unknown\n}\n\n/** Матчер по умолчанию: метка и значение опции. */\nexport function matchesOptionQuery(option: OptionLike, needle: string): boolean {\n return matchesQueryParts([option.label, String(option.value)], needle)\n}\n\n/**\n * Плоская фильтрация списка опций.\n *\n * Пустой запрос отдаёт **тот же массив** и матчер не зовёт вовсе: свой `filter`\n * потребителя не должен решать, что показывать «без запроса», а копия на\n * каждый пересчёт била бы по виртуализированным спискам на тысячи опций.\n *\n * Пользовательскому матчеру уходит **сырой** запрос (только `trim`), а не\n * нормализованный: это публичный контракт пропа `filter` у `GrAutocomplete`,\n * и чужая реализация вправе смотреть на регистр.\n */\nexport function filterOptions<TOption extends OptionLike>(\n options: TOption[],\n rawQuery: string,\n filter?: (option: TOption, query: string) => boolean,\n): TOption[] {\n const query = rawQuery.trim()\n if (!query) return options\n\n if (filter) return options.filter(option => filter(option, query))\n\n const needle = query.toLowerCase()\n return options.filter(option => matchesOptionQuery(option, needle))\n}\n\n/**\n * Значения выбора — в опции для чипов.\n *\n * Значение, которого нет среди опций, показывается как есть: так выглядит\n * произвольное значение (`allowCustomValue`) и значение, пришедшее из модели\n * раньше, чем подгрузились опции. Прятать его нельзя — пользователь видел бы\n * пустое место вместо своего выбора.\n *\n * Сравнение идёт через ключ, а не `===`: объектные значения приходят в модель\n * отдельной копией, и ссылочное равенство не нашло бы ничего.\n */\nexport function resolveSelectedOptions<TValue, TOption extends { label: string, value: TValue }>(\n values: readonly TValue[],\n options: readonly TOption[],\n keyOf: (value: TValue) => string = value => String(value),\n): (TOption | { value: TValue, label: string })[] {\n const byKey = new Map<string, TOption>()\n\n // Первый выигрывает: одно и то же значение в двух группах — это одна опция,\n // и подпись чипа не должна зависеть от порядка обхода.\n for (const option of options) {\n const key = keyOf(option.value)\n if (!byKey.has(key)) byKey.set(key, option)\n }\n\n return values.map((value) => {\n const key = keyOf(value)\n return byKey.get(key) ?? { value, label: key }\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAgBA,SAAgB,qBAAqB,KAAqB;CACxD,OAAO,IAAI,KAAK,CAAC,CAAC,YAAY;AAChC;;;;;;;;AASA,SAAgB,kBAAkB,OAAwC,QAAyB;CACjG,IAAI,CAAC,QAAQ,OAAO;CAEpB,OAAO,MAAM,MAAK,SAAQ,SAAS,KAAA,KAAa,KAAK,YAAY,CAAC,CAAC,SAAS,MAAM,CAAC;AACrF;;AASA,SAAgB,mBAAmB,QAAoB,QAAyB;CAC9E,OAAO,kBAAkB,CAAC,OAAO,OAAO,OAAO,OAAO,KAAK,CAAC,GAAG,MAAM;AACvE;;;;;;;;;;;;AAaA,SAAgB,cACd,SACA,UACA,QACW;CACX,MAAM,QAAQ,SAAS,KAAK;CAC5B,IAAI,CAAC,OAAO,OAAO;CAEnB,IAAI,QAAQ,OAAO,QAAQ,QAAO,WAAU,OAAO,QAAQ,KAAK,CAAC;CAEjE,MAAM,SAAS,MAAM,YAAY;CACjC,OAAO,QAAQ,QAAO,WAAU,mBAAmB,QAAQ,MAAM,CAAC;AACpE;;;;;;;;;;;;AAaA,SAAgB,uBACd,QACA,SACA,SAAmC,UAAS,OAAO,KAAK,GACR;CAChD,MAAM,wBAAQ,IAAI,IAAqB;CAIvC,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,MAAM,MAAM,OAAO,KAAK;EAC9B,IAAI,CAAC,MAAM,IAAI,GAAG,GAAG,MAAM,IAAI,KAAK,MAAM;CAC5C;CAEA,OAAO,OAAO,KAAK,UAAU;EAC3B,MAAM,MAAM,MAAM,KAAK;EACvB,OAAO,MAAM,IAAI,GAAG,KAAK;GAAE;GAAO,OAAO;EAAI;CAC/C,CAAC;AACH"}
1
+ {"version":3,"file":"optionFilter-DYw9XPPx.js","names":[],"sources":["../../src/components/shared/optionFilter.ts"],"sourcesContent":["/**\n * Подстрочный поиск по опциям — один на пакет.\n *\n * Тот же предикат был написан трижды, и у каждой копии был свой контракт\n * нормализации: `GrSelect` лоукейсил запрос в вызывающем коде, `GrAutocomplete`\n * внутри матчера, `GrCommandPalette` — внутри своего. Расхождение такого рода\n * не видно на глаз и всплывает как «в одном компоненте регистр учитывается, в\n * другом нет».\n *\n * Нормализация здесь ровно одна (`normalizeOptionQuery`), а компонент выбирает,\n * что ему нужно: готовый плоский список (`filterOptions`) или голый предикат\n * (`matchesOptionQuery`) — `GrSelect` фильтрует внутри обхода групп и списка не\n * имеет.\n */\n\n/** Запрос в сравнимом виде: без краевых пробелов и в нижнем регистре. */\nexport function normalizeOptionQuery(raw: string): string {\n return raw.trim().toLowerCase()\n}\n\n/**\n * Ядро матча: подстрока в любом из полей. `undefined` в наборе пропускается —\n * у команды палитры может не быть ни описания, ни группы.\n *\n * `needle` ожидается уже нормализованным: нормализовать на каждый элемент\n * списка значило бы делать одну и ту же работу n раз.\n */\nexport function matchesQueryParts(parts: readonly (string | undefined)[], needle: string): boolean {\n if (!needle)\n return true\n\n return parts.some(part => part !== undefined && part.toLowerCase().includes(needle))\n}\n\n/** Опция в терминах поиска: метка видна пользователю, значение — то, что он мог запомнить. */\nexport interface OptionLike {\n label: string\n value: unknown\n}\n\n/** Матчер по умолчанию: метка и значение опции. */\nexport function matchesOptionQuery(option: OptionLike, needle: string): boolean {\n return matchesQueryParts([option.label, String(option.value)], needle)\n}\n\n/**\n * Плоская фильтрация списка опций.\n *\n * Пустой запрос отдаёт **тот же массив** и матчер не зовёт вовсе: свой `filter`\n * потребителя не должен решать, что показывать «без запроса», а копия на\n * каждый пересчёт била бы по виртуализированным спискам на тысячи опций.\n *\n * Пользовательскому матчеру уходит **сырой** запрос (только `trim`), а не\n * нормализованный: это публичный контракт пропа `filter` у `GrAutocomplete`,\n * и чужая реализация вправе смотреть на регистр.\n */\nexport function filterOptions<TOption extends OptionLike>(\n options: TOption[],\n rawQuery: string,\n filter?: (option: TOption, query: string) => boolean,\n): TOption[] {\n const query = rawQuery.trim()\n if (!query)\n return options\n\n if (filter)\n return options.filter(option => filter(option, query))\n\n const needle = query.toLowerCase()\n return options.filter(option => matchesOptionQuery(option, needle))\n}\n\n/**\n * Значения выбора — в опции для чипов.\n *\n * Значение, которого нет среди опций, показывается как есть: так выглядит\n * произвольное значение (`allowCustomValue`) и значение, пришедшее из модели\n * раньше, чем подгрузились опции. Прятать его нельзя — пользователь видел бы\n * пустое место вместо своего выбора.\n *\n * Сравнение идёт через ключ, а не `===`: объектные значения приходят в модель\n * отдельной копией, и ссылочное равенство не нашло бы ничего.\n */\nexport function resolveSelectedOptions<TValue, TOption extends { label: string, value: TValue }>(\n values: readonly TValue[],\n options: readonly TOption[],\n keyOf: (value: TValue) => string = value => String(value),\n): (TOption | { value: TValue, label: string })[] {\n const byKey = new Map<string, TOption>()\n\n // Первый выигрывает: одно и то же значение в двух группах — это одна опция,\n // и подпись чипа не должна зависеть от порядка обхода.\n for (const option of options) {\n const key = keyOf(option.value)\n if (!byKey.has(key))\n byKey.set(key, option)\n }\n\n return values.map((value) => {\n const key = keyOf(value)\n return byKey.get(key) ?? { value, label: key }\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAgBA,SAAgB,qBAAqB,KAAqB;CACxD,OAAO,IAAI,KAAK,CAAC,CAAC,YAAY;AAChC;;;;;;;;AASA,SAAgB,kBAAkB,OAAwC,QAAyB;CACjG,IAAI,CAAC,QACH,OAAO;CAET,OAAO,MAAM,MAAK,SAAQ,SAAS,KAAA,KAAa,KAAK,YAAY,CAAC,CAAC,SAAS,MAAM,CAAC;AACrF;;AASA,SAAgB,mBAAmB,QAAoB,QAAyB;CAC9E,OAAO,kBAAkB,CAAC,OAAO,OAAO,OAAO,OAAO,KAAK,CAAC,GAAG,MAAM;AACvE;;;;;;;;;;;;AAaA,SAAgB,cACd,SACA,UACA,QACW;CACX,MAAM,QAAQ,SAAS,KAAK;CAC5B,IAAI,CAAC,OACH,OAAO;CAET,IAAI,QACF,OAAO,QAAQ,QAAO,WAAU,OAAO,QAAQ,KAAK,CAAC;CAEvD,MAAM,SAAS,MAAM,YAAY;CACjC,OAAO,QAAQ,QAAO,WAAU,mBAAmB,QAAQ,MAAM,CAAC;AACpE;;;;;;;;;;;;AAaA,SAAgB,uBACd,QACA,SACA,SAAmC,UAAS,OAAO,KAAK,GACR;CAChD,MAAM,wBAAQ,IAAI,IAAqB;CAIvC,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,MAAM,MAAM,OAAO,KAAK;EAC9B,IAAI,CAAC,MAAM,IAAI,GAAG,GAChB,MAAM,IAAI,KAAK,MAAM;CACzB;CAEA,OAAO,OAAO,KAAK,UAAU;EAC3B,MAAM,MAAM,MAAM,KAAK;EACvB,OAAO,MAAM,IAAI,GAAG,KAAK;GAAE;GAAO,OAAO;EAAI;CAC/C,CAAC;AACH"}
@@ -1 +1 @@
1
- {"version":3,"file":"overlayStack-C8cfsqXa.js","names":[],"sources":["../../src/composables/internal/overlayStack.ts"],"sourcesContent":["/**\n * Единый стек слоёв оверлеев: модалки, drawer'ы, просмотрщик, панели селектов,\n * дропдауны, подсказки — всё, что открывается поверх страницы.\n *\n * Список один, и порядок регистрации совпадает с порядком открытия, но из него\n * выводятся **две разные вершины**:\n *\n * - **Esc** → последний слой любого рода. Дропдаун, открытый внутри модалки,\n * обязан закрыться первым — пользователь видит верхним именно его.\n * - **`inert`** → ставится на модальные слои ниже последнего **модального**.\n * Немодальный слой сверху модалку не понижает: иначе окно ушло бы в `inert`\n * вместе со своим же открытым дропдауном и перестало отвечать.\n * - **высота** → `openModalCount()` поднимает floating-панель над окном, из\n * которого её открыли: телепортированная панель лежит рядом с корнем окна,\n * и статический `z-index` дропдауна оставлял её под ним.\n *\n * Вести эти две вершины в двух независимых реестрах нельзя: реестры описывают\n * один и тот же порядок и расходятся молча — достаточно оверлея, который\n * зарегистрировался в одном и забыл про другой.\n *\n * Ловушку фокуса стек не реализует сам, но питает её (`useFocusTrap`): он\n * видит слои из **разных деревьев рендера** (диалоги `useDialogService`\n * монтируются отдельным `render()` в `body`) и знает их корни.\n */\n\nimport { isComposingEvent } from '../../internal/keyboard'\n\nexport interface OverlayLayer {\n /** Уникальный id зарегистрированного слоя. */\n id: number\n /**\n * Модальный слой: блокирует страницу и участвует в вычислении `inert`.\n * Немодальные (поповеры, меню, подсказки) — только в очереди Esc.\n */\n modal: boolean\n /** Закрывать ли слой по Esc (реактивный геттер: `closeOnEsc` и подобные). */\n shouldClose: () => boolean\n /** Закрыть слой. */\n close: () => void\n /** Уведомление «этот модальный слой сейчас верхний». Только для `modal`. */\n setTopmost?: (isTopmost: boolean) => void\n /**\n * Корень слоя. Ловушка фокуса нижнего слоя обязана считать его своим:\n * панель селекта, открытого внутри модалки, телепортирована в `body` и лежит\n * вне DOM-поддерева окна, но для пользователя это тот же слой.\n */\n root?: () => HTMLElement | null\n}\n\nconst stack: OverlayLayer[] = []\nlet listening = false\nlet nextId = 1\n\n/** Пересчитывает верхний **модальный** слой и уведомляет все модальные. */\nfunction syncTopmost(): void {\n const modals = stack.filter(layer => layer.modal)\n const top = modals[modals.length - 1]\n for (const layer of modals) layer.setTopmost?.(layer === top)\n}\n\nfunction handleKeydown(event: KeyboardEvent): void {\n if (event.key !== 'Escape') return\n // Esc во время IME-композиции отменяет её, а не закрывает слой.\n if (isComposingEvent(event)) return\n if (stack.length === 0) return\n\n const top = stack[stack.length - 1]\n\n // Гасим всегда, даже если верхний слой не закрывается по Esc: иначе нажатие\n // «провалится» на слой ниже и закроет не то, что видит пользователь.\n event.preventDefault()\n event.stopPropagation()\n if (typeof event.stopImmediatePropagation === 'function')\n event.stopImmediatePropagation()\n\n if (top.shouldClose()) top.close()\n}\n\nfunction startListening(): void {\n if (listening) return\n if (typeof window === 'undefined') return\n // Capture-фаза опережает любые локальные обработчики: Esc обязан достаться\n // верхнему слою, а не тому, кто первым подписался.\n window.addEventListener('keydown', handleKeydown, true)\n listening = true\n}\n\nfunction stopListening(): void {\n if (!listening) return\n if (typeof window === 'undefined') return\n window.removeEventListener('keydown', handleKeydown, true)\n listening = false\n}\n\n/** Регистрирует слой как верхний и возвращает его id. */\nexport function pushOverlayLayer(layer: Omit<OverlayLayer, 'id'>): number {\n const id = nextId++\n stack.push({ ...layer, id })\n startListening()\n syncTopmost()\n return id\n}\n\n/** Снимает слой со стека (при закрытии или размонтировании). */\nexport function removeOverlayLayer(id: number): void {\n const index = stack.findIndex(item => item.id === id)\n if (index >= 0) stack.splice(index, 1)\n if (stack.length === 0) stopListening()\n syncTopmost()\n}\n\n/**\n * Корни слоёв, открытых **поверх** данного.\n *\n * Ими ловушка фокуса дополняет свой контейнер: всё, что открыто изнутри слоя\n * (панель селекта, меню, подсказка), лежит в `body` отдельным поддеревом, и\n * без этого списка ловушка утаскивала бы из него фокус обратно в окно.\n */\nexport function layerRootsAbove(id: number): HTMLElement[] {\n const index = stack.findIndex(layer => layer.id === id)\n if (index < 0) return []\n\n return stack\n .slice(index + 1)\n .map(layer => layer.root?.() ?? null)\n .filter((root): root is HTMLElement => root !== null)\n}\n\n/** Корни всех зарегистрированных слоёв. */\nexport function allLayerRoots(): HTMLElement[] {\n return stack\n .map(layer => layer.root?.() ?? null)\n .filter((root): root is HTMLElement => root !== null)\n}\n\n/** Сколько слоёв открыто. Для тестов и диагностики. */\nexport function overlayStackSize(): number {\n return stack.length\n}\n\n/**\n * Сколько **модальных** слоёв открыто прямо сейчас.\n *\n * Читает `useFloating`, когда считает высоту панели: панель телепортирована в\n * общий портал и лежит рядом с корнем окна, а не внутри него, поэтому свой\n * стек-контекст её не спасает — высоту приходится брать из порядка слоёв.\n */\nexport function openModalCount(): number {\n return stack.reduce((count, layer) => count + (layer.modal ? 1 : 0), 0)\n}\n\n/** Тестовая/служебная очистка стека. */\nexport function resetOverlayStack(): void {\n stack.splice(0, stack.length)\n stopListening()\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,IAAM,QAAwB,CAAC;AAC/B,IAAI,YAAY;AAChB,IAAI,SAAS;;AAGb,SAAS,cAAoB;CAC3B,MAAM,SAAS,MAAM,QAAO,UAAS,MAAM,KAAK;CAChD,MAAM,MAAM,OAAO,OAAO,SAAS;CACnC,KAAK,MAAM,SAAS,QAAQ,MAAM,aAAa,UAAU,GAAG;AAC9D;AAEA,SAAS,cAAc,OAA4B;CACjD,IAAI,MAAM,QAAQ,UAAU;CAE5B,IAAI,iBAAiB,KAAK,GAAG;CAC7B,IAAI,MAAM,WAAW,GAAG;CAExB,MAAM,MAAM,MAAM,MAAM,SAAS;CAIjC,MAAM,eAAe;CACrB,MAAM,gBAAgB;CACtB,IAAI,OAAO,MAAM,6BAA6B,YAC5C,MAAM,yBAAyB;CAEjC,IAAI,IAAI,YAAY,GAAG,IAAI,MAAM;AACnC;AAEA,SAAS,iBAAuB;CAC9B,IAAI,WAAW;CACf,IAAI,OAAO,WAAW,aAAa;CAGnC,OAAO,iBAAiB,WAAW,eAAe,IAAI;CACtD,YAAY;AACd;AAEA,SAAS,gBAAsB;CAC7B,IAAI,CAAC,WAAW;CAChB,IAAI,OAAO,WAAW,aAAa;CACnC,OAAO,oBAAoB,WAAW,eAAe,IAAI;CACzD,YAAY;AACd;;AAGA,SAAgB,iBAAiB,OAAyC;CACxE,MAAM,KAAK;CACX,MAAM,KAAK;EAAE,GAAG;EAAO;CAAG,CAAC;CAC3B,eAAe;CACf,YAAY;CACZ,OAAO;AACT;;AAGA,SAAgB,mBAAmB,IAAkB;CACnD,MAAM,QAAQ,MAAM,WAAU,SAAQ,KAAK,OAAO,EAAE;CACpD,IAAI,SAAS,GAAG,MAAM,OAAO,OAAO,CAAC;CACrC,IAAI,MAAM,WAAW,GAAG,cAAc;CACtC,YAAY;AACd;;;;;;;;AASA,SAAgB,gBAAgB,IAA2B;CACzD,MAAM,QAAQ,MAAM,WAAU,UAAS,MAAM,OAAO,EAAE;CACtD,IAAI,QAAQ,GAAG,OAAO,CAAC;CAEvB,OAAO,MACJ,MAAM,QAAQ,CAAC,CAAC,CAChB,KAAI,UAAS,MAAM,OAAO,KAAK,IAAI,CAAC,CACpC,QAAQ,SAA8B,SAAS,IAAI;AACxD;;;;;;;;AAqBA,SAAgB,iBAAyB;CACvC,OAAO,MAAM,QAAQ,OAAO,UAAU,SAAS,MAAM,QAAQ,IAAI,IAAI,CAAC;AACxE"}
1
+ {"version":3,"file":"overlayStack-C8cfsqXa.js","names":[],"sources":["../../src/composables/internal/overlayStack.ts"],"sourcesContent":["/**\n * Единый стек слоёв оверлеев: модалки, drawer'ы, просмотрщик, панели селектов,\n * дропдауны, подсказки — всё, что открывается поверх страницы.\n *\n * Список один, и порядок регистрации совпадает с порядком открытия, но из него\n * выводятся **две разные вершины**:\n *\n * - **Esc** → последний слой любого рода. Дропдаун, открытый внутри модалки,\n * обязан закрыться первым — пользователь видит верхним именно его.\n * - **`inert`** → ставится на модальные слои ниже последнего **модального**.\n * Немодальный слой сверху модалку не понижает: иначе окно ушло бы в `inert`\n * вместе со своим же открытым дропдауном и перестало отвечать.\n * - **высота** → `openModalCount()` поднимает floating-панель над окном, из\n * которого её открыли: телепортированная панель лежит рядом с корнем окна,\n * и статический `z-index` дропдауна оставлял её под ним.\n *\n * Вести эти две вершины в двух независимых реестрах нельзя: реестры описывают\n * один и тот же порядок и расходятся молча — достаточно оверлея, который\n * зарегистрировался в одном и забыл про другой.\n *\n * Ловушку фокуса стек не реализует сам, но питает её (`useFocusTrap`): он\n * видит слои из **разных деревьев рендера** (диалоги `useDialogService`\n * монтируются отдельным `render()` в `body`) и знает их корни.\n */\n\nimport { isComposingEvent } from '../../internal/keyboard'\n\nexport interface OverlayLayer {\n /** Уникальный id зарегистрированного слоя. */\n id: number\n /**\n * Модальный слой: блокирует страницу и участвует в вычислении `inert`.\n * Немодальные (поповеры, меню, подсказки) — только в очереди Esc.\n */\n modal: boolean\n /** Закрывать ли слой по Esc (реактивный геттер: `closeOnEsc` и подобные). */\n shouldClose: () => boolean\n /** Закрыть слой. */\n close: () => void\n /** Уведомление «этот модальный слой сейчас верхний». Только для `modal`. */\n setTopmost?: (isTopmost: boolean) => void\n /**\n * Корень слоя. Ловушка фокуса нижнего слоя обязана считать его своим:\n * панель селекта, открытого внутри модалки, телепортирована в `body` и лежит\n * вне DOM-поддерева окна, но для пользователя это тот же слой.\n */\n root?: () => HTMLElement | null\n}\n\nconst stack: OverlayLayer[] = []\nlet listening = false\nlet nextId = 1\n\n/** Пересчитывает верхний **модальный** слой и уведомляет все модальные. */\nfunction syncTopmost(): void {\n const modals = stack.filter(layer => layer.modal)\n const top = modals[modals.length - 1]\n for (const layer of modals) layer.setTopmost?.(layer === top)\n}\n\nfunction handleKeydown(event: KeyboardEvent): void {\n if (event.key !== 'Escape')\n return\n // Esc во время IME-композиции отменяет её, а не закрывает слой.\n if (isComposingEvent(event))\n return\n if (stack.length === 0)\n return\n\n const top = stack[stack.length - 1]\n\n // Гасим всегда, даже если верхний слой не закрывается по Esc: иначе нажатие\n // «провалится» на слой ниже и закроет не то, что видит пользователь.\n event.preventDefault()\n event.stopPropagation()\n if (typeof event.stopImmediatePropagation === 'function')\n event.stopImmediatePropagation()\n\n if (top.shouldClose())\n top.close()\n}\n\nfunction startListening(): void {\n if (listening)\n return\n if (typeof window === 'undefined')\n return\n // Capture-фаза опережает любые локальные обработчики: Esc обязан достаться\n // верхнему слою, а не тому, кто первым подписался.\n window.addEventListener('keydown', handleKeydown, true)\n listening = true\n}\n\nfunction stopListening(): void {\n if (!listening)\n return\n if (typeof window === 'undefined')\n return\n window.removeEventListener('keydown', handleKeydown, true)\n listening = false\n}\n\n/** Регистрирует слой как верхний и возвращает его id. */\nexport function pushOverlayLayer(layer: Omit<OverlayLayer, 'id'>): number {\n const id = nextId++\n stack.push({ ...layer, id })\n startListening()\n syncTopmost()\n return id\n}\n\n/** Снимает слой со стека (при закрытии или размонтировании). */\nexport function removeOverlayLayer(id: number): void {\n const index = stack.findIndex(item => item.id === id)\n if (index >= 0)\n stack.splice(index, 1)\n if (stack.length === 0)\n stopListening()\n syncTopmost()\n}\n\n/**\n * Корни слоёв, открытых **поверх** данного.\n *\n * Ими ловушка фокуса дополняет свой контейнер: всё, что открыто изнутри слоя\n * (панель селекта, меню, подсказка), лежит в `body` отдельным поддеревом, и\n * без этого списка ловушка утаскивала бы из него фокус обратно в окно.\n */\nexport function layerRootsAbove(id: number): HTMLElement[] {\n const index = stack.findIndex(layer => layer.id === id)\n if (index < 0)\n return []\n\n return stack\n .slice(index + 1)\n .map(layer => layer.root?.() ?? null)\n .filter((root): root is HTMLElement => root !== null)\n}\n\n/** Корни всех зарегистрированных слоёв. */\nexport function allLayerRoots(): HTMLElement[] {\n return stack\n .map(layer => layer.root?.() ?? null)\n .filter((root): root is HTMLElement => root !== null)\n}\n\n/** Сколько слоёв открыто. Для тестов и диагностики. */\nexport function overlayStackSize(): number {\n return stack.length\n}\n\n/**\n * Сколько **модальных** слоёв открыто прямо сейчас.\n *\n * Читает `useFloating`, когда считает высоту панели: панель телепортирована в\n * общий портал и лежит рядом с корнем окна, а не внутри него, поэтому свой\n * стек-контекст её не спасает — высоту приходится брать из порядка слоёв.\n */\nexport function openModalCount(): number {\n return stack.reduce((count, layer) => count + (layer.modal ? 1 : 0), 0)\n}\n\n/** Тестовая/служебная очистка стека. */\nexport function resetOverlayStack(): void {\n stack.splice(0, stack.length)\n stopListening()\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,IAAM,QAAwB,CAAC;AAC/B,IAAI,YAAY;AAChB,IAAI,SAAS;;AAGb,SAAS,cAAoB;CAC3B,MAAM,SAAS,MAAM,QAAO,UAAS,MAAM,KAAK;CAChD,MAAM,MAAM,OAAO,OAAO,SAAS;CACnC,KAAK,MAAM,SAAS,QAAQ,MAAM,aAAa,UAAU,GAAG;AAC9D;AAEA,SAAS,cAAc,OAA4B;CACjD,IAAI,MAAM,QAAQ,UAChB;CAEF,IAAI,iBAAiB,KAAK,GACxB;CACF,IAAI,MAAM,WAAW,GACnB;CAEF,MAAM,MAAM,MAAM,MAAM,SAAS;CAIjC,MAAM,eAAe;CACrB,MAAM,gBAAgB;CACtB,IAAI,OAAO,MAAM,6BAA6B,YAC5C,MAAM,yBAAyB;CAEjC,IAAI,IAAI,YAAY,GAClB,IAAI,MAAM;AACd;AAEA,SAAS,iBAAuB;CAC9B,IAAI,WACF;CACF,IAAI,OAAO,WAAW,aACpB;CAGF,OAAO,iBAAiB,WAAW,eAAe,IAAI;CACtD,YAAY;AACd;AAEA,SAAS,gBAAsB;CAC7B,IAAI,CAAC,WACH;CACF,IAAI,OAAO,WAAW,aACpB;CACF,OAAO,oBAAoB,WAAW,eAAe,IAAI;CACzD,YAAY;AACd;;AAGA,SAAgB,iBAAiB,OAAyC;CACxE,MAAM,KAAK;CACX,MAAM,KAAK;EAAE,GAAG;EAAO;CAAG,CAAC;CAC3B,eAAe;CACf,YAAY;CACZ,OAAO;AACT;;AAGA,SAAgB,mBAAmB,IAAkB;CACnD,MAAM,QAAQ,MAAM,WAAU,SAAQ,KAAK,OAAO,EAAE;CACpD,IAAI,SAAS,GACX,MAAM,OAAO,OAAO,CAAC;CACvB,IAAI,MAAM,WAAW,GACnB,cAAc;CAChB,YAAY;AACd;;;;;;;;AASA,SAAgB,gBAAgB,IAA2B;CACzD,MAAM,QAAQ,MAAM,WAAU,UAAS,MAAM,OAAO,EAAE;CACtD,IAAI,QAAQ,GACV,OAAO,CAAC;CAEV,OAAO,MACJ,MAAM,QAAQ,CAAC,CAAC,CAChB,KAAI,UAAS,MAAM,OAAO,KAAK,IAAI,CAAC,CACpC,QAAQ,SAA8B,SAAS,IAAI;AACxD;;;;;;;;AAqBA,SAAgB,iBAAyB;CACvC,OAAO,MAAM,QAAQ,OAAO,UAAU,SAAS,MAAM,QAAQ,IAAI,IAAI,CAAC;AACxE"}
@@ -1 +1 @@
1
- {"version":3,"file":"portalRoot-CVhCgdG6.js","names":[],"sources":["../../src/composables/internal/portalRoot.ts"],"sourcesContent":["/**\n * Единая точка монтирования оверлеев.\n *\n * До неё каждый оверлей телепортировался в `body` сам по себе, и правило\n * `inert` держалось на договорённости «все оверлеи лежат ровно на один уровень\n * ниже `body`». Один контейнер даёт управляемую точку монтирования (своё\n * приложение в контейнере, микрофронтенд, shadow DOM), одну точку очистки и\n * порядок, совпадающий с порядком открытия.\n *\n * **У корня не должно быть ни стилей, ни классов.** `transform`, `filter`,\n * `contain`, `perspective` и `will-change` создают containing block для\n * `position: fixed`, и тогда панели `useFloating` начинают позиционироваться\n * относительно портала, а не вьюпорта — с виду это «панель прилипла не туда»,\n * и ищется такое долго.\n */\n\nexport const GR_PORTAL_ID = 'gr-portal'\n\n/**\n * Создаёт корень портала при первом обращении и возвращает его.\n *\n * `null` на сервере: DOM там трогать нечем, а телепорт всё равно выключен до\n * монтирования (см. `useTeleportEnabled`).\n */\nexport function ensurePortalRoot(): HTMLElement | null {\n if (typeof document === 'undefined') return null\n\n const existing = document.getElementById(GR_PORTAL_ID)\n if (existing) return existing\n\n const root = document.createElement('div')\n root.id = GR_PORTAL_ID\n root.setAttribute('data-gr-portal', '')\n document.body.appendChild(root)\n\n return root\n}\n\n/** Текущий корень портала, если он уже создан. */\nexport function getPortalRoot(): HTMLElement | null {\n if (typeof document === 'undefined') return null\n return document.getElementById(GR_PORTAL_ID)\n}\n\n/** Тестовая/служебная очистка: убирает корень из документа. */\nexport function resetPortalRoot(): void {\n getPortalRoot()?.remove()\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAgBA,IAAa,eAAe;;;;;;;AAQ5B,SAAgB,mBAAuC;CACrD,IAAI,OAAO,aAAa,aAAa,OAAO;CAE5C,MAAM,WAAW,SAAS,eAAe,YAAY;CACrD,IAAI,UAAU,OAAO;CAErB,MAAM,OAAO,SAAS,cAAc,KAAK;CACzC,KAAK,KAAK;CACV,KAAK,aAAa,kBAAkB,EAAE;CACtC,SAAS,KAAK,YAAY,IAAI;CAE9B,OAAO;AACT;;AAGA,SAAgB,gBAAoC;CAClD,IAAI,OAAO,aAAa,aAAa,OAAO;CAC5C,OAAO,SAAS,eAAe,YAAY;AAC7C;;AAGA,SAAgB,kBAAwB;CACtC,cAAc,CAAC,EAAE,OAAO;AAC1B"}
1
+ {"version":3,"file":"portalRoot-CVhCgdG6.js","names":[],"sources":["../../src/composables/internal/portalRoot.ts"],"sourcesContent":["/**\n * Единая точка монтирования оверлеев.\n *\n * До неё каждый оверлей телепортировался в `body` сам по себе, и правило\n * `inert` держалось на договорённости «все оверлеи лежат ровно на один уровень\n * ниже `body`». Один контейнер даёт управляемую точку монтирования (своё\n * приложение в контейнере, микрофронтенд, shadow DOM), одну точку очистки и\n * порядок, совпадающий с порядком открытия.\n *\n * **У корня не должно быть ни стилей, ни классов.** `transform`, `filter`,\n * `contain`, `perspective` и `will-change` создают containing block для\n * `position: fixed`, и тогда панели `useFloating` начинают позиционироваться\n * относительно портала, а не вьюпорта — с виду это «панель прилипла не туда»,\n * и ищется такое долго.\n */\n\nexport const GR_PORTAL_ID = 'gr-portal'\n\n/**\n * Создаёт корень портала при первом обращении и возвращает его.\n *\n * `null` на сервере: DOM там трогать нечем, а телепорт всё равно выключен до\n * монтирования (см. `useTeleportEnabled`).\n */\nexport function ensurePortalRoot(): HTMLElement | null {\n if (typeof document === 'undefined')\n return null\n\n const existing = document.getElementById(GR_PORTAL_ID)\n if (existing)\n return existing\n\n const root = document.createElement('div')\n root.id = GR_PORTAL_ID\n root.setAttribute('data-gr-portal', '')\n document.body.appendChild(root)\n\n return root\n}\n\n/** Текущий корень портала, если он уже создан. */\nexport function getPortalRoot(): HTMLElement | null {\n if (typeof document === 'undefined')\n return null\n return document.getElementById(GR_PORTAL_ID)\n}\n\n/** Тестовая/служебная очистка: убирает корень из документа. */\nexport function resetPortalRoot(): void {\n getPortalRoot()?.remove()\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAgBA,IAAa,eAAe;;;;;;;AAQ5B,SAAgB,mBAAuC;CACrD,IAAI,OAAO,aAAa,aACtB,OAAO;CAET,MAAM,WAAW,SAAS,eAAe,YAAY;CACrD,IAAI,UACF,OAAO;CAET,MAAM,OAAO,SAAS,cAAc,KAAK;CACzC,KAAK,KAAK;CACV,KAAK,aAAa,kBAAkB,EAAE;CACtC,SAAS,KAAK,YAAY,IAAI;CAE9B,OAAO;AACT;;AAGA,SAAgB,gBAAoC;CAClD,IAAI,OAAO,aAAa,aACtB,OAAO;CACT,OAAO,SAAS,eAAe,YAAY;AAC7C;;AAGA,SAAgB,kBAAwB;CACtC,cAAc,CAAC,EAAE,OAAO;AAC1B"}
@@ -1 +1 @@
1
- {"version":3,"file":"renamedProp-BwcN-SJg.js","names":[],"sources":["../../src/components/shared/renamedProp.ts"],"sourcesContent":["import { watchEffect } from 'vue'\n\n/**\n * Предупреждение о пропе, который переименовали.\n *\n * Незнакомый атрибут Vue не отвергает: он уходит в `$attrs` и садится на\n * корневой узел. Значит переименование пропа проходит у потребителя **молча** —\n * не падают ни типы (`vue-tsc` не проверяет лишние атрибуты: принимать их\n * вправе любой компонент), ни рантайм. Компонент просто рисуется дефолтом, и\n * найти это можно только сверкой шаблона со списком пропов.\n *\n * Так и случилось со сменой `variant` на `tone` у `GrBadge` и `GrChip`: у\n * стороннего пакета тихо разъехались десять мест в шести файлах. Причём\n * `variant` в кольце остаётся законным именем у `GrButton`, `GrCard`, `GrAlert`\n * и десятка других — то есть по аналогии не угадать.\n *\n * Предупреждение живёт только в разработке и ничего не чинит за потребителя:\n * подставлять снятое имя значило бы завести алиас, который после 1.0 останется\n * навсегда.\n */\nexport function warnRenamedProp(\n component: string,\n attrs: Record<string, unknown>,\n renames: Readonly<Record<string, string>>,\n): void {\n if (!__GR_DEV__) return\n\n const reported = new Set<string>()\n\n watchEffect(() => {\n for (const [from, to] of Object.entries(renames)) {\n if (attrs[from] === undefined || reported.has(from)) continue\n\n reported.add(from)\n console.warn(\n `[granularity] ${component}: проп \\`${from}\\` переименован в \\`${to}\\`. `\n + `Сейчас \\`${from}\\` уходит в атрибуты корневого узла и на вид компонента не влияет.`,\n )\n }\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,gBACd,WACA,OACA,SACM;CACN,IAAI,EAAA,OAAA,YAAA,eAAA,QAAA,IAAA,aAAA,eAAa;CAEjB,MAAM,2BAAW,IAAI,IAAY;CAEjC,kBAAkB;EAChB,KAAK,MAAM,CAAC,MAAM,OAAO,OAAO,QAAQ,OAAO,GAAG;GAChD,IAAI,MAAM,UAAU,KAAA,KAAa,SAAS,IAAI,IAAI,GAAG;GAErD,SAAS,IAAI,IAAI;GACjB,QAAQ,KACN,iBAAiB,UAAU,WAAW,KAAK,sBAAsB,GAAG,eACtD,KAAK,mEACrB;EACF;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"renamedProp-BwcN-SJg.js","names":[],"sources":["../../src/components/shared/renamedProp.ts"],"sourcesContent":["import { watchEffect } from 'vue'\n\n/**\n * Предупреждение о пропе, который переименовали.\n *\n * Незнакомый атрибут Vue не отвергает: он уходит в `$attrs` и садится на\n * корневой узел. Значит переименование пропа проходит у потребителя **молча** —\n * не падают ни типы (`vue-tsc` не проверяет лишние атрибуты: принимать их\n * вправе любой компонент), ни рантайм. Компонент просто рисуется дефолтом, и\n * найти это можно только сверкой шаблона со списком пропов.\n *\n * Так и случилось со сменой `variant` на `tone` у `GrBadge` и `GrChip`: у\n * стороннего пакета тихо разъехались десять мест в шести файлах. Причём\n * `variant` в кольце остаётся законным именем у `GrButton`, `GrCard`, `GrAlert`\n * и десятка других — то есть по аналогии не угадать.\n *\n * Предупреждение живёт только в разработке и ничего не чинит за потребителя:\n * подставлять снятое имя значило бы завести алиас, который после 1.0 останется\n * навсегда.\n */\nexport function warnRenamedProp(\n component: string,\n attrs: Record<string, unknown>,\n renames: Readonly<Record<string, string>>,\n): void {\n if (!__GR_DEV__)\n return\n\n const reported = new Set<string>()\n\n watchEffect(() => {\n for (const [from, to] of Object.entries(renames)) {\n if (attrs[from] === undefined || reported.has(from))\n continue\n\n reported.add(from)\n console.warn(\n `[granularity] ${component}: проп \\`${from}\\` переименован в \\`${to}\\`. `\n + `Сейчас \\`${from}\\` уходит в атрибуты корневого узла и на вид компонента не влияет.`,\n )\n }\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,gBACd,WACA,OACA,SACM;CACN,IAAI,EAAA,OAAA,YAAA,eAAA,QAAA,IAAA,aAAA,eACF;CAEF,MAAM,2BAAW,IAAI,IAAY;CAEjC,kBAAkB;EAChB,KAAK,MAAM,CAAC,MAAM,OAAO,OAAO,QAAQ,OAAO,GAAG;GAChD,IAAI,MAAM,UAAU,KAAA,KAAa,SAAS,IAAI,IAAI,GAChD;GAEF,SAAS,IAAI,IAAI;GACjB,QAAQ,KACN,iBAAiB,UAAU,WAAW,KAAK,sBAAsB,GAAG,eACtD,KAAK,mEACrB;EACF;CACF,CAAC;AACH"}
@@ -1,5 +1,5 @@
1
1
  import { n as splitClassTokens } from "./classTokens-8v2XC74x.js";
2
- import { c as tones, l as variantClass, n as blockClass, o as sizes, r as disabledClassByVariant, s as squareSizes, t as base } from "./grButtonStyles-B_-YRE__.js";
2
+ import { c as tones, l as variantClass, n as blockClass, o as sizes, r as disabledClassByVariant, s as squareSizes, t as base } from "./grButtonStyles-B3wRWW_i.js";
3
3
  //#region src/components/GrButton/safelist.ts
4
4
  var variantTokens = Object.keys(tones).flatMap((tone) => [
5
5
  "primary",
@@ -1 +1 @@
1
- {"version":3,"file":"safelist-BOq5Li7p.js","names":[],"sources":["../../src/components/GrButton/safelist.ts"],"sourcesContent":["import { splitClassTokens } from '../shared/classTokens'\nimport {\n base,\n blockClass,\n disabledClassByVariant,\n sizes,\n squareSizes,\n tones,\n variantClass,\n type GrButtonTone,\n type GrButtonVariant,\n} from './grButtonStyles'\n\nconst variantTokens = (Object.keys(tones) as GrButtonTone[]).flatMap(tone =>\n (['primary', 'secondary', 'outline', 'ghost', 'ghost-border'] as GrButtonVariant[])\n .flatMap(variant => splitClassTokens(variantClass(variant, tone))),\n)\n\nexport const grButtonClassTokens = {\n base: splitClassTokens(base),\n sizes: Object.values(sizes).flatMap(splitClassTokens),\n squareSizes: Object.values(squareSizes).flatMap(splitClassTokens),\n variants: variantTokens,\n states: [...splitClassTokens(blockClass), ...Object.values(disabledClassByVariant).flatMap(splitClassTokens)],\n} as const\n\nexport const grButtonSafelist = [...new Set([\n ...grButtonClassTokens.base,\n ...grButtonClassTokens.sizes,\n ...grButtonClassTokens.squareSizes,\n ...grButtonClassTokens.variants,\n ...grButtonClassTokens.states,\n])]\n"],"mappings":";;;AAaA,IAAM,gBAAiB,OAAO,KAAK,KAAK,CAAC,CAAoB,SAAQ,SAClE;CAAC;CAAW;CAAa;CAAW;CAAS;AAAc,CAAC,CAC1D,SAAQ,YAAW,iBAAiB,aAAa,SAAS,IAAI,CAAC,CAAC,CACrE;AAEA,IAAa,sBAAsB;CACjC,MAAM,iBAAiB,IAAI;CAC3B,OAAO,OAAO,OAAO,KAAK,CAAC,CAAC,QAAQ,gBAAgB;CACpD,aAAa,OAAO,OAAO,WAAW,CAAC,CAAC,QAAQ,gBAAgB;CAChE,UAAU;CACV,QAAQ,CAAC,GAAG,iBAAiB,UAAU,GAAG,GAAG,OAAO,OAAO,sBAAsB,CAAC,CAAC,QAAQ,gBAAgB,CAAC;AAC9G;AAEA,IAAa,mBAAmB,CAAC,mBAAG,IAAI,IAAI;CAC1C,GAAG,oBAAoB;CACvB,GAAG,oBAAoB;CACvB,GAAG,oBAAoB;CACvB,GAAG,oBAAoB;CACvB,GAAG,oBAAoB;AACzB,CAAC,CAAC"}
1
+ {"version":3,"file":"safelist-DxkMj5IW.js","names":[],"sources":["../../src/components/GrButton/safelist.ts"],"sourcesContent":["import { splitClassTokens } from '../shared/classTokens'\nimport {\n base,\n blockClass,\n disabledClassByVariant,\n sizes,\n squareSizes,\n tones,\n variantClass,\n type GrButtonTone,\n type GrButtonVariant,\n} from './grButtonStyles'\n\nconst variantTokens = (Object.keys(tones) as GrButtonTone[]).flatMap(tone =>\n (['primary', 'secondary', 'outline', 'ghost', 'ghost-border'] as GrButtonVariant[])\n .flatMap(variant => splitClassTokens(variantClass(variant, tone))),\n)\n\nexport const grButtonClassTokens = {\n base: splitClassTokens(base),\n sizes: Object.values(sizes).flatMap(splitClassTokens),\n squareSizes: Object.values(squareSizes).flatMap(splitClassTokens),\n variants: variantTokens,\n states: [...splitClassTokens(blockClass), ...Object.values(disabledClassByVariant).flatMap(splitClassTokens)],\n} as const\n\nexport const grButtonSafelist = [...new Set([\n ...grButtonClassTokens.base,\n ...grButtonClassTokens.sizes,\n ...grButtonClassTokens.squareSizes,\n ...grButtonClassTokens.variants,\n ...grButtonClassTokens.states,\n])]\n"],"mappings":";;;AAaA,IAAM,gBAAiB,OAAO,KAAK,KAAK,CAAC,CAAoB,SAAQ,SAClE;CAAC;CAAW;CAAa;CAAW;CAAS;AAAc,CAAC,CAC1D,SAAQ,YAAW,iBAAiB,aAAa,SAAS,IAAI,CAAC,CAAC,CACrE;AAEA,IAAa,sBAAsB;CACjC,MAAM,iBAAiB,IAAI;CAC3B,OAAO,OAAO,OAAO,KAAK,CAAC,CAAC,QAAQ,gBAAgB;CACpD,aAAa,OAAO,OAAO,WAAW,CAAC,CAAC,QAAQ,gBAAgB;CAChE,UAAU;CACV,QAAQ,CAAC,GAAG,iBAAiB,UAAU,GAAG,GAAG,OAAO,OAAO,sBAAsB,CAAC,CAAC,QAAQ,gBAAgB,CAAC;AAC9G;AAEA,IAAa,mBAAmB,CAAC,mBAAG,IAAI,IAAI;CAC1C,GAAG,oBAAoB;CACvB,GAAG,oBAAoB;CACvB,GAAG,oBAAoB;CACvB,GAAG,oBAAoB;CACvB,GAAG,oBAAoB;AACzB,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"slotNodes-DxNXpC7v.js","names":[],"sources":["../../src/components/shared/slotNodes.ts"],"sourcesContent":["import { Comment, Fragment, Text, type VNode } from 'vue'\n\n/**\n * Разбор содержимого слота.\n *\n * Общий модуль, а не приватные функции внутри SFC: то же самое нужно и\n * `GrFileUpload` («слот с кастомным UI или просто текст»), и `GrList` («в\n * списке пусто»). Импорт из чужой компонентной директории на сборке дал бы\n * ребро между компонентами, которого в разметке нет.\n */\n\n/** `v-for` и `<template>` приходят фрагментами — разворачиваем до реальных узлов. */\nexport function flattenSlotNodes(nodes: VNode[]): VNode[] {\n const out: VNode[] = []\n\n for (const node of nodes) {\n if (node.type === Fragment && Array.isArray(node.children)) {\n out.push(...flattenSlotNodes(node.children as VNode[]))\n continue\n }\n\n out.push(node)\n }\n\n return out\n}\n\nexport function isWhitespaceTextNode(node: VNode): boolean {\n if (node.type !== Text) return false\n return typeof node.children === 'string' && node.children.trim().length === 0\n}\n\n/**\n * Узлы, которые пользователь действительно видит: без комментариев (их\n * оставляет после себя каждый `v-if`) и без переносов строк из шаблона.\n */\nexport function meaningfulSlotNodes(nodes: VNode[]): VNode[] {\n return flattenSlotNodes(nodes).filter(node => node.type !== Comment && !isWhitespaceTextNode(node))\n}\n\n/** Есть ли в слоте хоть что-то, кроме комментариев и пробелов. */\nexport function hasMeaningfulSlotContent(nodes: VNode[]): boolean {\n return meaningfulSlotNodes(nodes).length > 0\n}\n"],"mappings":";;;;;;;;;;;AAYA,SAAgB,iBAAiB,OAAyB;CACxD,MAAM,MAAe,CAAC;CAEtB,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,KAAK,SAAS,YAAY,MAAM,QAAQ,KAAK,QAAQ,GAAG;GAC1D,IAAI,KAAK,GAAG,iBAAiB,KAAK,QAAmB,CAAC;GACtD;EACF;EAEA,IAAI,KAAK,IAAI;CACf;CAEA,OAAO;AACT;AAEA,SAAgB,qBAAqB,MAAsB;CACzD,IAAI,KAAK,SAAS,MAAM,OAAO;CAC/B,OAAO,OAAO,KAAK,aAAa,YAAY,KAAK,SAAS,KAAK,CAAC,CAAC,WAAW;AAC9E;;;;;AAMA,SAAgB,oBAAoB,OAAyB;CAC3D,OAAO,iBAAiB,KAAK,CAAC,CAAC,QAAO,SAAQ,KAAK,SAAS,WAAW,CAAC,qBAAqB,IAAI,CAAC;AACpG;;AAGA,SAAgB,yBAAyB,OAAyB;CAChE,OAAO,oBAAoB,KAAK,CAAC,CAAC,SAAS;AAC7C"}
1
+ {"version":3,"file":"slotNodes-DxNXpC7v.js","names":[],"sources":["../../src/components/shared/slotNodes.ts"],"sourcesContent":["import { Comment, Fragment, Text, type VNode } from 'vue'\n\n/**\n * Разбор содержимого слота.\n *\n * Общий модуль, а не приватные функции внутри SFC: то же самое нужно и\n * `GrFileUpload` («слот с кастомным UI или просто текст»), и `GrList` («в\n * списке пусто»). Импорт из чужой компонентной директории на сборке дал бы\n * ребро между компонентами, которого в разметке нет.\n */\n\n/** `v-for` и `<template>` приходят фрагментами — разворачиваем до реальных узлов. */\nexport function flattenSlotNodes(nodes: VNode[]): VNode[] {\n const out: VNode[] = []\n\n for (const node of nodes) {\n if (node.type === Fragment && Array.isArray(node.children)) {\n out.push(...flattenSlotNodes(node.children as VNode[]))\n continue\n }\n\n out.push(node)\n }\n\n return out\n}\n\nexport function isWhitespaceTextNode(node: VNode): boolean {\n if (node.type !== Text)\n return false\n return typeof node.children === 'string' && node.children.trim().length === 0\n}\n\n/**\n * Узлы, которые пользователь действительно видит: без комментариев (их\n * оставляет после себя каждый `v-if`) и без переносов строк из шаблона.\n */\nexport function meaningfulSlotNodes(nodes: VNode[]): VNode[] {\n return flattenSlotNodes(nodes).filter(node => node.type !== Comment && !isWhitespaceTextNode(node))\n}\n\n/** Есть ли в слоте хоть что-то, кроме комментариев и пробелов. */\nexport function hasMeaningfulSlotContent(nodes: VNode[]): boolean {\n return meaningfulSlotNodes(nodes).length > 0\n}\n"],"mappings":";;;;;;;;;;;AAYA,SAAgB,iBAAiB,OAAyB;CACxD,MAAM,MAAe,CAAC;CAEtB,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,KAAK,SAAS,YAAY,MAAM,QAAQ,KAAK,QAAQ,GAAG;GAC1D,IAAI,KAAK,GAAG,iBAAiB,KAAK,QAAmB,CAAC;GACtD;EACF;EAEA,IAAI,KAAK,IAAI;CACf;CAEA,OAAO;AACT;AAEA,SAAgB,qBAAqB,MAAsB;CACzD,IAAI,KAAK,SAAS,MAChB,OAAO;CACT,OAAO,OAAO,KAAK,aAAa,YAAY,KAAK,SAAS,KAAK,CAAC,CAAC,WAAW;AAC9E;;;;;AAMA,SAAgB,oBAAoB,OAAyB;CAC3D,OAAO,iBAAiB,KAAK,CAAC,CAAC,QAAO,SAAQ,KAAK,SAAS,WAAW,CAAC,qBAAqB,IAAI,CAAC;AACpG;;AAGA,SAAgB,yBAAyB,OAAyB;CAChE,OAAO,oBAAoB,KAAK,CAAC,CAAC,SAAS;AAC7C"}
@@ -1 +1 @@
1
- {"version":3,"file":"useControlAddons-asUr10Pf.js","names":[],"sources":["../../src/composables/internal/useAddonMeasurement.ts","../../src/composables/internal/useControlAddons.ts"],"sourcesContent":["import { nextTick, onBeforeUnmount, onMounted, onUpdated, ref } from 'vue'\nimport type { Ref } from 'vue'\n\n/**\n * Измерение фактической ширины prefix/suffix add-on'ов через `ResizeObserver`.\n * Общая логика GrInput/GrNumberInput.\n *\n * Потребитель вешает возвращённые `prefixEl`/`suffixEl` на DOM-элементы аддонов\n * и передаёт реактивные `hasPrefix`/`hasSuffix` (обычно — наличие слотов).\n * Возвращает измеренные ширины (`\"<n>px\"` или `undefined`, если элемент\n * отсутствует/скрыт/нулевой — например, в jsdom).\n */\nexport function useAddonMeasurement(hasPrefix: Ref<boolean>, hasSuffix: Ref<boolean>) {\n const prefixEl = ref<HTMLElement | null>(null)\n const suffixEl = ref<HTMLElement | null>(null)\n\n const measuredPrefixWidth = ref<string | undefined>(undefined)\n const measuredSuffixWidth = ref<string | undefined>(undefined)\n\n let ro: ResizeObserver | null = null\n let scheduled = false\n\n function readWidthPx(el: HTMLElement | null): string | undefined {\n if (!el) return undefined\n\n const width = Math.ceil(el.getBoundingClientRect().width || 0)\n // jsdom часто отдаёт 0; не перезатираем fallback minWidth в этом случае.\n if (width <= 0) return undefined\n\n return `${width}px`\n }\n\n function measure(): void {\n measuredPrefixWidth.value = hasPrefix.value ? readWidthPx(prefixEl.value) : undefined\n measuredSuffixWidth.value = hasSuffix.value ? readWidthPx(suffixEl.value) : undefined\n }\n\n function refreshObserver(): void {\n if (typeof ResizeObserver === 'undefined') return\n if (!ro) ro = new ResizeObserver(() => measure())\n\n ro.disconnect()\n if (prefixEl.value) ro.observe(prefixEl.value)\n if (suffixEl.value) ro.observe(suffixEl.value)\n }\n\n function scheduleMeasure(): void {\n if (scheduled) return\n scheduled = true\n\n void nextTick(() => {\n scheduled = false\n measure()\n refreshObserver()\n })\n }\n\n onMounted(() => scheduleMeasure())\n onUpdated(() => scheduleMeasure())\n onBeforeUnmount(() => ro?.disconnect())\n\n return {\n prefixEl,\n suffixEl,\n measuredPrefixWidth,\n measuredSuffixWidth,\n }\n}\n\n/** Складывает две CSS-длины: числовые `px` — арифметически, иначе через `calc()`. */\nexport function addLen(a: string, b: string): string {\n if (a === '0px') return b\n if (b === '0px') return a\n\n const apx = a.endsWith('px') ? Number(a.slice(0, -2)) : null\n const bpx = b.endsWith('px') ? Number(b.slice(0, -2)) : null\n\n if (apx !== null && Number.isFinite(apx) && bpx !== null && Number.isFinite(bpx)) {\n return `${apx + bpx}px`\n }\n\n return `calc(${a} + ${b})`\n}\n","import type { ComputedRef, Ref } from 'vue'\nimport { computed, useSlots } from 'vue'\n\nimport { addLen, useAddonMeasurement } from './useAddonMeasurement'\n\n/**\n * Аддоны контрола: слоты `prefix`/`suffix` и геометрия вокруг них.\n *\n * Слот здесь — вершина айсберга. Контракт аддона состоит из шести пропов ширины,\n * замера фактического размера через `ResizeObserver` и пересчёта внутренних\n * отступов поля, чтобы текст не заезжал под аддон. Эта математика была написана\n * дважды — в `GrInput` и `GrNumberInput`, — и раскатать её ещё на четыре\n * контрола копипастой значило бы завести шесть копий одного расчёта.\n *\n * Правило, которое держит композабл: **аддон резервирует место, а не заменяет\n * отступ**. Поле с аддоном и без него выглядит одинаково отбитым от края —\n * ширина аддона прибавляется к базовому паддингу ступени, а не вытесняет его.\n *\n * Карта ширины по умолчанию остаётся у компонента (`defaultMinWidth`): у\n * `GrInput` она в `rem`, у `GrNumberInput` — в тех же значениях, но в пикселях,\n * потому что оттуда же считаются ширины степперов. Сводить их к одной строке\n * значило бы менять то, что уезжает в `style`, ради косметики.\n */\nexport interface GrControlAddonProps {\n /** Минимальная ширина префикса. По умолчанию — ширина аддона для ступени размера. */\n prefixMinWidth?: string\n /** Максимальная ширина префикса: содержимое шире — обрезается. */\n prefixMaxWidth?: string\n suffixMinWidth?: string\n suffixMaxWidth?: string\n /**\n * Жёсткая ширина префикса (из `prefixMaxWidth` → `prefixMinWidth` → дефолт).\n * Нужна, когда аддоны соседних полей обязаны совпасть по ширине: без этого\n * каждое поле меряет свой аддон и колонка «плывёт».\n */\n prefixFixed?: boolean\n suffixFixed?: boolean\n}\n\nexport interface UseControlAddonsOptions {\n /** Ширина аддона по умолчанию для текущей ступени размера. */\n defaultMinWidth: () => string\n /** Базовый горизонтальный отступ поля из size-мапы. */\n paddingX: () => string\n /** Место, занятое собственными элементами контрола слева (степперы `GrNumberInput`). */\n leadingReserve?: () => string\n /** То же справа: кнопки очистки и показа пароля, спиннер, степперы. */\n trailingReserve?: () => string\n /** Прижать суффикс к правому краю — там, где аддон позиционируется абсолютно. */\n anchorSuffixRight?: boolean\n}\n\nexport interface ControlAddons {\n hasPrefix: ComputedRef<boolean>\n hasSuffix: ComputedRef<boolean>\n /** Вешаются на DOM-элементы аддонов: по ним идёт замер. */\n prefixEl: Ref<HTMLElement | null>\n suffixEl: Ref<HTMLElement | null>\n /** Занятое аддоном место (`0px`, если слота нет) — контролу нужно для своих кнопок. */\n prefixLen: ComputedRef<string>\n suffixLen: ComputedRef<string>\n prefixStyle: ComputedRef<Record<string, string | undefined>>\n suffixStyle: ComputedRef<Record<string, string | undefined>>\n /** `paddingLeft`/`paddingRight` поля с учётом аддонов и собственных кнопок. */\n fieldPadding: ComputedRef<Record<string, string | undefined>>\n}\n\nexport function useControlAddons(\n props: () => GrControlAddonProps,\n options: UseControlAddonsOptions,\n): ControlAddons {\n const slots = useSlots()\n\n const hasPrefix = computed(() => Boolean(slots.prefix))\n const hasSuffix = computed(() => Boolean(slots.suffix))\n\n const prefixMinWidth = computed(() => props().prefixMinWidth ?? options.defaultMinWidth())\n const suffixMinWidth = computed(() => props().suffixMinWidth ?? options.defaultMinWidth())\n\n // Жёсткая ширина для fixed-режима: max → min → дефолт.\n const prefixFixedWidth = computed(() => props().prefixMaxWidth ?? props().prefixMinWidth ?? options.defaultMinWidth())\n const suffixFixedWidth = computed(() => props().suffixMaxWidth ?? props().suffixMinWidth ?? options.defaultMinWidth())\n\n const { prefixEl, suffixEl, measuredPrefixWidth, measuredSuffixWidth } = useAddonMeasurement(hasPrefix, hasSuffix)\n\n const prefixLen = computed(() => {\n if (!hasPrefix.value) return '0px'\n return props().prefixFixed ? prefixFixedWidth.value : measuredPrefixWidth.value ?? prefixMinWidth.value\n })\n\n const suffixLen = computed(() => {\n if (!hasSuffix.value) return '0px'\n return props().suffixFixed ? suffixFixedWidth.value : measuredSuffixWidth.value ?? suffixMinWidth.value\n })\n\n const prefixStyle = computed<Record<string, string | undefined>>(() => {\n if (props().prefixFixed) {\n return {\n width: prefixFixedWidth.value,\n minWidth: prefixFixedWidth.value,\n maxWidth: prefixFixedWidth.value,\n }\n }\n\n return {\n minWidth: prefixMinWidth.value,\n maxWidth: props().prefixMaxWidth,\n }\n })\n\n const suffixStyle = computed<Record<string, string | undefined>>(() => {\n const anchor = options.anchorSuffixRight ? { right: '0px' } : {}\n\n if (props().suffixFixed) {\n return {\n ...anchor,\n width: suffixFixedWidth.value,\n minWidth: suffixFixedWidth.value,\n maxWidth: suffixFixedWidth.value,\n }\n }\n\n return {\n ...anchor,\n minWidth: suffixMinWidth.value,\n maxWidth: props().suffixMaxWidth,\n }\n })\n\n const fieldPadding = computed<Record<string, string | undefined>>(() => {\n const leading = options.leadingReserve?.() ?? '0px'\n const trailing = options.trailingReserve?.() ?? '0px'\n\n const left = addLen(prefixLen.value, leading)\n const right = addLen(suffixLen.value, trailing)\n\n return {\n // Отступ добавляется поверх зарезервированного места: поле с аддоном и без\n // него отбито от края одинаково.\n paddingLeft: hasPrefix.value || leading !== '0px' ? addLen(left, options.paddingX()) : undefined,\n paddingRight: hasSuffix.value || trailing !== '0px' ? addLen(right, options.paddingX()) : undefined,\n }\n })\n\n return {\n hasPrefix,\n hasSuffix,\n prefixEl,\n suffixEl,\n prefixLen,\n suffixLen,\n prefixStyle,\n suffixStyle,\n fieldPadding,\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useControlAddons-asUr10Pf.js","names":[],"sources":["../../src/composables/internal/useAddonMeasurement.ts","../../src/composables/internal/useControlAddons.ts"],"sourcesContent":["import { nextTick, onBeforeUnmount, onMounted, onUpdated, ref } from 'vue'\nimport type { Ref } from 'vue'\n\n/**\n * Измерение фактической ширины prefix/suffix add-on'ов через `ResizeObserver`.\n * Общая логика GrInput/GrNumberInput.\n *\n * Потребитель вешает возвращённые `prefixEl`/`suffixEl` на DOM-элементы аддонов\n * и передаёт реактивные `hasPrefix`/`hasSuffix` (обычно — наличие слотов).\n * Возвращает измеренные ширины (`\"<n>px\"` или `undefined`, если элемент\n * отсутствует/скрыт/нулевой — например, в jsdom).\n */\nexport function useAddonMeasurement(hasPrefix: Ref<boolean>, hasSuffix: Ref<boolean>) {\n const prefixEl = ref<HTMLElement | null>(null)\n const suffixEl = ref<HTMLElement | null>(null)\n\n const measuredPrefixWidth = ref<string | undefined>(undefined)\n const measuredSuffixWidth = ref<string | undefined>(undefined)\n\n let ro: ResizeObserver | null = null\n let scheduled = false\n\n function readWidthPx(el: HTMLElement | null): string | undefined {\n if (!el)\n return undefined\n\n const width = Math.ceil(el.getBoundingClientRect().width || 0)\n // jsdom часто отдаёт 0; не перезатираем fallback minWidth в этом случае.\n if (width <= 0)\n return undefined\n\n return `${width}px`\n }\n\n function measure(): void {\n measuredPrefixWidth.value = hasPrefix.value ? readWidthPx(prefixEl.value) : undefined\n measuredSuffixWidth.value = hasSuffix.value ? readWidthPx(suffixEl.value) : undefined\n }\n\n function refreshObserver(): void {\n if (typeof ResizeObserver === 'undefined')\n return\n if (!ro)\n ro = new ResizeObserver(() => measure())\n\n ro.disconnect()\n if (prefixEl.value)\n ro.observe(prefixEl.value)\n if (suffixEl.value)\n ro.observe(suffixEl.value)\n }\n\n function scheduleMeasure(): void {\n if (scheduled)\n return\n scheduled = true\n\n void nextTick(() => {\n scheduled = false\n measure()\n refreshObserver()\n })\n }\n\n onMounted(() => scheduleMeasure())\n onUpdated(() => scheduleMeasure())\n onBeforeUnmount(() => ro?.disconnect())\n\n return {\n prefixEl,\n suffixEl,\n measuredPrefixWidth,\n measuredSuffixWidth,\n }\n}\n\n/** Складывает две CSS-длины: числовые `px` — арифметически, иначе через `calc()`. */\nexport function addLen(a: string, b: string): string {\n if (a === '0px')\n return b\n if (b === '0px')\n return a\n\n const apx = a.endsWith('px') ? Number(a.slice(0, -2)) : null\n const bpx = b.endsWith('px') ? Number(b.slice(0, -2)) : null\n\n if (apx !== null && Number.isFinite(apx) && bpx !== null && Number.isFinite(bpx)) {\n return `${apx + bpx}px`\n }\n\n return `calc(${a} + ${b})`\n}\n","import type { ComputedRef, Ref } from 'vue'\nimport { computed, useSlots } from 'vue'\n\nimport { addLen, useAddonMeasurement } from './useAddonMeasurement'\n\n/**\n * Аддоны контрола: слоты `prefix`/`suffix` и геометрия вокруг них.\n *\n * Слот здесь — вершина айсберга. Контракт аддона состоит из шести пропов ширины,\n * замера фактического размера через `ResizeObserver` и пересчёта внутренних\n * отступов поля, чтобы текст не заезжал под аддон. Эта математика была написана\n * дважды — в `GrInput` и `GrNumberInput`, — и раскатать её ещё на четыре\n * контрола копипастой значило бы завести шесть копий одного расчёта.\n *\n * Правило, которое держит композабл: **аддон резервирует место, а не заменяет\n * отступ**. Поле с аддоном и без него выглядит одинаково отбитым от края —\n * ширина аддона прибавляется к базовому паддингу ступени, а не вытесняет его.\n *\n * Карта ширины по умолчанию остаётся у компонента (`defaultMinWidth`): у\n * `GrInput` она в `rem`, у `GrNumberInput` — в тех же значениях, но в пикселях,\n * потому что оттуда же считаются ширины степперов. Сводить их к одной строке\n * значило бы менять то, что уезжает в `style`, ради косметики.\n */\nexport interface GrControlAddonProps {\n /** Минимальная ширина префикса. По умолчанию — ширина аддона для ступени размера. */\n prefixMinWidth?: string\n /** Максимальная ширина префикса: содержимое шире — обрезается. */\n prefixMaxWidth?: string\n suffixMinWidth?: string\n suffixMaxWidth?: string\n /**\n * Жёсткая ширина префикса (из `prefixMaxWidth` → `prefixMinWidth` → дефолт).\n * Нужна, когда аддоны соседних полей обязаны совпасть по ширине: без этого\n * каждое поле меряет свой аддон и колонка «плывёт».\n */\n prefixFixed?: boolean\n suffixFixed?: boolean\n}\n\nexport interface UseControlAddonsOptions {\n /** Ширина аддона по умолчанию для текущей ступени размера. */\n defaultMinWidth: () => string\n /** Базовый горизонтальный отступ поля из size-мапы. */\n paddingX: () => string\n /** Место, занятое собственными элементами контрола слева (степперы `GrNumberInput`). */\n leadingReserve?: () => string\n /** То же справа: кнопки очистки и показа пароля, спиннер, степперы. */\n trailingReserve?: () => string\n /** Прижать суффикс к правому краю — там, где аддон позиционируется абсолютно. */\n anchorSuffixRight?: boolean\n}\n\nexport interface ControlAddons {\n hasPrefix: ComputedRef<boolean>\n hasSuffix: ComputedRef<boolean>\n /** Вешаются на DOM-элементы аддонов: по ним идёт замер. */\n prefixEl: Ref<HTMLElement | null>\n suffixEl: Ref<HTMLElement | null>\n /** Занятое аддоном место (`0px`, если слота нет) — контролу нужно для своих кнопок. */\n prefixLen: ComputedRef<string>\n suffixLen: ComputedRef<string>\n prefixStyle: ComputedRef<Record<string, string | undefined>>\n suffixStyle: ComputedRef<Record<string, string | undefined>>\n /** `paddingLeft`/`paddingRight` поля с учётом аддонов и собственных кнопок. */\n fieldPadding: ComputedRef<Record<string, string | undefined>>\n}\n\nexport function useControlAddons(\n props: () => GrControlAddonProps,\n options: UseControlAddonsOptions,\n): ControlAddons {\n const slots = useSlots()\n\n const hasPrefix = computed(() => Boolean(slots.prefix))\n const hasSuffix = computed(() => Boolean(slots.suffix))\n\n const prefixMinWidth = computed(() => props().prefixMinWidth ?? options.defaultMinWidth())\n const suffixMinWidth = computed(() => props().suffixMinWidth ?? options.defaultMinWidth())\n\n // Жёсткая ширина для fixed-режима: max → min → дефолт.\n const prefixFixedWidth = computed(() => props().prefixMaxWidth ?? props().prefixMinWidth ?? options.defaultMinWidth())\n const suffixFixedWidth = computed(() => props().suffixMaxWidth ?? props().suffixMinWidth ?? options.defaultMinWidth())\n\n const { prefixEl, suffixEl, measuredPrefixWidth, measuredSuffixWidth } = useAddonMeasurement(hasPrefix, hasSuffix)\n\n const prefixLen = computed(() => {\n if (!hasPrefix.value)\n return '0px'\n return props().prefixFixed ? prefixFixedWidth.value : measuredPrefixWidth.value ?? prefixMinWidth.value\n })\n\n const suffixLen = computed(() => {\n if (!hasSuffix.value)\n return '0px'\n return props().suffixFixed ? suffixFixedWidth.value : measuredSuffixWidth.value ?? suffixMinWidth.value\n })\n\n const prefixStyle = computed<Record<string, string | undefined>>(() => {\n if (props().prefixFixed) {\n return {\n width: prefixFixedWidth.value,\n minWidth: prefixFixedWidth.value,\n maxWidth: prefixFixedWidth.value,\n }\n }\n\n return {\n minWidth: prefixMinWidth.value,\n maxWidth: props().prefixMaxWidth,\n }\n })\n\n const suffixStyle = computed<Record<string, string | undefined>>(() => {\n const anchor = options.anchorSuffixRight ? { right: '0px' } : {}\n\n if (props().suffixFixed) {\n return {\n ...anchor,\n width: suffixFixedWidth.value,\n minWidth: suffixFixedWidth.value,\n maxWidth: suffixFixedWidth.value,\n }\n }\n\n return {\n ...anchor,\n minWidth: suffixMinWidth.value,\n maxWidth: props().suffixMaxWidth,\n }\n })\n\n const fieldPadding = computed<Record<string, string | undefined>>(() => {\n const leading = options.leadingReserve?.() ?? '0px'\n const trailing = options.trailingReserve?.() ?? '0px'\n\n const left = addLen(prefixLen.value, leading)\n const right = addLen(suffixLen.value, trailing)\n\n return {\n // Отступ добавляется поверх зарезервированного места: поле с аддоном и без\n // него отбито от края одинаково.\n paddingLeft: hasPrefix.value || leading !== '0px' ? addLen(left, options.paddingX()) : undefined,\n paddingRight: hasSuffix.value || trailing !== '0px' ? addLen(right, options.paddingX()) : undefined,\n }\n })\n\n return {\n hasPrefix,\n hasSuffix,\n prefixEl,\n suffixEl,\n prefixLen,\n suffixLen,\n prefixStyle,\n suffixStyle,\n fieldPadding,\n }\n}\n"],"mappings":";;;;;;;;;;;AAYA,SAAgB,oBAAoB,WAAyB,WAAyB;CACpF,MAAM,WAAW,IAAwB,IAAI;CAC7C,MAAM,WAAW,IAAwB,IAAI;CAE7C,MAAM,sBAAsB,IAAwB,KAAA,CAAS;CAC7D,MAAM,sBAAsB,IAAwB,KAAA,CAAS;CAE7D,IAAI,KAA4B;CAChC,IAAI,YAAY;CAEhB,SAAS,YAAY,IAA4C;EAC/D,IAAI,CAAC,IACH,OAAO,KAAA;EAET,MAAM,QAAQ,KAAK,KAAK,GAAG,sBAAsB,CAAC,CAAC,SAAS,CAAC;EAE7D,IAAI,SAAS,GACX,OAAO,KAAA;EAET,OAAO,GAAG,MAAM;CAClB;CAEA,SAAS,UAAgB;EACvB,oBAAoB,QAAQ,UAAU,QAAQ,YAAY,SAAS,KAAK,IAAI,KAAA;EAC5E,oBAAoB,QAAQ,UAAU,QAAQ,YAAY,SAAS,KAAK,IAAI,KAAA;CAC9E;CAEA,SAAS,kBAAwB;EAC/B,IAAI,OAAO,mBAAmB,aAC5B;EACF,IAAI,CAAC,IACH,KAAK,IAAI,qBAAqB,QAAQ,CAAC;EAEzC,GAAG,WAAW;EACd,IAAI,SAAS,OACX,GAAG,QAAQ,SAAS,KAAK;EAC3B,IAAI,SAAS,OACX,GAAG,QAAQ,SAAS,KAAK;CAC7B;CAEA,SAAS,kBAAwB;EAC/B,IAAI,WACF;EACF,YAAY;EAEZ,eAAoB;GAClB,YAAY;GACZ,QAAQ;GACR,gBAAgB;EAClB,CAAC;CACH;CAEA,gBAAgB,gBAAgB,CAAC;CACjC,gBAAgB,gBAAgB,CAAC;CACjC,sBAAsB,IAAI,WAAW,CAAC;CAEtC,OAAO;EACL;EACA;EACA;EACA;CACF;AACF;;AAGA,SAAgB,OAAO,GAAW,GAAmB;CACnD,IAAI,MAAM,OACR,OAAO;CACT,IAAI,MAAM,OACR,OAAO;CAET,MAAM,MAAM,EAAE,SAAS,IAAI,IAAI,OAAO,EAAE,MAAM,GAAG,EAAE,CAAC,IAAI;CACxD,MAAM,MAAM,EAAE,SAAS,IAAI,IAAI,OAAO,EAAE,MAAM,GAAG,EAAE,CAAC,IAAI;CAExD,IAAI,QAAQ,QAAQ,OAAO,SAAS,GAAG,KAAK,QAAQ,QAAQ,OAAO,SAAS,GAAG,GAC7E,OAAO,GAAG,MAAM,IAAI;CAGtB,OAAO,QAAQ,EAAE,KAAK,EAAE;AAC1B;;;ACxBA,SAAgB,iBACd,OACA,SACe;CACf,MAAM,QAAQ,SAAS;CAEvB,MAAM,YAAY,eAAe,QAAQ,MAAM,MAAM,CAAC;CACtD,MAAM,YAAY,eAAe,QAAQ,MAAM,MAAM,CAAC;CAEtD,MAAM,iBAAiB,eAAe,MAAM,CAAC,CAAC,kBAAkB,QAAQ,gBAAgB,CAAC;CACzF,MAAM,iBAAiB,eAAe,MAAM,CAAC,CAAC,kBAAkB,QAAQ,gBAAgB,CAAC;CAGzF,MAAM,mBAAmB,eAAe,MAAM,CAAC,CAAC,kBAAkB,MAAM,CAAC,CAAC,kBAAkB,QAAQ,gBAAgB,CAAC;CACrH,MAAM,mBAAmB,eAAe,MAAM,CAAC,CAAC,kBAAkB,MAAM,CAAC,CAAC,kBAAkB,QAAQ,gBAAgB,CAAC;CAErH,MAAM,EAAE,UAAU,UAAU,qBAAqB,wBAAwB,oBAAoB,WAAW,SAAS;CAEjH,MAAM,YAAY,eAAe;EAC/B,IAAI,CAAC,UAAU,OACb,OAAO;EACT,OAAO,MAAM,CAAC,CAAC,cAAc,iBAAiB,QAAQ,oBAAoB,SAAS,eAAe;CACpG,CAAC;CAED,MAAM,YAAY,eAAe;EAC/B,IAAI,CAAC,UAAU,OACb,OAAO;EACT,OAAO,MAAM,CAAC,CAAC,cAAc,iBAAiB,QAAQ,oBAAoB,SAAS,eAAe;CACpG,CAAC;CAmDD,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA,aAxDkB,eAAmD;GACrE,IAAI,MAAM,CAAC,CAAC,aACV,OAAO;IACL,OAAO,iBAAiB;IACxB,UAAU,iBAAiB;IAC3B,UAAU,iBAAiB;GAC7B;GAGF,OAAO;IACL,UAAU,eAAe;IACzB,UAAU,MAAM,CAAC,CAAC;GACpB;EACF,CA2CE;EACA,aA1CkB,eAAmD;GACrE,MAAM,SAAS,QAAQ,oBAAoB,EAAE,OAAO,MAAM,IAAI,CAAC;GAE/D,IAAI,MAAM,CAAC,CAAC,aACV,OAAO;IACL,GAAG;IACH,OAAO,iBAAiB;IACxB,UAAU,iBAAiB;IAC3B,UAAU,iBAAiB;GAC7B;GAGF,OAAO;IACL,GAAG;IACH,UAAU,eAAe;IACzB,UAAU,MAAM,CAAC,CAAC;GACpB;EACF,CAyBE;EACA,cAxBmB,eAAmD;GACtE,MAAM,UAAU,QAAQ,iBAAiB,KAAK;GAC9C,MAAM,WAAW,QAAQ,kBAAkB,KAAK;GAEhD,MAAM,OAAO,OAAO,UAAU,OAAO,OAAO;GAC5C,MAAM,QAAQ,OAAO,UAAU,OAAO,QAAQ;GAE9C,OAAO;IAGL,aAAa,UAAU,SAAS,YAAY,QAAQ,OAAO,MAAM,QAAQ,SAAS,CAAC,IAAI,KAAA;IACvF,cAAc,UAAU,SAAS,aAAa,QAAQ,OAAO,OAAO,QAAQ,SAAS,CAAC,IAAI,KAAA;GAC5F;EACF,CAWE;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"useControlledOpen-DV3SqLTP.js","names":[],"sources":["../../src/composables/internal/useControlledOpen.ts"],"sourcesContent":["import type { ComputedRef } from 'vue'\nimport { computed, ref } from 'vue'\n\n/**\n * Контролируемое состояние открытия оверлея — единый контракт `v-model:open`\n * панельных компонентов (Popover, Select, TreeSelect, Autocomplete, Dropdown).\n *\n * Uncontrolled-состояние; в controlled-режиме перекрывается пропом `open`.\n * Без пропа `v-model` был бы обязателен, а поповер без модели — самый частый\n * случай использования. Гарды компонента (disabled/readonly) остаются на его\n * стороне — примитив ведёт только состояние и эмит.\n */\nexport function useControlledOpen(\n controlled: () => boolean | undefined,\n onChange: (value: boolean) => void,\n): { open: ComputedRef<boolean>, setOpen: (next: boolean) => void } {\n const internalOpen = ref(false)\n const isControlled = computed(() => controlled() !== undefined)\n const open = computed(() => controlled() ?? internalOpen.value)\n\n function setOpen(next: boolean): void {\n // Сравнение — ДО мутации: в uncontrolled-режиме запись в `internalOpen`\n // немедленно меняет `open`, и проверка после неё всегда была бы ложной,\n // то есть `onChange` не вызывался бы никогда.\n if (next === open.value) return\n\n if (!isControlled.value) internalOpen.value = next\n\n onChange(next)\n }\n\n return { open, setOpen }\n}\n"],"mappings":";;;;;;;;;;;AAYA,SAAgB,kBACd,YACA,UACkE;CAClE,MAAM,eAAe,IAAI,KAAK;CAC9B,MAAM,eAAe,eAAe,WAAW,MAAM,KAAA,CAAS;CAC9D,MAAM,OAAO,eAAe,WAAW,KAAK,aAAa,KAAK;CAE9D,SAAS,QAAQ,MAAqB;EAIpC,IAAI,SAAS,KAAK,OAAO;EAEzB,IAAI,CAAC,aAAa,OAAO,aAAa,QAAQ;EAE9C,SAAS,IAAI;CACf;CAEA,OAAO;EAAE;EAAM;CAAQ;AACzB"}
1
+ {"version":3,"file":"useControlledOpen-DV3SqLTP.js","names":[],"sources":["../../src/composables/internal/useControlledOpen.ts"],"sourcesContent":["import type { ComputedRef } from 'vue'\nimport { computed, ref } from 'vue'\n\n/**\n * Контролируемое состояние открытия оверлея — единый контракт `v-model:open`\n * панельных компонентов (Popover, Select, TreeSelect, Autocomplete, Dropdown).\n *\n * Uncontrolled-состояние; в controlled-режиме перекрывается пропом `open`.\n * Без пропа `v-model` был бы обязателен, а поповер без модели — самый частый\n * случай использования. Гарды компонента (disabled/readonly) остаются на его\n * стороне — примитив ведёт только состояние и эмит.\n */\nexport function useControlledOpen(\n controlled: () => boolean | undefined,\n onChange: (value: boolean) => void,\n): { open: ComputedRef<boolean>, setOpen: (next: boolean) => void } {\n const internalOpen = ref(false)\n const isControlled = computed(() => controlled() !== undefined)\n const open = computed(() => controlled() ?? internalOpen.value)\n\n function setOpen(next: boolean): void {\n // Сравнение — ДО мутации: в uncontrolled-режиме запись в `internalOpen`\n // немедленно меняет `open`, и проверка после неё всегда была бы ложной,\n // то есть `onChange` не вызывался бы никогда.\n if (next === open.value)\n return\n\n if (!isControlled.value)\n internalOpen.value = next\n\n onChange(next)\n }\n\n return { open, setOpen }\n}\n"],"mappings":";;;;;;;;;;;AAYA,SAAgB,kBACd,YACA,UACkE;CAClE,MAAM,eAAe,IAAI,KAAK;CAC9B,MAAM,eAAe,eAAe,WAAW,MAAM,KAAA,CAAS;CAC9D,MAAM,OAAO,eAAe,WAAW,KAAK,aAAa,KAAK;CAE9D,SAAS,QAAQ,MAAqB;EAIpC,IAAI,SAAS,KAAK,OAChB;EAEF,IAAI,CAAC,aAAa,OAChB,aAAa,QAAQ;EAEvB,SAAS,IAAI;CACf;CAEA,OAAO;EAAE;EAAM;CAAQ;AACzB"}
@@ -1 +1 @@
1
- {"version":3,"file":"useDragSort-0l47h4yb.js","names":[],"sources":["../../src/composables/internal/dragSortGeometry.ts","../../src/composables/useDragSort.ts"],"sourcesContent":["/**\n * Геометрия перестановки: чистая арифметика без DOM и без реактивности.\n *\n * Вынесена отдельно, потому что ошибка здесь проявляется не исключением, а\n * «элемент встал не туда» — поймать это можно только тестом на числах.\n * Композабл `useDragSort` сводит `DOMRect` к отрезкам вдоль оси и зовёт эти\n * функции; какой смысл у попадания, решает уже компонент.\n */\n\n/** Отрезок, занимаемый элементом вдоль оси переноса. */\nexport interface DragSortSpan {\n start: number\n end: number\n}\n\nexport interface DragSortHitResult {\n /** Индекс элемента в переданном наборе. */\n index: number\n /** Доля вдоль элемента: 0 — его начало, 1 — конец. */\n fraction: number\n}\n\n/**\n * Какой элемент под указателем и в каком месте его.\n *\n * Промах не отбрасывается: между строками есть зазоры, а курсор уезжает за\n * край списка — и в обоих случаях перенос обязан продолжать показывать\n * ближайшую цель, иначе индикатор мигает на каждом пикселе между строками.\n *\n * Вырожденный отрезок (нулевая высота строки) даёт `fraction: 0` — цель\n * остаётся детерминированной, а не зависящей от направления подхода.\n */\nexport function hitTest(spans: readonly DragSortSpan[], position: number): DragSortHitResult | null {\n if (spans.length === 0) return null\n\n let nearestIndex = 0\n let nearestDistance = Number.POSITIVE_INFINITY\n\n for (let index = 0; index < spans.length; index += 1) {\n const span = spans[index]\n\n if (position >= span.start && position <= span.end)\n return { index, fraction: fractionIn(span, position) }\n\n const distance = position < span.start ? span.start - position : position - span.end\n if (distance < nearestDistance) {\n nearestDistance = distance\n nearestIndex = index\n }\n }\n\n const nearest = spans[nearestIndex]\n\n return { index: nearestIndex, fraction: fractionIn(nearest, position) }\n}\n\nfunction fractionIn(span: DragSortSpan, position: number): number {\n const size = span.end - span.start\n if (size <= 0) return 0\n\n return clamp((position - span.start) / size, 0, 1)\n}\n\n/**\n * Куда встанет элемент, если отпустить сейчас.\n *\n * Верхняя половина строки — «до неё», нижняя — «после». Индекс возвращается\n * **в терминах итогового массива**, то есть уже с поправкой на то, что\n * перетаскиваемый элемент из него временно вынут: без поправки перенос вниз\n * на одну позицию не двигал бы ничего.\n */\nexport function insertionIndex(hit: DragSortHitResult, from: number, count: number): number {\n if (count <= 0) return 0\n\n const base = hit.fraction < 0.5 ? hit.index : hit.index + 1\n const adjusted = from < base ? base - 1 : base\n\n return clamp(adjusted, 0, count - 1)\n}\n\nexport interface AutoScrollOptions {\n /** Ширина чувствительной полосы у каждого края, px. По умолчанию 32. */\n zone?: number\n /** Скорость на самом краю, px за кадр. По умолчанию 12. */\n speed?: number\n}\n\n/**\n * На сколько прокрутить контейнер за кадр: отрицательное — к началу.\n *\n * Скорость растёт линейно по мере приближения к краю, а не включается\n * ступенькой: ступенька на длинном списке ощущается как рывок и промахивается\n * мимо цели.\n */\nexport function autoScrollDelta(viewport: DragSortSpan, position: number, options: AutoScrollOptions = {}): number {\n const zone = options.zone ?? 32\n const speed = options.speed ?? 12\n\n if (zone <= 0 || speed <= 0) return 0\n // Вьюпорт короче двух зон: полосы наложились бы и тянули список в обе стороны.\n if (viewport.end - viewport.start <= zone * 2) return 0\n\n if (position < viewport.start || position > viewport.end) return 0\n\n const fromStart = position - viewport.start\n if (fromStart < zone)\n return -Math.ceil(((zone - fromStart) / zone) * speed)\n\n const fromEnd = viewport.end - position\n if (fromEnd < zone)\n return Math.ceil(((zone - fromEnd) / zone) * speed)\n\n return 0\n}\n\n/**\n * Перестановка без мутации входа: массив потребителя остаётся нетронутым,\n * наружу уходит новый. `v-model` с мутацией исходного набора ломает и\n * сравнение «было — стало», и историю в сторе потребителя.\n */\nexport function moveItem<T>(items: readonly T[], from: number, to: number): T[] {\n const next = items.slice()\n if (from === to || from < 0 || from >= items.length) return next\n\n const [moved] = next.splice(from, 1)\n next.splice(clamp(to, 0, next.length), 0, moved)\n\n return next\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value))\n}\n","import type { Ref } from 'vue'\nimport { computed, onScopeDispose, shallowRef } from 'vue'\n\nimport type { DragSortSpan } from './internal/dragSortGeometry'\nimport { autoScrollDelta, hitTest } from './internal/dragSortGeometry'\nimport { useDragGesture } from './useDragGesture'\n\n/**\n * Модель перестановки: перенос элемента указателем и с клавиатуры.\n *\n * Композабл владеет **механикой** — сессией жеста поверх `useDragGesture`,\n * порогом начала, попаданием в элемент, автопрокруткой у краёв и состоянием\n * переноса. **Смысл** остаётся у компонента: что считать целью (индекс вставки\n * у списка, «до / внутрь / после» у дерева), какими клавишами это делается и\n * что сказать в живой регион.\n *\n * Граница проведена так не из вкуса: у списка цель переноса — число, у дерева —\n * пара «узел и зона», а `Space` в дереве уже занят чекбоксом. Общая у них\n * механика, а не семантика; обобщать семантику значило бы завести опцию на\n * каждое расхождение.\n *\n * Данные композабл не меняет: он сообщает «источник и цель», перестановку\n * делает потребитель (`moveItem` из `internal/dragSortGeometry` — если она\n * обычная).\n *\n * ```ts\n * const sort = useDragSort<string, number>({\n * items: () => keys.value,\n * elementFor: key => rowEls.get(key),\n * scroller: () => scrollerEl.value,\n * resolveTarget: (hit, source) => insertionIndex(hit, indexOf(source), keys.value.length),\n * onDrop: (source, to) => emit('move', indexOf(source), to),\n * })\n * ```\n */\n\nexport type DragSortOrientation = 'vertical' | 'horizontal'\n\n/** Чем ведётся перенос: указателем или с клавиатуры. */\nexport type DragSortMode = 'pointer' | 'keyboard'\n\nexport interface DragSortHit<TKey> {\n key: TKey\n index: number\n /** Доля вдоль элемента: 0 — начало, 1 — конец. Зоны нарезает компонент. */\n fraction: number\n}\n\nexport interface UseDragSortOptions<TKey, TTarget> {\n items: () => readonly TKey[]\n elementFor: (key: TKey) => HTMLElement | null | undefined\n /** По умолчанию `vertical`. */\n orientation?: () => DragSortOrientation\n /** Скроллер для автопрокрутки у краёв. Не задан — автопрокрутки нет. */\n scroller?: () => HTMLElement | null | undefined\n disabled?: () => boolean\n canDrag?: (key: TKey) => boolean\n /** Сдвиг, после которого нажатие становится переносом. По умолчанию 4px. */\n threshold?: () => number\n /**\n * Попадание указателя → цель переноса. `null` — сюда положить нельзя.\n *\n * Третьим аргументом приходит прежняя цель: цели сравниваются по ссылке, и\n * объектной цели (как «узел плюс зона» у дерева) стоит вернуть **ту же**\n * ссылку, пока смысл не изменился, — иначе подсветка и объявления будут\n * дёргаться на каждом движении указателя.\n */\n resolveTarget: (hit: DragSortHit<TKey>, source: TKey, previous: TTarget | null) => TTarget | null\n /** Перенос состоялся. */\n onDrop: (source: TKey, target: TTarget) => void\n /** Состояние изменилось: подсветка строк и объявления — на этом. */\n onUpdate?: (source: TKey | null, target: TTarget | null, mode: DragSortMode | null) => void\n}\n\nexport interface UseDragSortReturn<TKey, TTarget> {\n source: Ref<TKey | null>\n target: Ref<TTarget | null>\n mode: Ref<DragSortMode | null>\n isActive: Ref<boolean>\n /** Вешается на `@pointerdown` ручки или строки. */\n startFrom: (key: TKey) => (event: PointerEvent) => void\n /** Клавиатурный перенос: взять элемент. */\n grab: (key: TKey) => void\n /** Клавиатурный перенос: назначить цель (двигать ведёт компонент). */\n setTarget: (target: TTarget | null) => void\n /** Положить: если цель есть — состоится `onDrop`. */\n drop: () => void\n /** Отменить: ничего не переносится. */\n cancel: () => void\n}\n\nexport function useDragSort<TKey, TTarget>(\n options: UseDragSortOptions<TKey, TTarget>,\n): UseDragSortReturn<TKey, TTarget> {\n const source = shallowRef<TKey | null>(null)\n const target = shallowRef<TTarget | null>(null)\n const mode = shallowRef<DragSortMode | null>(null)\n const isActive = computed(() => mode.value !== null)\n\n // Нажатая, но ещё не потянутая строка: между `pointerdown` и порогом это\n // остаётся обычным кликом, и объявлять перенос рано.\n let pendingKey: TKey | null = null\n let originX = 0\n let originY = 0\n let lastPoint: { x: number, y: number } | null = null\n let scrollFrame: number | null = null\n\n const gesture = useDragGesture({\n disabled: () => options.disabled?.() ?? false,\n onStart: (event) => {\n if (pendingKey === null) return false\n if (options.canDrag?.(pendingKey) === false) return false\n\n originX = event.clientX\n originY = event.clientY\n\n return true\n },\n onMove: (event) => {\n if (mode.value === null) {\n const moved = Math.hypot(event.clientX - originX, event.clientY - originY)\n if (moved < (options.threshold?.() ?? 4)) return\n\n source.value = pendingKey\n mode.value = 'pointer'\n // Esc отменяет перенос, но `pointercancel` браузер при этом не шлёт —\n // клавишу приходится слушать самим, в capture, до слоёв приложения.\n window.addEventListener('keydown', onWindowKeydown, true)\n startScrolling()\n notify()\n }\n\n // Против выделения текста под курсором во время переноса.\n event.preventDefault()\n\n lastPoint = { x: event.clientX, y: event.clientY }\n updateTarget(lastPoint)\n },\n onEnd: () => {\n if (mode.value === 'pointer') commit()\n else reset()\n },\n onCancel: reset,\n })\n\n function isVertical(): boolean {\n return (options.orientation?.() ?? 'vertical') === 'vertical'\n }\n\n function spanOf(rect: DOMRect): DragSortSpan {\n return isVertical() ? { start: rect.top, end: rect.bottom } : { start: rect.left, end: rect.right }\n }\n\n function notify(): void {\n options.onUpdate?.(source.value, target.value, mode.value)\n }\n\n function updateTarget(point: { x: number, y: number }): void {\n const current = source.value\n if (current === null) return\n\n // Отрисованы не все: список может быть под окном отсечения, ветка дерева —\n // свёрнута. Попадание считается по тому, что на экране есть.\n const rendered: { key: TKey, index: number, span: DragSortSpan }[] = []\n\n options.items().forEach((key, index) => {\n const el = options.elementFor(key)\n if (el) rendered.push({ key, index, span: spanOf(el.getBoundingClientRect()) })\n })\n\n const hit = hitTest(rendered.map(entry => entry.span), isVertical() ? point.y : point.x)\n const entry = hit === null ? null : rendered[hit.index]\n const next = entry === null || hit === null\n ? null\n : options.resolveTarget({ key: entry.key, index: entry.index, fraction: hit.fraction }, current, target.value)\n\n if (next === target.value) return\n\n target.value = next\n notify()\n }\n\n /**\n * Автопрокрутка живёт в своём кадре, а не в обработчике движения: у края\n * указатель обычно стоит неподвижно, событий больше нет — а список должен\n * продолжать ехать.\n */\n function tickScroll(): void {\n scrollFrame = null\n\n const scroller = options.scroller?.()\n const point = lastPoint\n if (!scroller || !point || mode.value !== 'pointer') return\n\n const delta = autoScrollDelta(spanOf(scroller.getBoundingClientRect()), isVertical() ? point.y : point.x)\n\n if (delta !== 0) {\n if (isVertical()) scroller.scrollTop += delta\n else scroller.scrollLeft += delta\n\n updateTarget(point)\n }\n\n scrollFrame = requestAnimationFrame(tickScroll)\n }\n\n function startScrolling(): void {\n if (scrollFrame !== null || !options.scroller?.()) return\n\n scrollFrame = requestAnimationFrame(tickScroll)\n }\n\n function stopScrolling(): void {\n if (scrollFrame === null) return\n\n cancelAnimationFrame(scrollFrame)\n scrollFrame = null\n }\n\n function onWindowKeydown(event: KeyboardEvent): void {\n if (event.key !== 'Escape') return\n\n event.preventDefault()\n gesture.stop(false)\n }\n\n function reset(): void {\n stopScrolling()\n window.removeEventListener('keydown', onWindowKeydown, true)\n\n pendingKey = null\n lastPoint = null\n source.value = null\n target.value = null\n mode.value = null\n notify()\n }\n\n function commit(): void {\n const from = source.value\n const to = target.value\n\n if (from !== null && to !== null) options.onDrop(from, to)\n\n reset()\n }\n\n function startFrom(key: TKey) {\n return (event: PointerEvent): void => {\n // Клавиатурный перенос идёт своим чередом: указатель его не перебивает.\n if (mode.value === 'keyboard') return\n\n pendingKey = key\n gesture.start(event)\n }\n }\n\n function grab(key: TKey): void {\n if (options.disabled?.() || options.canDrag?.(key) === false) return\n if (mode.value !== null) return\n\n source.value = key\n target.value = null\n mode.value = 'keyboard'\n notify()\n }\n\n function setTarget(next: TTarget | null): void {\n if (mode.value !== 'keyboard') return\n\n target.value = next\n notify()\n }\n\n function drop(): void {\n if (mode.value !== 'keyboard') return\n\n commit()\n }\n\n function cancel(): void {\n if (mode.value === 'pointer') {\n gesture.stop(false)\n return\n }\n\n if (mode.value === 'keyboard') reset()\n }\n\n onScopeDispose(() => {\n stopScrolling()\n window.removeEventListener('keydown', onWindowKeydown, true)\n })\n\n return { source, target, mode, isActive, startFrom, grab, setTarget, drop, cancel }\n}\n"],"mappings":";;;;;;;;;;;;;AAgCA,SAAgB,QAAQ,OAAgC,UAA4C;CAClG,IAAI,MAAM,WAAW,GAAG,OAAO;CAE/B,IAAI,eAAe;CACnB,IAAI,kBAAkB,OAAO;CAE7B,KAAK,IAAI,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SAAS,GAAG;EACpD,MAAM,OAAO,MAAM;EAEnB,IAAI,YAAY,KAAK,SAAS,YAAY,KAAK,KAC7C,OAAO;GAAE;GAAO,UAAU,WAAW,MAAM,QAAQ;EAAE;EAEvD,MAAM,WAAW,WAAW,KAAK,QAAQ,KAAK,QAAQ,WAAW,WAAW,KAAK;EACjF,IAAI,WAAW,iBAAiB;GAC9B,kBAAkB;GAClB,eAAe;EACjB;CACF;CAEA,MAAM,UAAU,MAAM;CAEtB,OAAO;EAAE,OAAO;EAAc,UAAU,WAAW,SAAS,QAAQ;CAAE;AACxE;AAEA,SAAS,WAAW,MAAoB,UAA0B;CAChE,MAAM,OAAO,KAAK,MAAM,KAAK;CAC7B,IAAI,QAAQ,GAAG,OAAO;CAEtB,OAAO,OAAO,WAAW,KAAK,SAAS,MAAM,GAAG,CAAC;AACnD;;;;;;;;;AAUA,SAAgB,eAAe,KAAwB,MAAc,OAAuB;CAC1F,IAAI,SAAS,GAAG,OAAO;CAEvB,MAAM,OAAO,IAAI,WAAW,KAAM,IAAI,QAAQ,IAAI,QAAQ;CAG1D,OAAO,MAFU,OAAO,OAAO,OAAO,IAAI,MAEnB,GAAG,QAAQ,CAAC;AACrC;;;;;;;;AAgBA,SAAgB,gBAAgB,UAAwB,UAAkB,UAA6B,CAAC,GAAW;CACjH,MAAM,OAAO,QAAQ,QAAQ;CAC7B,MAAM,QAAQ,QAAQ,SAAS;CAE/B,IAAI,QAAQ,KAAK,SAAS,GAAG,OAAO;CAEpC,IAAI,SAAS,MAAM,SAAS,SAAS,OAAO,GAAG,OAAO;CAEtD,IAAI,WAAW,SAAS,SAAS,WAAW,SAAS,KAAK,OAAO;CAEjE,MAAM,YAAY,WAAW,SAAS;CACtC,IAAI,YAAY,MACd,OAAO,CAAC,KAAK,MAAO,OAAO,aAAa,OAAQ,KAAK;CAEvD,MAAM,UAAU,SAAS,MAAM;CAC/B,IAAI,UAAU,MACZ,OAAO,KAAK,MAAO,OAAO,WAAW,OAAQ,KAAK;CAEpD,OAAO;AACT;;;;;;AAOA,SAAgB,SAAY,OAAqB,MAAc,IAAiB;CAC9E,MAAM,OAAO,MAAM,MAAM;CACzB,IAAI,SAAS,MAAM,OAAO,KAAK,QAAQ,MAAM,QAAQ,OAAO;CAE5D,MAAM,CAAC,SAAS,KAAK,OAAO,MAAM,CAAC;CACnC,KAAK,OAAO,MAAM,IAAI,GAAG,KAAK,MAAM,GAAG,GAAG,KAAK;CAE/C,OAAO;AACT;AAEA,SAAS,MAAM,OAAe,KAAa,KAAqB;CAC9D,OAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,CAAC;AAC3C;;;ACzCA,SAAgB,YACd,SACkC;CAClC,MAAM,SAAS,WAAwB,IAAI;CAC3C,MAAM,SAAS,WAA2B,IAAI;CAC9C,MAAM,OAAO,WAAgC,IAAI;CACjD,MAAM,WAAW,eAAe,KAAK,UAAU,IAAI;CAInD,IAAI,aAA0B;CAC9B,IAAI,UAAU;CACd,IAAI,UAAU;CACd,IAAI,YAA6C;CACjD,IAAI,cAA6B;CAEjC,MAAM,UAAU,eAAe;EAC7B,gBAAgB,QAAQ,WAAW,KAAK;EACxC,UAAU,UAAU;GAClB,IAAI,eAAe,MAAM,OAAO;GAChC,IAAI,QAAQ,UAAU,UAAU,MAAM,OAAO,OAAO;GAEpD,UAAU,MAAM;GAChB,UAAU,MAAM;GAEhB,OAAO;EACT;EACA,SAAS,UAAU;GACjB,IAAI,KAAK,UAAU,MAAM;IAEvB,IADc,KAAK,MAAM,MAAM,UAAU,SAAS,MAAM,UAAU,OAC9D,KAAS,QAAQ,YAAY,KAAK,IAAI;IAE1C,OAAO,QAAQ;IACf,KAAK,QAAQ;IAGb,OAAO,iBAAiB,WAAW,iBAAiB,IAAI;IACxD,eAAe;IACf,OAAO;GACT;GAGA,MAAM,eAAe;GAErB,YAAY;IAAE,GAAG,MAAM;IAAS,GAAG,MAAM;GAAQ;GACjD,aAAa,SAAS;EACxB;EACA,aAAa;GACX,IAAI,KAAK,UAAU,WAAW,OAAO;QAChC,MAAM;EACb;EACA,UAAU;CACZ,CAAC;CAED,SAAS,aAAsB;EAC7B,QAAQ,QAAQ,cAAc,KAAK,gBAAgB;CACrD;CAEA,SAAS,OAAO,MAA6B;EAC3C,OAAO,WAAW,IAAI;GAAE,OAAO,KAAK;GAAK,KAAK,KAAK;EAAO,IAAI;GAAE,OAAO,KAAK;GAAM,KAAK,KAAK;EAAM;CACpG;CAEA,SAAS,SAAe;EACtB,QAAQ,WAAW,OAAO,OAAO,OAAO,OAAO,KAAK,KAAK;CAC3D;CAEA,SAAS,aAAa,OAAuC;EAC3D,MAAM,UAAU,OAAO;EACvB,IAAI,YAAY,MAAM;EAItB,MAAM,WAA+D,CAAC;EAEtE,QAAQ,MAAM,CAAC,CAAC,SAAS,KAAK,UAAU;GACtC,MAAM,KAAK,QAAQ,WAAW,GAAG;GACjC,IAAI,IAAI,SAAS,KAAK;IAAE;IAAK;IAAO,MAAM,OAAO,GAAG,sBAAsB,CAAC;GAAE,CAAC;EAChF,CAAC;EAED,MAAM,MAAM,QAAQ,SAAS,KAAI,UAAS,MAAM,IAAI,GAAG,WAAW,IAAI,MAAM,IAAI,MAAM,CAAC;EACvF,MAAM,QAAQ,QAAQ,OAAO,OAAO,SAAS,IAAI;EACjD,MAAM,OAAO,UAAU,QAAQ,QAAQ,OACnC,OACA,QAAQ,cAAc;GAAE,KAAK,MAAM;GAAK,OAAO,MAAM;GAAO,UAAU,IAAI;EAAS,GAAG,SAAS,OAAO,KAAK;EAE/G,IAAI,SAAS,OAAO,OAAO;EAE3B,OAAO,QAAQ;EACf,OAAO;CACT;;;;;;CAOA,SAAS,aAAmB;EAC1B,cAAc;EAEd,MAAM,WAAW,QAAQ,WAAW;EACpC,MAAM,QAAQ;EACd,IAAI,CAAC,YAAY,CAAC,SAAS,KAAK,UAAU,WAAW;EAErD,MAAM,QAAQ,gBAAgB,OAAO,SAAS,sBAAsB,CAAC,GAAG,WAAW,IAAI,MAAM,IAAI,MAAM,CAAC;EAExG,IAAI,UAAU,GAAG;GACf,IAAI,WAAW,GAAG,SAAS,aAAa;QACnC,SAAS,cAAc;GAE5B,aAAa,KAAK;EACpB;EAEA,cAAc,sBAAsB,UAAU;CAChD;CAEA,SAAS,iBAAuB;EAC9B,IAAI,gBAAgB,QAAQ,CAAC,QAAQ,WAAW,GAAG;EAEnD,cAAc,sBAAsB,UAAU;CAChD;CAEA,SAAS,gBAAsB;EAC7B,IAAI,gBAAgB,MAAM;EAE1B,qBAAqB,WAAW;EAChC,cAAc;CAChB;CAEA,SAAS,gBAAgB,OAA4B;EACnD,IAAI,MAAM,QAAQ,UAAU;EAE5B,MAAM,eAAe;EACrB,QAAQ,KAAK,KAAK;CACpB;CAEA,SAAS,QAAc;EACrB,cAAc;EACd,OAAO,oBAAoB,WAAW,iBAAiB,IAAI;EAE3D,aAAa;EACb,YAAY;EACZ,OAAO,QAAQ;EACf,OAAO,QAAQ;EACf,KAAK,QAAQ;EACb,OAAO;CACT;CAEA,SAAS,SAAe;EACtB,MAAM,OAAO,OAAO;EACpB,MAAM,KAAK,OAAO;EAElB,IAAI,SAAS,QAAQ,OAAO,MAAM,QAAQ,OAAO,MAAM,EAAE;EAEzD,MAAM;CACR;CAEA,SAAS,UAAU,KAAW;EAC5B,QAAQ,UAA8B;GAEpC,IAAI,KAAK,UAAU,YAAY;GAE/B,aAAa;GACb,QAAQ,MAAM,KAAK;EACrB;CACF;CAEA,SAAS,KAAK,KAAiB;EAC7B,IAAI,QAAQ,WAAW,KAAK,QAAQ,UAAU,GAAG,MAAM,OAAO;EAC9D,IAAI,KAAK,UAAU,MAAM;EAEzB,OAAO,QAAQ;EACf,OAAO,QAAQ;EACf,KAAK,QAAQ;EACb,OAAO;CACT;CAEA,SAAS,UAAU,MAA4B;EAC7C,IAAI,KAAK,UAAU,YAAY;EAE/B,OAAO,QAAQ;EACf,OAAO;CACT;CAEA,SAAS,OAAa;EACpB,IAAI,KAAK,UAAU,YAAY;EAE/B,OAAO;CACT;CAEA,SAAS,SAAe;EACtB,IAAI,KAAK,UAAU,WAAW;GAC5B,QAAQ,KAAK,KAAK;GAClB;EACF;EAEA,IAAI,KAAK,UAAU,YAAY,MAAM;CACvC;CAEA,qBAAqB;EACnB,cAAc;EACd,OAAO,oBAAoB,WAAW,iBAAiB,IAAI;CAC7D,CAAC;CAED,OAAO;EAAE;EAAQ;EAAQ;EAAM;EAAU;EAAW;EAAM;EAAW;EAAM;CAAO;AACpF"}
1
+ {"version":3,"file":"useDragSort-0l47h4yb.js","names":[],"sources":["../../src/composables/internal/dragSortGeometry.ts","../../src/composables/useDragSort.ts"],"sourcesContent":["/**\n * Геометрия перестановки: чистая арифметика без DOM и без реактивности.\n *\n * Вынесена отдельно, потому что ошибка здесь проявляется не исключением, а\n * «элемент встал не туда» — поймать это можно только тестом на числах.\n * Композабл `useDragSort` сводит `DOMRect` к отрезкам вдоль оси и зовёт эти\n * функции; какой смысл у попадания, решает уже компонент.\n */\n\n/** Отрезок, занимаемый элементом вдоль оси переноса. */\nexport interface DragSortSpan {\n start: number\n end: number\n}\n\nexport interface DragSortHitResult {\n /** Индекс элемента в переданном наборе. */\n index: number\n /** Доля вдоль элемента: 0 — его начало, 1 — конец. */\n fraction: number\n}\n\n/**\n * Какой элемент под указателем и в каком месте его.\n *\n * Промах не отбрасывается: между строками есть зазоры, а курсор уезжает за\n * край списка — и в обоих случаях перенос обязан продолжать показывать\n * ближайшую цель, иначе индикатор мигает на каждом пикселе между строками.\n *\n * Вырожденный отрезок (нулевая высота строки) даёт `fraction: 0` — цель\n * остаётся детерминированной, а не зависящей от направления подхода.\n */\nexport function hitTest(spans: readonly DragSortSpan[], position: number): DragSortHitResult | null {\n if (spans.length === 0)\n return null\n\n let nearestIndex = 0\n let nearestDistance = Number.POSITIVE_INFINITY\n\n for (let index = 0; index < spans.length; index += 1) {\n const span = spans[index]\n\n if (position >= span.start && position <= span.end)\n return { index, fraction: fractionIn(span, position) }\n\n const distance = position < span.start ? span.start - position : position - span.end\n if (distance < nearestDistance) {\n nearestDistance = distance\n nearestIndex = index\n }\n }\n\n const nearest = spans[nearestIndex]\n\n return { index: nearestIndex, fraction: fractionIn(nearest, position) }\n}\n\nfunction fractionIn(span: DragSortSpan, position: number): number {\n const size = span.end - span.start\n if (size <= 0)\n return 0\n\n return clamp((position - span.start) / size, 0, 1)\n}\n\n/**\n * Куда встанет элемент, если отпустить сейчас.\n *\n * Верхняя половина строки — «до неё», нижняя — «после». Индекс возвращается\n * **в терминах итогового массива**, то есть уже с поправкой на то, что\n * перетаскиваемый элемент из него временно вынут: без поправки перенос вниз\n * на одну позицию не двигал бы ничего.\n */\nexport function insertionIndex(hit: DragSortHitResult, from: number, count: number): number {\n if (count <= 0)\n return 0\n\n const base = hit.fraction < 0.5 ? hit.index : hit.index + 1\n const adjusted = from < base ? base - 1 : base\n\n return clamp(adjusted, 0, count - 1)\n}\n\nexport interface AutoScrollOptions {\n /** Ширина чувствительной полосы у каждого края, px. По умолчанию 32. */\n zone?: number\n /** Скорость на самом краю, px за кадр. По умолчанию 12. */\n speed?: number\n}\n\n/**\n * На сколько прокрутить контейнер за кадр: отрицательное — к началу.\n *\n * Скорость растёт линейно по мере приближения к краю, а не включается\n * ступенькой: ступенька на длинном списке ощущается как рывок и промахивается\n * мимо цели.\n */\nexport function autoScrollDelta(viewport: DragSortSpan, position: number, options: AutoScrollOptions = {}): number {\n const zone = options.zone ?? 32\n const speed = options.speed ?? 12\n\n if (zone <= 0 || speed <= 0)\n return 0\n // Вьюпорт короче двух зон: полосы наложились бы и тянули список в обе стороны.\n if (viewport.end - viewport.start <= zone * 2)\n return 0\n\n if (position < viewport.start || position > viewport.end)\n return 0\n\n const fromStart = position - viewport.start\n if (fromStart < zone)\n return -Math.ceil(((zone - fromStart) / zone) * speed)\n\n const fromEnd = viewport.end - position\n if (fromEnd < zone)\n return Math.ceil(((zone - fromEnd) / zone) * speed)\n\n return 0\n}\n\n/**\n * Перестановка без мутации входа: массив потребителя остаётся нетронутым,\n * наружу уходит новый. `v-model` с мутацией исходного набора ломает и\n * сравнение «было — стало», и историю в сторе потребителя.\n */\nexport function moveItem<T>(items: readonly T[], from: number, to: number): T[] {\n const next = items.slice()\n if (from === to || from < 0 || from >= items.length)\n return next\n\n const [moved] = next.splice(from, 1)\n next.splice(clamp(to, 0, next.length), 0, moved)\n\n return next\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value))\n}\n","import type { Ref } from 'vue'\nimport { computed, onScopeDispose, shallowRef } from 'vue'\n\nimport type { DragSortSpan } from './internal/dragSortGeometry'\nimport { autoScrollDelta, hitTest } from './internal/dragSortGeometry'\nimport { useDragGesture } from './useDragGesture'\n\n/**\n * Модель перестановки: перенос элемента указателем и с клавиатуры.\n *\n * Композабл владеет **механикой** — сессией жеста поверх `useDragGesture`,\n * порогом начала, попаданием в элемент, автопрокруткой у краёв и состоянием\n * переноса. **Смысл** остаётся у компонента: что считать целью (индекс вставки\n * у списка, «до / внутрь / после» у дерева), какими клавишами это делается и\n * что сказать в живой регион.\n *\n * Граница проведена так не из вкуса: у списка цель переноса — число, у дерева —\n * пара «узел и зона», а `Space` в дереве уже занят чекбоксом. Общая у них\n * механика, а не семантика; обобщать семантику значило бы завести опцию на\n * каждое расхождение.\n *\n * Данные композабл не меняет: он сообщает «источник и цель», перестановку\n * делает потребитель (`moveItem` из `internal/dragSortGeometry` — если она\n * обычная).\n *\n * ```ts\n * const sort = useDragSort<string, number>({\n * items: () => keys.value,\n * elementFor: key => rowEls.get(key),\n * scroller: () => scrollerEl.value,\n * resolveTarget: (hit, source) => insertionIndex(hit, indexOf(source), keys.value.length),\n * onDrop: (source, to) => emit('move', indexOf(source), to),\n * })\n * ```\n */\n\nexport type DragSortOrientation = 'vertical' | 'horizontal'\n\n/** Чем ведётся перенос: указателем или с клавиатуры. */\nexport type DragSortMode = 'pointer' | 'keyboard'\n\nexport interface DragSortHit<TKey> {\n key: TKey\n index: number\n /** Доля вдоль элемента: 0 — начало, 1 — конец. Зоны нарезает компонент. */\n fraction: number\n}\n\nexport interface UseDragSortOptions<TKey, TTarget> {\n items: () => readonly TKey[]\n elementFor: (key: TKey) => HTMLElement | null | undefined\n /** По умолчанию `vertical`. */\n orientation?: () => DragSortOrientation\n /** Скроллер для автопрокрутки у краёв. Не задан — автопрокрутки нет. */\n scroller?: () => HTMLElement | null | undefined\n disabled?: () => boolean\n canDrag?: (key: TKey) => boolean\n /** Сдвиг, после которого нажатие становится переносом. По умолчанию 4px. */\n threshold?: () => number\n /**\n * Попадание указателя → цель переноса. `null` — сюда положить нельзя.\n *\n * Третьим аргументом приходит прежняя цель: цели сравниваются по ссылке, и\n * объектной цели (как «узел плюс зона» у дерева) стоит вернуть **ту же**\n * ссылку, пока смысл не изменился, — иначе подсветка и объявления будут\n * дёргаться на каждом движении указателя.\n */\n resolveTarget: (hit: DragSortHit<TKey>, source: TKey, previous: TTarget | null) => TTarget | null\n /** Перенос состоялся. */\n onDrop: (source: TKey, target: TTarget) => void\n /** Состояние изменилось: подсветка строк и объявления — на этом. */\n onUpdate?: (source: TKey | null, target: TTarget | null, mode: DragSortMode | null) => void\n}\n\nexport interface UseDragSortReturn<TKey, TTarget> {\n source: Ref<TKey | null>\n target: Ref<TTarget | null>\n mode: Ref<DragSortMode | null>\n isActive: Ref<boolean>\n /** Вешается на `@pointerdown` ручки или строки. */\n startFrom: (key: TKey) => (event: PointerEvent) => void\n /** Клавиатурный перенос: взять элемент. */\n grab: (key: TKey) => void\n /** Клавиатурный перенос: назначить цель (двигать ведёт компонент). */\n setTarget: (target: TTarget | null) => void\n /** Положить: если цель есть — состоится `onDrop`. */\n drop: () => void\n /** Отменить: ничего не переносится. */\n cancel: () => void\n}\n\nexport function useDragSort<TKey, TTarget>(\n options: UseDragSortOptions<TKey, TTarget>,\n): UseDragSortReturn<TKey, TTarget> {\n const source = shallowRef<TKey | null>(null)\n const target = shallowRef<TTarget | null>(null)\n const mode = shallowRef<DragSortMode | null>(null)\n const isActive = computed(() => mode.value !== null)\n\n // Нажатая, но ещё не потянутая строка: между `pointerdown` и порогом это\n // остаётся обычным кликом, и объявлять перенос рано.\n let pendingKey: TKey | null = null\n let originX = 0\n let originY = 0\n let lastPoint: { x: number, y: number } | null = null\n let scrollFrame: number | null = null\n\n const gesture = useDragGesture({\n disabled: () => options.disabled?.() ?? false,\n onStart: (event) => {\n if (pendingKey === null)\n return false\n if (options.canDrag?.(pendingKey) === false)\n return false\n\n originX = event.clientX\n originY = event.clientY\n\n return true\n },\n onMove: (event) => {\n if (mode.value === null) {\n const moved = Math.hypot(event.clientX - originX, event.clientY - originY)\n if (moved < (options.threshold?.() ?? 4))\n return\n\n source.value = pendingKey\n mode.value = 'pointer'\n // Esc отменяет перенос, но `pointercancel` браузер при этом не шлёт —\n // клавишу приходится слушать самим, в capture, до слоёв приложения.\n window.addEventListener('keydown', onWindowKeydown, true)\n startScrolling()\n notify()\n }\n\n // Против выделения текста под курсором во время переноса.\n event.preventDefault()\n\n lastPoint = { x: event.clientX, y: event.clientY }\n updateTarget(lastPoint)\n },\n onEnd: () => {\n if (mode.value === 'pointer')\n commit()\n else reset()\n },\n onCancel: reset,\n })\n\n function isVertical(): boolean {\n return (options.orientation?.() ?? 'vertical') === 'vertical'\n }\n\n function spanOf(rect: DOMRect): DragSortSpan {\n return isVertical() ? { start: rect.top, end: rect.bottom } : { start: rect.left, end: rect.right }\n }\n\n function notify(): void {\n options.onUpdate?.(source.value, target.value, mode.value)\n }\n\n function updateTarget(point: { x: number, y: number }): void {\n const current = source.value\n if (current === null)\n return\n\n // Отрисованы не все: список может быть под окном отсечения, ветка дерева —\n // свёрнута. Попадание считается по тому, что на экране есть.\n const rendered: { key: TKey, index: number, span: DragSortSpan }[] = []\n\n options.items().forEach((key, index) => {\n const el = options.elementFor(key)\n if (el)\n rendered.push({ key, index, span: spanOf(el.getBoundingClientRect()) })\n })\n\n const hit = hitTest(rendered.map(entry => entry.span), isVertical() ? point.y : point.x)\n const entry = hit === null ? null : rendered[hit.index]\n const next = entry === null || hit === null\n ? null\n : options.resolveTarget({ key: entry.key, index: entry.index, fraction: hit.fraction }, current, target.value)\n\n if (next === target.value)\n return\n\n target.value = next\n notify()\n }\n\n /**\n * Автопрокрутка живёт в своём кадре, а не в обработчике движения: у края\n * указатель обычно стоит неподвижно, событий больше нет — а список должен\n * продолжать ехать.\n */\n function tickScroll(): void {\n scrollFrame = null\n\n const scroller = options.scroller?.()\n const point = lastPoint\n if (!scroller || !point || mode.value !== 'pointer')\n return\n\n const delta = autoScrollDelta(spanOf(scroller.getBoundingClientRect()), isVertical() ? point.y : point.x)\n\n if (delta !== 0) {\n if (isVertical())\n scroller.scrollTop += delta\n else scroller.scrollLeft += delta\n\n updateTarget(point)\n }\n\n scrollFrame = requestAnimationFrame(tickScroll)\n }\n\n function startScrolling(): void {\n if (scrollFrame !== null || !options.scroller?.())\n return\n\n scrollFrame = requestAnimationFrame(tickScroll)\n }\n\n function stopScrolling(): void {\n if (scrollFrame === null)\n return\n\n cancelAnimationFrame(scrollFrame)\n scrollFrame = null\n }\n\n function onWindowKeydown(event: KeyboardEvent): void {\n if (event.key !== 'Escape')\n return\n\n event.preventDefault()\n gesture.stop(false)\n }\n\n function reset(): void {\n stopScrolling()\n window.removeEventListener('keydown', onWindowKeydown, true)\n\n pendingKey = null\n lastPoint = null\n source.value = null\n target.value = null\n mode.value = null\n notify()\n }\n\n function commit(): void {\n const from = source.value\n const to = target.value\n\n if (from !== null && to !== null)\n options.onDrop(from, to)\n\n reset()\n }\n\n function startFrom(key: TKey) {\n return (event: PointerEvent): void => {\n // Клавиатурный перенос идёт своим чередом: указатель его не перебивает.\n if (mode.value === 'keyboard')\n return\n\n pendingKey = key\n gesture.start(event)\n }\n }\n\n function grab(key: TKey): void {\n if (options.disabled?.() || options.canDrag?.(key) === false)\n return\n if (mode.value !== null)\n return\n\n source.value = key\n target.value = null\n mode.value = 'keyboard'\n notify()\n }\n\n function setTarget(next: TTarget | null): void {\n if (mode.value !== 'keyboard')\n return\n\n target.value = next\n notify()\n }\n\n function drop(): void {\n if (mode.value !== 'keyboard')\n return\n\n commit()\n }\n\n function cancel(): void {\n if (mode.value === 'pointer') {\n gesture.stop(false)\n return\n }\n\n if (mode.value === 'keyboard')\n reset()\n }\n\n onScopeDispose(() => {\n stopScrolling()\n window.removeEventListener('keydown', onWindowKeydown, true)\n })\n\n return { source, target, mode, isActive, startFrom, grab, setTarget, drop, cancel }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useFilePreviews-DnwgdyBs.js","names":[],"sources":["../../src/composables/internal/useFilePreviews.ts"],"sourcesContent":["export interface UseFilePreviewsOptions {\n /** Включены ли миниатюры. Геттер — проп реактивен. */\n enabled: () => boolean\n}\n\nexport interface UseFilePreviewsReturn {\n /** Стабильный ключ файла для `v-for`. */\n fileKey: (file: File) => string\n /** `object URL` миниатюры или `undefined`, если файл не картинка. */\n previewUrl: (file: File) => string | undefined\n revokePreview: (file: File) => void\n revokeAllPreviews: () => void\n}\n\n/**\n * Ключи списка и миниатюры превью. Общий на файловые компоненты пакета:\n * `GrFileUpload` (отправка) и `GrFormFile` (поле формы) показывают один и тот\n * же набор одинаково, и правила «что считать картинкой» у них обязаны совпадать.\n *\n * Ключ считается от самого файла, а не от имени: два одноимённых файла из\n * разных папок делили бы один `<li>`, и кнопка удаления в строке снимала бы\n * не тот файл.\n *\n * `object URL` живёт ровно столько, сколько файл в наборе: без отзыва blob\n * висит в памяти вкладки до перезагрузки — утечка тем заметнее, чем чаще\n * пользователь перевыбирает файлы.\n */\nexport function useFilePreviews(options: UseFilePreviewsOptions): UseFilePreviewsReturn {\n const keys = new WeakMap<File, string>()\n const urls = new Map<File, string>()\n let counter = 0\n\n function fileKey(file: File): string {\n const existing = keys.get(file)\n if (existing !== undefined) return existing\n\n counter += 1\n const key = `gr-file-${counter}`\n keys.set(file, key)\n return key\n }\n\n function previewUrl(file: File): string | undefined {\n if (!options.enabled() || !file.type.startsWith('image/')) return undefined\n\n const existing = urls.get(file)\n if (existing) return existing\n\n if (typeof URL === 'undefined' || typeof URL.createObjectURL !== 'function') return undefined\n\n const url = URL.createObjectURL(file)\n urls.set(file, url)\n return url\n }\n\n function revokePreview(file: File): void {\n const url = urls.get(file)\n if (!url) return\n\n URL.revokeObjectURL(url)\n urls.delete(file)\n }\n\n function revokeAllPreviews(): void {\n for (const file of [...urls.keys()]) revokePreview(file)\n }\n\n return { fileKey, previewUrl, revokePreview, revokeAllPreviews }\n}\n"],"mappings":";;;;;;;;;;;;;;AA2BA,SAAgB,gBAAgB,SAAwD;CACtF,MAAM,uBAAO,IAAI,QAAsB;CACvC,MAAM,uBAAO,IAAI,IAAkB;CACnC,IAAI,UAAU;CAEd,SAAS,QAAQ,MAAoB;EACnC,MAAM,WAAW,KAAK,IAAI,IAAI;EAC9B,IAAI,aAAa,KAAA,GAAW,OAAO;EAEnC,WAAW;EACX,MAAM,MAAM,WAAW;EACvB,KAAK,IAAI,MAAM,GAAG;EAClB,OAAO;CACT;CAEA,SAAS,WAAW,MAAgC;EAClD,IAAI,CAAC,QAAQ,QAAQ,KAAK,CAAC,KAAK,KAAK,WAAW,QAAQ,GAAG,OAAO,KAAA;EAElE,MAAM,WAAW,KAAK,IAAI,IAAI;EAC9B,IAAI,UAAU,OAAO;EAErB,IAAI,OAAO,QAAQ,eAAe,OAAO,IAAI,oBAAoB,YAAY,OAAO,KAAA;EAEpF,MAAM,MAAM,IAAI,gBAAgB,IAAI;EACpC,KAAK,IAAI,MAAM,GAAG;EAClB,OAAO;CACT;CAEA,SAAS,cAAc,MAAkB;EACvC,MAAM,MAAM,KAAK,IAAI,IAAI;EACzB,IAAI,CAAC,KAAK;EAEV,IAAI,gBAAgB,GAAG;EACvB,KAAK,OAAO,IAAI;CAClB;CAEA,SAAS,oBAA0B;EACjC,KAAK,MAAM,QAAQ,CAAC,GAAG,KAAK,KAAK,CAAC,GAAG,cAAc,IAAI;CACzD;CAEA,OAAO;EAAE;EAAS;EAAY;EAAe;CAAkB;AACjE"}
1
+ {"version":3,"file":"useFilePreviews-DnwgdyBs.js","names":[],"sources":["../../src/composables/internal/useFilePreviews.ts"],"sourcesContent":["export interface UseFilePreviewsOptions {\n /** Включены ли миниатюры. Геттер — проп реактивен. */\n enabled: () => boolean\n}\n\nexport interface UseFilePreviewsReturn {\n /** Стабильный ключ файла для `v-for`. */\n fileKey: (file: File) => string\n /** `object URL` миниатюры или `undefined`, если файл не картинка. */\n previewUrl: (file: File) => string | undefined\n revokePreview: (file: File) => void\n revokeAllPreviews: () => void\n}\n\n/**\n * Ключи списка и миниатюры превью. Общий на файловые компоненты пакета:\n * `GrFileUpload` (отправка) и `GrFormFile` (поле формы) показывают один и тот\n * же набор одинаково, и правила «что считать картинкой» у них обязаны совпадать.\n *\n * Ключ считается от самого файла, а не от имени: два одноимённых файла из\n * разных папок делили бы один `<li>`, и кнопка удаления в строке снимала бы\n * не тот файл.\n *\n * `object URL` живёт ровно столько, сколько файл в наборе: без отзыва blob\n * висит в памяти вкладки до перезагрузки — утечка тем заметнее, чем чаще\n * пользователь перевыбирает файлы.\n */\nexport function useFilePreviews(options: UseFilePreviewsOptions): UseFilePreviewsReturn {\n const keys = new WeakMap<File, string>()\n const urls = new Map<File, string>()\n let counter = 0\n\n function fileKey(file: File): string {\n const existing = keys.get(file)\n if (existing !== undefined)\n return existing\n\n counter += 1\n const key = `gr-file-${counter}`\n keys.set(file, key)\n return key\n }\n\n function previewUrl(file: File): string | undefined {\n if (!options.enabled() || !file.type.startsWith('image/'))\n return undefined\n\n const existing = urls.get(file)\n if (existing)\n return existing\n\n if (typeof URL === 'undefined' || typeof URL.createObjectURL !== 'function')\n return undefined\n\n const url = URL.createObjectURL(file)\n urls.set(file, url)\n return url\n }\n\n function revokePreview(file: File): void {\n const url = urls.get(file)\n if (!url)\n return\n\n URL.revokeObjectURL(url)\n urls.delete(file)\n }\n\n function revokeAllPreviews(): void {\n for (const file of [...urls.keys()]) revokePreview(file)\n }\n\n return { fileKey, previewUrl, revokePreview, revokeAllPreviews }\n}\n"],"mappings":";;;;;;;;;;;;;;AA2BA,SAAgB,gBAAgB,SAAwD;CACtF,MAAM,uBAAO,IAAI,QAAsB;CACvC,MAAM,uBAAO,IAAI,IAAkB;CACnC,IAAI,UAAU;CAEd,SAAS,QAAQ,MAAoB;EACnC,MAAM,WAAW,KAAK,IAAI,IAAI;EAC9B,IAAI,aAAa,KAAA,GACf,OAAO;EAET,WAAW;EACX,MAAM,MAAM,WAAW;EACvB,KAAK,IAAI,MAAM,GAAG;EAClB,OAAO;CACT;CAEA,SAAS,WAAW,MAAgC;EAClD,IAAI,CAAC,QAAQ,QAAQ,KAAK,CAAC,KAAK,KAAK,WAAW,QAAQ,GACtD,OAAO,KAAA;EAET,MAAM,WAAW,KAAK,IAAI,IAAI;EAC9B,IAAI,UACF,OAAO;EAET,IAAI,OAAO,QAAQ,eAAe,OAAO,IAAI,oBAAoB,YAC/D,OAAO,KAAA;EAET,MAAM,MAAM,IAAI,gBAAgB,IAAI;EACpC,KAAK,IAAI,MAAM,GAAG;EAClB,OAAO;CACT;CAEA,SAAS,cAAc,MAAkB;EACvC,MAAM,MAAM,KAAK,IAAI,IAAI;EACzB,IAAI,CAAC,KACH;EAEF,IAAI,gBAAgB,GAAG;EACvB,KAAK,OAAO,IAAI;CAClB;CAEA,SAAS,oBAA0B;EACjC,KAAK,MAAM,QAAQ,CAAC,GAAG,KAAK,KAAK,CAAC,GAAG,cAAc,IAAI;CACzD;CAEA,OAAO;EAAE;EAAS;EAAY;EAAe;CAAkB;AACjE"}
@@ -1 +1 @@
1
- {"version":3,"file":"useFocusWithin-BwHE0rzm.js","names":[],"sources":["../../src/composables/internal/useFocusWithin.ts"],"sourcesContent":["import type { Ref } from 'vue'\n\nexport interface UseFocusWithinOptions {\n /** Фокус пришёл снаружи внутрь. */\n enter?: (event: FocusEvent) => void\n /** Фокус ушёл изнутри наружу. */\n leave?: (event: FocusEvent) => void\n /**\n * Узлы, которые считаются частью контрола, хотя лежат вне корня. Нужны там,\n * где панель телепортирована в `body`, а фокус в неё уходит по-настоящему:\n * без них переход в поле фильтра открытой панели выглядел бы уходом наружу.\n */\n containers?: () => (HTMLElement | null | undefined)[]\n}\n\nexport interface UseFocusWithinHandlers {\n onFocusIn: (event: FocusEvent) => void\n onFocusOut: (event: FocusEvent) => void\n}\n\n/**\n * Граница фокуса составного контрола.\n *\n * Нативные `focus`/`blur` не всплывают, поэтому у группы (радиогруппа,\n * сегменты, набор чекбоксов) их не поймать на корне вовсе — всплывают только\n * `focusin`/`focusout`. Но всплывают они и когда фокус переезжает **внутри**\n * группы: между её пунктами, с поля на его же кнопку очистки. Для потребителя\n * это не уход и не приход — контрол всё это время в фокусе, — а необработанный\n * переход дал бы `blur` + `focus` на каждую стрелку клавиатуры.\n *\n * Поэтому событие считается состоявшимся, только если `relatedTarget` лежит вне\n * корня. `relatedTarget: null` (клик по body, уход из окна, программный фокус)\n * — тоже пересечение границы.\n */\nexport function useFocusWithin(\n root: Ref<HTMLElement | null>,\n options: UseFocusWithinOptions,\n): UseFocusWithinHandlers {\n function crossedBoundary(event: FocusEvent): boolean {\n const other = event.relatedTarget as Node | null\n if (!other) return true\n\n if (root.value?.contains(other)) return false\n\n return !options.containers?.().some(node => node?.contains(other))\n }\n\n return {\n onFocusIn(event) {\n if (crossedBoundary(event)) options.enter?.(event)\n },\n onFocusOut(event) {\n if (crossedBoundary(event)) options.leave?.(event)\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAkCA,SAAgB,eACd,MACA,SACwB;CACxB,SAAS,gBAAgB,OAA4B;EACnD,MAAM,QAAQ,MAAM;EACpB,IAAI,CAAC,OAAO,OAAO;EAEnB,IAAI,KAAK,OAAO,SAAS,KAAK,GAAG,OAAO;EAExC,OAAO,CAAC,QAAQ,aAAa,CAAC,CAAC,MAAK,SAAQ,MAAM,SAAS,KAAK,CAAC;CACnE;CAEA,OAAO;EACL,UAAU,OAAO;GACf,IAAI,gBAAgB,KAAK,GAAG,QAAQ,QAAQ,KAAK;EACnD;EACA,WAAW,OAAO;GAChB,IAAI,gBAAgB,KAAK,GAAG,QAAQ,QAAQ,KAAK;EACnD;CACF;AACF"}
1
+ {"version":3,"file":"useFocusWithin-BwHE0rzm.js","names":[],"sources":["../../src/composables/internal/useFocusWithin.ts"],"sourcesContent":["import type { Ref } from 'vue'\n\nexport interface UseFocusWithinOptions {\n /** Фокус пришёл снаружи внутрь. */\n enter?: (event: FocusEvent) => void\n /** Фокус ушёл изнутри наружу. */\n leave?: (event: FocusEvent) => void\n /**\n * Узлы, которые считаются частью контрола, хотя лежат вне корня. Нужны там,\n * где панель телепортирована в `body`, а фокус в неё уходит по-настоящему:\n * без них переход в поле фильтра открытой панели выглядел бы уходом наружу.\n */\n containers?: () => (HTMLElement | null | undefined)[]\n}\n\nexport interface UseFocusWithinHandlers {\n onFocusIn: (event: FocusEvent) => void\n onFocusOut: (event: FocusEvent) => void\n}\n\n/**\n * Граница фокуса составного контрола.\n *\n * Нативные `focus`/`blur` не всплывают, поэтому у группы (радиогруппа,\n * сегменты, набор чекбоксов) их не поймать на корне вовсе — всплывают только\n * `focusin`/`focusout`. Но всплывают они и когда фокус переезжает **внутри**\n * группы: между её пунктами, с поля на его же кнопку очистки. Для потребителя\n * это не уход и не приход — контрол всё это время в фокусе, — а необработанный\n * переход дал бы `blur` + `focus` на каждую стрелку клавиатуры.\n *\n * Поэтому событие считается состоявшимся, только если `relatedTarget` лежит вне\n * корня. `relatedTarget: null` (клик по body, уход из окна, программный фокус)\n * — тоже пересечение границы.\n */\nexport function useFocusWithin(\n root: Ref<HTMLElement | null>,\n options: UseFocusWithinOptions,\n): UseFocusWithinHandlers {\n function crossedBoundary(event: FocusEvent): boolean {\n const other = event.relatedTarget as Node | null\n if (!other)\n return true\n\n if (root.value?.contains(other))\n return false\n\n return !options.containers?.().some(node => node?.contains(other))\n }\n\n return {\n onFocusIn(event) {\n if (crossedBoundary(event))\n options.enter?.(event)\n },\n onFocusOut(event) {\n if (crossedBoundary(event))\n options.leave?.(event)\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAkCA,SAAgB,eACd,MACA,SACwB;CACxB,SAAS,gBAAgB,OAA4B;EACnD,MAAM,QAAQ,MAAM;EACpB,IAAI,CAAC,OACH,OAAO;EAET,IAAI,KAAK,OAAO,SAAS,KAAK,GAC5B,OAAO;EAET,OAAO,CAAC,QAAQ,aAAa,CAAC,CAAC,MAAK,SAAQ,MAAM,SAAS,KAAK,CAAC;CACnE;CAEA,OAAO;EACL,UAAU,OAAO;GACf,IAAI,gBAAgB,KAAK,GACvB,QAAQ,QAAQ,KAAK;EACzB;EACA,WAAW,OAAO;GAChB,IAAI,gBAAgB,KAAK,GACvB,QAAQ,QAAQ,KAAK;EACzB;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"useModalOverlay-Cv5hF63W.js","names":[],"sources":["../../src/composables/internal/useInertOthers.ts","../../src/composables/internal/useOverlayPresence.ts","../../src/composables/internal/useScrollLock.ts","../../src/composables/internal/useModalOverlay.ts"],"sourcesContent":["import type { Ref } from 'vue'\nimport { onScopeDispose, watch } from 'vue'\n\nimport { inertableOutside, markInert } from './inert'\n\n/**\n * Пока слой активен, всё вне его ветки помечено `inert`: от корня оверлея вверх\n * до `<body>`, на каждом уровне — соседи.\n *\n * Это вторая половина модальности: `z-index` перекрывает страницу глазами,\n * `inert` — клавиатурой и скринридером.\n */\nexport function useInertOthers(root: Ref<HTMLElement | null>, active: () => boolean): void {\n let release: (() => void) | undefined\n\n function apply(): void {\n release?.()\n release = markInert(inertableOutside(root.value))\n }\n\n function clear(): void {\n release?.()\n release = undefined\n }\n\n watch(\n () => Boolean(active()) && Boolean(root.value),\n (isActive) => {\n if (isActive) apply()\n else clear()\n },\n { immediate: true, flush: 'post' },\n )\n\n onScopeDispose(clear)\n}\n","import type { Ref } from 'vue'\nimport { ref, watch } from 'vue'\n\nexport interface OverlayPresence {\n /** Смонтировано ли поддерево слоя: держится до конца leave-анимации. */\n mounted: Ref<boolean>\n /** Показан ли слой: этим переключаются `<Transition>` внутри. */\n visible: Ref<boolean>\n /** Вызывается из `@after-leave` панели. */\n onPanelAfterLeave: () => void\n}\n\n/**\n * Присутствие оверлея в DOM отдельно от его видимости.\n *\n * Штатный `<Transition>` анимирует свой элемент, но не координирует соседей:\n * убери обёртку по `v-if` — и подложка с панелью исчезнут мгновенно, без\n * leave-анимации. Поэтому присутствие поддерева и видимость слоя — два разных\n * флага: первый снимается только по концу leave-анимации панели.\n *\n * Видимость включается **в том же такте**, что и монтирование: содержимое окна\n * обязано появляться в DOM сразу после открытия — на этом стоит и код\n * потребителя, и внутренние компоненты, которые ищут в панели свои элементы.\n * Enter-анимацию это не отменяет: `<Transition appear>` проигрывает её и на\n * первом рендере ребёнка.\n */\nexport function useOverlayPresence(open: Ref<boolean>): OverlayPresence {\n const mounted = ref(open.value)\n const visible = ref(open.value)\n\n watch(open, (isOpen) => {\n if (isOpen) mounted.value = true\n visible.value = isOpen\n })\n\n function onPanelAfterLeave(): void {\n // Пока панель уезжала, слой могли открыть заново — тогда размонтировать\n // нечего и незачем.\n if (!open.value) mounted.value = false\n }\n\n return { mounted, visible, onPanelAfterLeave }\n}\n","/**\n * useScrollLock — общий **reference-counted** lock скролла `<body>` для оверлеев\n * (GrModal, GrDrawer, GrImageViewer).\n *\n * Зачем счётчик: состояние `<body>` принадлежит не оверлею, а их совокупности.\n * Держи каждый свой `savedBodyOverflow`, и закрытие A при ещё открытой B вернуло\n * бы странице прокрутку под открытым окном — закрывают не всегда в порядке\n * открытия. Поэтому состояние сохраняется один раз (переход счётчика 0→1) и\n * восстанавливается только когда закрыт последний оверлей (1→0).\n *\n * Плюс компенсация ширины скроллбара: при `overflow: hidden` скроллбар исчезает и\n * контент дёргается вправо — добавляем `padding-right` на его ширину.\n *\n * Отдельная ветка — iOS Safari: там `overflow: hidden` фон не держит, страница\n * продолжает «резинить» под открытым окном. Лечится тем же, чем лечил\n * HeadlessUI (`handleIOSLocking`): гасим `touchmove` вне скроллящихся областей\n * оверлея и возвращаем позицию прокрутки после снятия лока.\n */\n\ninterface SavedBodyStyle {\n overflow: string\n paddingRight: string\n overscrollBehavior: string\n}\n\nlet lockCount = 0\nlet saved: SavedBodyStyle | null = null\nlet savedScrollY = 0\nlet releaseTouchGuard: (() => void) | null = null\n\n/**\n * iOS определяем по платформе, а не по ширине экрана: лечение специфично для\n * движка, а не для размера. `maxTouchPoints` отличает iPadOS, который со времён\n * 13-й версии представляется десктопным Mac.\n */\nfunction isIOS(): boolean {\n if (typeof navigator === 'undefined') return false\n return /iP(?:hone|ad|od)/.test(navigator.platform)\n || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)\n}\n\n/** Прокручиваемая область оверлея — единственное, чему `touchmove` разрешён. */\nfunction isScrollableOverlayArea(target: EventTarget | null): boolean {\n if (!(target instanceof Element)) return false\n\n for (let node: Element | null = target; node; node = node.parentElement) {\n if (!(node instanceof HTMLElement)) continue\n if (node.hasAttribute('data-gr-overlay-root')) return true\n }\n\n return false\n}\n\nfunction applyTouchGuard(): void {\n if (!isIOS()) return\n\n const onTouchMove = (event: TouchEvent): void => {\n // Внутри оверлея скролл законен (длинное окно, лента изображений), а всё\n // остальное — это и есть фон, который лок обязан держать.\n if (isScrollableOverlayArea(event.target)) return\n if (event.cancelable) event.preventDefault()\n }\n\n document.addEventListener('touchmove', onTouchMove, { passive: false })\n releaseTouchGuard = () => document.removeEventListener('touchmove', onTouchMove)\n}\n\nfunction getScrollbarWidth(): number {\n return window.innerWidth - document.documentElement.clientWidth\n}\n\nfunction applyLock(): void {\n const body = document.body\n saved = {\n overflow: body.style.overflow,\n paddingRight: body.style.paddingRight,\n overscrollBehavior: body.style.overscrollBehavior,\n }\n savedScrollY = window.scrollY\n\n // Ширину скроллбара считаем ДО `overflow: hidden` (после — `clientWidth` меняется).\n const scrollbarWidth = getScrollbarWidth()\n\n body.style.overflow = 'hidden'\n // Прокрутка, «доехавшая» до края внутри окна, не должна продолжаться фоном.\n body.style.overscrollBehavior = 'contain'\n if (scrollbarWidth > 0) {\n const currentPadding = Number.parseFloat(getComputedStyle(body).paddingRight) || 0\n body.style.paddingRight = `${currentPadding + scrollbarWidth}px`\n }\n\n applyTouchGuard()\n}\n\nfunction releaseLock(): void {\n releaseTouchGuard?.()\n releaseTouchGuard = null\n\n if (!saved) return\n document.body.style.overflow = saved.overflow\n document.body.style.paddingRight = saved.paddingRight\n document.body.style.overscrollBehavior = saved.overscrollBehavior\n saved = null\n\n // iOS сдвигает страницу, пока фон заблокирован; возвращаем её на место.\n if (isIOS() && window.scrollY !== savedScrollY) window.scrollTo(0, savedScrollY)\n}\n\n/**\n * Возвращает пару `lock`/`unlock`, идемпотентную **в пределах одного инстанса**\n * (повторный `lock()` без `unlock()` не увеличивает глобальный счётчик дважды).\n * Глобальный счётчик разделяется всеми оверлеями.\n */\nexport function useScrollLock() {\n const isClient = typeof window !== 'undefined'\n let held = false\n\n function lock(): void {\n if (!isClient || held) return\n held = true\n lockCount += 1\n if (lockCount === 1) applyLock()\n }\n\n function unlock(): void {\n if (!isClient || !held) return\n held = false\n lockCount = Math.max(0, lockCount - 1)\n if (lockCount === 0) releaseLock()\n }\n\n return { lock, unlock }\n}\n\n/** Тестовая/служебная очистка глобального состояния. */\nexport function resetScrollLock(): void {\n lockCount = 0\n if (typeof document !== 'undefined') releaseLock()\n saved = null\n}\n","import type { ComputedRef, Ref } from 'vue'\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\n\nimport { useGrThemeAttrs } from '../../components/GrConfigProvider/context'\nimport { useFocusTrap } from '../useFocusTrap'\nimport { useOverlayLayer } from '../useOverlayLayer'\nimport { usePortalTarget } from '../usePortalTarget'\nimport { useInertOthers } from './useInertOthers'\nimport { useOverlayPresence } from './useOverlayPresence'\nimport { useScrollLock } from './useScrollLock'\n\n/**\n * Модальный слой целиком: одна сборка на весь пакет.\n *\n * Модальность собиралась вручную в трёх компонентах (`GrModal`, `GrDrawer`,\n * `GrImageViewer`) из шести примитивов, и сборка повторялась дословно. Дело не\n * в объёме: в этой проводке сидят инварианты, каждый из которых при пропуске\n * даёт тихий баг доступности, а не заметную поломку. Здесь они собраны в одном\n * месте — забыть часть модального слоя больше нельзя.\n *\n * Что именно неочевидно:\n *\n * - **`restoreFocus: false` у ловушки.** Возврат фокуса принадлежит стеку\n * слоёв: у него правило строже — вернуть, только если на момент закрытия\n * фокус ещё был внутри слоя. Две системы возврата подрались бы за фокус.\n * - **`containers: layer.rootsAbove`.** Панель селекта, открытого ВНУТРИ окна,\n * телепортирована в `body` и лежит вне поддерева панели. Без этого списка\n * ловушка считала бы её «снаружи» и отбирала фокус.\n * - **Ловушка и `inert` работают, только пока слой верхний.** Иначе нижнее\n * окно ворует фокус у открытого поверх него, а `inert` гасит собственную\n * панель вместе со страницей.\n * - **Клик по подложке считается только тот, что на ней и начался.** Иначе\n * выделение текста мышью или перетаскивание, отпущенное за пределами панели,\n * закрывает слой.\n *\n * Чего композабл НЕ делает: не рисует разметку. Корневой `div`, подложка и\n * панель у трёх компонентов различаются по существу (обёртки скролла у окна,\n * сквозные клики у немодальной панели, слои хрома у просмотрщика), а классами\n * общий модуль владеть не вправе — пресет гранулярности подмешивает safelist\n * только по директориям выбранных компонентов.\n */\nexport interface UseModalOverlayOptions {\n /**\n * Модальный ли слой: блокирует страницу, держит фокус, гасит нижние окна.\n * Геттером — там, где модальность включается пропом (`GrDrawer`).\n */\n modal?: boolean | (() => boolean)\n /** Закрывать ли по Esc. Геттер — проп компонента реактивен. */\n closeOnEscape?: () => boolean\n /** Закрывать ли по клику в подложку; читается обработчиками `backdrop`. */\n closeOnBackdrop?: () => boolean\n /** Панель слоя: контейнер ловушки фокуса и её запасной приёмник. */\n panel: Ref<HTMLElement | null>\n /**\n * Свой корень слоя. У якорного оверлея (`GrPopover`) корень и панель — один\n * элемент: в портал уезжает сама панель, отдельной обёртки нет. Не задан —\n * композабл заводит корень сам, как у центрированных окон.\n */\n root?: Ref<HTMLElement | null>\n /**\n * Точечное переопределение точки монтирования (проп `teleportTo`). По\n * умолчанию — общий портал оверлеев.\n */\n teleportTo?: () => string | HTMLElement | undefined\n /**\n * Кому отдать фокус при открытии. Не задан — первому фокусируемому внутри\n * панели (правило `useFocusTrap`): просмотрщику нужна именно кнопка тулбара,\n * а окну — сама панель, и оно передаёт её явно.\n */\n initialFocus?: () => HTMLElement | null | undefined\n /**\n * Блокировать ли скролл страницы. По умолчанию — пока слой открыт и модален:\n * немодальная панель существует ровно ради работы со страницей под ней.\n */\n lockScroll?: () => boolean\n}\n\nexport interface ModalOverlay {\n /** Корень слоя: цель `ref` в шаблоне, хозяин `inert` и корень для стека слоёв. */\n rootEl: Ref<HTMLElement | null>\n /** Смонтировано ли поддерево: держится до конца leave-анимации панели. */\n isMounted: Ref<boolean>\n /** Показан ли слой: этим переключаются `<Transition>` внутри. */\n isVisible: Ref<boolean>\n /** Верхний ли это слой сейчас. */\n isTopmost: Ref<boolean>\n /** Значение атрибута `inert` для корня: слой перестал быть верхним. */\n inertAttr: ComputedRef<true | undefined>\n portalTarget: ComputedRef<string | HTMLElement>\n teleportEnabled: ComputedRef<boolean>\n /** `data-theme` на телепортированный корень: в DOM он вне обёртки провайдера. */\n themeAttrs: ComputedRef<{ 'data-theme'?: string }>\n /** Для `@after-leave` панели. Компонент оборачивает, если ему нужен эмит `closed`. */\n onPanelAfterLeave: () => void\n /**\n * Обработчики подложки вместе с guard'ом «клик начался на ней». Имена — под\n * `v-on=\"backdrop\"`: объектный синтаксис ждёт имена событий, а не пропов.\n */\n backdrop: {\n mousedown: (event: MouseEvent) => void\n click: (event: MouseEvent) => void\n }\n}\n\nexport function useModalOverlay(\n open: Ref<boolean>,\n onDismiss: () => void,\n options: UseModalOverlayOptions,\n): ModalOverlay {\n const isModal = (): boolean =>\n typeof options.modal === 'function' ? options.modal() : options.modal ?? true\n\n const rootEl = options.root ?? ref<HTMLElement | null>(null)\n const isTopmost = ref(true)\n\n const inertAttr = computed(() => (open.value && isModal() && !isTopmost.value ? true : undefined))\n\n // Телепорт включается только ПОСЛЕ монтирования: иначе первый клиентский\n // рендер не совпадёт с серверным и сломается гидрация.\n const { target: portalTarget, enabled: teleportEnabled } = usePortalTarget(options.teleportTo)\n\n // Панель уезжает в `body`, то есть вне обёртки провайдера, и `data-theme` с\n // неё не наследуется. В дереве компонентов слой остаётся внутри — `inject`\n // доходит, и тему он ставит себе сам.\n const themeAttrs = useGrThemeAttrs()\n\n const { mounted: isMounted, visible: isVisible, onPanelAfterLeave } = useOverlayPresence(open)\n\n const layer = useOverlayLayer(open, onDismiss, {\n modal: isModal,\n closeOnEscape: options.closeOnEscape,\n onTopmostChange: (value) => { isTopmost.value = value },\n root: rootEl,\n })\n\n useFocusTrap(options.panel, {\n active: () => open.value && isModal() && isTopmost.value,\n initialFocus: options.initialFocus,\n fallbackFocus: () => options.panel.value,\n containers: layer.rootsAbove,\n restoreFocus: false,\n })\n\n useInertOthers(rootEl, () => open.value && isModal() && isTopmost.value)\n\n // Общий reference-counted lock: несколько открытых оверлеев снимают блокировку\n // независимо от порядка закрытия, ширина скроллбара компенсируется.\n const { lock, unlock } = useScrollLock()\n\n watch(\n () => options.lockScroll?.() ?? (open.value && isModal()),\n (locked) => {\n if (locked) lock()\n else unlock()\n },\n { immediate: true },\n )\n\n onBeforeUnmount(unlock)\n\n let pointerDownOnBackdrop = false\n\n return {\n rootEl,\n isMounted,\n isVisible,\n isTopmost,\n inertAttr,\n portalTarget,\n teleportEnabled,\n themeAttrs,\n onPanelAfterLeave,\n backdrop: {\n mousedown(event: MouseEvent): void {\n pointerDownOnBackdrop = event.target === event.currentTarget\n },\n click(event: MouseEvent): void {\n const startedHere = pointerDownOnBackdrop\n pointerDownOnBackdrop = false\n\n if (!startedHere || event.target !== event.currentTarget) return\n if (options.closeOnBackdrop?.() === false) return\n\n onDismiss()\n },\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAYA,SAAgB,eAAe,MAA+B,QAA6B;CACzF,IAAI;CAEJ,SAAS,QAAc;EACrB,UAAU;EACV,UAAU,UAAU,iBAAiB,KAAK,KAAK,CAAC;CAClD;CAEA,SAAS,QAAc;EACrB,UAAU;EACV,UAAU,KAAA;CACZ;CAEA,YACQ,QAAQ,OAAO,CAAC,KAAK,QAAQ,KAAK,KAAK,IAC5C,aAAa;EACZ,IAAI,UAAU,MAAM;OACf,MAAM;CACb,GACA;EAAE,WAAW;EAAM,OAAO;CAAO,CACnC;CAEA,eAAe,KAAK;AACtB;;;;;;;;;;;;;;;;;ACTA,SAAgB,mBAAmB,MAAqC;CACtE,MAAM,UAAU,IAAI,KAAK,KAAK;CAC9B,MAAM,UAAU,IAAI,KAAK,KAAK;CAE9B,MAAM,OAAO,WAAW;EACtB,IAAI,QAAQ,QAAQ,QAAQ;EAC5B,QAAQ,QAAQ;CAClB,CAAC;CAED,SAAS,oBAA0B;EAGjC,IAAI,CAAC,KAAK,OAAO,QAAQ,QAAQ;CACnC;CAEA,OAAO;EAAE;EAAS;EAAS;CAAkB;AAC/C;;;ACjBA,IAAI,YAAY;AAChB,IAAI,QAA+B;AACnC,IAAI,eAAe;AACnB,IAAI,oBAAyC;;;;;;AAO7C,SAAS,QAAiB;CACxB,IAAI,OAAO,cAAc,aAAa,OAAO;CAC7C,OAAO,mBAAmB,KAAK,UAAU,QAAQ,KAC3C,UAAU,aAAa,cAAc,UAAU,iBAAiB;AACxE;;AAGA,SAAS,wBAAwB,QAAqC;CACpE,IAAI,EAAE,kBAAkB,UAAU,OAAO;CAEzC,KAAK,IAAI,OAAuB,QAAQ,MAAM,OAAO,KAAK,eAAe;EACvE,IAAI,EAAE,gBAAgB,cAAc;EACpC,IAAI,KAAK,aAAa,sBAAsB,GAAG,OAAO;CACxD;CAEA,OAAO;AACT;AAEA,SAAS,kBAAwB;CAC/B,IAAI,CAAC,MAAM,GAAG;CAEd,MAAM,eAAe,UAA4B;EAG/C,IAAI,wBAAwB,MAAM,MAAM,GAAG;EAC3C,IAAI,MAAM,YAAY,MAAM,eAAe;CAC7C;CAEA,SAAS,iBAAiB,aAAa,aAAa,EAAE,SAAS,MAAM,CAAC;CACtE,0BAA0B,SAAS,oBAAoB,aAAa,WAAW;AACjF;AAEA,SAAS,oBAA4B;CACnC,OAAO,OAAO,aAAa,SAAS,gBAAgB;AACtD;AAEA,SAAS,YAAkB;CACzB,MAAM,OAAO,SAAS;CACtB,QAAQ;EACN,UAAU,KAAK,MAAM;EACrB,cAAc,KAAK,MAAM;EACzB,oBAAoB,KAAK,MAAM;CACjC;CACA,eAAe,OAAO;CAGtB,MAAM,iBAAiB,kBAAkB;CAEzC,KAAK,MAAM,WAAW;CAEtB,KAAK,MAAM,qBAAqB;CAChC,IAAI,iBAAiB,GAAG;EACtB,MAAM,iBAAiB,OAAO,WAAW,iBAAiB,IAAI,CAAC,CAAC,YAAY,KAAK;EACjF,KAAK,MAAM,eAAe,GAAG,iBAAiB,eAAe;CAC/D;CAEA,gBAAgB;AAClB;AAEA,SAAS,cAAoB;CAC3B,oBAAoB;CACpB,oBAAoB;CAEpB,IAAI,CAAC,OAAO;CACZ,SAAS,KAAK,MAAM,WAAW,MAAM;CACrC,SAAS,KAAK,MAAM,eAAe,MAAM;CACzC,SAAS,KAAK,MAAM,qBAAqB,MAAM;CAC/C,QAAQ;CAGR,IAAI,MAAM,KAAK,OAAO,YAAY,cAAc,OAAO,SAAS,GAAG,YAAY;AACjF;;;;;;AAOA,SAAgB,gBAAgB;CAC9B,MAAM,WAAW,OAAO,WAAW;CACnC,IAAI,OAAO;CAEX,SAAS,OAAa;EACpB,IAAI,CAAC,YAAY,MAAM;EACvB,OAAO;EACP,aAAa;EACb,IAAI,cAAc,GAAG,UAAU;CACjC;CAEA,SAAS,SAAe;EACtB,IAAI,CAAC,YAAY,CAAC,MAAM;EACxB,OAAO;EACP,YAAY,KAAK,IAAI,GAAG,YAAY,CAAC;EACrC,IAAI,cAAc,GAAG,YAAY;CACnC;CAEA,OAAO;EAAE;EAAM;CAAO;AACxB;;;AC5BA,SAAgB,gBACd,MACA,WACA,SACc;CACd,MAAM,gBACJ,OAAO,QAAQ,UAAU,aAAa,QAAQ,MAAM,IAAI,QAAQ,SAAS;CAE3E,MAAM,SAAS,QAAQ,QAAQ,IAAwB,IAAI;CAC3D,MAAM,YAAY,IAAI,IAAI;CAE1B,MAAM,YAAY,eAAgB,KAAK,SAAS,QAAQ,KAAK,CAAC,UAAU,QAAQ,OAAO,KAAA,CAAU;CAIjG,MAAM,EAAE,QAAQ,cAAc,SAAS,oBAAoB,gBAAgB,QAAQ,UAAU;CAK7F,MAAM,aAAa,gBAAgB;CAEnC,MAAM,EAAE,SAAS,WAAW,SAAS,WAAW,sBAAsB,mBAAmB,IAAI;CAE7F,MAAM,QAAQ,gBAAgB,MAAM,WAAW;EAC7C,OAAO;EACP,eAAe,QAAQ;EACvB,kBAAkB,UAAU;GAAE,UAAU,QAAQ;EAAM;EACtD,MAAM;CACR,CAAC;CAED,aAAa,QAAQ,OAAO;EAC1B,cAAc,KAAK,SAAS,QAAQ,KAAK,UAAU;EACnD,cAAc,QAAQ;EACtB,qBAAqB,QAAQ,MAAM;EACnC,YAAY,MAAM;EAClB,cAAc;CAChB,CAAC;CAED,eAAe,cAAc,KAAK,SAAS,QAAQ,KAAK,UAAU,KAAK;CAIvE,MAAM,EAAE,MAAM,WAAW,cAAc;CAEvC,YACQ,QAAQ,aAAa,MAAM,KAAK,SAAS,QAAQ,KACtD,WAAW;EACV,IAAI,QAAQ,KAAK;OACZ,OAAO;CACd,GACA,EAAE,WAAW,KAAK,CACpB;CAEA,gBAAgB,MAAM;CAEtB,IAAI,wBAAwB;CAE5B,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,UAAU;GACR,UAAU,OAAyB;IACjC,wBAAwB,MAAM,WAAW,MAAM;GACjD;GACA,MAAM,OAAyB;IAC7B,MAAM,cAAc;IACpB,wBAAwB;IAExB,IAAI,CAAC,eAAe,MAAM,WAAW,MAAM,eAAe;IAC1D,IAAI,QAAQ,kBAAkB,MAAM,OAAO;IAE3C,UAAU;GACZ;EACF;CACF;AACF"}
1
+ {"version":3,"file":"useModalOverlay-Cv5hF63W.js","names":[],"sources":["../../src/composables/internal/useInertOthers.ts","../../src/composables/internal/useOverlayPresence.ts","../../src/composables/internal/useScrollLock.ts","../../src/composables/internal/useModalOverlay.ts"],"sourcesContent":["import type { Ref } from 'vue'\nimport { onScopeDispose, watch } from 'vue'\n\nimport { inertableOutside, markInert } from './inert'\n\n/**\n * Пока слой активен, всё вне его ветки помечено `inert`: от корня оверлея вверх\n * до `<body>`, на каждом уровне — соседи.\n *\n * Это вторая половина модальности: `z-index` перекрывает страницу глазами,\n * `inert` — клавиатурой и скринридером.\n */\nexport function useInertOthers(root: Ref<HTMLElement | null>, active: () => boolean): void {\n let release: (() => void) | undefined\n\n function apply(): void {\n release?.()\n release = markInert(inertableOutside(root.value))\n }\n\n function clear(): void {\n release?.()\n release = undefined\n }\n\n watch(\n () => Boolean(active()) && Boolean(root.value),\n (isActive) => {\n if (isActive)\n apply()\n else clear()\n },\n { immediate: true, flush: 'post' },\n )\n\n onScopeDispose(clear)\n}\n","import type { Ref } from 'vue'\nimport { ref, watch } from 'vue'\n\nexport interface OverlayPresence {\n /** Смонтировано ли поддерево слоя: держится до конца leave-анимации. */\n mounted: Ref<boolean>\n /** Показан ли слой: этим переключаются `<Transition>` внутри. */\n visible: Ref<boolean>\n /** Вызывается из `@after-leave` панели. */\n onPanelAfterLeave: () => void\n}\n\n/**\n * Присутствие оверлея в DOM отдельно от его видимости.\n *\n * Штатный `<Transition>` анимирует свой элемент, но не координирует соседей:\n * убери обёртку по `v-if` — и подложка с панелью исчезнут мгновенно, без\n * leave-анимации. Поэтому присутствие поддерева и видимость слоя — два разных\n * флага: первый снимается только по концу leave-анимации панели.\n *\n * Видимость включается **в том же такте**, что и монтирование: содержимое окна\n * обязано появляться в DOM сразу после открытия — на этом стоит и код\n * потребителя, и внутренние компоненты, которые ищут в панели свои элементы.\n * Enter-анимацию это не отменяет: `<Transition appear>` проигрывает её и на\n * первом рендере ребёнка.\n */\nexport function useOverlayPresence(open: Ref<boolean>): OverlayPresence {\n const mounted = ref(open.value)\n const visible = ref(open.value)\n\n watch(open, (isOpen) => {\n if (isOpen)\n mounted.value = true\n visible.value = isOpen\n })\n\n function onPanelAfterLeave(): void {\n // Пока панель уезжала, слой могли открыть заново — тогда размонтировать\n // нечего и незачем.\n if (!open.value)\n mounted.value = false\n }\n\n return { mounted, visible, onPanelAfterLeave }\n}\n","/**\n * useScrollLock — общий **reference-counted** lock скролла `<body>` для оверлеев\n * (GrModal, GrDrawer, GrImageViewer).\n *\n * Зачем счётчик: состояние `<body>` принадлежит не оверлею, а их совокупности.\n * Держи каждый свой `savedBodyOverflow`, и закрытие A при ещё открытой B вернуло\n * бы странице прокрутку под открытым окном — закрывают не всегда в порядке\n * открытия. Поэтому состояние сохраняется один раз (переход счётчика 0→1) и\n * восстанавливается только когда закрыт последний оверлей (1→0).\n *\n * Плюс компенсация ширины скроллбара: при `overflow: hidden` скроллбар исчезает и\n * контент дёргается вправо — добавляем `padding-right` на его ширину.\n *\n * Отдельная ветка — iOS Safari: там `overflow: hidden` фон не держит, страница\n * продолжает «резинить» под открытым окном. Лечится тем же, чем лечил\n * HeadlessUI (`handleIOSLocking`): гасим `touchmove` вне скроллящихся областей\n * оверлея и возвращаем позицию прокрутки после снятия лока.\n */\n\ninterface SavedBodyStyle {\n overflow: string\n paddingRight: string\n overscrollBehavior: string\n}\n\nlet lockCount = 0\nlet saved: SavedBodyStyle | null = null\nlet savedScrollY = 0\nlet releaseTouchGuard: (() => void) | null = null\n\n/**\n * iOS определяем по платформе, а не по ширине экрана: лечение специфично для\n * движка, а не для размера. `maxTouchPoints` отличает iPadOS, который со времён\n * 13-й версии представляется десктопным Mac.\n */\nfunction isIOS(): boolean {\n if (typeof navigator === 'undefined')\n return false\n return /iP(?:hone|ad|od)/.test(navigator.platform)\n || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)\n}\n\n/** Прокручиваемая область оверлея — единственное, чему `touchmove` разрешён. */\nfunction isScrollableOverlayArea(target: EventTarget | null): boolean {\n if (!(target instanceof Element))\n return false\n\n for (let node: Element | null = target; node; node = node.parentElement) {\n if (!(node instanceof HTMLElement))\n continue\n if (node.hasAttribute('data-gr-overlay-root'))\n return true\n }\n\n return false\n}\n\nfunction applyTouchGuard(): void {\n if (!isIOS())\n return\n\n const onTouchMove = (event: TouchEvent): void => {\n // Внутри оверлея скролл законен (длинное окно, лента изображений), а всё\n // остальное — это и есть фон, который лок обязан держать.\n if (isScrollableOverlayArea(event.target))\n return\n if (event.cancelable)\n event.preventDefault()\n }\n\n document.addEventListener('touchmove', onTouchMove, { passive: false })\n releaseTouchGuard = () => document.removeEventListener('touchmove', onTouchMove)\n}\n\nfunction getScrollbarWidth(): number {\n return window.innerWidth - document.documentElement.clientWidth\n}\n\nfunction applyLock(): void {\n const body = document.body\n saved = {\n overflow: body.style.overflow,\n paddingRight: body.style.paddingRight,\n overscrollBehavior: body.style.overscrollBehavior,\n }\n savedScrollY = window.scrollY\n\n // Ширину скроллбара считаем ДО `overflow: hidden` (после — `clientWidth` меняется).\n const scrollbarWidth = getScrollbarWidth()\n\n body.style.overflow = 'hidden'\n // Прокрутка, «доехавшая» до края внутри окна, не должна продолжаться фоном.\n body.style.overscrollBehavior = 'contain'\n if (scrollbarWidth > 0) {\n const currentPadding = Number.parseFloat(getComputedStyle(body).paddingRight) || 0\n body.style.paddingRight = `${currentPadding + scrollbarWidth}px`\n }\n\n applyTouchGuard()\n}\n\nfunction releaseLock(): void {\n releaseTouchGuard?.()\n releaseTouchGuard = null\n\n if (!saved)\n return\n document.body.style.overflow = saved.overflow\n document.body.style.paddingRight = saved.paddingRight\n document.body.style.overscrollBehavior = saved.overscrollBehavior\n saved = null\n\n // iOS сдвигает страницу, пока фон заблокирован; возвращаем её на место.\n if (isIOS() && window.scrollY !== savedScrollY)\n window.scrollTo(0, savedScrollY)\n}\n\n/**\n * Возвращает пару `lock`/`unlock`, идемпотентную **в пределах одного инстанса**\n * (повторный `lock()` без `unlock()` не увеличивает глобальный счётчик дважды).\n * Глобальный счётчик разделяется всеми оверлеями.\n */\nexport function useScrollLock() {\n const isClient = typeof window !== 'undefined'\n let held = false\n\n function lock(): void {\n if (!isClient || held)\n return\n held = true\n lockCount += 1\n if (lockCount === 1)\n applyLock()\n }\n\n function unlock(): void {\n if (!isClient || !held)\n return\n held = false\n lockCount = Math.max(0, lockCount - 1)\n if (lockCount === 0)\n releaseLock()\n }\n\n return { lock, unlock }\n}\n\n/** Тестовая/служебная очистка глобального состояния. */\nexport function resetScrollLock(): void {\n lockCount = 0\n if (typeof document !== 'undefined')\n releaseLock()\n saved = null\n}\n","import type { ComputedRef, Ref } from 'vue'\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\n\nimport { useGrThemeAttrs } from '../../components/GrConfigProvider/context'\nimport { useFocusTrap } from '../useFocusTrap'\nimport { useOverlayLayer } from '../useOverlayLayer'\nimport { usePortalTarget } from '../usePortalTarget'\nimport { useInertOthers } from './useInertOthers'\nimport { useOverlayPresence } from './useOverlayPresence'\nimport { useScrollLock } from './useScrollLock'\n\n/**\n * Модальный слой целиком: одна сборка на весь пакет.\n *\n * Модальность собиралась вручную в трёх компонентах (`GrModal`, `GrDrawer`,\n * `GrImageViewer`) из шести примитивов, и сборка повторялась дословно. Дело не\n * в объёме: в этой проводке сидят инварианты, каждый из которых при пропуске\n * даёт тихий баг доступности, а не заметную поломку. Здесь они собраны в одном\n * месте — забыть часть модального слоя больше нельзя.\n *\n * Что именно неочевидно:\n *\n * - **`restoreFocus: false` у ловушки.** Возврат фокуса принадлежит стеку\n * слоёв: у него правило строже — вернуть, только если на момент закрытия\n * фокус ещё был внутри слоя. Две системы возврата подрались бы за фокус.\n * - **`containers: layer.rootsAbove`.** Панель селекта, открытого ВНУТРИ окна,\n * телепортирована в `body` и лежит вне поддерева панели. Без этого списка\n * ловушка считала бы её «снаружи» и отбирала фокус.\n * - **Ловушка и `inert` работают, только пока слой верхний.** Иначе нижнее\n * окно ворует фокус у открытого поверх него, а `inert` гасит собственную\n * панель вместе со страницей.\n * - **Клик по подложке считается только тот, что на ней и начался.** Иначе\n * выделение текста мышью или перетаскивание, отпущенное за пределами панели,\n * закрывает слой.\n *\n * Чего композабл НЕ делает: не рисует разметку. Корневой `div`, подложка и\n * панель у трёх компонентов различаются по существу (обёртки скролла у окна,\n * сквозные клики у немодальной панели, слои хрома у просмотрщика), а классами\n * общий модуль владеть не вправе — пресет гранулярности подмешивает safelist\n * только по директориям выбранных компонентов.\n */\nexport interface UseModalOverlayOptions {\n /**\n * Модальный ли слой: блокирует страницу, держит фокус, гасит нижние окна.\n * Геттером — там, где модальность включается пропом (`GrDrawer`).\n */\n modal?: boolean | (() => boolean)\n /** Закрывать ли по Esc. Геттер — проп компонента реактивен. */\n closeOnEscape?: () => boolean\n /** Закрывать ли по клику в подложку; читается обработчиками `backdrop`. */\n closeOnBackdrop?: () => boolean\n /** Панель слоя: контейнер ловушки фокуса и её запасной приёмник. */\n panel: Ref<HTMLElement | null>\n /**\n * Свой корень слоя. У якорного оверлея (`GrPopover`) корень и панель — один\n * элемент: в портал уезжает сама панель, отдельной обёртки нет. Не задан —\n * композабл заводит корень сам, как у центрированных окон.\n */\n root?: Ref<HTMLElement | null>\n /**\n * Точечное переопределение точки монтирования (проп `teleportTo`). По\n * умолчанию — общий портал оверлеев.\n */\n teleportTo?: () => string | HTMLElement | undefined\n /**\n * Кому отдать фокус при открытии. Не задан — первому фокусируемому внутри\n * панели (правило `useFocusTrap`): просмотрщику нужна именно кнопка тулбара,\n * а окну — сама панель, и оно передаёт её явно.\n */\n initialFocus?: () => HTMLElement | null | undefined\n /**\n * Блокировать ли скролл страницы. По умолчанию — пока слой открыт и модален:\n * немодальная панель существует ровно ради работы со страницей под ней.\n */\n lockScroll?: () => boolean\n}\n\nexport interface ModalOverlay {\n /** Корень слоя: цель `ref` в шаблоне, хозяин `inert` и корень для стека слоёв. */\n rootEl: Ref<HTMLElement | null>\n /** Смонтировано ли поддерево: держится до конца leave-анимации панели. */\n isMounted: Ref<boolean>\n /** Показан ли слой: этим переключаются `<Transition>` внутри. */\n isVisible: Ref<boolean>\n /** Верхний ли это слой сейчас. */\n isTopmost: Ref<boolean>\n /** Значение атрибута `inert` для корня: слой перестал быть верхним. */\n inertAttr: ComputedRef<true | undefined>\n portalTarget: ComputedRef<string | HTMLElement>\n teleportEnabled: ComputedRef<boolean>\n /** `data-theme` на телепортированный корень: в DOM он вне обёртки провайдера. */\n themeAttrs: ComputedRef<{ 'data-theme'?: string }>\n /** Для `@after-leave` панели. Компонент оборачивает, если ему нужен эмит `closed`. */\n onPanelAfterLeave: () => void\n /**\n * Обработчики подложки вместе с guard'ом «клик начался на ней». Имена — под\n * `v-on=\"backdrop\"`: объектный синтаксис ждёт имена событий, а не пропов.\n */\n backdrop: {\n mousedown: (event: MouseEvent) => void\n click: (event: MouseEvent) => void\n }\n}\n\nexport function useModalOverlay(\n open: Ref<boolean>,\n onDismiss: () => void,\n options: UseModalOverlayOptions,\n): ModalOverlay {\n const isModal = (): boolean =>\n typeof options.modal === 'function' ? options.modal() : options.modal ?? true\n\n const rootEl = options.root ?? ref<HTMLElement | null>(null)\n const isTopmost = ref(true)\n\n const inertAttr = computed(() => (open.value && isModal() && !isTopmost.value ? true : undefined))\n\n // Телепорт включается только ПОСЛЕ монтирования: иначе первый клиентский\n // рендер не совпадёт с серверным и сломается гидрация.\n const { target: portalTarget, enabled: teleportEnabled } = usePortalTarget(options.teleportTo)\n\n // Панель уезжает в `body`, то есть вне обёртки провайдера, и `data-theme` с\n // неё не наследуется. В дереве компонентов слой остаётся внутри — `inject`\n // доходит, и тему он ставит себе сам.\n const themeAttrs = useGrThemeAttrs()\n\n const { mounted: isMounted, visible: isVisible, onPanelAfterLeave } = useOverlayPresence(open)\n\n const layer = useOverlayLayer(open, onDismiss, {\n modal: isModal,\n closeOnEscape: options.closeOnEscape,\n onTopmostChange: (value) => { isTopmost.value = value },\n root: rootEl,\n })\n\n useFocusTrap(options.panel, {\n active: () => open.value && isModal() && isTopmost.value,\n initialFocus: options.initialFocus,\n fallbackFocus: () => options.panel.value,\n containers: layer.rootsAbove,\n restoreFocus: false,\n })\n\n useInertOthers(rootEl, () => open.value && isModal() && isTopmost.value)\n\n // Общий reference-counted lock: несколько открытых оверлеев снимают блокировку\n // независимо от порядка закрытия, ширина скроллбара компенсируется.\n const { lock, unlock } = useScrollLock()\n\n watch(\n () => options.lockScroll?.() ?? (open.value && isModal()),\n (locked) => {\n if (locked)\n lock()\n else unlock()\n },\n { immediate: true },\n )\n\n onBeforeUnmount(unlock)\n\n let pointerDownOnBackdrop = false\n\n return {\n rootEl,\n isMounted,\n isVisible,\n isTopmost,\n inertAttr,\n portalTarget,\n teleportEnabled,\n themeAttrs,\n onPanelAfterLeave,\n backdrop: {\n mousedown(event: MouseEvent): void {\n pointerDownOnBackdrop = event.target === event.currentTarget\n },\n click(event: MouseEvent): void {\n const startedHere = pointerDownOnBackdrop\n pointerDownOnBackdrop = false\n\n if (!startedHere || event.target !== event.currentTarget)\n return\n if (options.closeOnBackdrop?.() === false)\n return\n\n onDismiss()\n },\n },\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"usePortalTarget-C0p-mhaM.js","names":[],"sources":["../../src/composables/internal/useTeleportEnabled.ts","../../src/composables/usePortalTarget.ts"],"sourcesContent":["import { onMounted, readonly, ref, type Ref } from 'vue'\n\n/**\n * Можно ли телепортировать содержимое прямо сейчас.\n *\n * Возвращает `false` на сервере И на первом клиентском рендере, `true` — сразу\n * после монтирования. Использовать так:\n *\n * ```vue\n * <teleport to=\"body\" :disabled=\"!teleportEnabled\">…</teleport>\n * ```\n *\n * ## Зачем, если есть `typeof window !== 'undefined'`\n *\n * Проверка окружения выглядит достаточной, но именно она и ломала гидрацию.\n * `isClient` вычисляется в `setup`, поэтому получалось так:\n *\n * - сервер: `window` нет → телепорт выключен → содержимое остаётся НА МЕСТЕ\n * (`:disabled` означает «рендерить на месте», а не «не рендерить»);\n * - клиент, первый рендер: `window` есть → телепорт включён → содержимое сразу\n * уезжает в `body`.\n *\n * Структуры не совпадают, Vue сообщает `Hydration node mismatch`. В лучшем\n * случае это лишний варнинг и осиротевшая копия панели в DOM; в худшем —\n * панель «прилипает» к контейнеру приложения и пишет в него свои стили\n * (`position: fixed` + `display: none` у закрытой панели), после чего страница\n * исчезает целиком.\n *\n * Флаг, поднимаемый в `onMounted`, снимает проблему по построению: **первый**\n * клиентский рендер повторяет серверный, а телепорт включается уже после\n * гидрации. Для компонентов без SSR это ничего не меняет: панели закрыты\n * (`v-show`/`v-if`), а `onMounted` срабатывает в том же тике.\n *\n * Стенд, на котором это воспроизводится и проверяется, — `apps/playground-ssr`.\n */\nexport function useTeleportEnabled(): Readonly<Ref<boolean>> {\n const enabled = ref(false)\n\n onMounted(() => {\n enabled.value = true\n })\n\n return readonly(enabled)\n}\n","import type { ComputedRef } from 'vue'\nimport { computed } from 'vue'\n\nimport { useGrConfig } from '../components/GrConfigProvider/context'\nimport { ensurePortalRoot } from './internal/portalRoot'\nimport { useTeleportEnabled } from './internal/useTeleportEnabled'\n\nexport interface PortalTarget {\n /** Куда телепортировать: элемент портала либо селектор из конфига. */\n target: ComputedRef<string | HTMLElement>\n /**\n * Можно ли телепортировать прямо сейчас. `false` на сервере и на первом\n * клиентском рендере — иначе разметка разъедется на гидрации.\n */\n enabled: ComputedRef<boolean>\n}\n\n/**\n * Куда монтируются оверлеи пакета.\n *\n * Приоритет: локальное переопределение компонента (`teleportTo`) →\n * `portalTarget` из `GrConfigProvider` → общий корень `#gr-portal`, который\n * создаётся при первом обращении.\n *\n * ```vue\n * const { target, enabled } = usePortalTarget(() => props.teleportTo)\n *\n * <teleport :to=\"target\" :disabled=\"!enabled\">…</teleport>\n * ```\n *\n * Композабл публичный: свой оверлей потребителя должен уезжать туда же, куда и\n * оверлеи библиотеки, — иначе он окажется вне ветки портала и будет помечен\n * `inert` вместе со страницей, как только откроется модалка.\n */\nexport function usePortalTarget(local?: () => string | HTMLElement | undefined): PortalTarget {\n const config = useGrConfig()\n const teleportEnabled = useTeleportEnabled()\n\n const target = computed<string | HTMLElement>(() => {\n const explicit = local?.() ?? config.portalTarget?.value\n if (explicit) return explicit\n\n // Корень создаётся лениво и только на клиенте; до монтирования телепорт\n // выключен, поэтому `body` здесь — недостижимый запасной вариант, а не\n // рабочая ветка.\n return ensurePortalRoot() ?? 'body'\n })\n\n return { target, enabled: computed(() => teleportEnabled.value) }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,SAAgB,qBAA6C;CAC3D,MAAM,UAAU,IAAI,KAAK;CAEzB,gBAAgB;EACd,QAAQ,QAAQ;CAClB,CAAC;CAED,OAAO,SAAS,OAAO;AACzB;;;;;;;;;;;;;;;;;;;;ACTA,SAAgB,gBAAgB,OAA8D;CAC5F,MAAM,SAAS,YAAY;CAC3B,MAAM,kBAAkB,mBAAmB;CAY3C,OAAO;EAAE,QAVM,eAAqC;GAClD,MAAM,WAAW,QAAQ,KAAK,OAAO,cAAc;GACnD,IAAI,UAAU,OAAO;GAKrB,OAAO,iBAAiB,KAAK;EAC/B,CAES;EAAQ,SAAS,eAAe,gBAAgB,KAAK;CAAE;AAClE"}
1
+ {"version":3,"file":"usePortalTarget-C0p-mhaM.js","names":[],"sources":["../../src/composables/internal/useTeleportEnabled.ts","../../src/composables/usePortalTarget.ts"],"sourcesContent":["import { onMounted, readonly, ref, type Ref } from 'vue'\n\n/**\n * Можно ли телепортировать содержимое прямо сейчас.\n *\n * Возвращает `false` на сервере И на первом клиентском рендере, `true` — сразу\n * после монтирования. Использовать так:\n *\n * ```vue\n * <teleport to=\"body\" :disabled=\"!teleportEnabled\">…</teleport>\n * ```\n *\n * ## Зачем, если есть `typeof window !== 'undefined'`\n *\n * Проверка окружения выглядит достаточной, но именно она и ломала гидрацию.\n * `isClient` вычисляется в `setup`, поэтому получалось так:\n *\n * - сервер: `window` нет → телепорт выключен → содержимое остаётся НА МЕСТЕ\n * (`:disabled` означает «рендерить на месте», а не «не рендерить»);\n * - клиент, первый рендер: `window` есть → телепорт включён → содержимое сразу\n * уезжает в `body`.\n *\n * Структуры не совпадают, Vue сообщает `Hydration node mismatch`. В лучшем\n * случае это лишний варнинг и осиротевшая копия панели в DOM; в худшем —\n * панель «прилипает» к контейнеру приложения и пишет в него свои стили\n * (`position: fixed` + `display: none` у закрытой панели), после чего страница\n * исчезает целиком.\n *\n * Флаг, поднимаемый в `onMounted`, снимает проблему по построению: **первый**\n * клиентский рендер повторяет серверный, а телепорт включается уже после\n * гидрации. Для компонентов без SSR это ничего не меняет: панели закрыты\n * (`v-show`/`v-if`), а `onMounted` срабатывает в том же тике.\n *\n * Стенд, на котором это воспроизводится и проверяется, — `apps/playground-ssr`.\n */\nexport function useTeleportEnabled(): Readonly<Ref<boolean>> {\n const enabled = ref(false)\n\n onMounted(() => {\n enabled.value = true\n })\n\n return readonly(enabled)\n}\n","import type { ComputedRef } from 'vue'\nimport { computed } from 'vue'\n\nimport { useGrConfig } from '../components/GrConfigProvider/context'\nimport { ensurePortalRoot } from './internal/portalRoot'\nimport { useTeleportEnabled } from './internal/useTeleportEnabled'\n\nexport interface PortalTarget {\n /** Куда телепортировать: элемент портала либо селектор из конфига. */\n target: ComputedRef<string | HTMLElement>\n /**\n * Можно ли телепортировать прямо сейчас. `false` на сервере и на первом\n * клиентском рендере — иначе разметка разъедется на гидрации.\n */\n enabled: ComputedRef<boolean>\n}\n\n/**\n * Куда монтируются оверлеи пакета.\n *\n * Приоритет: локальное переопределение компонента (`teleportTo`) →\n * `portalTarget` из `GrConfigProvider` → общий корень `#gr-portal`, который\n * создаётся при первом обращении.\n *\n * ```vue\n * const { target, enabled } = usePortalTarget(() => props.teleportTo)\n *\n * <teleport :to=\"target\" :disabled=\"!enabled\">…</teleport>\n * ```\n *\n * Композабл публичный: свой оверлей потребителя должен уезжать туда же, куда и\n * оверлеи библиотеки, — иначе он окажется вне ветки портала и будет помечен\n * `inert` вместе со страницей, как только откроется модалка.\n */\nexport function usePortalTarget(local?: () => string | HTMLElement | undefined): PortalTarget {\n const config = useGrConfig()\n const teleportEnabled = useTeleportEnabled()\n\n const target = computed<string | HTMLElement>(() => {\n const explicit = local?.() ?? config.portalTarget?.value\n if (explicit)\n return explicit\n\n // Корень создаётся лениво и только на клиенте; до монтирования телепорт\n // выключен, поэтому `body` здесь — недостижимый запасной вариант, а не\n // рабочая ветка.\n return ensurePortalRoot() ?? 'body'\n })\n\n return { target, enabled: computed(() => teleportEnabled.value) }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,SAAgB,qBAA6C;CAC3D,MAAM,UAAU,IAAI,KAAK;CAEzB,gBAAgB;EACd,QAAQ,QAAQ;CAClB,CAAC;CAED,OAAO,SAAS,OAAO;AACzB;;;;;;;;;;;;;;;;;;;;ACTA,SAAgB,gBAAgB,OAA8D;CAC5F,MAAM,SAAS,YAAY;CAC3B,MAAM,kBAAkB,mBAAmB;CAa3C,OAAO;EAAE,QAXM,eAAqC;GAClD,MAAM,WAAW,QAAQ,KAAK,OAAO,cAAc;GACnD,IAAI,UACF,OAAO;GAKT,OAAO,iBAAiB,KAAK;EAC/B,CAES;EAAQ,SAAS,eAAe,gBAAgB,KAAK;CAAE;AAClE"}