@feugene/granularity 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/dist/chunks/chevron-left-D8AUBAM7.js +23 -0
  2. package/dist/chunks/chevron-left-D8AUBAM7.js.map +1 -0
  3. package/dist/chunks/{chevron-right-BfqMVpbB.js → chevron-right-C6Z01h0X.js} +1 -22
  4. package/dist/chunks/chevron-right-C6Z01h0X.js.map +1 -0
  5. package/dist/chunks/config-8Pcutu1j2.js.map +1 -0
  6. package/dist/chunks/{config-K0C2tcLB.js → config-9PYRZlKE.js} +1 -1
  7. package/dist/chunks/{config-K0C2tcLB.js.map → config-9PYRZlKE.js.map} +1 -1
  8. package/dist/chunks/{config-DEvsMTQE.js → config-BJ7Bv_G2.js} +1 -3
  9. package/dist/chunks/config-BJ7Bv_G2.js.map +1 -0
  10. package/dist/chunks/config-BKvX0q102.js.map +1 -0
  11. package/dist/chunks/{config-gvq_hWNM.js → config-Be9k9bOf.js} +1 -7
  12. package/dist/chunks/config-Be9k9bOf.js.map +1 -0
  13. package/dist/chunks/{config-DpqOnJiz2.js → config-CDat1wXf2.js} +1 -1
  14. package/dist/chunks/config-CDat1wXf2.js.map +1 -0
  15. package/dist/chunks/config-CY0_6cMi2.js.map +1 -0
  16. package/dist/chunks/config-D94Z9po52.js.map +1 -0
  17. package/dist/chunks/{config-D2aNP7Es.js → config-DAX8Deh1.js} +1 -3
  18. package/dist/chunks/config-DAX8Deh1.js.map +1 -0
  19. package/dist/chunks/config-DCFvmHEv.js.map +1 -0
  20. package/dist/chunks/config-DKdlqXiY.js.map +1 -1
  21. package/dist/chunks/config-Rw9OyW702.js.map +1 -0
  22. package/dist/chunks/generated-BRkbhyxk.js +3270 -0
  23. package/dist/chunks/generated-BRkbhyxk.js.map +1 -0
  24. package/dist/chunks/{granular-provider-B9nXQCMu.js → granular-provider-C6uAujdJ.js} +12 -12
  25. package/dist/chunks/{granular-provider-B9nXQCMu.js.map → granular-provider-C6uAujdJ.js.map} +1 -1
  26. package/dist/chunks/grip-vertical-lRha2S7q.js +16 -0
  27. package/dist/chunks/grip-vertical-lRha2S7q.js.map +1 -0
  28. package/dist/chunks/icon-Dag2tIMs.js +21 -0
  29. package/dist/chunks/icon-Dag2tIMs.js.map +1 -0
  30. package/dist/chunks/{portalRoot-CWD4zHBm.js → portalRoot-CVhCgdG6.js} +10 -1
  31. package/dist/chunks/{portalRoot-CWD4zHBm.js.map → portalRoot-CVhCgdG6.js.map} +1 -1
  32. package/dist/chunks/{useModalOverlay-BTq-0ImG.js → useModalOverlay-DrQVYwwA.js} +1 -1
  33. package/dist/chunks/{useModalOverlay-BTq-0ImG.js.map → useModalOverlay-DrQVYwwA.js.map} +1 -1
  34. package/dist/chunks/{usePortalTarget-RyzEgNTb.js → usePortalTarget-Cn-Wiz5d.js} +1 -1
  35. package/dist/chunks/{usePortalTarget-RyzEgNTb.js.map → usePortalTarget-Cn-Wiz5d.js.map} +1 -1
  36. package/dist/components/GrAutocomplete/chunks/{GrAutocomplete-CPEQxmLZ.js → GrAutocomplete-gY4Xopza.js} +32 -24
  37. package/dist/components/GrAutocomplete/chunks/GrAutocomplete-gY4Xopza.js.map +1 -0
  38. package/dist/components/GrAutocomplete/index.js +1 -1
  39. package/dist/components/GrAutocomplete/styles.css +1 -1
  40. package/dist/components/GrBreadcrumbs/chunks/{GrBreadcrumbs-BCNGDS6k.js → GrBreadcrumbs-DS_-PBVt.js} +8 -7
  41. package/dist/components/GrBreadcrumbs/chunks/GrBreadcrumbs-DS_-PBVt.js.map +1 -0
  42. package/dist/components/GrBreadcrumbs/index.js +1 -1
  43. package/dist/components/GrCollapse/chunks/{GrCollapse-B2Fc6A9r.js.map → GrCollapse-Kk0DE8tb.js.map} +1 -1
  44. package/dist/components/GrCollapse/index.js +1 -1
  45. package/dist/components/GrColorPicker/chunks/{GrColorPicker-DAdQ-Gc3.js → GrColorPicker-CkBTzkqn.js} +1 -1
  46. package/dist/components/GrColorPicker/chunks/{GrColorPicker-DAdQ-Gc3.js.map → GrColorPicker-CkBTzkqn.js.map} +1 -1
  47. package/dist/components/GrColorPicker/index.js +1 -1
  48. package/dist/components/GrCommandPalette/chunks/{GrCommandPalette-DMeSqkEv.js → GrCommandPalette-BugTlGdj.js} +37 -11
  49. package/dist/components/GrCommandPalette/chunks/GrCommandPalette-BugTlGdj.js.map +1 -0
  50. package/dist/components/GrCommandPalette/index.js +2 -2
  51. package/dist/components/GrCommandPalette/styles.css +1 -1
  52. package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-hUTi-GdJ.js → GrConfirmDialog-Ds5T0GiR.js} +1 -1
  53. package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-hUTi-GdJ.js.map → GrConfirmDialog-Ds5T0GiR.js.map} +1 -1
  54. package/dist/components/GrConfirmDialog/index.js +1 -1
  55. package/dist/components/GrDataTable/chunks/{GrDataTable-DzaGLG3A.js → GrDataTable-CYe2A2WA.js} +9 -7
  56. package/dist/components/GrDataTable/chunks/GrDataTable-CYe2A2WA.js.map +1 -0
  57. package/dist/components/GrDataTable/index.js +2 -2
  58. package/dist/components/GrDialog/chunks/{GrDialog-MD9Fzy9C.js → GrDialog-C3tYaA8v.js} +1 -1
  59. package/dist/components/GrDialog/chunks/{GrDialog-MD9Fzy9C.js.map → GrDialog-C3tYaA8v.js.map} +1 -1
  60. package/dist/components/GrDialog/index.js +1 -1
  61. package/dist/components/GrDialogService/chunks/{GrDialogService-DtgUijMj.js → GrDialogService-CTBiiJDI.js} +3 -3
  62. package/dist/components/GrDialogService/chunks/{GrDialogService-DtgUijMj.js.map → GrDialogService-CTBiiJDI.js.map} +1 -1
  63. package/dist/components/GrDialogService/index.js +1 -1
  64. package/dist/components/GrDivider/chunks/{GrDivider-B2Xwc_QO.js → GrDivider-xxq1_fIl.js} +1 -1
  65. package/dist/components/GrDivider/chunks/{GrDivider-B2Xwc_QO.js.map → GrDivider-xxq1_fIl.js.map} +1 -1
  66. package/dist/components/GrDivider/index.js +2 -2
  67. package/dist/components/GrDrawer/chunks/{GrDrawer-DWHdZZzW.js → GrDrawer-BSSuiNHU.js} +2 -2
  68. package/dist/components/GrDrawer/chunks/{GrDrawer-DWHdZZzW.js.map → GrDrawer-BSSuiNHU.js.map} +1 -1
  69. package/dist/components/GrDrawer/index.js +2 -2
  70. package/dist/components/GrDropdown/chunks/{GrDropdown-0tJK01Kq.js → GrDropdown-Bh6UK2M5.js} +1 -1
  71. package/dist/components/GrDropdown/chunks/{GrDropdown-0tJK01Kq.js.map → GrDropdown-Bh6UK2M5.js.map} +1 -1
  72. package/dist/components/GrDropdown/index.js +1 -1
  73. package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-VPPrpCoA.js → GrDropdownMenu-LyfEFUbg.js} +2 -2
  74. package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-VPPrpCoA.js.map → GrDropdownMenu-LyfEFUbg.js.map} +1 -1
  75. package/dist/components/GrDropdownMenu/index.js +2 -2
  76. package/dist/components/GrImageViewer/chunks/{GrImageViewer-DE7MBbF8.js → GrImageViewer-DfD4euH5.js} +3 -2
  77. package/dist/components/GrImageViewer/chunks/{GrImageViewer-DE7MBbF8.js.map → GrImageViewer-DfD4euH5.js.map} +1 -1
  78. package/dist/components/GrImageViewer/index.js +1 -1
  79. package/dist/components/GrModal/chunks/{GrModal-BbhcsDQ8.js → GrModal-CbMMr6W1.js} +1 -1
  80. package/dist/components/GrModal/chunks/{GrModal-BbhcsDQ8.js.map → GrModal-CbMMr6W1.js.map} +1 -1
  81. package/dist/components/GrModal/index.js +1 -1
  82. package/dist/components/GrNumberInput/chunks/{GrNumberInput-CZOyPz4J.js → GrNumberInput-B5CHZDBj.js} +2 -1
  83. package/dist/components/GrNumberInput/chunks/{GrNumberInput-CZOyPz4J.js.map → GrNumberInput-B5CHZDBj.js.map} +1 -1
  84. package/dist/components/GrNumberInput/index.js +1 -1
  85. package/dist/components/GrPagination/chunks/{GrPagination-waKhxBP7.js → GrPagination-BM4FrD6Q.js} +1 -1
  86. package/dist/components/GrPagination/chunks/{GrPagination-waKhxBP7.js.map → GrPagination-BM4FrD6Q.js.map} +1 -1
  87. package/dist/components/GrPagination/index.js +1 -1
  88. package/dist/components/GrPopover/chunks/{GrPopover-DuqeoFaI.js → GrPopover-DMH4Jjst.js} +1 -1
  89. package/dist/components/GrPopover/chunks/{GrPopover-DuqeoFaI.js.map → GrPopover-DMH4Jjst.js.map} +1 -1
  90. package/dist/components/GrPopover/index.js +1 -1
  91. package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-JVVqBsPY.js → GrPromptDialog-Pwr9NPxK.js} +1 -1
  92. package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-JVVqBsPY.js.map → GrPromptDialog-Pwr9NPxK.js.map} +1 -1
  93. package/dist/components/GrPromptDialog/index.js +1 -1
  94. package/dist/components/GrRating/chunks/{GrRating-LarIiwJL.js → GrRating-DVFH3lYu.js} +8 -7
  95. package/dist/components/GrRating/chunks/GrRating-DVFH3lYu.js.map +1 -0
  96. package/dist/components/GrRating/index.js +1 -1
  97. package/dist/components/GrSelect/chunks/{GrSelect-DAdu044K.js → GrSelect-B-Voni7D.js} +65 -52
  98. package/dist/components/GrSelect/chunks/GrSelect-B-Voni7D.js.map +1 -0
  99. package/dist/components/GrSelect/index.js +1 -1
  100. package/dist/components/GrSelect/styles.css +1 -1
  101. package/dist/components/GrSidebar/chunks/{GrSidebar-DRGnnViw.js → GrSidebar-i7AxjhY_.js} +3 -2
  102. package/dist/components/GrSidebar/chunks/{GrSidebar-DRGnnViw.js.map → GrSidebar-i7AxjhY_.js.map} +1 -1
  103. package/dist/components/GrSidebar/index.js +2 -2
  104. package/dist/components/GrSkeleton/index.js +1 -1
  105. package/dist/components/GrSortableList/chunks/{GrSortableList-BefVfWKh.js → GrSortableList-XB7mXM1W.js} +6 -5
  106. package/dist/components/GrSortableList/chunks/GrSortableList-XB7mXM1W.js.map +1 -0
  107. package/dist/components/GrSortableList/index.js +2 -2
  108. package/dist/components/GrStatistic/chunks/{GrStatistic-J4qLs3TH.js → GrStatistic-DOsM6s5V.js} +54 -7
  109. package/dist/components/GrStatistic/chunks/GrStatistic-DOsM6s5V.js.map +1 -0
  110. package/dist/components/GrStatistic/index.js +2 -2
  111. package/dist/components/GrTabs/chunks/{GrTabs-TeXaU5Uh.js → GrTabs-f3-oQenu.js} +5 -4
  112. package/dist/components/GrTabs/chunks/GrTabs-f3-oQenu.js.map +1 -0
  113. package/dist/components/GrTabs/index.js +1 -1
  114. package/dist/components/GrToaster/chunks/{GrToaster-Ct-39hga.js → GrToaster-Dqja0Lf9.js} +1 -1
  115. package/dist/components/GrToaster/chunks/{GrToaster-Ct-39hga.js.map → GrToaster-Dqja0Lf9.js.map} +1 -1
  116. package/dist/components/GrToaster/index.js +1 -1
  117. package/dist/components/GrTooltip/chunks/{GrTooltip-xAcqs0bP.js → GrTooltip-Cn08heWz.js} +1 -1
  118. package/dist/components/GrTooltip/chunks/{GrTooltip-xAcqs0bP.js.map → GrTooltip-Cn08heWz.js.map} +1 -1
  119. package/dist/components/GrTooltip/index.js +1 -1
  120. package/dist/components/GrTree/chunks/{GrTree-CaYK8dGZ.js → GrTree-BR9a2pay.js} +25 -20
  121. package/dist/components/GrTree/chunks/GrTree-BR9a2pay.js.map +1 -0
  122. package/dist/components/GrTree/index.js +2 -2
  123. package/dist/components/GrTree/styles.css +1 -1
  124. package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-DZp7CqqW.js → GrTreeSelect--muUeB0v.js} +27 -13
  125. package/dist/components/GrTreeSelect/chunks/GrTreeSelect--muUeB0v.js.map +1 -0
  126. package/dist/components/GrTreeSelect/index.js +2 -2
  127. package/dist/composables/useAnnouncer.js +1 -1
  128. package/dist/composables/usePortalTarget.js +1 -1
  129. package/dist/directives/loading.js +1 -1
  130. package/dist/granular-provider-node.js +1 -1
  131. package/dist/granular-provider.js +1 -1
  132. package/dist/index.js +41 -41
  133. package/dist/styles/themes/dark.css +6 -1
  134. package/dist/testing.js +199 -0
  135. package/dist/testing.js.map +1 -0
  136. package/dist/theme-apply.js +33 -0
  137. package/dist/theme-apply.js.map +1 -0
  138. package/dist/theme.js +501 -0
  139. package/dist/theme.js.map +1 -0
  140. package/dist/tokens.js +1 -3215
  141. package/dist/types/src/components/GrBreadcrumbs/grBreadcrumbsStyles.d.ts +5 -2
  142. package/dist/types/src/components/GrCommandPalette/GrCommandPalette.vue.d.ts +6 -6
  143. package/dist/types/src/components/GrCommandPalette/filtering.d.ts +6 -2
  144. package/dist/types/src/components/GrDataTable/grDataTableStyles.d.ts +0 -1
  145. package/dist/types/src/components/GrRating/GrRating.vue.d.ts +9 -4
  146. package/dist/types/src/components/GrSortableList/grSortableListStyles.d.ts +0 -1
  147. package/dist/types/src/components/GrStatistic/GrStatistic.vue.d.ts +12 -9
  148. package/dist/types/src/components/GrStatistic/grStatisticStyles.d.ts +0 -1
  149. package/dist/types/src/components/GrTabs/GrTabs.vue.d.ts +6 -2
  150. package/dist/types/src/components/GrTree/grTreeProps.d.ts +11 -4
  151. package/dist/types/src/components/GrTree/safelist.d.ts +1 -1
  152. package/dist/types/src/components/shared/icon.d.ts +13 -0
  153. package/dist/types/src/testing/announcer.d.ts +10 -0
  154. package/dist/types/src/testing/context.d.ts +36 -0
  155. package/dist/types/src/testing/env.d.ts +25 -0
  156. package/dist/types/src/testing/geometry.d.ts +23 -0
  157. package/dist/types/src/testing/index.d.ts +12 -0
  158. package/dist/types/src/testing/pointer.d.ts +31 -0
  159. package/dist/types/src/theme/apply.d.ts +27 -0
  160. package/dist/types/src/theme/color.d.ts +35 -0
  161. package/dist/types/src/theme/extendTheme.d.ts +63 -0
  162. package/dist/types/src/theme/index.d.ts +22 -0
  163. package/dist/types/src/theme/roles.d.ts +16 -0
  164. package/dist/types/src/theme/tone.d.ts +32 -0
  165. package/dist/types/src/theme/validate.d.ts +16 -0
  166. package/dist/types/src/tokens/types.d.ts +8 -0
  167. package/dist/web-types.json +14 -11
  168. package/package.json +17 -2
  169. package/tokens/themes/dark.json +2 -1
  170. package/dist/chunks/chevron-right-BfqMVpbB.js.map +0 -1
  171. package/dist/chunks/config-8Pcutu1j.js.map +0 -1
  172. package/dist/chunks/config-BKvX0q10.js.map +0 -1
  173. package/dist/chunks/config-CY0_6cMi.js.map +0 -1
  174. package/dist/chunks/config-D2aNP7Es.js.map +0 -1
  175. package/dist/chunks/config-D94Z9po5.js.map +0 -1
  176. package/dist/chunks/config-DCFvmHEv2.js.map +0 -1
  177. package/dist/chunks/config-DEvsMTQE.js.map +0 -1
  178. package/dist/chunks/config-DpqOnJiz2.js.map +0 -1
  179. package/dist/chunks/config-Rw9OyW70.js.map +0 -1
  180. package/dist/chunks/config-gvq_hWNM.js.map +0 -1
  181. package/dist/components/GrAutocomplete/chunks/GrAutocomplete-CPEQxmLZ.js.map +0 -1
  182. package/dist/components/GrBreadcrumbs/chunks/GrBreadcrumbs-BCNGDS6k.js.map +0 -1
  183. package/dist/components/GrCommandPalette/chunks/GrCommandPalette-DMeSqkEv.js.map +0 -1
  184. package/dist/components/GrDataTable/chunks/GrDataTable-DzaGLG3A.js.map +0 -1
  185. package/dist/components/GrRating/chunks/GrRating-LarIiwJL.js.map +0 -1
  186. package/dist/components/GrSelect/chunks/GrSelect-DAdu044K.js.map +0 -1
  187. package/dist/components/GrSortableList/chunks/GrSortableList-BefVfWKh.js.map +0 -1
  188. package/dist/components/GrStatistic/chunks/GrStatistic-J4qLs3TH.js.map +0 -1
  189. package/dist/components/GrTabs/chunks/GrTabs-TeXaU5Uh.js.map +0 -1
  190. package/dist/components/GrTree/chunks/GrTree-CaYK8dGZ.js.map +0 -1
  191. package/dist/components/GrTreeSelect/chunks/GrTreeSelect-DZp7CqqW.js.map +0 -1
  192. package/dist/tokens.js.map +0 -1
  193. package/dist/chunks/{config-8Pcutu1j.js → config-8Pcutu1j2.js} +0 -0
  194. package/dist/chunks/{config-BKvX0q10.js → config-BKvX0q102.js} +0 -0
  195. package/dist/chunks/{config-CY0_6cMi.js → config-CY0_6cMi2.js} +0 -0
  196. package/dist/chunks/{config-D94Z9po5.js → config-D94Z9po52.js} +0 -0
  197. package/dist/chunks/{config-DCFvmHEv2.js → config-DCFvmHEv.js} +0 -0
  198. package/dist/chunks/{config-Rw9OyW70.js → config-Rw9OyW702.js} +0 -0
  199. package/dist/components/GrCollapse/chunks/{GrCollapse-B2Fc6A9r.js → GrCollapse-Kk0DE8tb.js} +1 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"GrStatistic-J4qLs3TH.js","names":["$slots"],"sources":["../../../../src/components/GrStatistic/countUp.ts","../../../../src/components/GrStatistic/formatStatisticValue.ts","../../../../src/components/GrStatistic/GrStatistic.vue","../../../../src/components/GrStatistic/GrStatistic.vue"],"sourcesContent":["/**\n * Перебор чисел для `GrStatistic`: чистая арифметика кадра без Vue и таймеров.\n *\n * Вынесено отдельным модулем по той же причине, что и `formatStatisticValue`:\n * кривая и округление проверяются напрямую, без монтирования и подмены `rAF`.\n */\n\n/** Быстрый старт, мягкая остановка — та же форма, что у `--gr-ease-out`. */\nexport function easeOutCubic(t: number): number {\n const clamped = Math.min(Math.max(t, 0), 1)\n return 1 - (1 - clamped) ** 3\n}\n\n/**\n * Значение кадра на `elapsed` миллисекунде перехода `from → to`.\n *\n * Округление до `precision` обязательно: без него кадры показывали бы\n * `1283.6666666666667` там, где показатель целый, и ширина строки прыгала бы\n * даже при `tabular-nums`. Последний кадр возвращает ровно `to` — приближение\n * не должно оставлять показатель в 1283.9999.\n */\nexport function countUpFrame(\n from: number,\n to: number,\n elapsed: number,\n duration: number,\n precision?: number,\n): number {\n if (!(duration > 0) || elapsed >= duration) return to\n\n const digits = Math.min(Math.max(precision ?? 0, 0), 20)\n const value = from + (to - from) * easeOutCubic(elapsed / duration)\n\n return Number(value.toFixed(digits))\n}\n","export type GrStatisticFormatOptions = {\n /** Число знаков после запятой. `undefined` — оставить как есть. */\n precision?: number\n /** Разделитель групп разрядов. Сильнее локали. */\n groupSeparator?: string\n /** Десятичный разделитель. Сильнее локали. */\n decimalSeparator?: string\n /** BCP-47 локаль: разделители берутся из `Intl.NumberFormat`. */\n locale?: string\n}\n\nconst DEFAULT_GROUP_SEPARATOR = ' '\nconst DEFAULT_DECIMAL_SEPARATOR = '.'\n\n/**\n * Форматирует значение показателя: фиксирует точность и расставляет разделители.\n *\n * Нечисловые значения возвращаются без изменений — это позволяет показывать в\n * `GrStatistic` уже готовые строки («2 ч 15 мин», «—») без обходных пропов.\n * Вынесено из SFC отдельным модулем, чтобы тестировалось без монтирования.\n *\n * Порядок разрешения разделителей: явный проп → локаль → встроенный дефолт.\n */\nexport function formatStatisticValue(\n value: number | string,\n options: GrStatisticFormatOptions = {},\n): string {\n const { precision, groupSeparator, decimalSeparator, locale } = options\n\n const numeric = typeof value === 'number' ? value : Number(value)\n if (typeof value === 'string' && (value.trim() === '' || !Number.isFinite(numeric))) {\n return value\n }\n if (!Number.isFinite(numeric)) return String(value)\n\n if (locale) {\n return formatByLocale(numeric, locale, { precision, groupSeparator, decimalSeparator })\n }\n\n const fixed = precision === undefined ? String(numeric) : numeric.toFixed(precision)\n // `toFixed` не даёт экспоненциальной записи, а `String(число)` — может (1e21+).\n // В таком случае группировать нечего: возвращаем как есть.\n if (fixed.includes('e') || fixed.includes('E')) return fixed\n\n const negative = fixed.startsWith('-')\n const [intPart, fracPart] = (negative ? fixed.slice(1) : fixed).split('.')\n\n const separator = groupSeparator ?? DEFAULT_GROUP_SEPARATOR\n const grouped = separator\n ? intPart.replace(/\\B(?=(\\d{3})+(?!\\d))/g, separator)\n : intPart\n\n return [\n negative ? '-' : '',\n grouped,\n fracPart ? `${decimalSeparator ?? DEFAULT_DECIMAL_SEPARATOR}${fracPart}` : '',\n ].join('')\n}\n\n/**\n * `formatToParts` вместо `format`: даёт заменить отдельные разделители, оставив\n * остальные правила локали (индийская группировка, знак минуса, порядок частей).\n */\nfunction formatByLocale(\n value: number,\n locale: string,\n options: Pick<GrStatisticFormatOptions, 'precision' | 'groupSeparator' | 'decimalSeparator'>,\n): string {\n const { precision, groupSeparator, decimalSeparator } = options\n // `Intl` принимает не больше 20 знаков и падает `RangeError` на большем,\n // тогда как ручная ветка через `toFixed` пережила бы до 100.\n const digits = precision === undefined ? undefined : Math.min(Math.max(precision, 0), 20)\n\n return new Intl.NumberFormat(locale, {\n useGrouping: true,\n minimumFractionDigits: digits,\n maximumFractionDigits: digits ?? 20,\n })\n .formatToParts(value)\n .map((part) => {\n if (part.type === 'group' && groupSeparator !== undefined) return groupSeparator\n if (part.type === 'decimal' && decimalSeparator !== undefined) return decimalSeparator\n return part.value\n })\n .join('')\n}\n","<script setup lang=\"ts\">\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport { computed, markRaw, onBeforeUnmount, onMounted, ref, useSlots, watch, type Component } from 'vue'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport GrSkeleton from '../GrSkeleton/GrSkeleton.vue'\n\nimport { countUpFrame } from './countUp'\nimport { formatStatisticValue } from './formatStatisticValue'\n\nimport {\n statisticAffixSizeBySize,\n statisticPlaceholderHeightBySize,\n statisticRootClass,\n statisticTitleClass,\n statisticTitleSizeBySize,\n statisticTrendClassByTrend,\n statisticTrendIconByTrend,\n statisticTrendSizeBySize,\n statisticValueClass,\n type GrStatisticSize,\n type GrStatisticTone,\n type GrStatisticTrend,\n} from './grStatisticStyles'\n\nexport type {\n GrStatisticSize,\n GrStatisticTone,\n GrStatisticTrend,\n} from './grStatisticStyles'\nexport type { GrStatisticFormatOptions } from './formatStatisticValue'\n\n/**\n * Публичный GR-примитив «Statistic» — крупный числовой показатель с подписью,\n * префиксом/суффиксом и строкой динамики.\n *\n * Компонент презентационный: не грузит данные и не считает дельту, только\n * форматирует и подаёт. Форматирование — чистая функция `formatStatisticValue`.\n */\nexport interface GrStatisticProps {\n /** Значение показателя. Нечисловая строка выводится как есть. */\n value: number | string\n /** Подпись над значением. */\n title?: string\n /** Число знаков после запятой. */\n precision?: number\n /** Разделитель разрядов. Сильнее локали; без него и без локали — узкий пробел. */\n groupSeparator?: string\n /** Десятичный разделитель. Сильнее локали; без него и без локали — точка. */\n decimalSeparator?: string\n /**\n * BCP-47 локаль форматирования. Не задана — берётся из i18n-адаптера; нет и\n * адаптера — работают ручные разделители.\n */\n locale?: string\n /** Приписка перед значением (валюта, знак). */\n prefix?: string\n /** Приписка после значения (единица измерения, `%`). */\n suffix?: string\n /** UnoCSS-класс иконки слева от блока (например `i-lucide-users`). */\n icon?: string\n size?: GrStatisticSize\n /** Тон значения; точечно перекрывается `--gr-statistic-value-color`. */\n tone?: GrStatisticTone\n /** Направление динамики — задаёт цвет и иконку строки под значением. */\n trend?: GrStatisticTrend\n /** Текст динамики (например `+12,5% к прошлой неделе`). */\n trendText?: string\n /** Состояние загрузки: вместо значения — плейсхолдер. */\n loading?: boolean\n /**\n * Перебирать числа при появлении и при смене значения. Нечисловое значение\n * ставится сразу, под `prefers-reduced-motion: reduce` — тоже.\n */\n animate?: boolean\n /**\n * Длительность перебора в миллисекундах. Не токеном: шкала `--gr-duration-*`\n * заканчивается на 300 мс и описывает смену состояния, а перебор чисел —\n * другой жанр, и его число живёт там же, где твин.\n */\n animateDuration?: number\n /** Свой корневой тег (`RouterLink`, `Link` от Inertia). Сильнее `href`. */\n as?: string | Component\n /** Показатель-ссылка: переход к деталям. */\n href?: string\n /** Показатель-кнопка: интерактивна вся плитка. */\n clickable?: boolean\n}\n\nexport interface GrStatisticEmits {\n (e: 'click', event: MouseEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrStatisticProps>(),\n {\n title: undefined,\n precision: undefined,\n // Дефолты живут в `formatStatisticValue`: иначе «пользователь задал пробел»\n // было бы неотличимо от «сработал дефолт», и локаль не смогла бы победить.\n groupSeparator: undefined,\n decimalSeparator: undefined,\n locale: undefined,\n prefix: undefined,\n suffix: undefined,\n icon: undefined,\n size: undefined,\n tone: 'neutral',\n trend: undefined,\n trendText: undefined,\n loading: false,\n animate: false,\n animateDuration: 600,\n as: undefined,\n href: undefined,\n clickable: false,\n },\n)\n\nconst emit = defineEmits<GrStatisticEmits>()\n\nconst slots = useSlots()\n\nconst { t, locale } = useGranularityTranslations()\n\nconst resolvedLocale = computed(() => props.locale ?? locale.value)\n\n/**\n * Число текущего кадра перебора. `null` — перебора нет, показываем сам проп:\n * так значение остаётся конечным и на сервере, и до первого твина.\n */\nconst frameValue = ref<number | null>(null)\n\nconst formatted = computed(() => formatStatisticValue(frameValue.value ?? props.value, {\n precision: props.precision,\n groupSeparator: props.groupSeparator,\n decimalSeparator: props.decimalSeparator,\n locale: resolvedLocale.value,\n}))\n\n/** Конечное значение — то, что читает диктор, пока на экране идёт перебор. */\nconst finalFormatted = computed(() => formatStatisticValue(props.value, {\n precision: props.precision,\n groupSeparator: props.groupSeparator,\n decimalSeparator: props.decimalSeparator,\n locale: resolvedLocale.value,\n}))\n\nconst isCounting = computed(() => frameValue.value !== null)\n\nconst hasTitle = computed(() => Boolean(props.title || slots.title))\n\nconst isInteractive = computed(() => Boolean(props.as || props.href || props.clickable))\n\nconst rootTag = computed<string | Component>(() => {\n if (props.as)\n return typeof props.as === 'string' ? props.as : markRaw(props.as)\n\n if (props.href)\n return 'a'\n\n return props.clickable ? 'button' : 'div'\n})\n\nconst rootClass = computed(() => statisticRootClass({ interactive: isInteractive.value }))\n\nfunction toNumber(value: number | string): number | null {\n const numeric = typeof value === 'number' ? value : Number(value)\n return Number.isFinite(numeric) ? numeric : null\n}\n\nlet frameHandle: number | null = null\n\nfunction stopCounting(): void {\n if (frameHandle !== null) cancelAnimationFrame(frameHandle)\n frameHandle = null\n}\n\n/**\n * Перебор чисел ведёт JS, а глобальный кламп в `base.css` гасит только CSS —\n * значит уважать «уменьшить движение» обязан сам компонент (`docs/motion.md`).\n * Читается в момент старта, а не в теле `setup`: `matchMedia` на сервере нет, и\n * первый клиентский рендер разошёлся бы с серверным.\n */\nfunction prefersReducedMotion(): boolean {\n return typeof window !== 'undefined'\n && window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches === true\n}\n\nfunction runCountUp(from: number, to: number): void {\n stopCounting()\n\n const duration = props.animateDuration\n if (!(duration > 0) || from === to || prefersReducedMotion()) {\n frameValue.value = null\n return\n }\n\n const started = performance.now()\n frameValue.value = from\n\n const step = (now: number): void => {\n const elapsed = now - started\n const value = countUpFrame(from, to, elapsed, duration, props.precision)\n\n if (elapsed >= duration) {\n // Конец перебора снимает и подмену значения, и подмену для диктора.\n frameValue.value = null\n frameHandle = null\n return\n }\n\n frameValue.value = value\n frameHandle = requestAnimationFrame(step)\n }\n\n frameHandle = requestAnimationFrame(step)\n}\n\n/** Перебирать можно только числа: «2 ч 15 мин» и «—» ставятся сразу. */\nfunction startIfNumeric(from: number | string, to: number | string): void {\n if (!props.animate || props.loading) {\n stopCounting()\n frameValue.value = null\n return\n }\n\n const target = toNumber(to)\n const source = toNumber(from)\n\n if (target === null || source === null) {\n stopCounting()\n frameValue.value = null\n return\n }\n\n runCountUp(source, target)\n}\n\n// Появление плитки считается переходом от нуля: ради этого эффекта проп и нужен.\nonMounted(() => startIfNumeric(0, props.value))\n\nwatch(() => props.value, (next, prev) => startIfNumeric(prev, next))\n\nonBeforeUnmount(stopCounting)\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrStatistic' })\n\n// Иконка направления `aria-hidden`, а «+12,5 %» само по себе рост от падения не\n// отличает: направление доносит скрытая подпись.\nconst trendLabel = computed(() => {\n if (!props.trend) return undefined\n\n const labels = {\n up: () => t('gr.statistic.trendUp', 'Increase'),\n down: () => t('gr.statistic.trendDown', 'Decrease'),\n flat: () => t('gr.statistic.trendFlat', 'No change'),\n }\n\n return labels[props.trend]()\n})\n</script>\n\n<template>\n <component\n :is=\"rootTag\"\n data-gr-statistic\n :type=\"rootTag === 'button' ? 'button' : undefined\"\n :href=\"rootTag === 'a' ? href : undefined\"\n :class=\"rootClass\"\n @click=\"emit('click', $event)\"\n >\n <span\n v-if=\"icon || $slots.icon\"\n data-gr-statistic-icon\n class=\"mt-0.5 inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-[var(--gr-radius-lg)] bg-[var(--gr-muted)] text-[var(--gr-muted-fg)]\"\n >\n <slot name=\"icon\">\n <span class=\"block h-4 w-4\" :class=\"icon\" aria-hidden=\"true\" />\n </slot>\n </span>\n\n <div class=\"min-w-0\">\n <!--\n Подпись и значение — пара «термин — значение», а не два соседних блока.\n `<dl>` появляется только вместе с подписью: список определений без `<dt>`\n — та же бессвязность, только с претензией на семантику. Поля обнуляются\n руками: preflight пакета сбрасывает их лишь у `body`.\n -->\n <component :is=\"hasTitle ? 'dl' : 'div'\" class=\"m-0\">\n <dt\n v-if=\"hasTitle\"\n data-gr-statistic-title\n :class=\"[statisticTitleClass, statisticTitleSizeBySize[resolvedSize]]\"\n >\n <slot name=\"title\">\n {{ title }}\n </slot>\n </dt>\n\n <!-- Плейсхолдер загрузки повторяет высоту строки значения, чтобы блок не прыгал. -->\n <component\n :is=\"hasTitle ? 'dd' : 'div'\"\n v-if=\"loading\"\n data-gr-statistic-placeholder\n data-testid=\"gr-statistic-placeholder\"\n class=\"m-0 mt-1\"\n role=\"status\"\n aria-busy=\"true\"\n >\n <GrSkeleton\n width=\"6rem\"\n :height=\"statisticPlaceholderHeightBySize[resolvedSize]\"\n rounded=\"var(--gr-radius-md)\"\n />\n <span class=\"sr-only\">{{ t('gr.loading.defaultText', 'Loading...') }}</span>\n </component>\n\n <component\n :is=\"hasTitle ? 'dd' : 'div'\"\n v-else\n data-gr-statistic-value\n data-testid=\"gr-statistic-value\"\n class=\"m-0 mt-0.5 flex items-baseline gap-1\"\n >\n <span\n v-if=\"prefix || $slots.prefix\"\n data-gr-statistic-prefix\n class=\"text-[var(--gr-muted-fg)]\"\n :class=\"statisticAffixSizeBySize[resolvedSize]\"\n >\n <slot name=\"prefix\">{{ prefix }}</slot>\n </span>\n\n <span\n data-gr-statistic-number\n :class=\"statisticValueClass({ size: resolvedSize, tone })\"\n :aria-hidden=\"isCounting ? 'true' : undefined\"\n >\n <slot>{{ formatted }}</slot>\n </span>\n\n <!--\n Пока идёт перебор, на экране промежуточное число. Диктору отдаётся\n конечное: «1 284» глазами и «743» в ушах — не шум, а неверные данные.\n -->\n <span v-if=\"isCounting\" data-gr-statistic-final class=\"sr-only\">{{ finalFormatted }}</span>\n\n <span\n v-if=\"suffix || $slots.suffix\"\n data-gr-statistic-suffix\n class=\"text-[var(--gr-muted-fg)]\"\n :class=\"statisticAffixSizeBySize[resolvedSize]\"\n >\n <slot name=\"suffix\">{{ suffix }}</slot>\n </span>\n </component>\n </component>\n\n <div\n v-if=\"trend || trendText || $slots.trend\"\n data-gr-statistic-trend\n data-testid=\"gr-statistic-trend\"\n class=\"mt-1 inline-flex items-center gap-1\"\n :class=\"[statisticTrendSizeBySize[resolvedSize], statisticTrendClassByTrend[trend ?? 'flat']]\"\n >\n <span v-if=\"trendLabel\" data-gr-statistic-trend-label class=\"sr-only\">{{ trendLabel }}</span>\n\n <slot name=\"trend\">\n <span\n v-if=\"trend\"\n class=\"block h-3.5 w-3.5\"\n :class=\"statisticTrendIconByTrend[trend]\"\n aria-hidden=\"true\"\n />\n <span>{{ trendText }}</span>\n </slot>\n </div>\n </div>\n </component>\n</template>\n","<script setup lang=\"ts\">\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport { computed, markRaw, onBeforeUnmount, onMounted, ref, useSlots, watch, type Component } from 'vue'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport GrSkeleton from '../GrSkeleton/GrSkeleton.vue'\n\nimport { countUpFrame } from './countUp'\nimport { formatStatisticValue } from './formatStatisticValue'\n\nimport {\n statisticAffixSizeBySize,\n statisticPlaceholderHeightBySize,\n statisticRootClass,\n statisticTitleClass,\n statisticTitleSizeBySize,\n statisticTrendClassByTrend,\n statisticTrendIconByTrend,\n statisticTrendSizeBySize,\n statisticValueClass,\n type GrStatisticSize,\n type GrStatisticTone,\n type GrStatisticTrend,\n} from './grStatisticStyles'\n\nexport type {\n GrStatisticSize,\n GrStatisticTone,\n GrStatisticTrend,\n} from './grStatisticStyles'\nexport type { GrStatisticFormatOptions } from './formatStatisticValue'\n\n/**\n * Публичный GR-примитив «Statistic» — крупный числовой показатель с подписью,\n * префиксом/суффиксом и строкой динамики.\n *\n * Компонент презентационный: не грузит данные и не считает дельту, только\n * форматирует и подаёт. Форматирование — чистая функция `formatStatisticValue`.\n */\nexport interface GrStatisticProps {\n /** Значение показателя. Нечисловая строка выводится как есть. */\n value: number | string\n /** Подпись над значением. */\n title?: string\n /** Число знаков после запятой. */\n precision?: number\n /** Разделитель разрядов. Сильнее локали; без него и без локали — узкий пробел. */\n groupSeparator?: string\n /** Десятичный разделитель. Сильнее локали; без него и без локали — точка. */\n decimalSeparator?: string\n /**\n * BCP-47 локаль форматирования. Не задана — берётся из i18n-адаптера; нет и\n * адаптера — работают ручные разделители.\n */\n locale?: string\n /** Приписка перед значением (валюта, знак). */\n prefix?: string\n /** Приписка после значения (единица измерения, `%`). */\n suffix?: string\n /** UnoCSS-класс иконки слева от блока (например `i-lucide-users`). */\n icon?: string\n size?: GrStatisticSize\n /** Тон значения; точечно перекрывается `--gr-statistic-value-color`. */\n tone?: GrStatisticTone\n /** Направление динамики — задаёт цвет и иконку строки под значением. */\n trend?: GrStatisticTrend\n /** Текст динамики (например `+12,5% к прошлой неделе`). */\n trendText?: string\n /** Состояние загрузки: вместо значения — плейсхолдер. */\n loading?: boolean\n /**\n * Перебирать числа при появлении и при смене значения. Нечисловое значение\n * ставится сразу, под `prefers-reduced-motion: reduce` — тоже.\n */\n animate?: boolean\n /**\n * Длительность перебора в миллисекундах. Не токеном: шкала `--gr-duration-*`\n * заканчивается на 300 мс и описывает смену состояния, а перебор чисел —\n * другой жанр, и его число живёт там же, где твин.\n */\n animateDuration?: number\n /** Свой корневой тег (`RouterLink`, `Link` от Inertia). Сильнее `href`. */\n as?: string | Component\n /** Показатель-ссылка: переход к деталям. */\n href?: string\n /** Показатель-кнопка: интерактивна вся плитка. */\n clickable?: boolean\n}\n\nexport interface GrStatisticEmits {\n (e: 'click', event: MouseEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrStatisticProps>(),\n {\n title: undefined,\n precision: undefined,\n // Дефолты живут в `formatStatisticValue`: иначе «пользователь задал пробел»\n // было бы неотличимо от «сработал дефолт», и локаль не смогла бы победить.\n groupSeparator: undefined,\n decimalSeparator: undefined,\n locale: undefined,\n prefix: undefined,\n suffix: undefined,\n icon: undefined,\n size: undefined,\n tone: 'neutral',\n trend: undefined,\n trendText: undefined,\n loading: false,\n animate: false,\n animateDuration: 600,\n as: undefined,\n href: undefined,\n clickable: false,\n },\n)\n\nconst emit = defineEmits<GrStatisticEmits>()\n\nconst slots = useSlots()\n\nconst { t, locale } = useGranularityTranslations()\n\nconst resolvedLocale = computed(() => props.locale ?? locale.value)\n\n/**\n * Число текущего кадра перебора. `null` — перебора нет, показываем сам проп:\n * так значение остаётся конечным и на сервере, и до первого твина.\n */\nconst frameValue = ref<number | null>(null)\n\nconst formatted = computed(() => formatStatisticValue(frameValue.value ?? props.value, {\n precision: props.precision,\n groupSeparator: props.groupSeparator,\n decimalSeparator: props.decimalSeparator,\n locale: resolvedLocale.value,\n}))\n\n/** Конечное значение — то, что читает диктор, пока на экране идёт перебор. */\nconst finalFormatted = computed(() => formatStatisticValue(props.value, {\n precision: props.precision,\n groupSeparator: props.groupSeparator,\n decimalSeparator: props.decimalSeparator,\n locale: resolvedLocale.value,\n}))\n\nconst isCounting = computed(() => frameValue.value !== null)\n\nconst hasTitle = computed(() => Boolean(props.title || slots.title))\n\nconst isInteractive = computed(() => Boolean(props.as || props.href || props.clickable))\n\nconst rootTag = computed<string | Component>(() => {\n if (props.as)\n return typeof props.as === 'string' ? props.as : markRaw(props.as)\n\n if (props.href)\n return 'a'\n\n return props.clickable ? 'button' : 'div'\n})\n\nconst rootClass = computed(() => statisticRootClass({ interactive: isInteractive.value }))\n\nfunction toNumber(value: number | string): number | null {\n const numeric = typeof value === 'number' ? value : Number(value)\n return Number.isFinite(numeric) ? numeric : null\n}\n\nlet frameHandle: number | null = null\n\nfunction stopCounting(): void {\n if (frameHandle !== null) cancelAnimationFrame(frameHandle)\n frameHandle = null\n}\n\n/**\n * Перебор чисел ведёт JS, а глобальный кламп в `base.css` гасит только CSS —\n * значит уважать «уменьшить движение» обязан сам компонент (`docs/motion.md`).\n * Читается в момент старта, а не в теле `setup`: `matchMedia` на сервере нет, и\n * первый клиентский рендер разошёлся бы с серверным.\n */\nfunction prefersReducedMotion(): boolean {\n return typeof window !== 'undefined'\n && window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches === true\n}\n\nfunction runCountUp(from: number, to: number): void {\n stopCounting()\n\n const duration = props.animateDuration\n if (!(duration > 0) || from === to || prefersReducedMotion()) {\n frameValue.value = null\n return\n }\n\n const started = performance.now()\n frameValue.value = from\n\n const step = (now: number): void => {\n const elapsed = now - started\n const value = countUpFrame(from, to, elapsed, duration, props.precision)\n\n if (elapsed >= duration) {\n // Конец перебора снимает и подмену значения, и подмену для диктора.\n frameValue.value = null\n frameHandle = null\n return\n }\n\n frameValue.value = value\n frameHandle = requestAnimationFrame(step)\n }\n\n frameHandle = requestAnimationFrame(step)\n}\n\n/** Перебирать можно только числа: «2 ч 15 мин» и «—» ставятся сразу. */\nfunction startIfNumeric(from: number | string, to: number | string): void {\n if (!props.animate || props.loading) {\n stopCounting()\n frameValue.value = null\n return\n }\n\n const target = toNumber(to)\n const source = toNumber(from)\n\n if (target === null || source === null) {\n stopCounting()\n frameValue.value = null\n return\n }\n\n runCountUp(source, target)\n}\n\n// Появление плитки считается переходом от нуля: ради этого эффекта проп и нужен.\nonMounted(() => startIfNumeric(0, props.value))\n\nwatch(() => props.value, (next, prev) => startIfNumeric(prev, next))\n\nonBeforeUnmount(stopCounting)\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrStatistic' })\n\n// Иконка направления `aria-hidden`, а «+12,5 %» само по себе рост от падения не\n// отличает: направление доносит скрытая подпись.\nconst trendLabel = computed(() => {\n if (!props.trend) return undefined\n\n const labels = {\n up: () => t('gr.statistic.trendUp', 'Increase'),\n down: () => t('gr.statistic.trendDown', 'Decrease'),\n flat: () => t('gr.statistic.trendFlat', 'No change'),\n }\n\n return labels[props.trend]()\n})\n</script>\n\n<template>\n <component\n :is=\"rootTag\"\n data-gr-statistic\n :type=\"rootTag === 'button' ? 'button' : undefined\"\n :href=\"rootTag === 'a' ? href : undefined\"\n :class=\"rootClass\"\n @click=\"emit('click', $event)\"\n >\n <span\n v-if=\"icon || $slots.icon\"\n data-gr-statistic-icon\n class=\"mt-0.5 inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-[var(--gr-radius-lg)] bg-[var(--gr-muted)] text-[var(--gr-muted-fg)]\"\n >\n <slot name=\"icon\">\n <span class=\"block h-4 w-4\" :class=\"icon\" aria-hidden=\"true\" />\n </slot>\n </span>\n\n <div class=\"min-w-0\">\n <!--\n Подпись и значение — пара «термин — значение», а не два соседних блока.\n `<dl>` появляется только вместе с подписью: список определений без `<dt>`\n — та же бессвязность, только с претензией на семантику. Поля обнуляются\n руками: preflight пакета сбрасывает их лишь у `body`.\n -->\n <component :is=\"hasTitle ? 'dl' : 'div'\" class=\"m-0\">\n <dt\n v-if=\"hasTitle\"\n data-gr-statistic-title\n :class=\"[statisticTitleClass, statisticTitleSizeBySize[resolvedSize]]\"\n >\n <slot name=\"title\">\n {{ title }}\n </slot>\n </dt>\n\n <!-- Плейсхолдер загрузки повторяет высоту строки значения, чтобы блок не прыгал. -->\n <component\n :is=\"hasTitle ? 'dd' : 'div'\"\n v-if=\"loading\"\n data-gr-statistic-placeholder\n data-testid=\"gr-statistic-placeholder\"\n class=\"m-0 mt-1\"\n role=\"status\"\n aria-busy=\"true\"\n >\n <GrSkeleton\n width=\"6rem\"\n :height=\"statisticPlaceholderHeightBySize[resolvedSize]\"\n rounded=\"var(--gr-radius-md)\"\n />\n <span class=\"sr-only\">{{ t('gr.loading.defaultText', 'Loading...') }}</span>\n </component>\n\n <component\n :is=\"hasTitle ? 'dd' : 'div'\"\n v-else\n data-gr-statistic-value\n data-testid=\"gr-statistic-value\"\n class=\"m-0 mt-0.5 flex items-baseline gap-1\"\n >\n <span\n v-if=\"prefix || $slots.prefix\"\n data-gr-statistic-prefix\n class=\"text-[var(--gr-muted-fg)]\"\n :class=\"statisticAffixSizeBySize[resolvedSize]\"\n >\n <slot name=\"prefix\">{{ prefix }}</slot>\n </span>\n\n <span\n data-gr-statistic-number\n :class=\"statisticValueClass({ size: resolvedSize, tone })\"\n :aria-hidden=\"isCounting ? 'true' : undefined\"\n >\n <slot>{{ formatted }}</slot>\n </span>\n\n <!--\n Пока идёт перебор, на экране промежуточное число. Диктору отдаётся\n конечное: «1 284» глазами и «743» в ушах — не шум, а неверные данные.\n -->\n <span v-if=\"isCounting\" data-gr-statistic-final class=\"sr-only\">{{ finalFormatted }}</span>\n\n <span\n v-if=\"suffix || $slots.suffix\"\n data-gr-statistic-suffix\n class=\"text-[var(--gr-muted-fg)]\"\n :class=\"statisticAffixSizeBySize[resolvedSize]\"\n >\n <slot name=\"suffix\">{{ suffix }}</slot>\n </span>\n </component>\n </component>\n\n <div\n v-if=\"trend || trendText || $slots.trend\"\n data-gr-statistic-trend\n data-testid=\"gr-statistic-trend\"\n class=\"mt-1 inline-flex items-center gap-1\"\n :class=\"[statisticTrendSizeBySize[resolvedSize], statisticTrendClassByTrend[trend ?? 'flat']]\"\n >\n <span v-if=\"trendLabel\" data-gr-statistic-trend-label class=\"sr-only\">{{ trendLabel }}</span>\n\n <slot name=\"trend\">\n <span\n v-if=\"trend\"\n class=\"block h-3.5 w-3.5\"\n :class=\"statisticTrendIconByTrend[trend]\"\n aria-hidden=\"true\"\n />\n <span>{{ trendText }}</span>\n </slot>\n </div>\n </div>\n </component>\n</template>\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"GrTabs-TeXaU5Uh.js","names":[],"sources":["../../../../src/components/GrTabs/GrTabs.vue","../../../../src/components/GrTabs/GrTabs.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, ref, watch } from 'vue'\n\nimport IconX from '~icons/lucide/x'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport {\n grTabsBadgeClass,\n grTabsCloseClass,\n grTabsEmptyClass,\n grTabsListClass,\n grTabsTabClass,\n tabCloseIconSizes,\n tabContentClass,\n tabIconClass,\n type GrTabsOrientation,\n type GrTabsSize,\n type GrTabsVariant,\n} from './grTabsStyles'\n\nexport interface GrTab {\n value: string\n label: string\n badge?: string\n /** UnoCSS-класс иконки слева от подписи (например `i-lucide-user`). */\n icon?: string\n disabled?: boolean\n /** Перекрывает `closable` ряда в обе стороны: закрепить вкладку или закрыть одну её. */\n closable?: boolean\n}\n\nexport interface GrTabsProps {\n modelValue: string\n tabs: GrTab[]\n /**\n * База id для ARIA-связки с `GrTabPanels`. Если задана, каждая вкладка\n * получает `id=\"<idBase>-tab-<value>\"` и `aria-controls=\"<idBase>-panel-<value>\"`.\n * Передайте тот же `idBase` в `GrTabPanels`, чтобы связать `tab`↔`tabpanel`.\n */\n idBase?: string\n size?: GrTabsSize\n /** Вид ряда: обойма с таблетками или ряд с подчёркиванием. */\n variant?: GrTabsVariant\n /**\n * `automatic` (по умолчанию) — стрелка сразу переключает вкладку;\n * `manual` — стрелка двигает только фокус, выбор подтверждается\n * `Enter`/`Space`. Второй режим для вкладок с тяжёлой загрузкой: перебор\n * стрелками иначе тянет каждую панель.\n */\n activationMode?: GrTabsActivationMode\n /** Горизонтальный (по умолчанию) или вертикальный список вкладок. */\n orientation?: GrTabsOrientation\n /**\n * Крестик на вкладках и закрытие по `Delete`/`Backspace`. Точечно снимается\n * `closable: false` у самой вкладки.\n */\n closable?: boolean\n /** Текст, когда список вкладок пуст. Слот `#empty` сильнее. */\n emptyText?: string\n}\n\nexport interface GrTabsEmits {\n (e: 'update:modelValue', value: string): void\n /**\n * Просьба закрыть вкладку. Компонент список не трогает: `tabs` — проп, и\n * закрытие может не состояться («сохранить изменения?»).\n */\n (e: 'close', value: string): void\n}\n\nexport type GrTabsActivationMode = 'automatic' | 'manual'\n\nexport type { GrTabsOrientation, GrTabsVariant } from './grTabsStyles'\n\n/**\n * GrTabs — горизонтальная группа вкладок с паттерном WAI-ARIA `tablist`.\n *\n * A11y:\n * - `role=\"tablist\"` на корне, `role=\"tab\"` на каждой вкладке.\n * - Roving `tabindex`: активная вкладка `0`, остальные `-1`.\n * - Клавиатура: `ArrowLeft`/`ArrowRight` — цикличный переход, `Home`/`End` — к первой/последней.\n * - При переключении стрелками DOM-фокус программно переносится на новую вкладку.\n *\n * Сам компонент не рендерит `tabpanel` — это ответственность консьюмера (по `aria-controls`/внешней разметке).\n */\nconst props = withDefaults(defineProps<GrTabsProps>(), {\n idBase: undefined,\n size: undefined,\n // Дефолт живёт в резолвере: Vue подставил бы свой раньше, чем компонент\n // заглянет в `GrConfigProvider`.\n variant: undefined,\n activationMode: 'automatic',\n orientation: 'horizontal',\n closable: false,\n emptyText: undefined,\n})\n\ndefineSlots<{\n /** Содержимое вкладки целиком — вместо подписи, иконки и счётчика. Крестик остаётся. */\n tab?: (props: { tab: GrTab, active: boolean, disabled: boolean }) => unknown\n /** Пустой ряд — вместо текста из локали. */\n empty?: () => unknown\n}>()\n\nconst emit = defineEmits<GrTabsEmits>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrTabs' })\nconst resolvedVariant = useGrComponentProp('GrTabs', 'variant', () => props.variant, 'pills')\n\nconst tablistClass = computed(() => grTabsListClass({\n variant: resolvedVariant.value,\n size: resolvedSize.value,\n orientation: props.orientation,\n}))\n\nfunction tabClass(tab: GrTab): string {\n return grTabsTabClass({\n variant: resolvedVariant.value,\n size: resolvedSize.value,\n active: tab.value === props.modelValue,\n disabled: Boolean(tab.disabled),\n })\n}\n\nconst badgeClass = computed(() => grTabsBadgeClass(resolvedSize.value))\nconst closeClass = computed(() => grTabsCloseClass(resolvedSize.value))\nconst closeIconClass = computed(() => tabCloseIconSizes[resolvedSize.value])\nconst emptyClass = computed(() => grTabsEmptyClass(resolvedSize.value))\n\nconst isEmpty = computed(() => props.tabs.length === 0)\nconst resolvedEmptyText = computed(() => props.emptyText ?? t('gr.tabs.empty', 'Nothing here yet'))\nconst closeTitle = computed(() => t('gr.tabs.close', 'Close tab'))\n\n/** Отключённая вкладка не принимает взаимодействия, а закрытие — тоже взаимодействие. */\nfunction isClosable(tab: GrTab): boolean {\n return !tab.disabled && (tab.closable ?? props.closable)\n}\n\nconst buttonRefs = ref<HTMLButtonElement[]>([])\n\n/**\n * Vue зовёт ref-функцию с `null`, когда узел исчез. Игнорировать такой вызов\n * нельзя: в массиве оставались отсоединённые от DOM кнопки, и `focus()` по\n * сократившемуся списку молча проваливался в `<body>`.\n */\nfunction setButtonRef(el: unknown, index: number): void {\n if (el instanceof HTMLButtonElement)\n buttonRefs.value[index] = el\n else\n delete buttonRefs.value[index]\n\n if (buttonRefs.value.length > props.tabs.length)\n buttonRefs.value.length = props.tabs.length\n}\n\nconst activeIndex = computed(() => props.tabs.findIndex(t => t.value === props.modelValue))\n\nfunction isEnabled(tab: GrTab): boolean {\n return !tab.disabled\n}\n\nconst tabIndexes = computed(() => props.tabs.map((_, index) => index))\n\n/**\n * Кольцо roving-фокуса. При `activationMode: 'manual'` фокус уезжает вперёд\n * выбора — это и есть разница между `activeKey` примитива и `initialKey`:\n * первый ведёт фокус, второй подставляет выбранную вкладку, пока фокус не\n * трогали.\n *\n * Выбор стрелка **не** переносит: этим заведует `onKeydown` по\n * `activationMode`. Через `onMove` это выразить нельзя — клик тоже зовёт\n * `focusKey`, и выбор эмитился бы дважды.\n */\nconst roving = useRovingFocus<number>({\n items: () => tabIndexes.value,\n elementFor: index => buttonRefs.value[index],\n isDisabled: index => Boolean(props.tabs[index]?.disabled),\n orientation: () => (props.orientation === 'vertical' ? 'vertical' : 'horizontal'),\n skipDisabled: () => true,\n /**\n * Пока фокус не трогали, остановку `Tab` держит выбранная вкладка. Если\n * `modelValue` не совпал ни с одной (пустое начальное значение, асинхронный\n * список, удалённая активная вкладка), примитив ставит её на первую\n * доступную — иначе tablist выпал бы из обхода целиком и молча.\n *\n * Выключенность здесь не проверяется намеренно: на кнопке `aria-disabled`, а\n * не нативный `disabled`, поэтому выключенная вкладка фокусируется и держать\n * остановку вправе.\n */\n initialKey: () => (activeIndex.value >= 0 ? activeIndex.value : undefined),\n /**\n * Прокрутка до фокуса, а не после: иначе браузер доскроллит сам по-своему, и\n * ряд дёрнется дважды. Синхронно — фокус обязан остаться в том же такте, что\n * и раньше: тик, где он нужен, добавляет `focusIndex`.\n */\n beforeFocus: index => scrollIntoView(index),\n})\n\n/**\n * Фокус после перерисовки — для путей, где список только что изменился\n * (закрытие вкладки). Клавиатура идёт через примитив напрямую: там ряд уже\n * отрисован, и лишний тик только отложил бы фокус.\n */\nasync function focusIndex(index: number): Promise<void> {\n await nextTick()\n await roving.focusKey(index)\n}\n\n/**\n * Ряд прокручивается, а не переносится, поэтому активная вкладка может уехать\n * за край — например, когда её выбрали снаружи. `scrollIntoView` нет в jsdom и\n * в старых движках, поэтому вызов необязательный.\n */\nfunction scrollIntoView(index: number): void {\n buttonRefs.value[index]?.scrollIntoView?.({ block: 'nearest', inline: 'nearest' })\n}\n\nwatch(() => props.modelValue, async () => {\n await nextTick()\n if (activeIndex.value >= 0)\n scrollIntoView(activeIndex.value)\n})\n\nasync function selectByIndex(index: number, focus = false): Promise<void> {\n const tab = props.tabs[index]\n if (!tab || !isEnabled(tab))\n return\n if (tab.value !== props.modelValue)\n emit('update:modelValue', tab.value)\n if (focus)\n await focusIndex(index)\n}\n\nfunction onKeydown(event: KeyboardEvent): void {\n if (props.tabs.length === 0)\n return\n\n const currentIndex = roving.rovingKey.value ?? 0\n\n switch (event.key) {\n case 'Enter':\n case ' ':\n // В ручном режиме выбор подтверждается явно — на той вкладке, где фокус.\n if (props.activationMode === 'manual') {\n event.preventDefault()\n void selectByIndex(currentIndex, false)\n }\n return\n case 'Delete':\n case 'Backspace': {\n // Единственный клавиатурный путь к закрытию: крестик в таб-порядке не стоит.\n const tab = props.tabs[currentIndex]\n if (tab && isClosable(tab)) {\n event.preventDefault()\n requestClose(currentIndex)\n }\n return\n }\n }\n\n // Стрелки, `Home`, `End` — фокус ведёт примитив по объявленной ориентации.\n if (!roving.handleNavigationKeys(event))\n return\n\n // `automatic`: выбор едет за фокусом. `manual`: ждёт `Enter`/`Space`.\n if (props.activationMode !== 'manual')\n void selectByIndex(roving.rovingKey.value ?? currentIndex, false)\n}\n\n/**\n * Закрытая вкладка, которая держала фокус. Компонент список не укорачивает —\n * это делает потребитель, — поэтому фокус возвращается только после того, как\n * `tabs` действительно стал короче.\n */\nlet pendingClose: { index: number, value: string } | null = null\n\nfunction requestClose(index: number): void {\n const tab = props.tabs[index]\n if (!tab || !isClosable(tab))\n return\n\n const held = buttonRefs.value[index] === document.activeElement\n pendingClose = held ? { index, value: tab.value } : null\n\n emit('close', tab.value)\n}\n\n// Без этого клавиатурное закрытие роняет фокус в `<body>`: кнопка, на которой\n// он стоял, исчезает из DOM вместе со вкладкой.\nwatch(() => props.tabs.length, async (next, prev) => {\n const pending = pendingClose\n pendingClose = null\n\n if (!pending || next >= prev)\n return\n // Ушла не та вкладка — потребитель поменял список по своей причине.\n if (props.tabs.some(tab => tab.value === pending.value))\n return\n\n await nextTick()\n\n const target = Math.min(pending.index, next - 1)\n if (target >= 0)\n void focusIndex(target)\n else\n roving.reset()\n})\n\nfunction onClick(event: MouseEvent, tab: GrTab, index: number): void {\n if (!isEnabled(tab))\n return\n\n // Крестик — не кнопка, поэтому его клик приходит сюда же и разбирается по цели.\n if (event.target instanceof Element && event.target.closest('[data-gr-tab-close]')) {\n requestClose(index)\n return\n }\n\n roving.setActive(index)\n\n if (tab.value !== props.modelValue)\n emit('update:modelValue', tab.value)\n}\n</script>\n\n<template>\n <!--\n Пустой ряд — не пустой `tablist`: роль обязана владеть потомками `tab`,\n и текст внутри неё был бы нарушением, а не пустым состоянием.\n -->\n <div v-if=\"isEmpty\" data-gr-tabs-empty :class=\"emptyClass\">\n <slot name=\"empty\">\n {{ resolvedEmptyText }}\n </slot>\n </div>\n\n <div\n v-else\n role=\"tablist\"\n data-gr-tabs\n :aria-orientation=\"orientation\"\n :class=\"tablistClass\"\n @keydown=\"onKeydown\"\n >\n <button\n v-for=\"(tab, index) in tabs\"\n :id=\"idBase ? `${idBase}-tab-${tab.value}` : undefined\"\n :key=\"tab.value\"\n :ref=\"el => setButtonRef(el, index)\"\n type=\"button\"\n role=\"tab\"\n data-gr-tab\n :aria-controls=\"idBase ? `${idBase}-panel-${tab.value}` : undefined\"\n :aria-selected=\"tab.value === modelValue ? 'true' : 'false'\"\n :aria-disabled=\"tab.disabled ? 'true' : undefined\"\n :aria-keyshortcuts=\"isClosable(tab) ? 'Delete' : undefined\"\n :tabindex=\"roving.tabindexFor(index)\"\n :class=\"tabClass(tab)\"\n @click=\"onClick($event, tab, index)\"\n >\n <span :class=\"tabContentClass\">\n <slot\n name=\"tab\"\n :tab=\"tab\"\n :active=\"tab.value === modelValue\"\n :disabled=\"Boolean(tab.disabled)\"\n >\n <span v-if=\"tab.icon\" :class=\"[tabIconClass, tab.icon]\" aria-hidden=\"true\" />\n <span>{{ tab.label }}</span>\n <span v-if=\"tab.badge\" :class=\"badgeClass\">\n {{ tab.badge }}\n </span>\n </slot>\n\n <!-- Снаружи слота: своя разметка вкладки не должна отнимать закрытие. -->\n <span\n v-if=\"isClosable(tab)\"\n data-gr-tab-close\n :class=\"closeClass\"\n :title=\"closeTitle\"\n aria-hidden=\"true\"\n >\n <IconX :class=\"closeIconClass\" />\n </span>\n </span>\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, ref, watch } from 'vue'\n\nimport IconX from '~icons/lucide/x'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport {\n grTabsBadgeClass,\n grTabsCloseClass,\n grTabsEmptyClass,\n grTabsListClass,\n grTabsTabClass,\n tabCloseIconSizes,\n tabContentClass,\n tabIconClass,\n type GrTabsOrientation,\n type GrTabsSize,\n type GrTabsVariant,\n} from './grTabsStyles'\n\nexport interface GrTab {\n value: string\n label: string\n badge?: string\n /** UnoCSS-класс иконки слева от подписи (например `i-lucide-user`). */\n icon?: string\n disabled?: boolean\n /** Перекрывает `closable` ряда в обе стороны: закрепить вкладку или закрыть одну её. */\n closable?: boolean\n}\n\nexport interface GrTabsProps {\n modelValue: string\n tabs: GrTab[]\n /**\n * База id для ARIA-связки с `GrTabPanels`. Если задана, каждая вкладка\n * получает `id=\"<idBase>-tab-<value>\"` и `aria-controls=\"<idBase>-panel-<value>\"`.\n * Передайте тот же `idBase` в `GrTabPanels`, чтобы связать `tab`↔`tabpanel`.\n */\n idBase?: string\n size?: GrTabsSize\n /** Вид ряда: обойма с таблетками или ряд с подчёркиванием. */\n variant?: GrTabsVariant\n /**\n * `automatic` (по умолчанию) — стрелка сразу переключает вкладку;\n * `manual` — стрелка двигает только фокус, выбор подтверждается\n * `Enter`/`Space`. Второй режим для вкладок с тяжёлой загрузкой: перебор\n * стрелками иначе тянет каждую панель.\n */\n activationMode?: GrTabsActivationMode\n /** Горизонтальный (по умолчанию) или вертикальный список вкладок. */\n orientation?: GrTabsOrientation\n /**\n * Крестик на вкладках и закрытие по `Delete`/`Backspace`. Точечно снимается\n * `closable: false` у самой вкладки.\n */\n closable?: boolean\n /** Текст, когда список вкладок пуст. Слот `#empty` сильнее. */\n emptyText?: string\n}\n\nexport interface GrTabsEmits {\n (e: 'update:modelValue', value: string): void\n /**\n * Просьба закрыть вкладку. Компонент список не трогает: `tabs` — проп, и\n * закрытие может не состояться («сохранить изменения?»).\n */\n (e: 'close', value: string): void\n}\n\nexport type GrTabsActivationMode = 'automatic' | 'manual'\n\nexport type { GrTabsOrientation, GrTabsVariant } from './grTabsStyles'\n\n/**\n * GrTabs — горизонтальная группа вкладок с паттерном WAI-ARIA `tablist`.\n *\n * A11y:\n * - `role=\"tablist\"` на корне, `role=\"tab\"` на каждой вкладке.\n * - Roving `tabindex`: активная вкладка `0`, остальные `-1`.\n * - Клавиатура: `ArrowLeft`/`ArrowRight` — цикличный переход, `Home`/`End` — к первой/последней.\n * - При переключении стрелками DOM-фокус программно переносится на новую вкладку.\n *\n * Сам компонент не рендерит `tabpanel` — это ответственность консьюмера (по `aria-controls`/внешней разметке).\n */\nconst props = withDefaults(defineProps<GrTabsProps>(), {\n idBase: undefined,\n size: undefined,\n // Дефолт живёт в резолвере: Vue подставил бы свой раньше, чем компонент\n // заглянет в `GrConfigProvider`.\n variant: undefined,\n activationMode: 'automatic',\n orientation: 'horizontal',\n closable: false,\n emptyText: undefined,\n})\n\ndefineSlots<{\n /** Содержимое вкладки целиком — вместо подписи, иконки и счётчика. Крестик остаётся. */\n tab?: (props: { tab: GrTab, active: boolean, disabled: boolean }) => unknown\n /** Пустой ряд — вместо текста из локали. */\n empty?: () => unknown\n}>()\n\nconst emit = defineEmits<GrTabsEmits>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrTabs' })\nconst resolvedVariant = useGrComponentProp('GrTabs', 'variant', () => props.variant, 'pills')\n\nconst tablistClass = computed(() => grTabsListClass({\n variant: resolvedVariant.value,\n size: resolvedSize.value,\n orientation: props.orientation,\n}))\n\nfunction tabClass(tab: GrTab): string {\n return grTabsTabClass({\n variant: resolvedVariant.value,\n size: resolvedSize.value,\n active: tab.value === props.modelValue,\n disabled: Boolean(tab.disabled),\n })\n}\n\nconst badgeClass = computed(() => grTabsBadgeClass(resolvedSize.value))\nconst closeClass = computed(() => grTabsCloseClass(resolvedSize.value))\nconst closeIconClass = computed(() => tabCloseIconSizes[resolvedSize.value])\nconst emptyClass = computed(() => grTabsEmptyClass(resolvedSize.value))\n\nconst isEmpty = computed(() => props.tabs.length === 0)\nconst resolvedEmptyText = computed(() => props.emptyText ?? t('gr.tabs.empty', 'Nothing here yet'))\nconst closeTitle = computed(() => t('gr.tabs.close', 'Close tab'))\n\n/** Отключённая вкладка не принимает взаимодействия, а закрытие — тоже взаимодействие. */\nfunction isClosable(tab: GrTab): boolean {\n return !tab.disabled && (tab.closable ?? props.closable)\n}\n\nconst buttonRefs = ref<HTMLButtonElement[]>([])\n\n/**\n * Vue зовёт ref-функцию с `null`, когда узел исчез. Игнорировать такой вызов\n * нельзя: в массиве оставались отсоединённые от DOM кнопки, и `focus()` по\n * сократившемуся списку молча проваливался в `<body>`.\n */\nfunction setButtonRef(el: unknown, index: number): void {\n if (el instanceof HTMLButtonElement)\n buttonRefs.value[index] = el\n else\n delete buttonRefs.value[index]\n\n if (buttonRefs.value.length > props.tabs.length)\n buttonRefs.value.length = props.tabs.length\n}\n\nconst activeIndex = computed(() => props.tabs.findIndex(t => t.value === props.modelValue))\n\nfunction isEnabled(tab: GrTab): boolean {\n return !tab.disabled\n}\n\nconst tabIndexes = computed(() => props.tabs.map((_, index) => index))\n\n/**\n * Кольцо roving-фокуса. При `activationMode: 'manual'` фокус уезжает вперёд\n * выбора — это и есть разница между `activeKey` примитива и `initialKey`:\n * первый ведёт фокус, второй подставляет выбранную вкладку, пока фокус не\n * трогали.\n *\n * Выбор стрелка **не** переносит: этим заведует `onKeydown` по\n * `activationMode`. Через `onMove` это выразить нельзя — клик тоже зовёт\n * `focusKey`, и выбор эмитился бы дважды.\n */\nconst roving = useRovingFocus<number>({\n items: () => tabIndexes.value,\n elementFor: index => buttonRefs.value[index],\n isDisabled: index => Boolean(props.tabs[index]?.disabled),\n orientation: () => (props.orientation === 'vertical' ? 'vertical' : 'horizontal'),\n skipDisabled: () => true,\n /**\n * Пока фокус не трогали, остановку `Tab` держит выбранная вкладка. Если\n * `modelValue` не совпал ни с одной (пустое начальное значение, асинхронный\n * список, удалённая активная вкладка), примитив ставит её на первую\n * доступную — иначе tablist выпал бы из обхода целиком и молча.\n *\n * Выключенность здесь не проверяется намеренно: на кнопке `aria-disabled`, а\n * не нативный `disabled`, поэтому выключенная вкладка фокусируется и держать\n * остановку вправе.\n */\n initialKey: () => (activeIndex.value >= 0 ? activeIndex.value : undefined),\n /**\n * Прокрутка до фокуса, а не после: иначе браузер доскроллит сам по-своему, и\n * ряд дёрнется дважды. Синхронно — фокус обязан остаться в том же такте, что\n * и раньше: тик, где он нужен, добавляет `focusIndex`.\n */\n beforeFocus: index => scrollIntoView(index),\n})\n\n/**\n * Фокус после перерисовки — для путей, где список только что изменился\n * (закрытие вкладки). Клавиатура идёт через примитив напрямую: там ряд уже\n * отрисован, и лишний тик только отложил бы фокус.\n */\nasync function focusIndex(index: number): Promise<void> {\n await nextTick()\n await roving.focusKey(index)\n}\n\n/**\n * Ряд прокручивается, а не переносится, поэтому активная вкладка может уехать\n * за край — например, когда её выбрали снаружи. `scrollIntoView` нет в jsdom и\n * в старых движках, поэтому вызов необязательный.\n */\nfunction scrollIntoView(index: number): void {\n buttonRefs.value[index]?.scrollIntoView?.({ block: 'nearest', inline: 'nearest' })\n}\n\nwatch(() => props.modelValue, async () => {\n await nextTick()\n if (activeIndex.value >= 0)\n scrollIntoView(activeIndex.value)\n})\n\nasync function selectByIndex(index: number, focus = false): Promise<void> {\n const tab = props.tabs[index]\n if (!tab || !isEnabled(tab))\n return\n if (tab.value !== props.modelValue)\n emit('update:modelValue', tab.value)\n if (focus)\n await focusIndex(index)\n}\n\nfunction onKeydown(event: KeyboardEvent): void {\n if (props.tabs.length === 0)\n return\n\n const currentIndex = roving.rovingKey.value ?? 0\n\n switch (event.key) {\n case 'Enter':\n case ' ':\n // В ручном режиме выбор подтверждается явно — на той вкладке, где фокус.\n if (props.activationMode === 'manual') {\n event.preventDefault()\n void selectByIndex(currentIndex, false)\n }\n return\n case 'Delete':\n case 'Backspace': {\n // Единственный клавиатурный путь к закрытию: крестик в таб-порядке не стоит.\n const tab = props.tabs[currentIndex]\n if (tab && isClosable(tab)) {\n event.preventDefault()\n requestClose(currentIndex)\n }\n return\n }\n }\n\n // Стрелки, `Home`, `End` — фокус ведёт примитив по объявленной ориентации.\n if (!roving.handleNavigationKeys(event))\n return\n\n // `automatic`: выбор едет за фокусом. `manual`: ждёт `Enter`/`Space`.\n if (props.activationMode !== 'manual')\n void selectByIndex(roving.rovingKey.value ?? currentIndex, false)\n}\n\n/**\n * Закрытая вкладка, которая держала фокус. Компонент список не укорачивает —\n * это делает потребитель, — поэтому фокус возвращается только после того, как\n * `tabs` действительно стал короче.\n */\nlet pendingClose: { index: number, value: string } | null = null\n\nfunction requestClose(index: number): void {\n const tab = props.tabs[index]\n if (!tab || !isClosable(tab))\n return\n\n const held = buttonRefs.value[index] === document.activeElement\n pendingClose = held ? { index, value: tab.value } : null\n\n emit('close', tab.value)\n}\n\n// Без этого клавиатурное закрытие роняет фокус в `<body>`: кнопка, на которой\n// он стоял, исчезает из DOM вместе со вкладкой.\nwatch(() => props.tabs.length, async (next, prev) => {\n const pending = pendingClose\n pendingClose = null\n\n if (!pending || next >= prev)\n return\n // Ушла не та вкладка — потребитель поменял список по своей причине.\n if (props.tabs.some(tab => tab.value === pending.value))\n return\n\n await nextTick()\n\n const target = Math.min(pending.index, next - 1)\n if (target >= 0)\n void focusIndex(target)\n else\n roving.reset()\n})\n\nfunction onClick(event: MouseEvent, tab: GrTab, index: number): void {\n if (!isEnabled(tab))\n return\n\n // Крестик — не кнопка, поэтому его клик приходит сюда же и разбирается по цели.\n if (event.target instanceof Element && event.target.closest('[data-gr-tab-close]')) {\n requestClose(index)\n return\n }\n\n roving.setActive(index)\n\n if (tab.value !== props.modelValue)\n emit('update:modelValue', tab.value)\n}\n</script>\n\n<template>\n <!--\n Пустой ряд — не пустой `tablist`: роль обязана владеть потомками `tab`,\n и текст внутри неё был бы нарушением, а не пустым состоянием.\n -->\n <div v-if=\"isEmpty\" data-gr-tabs-empty :class=\"emptyClass\">\n <slot name=\"empty\">\n {{ resolvedEmptyText }}\n </slot>\n </div>\n\n <div\n v-else\n role=\"tablist\"\n data-gr-tabs\n :aria-orientation=\"orientation\"\n :class=\"tablistClass\"\n @keydown=\"onKeydown\"\n >\n <button\n v-for=\"(tab, index) in tabs\"\n :id=\"idBase ? `${idBase}-tab-${tab.value}` : undefined\"\n :key=\"tab.value\"\n :ref=\"el => setButtonRef(el, index)\"\n type=\"button\"\n role=\"tab\"\n data-gr-tab\n :aria-controls=\"idBase ? `${idBase}-panel-${tab.value}` : undefined\"\n :aria-selected=\"tab.value === modelValue ? 'true' : 'false'\"\n :aria-disabled=\"tab.disabled ? 'true' : undefined\"\n :aria-keyshortcuts=\"isClosable(tab) ? 'Delete' : undefined\"\n :tabindex=\"roving.tabindexFor(index)\"\n :class=\"tabClass(tab)\"\n @click=\"onClick($event, tab, index)\"\n >\n <span :class=\"tabContentClass\">\n <slot\n name=\"tab\"\n :tab=\"tab\"\n :active=\"tab.value === modelValue\"\n :disabled=\"Boolean(tab.disabled)\"\n >\n <span v-if=\"tab.icon\" :class=\"[tabIconClass, tab.icon]\" aria-hidden=\"true\" />\n <span>{{ tab.label }}</span>\n <span v-if=\"tab.badge\" :class=\"badgeClass\">\n {{ tab.badge }}\n </span>\n </slot>\n\n <!-- Снаружи слота: своя разметка вкладки не должна отнимать закрытие. -->\n <span\n v-if=\"isClosable(tab)\"\n data-gr-tab-close\n :class=\"closeClass\"\n :title=\"closeTitle\"\n aria-hidden=\"true\"\n >\n <IconX :class=\"closeIconClass\" />\n </span>\n </span>\n </button>\n </div>\n</template>\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"GrTree-CaYK8dGZ.js","names":[],"sources":["../../../../src/components/GrTree/grTreeDataAdapter.ts","../../../../src/components/GrTree/grTreeInteractionContext.ts","../../../../src/components/GrTree/grTreeChecking.ts","../../../../src/components/GrTree/grTreeStore.ts","../../../../src/components/GrTree/grTreeStyles.ts","../../../../src/components/GrTree/GrTree.vue","../../../../src/components/GrTree/GrTree.vue"],"sourcesContent":["import type { GrTreeKey } from './grTreeTypes'\nimport type {\n GrTreeDataProps,\n GrTreePropsMap,\n} from './grTreeProps'\n\nconst DEFAULT_CHILDREN_KEY = 'children'\nconst DEFAULT_LABEL_KEY = 'label'\nconst DEFAULT_IS_LEAF_KEY = 'isLeaf'\n\nexport type GrTreeDataAdapterOptions<T extends object> = Pick<GrTreeDataProps<T>, 'props' | 'nodeKey'>\n\nexport type GrTreeDataAdapter<T extends object> = {\n hasNodeKey: () => boolean\n getChildrenKey: () => string\n getLabelKey: () => string\n getLabel: (data: T) => string\n /** Признак «лист» из данных. `undefined` — данные молчат, решает вызывающий. */\n getIsLeaf: (data: T) => boolean | undefined\n getChildren: (data: T) => T[]\n ensureChildren: (data: T) => T[]\n getNodeKey: (data: T, index: number, parentKey: GrTreeKey | undefined) => GrTreeKey\n getExplicitNodeKey: (data: T) => GrTreeKey | undefined\n}\n\nfunction isTreeKey(value: unknown): value is GrTreeKey {\n return typeof value === 'string' || typeof value === 'number'\n}\n\nfunction resolveMapValue(map: GrTreePropsMap | undefined, key: keyof GrTreePropsMap, fallback: string) {\n return map?.[key] ?? fallback\n}\n\nexport function createGrTreeDataAdapter<T extends Record<string, any> = any>(\n options: GrTreeDataAdapterOptions<T>,\n): GrTreeDataAdapter<T> {\n function hasNodeKey() {\n return options.nodeKey != null\n }\n\n function getChildrenKey() {\n return resolveMapValue(options.props, 'children', DEFAULT_CHILDREN_KEY)\n }\n\n function getLabelKey() {\n return resolveMapValue(options.props, 'label', DEFAULT_LABEL_KEY)\n }\n\n function getLabel(data: T): string {\n const key = getLabelKey() as keyof T\n const value = data[key]\n return value == null ? '' : String(value)\n }\n\n function getIsLeaf(data: T): boolean | undefined {\n const key = resolveMapValue(options.props, 'isLeaf', DEFAULT_IS_LEAF_KEY)\n const value = (data as Record<string, unknown>)[key]\n return typeof value === 'boolean' ? value : undefined\n }\n\n function getChildren(data: T): T[] {\n const key = getChildrenKey() as keyof T\n const value = data[key]\n return Array.isArray(value) ? (value as T[]) : []\n }\n\n function ensureChildren(data: T): T[] {\n const key = getChildrenKey()\n const value = (data as Record<string, unknown>)[key]\n if (Array.isArray(value))\n return value as T[]\n\n ;(data as Record<string, unknown>)[key] = []\n\n // Читаем обратно, а не возвращаем только что созданный массив: у\n // реактивных данных чтение отдаёт прокси, и только запись через него\n // видна дереву. Иначе `push` уходит мимо реактивности, и добавленные\n // дети не появляются на экране.\n return (data as Record<string, unknown>)[key] as T[]\n }\n\n function getExplicitNodeKey(data: T): GrTreeKey | undefined {\n if (options.nodeKey == null)\n return undefined\n\n const keyName = options.nodeKey as keyof T\n const value = data[keyName] as unknown\n return isTreeKey(value) ? value : undefined\n }\n\n function getNodeKey(data: T, index: number, parentKey: GrTreeKey | undefined): GrTreeKey {\n const explicitKey = getExplicitNodeKey(data)\n if (explicitKey != null)\n return explicitKey\n\n return parentKey == null ? index : `${String(parentKey)}:${index}`\n }\n\n return {\n hasNodeKey,\n getChildrenKey,\n getLabelKey,\n getLabel,\n getIsLeaf,\n getChildren,\n ensureChildren,\n getNodeKey,\n getExplicitNodeKey,\n }\n}","import type { Ref, ShallowRef } from 'vue'\nimport { ref, shallowRef } from 'vue'\nimport type {\n GrTreeAllowDropType,\n GrTreeKey,\n GrTreeNode,\n GrTreeNodeDropType,\n} from './grTreeTypes'\n\nexport type GrTreeDropTarget = {\n key: GrTreeKey\n type: GrTreeNodeDropType\n allowed: boolean\n}\n\nexport type GrTreeInteractionOptions<T> = {\n draggable?: boolean\n allowDrop?: (draggingNode: GrTreeNode<T>, dropNode: GrTreeNode<T>, type: GrTreeAllowDropType) => boolean\n allowDrag?: (draggingNode: GrTreeNode<T>) => boolean\n}\n\nexport type GrTreeInteractionEmitters<T> = {\n emitNodeClick: (data: T, node: GrTreeNode<T>) => void\n emitNodeExpand: (data: T, node: GrTreeNode<T>) => void\n emitNodeCollapse: (data: T, node: GrTreeNode<T>) => void\n emitNodeDrop: (draggingNode: GrTreeNode<T>, dropNode: GrTreeNode<T>, dropType: GrTreeNodeDropType) => void\n emitNodeContextMenu: (evt: MouseEvent, data: T, node: GrTreeNode<T>) => void\n}\n\nexport type GrTreeInteractionContext<T> = {\n hoveredKey: Ref<GrTreeKey | undefined>\n /** Клавиша узла, держащего roving-фокус (единственный tabindex=0 во всём дереве). */\n /**\n * Реестр DOM-узлов строк, общий на все уровни дерева. Нужен клавиатуре: без\n * него каждое нажатие стрелки обходило бы весь DOM поддерева в поисках\n * строки по `data`-атрибуту.\n */\n nodeEls: Map<GrTreeKey, HTMLElement>\n registerNodeEl: (key: GrTreeKey, el: HTMLElement | null) => void\n draggingNode: ShallowRef<GrTreeNode<T> | null>\n dropTarget: ShallowRef<GrTreeDropTarget | null>\n canDrag: (node: GrTreeNode<T>) => boolean\n canDrop: (drag: GrTreeNode<T>, target: GrTreeNode<T>, type: GrTreeAllowDropType) => boolean\n resetDragState: () => void\n emitNodeClick: (data: T, node: GrTreeNode<T>) => void\n emitNodeExpand: (data: T, node: GrTreeNode<T>) => void\n emitNodeCollapse: (data: T, node: GrTreeNode<T>) => void\n emitNodeDrop: (draggingNode: GrTreeNode<T>, dropNode: GrTreeNode<T>, dropType: GrTreeNodeDropType) => void\n emitNodeContextMenu: (evt: MouseEvent, data: T, node: GrTreeNode<T>) => void\n}\n\nexport function createGrTreeInteractionContext<T>(\n options: GrTreeInteractionOptions<T>,\n emitters: GrTreeInteractionEmitters<T>,\n): GrTreeInteractionContext<T> {\n const hoveredKey = ref<GrTreeKey | undefined>(undefined)\n const draggingNode = shallowRef<GrTreeNode<T> | null>(null)\n const dropTarget = shallowRef<GrTreeDropTarget | null>(null)\n const nodeEls = new Map<GrTreeKey, HTMLElement>()\n\n function registerNodeEl(key: GrTreeKey, el: HTMLElement | null) {\n if (el) {\n nodeEls.set(key, el)\n return\n }\n\n // Перенос узла в другую ветку размонтирует старый элемент уже после того,\n // как зарегистрировался новый: снимаем запись, только если она протухла.\n const previous = nodeEls.get(key)\n if (previous && !previous.isConnected)\n nodeEls.delete(key)\n }\n\n function canDrag(node: GrTreeNode<T>): boolean {\n if (!options.draggable)\n return false\n\n if (options.allowDrag)\n return options.allowDrag(node)\n\n return true\n }\n\n function canDrop(drag: GrTreeNode<T>, target: GrTreeNode<T>, type: GrTreeAllowDropType): boolean {\n if (!options.allowDrop)\n return true\n\n return options.allowDrop(drag, target, type)\n }\n\n function resetDragState() {\n draggingNode.value = null\n dropTarget.value = null\n }\n\n return {\n hoveredKey,\n nodeEls,\n registerNodeEl,\n draggingNode,\n dropTarget,\n canDrag,\n canDrop,\n resetDragState,\n ...emitters,\n }\n}","import type { GrTreeKey, GrTreeNode } from './grTreeTypes'\n\n/**\n * Состояние отметки узла. `half` — часть потомков отмечена: в ARIA это\n * `aria-checked=\"mixed\"`.\n */\nexport const GR_TREE_CHECK_STATES = ['checked', 'half', 'unchecked'] as const\n\nexport type GrTreeCheckState = typeof GR_TREE_CHECK_STATES[number]\n\nexport type GrTreeCheckStates = Map<GrTreeKey, GrTreeCheckState>\n\n/**\n * Раскладывает отмеченные ключи по всему дереву: родитель отмечен, когда\n * отмечены все дети, и наполовину — когда часть.\n *\n * Считается от листьев вверх, поэтому `checkedKeys` снаружи может содержать\n * только листья (типовой формат ответа сервера) — родители посчитаются сами.\n * При `strict` дерево не связывает узлы вовсе: каждый отвечает сам за себя.\n */\nexport function resolveCheckStates<T>(\n roots: GrTreeNode<T>[],\n checkedKeys: Set<GrTreeKey>,\n strict: boolean,\n): GrTreeCheckStates {\n const states: GrTreeCheckStates = new Map()\n\n const walk = (node: GrTreeNode<T>): GrTreeCheckState => {\n if (strict || node.childNodes.length === 0) {\n const state: GrTreeCheckState = checkedKeys.has(node.key) ? 'checked' : 'unchecked'\n // В strict-режиме потомков всё равно нужно обойти: у них своё состояние.\n if (strict)\n node.childNodes.forEach(walk)\n states.set(node.key, state)\n return state\n }\n\n const childStates = node.childNodes.map(walk)\n const allChecked = childStates.every(s => s === 'checked')\n const someMarked = childStates.some(s => s !== 'unchecked')\n\n const state: GrTreeCheckState = allChecked\n ? 'checked'\n : (someMarked || checkedKeys.has(node.key) ? 'half' : 'unchecked')\n\n states.set(node.key, state)\n return state\n }\n\n roots.forEach(walk)\n return states\n}\n\n/**\n * Новый набор отмеченных ключей после переключения узла.\n *\n * Каскад идёт вниз (все потомки повторяют решение) и вверх (родитель отмечен,\n * только если отмечены все его дети). Возвращается набор с родителями —\n * `getCheckedKeys({ leafOnly: true })` при необходимости отбросит их обратно.\n */\nexport function toggleCheckedKeys<T>(\n roots: GrTreeNode<T>[],\n checkedKeys: Set<GrTreeKey>,\n node: GrTreeNode<T>,\n checked: boolean,\n strict: boolean,\n): Set<GrTreeKey> {\n const next = new Set(checkedKeys)\n\n if (strict) {\n if (checked) next.add(node.key)\n else next.delete(node.key)\n return next\n }\n\n const applyDown = (target: GrTreeNode<T>): void => {\n if (checked) next.add(target.key)\n else next.delete(target.key)\n target.childNodes.forEach(applyDown)\n }\n applyDown(node)\n\n for (let parent = node.parent; parent; parent = parent.parent) {\n const allChecked = parent.childNodes.every(child => next.has(child.key))\n if (allChecked) next.add(parent.key)\n else next.delete(parent.key)\n }\n\n // Отметки узлов, которых уже нет в дереве, тянуть незачем.\n return pruneToTree(roots, next)\n}\n\n/** Отбрасывает ключи, которых нет в текущей модели (данные могли смениться). */\nexport function pruneToTree<T>(roots: GrTreeNode<T>[], keys: Set<GrTreeKey>): Set<GrTreeKey> {\n const known = new Set<GrTreeKey>()\n const walk = (node: GrTreeNode<T>): void => {\n known.add(node.key)\n node.childNodes.forEach(walk)\n }\n roots.forEach(walk)\n\n const next = new Set<GrTreeKey>()\n for (const key of keys) {\n if (known.has(key)) next.add(key)\n }\n return next\n}\n\n/** Ключи в состоянии `checked`; `leafOnly` оставляет только листья. */\nexport function collectCheckedKeys<T>(\n roots: GrTreeNode<T>[],\n states: GrTreeCheckStates,\n leafOnly: boolean,\n): GrTreeKey[] {\n const out: GrTreeKey[] = []\n\n const walk = (node: GrTreeNode<T>): void => {\n const isLeaf = node.childNodes.length === 0\n if (states.get(node.key) === 'checked' && (!leafOnly || isLeaf))\n out.push(node.key)\n node.childNodes.forEach(walk)\n }\n\n roots.forEach(walk)\n return out\n}\n\n/** Ключи в состоянии `half` — родители с частично отмеченными потомками. */\nexport function collectHalfCheckedKeys(states: GrTreeCheckStates): GrTreeKey[] {\n const out: GrTreeKey[] = []\n for (const [key, state] of states) {\n if (state === 'half') out.push(key)\n }\n return out\n}\n","import type { ComputedRef, MaybeRefOrGetter, Ref, ShallowRef } from 'vue'\nimport { computed, ref, shallowRef, toValue, watch } from 'vue'\nimport type {\n GrTreeKey,\n GrTreeNode,\n GrTreeNodeDropType,\n GrTreeNodeTarget,\n} from './grTreeTypes'\nimport type { GrTreeDataAdapter } from './grTreeDataAdapter'\nimport type { GrTreeDataProps, GrTreeFilterNodeMethod, GrTreeLoad } from './grTreeProps'\nimport type { GrTreeCheckState, GrTreeCheckStates } from './grTreeChecking'\nimport {\n collectCheckedKeys,\n collectHalfCheckedKeys,\n pruneToTree,\n resolveCheckStates,\n toggleCheckedKeys,\n} from './grTreeChecking'\n\ntype TreeModel<T extends object> = {\n roots: GrTreeNode<T>[]\n byKey: Map<GrTreeKey, GrTreeNode<T>>\n byData: WeakMap<T, GrTreeNode<T>>\n}\n\ntype FilterInfo = {\n isActive: boolean\n subtreeHasMatch: Map<GrTreeKey, boolean>\n matchedKeys: Set<GrTreeKey>\n autoExpandKeys: Set<GrTreeKey>\n}\n\nexport type GrTreeStoreOptions<T extends object> = {\n data: MaybeRefOrGetter<GrTreeDataProps<T>['data']>\n defaultExpandedKeys?: MaybeRefOrGetter<GrTreeDataProps<T>['defaultExpandedKeys']>\n defaultExpandAll?: MaybeRefOrGetter<boolean | undefined>\n filterNodeMethod?: MaybeRefOrGetter<GrTreeFilterNodeMethod<T> | undefined>\n defaultCheckedKeys?: MaybeRefOrGetter<GrTreeKey[] | undefined>\n /** Внешние отмеченные ключи (`v-model:checked-keys`), если потребитель их ведёт. */\n checkedKeys?: MaybeRefOrGetter<GrTreeKey[] | undefined>\n checkStrictly?: MaybeRefOrGetter<boolean | undefined>\n lazy?: MaybeRefOrGetter<boolean | undefined>\n load?: MaybeRefOrGetter<GrTreeLoad<T> | undefined>\n adapter: GrTreeDataAdapter<T>\n}\n\nexport type GrTreeStore<T extends object> = {\n filterValue: Ref<string>\n expandedKeys: ShallowRef<Set<GrTreeKey>>\n checkedKeys: ShallowRef<Set<GrTreeKey>>\n checkStates: ComputedRef<GrTreeCheckStates>\n loadingKeys: ShallowRef<Set<GrTreeKey>>\n loadedKeys: ShallowRef<Set<GrTreeKey>>\n loadedChildren: ShallowRef<Map<GrTreeKey, T[]>>\n currentKey: Ref<GrTreeKey | undefined>\n treeModel: ComputedRef<TreeModel<T>>\n filterInfo: ComputedRef<FilterInfo>\n filter: (value: string) => void\n isExpandedKey: (key: GrTreeKey) => boolean\n setExpandedKey: (key: GrTreeKey, expanded: boolean) => void\n toggleExpand: (node: GrTreeNode<T>) => void\n setCurrentKey: (key?: GrTreeKey) => void\n getCurrentKey: () => GrTreeKey | undefined\n getCurrentNode: () => GrTreeNode<T> | undefined\n setCurrentNode: (node?: GrTreeNodeTarget<T>) => boolean\n getNode: (key: GrTreeKey) => GrTreeNode<T> | undefined\n canMoveNode: (node: GrTreeNodeTarget<T>, referenceNode: GrTreeNodeTarget<T>, dropType: GrTreeNodeDropType) => boolean\n moveNode: (node: GrTreeNodeTarget<T>, referenceNode: GrTreeNodeTarget<T>, dropType: GrTreeNodeDropType) => GrTreeNode<T> | undefined\n appendNode: (data: T, parent: GrTreeNodeTarget<T>) => GrTreeNode<T> | undefined\n removeNode: (node: GrTreeNodeTarget<T>) => boolean\n insertNodeBefore: (data: T, referenceNode: GrTreeNodeTarget<T>) => GrTreeNode<T> | undefined\n insertNodeAfter: (data: T, referenceNode: GrTreeNodeTarget<T>) => GrTreeNode<T> | undefined\n getCheckState: (key: GrTreeKey) => GrTreeCheckState\n setChecked: (node: GrTreeNodeTarget<T>, checked: boolean) => GrTreeNode<T> | undefined\n toggleChecked: (node: GrTreeNode<T>) => GrTreeNode<T> | undefined\n getCheckedKeys: (options?: { leafOnly?: boolean }) => GrTreeKey[]\n setCheckedKeys: (keys: GrTreeKey[]) => void\n getHalfCheckedKeys: () => GrTreeKey[]\n /** Лист ли узел с точки зрения раскрытия: в ленивом режиме решают данные и факт загрузки. */\n isLeafNode: (node: GrTreeNode<T>) => boolean\n isLoadingKey: (key: GrTreeKey) => boolean\n /** Грузит детей ветки, если это нужно и ещё не делалось. */\n ensureLoaded: (node: GrTreeNode<T>) => void\n}\n\nexport function createGrTreeStore<T extends Record<string, any> = any>(\n options: GrTreeStoreOptions<T>,\n): GrTreeStore<T> {\n const filterValue = ref('')\n const expandedKeys = shallowRef<Set<GrTreeKey>>(new Set())\n const checkedKeys = shallowRef<Set<GrTreeKey>>(new Set(toValue(options.defaultCheckedKeys) ?? []))\n const loadingKeys = shallowRef<Set<GrTreeKey>>(new Set())\n const loadedKeys = shallowRef<Set<GrTreeKey>>(new Set())\n // Дети, пришедшие ленивой загрузкой, живут в сторе, а не в данных потребителя:\n // `data` — обычный проп и реактивным быть не обязан, а дерево обязано\n // показать загруженное в любом случае.\n const loadedChildren = shallowRef<Map<GrTreeKey, T[]>>(new Map())\n const currentKey = ref<GrTreeKey | undefined>(undefined)\n const getData = () => toValue(options.data) ?? []\n const getFilterNodeMethod = () => toValue(options.filterNodeMethod)\n\n watch(\n () => toValue(options.defaultExpandedKeys),\n (keys) => {\n expandedKeys.value = new Set(keys ?? [])\n },\n { immediate: true },\n )\n\n // Внешние отметки: компонент управляемый, пока проп задан. Не задан — ведёт\n // набор сам, начиная с `defaultCheckedKeys`.\n watch(\n () => toValue(options.checkedKeys),\n (keys) => {\n if (keys)\n checkedKeys.value = new Set(keys)\n },\n { immediate: true },\n )\n\n const treeModel = computed<TreeModel<T>>(() => {\n const byKey = new Map<GrTreeKey, GrTreeNode<T>>()\n const byData = new WeakMap<T, GrTreeNode<T>>()\n\n const build = (items: T[], parent: GrTreeNode<T> | undefined, level: number): GrTreeNode<T>[] => {\n return items.map((item, index) => {\n const node: GrTreeNode<T> = {\n key: options.adapter.getNodeKey(item, index, parent?.key),\n label: options.adapter.getLabel(item),\n data: item,\n level,\n parent,\n childNodes: [],\n }\n\n byKey.set(node.key, node)\n byData.set(item, node)\n\n const ownChildren = options.adapter.getChildren(item)\n const lazyChildren = loadedChildren.value.get(node.key) ?? []\n node.childNodes = build([...ownChildren, ...lazyChildren], node, level + 1)\n\n return node\n })\n }\n\n return {\n roots: build(getData(), undefined, 1),\n byKey,\n byData,\n }\n })\n\n // `defaultExpandAll` раскрывает узел один раз — в момент его появления в\n // данных. Иначе любое обновление `data` отменяло бы всё, что пользователь\n // свернул руками.\n const autoExpandedKeys = new Set<GrTreeKey>()\n watch(treeModel, (model) => {\n if (!toValue(options.defaultExpandAll))\n return\n\n const next = new Set(expandedKeys.value)\n let changed = false\n\n for (const key of model.byKey.keys()) {\n if (autoExpandedKeys.has(key))\n continue\n\n autoExpandedKeys.add(key)\n next.add(key)\n changed = true\n }\n\n if (changed)\n expandedKeys.value = next\n }, { immediate: true })\n\n const filterInfo = computed<FilterInfo>(() => {\n const value = filterValue.value\n const isActive = value.trim().length > 0\n const valueLower = value.toLowerCase()\n const subtreeHasMatch = new Map<GrTreeKey, boolean>()\n const matchedKeys = new Set<GrTreeKey>()\n const autoExpandKeys = new Set<GrTreeKey>()\n\n if (!isActive) {\n return {\n isActive,\n subtreeHasMatch,\n matchedKeys,\n autoExpandKeys,\n }\n }\n\n const match = (node: GrTreeNode<T>): boolean => {\n const filterNodeMethod = getFilterNodeMethod()\n if (filterNodeMethod)\n return filterNodeMethod(value, node.data, node)\n\n return node.label.toLowerCase().includes(valueLower)\n }\n\n const walk = (nodes: GrTreeNode<T>[]): boolean => {\n let any = false\n\n for (const node of nodes) {\n const self = match(node)\n const childAny = walk(node.childNodes)\n const has = self || childAny\n\n subtreeHasMatch.set(node.key, has)\n\n if (self)\n matchedKeys.add(node.key)\n\n if (childAny)\n autoExpandKeys.add(node.key)\n\n any ||= has\n }\n\n return any\n }\n\n walk(treeModel.value.roots)\n\n return {\n isActive,\n subtreeHasMatch,\n matchedKeys,\n autoExpandKeys,\n }\n })\n\n function getNode(key: GrTreeKey) {\n return treeModel.value.byKey.get(key)\n }\n\n function findNodeByData(data: T): GrTreeNode<T> | undefined {\n return treeModel.value.byData.get(data)\n }\n\n function isTreeNode(target: GrTreeNodeTarget<T>): target is GrTreeNode<T> {\n return typeof target === 'object'\n && target !== null\n && 'key' in target\n && 'data' in target\n && 'childNodes' in target\n }\n\n function resolveNodeTarget(target?: GrTreeNodeTarget<T>): GrTreeNode<T> | undefined {\n if (target == null)\n return undefined\n\n if (typeof target === 'string' || typeof target === 'number')\n return getNode(target)\n\n if (isTreeNode(target))\n return getNode(target.key) ?? target\n\n const key = options.adapter.getExplicitNodeKey(target)\n if (key != null)\n return getNode(key)\n\n return findNodeByData(target)\n }\n\n function getNodeContainer(node: GrTreeNode<T>) {\n const items = node.parent ? options.adapter.ensureChildren(node.parent.data) : getData()\n\n let index = items.findIndex(item => item === node.data)\n if (index >= 0)\n return { items, index }\n\n const key = options.adapter.getExplicitNodeKey(node.data)\n if (key == null)\n return undefined\n\n index = items.findIndex(item => options.adapter.getExplicitNodeKey(item) === key)\n if (index < 0)\n return undefined\n\n return { items, index }\n }\n\n function isDescendantNode(ancestor: GrTreeNode<T>, node: GrTreeNode<T>) {\n let current = node.parent\n\n while (current) {\n if (current.key === ancestor.key)\n return true\n\n current = current.parent\n }\n\n return false\n }\n\n function canMoveNode(node: GrTreeNodeTarget<T>, referenceNode: GrTreeNodeTarget<T>, dropType: GrTreeNodeDropType) {\n const sourceNode = resolveNodeTarget(node)\n const targetNode = resolveNodeTarget(referenceNode)\n\n if (!sourceNode || !targetNode)\n return false\n\n if (sourceNode.key === targetNode.key)\n return false\n\n if (isDescendantNode(sourceNode, targetNode))\n return false\n\n const sourceContainer = getNodeContainer(sourceNode)\n if (!sourceContainer)\n return false\n\n if (dropType === 'inner')\n return true\n\n return Boolean(getNodeContainer(targetNode))\n }\n\n function sanitizeTreeState() {\n const byKey = treeModel.value.byKey\n\n if (currentKey.value != null && !byKey.has(currentKey.value))\n currentKey.value = undefined\n\n const nextExpandedKeys = new Set(Array.from(expandedKeys.value).filter(key => byKey.has(key)))\n if (nextExpandedKeys.size !== expandedKeys.value.size)\n expandedKeys.value = nextExpandedKeys\n\n const nextCheckedKeys = new Set(Array.from(checkedKeys.value).filter(key => byKey.has(key)))\n if (nextCheckedKeys.size !== checkedKeys.value.size)\n checkedKeys.value = nextCheckedKeys\n }\n\n function isExpandedKey(key: GrTreeKey): boolean {\n return expandedKeys.value.has(key)\n }\n\n function setExpandedKey(key: GrTreeKey, expanded: boolean) {\n const next = new Set(expandedKeys.value)\n\n if (expanded)\n next.add(key)\n else\n next.delete(key)\n\n expandedKeys.value = next\n }\n\n function toggleExpand(node: GrTreeNode<T>) {\n setExpandedKey(node.key, !isExpandedKey(node.key))\n }\n\n function setCurrentKey(key?: GrTreeKey) {\n currentKey.value = key\n }\n\n function getCurrentKey() {\n return currentKey.value\n }\n\n function getCurrentNode() {\n const key = currentKey.value\n return key == null ? undefined : getNode(key)\n }\n\n function setCurrentNode(node?: GrTreeNodeTarget<T>) {\n if (node == null) {\n currentKey.value = undefined\n return true\n }\n\n const targetNode = resolveNodeTarget(node)\n if (!targetNode)\n return false\n\n currentKey.value = targetNode.key\n return true\n }\n\n function resolveInsertedNode(data: T) {\n const key = options.adapter.getExplicitNodeKey(data)\n if (key != null)\n return getNode(key)\n\n return findNodeByData(data)\n }\n\n function moveNode(node: GrTreeNodeTarget<T>, referenceNode: GrTreeNodeTarget<T>, dropType: GrTreeNodeDropType) {\n const sourceNode = resolveNodeTarget(node)\n const targetNode = resolveNodeTarget(referenceNode)\n\n if (!sourceNode || !targetNode)\n return undefined\n\n if (!canMoveNode(sourceNode, targetNode, dropType))\n return undefined\n\n const sourceContainer = getNodeContainer(sourceNode)\n if (!sourceContainer)\n return undefined\n\n let destinationItems: T[]\n let destinationIndex: number\n\n if (dropType === 'inner') {\n destinationItems = options.adapter.ensureChildren(targetNode.data)\n destinationIndex = destinationItems.length\n }\n else {\n const targetContainer = getNodeContainer(targetNode)\n if (!targetContainer)\n return undefined\n\n destinationItems = targetContainer.items\n destinationIndex = dropType === 'prev'\n ? targetContainer.index\n : targetContainer.index + 1\n }\n\n if (destinationItems === sourceContainer.items && sourceContainer.index < destinationIndex)\n destinationIndex -= 1\n\n const [movedData] = sourceContainer.items.splice(sourceContainer.index, 1)\n destinationItems.splice(destinationIndex, 0, movedData)\n\n if (dropType === 'inner')\n setExpandedKey(targetNode.key, true)\n\n sanitizeTreeState()\n return resolveInsertedNode(movedData)\n }\n\n function appendNode(data: T, parent: GrTreeNodeTarget<T>) {\n const parentNode = resolveNodeTarget(parent)\n if (!parentNode)\n return undefined\n\n options.adapter.ensureChildren(parentNode.data).push(data)\n setExpandedKey(parentNode.key, true)\n sanitizeTreeState()\n return resolveInsertedNode(data)\n }\n\n function removeNode(node: GrTreeNodeTarget<T>) {\n if (!options.adapter.hasNodeKey())\n return false\n\n const targetNode = resolveNodeTarget(node)\n if (!targetNode)\n return false\n\n const container = getNodeContainer(targetNode)\n if (!container)\n return false\n\n container.items.splice(container.index, 1)\n sanitizeTreeState()\n return true\n }\n\n function insertNodeBefore(data: T, referenceNode: GrTreeNodeTarget<T>) {\n const targetNode = resolveNodeTarget(referenceNode)\n if (!targetNode)\n return undefined\n\n const container = getNodeContainer(targetNode)\n if (!container)\n return undefined\n\n container.items.splice(container.index, 0, data)\n sanitizeTreeState()\n return resolveInsertedNode(data)\n }\n\n function insertNodeAfter(data: T, referenceNode: GrTreeNodeTarget<T>) {\n const targetNode = resolveNodeTarget(referenceNode)\n if (!targetNode)\n return undefined\n\n const container = getNodeContainer(targetNode)\n if (!container)\n return undefined\n\n container.items.splice(container.index + 1, 0, data)\n sanitizeTreeState()\n return resolveInsertedNode(data)\n }\n\n // ————— Отметки (чекбоксы).\n\n const checkStates = computed<GrTreeCheckStates>(() => resolveCheckStates(\n treeModel.value.roots,\n checkedKeys.value,\n Boolean(toValue(options.checkStrictly)),\n ))\n\n function getCheckState(key: GrTreeKey): GrTreeCheckState {\n return checkStates.value.get(key) ?? 'unchecked'\n }\n\n function setChecked(node: GrTreeNodeTarget<T>, checked: boolean) {\n const targetNode = resolveNodeTarget(node)\n if (!targetNode)\n return undefined\n\n checkedKeys.value = toggleCheckedKeys(\n treeModel.value.roots,\n checkedKeys.value,\n targetNode,\n checked,\n Boolean(toValue(options.checkStrictly)),\n )\n\n return targetNode\n }\n\n function toggleChecked(node: GrTreeNode<T>) {\n return setChecked(node, getCheckState(node.key) !== 'checked')\n }\n\n function getCheckedKeys(opts?: { leafOnly?: boolean }) {\n return collectCheckedKeys(treeModel.value.roots, checkStates.value, Boolean(opts?.leafOnly))\n }\n\n function setCheckedKeys(keys: GrTreeKey[]) {\n checkedKeys.value = pruneToTree(treeModel.value.roots, new Set(keys))\n }\n\n function getHalfCheckedKeys() {\n return collectHalfCheckedKeys(checkStates.value)\n }\n\n // ————— Ленивая подгрузка ветки.\n\n function isLeafNode(node: GrTreeNode<T>): boolean {\n if (node.childNodes.length > 0)\n return false\n\n if (!toValue(options.lazy))\n return true\n\n const declared = options.adapter.getIsLeaf(node.data)\n if (declared !== undefined)\n return declared\n\n // Данные молчат: пока ветку не грузили, считаем её разворачиваемой —\n // иначе до первого запроса дело бы не дошло.\n return loadedKeys.value.has(node.key)\n }\n\n function isLoadingKey(key: GrTreeKey): boolean {\n return loadingKeys.value.has(key)\n }\n\n function markLoading(key: GrTreeKey, loading: boolean) {\n const next = new Set(loadingKeys.value)\n if (loading)\n next.add(key)\n else\n next.delete(key)\n loadingKeys.value = next\n }\n\n function ensureLoaded(node: GrTreeNode<T>) {\n const load = toValue(options.load)\n if (!toValue(options.lazy) || !load)\n return\n\n if (loadedKeys.value.has(node.key) || loadingKeys.value.has(node.key) || isLeafNode(node))\n return\n\n markLoading(node.key, true)\n\n let settled = false\n load(node, (children) => {\n // Ответ приходит от потребителя: второй вызов `resolve` — его ошибка, но\n // дублировать детей из-за неё дерево не обязано.\n if (settled)\n return\n settled = true\n\n const next = new Map(loadedChildren.value)\n next.set(node.key, [...(children ?? [])])\n loadedChildren.value = next\n loadedKeys.value = new Set(loadedKeys.value).add(node.key)\n markLoading(node.key, false)\n })\n }\n function filter(value: string) {\n filterValue.value = value\n }\n\n return {\n filterValue,\n expandedKeys,\n loadedChildren,\n checkedKeys,\n checkStates,\n loadingKeys,\n loadedKeys,\n currentKey,\n treeModel,\n filterInfo,\n filter,\n isExpandedKey,\n setExpandedKey,\n toggleExpand,\n setCurrentKey,\n getCurrentKey,\n getCurrentNode,\n setCurrentNode,\n getNode,\n canMoveNode,\n moveNode,\n appendNode,\n removeNode,\n insertNodeBefore,\n insertNodeAfter,\n getCheckState,\n setChecked,\n toggleChecked,\n getCheckedKeys,\n setCheckedKeys,\n getHalfCheckedKeys,\n isLeafNode,\n isLoadingKey,\n ensureLoaded,\n }\n}","import type { GrComponentSize } from '../shared/sizes'\n\nexport type GrTreeSize = GrComponentSize\n\n/**\n * Размер дерева выражен переменными, а не утилитарными классами: те же\n * `--gr-tree-*` уже объявлены точками кастомизации в `GrTree.vue`, и size\n * обязан задавать им дефолты, а не спорить с ними отдельным каналом.\n *\n * `font-size` на `md` намеренно `inherit`: дерево по умолчанию набирается\n * кеглем родителя, и подстановка конкретных пикселей сменила бы вид всем,\n * кто ничего не просил.\n */\nexport type GrTreeSizeVars = {\n '--gr-tree-row-min-height': string\n '--gr-tree-row-px': string\n '--gr-tree-row-py': string\n '--gr-tree-toggle-size': string\n '--gr-tree-drag-handle-size': string\n '--gr-tree-icon-size': string\n '--gr-tree-content-gap': string\n '--gr-tree-font-size': string\n}\n\nexport const treeSizeVars: Record<GrTreeSize, GrTreeSizeVars> = {\n xs: {\n '--gr-tree-row-min-height': '22px',\n '--gr-tree-row-px': '4px',\n '--gr-tree-row-py': '3px',\n '--gr-tree-toggle-size': '18px',\n '--gr-tree-drag-handle-size': '18px',\n '--gr-tree-icon-size': '12px',\n '--gr-tree-content-gap': '4px',\n '--gr-tree-font-size': '12px',\n },\n sm: {\n '--gr-tree-row-min-height': '24px',\n '--gr-tree-row-px': '6px',\n '--gr-tree-row-py': '5px',\n '--gr-tree-toggle-size': '20px',\n '--gr-tree-drag-handle-size': '20px',\n '--gr-tree-icon-size': '14px',\n '--gr-tree-content-gap': '6px',\n '--gr-tree-font-size': '13px',\n },\n md: {\n '--gr-tree-row-min-height': '28px',\n '--gr-tree-row-px': '8px',\n '--gr-tree-row-py': '8px',\n '--gr-tree-toggle-size': '24px',\n '--gr-tree-drag-handle-size': '24px',\n '--gr-tree-icon-size': '16px',\n '--gr-tree-content-gap': '8px',\n '--gr-tree-font-size': 'inherit',\n },\n lg: {\n '--gr-tree-row-min-height': '32px',\n '--gr-tree-row-px': '10px',\n '--gr-tree-row-py': '10px',\n '--gr-tree-toggle-size': '28px',\n '--gr-tree-drag-handle-size': '28px',\n '--gr-tree-icon-size': '20px',\n '--gr-tree-content-gap': '10px',\n '--gr-tree-font-size': '15px',\n },\n}\n","<script setup lang=\"ts\" generic=\"T extends Record<string, any> = any\">\nimport { computed, nextTick, onBeforeUnmount, onUnmounted, ref } from 'vue'\nimport type {\n GrTreeAllowDropType,\n GrTreeInstance,\n GrTreeKey,\n GrTreeNode,\n GrTreeNodeDropType,\n} from './grTreeTypes'\nimport { createGrTreeDataAdapter } from './grTreeDataAdapter'\nimport type { GrTreeDropTarget } from './grTreeInteractionContext'\nimport { createGrTreeInteractionContext } from './grTreeInteractionContext'\nimport type { GrTreeCheckState } from './grTreeChecking'\nimport { createGrTreeStore } from './grTreeStore'\nimport { treeSizeVars } from './grTreeStyles'\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport { useAnnouncer } from '../../composables/useAnnouncer'\nimport { useDragSort } from '../../composables/useDragSort'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { useVirtualList } from '../../composables/useVirtualList'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport type {\n GrTreeBranchLineColor,\n GrTreeNodeClass,\n GrTreeProps,\n GrTreeVisibleRow,\n} from './grTreeProps'\n\nexport interface GrTreeEmits<T extends Record<string, any> = any> {\n (e: 'nodeClick', data: T, node: GrTreeNode<T>): void\n (e: 'nodeExpand', data: T, node: GrTreeNode<T>): void\n (e: 'nodeCollapse', data: T, node: GrTreeNode<T>): void\n (e: 'nodeDrop', draggingNode: GrTreeNode<T>, dropNode: GrTreeNode<T>, dropType: GrTreeNodeDropType): void\n (e: 'nodeContextMenu', evt: MouseEvent, data: T, node: GrTreeNode<T>): void\n (e: 'update:checkedKeys', keys: GrTreeKey[]): void\n (e: 'check', data: T, node: GrTreeNode<T>, info: { checkedKeys: GrTreeKey[], halfCheckedKeys: GrTreeKey[] }): void\n}\n\nconst DEFAULT_BRANCH_LINE_COLOR = 'var(--gr-tree-branch-line-default-color, var(--gr-brd))'\n\n/** Стрелки, которые вместе с `Shift` двигают узел, а не фокус. */\nconst MOVE_KEYS = new Set(['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'])\n\nconst props = withDefaults(defineProps<GrTreeProps<T>>(), {\n props: () => ({\n children: 'children',\n label: 'label',\n }),\n nodeKey: 'id' as any,\n defaultExpandedKeys: () => [],\n defaultExpandAll: false,\n expandOnClickNode: false,\n accordion: false,\n size: undefined,\n highlightCurrent: true,\n indent: 0,\n virtual: false,\n maxHeight: undefined,\n showCheckbox: false,\n checkedKeys: undefined,\n defaultCheckedKeys: () => [],\n checkStrictly: false,\n lazy: false,\n load: undefined,\n expandIcon: 'i-lucide-chevron-right',\n collapseIcon: 'i-lucide-chevron-right',\n toggleIconRotate: true,\n branchLine: false,\n branchLineColor: undefined,\n branchLineActiveColor: undefined,\n draggable: false,\n dragHandleIcon: 'i-lucide-grip-vertical',\n dragLabel: undefined,\n expandLabel: undefined,\n collapseLabel: undefined,\n rowClass: undefined,\n dragHandleClass: undefined,\n toggleClass: undefined,\n toggleIconClass: undefined,\n toggleSpacerClass: undefined,\n contentClass: undefined,\n})\n\nconst emit = defineEmits<GrTreeEmits<T>>()\n\ndefineSlots<{\n default?: (props: { node: GrTreeNode<T>; data: T }) => any\n}>()\n\nconst dataAdapter = createGrTreeDataAdapter(props)\nconst treeStore = createGrTreeStore({\n adapter: dataAdapter,\n data: () => props.data,\n defaultExpandedKeys: () => props.defaultExpandedKeys,\n defaultExpandAll: () => props.defaultExpandAll,\n filterNodeMethod: () => props.filterNodeMethod,\n defaultCheckedKeys: () => props.defaultCheckedKeys,\n checkedKeys: () => props.checkedKeys,\n checkStrictly: () => props.checkStrictly,\n lazy: () => props.lazy,\n load: () => props.load,\n})\nconst interactionContext = createGrTreeInteractionContext(props, {\n emitNodeClick: (data, node) => emit('nodeClick', data, node),\n emitNodeExpand: (data, node) => emit('nodeExpand', data, node),\n emitNodeCollapse: (data, node) => emit('nodeCollapse', data, node),\n emitNodeDrop: (draggingNode, dropNode, dropType) => emit('nodeDrop', draggingNode, dropNode, dropType),\n emitNodeContextMenu: (evt, data, node) => emit('nodeContextMenu', evt, data, node),\n})\n\nconst treeProps = props as Readonly<GrTreeProps<T>>\n\nconst { t } = useGranularityTranslations()\nconst { announce } = useAnnouncer()\nconst dragLabel = computed(() => treeProps.dragLabel ?? t('gr.tree.drag', 'Drag'))\nconst expandLabel = computed(() => treeProps.expandLabel ?? t('gr.tree.expand', 'Expand'))\nconst collapseLabel = computed(() => treeProps.collapseLabel ?? t('gr.tree.collapse', 'Collapse'))\nconst currentKey = treeStore.currentKey\nconst hoveredKey = interactionContext.hoveredKey\nconst dropTarget = interactionContext.dropTarget\n\n// Корневой элемент дерева — область для делегированной клавиатуры (WAI-ARIA tree).\nconst treeRootEl = ref<HTMLElement | null>(null)\n\n/**\n * Видимые строки одним плоским списком в порядке отображения.\n *\n * Плоско — потому что рекурсивный рендер стоил по инстансу компонента на каждый\n * раскрытый узел: дерево на 2 000 видимых строк превращалось в 2 000\n * компонентов со своими вычислениями и подписками. Иерархию несут `aria-level`,\n * `aria-posinset` и `aria-setsize`, отступ — `padding-left` строки.\n */\nconst visibleRows = computed<GrTreeVisibleRow<T>[]>(() => {\n const { roots } = treeStore.treeModel.value\n const { isActive, subtreeHasMatch, matchedKeys, autoExpandKeys } = treeStore.filterInfo.value\n const branch = treeProps.branchLine\n const rows: GrTreeVisibleRow<T>[] = []\n\n const walk = (nodes: GrTreeNode<T>[], ancestorColors: string[]): void => {\n const siblings = isActive ? nodes.filter(node => subtreeHasMatch.get(node.key)) : nodes\n\n siblings.forEach((node, index) => {\n const isLeaf = treeStore.isLeafNode(node)\n const isExpanded = treeStore.isExpandedKey(node.key) || (isActive && autoExpandKeys.has(node.key))\n\n rows.push({\n node,\n isExpanded,\n isLeaf,\n isMatched: matchedKeys.has(node.key),\n posInSet: index + 1,\n setSize: siblings.length,\n isLoading: treeStore.isLoadingKey(node.key),\n branchColors: ancestorColors,\n })\n\n if (isExpanded && node.childNodes.length)\n walk(node.childNodes, branch ? [...ancestorColors, resolveBranchLineColor(node)] : ancestorColors)\n })\n }\n\n walk(roots, [])\n return rows\n})\n\n/**\n * Кольцо roving-фокуса: ровно один `treeitem` на всё дерево держит `tabindex=0`.\n *\n * Ориентация вертикальная намеренно: влево-вправо в дереве заняты раскрытием\n * ветки и переходом к родителю, и примитив их не трогает — это доменная логика,\n * она осталась в `onTreeKeydown`.\n */\nconst roving = useRovingFocus<GrTreeNode<T>['key']>({\n items: () => visibleRows.value.map(row => row.node.key),\n elementFor: key => interactionContext.nodeEls.get(key),\n orientation: () => 'vertical',\n // У дерева есть верх и низ: с последней строки вниз идти некуда.\n wrap: () => false,\n // Но клавишу на краю всё равно гасим: иначе `ArrowDown` на последнем узле\n // прокрутит страницу под деревом.\n onOverflow: () => true,\n // Пока фокус не трогали, остановку держит текущий (выбранный) узел, а если\n // его не видно — первая видимая строка.\n initialKey: () => currentKey.value ?? undefined,\n beforeFocus: key => scrollRowIntoView(key),\n})\n\nfunction onRowClick(node: GrTreeNode<T>) {\n treeStore.setCurrentKey(node.key)\n roving.setActive(node.key)\n interactionContext.emitNodeClick(node.data, node)\n}\n\n// Клик мышью, в отличие от Enter, умеет ещё и раскрывать узел: `Enter` в\n// паттерне tree закреплён за выбором, и подмешивать в него раскрытие нельзя.\nfunction onRowActivate(row: GrTreeVisibleRow<T>) {\n onRowClick(row.node)\n\n if (treeProps.expandOnClickNode && !row.isLeaf)\n toggleExpand(row.node)\n}\n\nfunction onRowContextMenu(evt: MouseEvent, node: GrTreeNode<T>) {\n interactionContext.emitNodeContextMenu(evt, node.data, node)\n}\n\n// ————— Клавиатурная навигация по WAI-ARIA tree pattern (только корневой инстанс).\n\nfunction isRovingItem(key: GrTreeNode<T>['key']): boolean {\n return roving.rovingKey.value === key\n}\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrTree' })\n\n// Переменные ставим и вложенным уровням тоже: `v-bind=\"props\"` доносит до них\n// `size`, а значения одинаковые — наследование от корня не нарушается.\nconst sizeStyle = computed(() => treeSizeVars[resolvedSize.value])\n\n/**\n * Виртуализация: в DOM живёт только окно вокруг вьюпорта.\n *\n * Корень дерева и есть скроллер — обёрток-распорок между `role=\"tree\"` и\n * `role=\"treeitem\"` быть не должно, поэтому срезанное сверху и снизу держат\n * отступы самого корня.\n */\nconst TREE_ROW_GAP = 2\n\nconst rowEstimate = computed(() => Number.parseFloat(sizeStyle.value['--gr-tree-row-min-height']))\n\nconst virtualMaxHeight = computed(() => {\n const value = treeProps.maxHeight\n if (value == null) return undefined\n return typeof value === 'number' ? `${value}px` : value\n})\n\nconst virtualizer = useVirtualList({\n container: treeRootEl,\n count: () => (treeProps.virtual ? visibleRows.value.length : 0),\n // Раскрытие/фильтр сдвигают индексы строк — замеры прошлого набора невалидны.\n source: () => visibleRows.value,\n itemSize: () => rowEstimate.value,\n gap: TREE_ROW_GAP,\n // Первый рендер обязан совпасть с серверным: контейнера ещё нет, и окно\n // считается от объявленной высоты, а не от измеренной.\n viewportSize: () => (typeof treeProps.maxHeight === 'number' ? treeProps.maxHeight : undefined),\n})\n\n/** Строки к отрисовке: при выключенной виртуализации — все. */\nconst renderedRows = computed(() => {\n if (!treeProps.virtual) return visibleRows.value\n\n const { start, end } = virtualizer.range.value\n return visibleRows.value.slice(start, end)\n})\n\n/**\n * Срезанное сверху и снизу держат псевдоэлементы контейнера.\n *\n * Ни один из трёх напрашивающихся вариантов не годится:\n *\n * - `padding` контейнера — `max-height` меряет ту же коробку, и распорка в сто\n * тысяч пикселей либо раздувает скроллер до той же сотни тысяч, либо съедает\n * всю его высоту;\n * - обёртка-распорка внутри — между `role=\"tree\"` и `role=\"treeitem\"` не должно\n * быть посторонних узлов, роль потеряла бы обязательных потомков;\n * - отступы крайних отрисованных строк — распорка исчезает ровно в тот момент,\n * когда строки заменяются целиком (прыжок прокрутки), содержимое схлопывается\n * до высоты окна, и браузер обрезает `scrollTop` до почти нуля.\n *\n * `::before`/`::after` не узлы DOM: их не за что размонтировать, в дереве\n * доступности их нет, а высота приезжает переменными в том же патче, что и сами\n * строки, — схлопнуться между кадрами нечему.\n */\nconst rootStyle = computed(() => {\n if (!treeProps.virtual) return sizeStyle.value\n\n return {\n ...sizeStyle.value,\n overflow: 'auto',\n maxHeight: virtualMaxHeight.value,\n ...virtualizer.spacerStyle.value,\n }\n})\n\n/** Абсолютный индекс отрисованной строки: поиск по массиву стоил бы O(n) на строку. */\nfunction absoluteIndex(offsetInWindow: number): number {\n return treeProps.virtual ? virtualizer.range.value.start + offsetInWindow : offsetInWindow\n}\n\n/**\n * Строка размонтируется: снимаем регистрацию и, если фокус был на ней, уводим\n * его на корень — делегированный keydown живёт там, первая же стрелка вернёт\n * видимую строку через `focusRow`. Иначе фокус упал бы на `body` и клавиатура\n * дерева умерла бы до клика мышью.\n *\n * Спрашиваем сам элемент, а не `document.activeElement === body`: Vue обнуляет\n * function ref ДО удаления узла, поэтому в момент вызова фокус ещё на строке —\n * а «фокус на body» означает лишь то, что страницу никто не трогал.\n */\nlet isUnmounting = false\n\nfunction releaseNodeEl(key: GrTreeKey): void {\n const el = interactionContext.nodeEls.get(key)\n const active = typeof document === 'undefined' ? null : document.activeElement\n const heldFocus = Boolean(el && active && (el === active || el.contains(active)))\n\n interactionContext.registerNodeEl(key, null)\n\n if (heldFocus && !isUnmounting)\n treeRootEl.value?.focus({ preventScroll: true })\n}\n\n/**\n * При виртуализации узла вне окна в DOM нет: `nodeEls.get` вернул бы\n * `undefined`, а фокус уехал бы на `body` вместе с размонтированной строкой.\n * Поэтому сперва прокрутка, и только следующим тиком — фокус.\n */\nfunction scrollRowIntoView(key: GrTreeNode<T>['key']): Promise<void> {\n if (treeProps.virtual) {\n const index = visibleRows.value.findIndex(row => row.node.key === key)\n if (index >= 0) virtualizer.scrollToIndex(index)\n }\n\n return nextTick()\n}\n\nfunction focusRow(key: GrTreeNode<T>['key']): void {\n void roving.focusKey(key)\n}\n\n/**\n * Typeahead паттерна tree: печатные символы копятся в буфер и переводят фокус\n * на первый подходящий видимый узел, начиная со следующего за текущим.\n */\nconst TYPEAHEAD_RESET_MS = 600\nlet typeaheadBuffer = ''\nlet typeaheadTimer: ReturnType<typeof setTimeout> | undefined\n\nfunction onTypeahead(char: string, rows: GrTreeVisibleRow<T>[], fromIndex: number): void {\n clearTimeout(typeaheadTimer)\n typeaheadTimer = setTimeout(() => { typeaheadBuffer = '' }, TYPEAHEAD_RESET_MS)\n\n // Повтор одной буквы — это «следующий на ту же букву», а не поиск «аа».\n const repeat = typeaheadBuffer.length === 1 && typeaheadBuffer === char\n typeaheadBuffer = repeat ? char : typeaheadBuffer + char\n\n const query = typeaheadBuffer.toLowerCase()\n const total = rows.length\n\n for (let step = 1; step <= total; step += 1) {\n const row = rows[(fromIndex + step) % total]\n if (row.node.label.toLowerCase().startsWith(query)) {\n focusRow(row.node.key)\n return\n }\n }\n}\n\n/** `*` — раскрыть всех соседей узла, на котором фокус (уровень целиком). */\nfunction expandSiblings(node: GrTreeNode<T>): void {\n const siblings = node.parent ? node.parent.childNodes : treeStore.treeModel.value.roots\n\n for (const sibling of siblings) {\n if (sibling.childNodes.length === 0 || treeStore.isExpandedKey(sibling.key))\n continue\n\n treeStore.setExpandedKey(sibling.key, true)\n interactionContext.emitNodeExpand(sibling.data, sibling)\n }\n}\n\nfunction onTreeKeydown(event: KeyboardEvent): void {\n // Делегированная клавиатура уважает интерактивные цели: печать в инпуте слота\n // узла — ввод текста, а не typeahead. Без гарда preventDefault отменял бы это.\n const target = event.target as HTMLElement | null\n const interactive = target?.closest<HTMLElement>('button, a[href], input, select, textarea, [contenteditable]')\n if (interactive && interactive !== event.currentTarget) {\n // Табируемая цель — самостоятельный таб-стоп: клавиатура целиком её.\n if (interactive.tabIndex >= 0)\n return\n\n // Кнопки строки (toggle, drag-handle) не табируемы: фокус попадает на них\n // только мышью. За ними остаётся активация — Enter на toggle это его клик, —\n // а навигация принадлежит дереву, иначе клик мышью убивал бы стрелки.\n if (event.key === 'Enter' || event.key === ' ')\n return\n }\n\n const rows = visibleRows.value\n if (rows.length === 0)\n return\n\n const idx = Math.max(0, rows.findIndex(r => r.node.key === roving.rovingKey.value))\n const cur = rows[idx]\n\n if (event.key === '*') {\n event.preventDefault()\n expandSiblings(cur.node)\n return\n }\n\n // Печатный символ без модификаторов — typeahead, а не команда.\n if (event.key.length === 1 && event.key !== ' ' && !event.ctrlKey && !event.metaKey && !event.altKey) {\n event.preventDefault()\n onTypeahead(event.key, rows, idx)\n return\n }\n\n // `Shift` со стрелкой двигает сам узел — раскладка аутлайнера. Проверяется\n // раньше навигации: иначе примитив увёл бы фокус вместо переноса.\n if (event.shiftKey && MOVE_KEYS.has(event.key)) {\n if (!treeProps.draggable) return\n\n event.preventDefault()\n void onNodeMoveKey(cur.node, event.key)\n return\n }\n\n // Вертикаль, `Home` и `End` ведёт примитив. Влево-вправо он не трогает: в\n // дереве это раскрытие ветки и переход к родителю, а не движение по списку.\n if (roving.handleNavigationKeys(event))\n return\n\n switch (event.key) {\n case 'ArrowRight':\n event.preventDefault()\n if (!cur.isLeaf) {\n if (!cur.isExpanded)\n toggleExpand(cur.node)\n else if (idx < rows.length - 1)\n focusRow(rows[idx + 1].node.key)\n }\n break\n case 'ArrowLeft':\n event.preventDefault()\n if (!cur.isLeaf && cur.isExpanded)\n toggleExpand(cur.node)\n else if (cur.node.parent && rows.some(r => r.node.key === cur.node.parent!.key))\n focusRow(cur.node.parent.key)\n break\n case 'Enter':\n event.preventDefault()\n onRowClick(cur.node)\n break\n // Space при чекбоксах переключает отметку — это и есть выбор в\n // multi-select дереве; без них он остаётся вторым способом выбрать узел.\n case ' ':\n event.preventDefault()\n if (treeProps.showCheckbox)\n toggleChecked(cur.node)\n else\n onRowClick(cur.node)\n break\n }\n}\n\nfunction onRowMouseEnter(node: GrTreeNode<T>) {\n hoveredKey.value = node.key\n}\n\nfunction onRowMouseLeave(node: GrTreeNode<T>) {\n if (hoveredKey.value === node.key)\n hoveredKey.value = undefined\n}\n\nfunction collapseSiblings(node: GrTreeNode<T>) {\n const siblings = node.parent ? node.parent.childNodes : treeStore.treeModel.value.roots\n\n for (const sibling of siblings) {\n if (sibling.key === node.key || !treeStore.isExpandedKey(sibling.key))\n continue\n\n treeStore.setExpandedKey(sibling.key, false)\n interactionContext.emitNodeCollapse(sibling.data, sibling)\n }\n}\n\nfunction toggleExpand(node: GrTreeNode<T>) {\n const expanded = !treeStore.isExpandedKey(node.key)\n\n if (expanded && treeProps.accordion)\n collapseSiblings(node)\n\n // Ленивая ветка грузится по первому раскрытию; повторное ничего не запрашивает.\n if (expanded)\n treeStore.ensureLoaded(node)\n\n treeStore.toggleExpand(node)\n\n if (expanded)\n interactionContext.emitNodeExpand(node.data, node)\n else\n interactionContext.emitNodeCollapse(node.data, node)\n}\n\n// ————— Отметки (чекбоксы).\n\nfunction checkStateOf(key: GrTreeKey): GrTreeCheckState {\n return treeStore.getCheckState(key)\n}\n\nfunction ariaCheckedOf(row: GrTreeVisibleRow<T>): 'true' | 'false' | 'mixed' | undefined {\n if (!treeProps.showCheckbox)\n return undefined\n\n const state = checkStateOf(row.node.key)\n return state === 'checked' ? 'true' : state === 'half' ? 'mixed' : 'false'\n}\n\nfunction emitCheck(node: GrTreeNode<T>): void {\n const checkedKeys = treeStore.getCheckedKeys()\n emit('update:checkedKeys', checkedKeys)\n emit('check', node.data, node, { checkedKeys, halfCheckedKeys: treeStore.getHalfCheckedKeys() })\n}\n\nfunction toggleChecked(node: GrTreeNode<T>): void {\n if (!treeProps.showCheckbox)\n return\n\n const changed = treeStore.toggleChecked(node)\n if (changed)\n emitCheck(changed)\n}\n\n// ————— Геометрия строки: отступ уровня и направляющие ветвей.\n\nfunction rowStyle(row: GrTreeVisibleRow<T>) {\n const step = treeProps.indent && treeProps.indent > 0\n ? `${treeProps.indent}px`\n : 'var(--gr-tree-indent-step)'\n\n return { '--gr-tree-row-indent': `calc(${step} * ${row.node.level - 1})` }\n}\n\n/**\n * Направляющая уровня-предка. Считается от того же шага отступа, что и сам\n * отступ строки, — иначе линия и содержимое разъедутся на вложенных уровнях.\n */\nfunction branchGuideStyle(row: GrTreeVisibleRow<T>, index: number) {\n const step = treeProps.indent && treeProps.indent > 0\n ? `${treeProps.indent}px`\n : 'var(--gr-tree-indent-step)'\n\n return {\n 'left': `calc(${step} * ${index} + var(--gr-tree-branch-line-offset))`,\n '--gr-tree-branch-line-color': row.branchColors[index],\n }\n}\n\n// Drag & drop\nfunction canDrag(node: GrTreeNode<T>): boolean {\n return interactionContext.canDrag(node)\n}\n\nfunction shouldShowDragHandle(node: GrTreeNode<T>): boolean {\n return canDrag(node) && hoveredKey.value === node.key\n}\n\n/**\n * Треть строки сверху — «до», треть снизу — «после», середина — «внутрь».\n * Зоны нарезает компонент: примитив отдаёт только долю вдоль строки.\n */\nfunction dropTypeFromFraction(fraction: number): GrTreeNodeDropType {\n if (fraction < 1 / 3) return 'prev'\n if (fraction > 2 / 3) return 'next'\n\n return 'inner'\n}\n\nfunction canDrop(drag: GrTreeNode<T>, target: GrTreeNode<T>, type: GrTreeAllowDropType): boolean {\n return interactionContext.canDrop(drag, target, type)\n}\n\n/** Перенос состоялся: одна дорога и для указателя, и для клавиатуры. */\nfunction applyMove(source: GrTreeNode<T>, targetKey: GrTreeKey, type: GrTreeNodeDropType): boolean {\n const reference = treeStore.getNode(targetKey)\n if (!reference) return false\n\n const movedNode = treeStore.moveNode(source, reference, type)\n if (!movedNode) return false\n\n const dropNode = treeStore.getNode(reference.key) ?? reference\n interactionContext.emitNodeDrop(movedNode, dropNode, type)\n\n return true\n}\n\nconst dragSort = useDragSort<GrTreeKey, GrTreeDropTarget>({\n items: () => visibleRows.value.map(row => row.node.key),\n elementFor: key => interactionContext.nodeEls.get(key) ?? null,\n scroller: () => treeRootEl.value,\n disabled: () => !treeProps.draggable,\n canDrag: (key) => {\n const node = treeStore.getNode(key)\n\n return node ? canDrag(node) : false\n },\n resolveTarget: (hit, sourceKey, previous) => {\n const source = treeStore.getNode(sourceKey)\n const node = treeStore.getNode(hit.key)\n if (!source || !node) return null\n\n const type = dropTypeFromFraction(hit.fraction)\n const allowed = treeStore.canMoveNode(source, node, type) && canDrop(source, node, type)\n\n // Та же ссылка, пока смысл не изменился: иначе подсветка перерисовывалась бы\n // на каждом движении указателя.\n if (previous && previous.key === node.key && previous.type === type && previous.allowed === allowed)\n return previous\n\n return { key: node.key, type, allowed }\n },\n onDrop: (sourceKey, target) => {\n const source = treeStore.getNode(sourceKey)\n if (!source || !target.allowed) return\n\n applyMove(source, target.key, target.type)\n },\n onUpdate: (sourceKey, target) => {\n interactionContext.draggingNode.value = sourceKey === null ? null : treeStore.getNode(sourceKey) ?? null\n interactionContext.dropTarget.value = target\n },\n})\n\nfunction onHandlePointerDown(event: PointerEvent, node: GrTreeNode<T>): void {\n if (!treeProps.draggable) return\n\n dragSort.startFrom(node.key)(event)\n}\n\n/**\n * Клавиатурный перенос — раскладка аутлайнера: `Shift` со стрелкой сразу\n * двигает узел, без режима «взято». Режим здесь был бы лишним: `Space` в дереве\n * уже отмечает чекбокс, а стрелки водят фокус по веткам.\n */\nfunction moveByKeyboard(node: GrTreeNode<T>, key: string): boolean {\n const siblings = node.parent ? node.parent.childNodes : treeStore.treeModel.value.roots\n const index = siblings.findIndex(sibling => sibling.key === node.key)\n\n if (key === 'ArrowUp' && index > 0)\n return applyMove(node, siblings[index - 1].key, 'prev')\n\n if (key === 'ArrowDown' && index >= 0 && index < siblings.length - 1)\n return applyMove(node, siblings[index + 1].key, 'next')\n\n // Вправо — в предыдущего соседа: у первого в уровне такого соседа нет.\n if (key === 'ArrowRight' && index > 0)\n return applyMove(node, siblings[index - 1].key, 'inner')\n\n if (key === 'ArrowLeft' && node.parent)\n return applyMove(node, node.parent.key, 'next')\n\n return false\n}\n\nasync function onNodeMoveKey(node: GrTreeNode<T>, key: string): Promise<void> {\n if (!treeProps.draggable || !canDrag(node)) return\n if (!moveByKeyboard(node, key)) return\n\n announce(t('gr.tree.moved', 'Moved {label}', { label: node.label }))\n\n // Перестановка перерисовывает уровень: без возврата фокуса следующий Shift\n // уже некуда было бы адресовать.\n await nextTick()\n focusRow(node.key)\n}\n\nfunction filter(value: string) {\n treeStore.filter(value)\n}\n\nfunction resolveNodeClass(classValue: GrTreeNodeClass<T> | undefined, row: GrTreeVisibleRow<T>) {\n if (!classValue)\n return undefined\n\n return typeof classValue === 'function'\n ? classValue(row)\n : classValue\n}\n\nfunction resolveBranchLineColorValue(color: GrTreeBranchLineColor<T> | undefined, node: GrTreeNode<T>) {\n if (!color)\n return undefined\n\n return typeof color === 'function'\n ? color(node) ?? undefined\n : color\n}\n\nfunction isBranchLineActive(node: GrTreeNode<T>) {\n const selectedKey = currentKey.value\n\n if (selectedKey == null)\n return false\n\n if (node.key === selectedKey)\n return true\n\n return node.childNodes.some(child => child.key === selectedKey && !treeStore.isExpandedKey(child.key))\n}\n\nfunction resolveBranchLineColor(node: GrTreeNode<T>) {\n const defaultColor = resolveBranchLineColorValue(treeProps.branchLineColor, node) ?? DEFAULT_BRANCH_LINE_COLOR\n\n if (!isBranchLineActive(node))\n return defaultColor\n\n return resolveBranchLineColorValue(treeProps.branchLineActiveColor, node) ?? defaultColor\n}\n\n/**\n * Переводит фокус на узел (по умолчанию — на тот, что держит roving tabindex).\n * Нужен снаружи: `GrTreeSelect` открывает панель и обязан отдать клавиатуру\n * дереву, иначе стрелки из триггера никуда не ведут.\n */\nfunction focus(key?: GrTreeKey): boolean {\n const rows = visibleRows.value\n if (rows.length === 0)\n return false\n\n const target = key != null && rows.some(row => row.node.key === key)\n ? key\n : roving.rovingKey.value ?? rows[0].node.key\n\n focusRow(target)\n return true\n}\n\nonUnmounted(() => clearTimeout(typeaheadTimer))\n// Дерево уничтожают целиком — строки размонтируются вместе с ним, и возвращать\n// фокус на умирающий корень нельзя: он уедет из документа вместе с деревом.\nonBeforeUnmount(() => { isUnmounting = true })\n\ndefineExpose<GrTreeInstance<T>>({\n appendNode: treeStore.appendNode,\n getCheckedKeys: options => treeStore.getCheckedKeys(options),\n setCheckedKeys: (keys) => {\n treeStore.setCheckedKeys(keys)\n emit('update:checkedKeys', treeStore.getCheckedKeys())\n },\n getHalfCheckedKeys: treeStore.getHalfCheckedKeys,\n setChecked: (node, checked) => {\n const changed = treeStore.setChecked(node, checked)\n if (changed)\n emitCheck(changed)\n return Boolean(changed)\n },\n filter,\n focus,\n getCurrentNode: treeStore.getCurrentNode,\n setCurrentKey: treeStore.setCurrentKey,\n getCurrentKey: treeStore.getCurrentKey,\n getNode: treeStore.getNode,\n insertNodeAfter: treeStore.insertNodeAfter,\n insertNodeBefore: treeStore.insertNodeBefore,\n removeNode: treeStore.removeNode,\n setCurrentNode: treeStore.setCurrentNode,\n})\n</script>\n\n<template>\n <div\n ref=\"treeRootEl\"\n data-gr-tree\n class=\"gr-tree\"\n tabindex=\"-1\"\n :data-gr-virtual=\"treeProps.virtual ? '' : undefined\"\n role=\"tree\"\n :aria-multiselectable=\"treeProps.showCheckbox ? 'true' : undefined\"\n :style=\"rootStyle\"\n @keydown=\"onTreeKeydown\"\n >\n <div\n v-for=\"(row, windowIndex) in renderedRows\"\n :key=\"row.node.key\"\n :ref=\"(el) => {\n if (el) interactionContext.registerNodeEl(row.node.key, el as HTMLElement)\n else releaseNodeEl(row.node.key)\n if (treeProps.virtual) virtualizer.measure(absoluteIndex(windowIndex), el as Element | null)\n }\"\n data-gr-tree-node\n :data-gr-tree-node-key=\"row.node.key\"\n role=\"treeitem\"\n :aria-level=\"row.node.level\"\n :aria-posinset=\"row.posInSet\"\n :aria-setsize=\"row.setSize\"\n :aria-expanded=\"row.isLeaf ? undefined : row.isExpanded\"\n :aria-selected=\"currentKey === row.node.key ? 'true' : undefined\"\n :aria-checked=\"ariaCheckedOf(row)\"\n :aria-busy=\"row.isLoading ? 'true' : undefined\"\n :tabindex=\"isRovingItem(row.node.key) ? 0 : -1\"\n >\n <div\n data-gr-tree-row\n class=\"gr-tree__row\"\n :class=\"[\n treeProps.highlightCurrent && currentKey === row.node.key ? 'gr-tree__row--current' : '',\n row.isMatched ? 'gr-tree__row--matched' : '',\n dropTarget?.key === row.node.key && dropTarget.allowed && dropTarget.type === 'inner' ? 'gr-tree__row--drop-inner' : '',\n dropTarget?.key === row.node.key && dropTarget.allowed && dropTarget.type === 'prev' ? 'gr-tree__row--drop-prev' : '',\n dropTarget?.key === row.node.key && dropTarget.allowed && dropTarget.type === 'next' ? 'gr-tree__row--drop-next' : '',\n resolveNodeClass(treeProps.rowClass, row),\n ]\"\n :style=\"rowStyle(row)\"\n @click=\"onRowActivate(row)\"\n @contextmenu=\"onRowContextMenu($event, row.node)\"\n @drop.prevent\n @mouseenter=\"onRowMouseEnter(row.node)\"\n @mouseleave=\"onRowMouseLeave(row.node)\"\n >\n <!--\n Направляющие ветвей: по одной на каждый уровень предка. Вложенной\n обёртки с `border-left` больше нет, поэтому линию рисует сама строка —\n и заходит в зазор между строками, иначе она распалась бы на штрихи.\n -->\n <span\n v-for=\"(color, guideIndex) in row.branchColors\"\n :key=\"guideIndex\"\n data-gr-tree-branch-guide\n class=\"gr-tree__branch-guide\"\n :style=\"branchGuideStyle(row, guideIndex)\"\n aria-hidden=\"true\"\n />\n\n <button\n v-if=\"treeProps.draggable\"\n type=\"button\"\n tabindex=\"-1\"\n data-gr-tree-drag-handle\n class=\"gr-tree__drag-handle\"\n :class=\"[\n shouldShowDragHandle(row.node) ? 'gr-tree__drag-handle--visible' : '',\n canDrag(row.node) ? '' : 'gr-tree__drag-handle--disabled',\n resolveNodeClass(treeProps.dragHandleClass, row),\n ]\"\n :aria-label=\"dragLabel\"\n :aria-disabled=\"canDrag(row.node) ? undefined : 'true'\"\n @click.stop\n @pointerdown=\"onHandlePointerDown($event, row.node)\"\n >\n <span class=\"gr-tree__drag-icon\" :class=\"treeProps.dragHandleIcon\" />\n </button>\n\n <!-- tabindex=\"-1\": внутри roving-композита интерактив не табируется,\n клавиатурный эквивалент — ArrowRight/ArrowLeft на строке. -->\n <button\n v-if=\"!row.isLeaf\"\n data-gr-tree-toggle\n type=\"button\"\n tabindex=\"-1\"\n class=\"gr-tree__toggle\"\n :class=\"resolveNodeClass(treeProps.toggleClass, row)\"\n :aria-label=\"row.isExpanded ? collapseLabel : expandLabel\"\n @click.stop=\"toggleExpand(row.node)\"\n >\n <span\n v-if=\"row.isLoading\"\n data-gr-tree-loading\n class=\"gr-tree__toggle-icon animate-spin i-lucide-loader-circle\"\n />\n <span\n v-else\n class=\"gr-tree__toggle-icon\"\n :class=\"[\n row.isExpanded ? treeProps.collapseIcon : treeProps.expandIcon,\n row.isExpanded && treeProps.toggleIconRotate ? 'gr-tree__toggle-icon--expanded' : '',\n resolveNodeClass(treeProps.toggleIconClass, row),\n ]\"\n />\n </button>\n <span\n v-else\n class=\"gr-tree__toggle-spacer\"\n :class=\"resolveNodeClass(treeProps.toggleSpacerClass, row)\"\n />\n\n <!--\n Квадратик отметки декоративен: состояние живёт на самом `treeitem`\n (`aria-checked`), а вложить интерактивный чекбокс внутрь роли-виджета\n нельзя — роль объявляет потомков презентационными.\n -->\n <span\n v-if=\"treeProps.showCheckbox\"\n data-gr-tree-checkbox\n class=\"gr-tree__checkbox\"\n :class=\"[\n checkStateOf(row.node.key) === 'checked' ? 'gr-tree__checkbox--checked' : '',\n checkStateOf(row.node.key) === 'half' ? 'gr-tree__checkbox--half' : '',\n ]\"\n aria-hidden=\"true\"\n @click.stop=\"toggleChecked(row.node)\"\n >\n <span\n v-if=\"checkStateOf(row.node.key) !== 'unchecked'\"\n class=\"gr-tree__checkbox-mark\"\n :class=\"checkStateOf(row.node.key) === 'half' ? 'i-lucide-minus' : 'i-lucide-check'\"\n />\n </span>\n\n <div class=\"gr-tree__content\" :class=\"resolveNodeClass(treeProps.contentClass, row)\">\n <slot\n :node=\"row.node\"\n :data=\"row.node.data\"\n >\n <span class=\"gr-tree__label\">{{ row.node.label }}</span>\n </slot>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.gr-tree {\n --gr-tree-gap: 2px;\n --gr-tree-children-pl: 10px;\n --gr-tree-row-min-height: 28px;\n --gr-tree-row-radius: 8px;\n --gr-tree-row-px: 8px;\n --gr-tree-row-py: 8px;\n --gr-tree-row-pr: var(--gr-tree-row-px);\n --gr-tree-font-size: inherit;\n --gr-tree-row-color: var(--gr-fg);\n --gr-tree-row-hover-bg: color-mix(in srgb, var(--gr-primary) 10%, transparent);\n --gr-tree-row-current-bg: color-mix(in srgb, var(--gr-primary) 5%, transparent);\n --gr-tree-row-current-hover-bg: color-mix(in srgb, var(--gr-primary) 16%, transparent);\n --gr-tree-drag-handle-size: 24px;\n --gr-tree-drag-handle-mr: 0;\n --gr-tree-drag-handle-radius: 6px;\n --gr-tree-drag-handle-color: inherit;\n --gr-tree-drag-handle-opacity: 0.55;\n --gr-tree-drag-handle-hover-bg: color-mix(in srgb, var(--gr-muted) 22%, transparent);\n --gr-tree-drag-handle-hover-color: var(--gr-tree-drag-handle-color);\n --gr-tree-drag-handle-hover-opacity: 0.9;\n --gr-tree-drag-handle-disabled-opacity: 0.25;\n --gr-tree-toggle-size: 24px;\n --gr-tree-toggle-mr: 0;\n --gr-tree-toggle-radius: 6px;\n --gr-tree-toggle-color: inherit;\n --gr-tree-toggle-hover-bg: color-mix(in srgb, var(--gr-muted) 25%, transparent);\n --gr-tree-toggle-hover-color: var(--gr-tree-toggle-color);\n --gr-tree-icon-size: 16px;\n --gr-tree-content-gap: 8px;\n --gr-tree-branch-line-default-color: var(--gr-tree-row-current-bg);\n --gr-tree-branch-line-width: 2px;\n /* Шаг отступа уровня: переключатель, отступ вложенного списка и толщина\n направляющей — линия обязана попадать между ними. */\n --gr-tree-indent-step: calc(24px + var(--gr-tree-children-pl) + var(--gr-tree-branch-line-width));\n --gr-tree-branch-line-offset: 24px;\n --gr-tree-checkbox-size: 16px;\n --gr-tree-checkbox-radius: 4px;\n --gr-tree-checkbox-brd: var(--gr-brd);\n --gr-tree-checkbox-bg: transparent;\n --gr-tree-checkbox-checked-bg: var(--gr-primary);\n --gr-tree-checkbox-checked-fg: var(--gr-primary-fg);\n --gr-tree-checkbox-mr: 4px;\n display: flex;\n flex-direction: column;\n gap: var(--gr-tree-gap);\n}\n\n[data-gr-virtual]::before,\n[data-gr-virtual]::after {\n content: '';\n display: block;\n flex: none;\n}\n\n[data-gr-virtual]::before {\n height: var(--gr-virtual-before, 0px);\n}\n\n[data-gr-virtual]::after {\n height: var(--gr-virtual-after, 0px);\n}\n\n.gr-tree__row {\n position: relative;\n display: flex;\n align-items: center;\n min-height: var(--gr-tree-row-min-height);\n padding: var(--gr-tree-row-py) var(--gr-tree-row-pr) var(--gr-tree-row-py)\n calc(var(--gr-tree-row-px) + var(--gr-tree-row-indent, 0px));\n font-size: var(--gr-tree-font-size);\n cursor: default;\n user-select: none;\n outline: none;\n color: var(--gr-tree-row-color);\n}\n\n/* Поверхность строки — отдельный слой, вставленный слева на отступ уровня.\n Фон на самой коробке шёл от левого края дерева и накрывал направляющие\n предков: линия ветки исчезала под подсветкой ровно там, где она и нужна.\n Слой лежит ниже направляющих (они — следующие элементы строки), поэтому\n линия остаётся видна при любом наложении. */\n.gr-tree__row::before {\n content: '';\n position: absolute;\n inset: 0 0 0 var(--gr-tree-row-indent, 0px);\n border-radius: var(--gr-tree-row-radius);\n pointer-events: none;\n}\n\n.gr-tree__row:hover::before {\n background: var(--gr-tree-row-hover-bg);\n}\n\n/* Направляющая тянется в зазор между строками: иначе линия ветки распалась бы\n на штрихи по числу строк. */\n.gr-tree__branch-guide {\n position: absolute;\n top: calc(var(--gr-tree-gap) / -2);\n bottom: calc(var(--gr-tree-gap) / -2);\n width: var(--gr-tree-branch-line-width);\n background: var(--gr-tree-branch-line-color, var(--gr-tree-branch-line-default-color));\n pointer-events: none;\n}\n\n.gr-tree__checkbox {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: var(--gr-tree-checkbox-size);\n height: var(--gr-tree-checkbox-size);\n margin-right: var(--gr-tree-checkbox-mr);\n border: 1px solid var(--gr-tree-checkbox-brd);\n border-radius: var(--gr-tree-checkbox-radius);\n background: var(--gr-tree-checkbox-bg);\n color: var(--gr-tree-checkbox-checked-fg);\n cursor: pointer;\n}\n\n.gr-tree__checkbox--checked,\n.gr-tree__checkbox--half {\n border-color: var(--gr-tree-checkbox-checked-bg);\n background: var(--gr-tree-checkbox-checked-bg);\n}\n\n.gr-tree__checkbox-mark {\n width: calc(var(--gr-tree-checkbox-size) - 4px);\n height: calc(var(--gr-tree-checkbox-size) - 4px);\n}\n\n\n[data-gr-tree-node] {\n outline: none;\n}\n\n/* Кольцо фокуса — на той же поверхности, что подсветка: иначе строка читалась бы\n двумя разными прямоугольниками. `outline` для этого не годится — он рисуется по\n границе коробки, а не по инсету. */\n[data-gr-tree-node]:focus-visible > .gr-tree__row::before {\n box-shadow: inset 0 0 0 2px var(--gr-primary);\n}\n\n.gr-tree__row--current::before {\n background: var(--gr-tree-row-current-bg);\n}\n\n.gr-tree__row--current:hover::before {\n background: var(--gr-tree-row-current-hover-bg);\n}\n\n.gr-tree__row--matched .gr-tree__label {\n font-weight: 600;\n}\n\n.gr-tree__drag-handle {\n width: var(--gr-tree-drag-handle-size);\n height: var(--gr-tree-drag-handle-size);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n margin-right: var(--gr-tree-drag-handle-mr);\n border-radius: var(--gr-tree-drag-handle-radius);\n color: var(--gr-tree-drag-handle-color);\n cursor: grab;\n /* Без этого вертикальный свайп по ручке уходит в прокрутку страницы,\n браузер отзывает указатель, и пальцем узел неперетаскиваем. */\n touch-action: none;\n visibility: hidden;\n pointer-events: none;\n opacity: 0;\n}\n\n.gr-tree__drag-handle--visible {\n visibility: visible;\n pointer-events: auto;\n opacity: var(--gr-tree-drag-handle-opacity);\n}\n\n.gr-tree__drag-handle:hover {\n background: var(--gr-tree-drag-handle-hover-bg);\n color: var(--gr-tree-drag-handle-hover-color);\n opacity: var(--gr-tree-drag-handle-hover-opacity);\n}\n\n.gr-tree__drag-handle:active {\n cursor: grabbing;\n}\n\n.gr-tree__drag-handle--disabled {\n cursor: default;\n opacity: var(--gr-tree-drag-handle-disabled-opacity);\n}\n\n.gr-tree__drag-icon {\n width: var(--gr-tree-icon-size);\n height: var(--gr-tree-icon-size);\n}\n\n.gr-tree__toggle {\n width: var(--gr-tree-toggle-size);\n height: var(--gr-tree-toggle-size);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n margin-right: var(--gr-tree-toggle-mr);\n border-radius: var(--gr-tree-toggle-radius);\n color: var(--gr-tree-toggle-color);\n}\n\n.gr-tree__toggle:hover {\n background: var(--gr-tree-toggle-hover-bg);\n color: var(--gr-tree-toggle-hover-color);\n}\n\n.gr-tree__toggle-icon {\n width: var(--gr-tree-icon-size);\n height: var(--gr-tree-icon-size);\n transition: transform var(--gr-duration-fast) var(--gr-ease-out);\n}\n\n.gr-tree__toggle-icon--expanded {\n transform: rotate(90deg);\n}\n\n.gr-tree__toggle-spacer {\n width: var(--gr-tree-toggle-size);\n height: var(--gr-tree-toggle-size);\n margin-right: var(--gr-tree-toggle-mr);\n display: inline-block;\n}\n\n.gr-tree__content {\n min-width: 0;\n display: flex;\n align-items: center;\n gap: var(--gr-tree-content-gap);\n}\n\n.gr-tree__label {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.gr-tree__row--drop-inner {\n outline: 2px solid color-mix(in srgb, var(--gr-primary) 40%, transparent);\n}\n\n.gr-tree__row--drop-prev::before,\n.gr-tree__row--drop-next::after {\n content: '';\n position: absolute;\n left: 8px;\n right: 8px;\n height: 2px;\n background: color-mix(in srgb, var(--gr-primary) 55%, transparent);\n}\n\n.gr-tree__row--drop-prev::before {\n top: 2px;\n}\n\n.gr-tree__row--drop-next::after {\n bottom: 2px;\n}\n</style>\n","<script setup lang=\"ts\" generic=\"T extends Record<string, any> = any\">\nimport { computed, nextTick, onBeforeUnmount, onUnmounted, ref } from 'vue'\nimport type {\n GrTreeAllowDropType,\n GrTreeInstance,\n GrTreeKey,\n GrTreeNode,\n GrTreeNodeDropType,\n} from './grTreeTypes'\nimport { createGrTreeDataAdapter } from './grTreeDataAdapter'\nimport type { GrTreeDropTarget } from './grTreeInteractionContext'\nimport { createGrTreeInteractionContext } from './grTreeInteractionContext'\nimport type { GrTreeCheckState } from './grTreeChecking'\nimport { createGrTreeStore } from './grTreeStore'\nimport { treeSizeVars } from './grTreeStyles'\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport { useAnnouncer } from '../../composables/useAnnouncer'\nimport { useDragSort } from '../../composables/useDragSort'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { useVirtualList } from '../../composables/useVirtualList'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport type {\n GrTreeBranchLineColor,\n GrTreeNodeClass,\n GrTreeProps,\n GrTreeVisibleRow,\n} from './grTreeProps'\n\nexport interface GrTreeEmits<T extends Record<string, any> = any> {\n (e: 'nodeClick', data: T, node: GrTreeNode<T>): void\n (e: 'nodeExpand', data: T, node: GrTreeNode<T>): void\n (e: 'nodeCollapse', data: T, node: GrTreeNode<T>): void\n (e: 'nodeDrop', draggingNode: GrTreeNode<T>, dropNode: GrTreeNode<T>, dropType: GrTreeNodeDropType): void\n (e: 'nodeContextMenu', evt: MouseEvent, data: T, node: GrTreeNode<T>): void\n (e: 'update:checkedKeys', keys: GrTreeKey[]): void\n (e: 'check', data: T, node: GrTreeNode<T>, info: { checkedKeys: GrTreeKey[], halfCheckedKeys: GrTreeKey[] }): void\n}\n\nconst DEFAULT_BRANCH_LINE_COLOR = 'var(--gr-tree-branch-line-default-color, var(--gr-brd))'\n\n/** Стрелки, которые вместе с `Shift` двигают узел, а не фокус. */\nconst MOVE_KEYS = new Set(['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'])\n\nconst props = withDefaults(defineProps<GrTreeProps<T>>(), {\n props: () => ({\n children: 'children',\n label: 'label',\n }),\n nodeKey: 'id' as any,\n defaultExpandedKeys: () => [],\n defaultExpandAll: false,\n expandOnClickNode: false,\n accordion: false,\n size: undefined,\n highlightCurrent: true,\n indent: 0,\n virtual: false,\n maxHeight: undefined,\n showCheckbox: false,\n checkedKeys: undefined,\n defaultCheckedKeys: () => [],\n checkStrictly: false,\n lazy: false,\n load: undefined,\n expandIcon: 'i-lucide-chevron-right',\n collapseIcon: 'i-lucide-chevron-right',\n toggleIconRotate: true,\n branchLine: false,\n branchLineColor: undefined,\n branchLineActiveColor: undefined,\n draggable: false,\n dragHandleIcon: 'i-lucide-grip-vertical',\n dragLabel: undefined,\n expandLabel: undefined,\n collapseLabel: undefined,\n rowClass: undefined,\n dragHandleClass: undefined,\n toggleClass: undefined,\n toggleIconClass: undefined,\n toggleSpacerClass: undefined,\n contentClass: undefined,\n})\n\nconst emit = defineEmits<GrTreeEmits<T>>()\n\ndefineSlots<{\n default?: (props: { node: GrTreeNode<T>; data: T }) => any\n}>()\n\nconst dataAdapter = createGrTreeDataAdapter(props)\nconst treeStore = createGrTreeStore({\n adapter: dataAdapter,\n data: () => props.data,\n defaultExpandedKeys: () => props.defaultExpandedKeys,\n defaultExpandAll: () => props.defaultExpandAll,\n filterNodeMethod: () => props.filterNodeMethod,\n defaultCheckedKeys: () => props.defaultCheckedKeys,\n checkedKeys: () => props.checkedKeys,\n checkStrictly: () => props.checkStrictly,\n lazy: () => props.lazy,\n load: () => props.load,\n})\nconst interactionContext = createGrTreeInteractionContext(props, {\n emitNodeClick: (data, node) => emit('nodeClick', data, node),\n emitNodeExpand: (data, node) => emit('nodeExpand', data, node),\n emitNodeCollapse: (data, node) => emit('nodeCollapse', data, node),\n emitNodeDrop: (draggingNode, dropNode, dropType) => emit('nodeDrop', draggingNode, dropNode, dropType),\n emitNodeContextMenu: (evt, data, node) => emit('nodeContextMenu', evt, data, node),\n})\n\nconst treeProps = props as Readonly<GrTreeProps<T>>\n\nconst { t } = useGranularityTranslations()\nconst { announce } = useAnnouncer()\nconst dragLabel = computed(() => treeProps.dragLabel ?? t('gr.tree.drag', 'Drag'))\nconst expandLabel = computed(() => treeProps.expandLabel ?? t('gr.tree.expand', 'Expand'))\nconst collapseLabel = computed(() => treeProps.collapseLabel ?? t('gr.tree.collapse', 'Collapse'))\nconst currentKey = treeStore.currentKey\nconst hoveredKey = interactionContext.hoveredKey\nconst dropTarget = interactionContext.dropTarget\n\n// Корневой элемент дерева — область для делегированной клавиатуры (WAI-ARIA tree).\nconst treeRootEl = ref<HTMLElement | null>(null)\n\n/**\n * Видимые строки одним плоским списком в порядке отображения.\n *\n * Плоско — потому что рекурсивный рендер стоил по инстансу компонента на каждый\n * раскрытый узел: дерево на 2 000 видимых строк превращалось в 2 000\n * компонентов со своими вычислениями и подписками. Иерархию несут `aria-level`,\n * `aria-posinset` и `aria-setsize`, отступ — `padding-left` строки.\n */\nconst visibleRows = computed<GrTreeVisibleRow<T>[]>(() => {\n const { roots } = treeStore.treeModel.value\n const { isActive, subtreeHasMatch, matchedKeys, autoExpandKeys } = treeStore.filterInfo.value\n const branch = treeProps.branchLine\n const rows: GrTreeVisibleRow<T>[] = []\n\n const walk = (nodes: GrTreeNode<T>[], ancestorColors: string[]): void => {\n const siblings = isActive ? nodes.filter(node => subtreeHasMatch.get(node.key)) : nodes\n\n siblings.forEach((node, index) => {\n const isLeaf = treeStore.isLeafNode(node)\n const isExpanded = treeStore.isExpandedKey(node.key) || (isActive && autoExpandKeys.has(node.key))\n\n rows.push({\n node,\n isExpanded,\n isLeaf,\n isMatched: matchedKeys.has(node.key),\n posInSet: index + 1,\n setSize: siblings.length,\n isLoading: treeStore.isLoadingKey(node.key),\n branchColors: ancestorColors,\n })\n\n if (isExpanded && node.childNodes.length)\n walk(node.childNodes, branch ? [...ancestorColors, resolveBranchLineColor(node)] : ancestorColors)\n })\n }\n\n walk(roots, [])\n return rows\n})\n\n/**\n * Кольцо roving-фокуса: ровно один `treeitem` на всё дерево держит `tabindex=0`.\n *\n * Ориентация вертикальная намеренно: влево-вправо в дереве заняты раскрытием\n * ветки и переходом к родителю, и примитив их не трогает — это доменная логика,\n * она осталась в `onTreeKeydown`.\n */\nconst roving = useRovingFocus<GrTreeNode<T>['key']>({\n items: () => visibleRows.value.map(row => row.node.key),\n elementFor: key => interactionContext.nodeEls.get(key),\n orientation: () => 'vertical',\n // У дерева есть верх и низ: с последней строки вниз идти некуда.\n wrap: () => false,\n // Но клавишу на краю всё равно гасим: иначе `ArrowDown` на последнем узле\n // прокрутит страницу под деревом.\n onOverflow: () => true,\n // Пока фокус не трогали, остановку держит текущий (выбранный) узел, а если\n // его не видно — первая видимая строка.\n initialKey: () => currentKey.value ?? undefined,\n beforeFocus: key => scrollRowIntoView(key),\n})\n\nfunction onRowClick(node: GrTreeNode<T>) {\n treeStore.setCurrentKey(node.key)\n roving.setActive(node.key)\n interactionContext.emitNodeClick(node.data, node)\n}\n\n// Клик мышью, в отличие от Enter, умеет ещё и раскрывать узел: `Enter` в\n// паттерне tree закреплён за выбором, и подмешивать в него раскрытие нельзя.\nfunction onRowActivate(row: GrTreeVisibleRow<T>) {\n onRowClick(row.node)\n\n if (treeProps.expandOnClickNode && !row.isLeaf)\n toggleExpand(row.node)\n}\n\nfunction onRowContextMenu(evt: MouseEvent, node: GrTreeNode<T>) {\n interactionContext.emitNodeContextMenu(evt, node.data, node)\n}\n\n// ————— Клавиатурная навигация по WAI-ARIA tree pattern (только корневой инстанс).\n\nfunction isRovingItem(key: GrTreeNode<T>['key']): boolean {\n return roving.rovingKey.value === key\n}\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrTree' })\n\n// Переменные ставим и вложенным уровням тоже: `v-bind=\"props\"` доносит до них\n// `size`, а значения одинаковые — наследование от корня не нарушается.\nconst sizeStyle = computed(() => treeSizeVars[resolvedSize.value])\n\n/**\n * Виртуализация: в DOM живёт только окно вокруг вьюпорта.\n *\n * Корень дерева и есть скроллер — обёрток-распорок между `role=\"tree\"` и\n * `role=\"treeitem\"` быть не должно, поэтому срезанное сверху и снизу держат\n * отступы самого корня.\n */\nconst TREE_ROW_GAP = 2\n\nconst rowEstimate = computed(() => Number.parseFloat(sizeStyle.value['--gr-tree-row-min-height']))\n\nconst virtualMaxHeight = computed(() => {\n const value = treeProps.maxHeight\n if (value == null) return undefined\n return typeof value === 'number' ? `${value}px` : value\n})\n\nconst virtualizer = useVirtualList({\n container: treeRootEl,\n count: () => (treeProps.virtual ? visibleRows.value.length : 0),\n // Раскрытие/фильтр сдвигают индексы строк — замеры прошлого набора невалидны.\n source: () => visibleRows.value,\n itemSize: () => rowEstimate.value,\n gap: TREE_ROW_GAP,\n // Первый рендер обязан совпасть с серверным: контейнера ещё нет, и окно\n // считается от объявленной высоты, а не от измеренной.\n viewportSize: () => (typeof treeProps.maxHeight === 'number' ? treeProps.maxHeight : undefined),\n})\n\n/** Строки к отрисовке: при выключенной виртуализации — все. */\nconst renderedRows = computed(() => {\n if (!treeProps.virtual) return visibleRows.value\n\n const { start, end } = virtualizer.range.value\n return visibleRows.value.slice(start, end)\n})\n\n/**\n * Срезанное сверху и снизу держат псевдоэлементы контейнера.\n *\n * Ни один из трёх напрашивающихся вариантов не годится:\n *\n * - `padding` контейнера — `max-height` меряет ту же коробку, и распорка в сто\n * тысяч пикселей либо раздувает скроллер до той же сотни тысяч, либо съедает\n * всю его высоту;\n * - обёртка-распорка внутри — между `role=\"tree\"` и `role=\"treeitem\"` не должно\n * быть посторонних узлов, роль потеряла бы обязательных потомков;\n * - отступы крайних отрисованных строк — распорка исчезает ровно в тот момент,\n * когда строки заменяются целиком (прыжок прокрутки), содержимое схлопывается\n * до высоты окна, и браузер обрезает `scrollTop` до почти нуля.\n *\n * `::before`/`::after` не узлы DOM: их не за что размонтировать, в дереве\n * доступности их нет, а высота приезжает переменными в том же патче, что и сами\n * строки, — схлопнуться между кадрами нечему.\n */\nconst rootStyle = computed(() => {\n if (!treeProps.virtual) return sizeStyle.value\n\n return {\n ...sizeStyle.value,\n overflow: 'auto',\n maxHeight: virtualMaxHeight.value,\n ...virtualizer.spacerStyle.value,\n }\n})\n\n/** Абсолютный индекс отрисованной строки: поиск по массиву стоил бы O(n) на строку. */\nfunction absoluteIndex(offsetInWindow: number): number {\n return treeProps.virtual ? virtualizer.range.value.start + offsetInWindow : offsetInWindow\n}\n\n/**\n * Строка размонтируется: снимаем регистрацию и, если фокус был на ней, уводим\n * его на корень — делегированный keydown живёт там, первая же стрелка вернёт\n * видимую строку через `focusRow`. Иначе фокус упал бы на `body` и клавиатура\n * дерева умерла бы до клика мышью.\n *\n * Спрашиваем сам элемент, а не `document.activeElement === body`: Vue обнуляет\n * function ref ДО удаления узла, поэтому в момент вызова фокус ещё на строке —\n * а «фокус на body» означает лишь то, что страницу никто не трогал.\n */\nlet isUnmounting = false\n\nfunction releaseNodeEl(key: GrTreeKey): void {\n const el = interactionContext.nodeEls.get(key)\n const active = typeof document === 'undefined' ? null : document.activeElement\n const heldFocus = Boolean(el && active && (el === active || el.contains(active)))\n\n interactionContext.registerNodeEl(key, null)\n\n if (heldFocus && !isUnmounting)\n treeRootEl.value?.focus({ preventScroll: true })\n}\n\n/**\n * При виртуализации узла вне окна в DOM нет: `nodeEls.get` вернул бы\n * `undefined`, а фокус уехал бы на `body` вместе с размонтированной строкой.\n * Поэтому сперва прокрутка, и только следующим тиком — фокус.\n */\nfunction scrollRowIntoView(key: GrTreeNode<T>['key']): Promise<void> {\n if (treeProps.virtual) {\n const index = visibleRows.value.findIndex(row => row.node.key === key)\n if (index >= 0) virtualizer.scrollToIndex(index)\n }\n\n return nextTick()\n}\n\nfunction focusRow(key: GrTreeNode<T>['key']): void {\n void roving.focusKey(key)\n}\n\n/**\n * Typeahead паттерна tree: печатные символы копятся в буфер и переводят фокус\n * на первый подходящий видимый узел, начиная со следующего за текущим.\n */\nconst TYPEAHEAD_RESET_MS = 600\nlet typeaheadBuffer = ''\nlet typeaheadTimer: ReturnType<typeof setTimeout> | undefined\n\nfunction onTypeahead(char: string, rows: GrTreeVisibleRow<T>[], fromIndex: number): void {\n clearTimeout(typeaheadTimer)\n typeaheadTimer = setTimeout(() => { typeaheadBuffer = '' }, TYPEAHEAD_RESET_MS)\n\n // Повтор одной буквы — это «следующий на ту же букву», а не поиск «аа».\n const repeat = typeaheadBuffer.length === 1 && typeaheadBuffer === char\n typeaheadBuffer = repeat ? char : typeaheadBuffer + char\n\n const query = typeaheadBuffer.toLowerCase()\n const total = rows.length\n\n for (let step = 1; step <= total; step += 1) {\n const row = rows[(fromIndex + step) % total]\n if (row.node.label.toLowerCase().startsWith(query)) {\n focusRow(row.node.key)\n return\n }\n }\n}\n\n/** `*` — раскрыть всех соседей узла, на котором фокус (уровень целиком). */\nfunction expandSiblings(node: GrTreeNode<T>): void {\n const siblings = node.parent ? node.parent.childNodes : treeStore.treeModel.value.roots\n\n for (const sibling of siblings) {\n if (sibling.childNodes.length === 0 || treeStore.isExpandedKey(sibling.key))\n continue\n\n treeStore.setExpandedKey(sibling.key, true)\n interactionContext.emitNodeExpand(sibling.data, sibling)\n }\n}\n\nfunction onTreeKeydown(event: KeyboardEvent): void {\n // Делегированная клавиатура уважает интерактивные цели: печать в инпуте слота\n // узла — ввод текста, а не typeahead. Без гарда preventDefault отменял бы это.\n const target = event.target as HTMLElement | null\n const interactive = target?.closest<HTMLElement>('button, a[href], input, select, textarea, [contenteditable]')\n if (interactive && interactive !== event.currentTarget) {\n // Табируемая цель — самостоятельный таб-стоп: клавиатура целиком её.\n if (interactive.tabIndex >= 0)\n return\n\n // Кнопки строки (toggle, drag-handle) не табируемы: фокус попадает на них\n // только мышью. За ними остаётся активация — Enter на toggle это его клик, —\n // а навигация принадлежит дереву, иначе клик мышью убивал бы стрелки.\n if (event.key === 'Enter' || event.key === ' ')\n return\n }\n\n const rows = visibleRows.value\n if (rows.length === 0)\n return\n\n const idx = Math.max(0, rows.findIndex(r => r.node.key === roving.rovingKey.value))\n const cur = rows[idx]\n\n if (event.key === '*') {\n event.preventDefault()\n expandSiblings(cur.node)\n return\n }\n\n // Печатный символ без модификаторов — typeahead, а не команда.\n if (event.key.length === 1 && event.key !== ' ' && !event.ctrlKey && !event.metaKey && !event.altKey) {\n event.preventDefault()\n onTypeahead(event.key, rows, idx)\n return\n }\n\n // `Shift` со стрелкой двигает сам узел — раскладка аутлайнера. Проверяется\n // раньше навигации: иначе примитив увёл бы фокус вместо переноса.\n if (event.shiftKey && MOVE_KEYS.has(event.key)) {\n if (!treeProps.draggable) return\n\n event.preventDefault()\n void onNodeMoveKey(cur.node, event.key)\n return\n }\n\n // Вертикаль, `Home` и `End` ведёт примитив. Влево-вправо он не трогает: в\n // дереве это раскрытие ветки и переход к родителю, а не движение по списку.\n if (roving.handleNavigationKeys(event))\n return\n\n switch (event.key) {\n case 'ArrowRight':\n event.preventDefault()\n if (!cur.isLeaf) {\n if (!cur.isExpanded)\n toggleExpand(cur.node)\n else if (idx < rows.length - 1)\n focusRow(rows[idx + 1].node.key)\n }\n break\n case 'ArrowLeft':\n event.preventDefault()\n if (!cur.isLeaf && cur.isExpanded)\n toggleExpand(cur.node)\n else if (cur.node.parent && rows.some(r => r.node.key === cur.node.parent!.key))\n focusRow(cur.node.parent.key)\n break\n case 'Enter':\n event.preventDefault()\n onRowClick(cur.node)\n break\n // Space при чекбоксах переключает отметку — это и есть выбор в\n // multi-select дереве; без них он остаётся вторым способом выбрать узел.\n case ' ':\n event.preventDefault()\n if (treeProps.showCheckbox)\n toggleChecked(cur.node)\n else\n onRowClick(cur.node)\n break\n }\n}\n\nfunction onRowMouseEnter(node: GrTreeNode<T>) {\n hoveredKey.value = node.key\n}\n\nfunction onRowMouseLeave(node: GrTreeNode<T>) {\n if (hoveredKey.value === node.key)\n hoveredKey.value = undefined\n}\n\nfunction collapseSiblings(node: GrTreeNode<T>) {\n const siblings = node.parent ? node.parent.childNodes : treeStore.treeModel.value.roots\n\n for (const sibling of siblings) {\n if (sibling.key === node.key || !treeStore.isExpandedKey(sibling.key))\n continue\n\n treeStore.setExpandedKey(sibling.key, false)\n interactionContext.emitNodeCollapse(sibling.data, sibling)\n }\n}\n\nfunction toggleExpand(node: GrTreeNode<T>) {\n const expanded = !treeStore.isExpandedKey(node.key)\n\n if (expanded && treeProps.accordion)\n collapseSiblings(node)\n\n // Ленивая ветка грузится по первому раскрытию; повторное ничего не запрашивает.\n if (expanded)\n treeStore.ensureLoaded(node)\n\n treeStore.toggleExpand(node)\n\n if (expanded)\n interactionContext.emitNodeExpand(node.data, node)\n else\n interactionContext.emitNodeCollapse(node.data, node)\n}\n\n// ————— Отметки (чекбоксы).\n\nfunction checkStateOf(key: GrTreeKey): GrTreeCheckState {\n return treeStore.getCheckState(key)\n}\n\nfunction ariaCheckedOf(row: GrTreeVisibleRow<T>): 'true' | 'false' | 'mixed' | undefined {\n if (!treeProps.showCheckbox)\n return undefined\n\n const state = checkStateOf(row.node.key)\n return state === 'checked' ? 'true' : state === 'half' ? 'mixed' : 'false'\n}\n\nfunction emitCheck(node: GrTreeNode<T>): void {\n const checkedKeys = treeStore.getCheckedKeys()\n emit('update:checkedKeys', checkedKeys)\n emit('check', node.data, node, { checkedKeys, halfCheckedKeys: treeStore.getHalfCheckedKeys() })\n}\n\nfunction toggleChecked(node: GrTreeNode<T>): void {\n if (!treeProps.showCheckbox)\n return\n\n const changed = treeStore.toggleChecked(node)\n if (changed)\n emitCheck(changed)\n}\n\n// ————— Геометрия строки: отступ уровня и направляющие ветвей.\n\nfunction rowStyle(row: GrTreeVisibleRow<T>) {\n const step = treeProps.indent && treeProps.indent > 0\n ? `${treeProps.indent}px`\n : 'var(--gr-tree-indent-step)'\n\n return { '--gr-tree-row-indent': `calc(${step} * ${row.node.level - 1})` }\n}\n\n/**\n * Направляющая уровня-предка. Считается от того же шага отступа, что и сам\n * отступ строки, — иначе линия и содержимое разъедутся на вложенных уровнях.\n */\nfunction branchGuideStyle(row: GrTreeVisibleRow<T>, index: number) {\n const step = treeProps.indent && treeProps.indent > 0\n ? `${treeProps.indent}px`\n : 'var(--gr-tree-indent-step)'\n\n return {\n 'left': `calc(${step} * ${index} + var(--gr-tree-branch-line-offset))`,\n '--gr-tree-branch-line-color': row.branchColors[index],\n }\n}\n\n// Drag & drop\nfunction canDrag(node: GrTreeNode<T>): boolean {\n return interactionContext.canDrag(node)\n}\n\nfunction shouldShowDragHandle(node: GrTreeNode<T>): boolean {\n return canDrag(node) && hoveredKey.value === node.key\n}\n\n/**\n * Треть строки сверху — «до», треть снизу — «после», середина — «внутрь».\n * Зоны нарезает компонент: примитив отдаёт только долю вдоль строки.\n */\nfunction dropTypeFromFraction(fraction: number): GrTreeNodeDropType {\n if (fraction < 1 / 3) return 'prev'\n if (fraction > 2 / 3) return 'next'\n\n return 'inner'\n}\n\nfunction canDrop(drag: GrTreeNode<T>, target: GrTreeNode<T>, type: GrTreeAllowDropType): boolean {\n return interactionContext.canDrop(drag, target, type)\n}\n\n/** Перенос состоялся: одна дорога и для указателя, и для клавиатуры. */\nfunction applyMove(source: GrTreeNode<T>, targetKey: GrTreeKey, type: GrTreeNodeDropType): boolean {\n const reference = treeStore.getNode(targetKey)\n if (!reference) return false\n\n const movedNode = treeStore.moveNode(source, reference, type)\n if (!movedNode) return false\n\n const dropNode = treeStore.getNode(reference.key) ?? reference\n interactionContext.emitNodeDrop(movedNode, dropNode, type)\n\n return true\n}\n\nconst dragSort = useDragSort<GrTreeKey, GrTreeDropTarget>({\n items: () => visibleRows.value.map(row => row.node.key),\n elementFor: key => interactionContext.nodeEls.get(key) ?? null,\n scroller: () => treeRootEl.value,\n disabled: () => !treeProps.draggable,\n canDrag: (key) => {\n const node = treeStore.getNode(key)\n\n return node ? canDrag(node) : false\n },\n resolveTarget: (hit, sourceKey, previous) => {\n const source = treeStore.getNode(sourceKey)\n const node = treeStore.getNode(hit.key)\n if (!source || !node) return null\n\n const type = dropTypeFromFraction(hit.fraction)\n const allowed = treeStore.canMoveNode(source, node, type) && canDrop(source, node, type)\n\n // Та же ссылка, пока смысл не изменился: иначе подсветка перерисовывалась бы\n // на каждом движении указателя.\n if (previous && previous.key === node.key && previous.type === type && previous.allowed === allowed)\n return previous\n\n return { key: node.key, type, allowed }\n },\n onDrop: (sourceKey, target) => {\n const source = treeStore.getNode(sourceKey)\n if (!source || !target.allowed) return\n\n applyMove(source, target.key, target.type)\n },\n onUpdate: (sourceKey, target) => {\n interactionContext.draggingNode.value = sourceKey === null ? null : treeStore.getNode(sourceKey) ?? null\n interactionContext.dropTarget.value = target\n },\n})\n\nfunction onHandlePointerDown(event: PointerEvent, node: GrTreeNode<T>): void {\n if (!treeProps.draggable) return\n\n dragSort.startFrom(node.key)(event)\n}\n\n/**\n * Клавиатурный перенос — раскладка аутлайнера: `Shift` со стрелкой сразу\n * двигает узел, без режима «взято». Режим здесь был бы лишним: `Space` в дереве\n * уже отмечает чекбокс, а стрелки водят фокус по веткам.\n */\nfunction moveByKeyboard(node: GrTreeNode<T>, key: string): boolean {\n const siblings = node.parent ? node.parent.childNodes : treeStore.treeModel.value.roots\n const index = siblings.findIndex(sibling => sibling.key === node.key)\n\n if (key === 'ArrowUp' && index > 0)\n return applyMove(node, siblings[index - 1].key, 'prev')\n\n if (key === 'ArrowDown' && index >= 0 && index < siblings.length - 1)\n return applyMove(node, siblings[index + 1].key, 'next')\n\n // Вправо — в предыдущего соседа: у первого в уровне такого соседа нет.\n if (key === 'ArrowRight' && index > 0)\n return applyMove(node, siblings[index - 1].key, 'inner')\n\n if (key === 'ArrowLeft' && node.parent)\n return applyMove(node, node.parent.key, 'next')\n\n return false\n}\n\nasync function onNodeMoveKey(node: GrTreeNode<T>, key: string): Promise<void> {\n if (!treeProps.draggable || !canDrag(node)) return\n if (!moveByKeyboard(node, key)) return\n\n announce(t('gr.tree.moved', 'Moved {label}', { label: node.label }))\n\n // Перестановка перерисовывает уровень: без возврата фокуса следующий Shift\n // уже некуда было бы адресовать.\n await nextTick()\n focusRow(node.key)\n}\n\nfunction filter(value: string) {\n treeStore.filter(value)\n}\n\nfunction resolveNodeClass(classValue: GrTreeNodeClass<T> | undefined, row: GrTreeVisibleRow<T>) {\n if (!classValue)\n return undefined\n\n return typeof classValue === 'function'\n ? classValue(row)\n : classValue\n}\n\nfunction resolveBranchLineColorValue(color: GrTreeBranchLineColor<T> | undefined, node: GrTreeNode<T>) {\n if (!color)\n return undefined\n\n return typeof color === 'function'\n ? color(node) ?? undefined\n : color\n}\n\nfunction isBranchLineActive(node: GrTreeNode<T>) {\n const selectedKey = currentKey.value\n\n if (selectedKey == null)\n return false\n\n if (node.key === selectedKey)\n return true\n\n return node.childNodes.some(child => child.key === selectedKey && !treeStore.isExpandedKey(child.key))\n}\n\nfunction resolveBranchLineColor(node: GrTreeNode<T>) {\n const defaultColor = resolveBranchLineColorValue(treeProps.branchLineColor, node) ?? DEFAULT_BRANCH_LINE_COLOR\n\n if (!isBranchLineActive(node))\n return defaultColor\n\n return resolveBranchLineColorValue(treeProps.branchLineActiveColor, node) ?? defaultColor\n}\n\n/**\n * Переводит фокус на узел (по умолчанию — на тот, что держит roving tabindex).\n * Нужен снаружи: `GrTreeSelect` открывает панель и обязан отдать клавиатуру\n * дереву, иначе стрелки из триггера никуда не ведут.\n */\nfunction focus(key?: GrTreeKey): boolean {\n const rows = visibleRows.value\n if (rows.length === 0)\n return false\n\n const target = key != null && rows.some(row => row.node.key === key)\n ? key\n : roving.rovingKey.value ?? rows[0].node.key\n\n focusRow(target)\n return true\n}\n\nonUnmounted(() => clearTimeout(typeaheadTimer))\n// Дерево уничтожают целиком — строки размонтируются вместе с ним, и возвращать\n// фокус на умирающий корень нельзя: он уедет из документа вместе с деревом.\nonBeforeUnmount(() => { isUnmounting = true })\n\ndefineExpose<GrTreeInstance<T>>({\n appendNode: treeStore.appendNode,\n getCheckedKeys: options => treeStore.getCheckedKeys(options),\n setCheckedKeys: (keys) => {\n treeStore.setCheckedKeys(keys)\n emit('update:checkedKeys', treeStore.getCheckedKeys())\n },\n getHalfCheckedKeys: treeStore.getHalfCheckedKeys,\n setChecked: (node, checked) => {\n const changed = treeStore.setChecked(node, checked)\n if (changed)\n emitCheck(changed)\n return Boolean(changed)\n },\n filter,\n focus,\n getCurrentNode: treeStore.getCurrentNode,\n setCurrentKey: treeStore.setCurrentKey,\n getCurrentKey: treeStore.getCurrentKey,\n getNode: treeStore.getNode,\n insertNodeAfter: treeStore.insertNodeAfter,\n insertNodeBefore: treeStore.insertNodeBefore,\n removeNode: treeStore.removeNode,\n setCurrentNode: treeStore.setCurrentNode,\n})\n</script>\n\n<template>\n <div\n ref=\"treeRootEl\"\n data-gr-tree\n class=\"gr-tree\"\n tabindex=\"-1\"\n :data-gr-virtual=\"treeProps.virtual ? '' : undefined\"\n role=\"tree\"\n :aria-multiselectable=\"treeProps.showCheckbox ? 'true' : undefined\"\n :style=\"rootStyle\"\n @keydown=\"onTreeKeydown\"\n >\n <div\n v-for=\"(row, windowIndex) in renderedRows\"\n :key=\"row.node.key\"\n :ref=\"(el) => {\n if (el) interactionContext.registerNodeEl(row.node.key, el as HTMLElement)\n else releaseNodeEl(row.node.key)\n if (treeProps.virtual) virtualizer.measure(absoluteIndex(windowIndex), el as Element | null)\n }\"\n data-gr-tree-node\n :data-gr-tree-node-key=\"row.node.key\"\n role=\"treeitem\"\n :aria-level=\"row.node.level\"\n :aria-posinset=\"row.posInSet\"\n :aria-setsize=\"row.setSize\"\n :aria-expanded=\"row.isLeaf ? undefined : row.isExpanded\"\n :aria-selected=\"currentKey === row.node.key ? 'true' : undefined\"\n :aria-checked=\"ariaCheckedOf(row)\"\n :aria-busy=\"row.isLoading ? 'true' : undefined\"\n :tabindex=\"isRovingItem(row.node.key) ? 0 : -1\"\n >\n <div\n data-gr-tree-row\n class=\"gr-tree__row\"\n :class=\"[\n treeProps.highlightCurrent && currentKey === row.node.key ? 'gr-tree__row--current' : '',\n row.isMatched ? 'gr-tree__row--matched' : '',\n dropTarget?.key === row.node.key && dropTarget.allowed && dropTarget.type === 'inner' ? 'gr-tree__row--drop-inner' : '',\n dropTarget?.key === row.node.key && dropTarget.allowed && dropTarget.type === 'prev' ? 'gr-tree__row--drop-prev' : '',\n dropTarget?.key === row.node.key && dropTarget.allowed && dropTarget.type === 'next' ? 'gr-tree__row--drop-next' : '',\n resolveNodeClass(treeProps.rowClass, row),\n ]\"\n :style=\"rowStyle(row)\"\n @click=\"onRowActivate(row)\"\n @contextmenu=\"onRowContextMenu($event, row.node)\"\n @drop.prevent\n @mouseenter=\"onRowMouseEnter(row.node)\"\n @mouseleave=\"onRowMouseLeave(row.node)\"\n >\n <!--\n Направляющие ветвей: по одной на каждый уровень предка. Вложенной\n обёртки с `border-left` больше нет, поэтому линию рисует сама строка —\n и заходит в зазор между строками, иначе она распалась бы на штрихи.\n -->\n <span\n v-for=\"(color, guideIndex) in row.branchColors\"\n :key=\"guideIndex\"\n data-gr-tree-branch-guide\n class=\"gr-tree__branch-guide\"\n :style=\"branchGuideStyle(row, guideIndex)\"\n aria-hidden=\"true\"\n />\n\n <button\n v-if=\"treeProps.draggable\"\n type=\"button\"\n tabindex=\"-1\"\n data-gr-tree-drag-handle\n class=\"gr-tree__drag-handle\"\n :class=\"[\n shouldShowDragHandle(row.node) ? 'gr-tree__drag-handle--visible' : '',\n canDrag(row.node) ? '' : 'gr-tree__drag-handle--disabled',\n resolveNodeClass(treeProps.dragHandleClass, row),\n ]\"\n :aria-label=\"dragLabel\"\n :aria-disabled=\"canDrag(row.node) ? undefined : 'true'\"\n @click.stop\n @pointerdown=\"onHandlePointerDown($event, row.node)\"\n >\n <span class=\"gr-tree__drag-icon\" :class=\"treeProps.dragHandleIcon\" />\n </button>\n\n <!-- tabindex=\"-1\": внутри roving-композита интерактив не табируется,\n клавиатурный эквивалент — ArrowRight/ArrowLeft на строке. -->\n <button\n v-if=\"!row.isLeaf\"\n data-gr-tree-toggle\n type=\"button\"\n tabindex=\"-1\"\n class=\"gr-tree__toggle\"\n :class=\"resolveNodeClass(treeProps.toggleClass, row)\"\n :aria-label=\"row.isExpanded ? collapseLabel : expandLabel\"\n @click.stop=\"toggleExpand(row.node)\"\n >\n <span\n v-if=\"row.isLoading\"\n data-gr-tree-loading\n class=\"gr-tree__toggle-icon animate-spin i-lucide-loader-circle\"\n />\n <span\n v-else\n class=\"gr-tree__toggle-icon\"\n :class=\"[\n row.isExpanded ? treeProps.collapseIcon : treeProps.expandIcon,\n row.isExpanded && treeProps.toggleIconRotate ? 'gr-tree__toggle-icon--expanded' : '',\n resolveNodeClass(treeProps.toggleIconClass, row),\n ]\"\n />\n </button>\n <span\n v-else\n class=\"gr-tree__toggle-spacer\"\n :class=\"resolveNodeClass(treeProps.toggleSpacerClass, row)\"\n />\n\n <!--\n Квадратик отметки декоративен: состояние живёт на самом `treeitem`\n (`aria-checked`), а вложить интерактивный чекбокс внутрь роли-виджета\n нельзя — роль объявляет потомков презентационными.\n -->\n <span\n v-if=\"treeProps.showCheckbox\"\n data-gr-tree-checkbox\n class=\"gr-tree__checkbox\"\n :class=\"[\n checkStateOf(row.node.key) === 'checked' ? 'gr-tree__checkbox--checked' : '',\n checkStateOf(row.node.key) === 'half' ? 'gr-tree__checkbox--half' : '',\n ]\"\n aria-hidden=\"true\"\n @click.stop=\"toggleChecked(row.node)\"\n >\n <span\n v-if=\"checkStateOf(row.node.key) !== 'unchecked'\"\n class=\"gr-tree__checkbox-mark\"\n :class=\"checkStateOf(row.node.key) === 'half' ? 'i-lucide-minus' : 'i-lucide-check'\"\n />\n </span>\n\n <div class=\"gr-tree__content\" :class=\"resolveNodeClass(treeProps.contentClass, row)\">\n <slot\n :node=\"row.node\"\n :data=\"row.node.data\"\n >\n <span class=\"gr-tree__label\">{{ row.node.label }}</span>\n </slot>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.gr-tree {\n --gr-tree-gap: 2px;\n --gr-tree-children-pl: 10px;\n --gr-tree-row-min-height: 28px;\n --gr-tree-row-radius: 8px;\n --gr-tree-row-px: 8px;\n --gr-tree-row-py: 8px;\n --gr-tree-row-pr: var(--gr-tree-row-px);\n --gr-tree-font-size: inherit;\n --gr-tree-row-color: var(--gr-fg);\n --gr-tree-row-hover-bg: color-mix(in srgb, var(--gr-primary) 10%, transparent);\n --gr-tree-row-current-bg: color-mix(in srgb, var(--gr-primary) 5%, transparent);\n --gr-tree-row-current-hover-bg: color-mix(in srgb, var(--gr-primary) 16%, transparent);\n --gr-tree-drag-handle-size: 24px;\n --gr-tree-drag-handle-mr: 0;\n --gr-tree-drag-handle-radius: 6px;\n --gr-tree-drag-handle-color: inherit;\n --gr-tree-drag-handle-opacity: 0.55;\n --gr-tree-drag-handle-hover-bg: color-mix(in srgb, var(--gr-muted) 22%, transparent);\n --gr-tree-drag-handle-hover-color: var(--gr-tree-drag-handle-color);\n --gr-tree-drag-handle-hover-opacity: 0.9;\n --gr-tree-drag-handle-disabled-opacity: 0.25;\n --gr-tree-toggle-size: 24px;\n --gr-tree-toggle-mr: 0;\n --gr-tree-toggle-radius: 6px;\n --gr-tree-toggle-color: inherit;\n --gr-tree-toggle-hover-bg: color-mix(in srgb, var(--gr-muted) 25%, transparent);\n --gr-tree-toggle-hover-color: var(--gr-tree-toggle-color);\n --gr-tree-icon-size: 16px;\n --gr-tree-content-gap: 8px;\n --gr-tree-branch-line-default-color: var(--gr-tree-row-current-bg);\n --gr-tree-branch-line-width: 2px;\n /* Шаг отступа уровня: переключатель, отступ вложенного списка и толщина\n направляющей — линия обязана попадать между ними. */\n --gr-tree-indent-step: calc(24px + var(--gr-tree-children-pl) + var(--gr-tree-branch-line-width));\n --gr-tree-branch-line-offset: 24px;\n --gr-tree-checkbox-size: 16px;\n --gr-tree-checkbox-radius: 4px;\n --gr-tree-checkbox-brd: var(--gr-brd);\n --gr-tree-checkbox-bg: transparent;\n --gr-tree-checkbox-checked-bg: var(--gr-primary);\n --gr-tree-checkbox-checked-fg: var(--gr-primary-fg);\n --gr-tree-checkbox-mr: 4px;\n display: flex;\n flex-direction: column;\n gap: var(--gr-tree-gap);\n}\n\n[data-gr-virtual]::before,\n[data-gr-virtual]::after {\n content: '';\n display: block;\n flex: none;\n}\n\n[data-gr-virtual]::before {\n height: var(--gr-virtual-before, 0px);\n}\n\n[data-gr-virtual]::after {\n height: var(--gr-virtual-after, 0px);\n}\n\n.gr-tree__row {\n position: relative;\n display: flex;\n align-items: center;\n min-height: var(--gr-tree-row-min-height);\n padding: var(--gr-tree-row-py) var(--gr-tree-row-pr) var(--gr-tree-row-py)\n calc(var(--gr-tree-row-px) + var(--gr-tree-row-indent, 0px));\n font-size: var(--gr-tree-font-size);\n cursor: default;\n user-select: none;\n outline: none;\n color: var(--gr-tree-row-color);\n}\n\n/* Поверхность строки — отдельный слой, вставленный слева на отступ уровня.\n Фон на самой коробке шёл от левого края дерева и накрывал направляющие\n предков: линия ветки исчезала под подсветкой ровно там, где она и нужна.\n Слой лежит ниже направляющих (они — следующие элементы строки), поэтому\n линия остаётся видна при любом наложении. */\n.gr-tree__row::before {\n content: '';\n position: absolute;\n inset: 0 0 0 var(--gr-tree-row-indent, 0px);\n border-radius: var(--gr-tree-row-radius);\n pointer-events: none;\n}\n\n.gr-tree__row:hover::before {\n background: var(--gr-tree-row-hover-bg);\n}\n\n/* Направляющая тянется в зазор между строками: иначе линия ветки распалась бы\n на штрихи по числу строк. */\n.gr-tree__branch-guide {\n position: absolute;\n top: calc(var(--gr-tree-gap) / -2);\n bottom: calc(var(--gr-tree-gap) / -2);\n width: var(--gr-tree-branch-line-width);\n background: var(--gr-tree-branch-line-color, var(--gr-tree-branch-line-default-color));\n pointer-events: none;\n}\n\n.gr-tree__checkbox {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: var(--gr-tree-checkbox-size);\n height: var(--gr-tree-checkbox-size);\n margin-right: var(--gr-tree-checkbox-mr);\n border: 1px solid var(--gr-tree-checkbox-brd);\n border-radius: var(--gr-tree-checkbox-radius);\n background: var(--gr-tree-checkbox-bg);\n color: var(--gr-tree-checkbox-checked-fg);\n cursor: pointer;\n}\n\n.gr-tree__checkbox--checked,\n.gr-tree__checkbox--half {\n border-color: var(--gr-tree-checkbox-checked-bg);\n background: var(--gr-tree-checkbox-checked-bg);\n}\n\n.gr-tree__checkbox-mark {\n width: calc(var(--gr-tree-checkbox-size) - 4px);\n height: calc(var(--gr-tree-checkbox-size) - 4px);\n}\n\n\n[data-gr-tree-node] {\n outline: none;\n}\n\n/* Кольцо фокуса — на той же поверхности, что подсветка: иначе строка читалась бы\n двумя разными прямоугольниками. `outline` для этого не годится — он рисуется по\n границе коробки, а не по инсету. */\n[data-gr-tree-node]:focus-visible > .gr-tree__row::before {\n box-shadow: inset 0 0 0 2px var(--gr-primary);\n}\n\n.gr-tree__row--current::before {\n background: var(--gr-tree-row-current-bg);\n}\n\n.gr-tree__row--current:hover::before {\n background: var(--gr-tree-row-current-hover-bg);\n}\n\n.gr-tree__row--matched .gr-tree__label {\n font-weight: 600;\n}\n\n.gr-tree__drag-handle {\n width: var(--gr-tree-drag-handle-size);\n height: var(--gr-tree-drag-handle-size);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n margin-right: var(--gr-tree-drag-handle-mr);\n border-radius: var(--gr-tree-drag-handle-radius);\n color: var(--gr-tree-drag-handle-color);\n cursor: grab;\n /* Без этого вертикальный свайп по ручке уходит в прокрутку страницы,\n браузер отзывает указатель, и пальцем узел неперетаскиваем. */\n touch-action: none;\n visibility: hidden;\n pointer-events: none;\n opacity: 0;\n}\n\n.gr-tree__drag-handle--visible {\n visibility: visible;\n pointer-events: auto;\n opacity: var(--gr-tree-drag-handle-opacity);\n}\n\n.gr-tree__drag-handle:hover {\n background: var(--gr-tree-drag-handle-hover-bg);\n color: var(--gr-tree-drag-handle-hover-color);\n opacity: var(--gr-tree-drag-handle-hover-opacity);\n}\n\n.gr-tree__drag-handle:active {\n cursor: grabbing;\n}\n\n.gr-tree__drag-handle--disabled {\n cursor: default;\n opacity: var(--gr-tree-drag-handle-disabled-opacity);\n}\n\n.gr-tree__drag-icon {\n width: var(--gr-tree-icon-size);\n height: var(--gr-tree-icon-size);\n}\n\n.gr-tree__toggle {\n width: var(--gr-tree-toggle-size);\n height: var(--gr-tree-toggle-size);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n margin-right: var(--gr-tree-toggle-mr);\n border-radius: var(--gr-tree-toggle-radius);\n color: var(--gr-tree-toggle-color);\n}\n\n.gr-tree__toggle:hover {\n background: var(--gr-tree-toggle-hover-bg);\n color: var(--gr-tree-toggle-hover-color);\n}\n\n.gr-tree__toggle-icon {\n width: var(--gr-tree-icon-size);\n height: var(--gr-tree-icon-size);\n transition: transform var(--gr-duration-fast) var(--gr-ease-out);\n}\n\n.gr-tree__toggle-icon--expanded {\n transform: rotate(90deg);\n}\n\n.gr-tree__toggle-spacer {\n width: var(--gr-tree-toggle-size);\n height: var(--gr-tree-toggle-size);\n margin-right: var(--gr-tree-toggle-mr);\n display: inline-block;\n}\n\n.gr-tree__content {\n min-width: 0;\n display: flex;\n align-items: center;\n gap: var(--gr-tree-content-gap);\n}\n\n.gr-tree__label {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.gr-tree__row--drop-inner {\n outline: 2px solid color-mix(in srgb, var(--gr-primary) 40%, transparent);\n}\n\n.gr-tree__row--drop-prev::before,\n.gr-tree__row--drop-next::after {\n content: '';\n position: absolute;\n left: 8px;\n right: 8px;\n height: 2px;\n background: color-mix(in srgb, var(--gr-primary) 55%, transparent);\n}\n\n.gr-tree__row--drop-prev::before {\n top: 2px;\n}\n\n.gr-tree__row--drop-next::after {\n bottom: 2px;\n}\n</style>\n"],"mappings":"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"}