@feugene/granularity 0.46.0 → 0.48.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 (162) hide show
  1. package/README.md +3 -3
  2. package/dist/chunks/{x-circle-BCBURwyX.js → check-circle-Bnrb-CGJ.js} +8 -32
  3. package/dist/chunks/check-circle-Bnrb-CGJ.js.map +1 -0
  4. package/dist/chunks/{config-BqT6PMzO.js → config-B42iJ48O.js} +5 -1
  5. package/dist/chunks/{config-BqT6PMzO.js.map → config-B42iJ48O.js.map} +1 -1
  6. package/dist/chunks/{config-BGCyyXiD.js → config-BAse0laT.js} +5 -1
  7. package/dist/chunks/{config-BGCyyXiD.js.map → config-BAse0laT.js.map} +1 -1
  8. package/dist/chunks/{config-MmPym-Z7.js → config-BLP94dPY.js} +5 -1
  9. package/dist/chunks/{config-MmPym-Z7.js.map → config-BLP94dPY.js.map} +1 -1
  10. package/dist/chunks/{config-CjKpUcmy.js → config-Bdf1bt-m.js} +25 -16
  11. package/dist/chunks/config-Bdf1bt-m.js.map +1 -0
  12. package/dist/chunks/{config-DeyT9dJl.js → config-Bs1HwUcx.js} +47 -4
  13. package/dist/chunks/{config-DeyT9dJl.js.map → config-Bs1HwUcx.js.map} +1 -1
  14. package/dist/chunks/{config-CIkdsWUk.js → config-CIUU9ZpY.js} +4 -1
  15. package/dist/chunks/config-CIUU9ZpY.js.map +1 -0
  16. package/dist/chunks/{config-B3J0alyY.js → config-CzeZg2LO.js} +49 -4
  17. package/dist/chunks/config-CzeZg2LO.js.map +1 -0
  18. package/dist/chunks/{config-DCq4JySZ.js → config-D3HOuHDe.js} +1 -1
  19. package/dist/chunks/{config-DCq4JySZ.js.map → config-D3HOuHDe.js.map} +1 -1
  20. package/dist/chunks/{config-BhfNgyWL.js → config-DENqOoh0.js} +5 -1
  21. package/dist/chunks/config-DENqOoh0.js.map +1 -0
  22. package/dist/chunks/controlState-DoImKciE.js +35 -0
  23. package/dist/chunks/controlState-DoImKciE.js.map +1 -0
  24. package/dist/chunks/{en-DiHzFact.js → en-Bxyn5lW7.js} +12 -2
  25. package/dist/chunks/en-Bxyn5lW7.js.map +1 -0
  26. package/dist/chunks/{es-CeKkMxZ_.js → es-Bj_vc18Z.js} +3 -3
  27. package/dist/chunks/{es-CeKkMxZ_.js.map → es-Bj_vc18Z.js.map} +1 -1
  28. package/dist/chunks/{es-1ZL6NtkB.js → es-Dtl-a7ui.js} +12 -2
  29. package/dist/chunks/es-Dtl-a7ui.js.map +1 -0
  30. package/dist/chunks/{generated-CQ4h6Ieq.js → generated-hELWB0gZ.js} +98 -0
  31. package/dist/chunks/{generated-CQ4h6Ieq.js.map → generated-hELWB0gZ.js.map} +1 -1
  32. package/dist/chunks/{grTableStyles-uuMPSOBR.js → grTableStyles-DkQPMfx9.js} +16 -3
  33. package/dist/chunks/grTableStyles-DkQPMfx9.js.map +1 -0
  34. package/dist/chunks/{granular-provider-CsN_R9dB.js → granular-provider-BOQp7N5o.js} +9 -9
  35. package/dist/chunks/{granular-provider-CsN_R9dB.js.map → granular-provider-BOQp7N5o.js.map} +1 -1
  36. package/dist/chunks/{ru-Dr2RYn0P.js → ru-hrB4I1u_.js} +12 -2
  37. package/dist/chunks/ru-hrB4I1u_.js.map +1 -0
  38. package/dist/chunks/x-circle-BigSZy8m.js +26 -0
  39. package/dist/chunks/x-circle-BigSZy8m.js.map +1 -0
  40. package/dist/components/GrAlert/chunks/{GrAlert-D72y1IEh.js → GrAlert-CfEZghSY.js} +2 -1
  41. package/dist/components/GrAlert/chunks/{GrAlert-D72y1IEh.js.map → GrAlert-CfEZghSY.js.map} +1 -1
  42. package/dist/components/GrAlert/index.js +1 -1
  43. package/dist/components/GrAutocomplete/chunks/{GrAutocomplete-D396_JrW.js → GrAutocomplete-C-uovdv8.js} +320 -224
  44. package/dist/components/GrAutocomplete/chunks/GrAutocomplete-C-uovdv8.js.map +1 -0
  45. package/dist/components/GrAutocomplete/index.js +1 -1
  46. package/dist/components/GrAutocomplete/styles.css +1 -1
  47. package/dist/components/GrAvatar/chunks/{GrAvatar-dWEXiCJG.js → GrAvatar-Do5-C409.js} +12 -1
  48. package/dist/components/GrAvatar/chunks/GrAvatar-Do5-C409.js.map +1 -0
  49. package/dist/components/GrAvatar/index.js +2 -2
  50. package/dist/components/GrAvatar/themes/light.css +35 -0
  51. package/dist/components/GrColorPicker/chunks/{GrColorPicker-9mTR-YZ_.js → GrColorPicker-BRM_4lyI.js} +1 -1
  52. package/dist/components/GrColorPicker/chunks/{GrColorPicker-9mTR-YZ_.js.map → GrColorPicker-BRM_4lyI.js.map} +1 -1
  53. package/dist/components/GrColorPicker/index.js +1 -1
  54. package/dist/components/GrCommandPalette/chunks/{GrCommandPalette-Ha4s3DMT.js.map → GrCommandPalette-BJHmkDQv.js.map} +1 -1
  55. package/dist/components/GrCommandPalette/index.js +1 -1
  56. package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-e15nFI8M.js → GrConfirmDialog-BzUQwbec.js} +1 -1
  57. package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-e15nFI8M.js.map → GrConfirmDialog-BzUQwbec.js.map} +1 -1
  58. package/dist/components/GrConfirmDialog/index.js +1 -1
  59. package/dist/components/GrDataTable/chunks/GrDataTable-DQxYYgQk.js +1519 -0
  60. package/dist/components/GrDataTable/chunks/GrDataTable-DQxYYgQk.js.map +1 -0
  61. package/dist/components/GrDataTable/index.js +2 -2
  62. package/dist/components/GrDataTable/themes/light.css +10 -0
  63. package/dist/components/GrDialogService/chunks/{GrDialogService-BmXiYmUp.js → GrDialogService-Dtq0ZCN5.js} +3 -3
  64. package/dist/components/GrDialogService/chunks/{GrDialogService-BmXiYmUp.js.map → GrDialogService-Dtq0ZCN5.js.map} +1 -1
  65. package/dist/components/GrDialogService/index.js +1 -1
  66. package/dist/components/GrFormField/chunks/GrFormField-DO61vxpS.js.map +1 -1
  67. package/dist/components/GrInput/chunks/{GrInput-TFeBm3gQ.js → GrInput-DV4gMWPR.js} +40 -11
  68. package/dist/components/GrInput/chunks/GrInput-DV4gMWPR.js.map +1 -0
  69. package/dist/components/GrInput/index.js +2 -2
  70. package/dist/components/GrInputTag/chunks/{GrInputTag-Ci8AO1ze.js → GrInputTag-CO0YU-ok.js} +30 -6
  71. package/dist/components/GrInputTag/chunks/GrInputTag-CO0YU-ok.js.map +1 -0
  72. package/dist/components/GrInputTag/index.js +2 -2
  73. package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-C6q8dEEZ.js → GrJsonViewer-D7CyWzhT.js} +1 -1
  74. package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-C6q8dEEZ.js.map → GrJsonViewer-D7CyWzhT.js.map} +1 -1
  75. package/dist/components/GrJsonViewer/index.js +1 -1
  76. package/dist/components/GrNumberInput/chunks/{GrNumberInput-CL5QhOmx.js → GrNumberInput-BI6-vkcs.js} +48 -16
  77. package/dist/components/GrNumberInput/chunks/GrNumberInput-BI6-vkcs.js.map +1 -0
  78. package/dist/components/GrNumberInput/index.js +2 -2
  79. package/dist/components/GrPagination/chunks/{GrPagination-DhbNZHEF.js → GrPagination-BbaF5eXk.js} +10 -7
  80. package/dist/components/GrPagination/chunks/GrPagination-BbaF5eXk.js.map +1 -0
  81. package/dist/components/GrPagination/index.js +2 -2
  82. package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-8RxQsOQ_.js → GrPromptDialog-BEL-QgwE.js} +3 -3
  83. package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-8RxQsOQ_.js.map → GrPromptDialog-BEL-QgwE.js.map} +1 -1
  84. package/dist/components/GrPromptDialog/index.js +1 -1
  85. package/dist/components/GrResponseErrorBanner/chunks/{GrResponseErrorBanner-NEEJZ8zi.js → GrResponseErrorBanner-Bdd5c07x.js} +1 -1
  86. package/dist/components/GrResponseErrorBanner/chunks/{GrResponseErrorBanner-NEEJZ8zi.js.map → GrResponseErrorBanner-Bdd5c07x.js.map} +1 -1
  87. package/dist/components/GrResponseErrorBanner/chunks/{GrResponseErrorBanner-ZvBvEQDZ.js → GrResponseErrorBanner-DySe1ym4.js} +1 -1
  88. package/dist/components/GrResponseErrorBanner/chunks/{GrResponseErrorBanner-ZvBvEQDZ.js.map → GrResponseErrorBanner-DySe1ym4.js.map} +1 -1
  89. package/dist/components/GrResponseErrorBanner/index.js +2 -2
  90. package/dist/components/GrSelect/chunks/{GrSelect-Do1iAyp1.js → GrSelect-0iI4wdR_.js} +3 -3
  91. package/dist/components/GrSelect/chunks/{GrSelect-Do1iAyp1.js.map → GrSelect-0iI4wdR_.js.map} +1 -1
  92. package/dist/components/GrSelect/index.js +1 -1
  93. package/dist/components/GrTable/chunks/{GrTable-iwzHvlMC.js → GrTable-CKJ-HrsN.js} +1 -1
  94. package/dist/components/GrTable/chunks/{GrTable-iwzHvlMC.js.map → GrTable-CKJ-HrsN.js.map} +1 -1
  95. package/dist/components/GrTable/index.js +2 -2
  96. package/dist/components/GrTextarea/chunks/{GrTextarea-BII3w61K.js → GrTextarea-Bfgc0DD2.js} +73 -32
  97. package/dist/components/GrTextarea/chunks/GrTextarea-Bfgc0DD2.js.map +1 -0
  98. package/dist/components/GrTextarea/index.js +2 -2
  99. package/dist/components/GrToaster/chunks/{GrToaster-D-sV-9of.js → GrToaster-CZixNk0l.js} +2 -1
  100. package/dist/components/GrToaster/chunks/{GrToaster-D-sV-9of.js.map → GrToaster-CZixNk0l.js.map} +1 -1
  101. package/dist/components/GrToaster/index.js +1 -1
  102. package/dist/components/GrTransfer/chunks/{GrTransfer-8FA78ny6.js → GrTransfer-DdfBUf35.js} +2 -2
  103. package/dist/components/GrTransfer/chunks/{GrTransfer-8FA78ny6.js.map → GrTransfer-DdfBUf35.js.map} +1 -1
  104. package/dist/components/GrTransfer/index.js +1 -1
  105. package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-DBu5nMGA.js → GrTreeSelect-B6KoDYiZ.js} +43 -18
  106. package/dist/components/GrTreeSelect/chunks/GrTreeSelect-B6KoDYiZ.js.map +1 -0
  107. package/dist/components/GrTreeSelect/index.js +2 -2
  108. package/dist/granular-provider-node.js +1 -1
  109. package/dist/granular-provider.js +1 -1
  110. package/dist/i18n/all.js +1 -1
  111. package/dist/i18n/index.js +1 -1
  112. package/dist/index.js +32 -32
  113. package/dist/theme.js +1 -1
  114. package/dist/tokens.js +1 -1
  115. package/dist/types/components/GrAutocomplete/composables/useAutocompleteNavigation.d.ts +39 -0
  116. package/dist/types/components/GrAutocomplete/composables/useAutocompletePanel.d.ts +36 -0
  117. package/dist/types/components/GrAutocomplete/composables/useAutocompleteValues.d.ts +28 -0
  118. package/dist/types/components/GrAutocomplete/composables/useAutocompleteVirtual.d.ts +33 -0
  119. package/dist/types/components/GrAutocomplete/composables/useRemoteOptions.d.ts +33 -0
  120. package/dist/types/components/GrAvatar/GrAvatar.vue.d.ts +11 -0
  121. package/dist/types/components/GrAvatar/defaults.d.ts +1 -0
  122. package/dist/types/components/GrAvatar/grAvatarStyles.d.ts +26 -1
  123. package/dist/types/components/GrDataTable/GrDataTable.vue.d.ts +60 -1
  124. package/dist/types/components/GrDataTable/composables/useDataTableColumnOrder.d.ts +45 -0
  125. package/dist/types/components/GrDataTable/composables/useDataTableColumnWidths.d.ts +38 -0
  126. package/dist/types/components/GrDataTable/composables/useDataTableExpansion.d.ts +46 -0
  127. package/dist/types/components/GrDataTable/composables/useDataTableLayout.d.ts +29 -0
  128. package/dist/types/components/GrDataTable/composables/useDataTableRowKeys.d.ts +16 -0
  129. package/dist/types/components/GrDataTable/composables/useDataTableSelection.d.ts +32 -0
  130. package/dist/types/components/GrDataTable/composables/useDataTableSort.d.ts +37 -0
  131. package/dist/types/components/GrDataTable/grDataTableStyles.d.ts +23 -0
  132. package/dist/types/components/GrFormField/GrFormField.vue.d.ts +7 -1
  133. package/dist/types/components/GrPagination/GrPagination.vue.d.ts +17 -2
  134. package/dist/types/components/GrPagination/grPaginationStyles.d.ts +15 -6
  135. package/dist/types/components/GrPagination/index.d.ts +1 -1
  136. package/dist/types/components/GrTable/grTableStyles.d.ts +16 -3
  137. package/dist/types/components/shared/controlState.d.ts +36 -0
  138. package/dist/types/i18n/messages/en.d.ts +20 -0
  139. package/dist/types/i18n/messages/es.d.ts +20 -0
  140. package/dist/types/i18n/messages/ru.d.ts +20 -0
  141. package/dist/web-types.json +79 -3
  142. package/package.json +1 -1
  143. package/dist/chunks/config-B3J0alyY.js.map +0 -1
  144. package/dist/chunks/config-BhfNgyWL.js.map +0 -1
  145. package/dist/chunks/config-CIkdsWUk.js.map +0 -1
  146. package/dist/chunks/config-CjKpUcmy.js.map +0 -1
  147. package/dist/chunks/en-DiHzFact.js.map +0 -1
  148. package/dist/chunks/es-1ZL6NtkB.js.map +0 -1
  149. package/dist/chunks/grTableStyles-uuMPSOBR.js.map +0 -1
  150. package/dist/chunks/ru-Dr2RYn0P.js.map +0 -1
  151. package/dist/chunks/x-circle-BCBURwyX.js.map +0 -1
  152. package/dist/components/GrAutocomplete/chunks/GrAutocomplete-D396_JrW.js.map +0 -1
  153. package/dist/components/GrAvatar/chunks/GrAvatar-dWEXiCJG.js.map +0 -1
  154. package/dist/components/GrDataTable/chunks/GrDataTable-xNSmO_I-.js +0 -1134
  155. package/dist/components/GrDataTable/chunks/GrDataTable-xNSmO_I-.js.map +0 -1
  156. package/dist/components/GrInput/chunks/GrInput-TFeBm3gQ.js.map +0 -1
  157. package/dist/components/GrInputTag/chunks/GrInputTag-Ci8AO1ze.js.map +0 -1
  158. package/dist/components/GrNumberInput/chunks/GrNumberInput-CL5QhOmx.js.map +0 -1
  159. package/dist/components/GrPagination/chunks/GrPagination-DhbNZHEF.js.map +0 -1
  160. package/dist/components/GrTextarea/chunks/GrTextarea-BII3w61K.js.map +0 -1
  161. package/dist/components/GrTreeSelect/chunks/GrTreeSelect-DBu5nMGA.js.map +0 -1
  162. package/dist/components/GrCommandPalette/chunks/{GrCommandPalette-Ha4s3DMT.js → GrCommandPalette-BJHmkDQv.js} +1 -1
@@ -1,1134 +0,0 @@
1
- import { a as useGrComponentSize } from "../../../chunks/context-CPMRfwVr.js";
2
- import { n as useGranularityTranslations } from "../../../chunks/granularityI18n-BC5zsj_d.js";
3
- import { t as loader_circle_default } from "../../../chunks/loader-circle-CfVLz7lb.js";
4
- import { t as GrIcon_default } from "../../GrIcon/chunks/GrIcon-DTZIgmje.js";
5
- import { useVirtualList } from "../../../composables/useVirtualList.js";
6
- import { useRovingFocus } from "../../../composables/useRovingFocus.js";
7
- import { useAnnouncer } from "../../../composables/useAnnouncer.js";
8
- import { useDragGesture } from "../../../composables/useDragGesture.js";
9
- import { t as GrCheckbox_default } from "../../GrCheckbox/chunks/GrCheckbox-Dp201frb.js";
10
- import { t as GrTable_default } from "../../GrTable/chunks/GrTable-iwzHvlMC.js";
11
- import { i as moveItem, r as insertionIndex } from "../../../chunks/dragSortGeometry-CY-llfrQ.js";
12
- import { useDragSort } from "../../../composables/useDragSort.js";
13
- import { C as sortIconSizes, S as selectColumnWidths, T as summaryRowClass, _ as headerTextSizes, a as columnDropAfterClass, b as rowSelectedClass, c as columnHandleClass, d as columnPinnedRightEdgeClass, f as columnResizerClass, g as headerGaps, h as columnResizerLineClass, i as columnDraggingClass, l as columnPinnedClass, m as columnResizerLineActiveClass, o as columnDropBeforeClass, p as columnResizerHoverClass, r as cellPaddings, s as columnHandleActiveClass, u as columnPinnedLeftEdgeClass, v as placeholderPaddings, w as spinnerSizes, x as selectCheckboxSizes, y as rowHeightEstimates } from "../../../chunks/config-DeyT9dJl.js";
14
- import { t as arrow_up_default } from "../../../chunks/arrow-up-C_G4jg8M.js";
15
- import { t as grip_vertical_default } from "../../../chunks/grip-vertical-lRha2S7q.js";
16
- import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, createTextVNode, createVNode, defineComponent, markRaw, mergeProps, nextTick, normalizeClass, normalizeStyle, onMounted, onUnmounted, openBlock, ref, renderList, renderSlot, toDisplayString, unref, useId, watch, watchEffect, withCtx, withModifiers } from "vue";
17
- //#region src/components/GrDataTable/grDataTableSort.ts
18
- /** Пустое значение ячейки: нечего сравнивать, место таким — в конце списка. */
19
- function isEmptyCell(value) {
20
- if (value === null || value === void 0) return true;
21
- return typeof value === "string" && value.trim() === "";
22
- }
23
- /**
24
- * Число, с которым имеет смысл сравнивать: сам `number`, числовая строка, `Date`
25
- * (по таймстампу), `boolean` (`false` < `true`). Иначе — `null`, и сравнение
26
- * уходит в строковую ветку.
27
- */
28
- function toComparableNumber(value) {
29
- if (typeof value === "number") return Number.isFinite(value) ? value : null;
30
- if (typeof value === "boolean") return value ? 1 : 0;
31
- if (typeof value === "bigint") return Number(value);
32
- if (value instanceof Date) {
33
- const time = value.getTime();
34
- return Number.isNaN(time) ? null : time;
35
- }
36
- if (typeof value === "string") {
37
- const trimmed = value.trim();
38
- if (trimmed === "") return null;
39
- const parsed = Number(trimmed);
40
- return Number.isFinite(parsed) ? parsed : null;
41
- }
42
- return null;
43
- }
44
- /**
45
- * Сравнивает два значения ячейки. Возвращает готовую разницу с учётом `dir` —
46
- * кроме пустых значений: они всегда в конце, иначе «пустые» в `desc` уезжали бы
47
- * наверх и таблица выглядела бы сломанной.
48
- */
49
- function compareCells(a, b, dir, locale) {
50
- const aEmpty = isEmptyCell(a);
51
- const bEmpty = isEmptyCell(b);
52
- if (aEmpty || bEmpty) return aEmpty && bEmpty ? 0 : aEmpty ? 1 : -1;
53
- const aNum = toComparableNumber(a);
54
- const bNum = toComparableNumber(b);
55
- const result = aNum !== null && bNum !== null ? aNum - bNum : String(a).localeCompare(String(b), locale, {
56
- sensitivity: "base",
57
- numeric: true
58
- });
59
- return dir === "asc" ? result : -result;
60
- }
61
- /**
62
- * Новый отсортированный массив. Исходный не мутируется: `rows` приходят пропом,
63
- * сортировка на месте меняла бы данные потребителя.
64
- */
65
- function sortRows(rows, key, dir, locale) {
66
- return [...rows].sort((a, b) => compareCells(a[key], b[key], dir, locale));
67
- }
68
- //#endregion
69
- //#region ~icons/lucide/arrow-down
70
- var _hoisted_1$1 = {
71
- viewBox: "0 0 24 24",
72
- width: "1.2em",
73
- height: "1.2em"
74
- };
75
- function render$1(_ctx, _cache) {
76
- return openBlock(), createElementBlock("svg", _hoisted_1$1, [..._cache[0] || (_cache[0] = [createElementVNode("path", {
77
- fill: "none",
78
- stroke: "currentColor",
79
- "stroke-linecap": "round",
80
- "stroke-linejoin": "round",
81
- "stroke-width": "2",
82
- d: "M12 5v14m7-7l-7 7l-7-7"
83
- }, null, -1)])]);
84
- }
85
- var arrow_down_default = markRaw({
86
- name: "lucide-arrow-down",
87
- render: render$1
88
- });
89
- //#endregion
90
- //#region src/components/GrDataTable/GrDataTable.vue?vue&type=script&setup=true&lang.ts
91
- var _hoisted_1 = ["id"];
92
- var _hoisted_2 = ["aria-rowindex"];
93
- var _hoisted_3 = ["data-column-key", "aria-sort"];
94
- var _hoisted_4 = [
95
- "tabindex",
96
- "aria-label",
97
- "disabled",
98
- "onPointerdown",
99
- "onKeydown",
100
- "onFocus"
101
- ];
102
- var _hoisted_5 = ["onClick"];
103
- var _hoisted_6 = { class: "sr-only" };
104
- var _hoisted_7 = {
105
- class: "inline-flex",
106
- "aria-hidden": "true"
107
- };
108
- var _hoisted_8 = {
109
- key: 2,
110
- class: "text-[var(--gr-muted-fg)]"
111
- };
112
- var _hoisted_9 = [
113
- "aria-label",
114
- "aria-valuenow",
115
- "aria-valuetext",
116
- "onPointerdown",
117
- "onKeydown",
118
- "onDblclick"
119
- ];
120
- var _hoisted_10 = {
121
- key: 0,
122
- "data-gr-datatable-loading": ""
123
- };
124
- var _hoisted_11 = ["colspan"];
125
- var _hoisted_12 = { class: "inline-flex items-center gap-2" };
126
- var _hoisted_13 = {
127
- key: 1,
128
- "data-gr-datatable-empty": ""
129
- };
130
- var _hoisted_14 = ["colspan"];
131
- var _hoisted_15 = {
132
- key: 0,
133
- "aria-hidden": "true",
134
- "data-gr-datatable-spacer": "before",
135
- style: { pointerEvents: "none" }
136
- };
137
- var _hoisted_16 = ["colspan"];
138
- var _hoisted_17 = [
139
- "data-row-key",
140
- "data-selected",
141
- "aria-rowindex",
142
- "onClick"
143
- ];
144
- var _hoisted_18 = {
145
- key: 1,
146
- "aria-hidden": "true",
147
- "data-gr-datatable-spacer": "after",
148
- style: { pointerEvents: "none" }
149
- };
150
- var _hoisted_19 = ["colspan"];
151
- var _hoisted_20 = ["aria-rowindex"];
152
- var _hoisted_21 = ["data-column-key"];
153
- /**
154
- * `GrDataTable` — data-таблица поверх `GrTable` с сортировкой по клику
155
- * на заголовок и scoped-слотами ячеек (`#cell-<key>`), `#header-<key>`,
156
- * `#caption`, `#footer`, `#empty`, `#loading`.
157
- *
158
- * Итоговая строка объявляется пропом `summaryRow` и оформляется слотами
159
- * `#summary-<key>`: она встаёт по той же колоночной сетке, что и тело.
160
- *
161
- * `#footer` — для свободной разметки под итогом: несколько строк, примечание,
162
- * `colspan`. Рендерится внутрь того же `<tfoot>`, то есть его содержимое тоже
163
- * строки таблицы (`<tr><td>`), а скоуп отдаёт `columns` (в текущем порядке) и
164
- * `totalColumns` (с колонкой выбора).
165
- *
166
- * Сортировка, включая крайние случаи с пустыми и смешанными значениями, живёт
167
- * в `grDataTableSort.ts` — см. `docs/components/GrDataTable.md`.
168
- */
169
- var MIN_COLUMN_WIDTH = 48;
170
- /** Шаг клавиатуры и крупный шаг — как у `GrSplitter`. */
171
- var RESIZE_STEP = 16;
172
- var RESIZE_BIG_STEP = 48;
173
- var SELECT_COLUMN_KEY = "__select__";
174
- //#endregion
175
- //#region src/components/GrDataTable/GrDataTable.vue
176
- var GrDataTable_default = /* @__PURE__ */ defineComponent({
177
- __name: "GrDataTable",
178
- props: {
179
- rows: {},
180
- columns: {},
181
- rowKey: {
182
- type: [String, Function],
183
- default: "id"
184
- },
185
- initialSortKey: { default: void 0 },
186
- initialSortDir: { default: "asc" },
187
- sortKey: { default: void 0 },
188
- sortDir: { default: void 0 },
189
- sortCycle: { default: "asc-desc" },
190
- externalSort: {
191
- type: Boolean,
192
- default: false
193
- },
194
- selectable: {
195
- type: Boolean,
196
- default: false
197
- },
198
- selected: { default: void 0 },
199
- selectableRow: {
200
- type: Function,
201
- default: void 0
202
- },
203
- loading: {
204
- type: Boolean,
205
- default: false
206
- },
207
- loadingText: { default: void 0 },
208
- emptyText: { default: void 0 },
209
- rowClass: {
210
- type: [String, Function],
211
- default: void 0
212
- },
213
- rowProps: {
214
- type: Function,
215
- default: void 0
216
- },
217
- summaryRow: { default: void 0 },
218
- size: { default: void 0 },
219
- caption: { default: void 0 },
220
- ariaLabel: { default: void 0 },
221
- ariaLabelledby: { default: void 0 },
222
- regionLabel: { default: void 0 },
223
- stickyHeader: {
224
- type: Boolean,
225
- default: false
226
- },
227
- virtual: {
228
- type: Boolean,
229
- default: false
230
- },
231
- maxHeight: { default: void 0 },
232
- reorderableColumns: {
233
- type: Boolean,
234
- default: false
235
- },
236
- columnOrder: { default: void 0 },
237
- resizableColumns: {
238
- type: Boolean,
239
- default: false
240
- },
241
- columnWidths: { default: void 0 }
242
- },
243
- emits: [
244
- "update:sortKey",
245
- "update:sortDir",
246
- "sortChange",
247
- "update:selected",
248
- "rowClick",
249
- "update:columnOrder",
250
- "columnReorder",
251
- "update:columnWidths",
252
- "columnResize"
253
- ],
254
- setup(__props, { expose: __expose, emit: __emit }) {
255
- const props = __props;
256
- if (process.env.NODE_ENV !== "production") watchEffect(() => {
257
- if (!Array.isArray(props.rows)) console.warn(`[granularity] GrDataTable: обязательный проп \`rows\` должен быть массивом — получено ${String(props.rows)}.`);
258
- if (!Array.isArray(props.columns)) console.warn(`[granularity] GrDataTable: обязательный проп \`columns\` должен быть массивом — получено ${String(props.columns)}.`);
259
- });
260
- const emit = __emit;
261
- const { t, locale } = useGranularityTranslations();
262
- const { announce } = useAnnouncer();
263
- const resolvedLoadingText = computed(() => props.loadingText ?? t("gr.dataTable.loading", "Loading…"));
264
- const resolvedEmptyText = computed(() => props.emptyText ?? t("gr.dataTable.empty", "No data"));
265
- const internalSortKey = ref(props.initialSortKey ?? "");
266
- const internalSortDir = ref(props.initialSortDir);
267
- const isSortKeyControlled = computed(() => props.sortKey !== void 0);
268
- const isSortDirControlled = computed(() => props.sortDir !== void 0);
269
- const currentSortKey = computed(() => props.sortKey ?? internalSortKey.value);
270
- const currentSortDir = computed(() => props.sortDir ?? internalSortDir.value);
271
- function applySort(key, dir) {
272
- if (!isSortKeyControlled.value) internalSortKey.value = key;
273
- if (!isSortDirControlled.value) internalSortDir.value = dir;
274
- emit("update:sortKey", key);
275
- emit("update:sortDir", dir);
276
- emit("sortChange", {
277
- key,
278
- dir
279
- });
280
- }
281
- const sortedRows = computed(() => {
282
- if (props.externalSort) return [...props.rows];
283
- const key = currentSortKey.value;
284
- if (!key) return [...props.rows];
285
- return sortRows(props.rows, key, currentSortDir.value, locale.value);
286
- });
287
- function toggleSort(col) {
288
- if (!col.sortable) return;
289
- if (currentSortKey.value !== col.key) {
290
- applySort(col.key, "asc");
291
- return;
292
- }
293
- if (currentSortDir.value === "asc") {
294
- applySort(col.key, "desc");
295
- return;
296
- }
297
- if (props.sortCycle === "asc-desc-none") {
298
- applySort("", "asc");
299
- return;
300
- }
301
- applySort(col.key, "asc");
302
- }
303
- function cellAlign(col) {
304
- if (col.align === "right") return "text-right";
305
- if (col.align === "center") return "text-center";
306
- return "text-left";
307
- }
308
- /**
309
- * Синтетические ключи для строк, у которых поле `rowKey` пустое.
310
- *
311
- * Без них строка получила бы `String(undefined ?? '')` — **один и тот же** ключ
312
- * на всю таблицу: Vue переиспользовал бы DOM не по назначению, а выбор одной
313
- * строки помечал бы выбранными все. Ключ привязан к идентичности объекта,
314
- * поэтому переживает сортировку и не зависит от индекса.
315
- */
316
- const syntheticKeys = /* @__PURE__ */ new WeakMap();
317
- let syntheticKeyCounter = 0;
318
- const missingKeyWarned = ref(false);
319
- function syntheticRowKey(row) {
320
- const existing = syntheticKeys.get(row);
321
- if (existing !== void 0) return existing;
322
- syntheticKeyCounter += 1;
323
- const generated = `gr-row-${syntheticKeyCounter}`;
324
- syntheticKeys.set(row, generated);
325
- if (!missingKeyWarned.value && process.env.NODE_ENV !== "production") {
326
- missingKeyWarned.value = true;
327
- console.warn(`[GrDataTable] У строки нет значения по ключу "${String(props.rowKey)}". Задайте \`rowKey\` (поле или функцию) — иначе выбор строк и переиспользование DOM работают по синтетическому ключу, который не переживёт перезагрузку данных.`);
328
- }
329
- return generated;
330
- }
331
- function rowKeyValue(row) {
332
- const rk = props.rowKey;
333
- if (typeof rk === "function") return rk(row);
334
- const value = row[rk];
335
- if (typeof value === "string" && value !== "") return value;
336
- if (typeof value === "number") return value;
337
- return syntheticRowKey(row);
338
- }
339
- function ariaSortFor(col) {
340
- if (!col.sortable) return void 0;
341
- if (currentSortKey.value !== col.key) return "none";
342
- return currentSortDir.value === "asc" ? "ascending" : "descending";
343
- }
344
- /**
345
- * Подсказка «что сделает нажатие» — visually hidden текстом **после** подписи
346
- * колонки, а не `aria-label`'ом на кнопке. `aria-label` подменял бы собой имя
347
- * `<th>`, и AT при переходе по ячейкам вместо «Название» читала бы
348
- * «Отсортировано по Название по возрастанию, нажмите…». Текущее состояние
349
- * сортировки при этом объявляет `aria-sort` на самом `<th>`.
350
- */
351
- function sortHint(col) {
352
- if (currentSortKey.value !== col.key) return t("gr.dataTable.sortAsc", "Activate to sort ascending");
353
- if (currentSortDir.value === "asc") return t("gr.dataTable.sortDesc", "Activate to sort descending");
354
- return props.sortCycle === "asc-desc-none" ? t("gr.dataTable.sortNone", "Activate to remove sorting") : t("gr.dataTable.sortAsc", "Activate to sort ascending");
355
- }
356
- const resolvedSize = useGrComponentSize(() => props.size, { component: "GrDataTable" });
357
- const cellClass = computed(() => cellPaddings[resolvedSize.value]);
358
- const placeholderClass = computed(() => placeholderPaddings[resolvedSize.value]);
359
- const headerTextClass = computed(() => headerTextSizes[resolvedSize.value]);
360
- const headerGapClass = computed(() => headerGaps[resolvedSize.value]);
361
- const selectColumnClass = computed(() => selectColumnWidths[resolvedSize.value]);
362
- const spinnerClass = computed(() => spinnerSizes[resolvedSize.value]);
363
- const sortIconSize = computed(() => sortIconSizes[resolvedSize.value]);
364
- const checkboxSize = computed(() => selectCheckboxSizes[resolvedSize.value]);
365
- /**
366
- * Номер строки и полное их число объявляем только при виртуализации: иначе
367
- * диктор считает строки по разметке, а там всего окно. Заголовок — строка
368
- * первая, поэтому строки набора начинаются со второй.
369
- */
370
- /**
371
- * Итог показывается, только когда набор на экране: «Итого 1 234» над спиннером —
372
- * утверждение о том, чего ещё не показали. Пустой набор при этом итог не гасит:
373
- * «Итого 0» под «нет данных» осмысленно, и считает его потребитель.
374
- *
375
- * Предикат один на всех: шаблон, `aria-rowcount` и номер строки обязаны сходиться,
376
- * иначе диктор объявит строку, которой в разметке нет.
377
- */
378
- const hasSummary = computed(() => props.summaryRow != null && !props.loading);
379
- const ariaRowCount = computed(() => props.virtual ? sortedRows.value.length + 1 + (hasSummary.value ? 1 : 0) : void 0);
380
- function ariaRowIndex(index) {
381
- return props.virtual ? index + 2 : void 0;
382
- }
383
- /** Итог идёт следующим номером за последней строкой набора. */
384
- const summaryAriaRowIndex = computed(() => props.virtual ? sortedRows.value.length + 2 : void 0);
385
- const isEmpty = computed(() => sortedRows.value.length === 0);
386
- /**
387
- * Значение итога по ключу колонки. Как и `cellValue`, отдаёт сырое: отсутствие
388
- * ключа даёт пустую ячейку, а ноль — это значение, а не пустота.
389
- */
390
- function summaryValue(key) {
391
- return props.summaryRow?.[key];
392
- }
393
- const totalColumns = computed(() => props.columns.length + (props.selectable ? 1 : 0));
394
- /** Uncontrolled-состояние; в controlled-режиме перекрывается пропом `columnOrder`. */
395
- const internalColumnOrder = ref(props.columns.map((col) => String(col.key)));
396
- /**
397
- * Колонки в пользовательском порядке.
398
- *
399
- * Состав задаёт `columns`, порядок — только порядок: ключ, которого в наборе
400
- * нет, игнорируется, а колонка, которой нет в порядке (её только что добавили),
401
- * встаёт в конец. Иначе правка `columns` теряла бы колонки молча.
402
- */
403
- const orderedColumns = computed(() => {
404
- const order = props.columnOrder ?? internalColumnOrder.value;
405
- const remaining = new Map(props.columns.map((col) => [String(col.key), col]));
406
- const ordered = [];
407
- for (const key of order) {
408
- const col = remaining.get(key);
409
- if (!col) continue;
410
- remaining.delete(key);
411
- ordered.push(col);
412
- }
413
- for (const col of props.columns) if (remaining.has(String(col.key))) ordered.push(col);
414
- return ordered.sort((a, b) => pinGroupOf(a) - pinGroupOf(b));
415
- });
416
- /** Группа закрепления: 0 — слева, 1 — обычная колонка, 2 — справа. */
417
- function pinGroupOf(col) {
418
- if (col.pinned === "left") return 0;
419
- if (col.pinned === "right") return 2;
420
- return 1;
421
- }
422
- /**
423
- * Сумма заданных ширин — минимальная ширина таблицы.
424
- *
425
- * Без неё фиксированная раскладка вписывает таблицу в контейнер и делит место
426
- * пропорционально: колонка, которую пользователь растянул, ужимается обратно, а
427
- * горизонтальной прокрутки — той самой, ради которой закрепляют колонки, — не
428
- * возникает. Считается, только когда ширина известна у **всех** колонок: с
429
- * одной неизвестной сумма врала бы.
430
- */
431
- const tableMinWidth = computed(() => {
432
- const cols = orderedColumns.value;
433
- if (cols.length === 0) return void 0;
434
- let total = 0;
435
- for (const col of cols) {
436
- const width = widthOf(col);
437
- if (typeof width !== "number") return void 0;
438
- total += width;
439
- }
440
- return total;
441
- });
442
- const columnKeys = computed(() => orderedColumns.value.map((col) => String(col.key)));
443
- const tableProps = computed(() => ({
444
- size: resolvedSize.value,
445
- ariaLabel: props.ariaLabel,
446
- ariaLabelledby: props.ariaLabelledby,
447
- regionLabel: props.regionLabel,
448
- stickyHeader: props.stickyHeader,
449
- maxHeight: props.maxHeight,
450
- rowCount: ariaRowCount.value,
451
- fixedLayout: props.virtual || props.resizableColumns,
452
- tableMinWidth: tableMinWidth.value
453
- }));
454
- function indexOfColumn(key) {
455
- return columnKeys.value.indexOf(key);
456
- }
457
- const headerCellEls = /* @__PURE__ */ new Map();
458
- const columnHandleEls = /* @__PURE__ */ new Map();
459
- function registerEl(store, key, el) {
460
- if (el instanceof HTMLElement) {
461
- store.set(key, el);
462
- return;
463
- }
464
- const previous = store.get(key);
465
- if (previous && !previous.isConnected) store.delete(key);
466
- }
467
- function applyColumnMove(from, to) {
468
- const keys = columnKeys.value;
469
- if (from < 0 || to < 0 || to >= keys.length || from === to) return;
470
- if (!sameGroup(from, to)) return;
471
- const key = keys[from];
472
- const label = orderedColumns.value[from]?.label ?? key;
473
- const next = moveItem(keys, from, to);
474
- internalColumnOrder.value = next;
475
- emit("update:columnOrder", next);
476
- emit("columnReorder", {
477
- key,
478
- from,
479
- to
480
- });
481
- announce(t("gr.dataTable.columnMoved", "Column {label} moved to position {position} of {count}", {
482
- label,
483
- position: to + 1,
484
- count: keys.length
485
- }));
486
- }
487
- const columnSort = useDragSort({
488
- items: () => columnKeys.value,
489
- elementFor: (key) => headerCellEls.get(key) ?? null,
490
- orientation: () => "horizontal",
491
- disabled: () => !props.reorderableColumns || props.loading,
492
- resolveTarget: (hit, source) => {
493
- const to = insertionIndex(hit, indexOfColumn(source), columnKeys.value.length);
494
- return sameGroup(indexOfColumn(source), to) ? to : null;
495
- },
496
- onDrop: (source, to) => applyColumnMove(indexOfColumn(source), to)
497
- });
498
- function sameGroup(from, to) {
499
- const cols = orderedColumns.value;
500
- if (!cols[from] || !cols[to]) return false;
501
- return pinGroupOf(cols[from]) === pinGroupOf(cols[to]);
502
- }
503
- /**
504
- * Кольцо роверного фокуса по ручкам: одна остановка `Tab` на всю шапку.
505
- * Иначе таблица из десяти колонок добавляла бы десять таб-стопов подряд.
506
- */
507
- const columnRoving = useRovingFocus({
508
- items: () => props.reorderableColumns ? columnKeys.value : [],
509
- elementFor: (key) => columnHandleEls.get(key) ?? null,
510
- orientation: () => "horizontal",
511
- wrap: () => false
512
- });
513
- async function onColumnHandleKeydown(event, key) {
514
- if (!props.reorderableColumns) return;
515
- if (event.shiftKey && (event.key === "ArrowLeft" || event.key === "ArrowRight")) {
516
- event.preventDefault();
517
- const from = indexOfColumn(key);
518
- applyColumnMove(from, event.key === "ArrowLeft" ? from - 1 : from + 1);
519
- await nextTick();
520
- await columnRoving.focusKey(key);
521
- return;
522
- }
523
- columnRoving.handleNavigationKeys(event);
524
- }
525
- const draggingColumnKey = computed(() => columnSort.mode.value === null ? null : columnSort.source.value);
526
- /** Полоса места вставки — на колонке-соседе, со стороны движения. */
527
- function columnDropClass(index) {
528
- const target = columnSort.target.value;
529
- if (!columnSort.isActive.value || target === null || target !== index) return "";
530
- const active = columnSort.source.value === null ? -1 : indexOfColumn(columnSort.source.value);
531
- if (index === active) return "";
532
- return target < active ? columnDropBeforeClass : columnDropAfterClass;
533
- }
534
- function columnHandleLabel(col) {
535
- return t("gr.dataTable.moveColumn", "Move column {label}", { label: col.label });
536
- }
537
- /** Уже колонки не бывает: в неё перестаёт помещаться даже стрелка сортировки. */
538
- const internalColumnWidths = ref({});
539
- const columnWidths = computed(() => props.columnWidths ?? internalColumnWidths.value);
540
- /** Фактические ширины шапки: заполняются замером, см. `measureLayout`. */
541
- const measuredColumnWidths = ref({});
542
- /** Ширина колонки: заданная пользователем сильнее объявленной в `columns`. */
543
- function widthOf(col) {
544
- return columnWidths.value[String(col.key)] ?? col.width;
545
- }
546
- const resizingKey = ref(null);
547
- let pendingResizeKey = null;
548
- let resizeStartX = 0;
549
- let resizeStartWidth = 0;
550
- let widthBeforeResize;
551
- function setColumnWidth(key, width) {
552
- const next = {
553
- ...columnWidths.value,
554
- [key]: Math.max(MIN_COLUMN_WIDTH, Math.round(width))
555
- };
556
- internalColumnWidths.value = next;
557
- emit("update:columnWidths", next);
558
- }
559
- function announceWidth(key) {
560
- const col = orderedColumns.value.find((item) => String(item.key) === key);
561
- if (!col) return;
562
- announce(t("gr.dataTable.columnResized", "Column {label} is {width} pixels wide", {
563
- label: col.label,
564
- width: columnWidths.value[key] ?? 0
565
- }));
566
- }
567
- const resizeGesture = useDragGesture({
568
- disabled: () => !props.resizableColumns || props.loading,
569
- onStart: (event) => {
570
- const key = pendingResizeKey;
571
- const el = key === null ? null : headerCellEls.get(key);
572
- if (key === null || !el) return false;
573
- resizeStartX = event.clientX;
574
- resizeStartWidth = el.getBoundingClientRect().width;
575
- widthBeforeResize = columnWidths.value[key];
576
- resizingKey.value = key;
577
- },
578
- onMove: (event) => {
579
- if (resizingKey.value === null) return;
580
- event.preventDefault();
581
- setColumnWidth(resizingKey.value, resizeStartWidth + (event.clientX - resizeStartX));
582
- },
583
- onEnd: () => {
584
- const key = resizingKey.value;
585
- resizingKey.value = null;
586
- if (key === null) return;
587
- emit("columnResize", {
588
- key,
589
- width: columnWidths.value[key] ?? Math.round(resizeStartWidth)
590
- });
591
- announceWidth(key);
592
- },
593
- onCancel: () => {
594
- const key = resizingKey.value;
595
- resizingKey.value = null;
596
- if (key === null) return;
597
- const next = { ...columnWidths.value };
598
- if (widthBeforeResize === void 0) delete next[key];
599
- else next[key] = widthBeforeResize;
600
- internalColumnWidths.value = next;
601
- emit("update:columnWidths", next);
602
- }
603
- });
604
- function onResizerPointerDown(event, key) {
605
- pendingResizeKey = key;
606
- resizeGesture.start(event);
607
- }
608
- /** Сброс к авторазметке: колонка снова считается по содержимому. */
609
- function resetColumnWidth(key) {
610
- if (columnWidths.value[key] === void 0) return;
611
- const next = { ...columnWidths.value };
612
- delete next[key];
613
- internalColumnWidths.value = next;
614
- emit("update:columnWidths", next);
615
- emit("columnResize", {
616
- key,
617
- width: 0
618
- });
619
- }
620
- function resizeByKeyboard(key, delta) {
621
- const measured = headerCellEls.get(key)?.getBoundingClientRect().width ?? MIN_COLUMN_WIDTH;
622
- const current = columnWidths.value[key] ?? measured;
623
- setColumnWidth(key, current + delta);
624
- emit("columnResize", {
625
- key,
626
- width: columnWidths.value[key] ?? current
627
- });
628
- announceWidth(key);
629
- }
630
- function onResizerKeydown(event, key) {
631
- if (!props.resizableColumns || props.loading) return;
632
- const step = event.shiftKey ? RESIZE_BIG_STEP : RESIZE_STEP;
633
- if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
634
- event.preventDefault();
635
- resizeByKeyboard(key, event.key === "ArrowLeft" ? -step : step);
636
- return;
637
- }
638
- if (event.key === "Enter") {
639
- event.preventDefault();
640
- resetColumnWidth(key);
641
- }
642
- }
643
- function resizerLabel(col) {
644
- return t("gr.dataTable.resizeColumn", "Width of column {label}", { label: col.label });
645
- }
646
- /**
647
- * Ручка ресайза — фокусируемый `separator`, а значит обязана называть текущее
648
- * значение с первого рендера, до всякого перетаскивания. Заданной ширины у
649
- * колонки может не быть вовсе (авторазметка), поэтому за значением идём к
650
- * измеренной; до замера остаётся объявленная в `columns`, а её тоже может не
651
- * быть — тогда минимум.
652
- */
653
- function resizerValue(col) {
654
- const key = String(col.key);
655
- const declared = typeof col.width === "number" ? col.width : void 0;
656
- return columnWidths.value[key] ?? measuredColumnWidths.value[key] ?? declared ?? MIN_COLUMN_WIDTH;
657
- }
658
- /** Служебная колонка выбора в реестре ячеек: своего ключа у неё нет. */
659
- const hasPinnedLeft = computed(() => orderedColumns.value.some((col) => col.pinned === "left"));
660
- const pinnedOffsets = ref({});
661
- /**
662
- * Смещения липких колонок считаются по **измеренным** ширинам соседей: колонка
663
- * может жить на авторазметке, и тогда взять смещение больше неоткуда. Отсюда же
664
- * порядок: замер после отрисовки, а не в вычислении.
665
- */
666
- function measurePinnedOffsets() {
667
- const next = {};
668
- const cols = orderedColumns.value;
669
- let left = props.selectable && hasPinnedLeft.value ? headerCellEls.get(SELECT_COLUMN_KEY)?.getBoundingClientRect().width ?? 0 : 0;
670
- for (const col of cols) {
671
- if (col.pinned !== "left") continue;
672
- next[String(col.key)] = left;
673
- left += headerCellEls.get(String(col.key))?.getBoundingClientRect().width ?? 0;
674
- }
675
- let right = 0;
676
- for (let index = cols.length - 1; index >= 0; index -= 1) {
677
- const col = cols[index];
678
- if (col.pinned !== "right") continue;
679
- next[String(col.key)] = right;
680
- right += headerCellEls.get(String(col.key))?.getBoundingClientRect().width ?? 0;
681
- }
682
- pinnedOffsets.value = next;
683
- }
684
- function measureColumnWidths() {
685
- if (!props.resizableColumns) return;
686
- const next = {};
687
- for (const col of orderedColumns.value) {
688
- const key = String(col.key);
689
- const width = headerCellEls.get(key)?.getBoundingClientRect().width;
690
- if (width) next[key] = Math.round(width);
691
- }
692
- measuredColumnWidths.value = next;
693
- }
694
- function measureLayout() {
695
- measurePinnedOffsets();
696
- measureColumnWidths();
697
- }
698
- function pinnedStyleOf(col) {
699
- if (!col.pinned) return void 0;
700
- const offset = pinnedOffsets.value[String(col.key)] ?? 0;
701
- return col.pinned === "left" ? { left: `${offset}px` } : { right: `${offset}px` };
702
- }
703
- /** Тень рисует только крайняя колонка группы — иначе полос было бы столько же, сколько колонок. */
704
- function pinnedEdgeClass(col, index) {
705
- if (col.pinned === "left") return orderedColumns.value[index + 1]?.pinned === "left" ? "" : columnPinnedLeftEdgeClass;
706
- if (col.pinned === "right") return orderedColumns.value[index - 1]?.pinned === "right" ? "" : columnPinnedRightEdgeClass;
707
- return "";
708
- }
709
- function pinnedCellClass(col, index) {
710
- if (!col.pinned) return [];
711
- return [columnPinnedClass, pinnedEdgeClass(col, index)];
712
- }
713
- /**
714
- * Ширину колонки меняет не только правка пропов: окно, шрифт и содержимое
715
- * ячейки двигают её сами. `ResizeObserver` — единственный способ узнать об этом;
716
- * в jsdom и на сервере его нет, там хватает пересчёта по изменению данных.
717
- */
718
- let layoutObserver = null;
719
- onMounted(() => {
720
- measureLayout();
721
- if (typeof ResizeObserver === "undefined") return;
722
- layoutObserver = new ResizeObserver(() => measureLayout());
723
- const el = headerCellEls.get(SELECT_COLUMN_KEY) ?? headerCellEls.get(columnKeys.value[0]);
724
- if (el?.parentElement) layoutObserver.observe(el.parentElement);
725
- });
726
- onUnmounted(() => {
727
- layoutObserver?.disconnect();
728
- layoutObserver = null;
729
- });
730
- watch(() => [
731
- orderedColumns.value,
732
- columnWidths.value,
733
- props.loading,
734
- props.rows.length
735
- ], () => void nextTick(measureLayout));
736
- /**
737
- * Живой регион существует с первого рендера и пуст, пока объявлять нечего.
738
- * Регион, который появляется уже с текстом, часть AT не объявляет вовсе —
739
- * а именно так вело себя `role="status"` внутри строки загрузки.
740
- */
741
- const liveMessage = computed(() => {
742
- if (props.loading) return resolvedLoadingText.value;
743
- if (isEmpty.value) return resolvedEmptyText.value;
744
- return "";
745
- });
746
- function cellValue(row, key) {
747
- return row[key];
748
- }
749
- function rowClassName(row, index) {
750
- const source = props.rowClass;
751
- return typeof source === "function" ? source(row, index) : source;
752
- }
753
- function onRowClick(row, index, event) {
754
- emit("rowClick", {
755
- row,
756
- index,
757
- event
758
- });
759
- }
760
- const internalSelected = ref([]);
761
- const isSelectedControlled = computed(() => props.selected !== void 0);
762
- const selectedKeys = computed(() => new Set(props.selected ?? internalSelected.value));
763
- function isRowSelectable(row) {
764
- return props.selectableRow ? props.selectableRow(row) : true;
765
- }
766
- function isRowSelected(row) {
767
- return selectedKeys.value.has(rowKeyValue(row));
768
- }
769
- /** «Выбрать все» работает по видимым и выбираемым строкам — они же считают состояние шапки. */
770
- const selectableRows = computed(() => sortedRows.value.filter(isRowSelectable));
771
- const allSelected = computed(() => selectableRows.value.length > 0 && selectableRows.value.every(isRowSelected));
772
- const someSelected = computed(() => selectableRows.value.some(isRowSelected) && !allSelected.value);
773
- function emitSelected(next) {
774
- if (!isSelectedControlled.value) internalSelected.value = [...next];
775
- emit("update:selected", [...next]);
776
- }
777
- function toggleRow(row) {
778
- if (!isRowSelectable(row)) return;
779
- const key = rowKeyValue(row);
780
- const next = new Set(selectedKeys.value);
781
- if (next.has(key)) next.delete(key);
782
- else next.add(key);
783
- emitSelected(next);
784
- }
785
- function toggleAll() {
786
- const next = new Set(selectedKeys.value);
787
- if (allSelected.value) {
788
- for (const row of selectableRows.value) next.delete(rowKeyValue(row));
789
- emitSelected(next);
790
- return;
791
- }
792
- for (const row of selectableRows.value) next.add(rowKeyValue(row));
793
- emitSelected(next);
794
- }
795
- const tableRef = ref(null);
796
- const rootId = useId();
797
- function rootEl() {
798
- return tableRef.value?.$el ?? null;
799
- }
800
- /** Скролл-контейнер таблицы — он же элемент с `maxHeight`. */
801
- function scrollEl() {
802
- const root = rootEl();
803
- return root?.matches("[data-gr-table-scroll]") ? root : root?.querySelector("[data-gr-table-scroll]") ?? null;
804
- }
805
- /**
806
- * Виртуализация строк.
807
- *
808
- * Распорки здесь не псевдоэлементы, как у списочных компонентов: `<tbody>`
809
- * игнорирует `padding`, а псевдоэлемент внутри группы строк не образует строку
810
- * с управляемой высотой. Поэтому срезанное держат две служебные `<tr>` — той же
811
- * формы, что строки загрузки и пустоты у `GrTable`.
812
- */
813
- const scrollContainer = ref(null);
814
- onMounted(() => {
815
- scrollContainer.value = scrollEl();
816
- });
817
- const virtualizer = useVirtualList({
818
- container: scrollContainer,
819
- count: () => props.virtual ? sortedRows.value.length : 0,
820
- source: () => sortedRows.value,
821
- itemSize: () => rowHeightEstimates[resolvedSize.value],
822
- viewportSize: () => typeof props.maxHeight === "number" ? props.maxHeight : void 0
823
- });
824
- /** Строки к отрисовке вместе с их абсолютной позицией в наборе. */
825
- const renderedRows = computed(() => {
826
- const rows = sortedRows.value;
827
- if (!props.virtual) return rows.map((row, index) => ({
828
- row,
829
- index
830
- }));
831
- const { start, end } = virtualizer.range.value;
832
- return rows.slice(start, end).map((row, offset) => ({
833
- row,
834
- index: start + offset
835
- }));
836
- });
837
- const spacerBefore = computed(() => props.virtual ? virtualizer.offset.value : 0);
838
- const spacerAfter = computed(() => props.virtual ? virtualizer.offsetEnd.value : 0);
839
- /** Ширина колонки: число трактуем как пиксели. */
840
- function columnWidthStyle(width) {
841
- if (width === void 0) return void 0;
842
- return { width: typeof width === "number" ? `${width}px` : width };
843
- }
844
- function scrollToRow(key, options) {
845
- const target = String(key);
846
- if (props.virtual) {
847
- const index = sortedRows.value.findIndex((row) => String(rowKeyValue(row)) === target);
848
- if (index < 0) return false;
849
- virtualizer.scrollToIndex(index);
850
- nextTick(() => {
851
- findRowEl(target)?.scrollIntoView?.(options ?? { block: "nearest" });
852
- });
853
- return true;
854
- }
855
- const row = findRowEl(target);
856
- if (!row) return false;
857
- row.scrollIntoView(options ?? { block: "nearest" });
858
- return true;
859
- }
860
- function findRowEl(rowKey) {
861
- const rows = rootEl()?.querySelectorAll("[data-gr-datatable-row]") ?? [];
862
- return Array.prototype.find.call(rows, (el) => el.dataset.rowKey === rowKey);
863
- }
864
- function scrollTo(options) {
865
- scrollEl()?.scrollTo(options);
866
- }
867
- function clearSort() {
868
- applySort("", "asc");
869
- }
870
- __expose({
871
- /** Прокрутить к строке по её ключу. `false` — строки нет в DOM. */
872
- scrollToRow,
873
- /** Прокрутить скролл-контейнер таблицы. */
874
- scrollTo,
875
- /** Снять сортировку (эквивалент третьего состояния). */
876
- clearSort,
877
- /** Отметить/снять все выбираемые строки. */
878
- toggleAll
879
- });
880
- /**
881
- * Три семейства слотов по ключу колонки — `#cell-<key>` (`{ row, index }`),
882
- * `#header-<key>` (`{ column }`) и `#summary-<key>` (`{ value, column }`) —
883
- * попадают под общую индексную сигнатуру, а не под шаблонные.
884
- *
885
- * Шаблонные (`` `cell-${string}` ``) сужали бы тип слотов строже
886
- * `InternalSlots`, и дженерик-компонент переставал приниматься `h()`: ломался
887
- * бы рендер-функцией, оставаясь рабочим в шаблоне. Цена — пропсы этих трёх
888
- * семейств не типизированы; они описаны здесь и на странице компонента.
889
- */
890
- return (_ctx, _cache) => {
891
- return openBlock(), createBlock(GrTable_default, mergeProps(tableProps.value, {
892
- ref_key: "tableRef",
893
- ref: tableRef,
894
- "data-gr-datatable": ""
895
- }), createSlots({
896
- caption: withCtx(() => [renderSlot(_ctx.$slots, "caption", {}, () => [createTextVNode(toDisplayString(__props.caption), 1)]), createElementVNode("span", {
897
- id: `${unref(rootId)}-live`,
898
- "data-gr-datatable-live": "",
899
- role: "status",
900
- "aria-live": "polite"
901
- }, toDisplayString(liveMessage.value), 9, _hoisted_1)]),
902
- header: withCtx(() => [createElementVNode("tr", {
903
- "data-gr-datatable-header": "",
904
- "aria-rowindex": __props.virtual ? 1 : void 0
905
- }, [__props.selectable ? (openBlock(), createElementBlock("th", {
906
- key: 0,
907
- ref: (el) => registerEl(unref(headerCellEls), SELECT_COLUMN_KEY, el),
908
- class: normalizeClass(["text-left", [
909
- selectColumnClass.value,
910
- cellClass.value,
911
- hasPinnedLeft.value ? unref(columnPinnedClass) : ""
912
- ]]),
913
- style: normalizeStyle(hasPinnedLeft.value ? { left: "0px" } : void 0),
914
- scope: "col"
915
- }, [createVNode(GrCheckbox_default, {
916
- "data-gr-datatable-select-all": "",
917
- "model-value": allSelected.value,
918
- indeterminate: someSelected.value,
919
- size: checkboxSize.value,
920
- disabled: __props.loading || selectableRows.value.length === 0,
921
- "aria-label": unref(t)("gr.dataTable.selectAll", "Select all rows"),
922
- "onUpdate:modelValue": toggleAll
923
- }, null, 8, [
924
- "model-value",
925
- "indeterminate",
926
- "size",
927
- "disabled",
928
- "aria-label"
929
- ])], 6)) : createCommentVNode("", true), (openBlock(true), createElementBlock(Fragment, null, renderList(orderedColumns.value, (col, colIndex) => {
930
- return openBlock(), createElementBlock("th", {
931
- key: col.key,
932
- ref_for: true,
933
- ref: (el) => registerEl(unref(headerCellEls), String(col.key), el),
934
- class: normalizeClass(["font-700", [
935
- headerTextClass.value,
936
- cellClass.value,
937
- cellAlign(col),
938
- __props.reorderableColumns || __props.resizableColumns ? "group relative" : "",
939
- draggingColumnKey.value === String(col.key) ? unref(columnDraggingClass) : "",
940
- columnDropClass(colIndex),
941
- ...pinnedCellClass(col, colIndex)
942
- ]]),
943
- "data-column-key": col.key,
944
- style: normalizeStyle({
945
- ...columnWidthStyle(widthOf(col)),
946
- ...pinnedStyleOf(col)
947
- }),
948
- "aria-sort": ariaSortFor(col),
949
- scope: "col"
950
- }, [createElementVNode("div", { class: normalizeClass(["inline-flex items-center", headerGapClass.value]) }, [__props.reorderableColumns ? (openBlock(), createElementBlock("button", {
951
- key: 0,
952
- ref_for: true,
953
- ref: (el) => registerEl(unref(columnHandleEls), String(col.key), el),
954
- type: "button",
955
- "data-gr-datatable-column-handle": "",
956
- class: normalizeClass([unref(columnHandleClass), unref(columnSort).isActive.value ? unref(columnHandleActiveClass) : ""]),
957
- tabindex: unref(columnRoving).tabindexFor(String(col.key)),
958
- "aria-label": columnHandleLabel(col),
959
- disabled: __props.loading,
960
- onPointerdown: ($event) => unref(columnSort).startFrom(String(col.key))($event),
961
- onKeydown: ($event) => onColumnHandleKeydown($event, String(col.key)),
962
- onFocus: ($event) => unref(columnRoving).setActive(String(col.key)),
963
- onClick: _cache[0] || (_cache[0] = withModifiers(() => {}, ["stop"]))
964
- }, [createVNode(unref(grip_vertical_default), {
965
- class: "block",
966
- "aria-hidden": "true"
967
- })], 42, _hoisted_4)) : createCommentVNode("", true), col.sortable ? (openBlock(), createElementBlock("button", {
968
- key: 1,
969
- type: "button",
970
- "data-gr-datatable-sort": "",
971
- class: normalizeClass(["inline-flex items-center text-[var(--gr-muted-fg)] hover:text-[var(--gr-fg)] transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)] rounded-[var(--gr-radius-sm)]", headerGapClass.value]),
972
- onClick: ($event) => toggleSort(col)
973
- }, [
974
- createElementVNode("span", null, [renderSlot(_ctx.$slots, `header-${col.key}`, { column: col }, () => [createTextVNode(toDisplayString(col.label), 1)])]),
975
- createElementVNode("span", _hoisted_6, toDisplayString(sortHint(col)), 1),
976
- createElementVNode("span", _hoisted_7, [currentSortKey.value === col.key && currentSortDir.value === "asc" ? (openBlock(), createBlock(GrIcon_default, {
977
- key: 0,
978
- size: sortIconSize.value
979
- }, {
980
- default: withCtx(() => [createVNode(unref(arrow_up_default))]),
981
- _: 1
982
- }, 8, ["size"])) : currentSortKey.value === col.key && currentSortDir.value === "desc" ? (openBlock(), createBlock(GrIcon_default, {
983
- key: 1,
984
- size: sortIconSize.value
985
- }, {
986
- default: withCtx(() => [createVNode(unref(arrow_down_default))]),
987
- _: 1
988
- }, 8, ["size"])) : createCommentVNode("", true)])
989
- ], 10, _hoisted_5)) : (openBlock(), createElementBlock("span", _hoisted_8, [renderSlot(_ctx.$slots, `header-${col.key}`, { column: col }, () => [createTextVNode(toDisplayString(col.label), 1)])]))], 2), __props.resizableColumns ? (openBlock(), createElementBlock("span", {
990
- key: 0,
991
- "data-gr-datatable-column-resizer": "",
992
- role: "separator",
993
- tabindex: "0",
994
- "aria-orientation": "vertical",
995
- class: normalizeClass(unref(columnResizerClass)),
996
- "aria-label": resizerLabel(col),
997
- "aria-valuenow": resizerValue(col),
998
- "aria-valuemin": MIN_COLUMN_WIDTH,
999
- "aria-valuetext": `${resizerValue(col)}px`,
1000
- onPointerdown: ($event) => onResizerPointerDown($event, String(col.key)),
1001
- onKeydown: ($event) => onResizerKeydown($event, String(col.key)),
1002
- onDblclick: ($event) => resetColumnWidth(String(col.key)),
1003
- onClick: _cache[1] || (_cache[1] = withModifiers(() => {}, ["stop"]))
1004
- }, [createElementVNode("span", {
1005
- class: normalizeClass([
1006
- unref(columnResizerLineClass),
1007
- unref(columnResizerHoverClass),
1008
- resizingKey.value === String(col.key) ? unref(columnResizerLineActiveClass) : ""
1009
- ]),
1010
- "aria-hidden": "true"
1011
- }, null, 2)], 42, _hoisted_9)) : createCommentVNode("", true)], 14, _hoisted_3);
1012
- }), 128))], 8, _hoisted_2)]),
1013
- default: withCtx(() => [__props.loading ? (openBlock(), createElementBlock("tr", _hoisted_10, [createElementVNode("td", {
1014
- colspan: totalColumns.value,
1015
- class: normalizeClass(["text-center text-[var(--gr-muted-fg)]", placeholderClass.value])
1016
- }, [renderSlot(_ctx.$slots, "loading", {}, () => [createElementVNode("span", _hoisted_12, [createVNode(unref(loader_circle_default), {
1017
- class: normalizeClass(["block animate-spin", spinnerClass.value]),
1018
- "aria-hidden": "true"
1019
- }, null, 8, ["class"]), createElementVNode("span", null, toDisplayString(resolvedLoadingText.value), 1)])])], 10, _hoisted_11)])) : isEmpty.value ? (openBlock(), createElementBlock("tr", _hoisted_13, [createElementVNode("td", {
1020
- colspan: totalColumns.value,
1021
- class: normalizeClass(["text-center text-[var(--gr-muted-fg)]", placeholderClass.value])
1022
- }, [renderSlot(_ctx.$slots, "empty", {}, () => [createTextVNode(toDisplayString(resolvedEmptyText.value), 1)])], 10, _hoisted_14)])) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [
1023
- __props.virtual && spacerBefore.value > 0 ? (openBlock(), createElementBlock("tr", _hoisted_15, [createElementVNode("td", {
1024
- colspan: totalColumns.value,
1025
- style: normalizeStyle({
1026
- height: `${spacerBefore.value}px`,
1027
- padding: "0",
1028
- border: "0"
1029
- })
1030
- }, null, 12, _hoisted_16)])) : createCommentVNode("", true),
1031
- (openBlock(true), createElementBlock(Fragment, null, renderList(renderedRows.value, ({ row, index }) => {
1032
- return openBlock(), createElementBlock("tr", mergeProps({
1033
- key: rowKeyValue(row),
1034
- ref_for: true,
1035
- ref: (el) => __props.virtual && unref(virtualizer).measure(index, el),
1036
- class: ["border-t border-[var(--gr-brd)]", [isRowSelected(row) ? unref(rowSelectedClass) : "", rowClassName(row, index)]],
1037
- "data-gr-datatable-row": "",
1038
- "data-row-key": rowKeyValue(row),
1039
- "data-selected": __props.selectable && isRowSelected(row) ? "true" : void 0,
1040
- "aria-rowindex": ariaRowIndex(index)
1041
- }, { ref_for: true }, __props.rowProps?.(row, index), { onClick: ($event) => onRowClick(row, index, $event) }), [__props.selectable ? (openBlock(), createElementBlock("td", {
1042
- key: 0,
1043
- class: normalizeClass(["text-left", [
1044
- selectColumnClass.value,
1045
- cellClass.value,
1046
- hasPinnedLeft.value ? unref(columnPinnedClass) : "",
1047
- hasPinnedLeft.value && isRowSelected(row) ? unref(rowSelectedClass) : ""
1048
- ]]),
1049
- style: normalizeStyle(hasPinnedLeft.value ? { left: "0px" } : void 0)
1050
- }, [isRowSelectable(row) ? (openBlock(), createBlock(GrCheckbox_default, {
1051
- key: 0,
1052
- "data-gr-datatable-select-row": "",
1053
- "model-value": isRowSelected(row),
1054
- size: checkboxSize.value,
1055
- "aria-label": unref(t)("gr.dataTable.selectRow", "Select row"),
1056
- onClick: _cache[2] || (_cache[2] = withModifiers(() => {}, ["stop"])),
1057
- "onUpdate:modelValue": ($event) => toggleRow(row)
1058
- }, null, 8, [
1059
- "model-value",
1060
- "size",
1061
- "aria-label",
1062
- "onUpdate:modelValue"
1063
- ])) : createCommentVNode("", true)], 6)) : createCommentVNode("", true), (openBlock(true), createElementBlock(Fragment, null, renderList(orderedColumns.value, (col, colIndex) => {
1064
- return openBlock(), createElementBlock("td", {
1065
- key: col.key,
1066
- class: normalizeClass([
1067
- cellClass.value,
1068
- cellAlign(col),
1069
- ...pinnedCellClass(col, colIndex),
1070
- col.pinned && isRowSelected(row) ? unref(rowSelectedClass) : ""
1071
- ]),
1072
- style: normalizeStyle({
1073
- ...columnWidthStyle(widthOf(col)),
1074
- ...pinnedStyleOf(col)
1075
- })
1076
- }, [renderSlot(_ctx.$slots, `cell-${col.key}`, {
1077
- row,
1078
- index
1079
- }, () => [createElementVNode("span", null, toDisplayString(cellValue(row, col.key)), 1)])], 6);
1080
- }), 128))], 16, _hoisted_17);
1081
- }), 128)),
1082
- __props.virtual && spacerAfter.value > 0 ? (openBlock(), createElementBlock("tr", _hoisted_18, [createElementVNode("td", {
1083
- colspan: totalColumns.value,
1084
- style: normalizeStyle({
1085
- height: `${spacerAfter.value}px`,
1086
- padding: "0",
1087
- border: "0"
1088
- })
1089
- }, null, 12, _hoisted_19)])) : createCommentVNode("", true)
1090
- ], 64))]),
1091
- _: 2
1092
- }, [_ctx.$slots.footer || hasSummary.value ? {
1093
- name: "footer",
1094
- fn: withCtx(() => [hasSummary.value ? (openBlock(), createElementBlock("tr", {
1095
- key: 0,
1096
- "data-gr-datatable-summary": "",
1097
- class: normalizeClass(unref(summaryRowClass)),
1098
- "aria-rowindex": summaryAriaRowIndex.value
1099
- }, [__props.selectable ? (openBlock(), createElementBlock("td", {
1100
- key: 0,
1101
- class: normalizeClass([
1102
- selectColumnClass.value,
1103
- cellClass.value,
1104
- hasPinnedLeft.value ? unref(columnPinnedClass) : ""
1105
- ]),
1106
- style: normalizeStyle(hasPinnedLeft.value ? { left: "0px" } : void 0)
1107
- }, null, 6)) : createCommentVNode("", true), (openBlock(true), createElementBlock(Fragment, null, renderList(orderedColumns.value, (col, colIndex) => {
1108
- return openBlock(), createElementBlock("td", {
1109
- key: col.key,
1110
- "data-column-key": col.key,
1111
- class: normalizeClass([
1112
- cellClass.value,
1113
- cellAlign(col),
1114
- ...pinnedCellClass(col, colIndex)
1115
- ]),
1116
- style: normalizeStyle({
1117
- ...columnWidthStyle(widthOf(col)),
1118
- ...pinnedStyleOf(col)
1119
- })
1120
- }, [renderSlot(_ctx.$slots, `summary-${col.key}`, {
1121
- value: summaryValue(col.key),
1122
- column: col
1123
- }, () => [createElementVNode("span", null, toDisplayString(summaryValue(col.key)), 1)])], 14, _hoisted_21);
1124
- }), 128))], 10, _hoisted_20)) : createCommentVNode("", true), renderSlot(_ctx.$slots, "footer", {
1125
- columns: orderedColumns.value,
1126
- totalColumns: totalColumns.value
1127
- })]),
1128
- key: "0"
1129
- } : void 0]), 1040);
1130
- };
1131
- }
1132
- });
1133
- //#endregion
1134
- export { GrDataTable_default as t };