@feugene/granularity 0.45.0 → 0.47.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.
- package/README.md +3 -3
- package/dist/chunks/{x-circle-BCBURwyX.js → check-circle-Bnrb-CGJ.js} +8 -32
- package/dist/chunks/check-circle-Bnrb-CGJ.js.map +1 -0
- package/dist/chunks/{config-BqT6PMzO.js → config-B42iJ48O.js} +5 -1
- package/dist/chunks/{config-BqT6PMzO.js.map → config-B42iJ48O.js.map} +1 -1
- package/dist/chunks/{config-BGCyyXiD.js → config-BAse0laT.js} +5 -1
- package/dist/chunks/{config-BGCyyXiD.js.map → config-BAse0laT.js.map} +1 -1
- package/dist/chunks/{config-MmPym-Z7.js → config-BLP94dPY.js} +5 -1
- package/dist/chunks/{config-MmPym-Z7.js.map → config-BLP94dPY.js.map} +1 -1
- package/dist/chunks/{config-CjKpUcmy.js → config-Bdf1bt-m.js} +25 -16
- package/dist/chunks/config-Bdf1bt-m.js.map +1 -0
- package/dist/chunks/{config-CIkdsWUk.js → config-CIUU9ZpY.js} +4 -1
- package/dist/chunks/config-CIUU9ZpY.js.map +1 -0
- package/dist/chunks/{config-BhfNgyWL.js → config-DENqOoh0.js} +5 -1
- package/dist/chunks/config-DENqOoh0.js.map +1 -0
- package/dist/chunks/{config-ChnYtO4o.js → config-bjDf8nBf.js} +16 -1
- package/dist/chunks/config-bjDf8nBf.js.map +1 -0
- package/dist/chunks/controlState-DoImKciE.js +35 -0
- package/dist/chunks/controlState-DoImKciE.js.map +1 -0
- package/dist/chunks/{en-DiHzFact.js → en-DjTnfTqM.js} +3 -1
- package/dist/chunks/en-DjTnfTqM.js.map +1 -0
- package/dist/chunks/{es-1ZL6NtkB.js → es-Bc_t3FI8.js} +3 -1
- package/dist/chunks/es-Bc_t3FI8.js.map +1 -0
- package/dist/chunks/{es-CeKkMxZ_.js → es-DIFzSXSZ.js} +3 -3
- package/dist/chunks/{es-CeKkMxZ_.js.map → es-DIFzSXSZ.js.map} +1 -1
- package/dist/chunks/{generated-DAKeOOqm.js → generated-CQ4h6Ieq.js} +28 -0
- package/dist/chunks/{generated-DAKeOOqm.js.map → generated-CQ4h6Ieq.js.map} +1 -1
- package/dist/chunks/{granular-provider-D_8_l-gM.js → granular-provider-AKukmYva.js} +7 -7
- package/dist/chunks/{granular-provider-D_8_l-gM.js.map → granular-provider-AKukmYva.js.map} +1 -1
- package/dist/chunks/{ru-Dr2RYn0P.js → ru-Bb-co59W.js} +3 -1
- package/dist/chunks/ru-Bb-co59W.js.map +1 -0
- package/dist/chunks/x-circle-BigSZy8m.js +26 -0
- package/dist/chunks/x-circle-BigSZy8m.js.map +1 -0
- package/dist/components/GrAlert/chunks/{GrAlert-D72y1IEh.js → GrAlert-CfEZghSY.js} +2 -1
- package/dist/components/GrAlert/chunks/{GrAlert-D72y1IEh.js.map → GrAlert-CfEZghSY.js.map} +1 -1
- package/dist/components/GrAlert/index.js +1 -1
- package/dist/components/GrAutocomplete/chunks/{GrAutocomplete-D396_JrW.js → GrAutocomplete-C-uovdv8.js} +320 -224
- package/dist/components/GrAutocomplete/chunks/GrAutocomplete-C-uovdv8.js.map +1 -0
- package/dist/components/GrAutocomplete/index.js +1 -1
- package/dist/components/GrAutocomplete/styles.css +1 -1
- package/dist/components/GrColorPicker/chunks/{GrColorPicker-9mTR-YZ_.js → GrColorPicker-BRM_4lyI.js} +1 -1
- package/dist/components/GrColorPicker/chunks/{GrColorPicker-9mTR-YZ_.js.map → GrColorPicker-BRM_4lyI.js.map} +1 -1
- package/dist/components/GrColorPicker/index.js +1 -1
- package/dist/components/GrCommandPalette/chunks/{GrCommandPalette-Ha4s3DMT.js.map → GrCommandPalette-BJHmkDQv.js.map} +1 -1
- package/dist/components/GrCommandPalette/index.js +1 -1
- package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-e15nFI8M.js → GrConfirmDialog-BzUQwbec.js} +1 -1
- package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-e15nFI8M.js.map → GrConfirmDialog-BzUQwbec.js.map} +1 -1
- package/dist/components/GrConfirmDialog/index.js +1 -1
- package/dist/components/GrDataTable/chunks/{GrDataTable-xNSmO_I-.js → GrDataTable-Dp9Fp_P5.js} +606 -499
- package/dist/components/GrDataTable/chunks/GrDataTable-Dp9Fp_P5.js.map +1 -0
- package/dist/components/GrDataTable/index.js +1 -1
- package/dist/components/GrDialogService/chunks/{GrDialogService-BmXiYmUp.js → GrDialogService-Dtq0ZCN5.js} +3 -3
- package/dist/components/GrDialogService/chunks/{GrDialogService-BmXiYmUp.js.map → GrDialogService-Dtq0ZCN5.js.map} +1 -1
- package/dist/components/GrDialogService/index.js +1 -1
- package/dist/components/GrFormField/chunks/GrFormField-DO61vxpS.js.map +1 -1
- package/dist/components/GrInput/chunks/{GrInput-TFeBm3gQ.js → GrInput-DV4gMWPR.js} +40 -11
- package/dist/components/GrInput/chunks/GrInput-DV4gMWPR.js.map +1 -0
- package/dist/components/GrInput/index.js +2 -2
- package/dist/components/GrInputTag/chunks/{GrInputTag-Ci8AO1ze.js → GrInputTag-CO0YU-ok.js} +30 -6
- package/dist/components/GrInputTag/chunks/GrInputTag-CO0YU-ok.js.map +1 -0
- package/dist/components/GrInputTag/index.js +2 -2
- package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-CdlWMGnF.js → GrJsonViewer-D7CyWzhT.js} +2 -2
- package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-CdlWMGnF.js.map → GrJsonViewer-D7CyWzhT.js.map} +1 -1
- package/dist/components/GrJsonViewer/index.js +1 -1
- package/dist/components/GrNumberInput/chunks/{GrNumberInput-CL5QhOmx.js → GrNumberInput-BI6-vkcs.js} +48 -16
- package/dist/components/GrNumberInput/chunks/GrNumberInput-BI6-vkcs.js.map +1 -0
- package/dist/components/GrNumberInput/index.js +2 -2
- package/dist/components/GrPagination/chunks/{GrPagination-DhbNZHEF.js → GrPagination-BbaF5eXk.js} +10 -7
- package/dist/components/GrPagination/chunks/GrPagination-BbaF5eXk.js.map +1 -0
- package/dist/components/GrPagination/index.js +2 -2
- package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-8RxQsOQ_.js → GrPromptDialog-BEL-QgwE.js} +3 -3
- package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-8RxQsOQ_.js.map → GrPromptDialog-BEL-QgwE.js.map} +1 -1
- package/dist/components/GrPromptDialog/index.js +1 -1
- package/dist/components/GrResponseErrorBanner/chunks/{GrResponseErrorBanner-NEEJZ8zi.js → GrResponseErrorBanner-Bdd5c07x.js} +1 -1
- package/dist/components/GrResponseErrorBanner/chunks/{GrResponseErrorBanner-NEEJZ8zi.js.map → GrResponseErrorBanner-Bdd5c07x.js.map} +1 -1
- package/dist/components/GrResponseErrorBanner/chunks/{GrResponseErrorBanner-ZvBvEQDZ.js → GrResponseErrorBanner-DySe1ym4.js} +1 -1
- package/dist/components/GrResponseErrorBanner/chunks/{GrResponseErrorBanner-ZvBvEQDZ.js.map → GrResponseErrorBanner-DySe1ym4.js.map} +1 -1
- package/dist/components/GrResponseErrorBanner/index.js +2 -2
- package/dist/components/GrSelect/chunks/{GrSelect-Do1iAyp1.js → GrSelect-0iI4wdR_.js} +3 -3
- package/dist/components/GrSelect/chunks/{GrSelect-Do1iAyp1.js.map → GrSelect-0iI4wdR_.js.map} +1 -1
- package/dist/components/GrSelect/index.js +1 -1
- package/dist/components/GrTextarea/chunks/{GrTextarea-BII3w61K.js → GrTextarea-Bfgc0DD2.js} +73 -32
- package/dist/components/GrTextarea/chunks/GrTextarea-Bfgc0DD2.js.map +1 -0
- package/dist/components/GrTextarea/index.js +2 -2
- package/dist/components/GrToaster/chunks/{GrToaster-D-sV-9of.js → GrToaster-CZixNk0l.js} +2 -1
- package/dist/components/GrToaster/chunks/{GrToaster-D-sV-9of.js.map → GrToaster-CZixNk0l.js.map} +1 -1
- package/dist/components/GrToaster/index.js +1 -1
- package/dist/components/GrTransfer/chunks/{GrTransfer-8FA78ny6.js → GrTransfer-DdfBUf35.js} +2 -2
- package/dist/components/GrTransfer/chunks/{GrTransfer-8FA78ny6.js.map → GrTransfer-DdfBUf35.js.map} +1 -1
- package/dist/components/GrTransfer/index.js +1 -1
- package/dist/components/GrTree/chunks/{GrTree-DnDGW0xO.js → GrTree-6yZrncJE.js} +52 -18
- package/dist/components/GrTree/chunks/GrTree-6yZrncJE.js.map +1 -0
- package/dist/components/GrTree/index.js +1 -1
- package/dist/components/GrTree/styles.css +1 -1
- package/dist/components/GrTreeSections/chunks/{GrTreeSections-D2PQ87mw.js → GrTreeSections-DcmDcLwj.js} +5 -3
- package/dist/components/GrTreeSections/chunks/GrTreeSections-DcmDcLwj.js.map +1 -0
- package/dist/components/GrTreeSections/index.js +2 -2
- package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-DzheDCuZ.js → GrTreeSelect-B6KoDYiZ.js} +44 -19
- package/dist/components/GrTreeSelect/chunks/GrTreeSelect-B6KoDYiZ.js.map +1 -0
- package/dist/components/GrTreeSelect/index.js +2 -2
- package/dist/granular-provider-node.js +1 -1
- package/dist/granular-provider.js +1 -1
- package/dist/i18n/all.js +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/index.js +30 -30
- package/dist/theme.js +1 -1
- package/dist/tokens.js +1 -1
- package/dist/types/components/GrAutocomplete/composables/useAutocompleteNavigation.d.ts +39 -0
- package/dist/types/components/GrAutocomplete/composables/useAutocompletePanel.d.ts +36 -0
- package/dist/types/components/GrAutocomplete/composables/useAutocompleteValues.d.ts +28 -0
- package/dist/types/components/GrAutocomplete/composables/useAutocompleteVirtual.d.ts +33 -0
- package/dist/types/components/GrAutocomplete/composables/useRemoteOptions.d.ts +33 -0
- package/dist/types/components/GrDataTable/GrDataTable.vue.d.ts +1 -1
- package/dist/types/components/GrDataTable/composables/useDataTableColumnOrder.d.ts +45 -0
- package/dist/types/components/GrDataTable/composables/useDataTableColumnWidths.d.ts +38 -0
- package/dist/types/components/GrDataTable/composables/useDataTableLayout.d.ts +29 -0
- package/dist/types/components/GrDataTable/composables/useDataTableRowKeys.d.ts +16 -0
- package/dist/types/components/GrDataTable/composables/useDataTableSelection.d.ts +32 -0
- package/dist/types/components/GrDataTable/composables/useDataTableSort.d.ts +37 -0
- package/dist/types/components/GrFormField/GrFormField.vue.d.ts +7 -1
- package/dist/types/components/GrPagination/GrPagination.vue.d.ts +17 -2
- package/dist/types/components/GrPagination/grPaginationStyles.d.ts +15 -6
- package/dist/types/components/GrPagination/index.d.ts +1 -1
- package/dist/types/components/GrTree/grTreeStyles.d.ts +1 -1
- package/dist/types/components/GrTreeSections/grTreeSectionsStyles.d.ts +15 -0
- package/dist/types/components/shared/controlState.d.ts +36 -0
- package/dist/types/i18n/messages/en.d.ts +4 -0
- package/dist/types/i18n/messages/es.d.ts +4 -0
- package/dist/types/i18n/messages/ru.d.ts +4 -0
- package/dist/web-types.json +4 -3
- package/package.json +1 -1
- package/dist/chunks/config-BhfNgyWL.js.map +0 -1
- package/dist/chunks/config-CIkdsWUk.js.map +0 -1
- package/dist/chunks/config-ChnYtO4o.js.map +0 -1
- package/dist/chunks/config-CjKpUcmy.js.map +0 -1
- package/dist/chunks/en-DiHzFact.js.map +0 -1
- package/dist/chunks/es-1ZL6NtkB.js.map +0 -1
- package/dist/chunks/ru-Dr2RYn0P.js.map +0 -1
- package/dist/chunks/x-circle-BCBURwyX.js.map +0 -1
- package/dist/components/GrAutocomplete/chunks/GrAutocomplete-D396_JrW.js.map +0 -1
- package/dist/components/GrDataTable/chunks/GrDataTable-xNSmO_I-.js.map +0 -1
- package/dist/components/GrInput/chunks/GrInput-TFeBm3gQ.js.map +0 -1
- package/dist/components/GrInputTag/chunks/GrInputTag-Ci8AO1ze.js.map +0 -1
- package/dist/components/GrNumberInput/chunks/GrNumberInput-CL5QhOmx.js.map +0 -1
- package/dist/components/GrPagination/chunks/GrPagination-DhbNZHEF.js.map +0 -1
- package/dist/components/GrTextarea/chunks/GrTextarea-BII3w61K.js.map +0 -1
- package/dist/components/GrTree/chunks/GrTree-DnDGW0xO.js.map +0 -1
- package/dist/components/GrTreeSections/chunks/GrTreeSections-D2PQ87mw.js.map +0 -1
- package/dist/components/GrTreeSelect/chunks/GrTreeSelect-DzheDCuZ.js.map +0 -1
- package/dist/components/GrCommandPalette/chunks/{GrCommandPalette-Ha4s3DMT.js → GrCommandPalette-BJHmkDQv.js} +1 -1
package/dist/components/GrDataTable/chunks/{GrDataTable-xNSmO_I-.js → GrDataTable-Dp9Fp_P5.js}
RENAMED
|
@@ -14,6 +14,34 @@ import { C as sortIconSizes, S as selectColumnWidths, T as summaryRowClass, _ as
|
|
|
14
14
|
import { t as arrow_up_default } from "../../../chunks/arrow-up-C_G4jg8M.js";
|
|
15
15
|
import { t as grip_vertical_default } from "../../../chunks/grip-vertical-lRha2S7q.js";
|
|
16
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/composables/useDataTableRowKeys.ts
|
|
18
|
+
function useDataTableRowKeys(options) {
|
|
19
|
+
const syntheticKeys = /* @__PURE__ */ new WeakMap();
|
|
20
|
+
let syntheticKeyCounter = 0;
|
|
21
|
+
let missingKeyWarned = false;
|
|
22
|
+
function syntheticRowKey(row) {
|
|
23
|
+
const existing = syntheticKeys.get(row);
|
|
24
|
+
if (existing !== void 0) return existing;
|
|
25
|
+
syntheticKeyCounter += 1;
|
|
26
|
+
const generated = `gr-row-${syntheticKeyCounter}`;
|
|
27
|
+
syntheticKeys.set(row, generated);
|
|
28
|
+
if (!missingKeyWarned && process.env.NODE_ENV !== "production") {
|
|
29
|
+
missingKeyWarned = true;
|
|
30
|
+
console.warn(`[GrDataTable] У строки нет значения по ключу "${String(options.rowKey())}". Задайте \`rowKey\` (поле или функцию) — иначе выбор строк и переиспользование DOM работают по синтетическому ключу, который не переживёт перезагрузку данных.`);
|
|
31
|
+
}
|
|
32
|
+
return generated;
|
|
33
|
+
}
|
|
34
|
+
function rowKeyValue(row) {
|
|
35
|
+
const rk = options.rowKey();
|
|
36
|
+
if (typeof rk === "function") return rk(row);
|
|
37
|
+
const value = row[rk];
|
|
38
|
+
if (typeof value === "string" && value !== "") return value;
|
|
39
|
+
if (typeof value === "number") return value;
|
|
40
|
+
return syntheticRowKey(row);
|
|
41
|
+
}
|
|
42
|
+
return { rowKeyValue };
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
17
45
|
//#region src/components/GrDataTable/grDataTableSort.ts
|
|
18
46
|
/** Пустое значение ячейки: нечего сравнивать, место таким — в конце списка. */
|
|
19
47
|
function isEmptyCell(value) {
|
|
@@ -66,6 +94,448 @@ function sortRows(rows, key, dir, locale) {
|
|
|
66
94
|
return [...rows].sort((a, b) => compareCells(a[key], b[key], dir, locale));
|
|
67
95
|
}
|
|
68
96
|
//#endregion
|
|
97
|
+
//#region src/components/GrDataTable/composables/useDataTableSort.ts
|
|
98
|
+
function useDataTableSort(options) {
|
|
99
|
+
const internalSortKey = ref(options.initialSortKey() ?? "");
|
|
100
|
+
const internalSortDir = ref(options.initialSortDir());
|
|
101
|
+
const isSortKeyControlled = computed(() => options.sortKey() !== void 0);
|
|
102
|
+
const isSortDirControlled = computed(() => options.sortDir() !== void 0);
|
|
103
|
+
const currentSortKey = computed(() => options.sortKey() ?? internalSortKey.value);
|
|
104
|
+
const currentSortDir = computed(() => options.sortDir() ?? internalSortDir.value);
|
|
105
|
+
function applySort(key, dir) {
|
|
106
|
+
if (!isSortKeyControlled.value) internalSortKey.value = key;
|
|
107
|
+
if (!isSortDirControlled.value) internalSortDir.value = dir;
|
|
108
|
+
options.onSortChange(key, dir);
|
|
109
|
+
}
|
|
110
|
+
const sortedRows = computed(() => {
|
|
111
|
+
if (options.externalSort()) return [...options.rows()];
|
|
112
|
+
const key = currentSortKey.value;
|
|
113
|
+
if (!key) return [...options.rows()];
|
|
114
|
+
return sortRows(options.rows(), key, currentSortDir.value, options.locale());
|
|
115
|
+
});
|
|
116
|
+
function toggleSort(col) {
|
|
117
|
+
if (!col.sortable) return;
|
|
118
|
+
if (currentSortKey.value !== col.key) {
|
|
119
|
+
applySort(col.key, "asc");
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (currentSortDir.value === "asc") {
|
|
123
|
+
applySort(col.key, "desc");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (options.sortCycle() === "asc-desc-none") {
|
|
127
|
+
applySort("", "asc");
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
applySort(col.key, "asc");
|
|
131
|
+
}
|
|
132
|
+
function clearSort() {
|
|
133
|
+
applySort("", "asc");
|
|
134
|
+
}
|
|
135
|
+
function ariaSortFor(col) {
|
|
136
|
+
if (!col.sortable) return void 0;
|
|
137
|
+
if (currentSortKey.value !== col.key) return "none";
|
|
138
|
+
return currentSortDir.value === "asc" ? "ascending" : "descending";
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
currentSortKey,
|
|
142
|
+
currentSortDir,
|
|
143
|
+
sortedRows,
|
|
144
|
+
toggleSort,
|
|
145
|
+
clearSort,
|
|
146
|
+
ariaSortFor
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/components/GrDataTable/composables/useDataTableSelection.ts
|
|
151
|
+
function useDataTableSelection(options) {
|
|
152
|
+
const internalSelected = ref([]);
|
|
153
|
+
const isSelectedControlled = computed(() => options.selected() !== void 0);
|
|
154
|
+
const selectedKeys = computed(() => new Set(options.selected() ?? internalSelected.value));
|
|
155
|
+
function isRowSelectable(row) {
|
|
156
|
+
const predicate = options.selectableRow();
|
|
157
|
+
return predicate ? predicate(row) : true;
|
|
158
|
+
}
|
|
159
|
+
function isRowSelected(row) {
|
|
160
|
+
return selectedKeys.value.has(options.rowKeyValue(row));
|
|
161
|
+
}
|
|
162
|
+
/** «Выбрать все» работает по видимым и выбираемым строкам — они же считают состояние шапки. */
|
|
163
|
+
const selectableRows = computed(() => options.rows().filter(isRowSelectable));
|
|
164
|
+
const allSelected = computed(() => selectableRows.value.length > 0 && selectableRows.value.every(isRowSelected));
|
|
165
|
+
const someSelected = computed(() => selectableRows.value.some(isRowSelected) && !allSelected.value);
|
|
166
|
+
function emitSelected(next) {
|
|
167
|
+
if (!isSelectedControlled.value) internalSelected.value = [...next];
|
|
168
|
+
options.onSelectedChange([...next]);
|
|
169
|
+
}
|
|
170
|
+
function toggleRow(row) {
|
|
171
|
+
if (!isRowSelectable(row)) return;
|
|
172
|
+
const key = options.rowKeyValue(row);
|
|
173
|
+
const next = new Set(selectedKeys.value);
|
|
174
|
+
if (next.has(key)) next.delete(key);
|
|
175
|
+
else next.add(key);
|
|
176
|
+
emitSelected(next);
|
|
177
|
+
}
|
|
178
|
+
function toggleAll() {
|
|
179
|
+
const next = new Set(selectedKeys.value);
|
|
180
|
+
if (allSelected.value) {
|
|
181
|
+
for (const row of selectableRows.value) next.delete(options.rowKeyValue(row));
|
|
182
|
+
emitSelected(next);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
for (const row of selectableRows.value) next.add(options.rowKeyValue(row));
|
|
186
|
+
emitSelected(next);
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
selectedKeys,
|
|
190
|
+
isRowSelectable,
|
|
191
|
+
isRowSelected,
|
|
192
|
+
selectableRows,
|
|
193
|
+
allSelected,
|
|
194
|
+
someSelected,
|
|
195
|
+
toggleRow,
|
|
196
|
+
toggleAll
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
//#endregion
|
|
200
|
+
//#region src/components/GrDataTable/composables/useDataTableColumnOrder.ts
|
|
201
|
+
function useDataTableColumnOrder(options) {
|
|
202
|
+
/** Uncontrolled-состояние; в controlled-режиме перекрывается пропом `columnOrder`. */
|
|
203
|
+
const internalColumnOrder = ref(options.columns().map((col) => String(col.key)));
|
|
204
|
+
/**
|
|
205
|
+
* Колонки в пользовательском порядке.
|
|
206
|
+
*
|
|
207
|
+
* Состав задаёт `columns`, порядок — только порядок: ключ, которого в наборе
|
|
208
|
+
* нет, игнорируется, а колонка, которой нет в порядке (её только что
|
|
209
|
+
* добавили), встаёт в конец. Иначе правка `columns` теряла бы колонки молча.
|
|
210
|
+
*/
|
|
211
|
+
const orderedColumns = computed(() => {
|
|
212
|
+
const order = options.columnOrder() ?? internalColumnOrder.value;
|
|
213
|
+
const remaining = new Map(options.columns().map((col) => [String(col.key), col]));
|
|
214
|
+
const ordered = [];
|
|
215
|
+
for (const key of order) {
|
|
216
|
+
const col = remaining.get(key);
|
|
217
|
+
if (!col) continue;
|
|
218
|
+
remaining.delete(key);
|
|
219
|
+
ordered.push(col);
|
|
220
|
+
}
|
|
221
|
+
for (const col of options.columns()) if (remaining.has(String(col.key))) ordered.push(col);
|
|
222
|
+
return ordered.sort((a, b) => pinGroupOf(a) - pinGroupOf(b));
|
|
223
|
+
});
|
|
224
|
+
/** Группа закрепления: 0 — слева, 1 — обычная колонка, 2 — справа. */
|
|
225
|
+
function pinGroupOf(col) {
|
|
226
|
+
if (col.pinned === "left") return 0;
|
|
227
|
+
if (col.pinned === "right") return 2;
|
|
228
|
+
return 1;
|
|
229
|
+
}
|
|
230
|
+
const columnKeys = computed(() => orderedColumns.value.map((col) => String(col.key)));
|
|
231
|
+
function indexOfColumn(key) {
|
|
232
|
+
return columnKeys.value.indexOf(key);
|
|
233
|
+
}
|
|
234
|
+
function sameGroup(from, to) {
|
|
235
|
+
const cols = orderedColumns.value;
|
|
236
|
+
if (!cols[from] || !cols[to]) return false;
|
|
237
|
+
return pinGroupOf(cols[from]) === pinGroupOf(cols[to]);
|
|
238
|
+
}
|
|
239
|
+
function applyColumnMove(from, to) {
|
|
240
|
+
const keys = columnKeys.value;
|
|
241
|
+
if (from < 0 || to < 0 || to >= keys.length || from === to) return;
|
|
242
|
+
if (!sameGroup(from, to)) return;
|
|
243
|
+
const key = keys[from];
|
|
244
|
+
const label = orderedColumns.value[from]?.label ?? key;
|
|
245
|
+
const next = moveItem(keys, from, to);
|
|
246
|
+
internalColumnOrder.value = next;
|
|
247
|
+
options.onOrderChange(next, {
|
|
248
|
+
key,
|
|
249
|
+
from,
|
|
250
|
+
to
|
|
251
|
+
});
|
|
252
|
+
options.announceMove(label, to + 1, keys.length);
|
|
253
|
+
}
|
|
254
|
+
const columnSort = useDragSort({
|
|
255
|
+
items: () => columnKeys.value,
|
|
256
|
+
elementFor: (key) => options.headerCellEls.get(key) ?? null,
|
|
257
|
+
orientation: () => "horizontal",
|
|
258
|
+
disabled: () => !options.reorderableColumns() || options.loading(),
|
|
259
|
+
resolveTarget: (hit, source) => {
|
|
260
|
+
const to = insertionIndex(hit, indexOfColumn(source), columnKeys.value.length);
|
|
261
|
+
return sameGroup(indexOfColumn(source), to) ? to : null;
|
|
262
|
+
},
|
|
263
|
+
onDrop: (source, to) => applyColumnMove(indexOfColumn(source), to)
|
|
264
|
+
});
|
|
265
|
+
/**
|
|
266
|
+
* Кольцо роверного фокуса по ручкам: одна остановка `Tab` на всю шапку.
|
|
267
|
+
* Иначе таблица из десяти колонок добавляла бы десять таб-стопов подряд.
|
|
268
|
+
*/
|
|
269
|
+
const columnRoving = useRovingFocus({
|
|
270
|
+
items: () => options.reorderableColumns() ? columnKeys.value : [],
|
|
271
|
+
elementFor: (key) => options.columnHandleEls.get(key) ?? null,
|
|
272
|
+
orientation: () => "horizontal",
|
|
273
|
+
wrap: () => false
|
|
274
|
+
});
|
|
275
|
+
async function onColumnHandleKeydown(event, key) {
|
|
276
|
+
if (!options.reorderableColumns()) return;
|
|
277
|
+
if (event.shiftKey && (event.key === "ArrowLeft" || event.key === "ArrowRight")) {
|
|
278
|
+
event.preventDefault();
|
|
279
|
+
const from = indexOfColumn(key);
|
|
280
|
+
applyColumnMove(from, event.key === "ArrowLeft" ? from - 1 : from + 1);
|
|
281
|
+
await nextTick();
|
|
282
|
+
await columnRoving.focusKey(key);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
columnRoving.handleNavigationKeys(event);
|
|
286
|
+
}
|
|
287
|
+
const draggingColumnKey = computed(() => columnSort.mode.value === null ? null : columnSort.source.value);
|
|
288
|
+
/** Полоса места вставки — на колонке-соседе, со стороны движения. */
|
|
289
|
+
function columnDropClass(index) {
|
|
290
|
+
const target = columnSort.target.value;
|
|
291
|
+
if (!columnSort.isActive.value || target === null || target !== index) return "";
|
|
292
|
+
const active = columnSort.source.value === null ? -1 : indexOfColumn(columnSort.source.value);
|
|
293
|
+
if (index === active) return "";
|
|
294
|
+
return target < active ? columnDropBeforeClass : columnDropAfterClass;
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
orderedColumns,
|
|
298
|
+
columnKeys,
|
|
299
|
+
pinGroupOf,
|
|
300
|
+
indexOfColumn,
|
|
301
|
+
draggingColumnKey,
|
|
302
|
+
columnDropClass,
|
|
303
|
+
onColumnHandleKeydown,
|
|
304
|
+
columnSort,
|
|
305
|
+
columnRoving
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
/** Шаг клавиатуры и крупный шаг — как у `GrSplitter`. */
|
|
309
|
+
var RESIZE_STEP = 16;
|
|
310
|
+
var RESIZE_BIG_STEP = 48;
|
|
311
|
+
function useDataTableColumnWidths(options) {
|
|
312
|
+
const internalColumnWidths = ref({});
|
|
313
|
+
const columnWidths = computed(() => options.columnWidths() ?? internalColumnWidths.value);
|
|
314
|
+
/** Фактические ширины шапки: заполняются замером раскладки. */
|
|
315
|
+
const measuredColumnWidths = ref({});
|
|
316
|
+
function setMeasuredWidths(widths) {
|
|
317
|
+
measuredColumnWidths.value = widths;
|
|
318
|
+
}
|
|
319
|
+
/** Ширина колонки: заданная пользователем сильнее объявленной в `columns`. */
|
|
320
|
+
function widthOf(col) {
|
|
321
|
+
return columnWidths.value[String(col.key)] ?? col.width;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Сумма заданных ширин — минимальная ширина таблицы.
|
|
325
|
+
*
|
|
326
|
+
* Без неё фиксированная раскладка вписывает таблицу в контейнер и делит место
|
|
327
|
+
* пропорционально: колонка, которую пользователь растянул, ужимается обратно, а
|
|
328
|
+
* горизонтальной прокрутки — той самой, ради которой закрепляют колонки, — не
|
|
329
|
+
* возникает. Считается, только когда ширина известна у **всех** колонок: с
|
|
330
|
+
* одной неизвестной сумма врала бы.
|
|
331
|
+
*/
|
|
332
|
+
const tableMinWidth = computed(() => {
|
|
333
|
+
const cols = options.orderedColumns();
|
|
334
|
+
if (cols.length === 0) return void 0;
|
|
335
|
+
let total = 0;
|
|
336
|
+
for (const col of cols) {
|
|
337
|
+
const width = widthOf(col);
|
|
338
|
+
if (typeof width !== "number") return void 0;
|
|
339
|
+
total += width;
|
|
340
|
+
}
|
|
341
|
+
return total;
|
|
342
|
+
});
|
|
343
|
+
const resizingKey = ref(null);
|
|
344
|
+
let pendingResizeKey = null;
|
|
345
|
+
let resizeStartX = 0;
|
|
346
|
+
let resizeStartWidth = 0;
|
|
347
|
+
let widthBeforeResize;
|
|
348
|
+
function commitWidths(next) {
|
|
349
|
+
internalColumnWidths.value = next;
|
|
350
|
+
options.onWidthsChange(next);
|
|
351
|
+
}
|
|
352
|
+
function setColumnWidth(key, width) {
|
|
353
|
+
commitWidths({
|
|
354
|
+
...columnWidths.value,
|
|
355
|
+
[key]: Math.max(48, Math.round(width))
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
function announceWidth(key) {
|
|
359
|
+
const col = options.orderedColumns().find((item) => String(item.key) === key);
|
|
360
|
+
if (!col) return;
|
|
361
|
+
options.announceWidth(col.label, columnWidths.value[key] ?? 0);
|
|
362
|
+
}
|
|
363
|
+
const resizeGesture = useDragGesture({
|
|
364
|
+
disabled: () => !options.resizableColumns() || options.loading(),
|
|
365
|
+
onStart: (event) => {
|
|
366
|
+
const key = pendingResizeKey;
|
|
367
|
+
const el = key === null ? null : options.headerCellEls.get(key);
|
|
368
|
+
if (key === null || !el) return false;
|
|
369
|
+
resizeStartX = event.clientX;
|
|
370
|
+
resizeStartWidth = el.getBoundingClientRect().width;
|
|
371
|
+
widthBeforeResize = columnWidths.value[key];
|
|
372
|
+
resizingKey.value = key;
|
|
373
|
+
},
|
|
374
|
+
onMove: (event) => {
|
|
375
|
+
if (resizingKey.value === null) return;
|
|
376
|
+
event.preventDefault();
|
|
377
|
+
setColumnWidth(resizingKey.value, resizeStartWidth + (event.clientX - resizeStartX));
|
|
378
|
+
},
|
|
379
|
+
onEnd: () => {
|
|
380
|
+
const key = resizingKey.value;
|
|
381
|
+
resizingKey.value = null;
|
|
382
|
+
if (key === null) return;
|
|
383
|
+
options.onColumnResize(key, columnWidths.value[key] ?? Math.round(resizeStartWidth));
|
|
384
|
+
announceWidth(key);
|
|
385
|
+
},
|
|
386
|
+
onCancel: () => {
|
|
387
|
+
const key = resizingKey.value;
|
|
388
|
+
resizingKey.value = null;
|
|
389
|
+
if (key === null) return;
|
|
390
|
+
const next = { ...columnWidths.value };
|
|
391
|
+
if (widthBeforeResize === void 0) delete next[key];
|
|
392
|
+
else next[key] = widthBeforeResize;
|
|
393
|
+
commitWidths(next);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
function onResizerPointerDown(event, key) {
|
|
397
|
+
pendingResizeKey = key;
|
|
398
|
+
resizeGesture.start(event);
|
|
399
|
+
}
|
|
400
|
+
/** Сброс к авторазметке: колонка снова считается по содержимому. */
|
|
401
|
+
function resetColumnWidth(key) {
|
|
402
|
+
if (columnWidths.value[key] === void 0) return;
|
|
403
|
+
const next = { ...columnWidths.value };
|
|
404
|
+
delete next[key];
|
|
405
|
+
commitWidths(next);
|
|
406
|
+
options.onColumnResize(key, 0);
|
|
407
|
+
}
|
|
408
|
+
function resizeByKeyboard(key, delta) {
|
|
409
|
+
const measured = options.headerCellEls.get(key)?.getBoundingClientRect().width ?? 48;
|
|
410
|
+
const current = columnWidths.value[key] ?? measured;
|
|
411
|
+
setColumnWidth(key, current + delta);
|
|
412
|
+
options.onColumnResize(key, columnWidths.value[key] ?? current);
|
|
413
|
+
announceWidth(key);
|
|
414
|
+
}
|
|
415
|
+
function onResizerKeydown(event, key) {
|
|
416
|
+
if (!options.resizableColumns() || options.loading()) return;
|
|
417
|
+
const step = event.shiftKey ? RESIZE_BIG_STEP : RESIZE_STEP;
|
|
418
|
+
if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
|
|
419
|
+
event.preventDefault();
|
|
420
|
+
resizeByKeyboard(key, event.key === "ArrowLeft" ? -step : step);
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (event.key === "Enter") {
|
|
424
|
+
event.preventDefault();
|
|
425
|
+
resetColumnWidth(key);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Ручка ресайза — фокусируемый `separator`, а значит обязана называть текущее
|
|
430
|
+
* значение с первого рендера, до всякого перетаскивания. Заданной ширины у
|
|
431
|
+
* колонки может не быть вовсе (авторазметка), поэтому за значением идём к
|
|
432
|
+
* измеренной; до замера остаётся объявленная в `columns`, а её тоже может не
|
|
433
|
+
* быть — тогда минимум.
|
|
434
|
+
*/
|
|
435
|
+
function resizerValue(col) {
|
|
436
|
+
const key = String(col.key);
|
|
437
|
+
const declared = typeof col.width === "number" ? col.width : void 0;
|
|
438
|
+
return columnWidths.value[key] ?? measuredColumnWidths.value[key] ?? declared ?? 48;
|
|
439
|
+
}
|
|
440
|
+
return {
|
|
441
|
+
columnWidths,
|
|
442
|
+
measuredColumnWidths,
|
|
443
|
+
setMeasuredWidths,
|
|
444
|
+
widthOf,
|
|
445
|
+
tableMinWidth,
|
|
446
|
+
resizingKey,
|
|
447
|
+
onResizerPointerDown,
|
|
448
|
+
onResizerKeydown,
|
|
449
|
+
resetColumnWidth,
|
|
450
|
+
resizerValue
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
//#endregion
|
|
454
|
+
//#region src/components/GrDataTable/composables/useDataTableLayout.ts
|
|
455
|
+
/** Служебная колонка выбора в реестре ячеек: своего ключа у неё нет. */
|
|
456
|
+
var SELECT_COLUMN_KEY = "__select__";
|
|
457
|
+
function useDataTableLayout(options) {
|
|
458
|
+
const hasPinnedLeft = computed(() => options.orderedColumns().some((col) => col.pinned === "left"));
|
|
459
|
+
const pinnedOffsets = ref({});
|
|
460
|
+
/**
|
|
461
|
+
* Смещения липких колонок считаются по **измеренным** ширинам соседей: колонка
|
|
462
|
+
* может жить на авторазметке, и тогда взять смещение больше неоткуда. Отсюда же
|
|
463
|
+
* порядок: замер после отрисовки, а не в вычислении.
|
|
464
|
+
*/
|
|
465
|
+
function measurePinnedOffsets() {
|
|
466
|
+
const next = {};
|
|
467
|
+
const cols = options.orderedColumns();
|
|
468
|
+
let left = options.selectable() && hasPinnedLeft.value ? options.headerCellEls.get("__select__")?.getBoundingClientRect().width ?? 0 : 0;
|
|
469
|
+
for (const col of cols) {
|
|
470
|
+
if (col.pinned !== "left") continue;
|
|
471
|
+
next[String(col.key)] = left;
|
|
472
|
+
left += options.headerCellEls.get(String(col.key))?.getBoundingClientRect().width ?? 0;
|
|
473
|
+
}
|
|
474
|
+
let right = 0;
|
|
475
|
+
for (let index = cols.length - 1; index >= 0; index -= 1) {
|
|
476
|
+
const col = cols[index];
|
|
477
|
+
if (col.pinned !== "right") continue;
|
|
478
|
+
next[String(col.key)] = right;
|
|
479
|
+
right += options.headerCellEls.get(String(col.key))?.getBoundingClientRect().width ?? 0;
|
|
480
|
+
}
|
|
481
|
+
pinnedOffsets.value = next;
|
|
482
|
+
}
|
|
483
|
+
function measureColumnWidths() {
|
|
484
|
+
if (!options.resizableColumns()) return;
|
|
485
|
+
const next = {};
|
|
486
|
+
for (const col of options.orderedColumns()) {
|
|
487
|
+
const key = String(col.key);
|
|
488
|
+
const width = options.headerCellEls.get(key)?.getBoundingClientRect().width;
|
|
489
|
+
if (width) next[key] = Math.round(width);
|
|
490
|
+
}
|
|
491
|
+
options.onMeasuredWidths(next);
|
|
492
|
+
}
|
|
493
|
+
function measureLayout() {
|
|
494
|
+
measurePinnedOffsets();
|
|
495
|
+
measureColumnWidths();
|
|
496
|
+
}
|
|
497
|
+
function pinnedStyleOf(col) {
|
|
498
|
+
if (!col.pinned) return void 0;
|
|
499
|
+
const offset = pinnedOffsets.value[String(col.key)] ?? 0;
|
|
500
|
+
return col.pinned === "left" ? { left: `${offset}px` } : { right: `${offset}px` };
|
|
501
|
+
}
|
|
502
|
+
/** Тень рисует только крайняя колонка группы — иначе полос было бы столько же, сколько колонок. */
|
|
503
|
+
function pinnedEdgeClass(col, index) {
|
|
504
|
+
const cols = options.orderedColumns();
|
|
505
|
+
if (col.pinned === "left") return cols[index + 1]?.pinned === "left" ? "" : columnPinnedLeftEdgeClass;
|
|
506
|
+
if (col.pinned === "right") return cols[index - 1]?.pinned === "right" ? "" : columnPinnedRightEdgeClass;
|
|
507
|
+
return "";
|
|
508
|
+
}
|
|
509
|
+
function pinnedCellClass(col, index) {
|
|
510
|
+
if (!col.pinned) return [];
|
|
511
|
+
return [columnPinnedClass, pinnedEdgeClass(col, index)];
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Ширину колонки меняет не только правка пропов: окно, шрифт и содержимое
|
|
515
|
+
* ячейки двигают её сами. `ResizeObserver` — единственный способ узнать об этом;
|
|
516
|
+
* в jsdom и на сервере его нет, там хватает пересчёта по изменению данных.
|
|
517
|
+
*/
|
|
518
|
+
let layoutObserver = null;
|
|
519
|
+
onMounted(() => {
|
|
520
|
+
measureLayout();
|
|
521
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
522
|
+
layoutObserver = new ResizeObserver(() => measureLayout());
|
|
523
|
+
const el = options.headerCellEls.get("__select__") ?? options.headerCellEls.get(options.columnKeys()[0]);
|
|
524
|
+
if (el?.parentElement) layoutObserver.observe(el.parentElement);
|
|
525
|
+
});
|
|
526
|
+
onUnmounted(() => {
|
|
527
|
+
layoutObserver?.disconnect();
|
|
528
|
+
layoutObserver = null;
|
|
529
|
+
});
|
|
530
|
+
watch(options.watchSources, () => void nextTick(measureLayout));
|
|
531
|
+
return {
|
|
532
|
+
hasPinnedLeft,
|
|
533
|
+
pinnedStyleOf,
|
|
534
|
+
pinnedCellClass,
|
|
535
|
+
measureLayout
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
//#endregion
|
|
69
539
|
//#region ~icons/lucide/arrow-down
|
|
70
540
|
var _hoisted_1$1 = {
|
|
71
541
|
viewBox: "0 0 24 24",
|
|
@@ -112,6 +582,7 @@ var _hoisted_8 = {
|
|
|
112
582
|
var _hoisted_9 = [
|
|
113
583
|
"aria-label",
|
|
114
584
|
"aria-valuenow",
|
|
585
|
+
"aria-valuemin",
|
|
115
586
|
"aria-valuetext",
|
|
116
587
|
"onPointerdown",
|
|
117
588
|
"onKeydown",
|
|
@@ -150,27 +621,6 @@ var _hoisted_18 = {
|
|
|
150
621
|
var _hoisted_19 = ["colspan"];
|
|
151
622
|
var _hoisted_20 = ["aria-rowindex"];
|
|
152
623
|
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
624
|
//#endregion
|
|
175
625
|
//#region src/components/GrDataTable/GrDataTable.vue
|
|
176
626
|
var GrDataTable_default = /* @__PURE__ */ defineComponent({
|
|
@@ -262,85 +712,30 @@ var GrDataTable_default = /* @__PURE__ */ defineComponent({
|
|
|
262
712
|
const { announce } = useAnnouncer();
|
|
263
713
|
const resolvedLoadingText = computed(() => props.loadingText ?? t("gr.dataTable.loading", "Loading…"));
|
|
264
714
|
const resolvedEmptyText = computed(() => props.emptyText ?? t("gr.dataTable.empty", "No data"));
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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;
|
|
715
|
+
const { currentSortKey, currentSortDir, sortedRows, toggleSort, clearSort, ariaSortFor } = useDataTableSort({
|
|
716
|
+
rows: () => props.rows,
|
|
717
|
+
sortKey: () => props.sortKey,
|
|
718
|
+
sortDir: () => props.sortDir,
|
|
719
|
+
initialSortKey: () => props.initialSortKey,
|
|
720
|
+
initialSortDir: () => props.initialSortDir,
|
|
721
|
+
externalSort: () => props.externalSort,
|
|
722
|
+
sortCycle: () => props.sortCycle,
|
|
723
|
+
locale: () => locale.value,
|
|
724
|
+
onSortChange: (key, dir) => {
|
|
725
|
+
emit("update:sortKey", key);
|
|
726
|
+
emit("update:sortDir", dir);
|
|
727
|
+
emit("sortChange", {
|
|
728
|
+
key,
|
|
729
|
+
dir
|
|
730
|
+
});
|
|
300
731
|
}
|
|
301
|
-
|
|
302
|
-
}
|
|
732
|
+
});
|
|
303
733
|
function cellAlign(col) {
|
|
304
734
|
if (col.align === "right") return "text-right";
|
|
305
735
|
if (col.align === "center") return "text-center";
|
|
306
736
|
return "text-left";
|
|
307
737
|
}
|
|
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
|
-
}
|
|
738
|
+
const { rowKeyValue } = useDataTableRowKeys({ rowKey: () => props.rowKey });
|
|
344
739
|
/**
|
|
345
740
|
* Подсказка «что сделает нажатие» — visually hidden текстом **после** подписи
|
|
346
741
|
* колонки, а не `aria-label`'ом на кнопке. `aria-label` подменял бы собой имя
|
|
@@ -391,55 +786,72 @@ var GrDataTable_default = /* @__PURE__ */ defineComponent({
|
|
|
391
786
|
return props.summaryRow?.[key];
|
|
392
787
|
}
|
|
393
788
|
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
789
|
/**
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
* нет, игнорируется, а колонка, которой нет в порядке (её только что добавили),
|
|
401
|
-
* встаёт в конец. Иначе правка `columns` теряла бы колонки молча.
|
|
790
|
+
* Карты живут в компоненте, а не в модуле: наполняет их шаблон, а читают все
|
|
791
|
+
* три колоночных модуля — порядок тянет за ячейку, ширина её замеряет,
|
|
792
|
+
* раскладка считает по ней смещения закрепления.
|
|
402
793
|
*/
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
if (!col) continue;
|
|
410
|
-
remaining.delete(key);
|
|
411
|
-
ordered.push(col);
|
|
794
|
+
const headerCellEls = /* @__PURE__ */ new Map();
|
|
795
|
+
const columnHandleEls = /* @__PURE__ */ new Map();
|
|
796
|
+
function registerEl(store, key, el) {
|
|
797
|
+
if (el instanceof HTMLElement) {
|
|
798
|
+
store.set(key, el);
|
|
799
|
+
return;
|
|
412
800
|
}
|
|
413
|
-
|
|
414
|
-
|
|
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;
|
|
801
|
+
const previous = store.get(key);
|
|
802
|
+
if (previous && !previous.isConnected) store.delete(key);
|
|
421
803
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
804
|
+
const { orderedColumns, columnKeys, draggingColumnKey, columnDropClass, onColumnHandleKeydown, columnSort, columnRoving } = useDataTableColumnOrder({
|
|
805
|
+
columns: () => props.columns,
|
|
806
|
+
columnOrder: () => props.columnOrder,
|
|
807
|
+
reorderableColumns: () => props.reorderableColumns,
|
|
808
|
+
loading: () => props.loading,
|
|
809
|
+
headerCellEls,
|
|
810
|
+
columnHandleEls,
|
|
811
|
+
onOrderChange: (order, moved) => {
|
|
812
|
+
emit("update:columnOrder", order);
|
|
813
|
+
emit("columnReorder", moved);
|
|
814
|
+
},
|
|
815
|
+
announceMove: (label, position, count) => {
|
|
816
|
+
announce(t("gr.dataTable.columnMoved", "Column {label} moved to position {position} of {count}", {
|
|
817
|
+
label,
|
|
818
|
+
position,
|
|
819
|
+
count
|
|
820
|
+
}));
|
|
439
821
|
}
|
|
440
|
-
return total;
|
|
441
822
|
});
|
|
442
|
-
const
|
|
823
|
+
const { columnWidths, setMeasuredWidths, widthOf, tableMinWidth, resizingKey, onResizerPointerDown, onResizerKeydown, resetColumnWidth, resizerValue } = useDataTableColumnWidths({
|
|
824
|
+
orderedColumns: () => orderedColumns.value,
|
|
825
|
+
columnWidths: () => props.columnWidths,
|
|
826
|
+
resizableColumns: () => props.resizableColumns,
|
|
827
|
+
loading: () => props.loading,
|
|
828
|
+
headerCellEls,
|
|
829
|
+
onWidthsChange: (widths) => emit("update:columnWidths", widths),
|
|
830
|
+
onColumnResize: (key, width) => emit("columnResize", {
|
|
831
|
+
key,
|
|
832
|
+
width
|
|
833
|
+
}),
|
|
834
|
+
announceWidth: (label, width) => {
|
|
835
|
+
announce(t("gr.dataTable.columnResized", "Column {label} is {width} pixels wide", {
|
|
836
|
+
label,
|
|
837
|
+
width
|
|
838
|
+
}));
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
const { hasPinnedLeft, pinnedStyleOf, pinnedCellClass } = useDataTableLayout({
|
|
842
|
+
orderedColumns: () => orderedColumns.value,
|
|
843
|
+
columnKeys: () => columnKeys.value,
|
|
844
|
+
selectable: () => props.selectable,
|
|
845
|
+
resizableColumns: () => props.resizableColumns,
|
|
846
|
+
headerCellEls,
|
|
847
|
+
watchSources: () => [
|
|
848
|
+
orderedColumns.value,
|
|
849
|
+
columnWidths.value,
|
|
850
|
+
props.loading,
|
|
851
|
+
props.rows.length
|
|
852
|
+
],
|
|
853
|
+
onMeasuredWidths: setMeasuredWidths
|
|
854
|
+
});
|
|
443
855
|
const tableProps = computed(() => ({
|
|
444
856
|
size: resolvedSize.value,
|
|
445
857
|
ariaLabel: props.ariaLabel,
|
|
@@ -451,288 +863,13 @@ var GrDataTable_default = /* @__PURE__ */ defineComponent({
|
|
|
451
863
|
fixedLayout: props.virtual || props.resizableColumns,
|
|
452
864
|
tableMinWidth: tableMinWidth.value
|
|
453
865
|
}));
|
|
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
866
|
function columnHandleLabel(col) {
|
|
535
867
|
return t("gr.dataTable.moveColumn", "Move column {label}", { label: col.label });
|
|
536
868
|
}
|
|
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
869
|
function resizerLabel(col) {
|
|
644
870
|
return t("gr.dataTable.resizeColumn", "Width of column {label}", { label: col.label });
|
|
645
871
|
}
|
|
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));
|
|
872
|
+
/** Уже колонки не бывает: в неё перестаёт помещаться даже стрелка сортировки. */
|
|
736
873
|
/**
|
|
737
874
|
* Живой регион существует с первого рендера и пуст, пока объявлять нечего.
|
|
738
875
|
* Регион, который появляется уже с текстом, часть AT не объявляет вовсе —
|
|
@@ -757,41 +894,13 @@ var GrDataTable_default = /* @__PURE__ */ defineComponent({
|
|
|
757
894
|
event
|
|
758
895
|
});
|
|
759
896
|
}
|
|
760
|
-
const
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
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
|
-
}
|
|
897
|
+
const { isRowSelectable, isRowSelected, selectableRows, allSelected, someSelected, toggleRow, toggleAll } = useDataTableSelection({
|
|
898
|
+
rows: () => sortedRows.value,
|
|
899
|
+
selected: () => props.selected,
|
|
900
|
+
selectableRow: () => props.selectableRow,
|
|
901
|
+
rowKeyValue,
|
|
902
|
+
onSelectedChange: (keys) => emit("update:selected", keys)
|
|
903
|
+
});
|
|
795
904
|
const tableRef = ref(null);
|
|
796
905
|
const rootId = useId();
|
|
797
906
|
function rootEl() {
|
|
@@ -864,9 +973,6 @@ var GrDataTable_default = /* @__PURE__ */ defineComponent({
|
|
|
864
973
|
function scrollTo(options) {
|
|
865
974
|
scrollEl()?.scrollTo(options);
|
|
866
975
|
}
|
|
867
|
-
function clearSort() {
|
|
868
|
-
applySort("", "asc");
|
|
869
|
-
}
|
|
870
976
|
__expose({
|
|
871
977
|
/** Прокрутить к строке по её ключу. `false` — строки нет в DOM. */
|
|
872
978
|
scrollToRow,
|
|
@@ -904,29 +1010,30 @@ var GrDataTable_default = /* @__PURE__ */ defineComponent({
|
|
|
904
1010
|
"aria-rowindex": __props.virtual ? 1 : void 0
|
|
905
1011
|
}, [__props.selectable ? (openBlock(), createElementBlock("th", {
|
|
906
1012
|
key: 0,
|
|
907
|
-
ref: (el) => registerEl(unref(headerCellEls), SELECT_COLUMN_KEY, el),
|
|
1013
|
+
ref: (el) => registerEl(unref(headerCellEls), unref(SELECT_COLUMN_KEY), el),
|
|
908
1014
|
class: normalizeClass(["text-left", [
|
|
909
1015
|
selectColumnClass.value,
|
|
910
1016
|
cellClass.value,
|
|
911
|
-
hasPinnedLeft
|
|
1017
|
+
unref(hasPinnedLeft) ? unref(columnPinnedClass) : ""
|
|
912
1018
|
]]),
|
|
913
|
-
style: normalizeStyle(hasPinnedLeft
|
|
1019
|
+
style: normalizeStyle(unref(hasPinnedLeft) ? { left: "0px" } : void 0),
|
|
914
1020
|
scope: "col"
|
|
915
1021
|
}, [createVNode(GrCheckbox_default, {
|
|
916
1022
|
"data-gr-datatable-select-all": "",
|
|
917
|
-
"model-value": allSelected
|
|
918
|
-
indeterminate: someSelected
|
|
1023
|
+
"model-value": unref(allSelected),
|
|
1024
|
+
indeterminate: unref(someSelected),
|
|
919
1025
|
size: checkboxSize.value,
|
|
920
|
-
disabled: __props.loading || selectableRows.
|
|
1026
|
+
disabled: __props.loading || unref(selectableRows).length === 0,
|
|
921
1027
|
"aria-label": unref(t)("gr.dataTable.selectAll", "Select all rows"),
|
|
922
|
-
"onUpdate:modelValue": toggleAll
|
|
1028
|
+
"onUpdate:modelValue": unref(toggleAll)
|
|
923
1029
|
}, null, 8, [
|
|
924
1030
|
"model-value",
|
|
925
1031
|
"indeterminate",
|
|
926
1032
|
"size",
|
|
927
1033
|
"disabled",
|
|
928
|
-
"aria-label"
|
|
929
|
-
|
|
1034
|
+
"aria-label",
|
|
1035
|
+
"onUpdate:modelValue"
|
|
1036
|
+
])], 6)) : createCommentVNode("", true), (openBlock(true), createElementBlock(Fragment, null, renderList(unref(orderedColumns), (col, colIndex) => {
|
|
930
1037
|
return openBlock(), createElementBlock("th", {
|
|
931
1038
|
key: col.key,
|
|
932
1039
|
ref_for: true,
|
|
@@ -936,16 +1043,16 @@ var GrDataTable_default = /* @__PURE__ */ defineComponent({
|
|
|
936
1043
|
cellClass.value,
|
|
937
1044
|
cellAlign(col),
|
|
938
1045
|
__props.reorderableColumns || __props.resizableColumns ? "group relative" : "",
|
|
939
|
-
draggingColumnKey
|
|
940
|
-
columnDropClass(colIndex),
|
|
941
|
-
...pinnedCellClass(col, colIndex)
|
|
1046
|
+
unref(draggingColumnKey) === String(col.key) ? unref(columnDraggingClass) : "",
|
|
1047
|
+
unref(columnDropClass)(colIndex),
|
|
1048
|
+
...unref(pinnedCellClass)(col, colIndex)
|
|
942
1049
|
]]),
|
|
943
1050
|
"data-column-key": col.key,
|
|
944
1051
|
style: normalizeStyle({
|
|
945
|
-
...columnWidthStyle(widthOf(col)),
|
|
946
|
-
...pinnedStyleOf(col)
|
|
1052
|
+
...columnWidthStyle(unref(widthOf)(col)),
|
|
1053
|
+
...unref(pinnedStyleOf)(col)
|
|
947
1054
|
}),
|
|
948
|
-
"aria-sort": ariaSortFor(col),
|
|
1055
|
+
"aria-sort": unref(ariaSortFor)(col),
|
|
949
1056
|
scope: "col"
|
|
950
1057
|
}, [createElementVNode("div", { class: normalizeClass(["inline-flex items-center", headerGapClass.value]) }, [__props.reorderableColumns ? (openBlock(), createElementBlock("button", {
|
|
951
1058
|
key: 0,
|
|
@@ -958,7 +1065,7 @@ var GrDataTable_default = /* @__PURE__ */ defineComponent({
|
|
|
958
1065
|
"aria-label": columnHandleLabel(col),
|
|
959
1066
|
disabled: __props.loading,
|
|
960
1067
|
onPointerdown: ($event) => unref(columnSort).startFrom(String(col.key))($event),
|
|
961
|
-
onKeydown: ($event) => onColumnHandleKeydown($event, String(col.key)),
|
|
1068
|
+
onKeydown: ($event) => unref(onColumnHandleKeydown)($event, String(col.key)),
|
|
962
1069
|
onFocus: ($event) => unref(columnRoving).setActive(String(col.key)),
|
|
963
1070
|
onClick: _cache[0] || (_cache[0] = withModifiers(() => {}, ["stop"]))
|
|
964
1071
|
}, [createVNode(unref(grip_vertical_default), {
|
|
@@ -969,17 +1076,17 @@ var GrDataTable_default = /* @__PURE__ */ defineComponent({
|
|
|
969
1076
|
type: "button",
|
|
970
1077
|
"data-gr-datatable-sort": "",
|
|
971
1078
|
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)
|
|
1079
|
+
onClick: ($event) => unref(toggleSort)(col)
|
|
973
1080
|
}, [
|
|
974
1081
|
createElementVNode("span", null, [renderSlot(_ctx.$slots, `header-${col.key}`, { column: col }, () => [createTextVNode(toDisplayString(col.label), 1)])]),
|
|
975
1082
|
createElementVNode("span", _hoisted_6, toDisplayString(sortHint(col)), 1),
|
|
976
|
-
createElementVNode("span", _hoisted_7, [currentSortKey
|
|
1083
|
+
createElementVNode("span", _hoisted_7, [unref(currentSortKey) === col.key && unref(currentSortDir) === "asc" ? (openBlock(), createBlock(GrIcon_default, {
|
|
977
1084
|
key: 0,
|
|
978
1085
|
size: sortIconSize.value
|
|
979
1086
|
}, {
|
|
980
1087
|
default: withCtx(() => [createVNode(unref(arrow_up_default))]),
|
|
981
1088
|
_: 1
|
|
982
|
-
}, 8, ["size"])) : currentSortKey
|
|
1089
|
+
}, 8, ["size"])) : unref(currentSortKey) === col.key && unref(currentSortDir) === "desc" ? (openBlock(), createBlock(GrIcon_default, {
|
|
983
1090
|
key: 1,
|
|
984
1091
|
size: sortIconSize.value
|
|
985
1092
|
}, {
|
|
@@ -994,18 +1101,18 @@ var GrDataTable_default = /* @__PURE__ */ defineComponent({
|
|
|
994
1101
|
"aria-orientation": "vertical",
|
|
995
1102
|
class: normalizeClass(unref(columnResizerClass)),
|
|
996
1103
|
"aria-label": resizerLabel(col),
|
|
997
|
-
"aria-valuenow": resizerValue(col),
|
|
998
|
-
"aria-valuemin":
|
|
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)),
|
|
1104
|
+
"aria-valuenow": unref(resizerValue)(col),
|
|
1105
|
+
"aria-valuemin": unref(48),
|
|
1106
|
+
"aria-valuetext": `${unref(resizerValue)(col)}px`,
|
|
1107
|
+
onPointerdown: ($event) => unref(onResizerPointerDown)($event, String(col.key)),
|
|
1108
|
+
onKeydown: ($event) => unref(onResizerKeydown)($event, String(col.key)),
|
|
1109
|
+
onDblclick: ($event) => unref(resetColumnWidth)(String(col.key)),
|
|
1003
1110
|
onClick: _cache[1] || (_cache[1] = withModifiers(() => {}, ["stop"]))
|
|
1004
1111
|
}, [createElementVNode("span", {
|
|
1005
1112
|
class: normalizeClass([
|
|
1006
1113
|
unref(columnResizerLineClass),
|
|
1007
1114
|
unref(columnResizerHoverClass),
|
|
1008
|
-
resizingKey
|
|
1115
|
+
unref(resizingKey) === String(col.key) ? unref(columnResizerLineActiveClass) : ""
|
|
1009
1116
|
]),
|
|
1010
1117
|
"aria-hidden": "true"
|
|
1011
1118
|
}, null, 2)], 42, _hoisted_9)) : createCommentVNode("", true)], 14, _hoisted_3);
|
|
@@ -1030,48 +1137,48 @@ var GrDataTable_default = /* @__PURE__ */ defineComponent({
|
|
|
1030
1137
|
}, null, 12, _hoisted_16)])) : createCommentVNode("", true),
|
|
1031
1138
|
(openBlock(true), createElementBlock(Fragment, null, renderList(renderedRows.value, ({ row, index }) => {
|
|
1032
1139
|
return openBlock(), createElementBlock("tr", mergeProps({
|
|
1033
|
-
key: rowKeyValue(row),
|
|
1140
|
+
key: unref(rowKeyValue)(row),
|
|
1034
1141
|
ref_for: true,
|
|
1035
1142
|
ref: (el) => __props.virtual && unref(virtualizer).measure(index, el),
|
|
1036
|
-
class: ["border-t border-[var(--gr-brd)]", [isRowSelected(row) ? unref(rowSelectedClass) : "", rowClassName(row, index)]],
|
|
1143
|
+
class: ["border-t border-[var(--gr-brd)]", [unref(isRowSelected)(row) ? unref(rowSelectedClass) : "", rowClassName(row, index)]],
|
|
1037
1144
|
"data-gr-datatable-row": "",
|
|
1038
|
-
"data-row-key": rowKeyValue(row),
|
|
1039
|
-
"data-selected": __props.selectable && isRowSelected(row) ? "true" : void 0,
|
|
1145
|
+
"data-row-key": unref(rowKeyValue)(row),
|
|
1146
|
+
"data-selected": __props.selectable && unref(isRowSelected)(row) ? "true" : void 0,
|
|
1040
1147
|
"aria-rowindex": ariaRowIndex(index)
|
|
1041
1148
|
}, { ref_for: true }, __props.rowProps?.(row, index), { onClick: ($event) => onRowClick(row, index, $event) }), [__props.selectable ? (openBlock(), createElementBlock("td", {
|
|
1042
1149
|
key: 0,
|
|
1043
1150
|
class: normalizeClass(["text-left", [
|
|
1044
1151
|
selectColumnClass.value,
|
|
1045
1152
|
cellClass.value,
|
|
1046
|
-
hasPinnedLeft
|
|
1047
|
-
hasPinnedLeft
|
|
1153
|
+
unref(hasPinnedLeft) ? unref(columnPinnedClass) : "",
|
|
1154
|
+
unref(hasPinnedLeft) && unref(isRowSelected)(row) ? unref(rowSelectedClass) : ""
|
|
1048
1155
|
]]),
|
|
1049
|
-
style: normalizeStyle(hasPinnedLeft
|
|
1050
|
-
}, [isRowSelectable(row) ? (openBlock(), createBlock(GrCheckbox_default, {
|
|
1156
|
+
style: normalizeStyle(unref(hasPinnedLeft) ? { left: "0px" } : void 0)
|
|
1157
|
+
}, [unref(isRowSelectable)(row) ? (openBlock(), createBlock(GrCheckbox_default, {
|
|
1051
1158
|
key: 0,
|
|
1052
1159
|
"data-gr-datatable-select-row": "",
|
|
1053
|
-
"model-value": isRowSelected(row),
|
|
1160
|
+
"model-value": unref(isRowSelected)(row),
|
|
1054
1161
|
size: checkboxSize.value,
|
|
1055
1162
|
"aria-label": unref(t)("gr.dataTable.selectRow", "Select row"),
|
|
1056
1163
|
onClick: _cache[2] || (_cache[2] = withModifiers(() => {}, ["stop"])),
|
|
1057
|
-
"onUpdate:modelValue": ($event) => toggleRow(row)
|
|
1164
|
+
"onUpdate:modelValue": ($event) => unref(toggleRow)(row)
|
|
1058
1165
|
}, null, 8, [
|
|
1059
1166
|
"model-value",
|
|
1060
1167
|
"size",
|
|
1061
1168
|
"aria-label",
|
|
1062
1169
|
"onUpdate:modelValue"
|
|
1063
|
-
])) : createCommentVNode("", true)], 6)) : createCommentVNode("", true), (openBlock(true), createElementBlock(Fragment, null, renderList(orderedColumns
|
|
1170
|
+
])) : createCommentVNode("", true)], 6)) : createCommentVNode("", true), (openBlock(true), createElementBlock(Fragment, null, renderList(unref(orderedColumns), (col, colIndex) => {
|
|
1064
1171
|
return openBlock(), createElementBlock("td", {
|
|
1065
1172
|
key: col.key,
|
|
1066
1173
|
class: normalizeClass([
|
|
1067
1174
|
cellClass.value,
|
|
1068
1175
|
cellAlign(col),
|
|
1069
|
-
...pinnedCellClass(col, colIndex),
|
|
1070
|
-
col.pinned && isRowSelected(row) ? unref(rowSelectedClass) : ""
|
|
1176
|
+
...unref(pinnedCellClass)(col, colIndex),
|
|
1177
|
+
col.pinned && unref(isRowSelected)(row) ? unref(rowSelectedClass) : ""
|
|
1071
1178
|
]),
|
|
1072
1179
|
style: normalizeStyle({
|
|
1073
|
-
...columnWidthStyle(widthOf(col)),
|
|
1074
|
-
...pinnedStyleOf(col)
|
|
1180
|
+
...columnWidthStyle(unref(widthOf)(col)),
|
|
1181
|
+
...unref(pinnedStyleOf)(col)
|
|
1075
1182
|
})
|
|
1076
1183
|
}, [renderSlot(_ctx.$slots, `cell-${col.key}`, {
|
|
1077
1184
|
row,
|
|
@@ -1101,28 +1208,28 @@ var GrDataTable_default = /* @__PURE__ */ defineComponent({
|
|
|
1101
1208
|
class: normalizeClass([
|
|
1102
1209
|
selectColumnClass.value,
|
|
1103
1210
|
cellClass.value,
|
|
1104
|
-
hasPinnedLeft
|
|
1211
|
+
unref(hasPinnedLeft) ? unref(columnPinnedClass) : ""
|
|
1105
1212
|
]),
|
|
1106
|
-
style: normalizeStyle(hasPinnedLeft
|
|
1107
|
-
}, null, 6)) : createCommentVNode("", true), (openBlock(true), createElementBlock(Fragment, null, renderList(orderedColumns
|
|
1213
|
+
style: normalizeStyle(unref(hasPinnedLeft) ? { left: "0px" } : void 0)
|
|
1214
|
+
}, null, 6)) : createCommentVNode("", true), (openBlock(true), createElementBlock(Fragment, null, renderList(unref(orderedColumns), (col, colIndex) => {
|
|
1108
1215
|
return openBlock(), createElementBlock("td", {
|
|
1109
1216
|
key: col.key,
|
|
1110
1217
|
"data-column-key": col.key,
|
|
1111
1218
|
class: normalizeClass([
|
|
1112
1219
|
cellClass.value,
|
|
1113
1220
|
cellAlign(col),
|
|
1114
|
-
...pinnedCellClass(col, colIndex)
|
|
1221
|
+
...unref(pinnedCellClass)(col, colIndex)
|
|
1115
1222
|
]),
|
|
1116
1223
|
style: normalizeStyle({
|
|
1117
|
-
...columnWidthStyle(widthOf(col)),
|
|
1118
|
-
...pinnedStyleOf(col)
|
|
1224
|
+
...columnWidthStyle(unref(widthOf)(col)),
|
|
1225
|
+
...unref(pinnedStyleOf)(col)
|
|
1119
1226
|
})
|
|
1120
1227
|
}, [renderSlot(_ctx.$slots, `summary-${col.key}`, {
|
|
1121
1228
|
value: summaryValue(col.key),
|
|
1122
1229
|
column: col
|
|
1123
1230
|
}, () => [createElementVNode("span", null, toDisplayString(summaryValue(col.key)), 1)])], 14, _hoisted_21);
|
|
1124
1231
|
}), 128))], 10, _hoisted_20)) : createCommentVNode("", true), renderSlot(_ctx.$slots, "footer", {
|
|
1125
|
-
columns: orderedColumns
|
|
1232
|
+
columns: unref(orderedColumns),
|
|
1126
1233
|
totalColumns: totalColumns.value
|
|
1127
1234
|
})]),
|
|
1128
1235
|
key: "0"
|