vueless 0.0.496 → 0.0.498

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.
@@ -1,269 +1,4 @@
1
- <template>
2
- <div :data-test="dataTest" v-bind="wrapperAttrs">
3
- <div
4
- v-show="isHeaderSticky || isShownActionsHeader"
5
- ref="sticky-header-row"
6
- :style="tableRowWidthStyle"
7
- v-bind="stickyHeaderAttrs"
8
- >
9
- <template v-if="isShownActionsHeader">
10
- <div v-bind="stickyHeaderCellAttrs">
11
- <UCheckbox
12
- v-if="selectable"
13
- v-model="selectAll"
14
- :partial="!isSelectedAllRows"
15
- :data-test="`${dataTest}-select-all`"
16
- v-bind="stickyHeaderActionsCheckboxAttrs"
17
- />
18
- </div>
19
-
20
- <div
21
- v-if="selectedRows.length"
22
- v-bind="stickyHeaderActionsCounterAttrs"
23
- v-text="selectedRows.length"
24
- />
25
-
26
- <!--
27
- @slot Use it to add action buttons within the actions header, which appear when rows are selected.
28
- @binding {array} selected-rows
29
- -->
30
- <slot name="header-actions" :selected-rows="selectedRows" />
31
- </template>
32
-
33
- <template v-else>
34
- <div v-bind="stickyHeaderCellAttrs">
35
- <UCheckbox
36
- v-if="selectable"
37
- v-model="selectAll"
38
- :partial="!isSelectedAllRows"
39
- :data-test="`${dataTest}-select-all`"
40
- v-bind="stickyHeaderCheckboxAttrs"
41
- />
42
-
43
- <div
44
- v-if="selectedRows.length"
45
- v-bind="stickyHeaderCounterAttrs"
46
- v-text="selectedRows.length"
47
- />
48
- </div>
49
-
50
- <div
51
- v-for="(column, index) in columns"
52
- :key="index"
53
- v-bind="stickyHeaderCellAttrs"
54
- :class="cx([stickyHeaderCellAttrs.class, column.thClass])"
55
- >
56
- <template v-if="hasSlotContent($slots[`header-${column.key}`])">
57
- <!--
58
- @slot Use it to customise needed header cell.
59
- @binding {object} column
60
- @binding {number} index
61
- -->
62
- <slot :name="`header-${column.key}`" :column="column" :index="index" />
63
- </template>
64
-
65
- <template v-else>
66
- {{ column.label }}
67
- </template>
68
-
69
- <!--
70
- @slot Use it to add something after the needed header cell.
71
- @binding {object} column
72
- @binding {number} index
73
- -->
74
- <slot :name="`header-${column.key}-after`" :column="column" :index="index" />
75
- </div>
76
- </template>
77
-
78
- <ULoaderProgress v-if="isHeaderSticky" :loading="loading" v-bind="stickyHeaderLoaderAttrs" />
79
- </div>
80
-
81
- <div ref="table-wrapper" v-bind="tableWrapperAttrs">
82
- <table v-bind="tableAttrs">
83
- <thead v-bind="headerAttrs" :style="tableRowWidthStyle">
84
- <tr v-if="hasSlotContent($slots['before-header'])" v-bind="headerRowAttrs">
85
- <td
86
- v-if="hasSlotContent($slots['before-header'])"
87
- :colspan="colsCount"
88
- v-bind="headerCellBaseAttrs"
89
- >
90
- <!--
91
- @slot Use it to add something before header row.
92
- @binding {number} cols-count
93
- -->
94
- <slot name="before-header" :cols-count="colsCount" />
95
- </td>
96
- </tr>
97
-
98
- <tr v-if="hasSlotContent($slots['before-header'])" v-bind="headerRowAttrs"></tr>
99
-
100
- <tr ref="header-row" v-bind="headerRowAttrs">
101
- <th v-if="selectable" v-bind="headerCellCheckboxAttrs">
102
- <UCheckbox
103
- v-model="selectAll"
104
- :partial="!isSelectedAllRows"
105
- :data-test="`${dataTest}-select-all`"
106
- v-bind="headerCheckboxAttrs"
107
- />
108
-
109
- <div
110
- v-if="selectedRows.length"
111
- v-bind="headerCounterAttrs"
112
- v-text="selectedRows.length"
113
- />
114
- </th>
115
-
116
- <th
117
- v-for="(column, index) in visibleColumns"
118
- :key="index"
119
- v-bind="headerCellBaseAttrs"
120
- :class="cx([headerCellBaseAttrs.class, column.thClass])"
121
- >
122
- <!--
123
- @slot Use it to customise needed header cell.
124
- @binding {object} column
125
- @binding {number} index
126
- -->
127
- <slot
128
- v-if="hasSlotContent($slots[`header-${column.key}`])"
129
- :name="`header-${column.key}`"
130
- :column="column"
131
- :index="index"
132
- />
133
-
134
- <template v-else>
135
- {{ column.label }}
136
- </template>
137
-
138
- <!--
139
- @slot Use it to add something after the needed header cell.
140
- @binding {object} column
141
- @binding {number} index
142
- -->
143
- <slot :name="`header-${column.key}-after`" :column="column" :index="index" />
144
- </th>
145
- </tr>
146
-
147
- <ULoaderProgress :loading="loading" v-bind="headerLoaderAttrs" />
148
- </thead>
149
-
150
- <tbody v-if="tableRows.length" v-bind="bodyAttrs">
151
- <template v-for="(row, rowIndex) in sortedRows" :key="row.id">
152
- <tr
153
- v-if="rowIndex === firstRow && hasSlotContent($slots['before-first-row'])"
154
- v-bind="bodyRowBeforeAttrs"
155
- >
156
- <td :colspan="colsCount" v-bind="bodyRowBeforeCellAttrs">
157
- <!-- @slot Use it to add something before first row. -->
158
- <slot name="before-first-row" />
159
- </td>
160
- </tr>
161
-
162
- <tr v-if="isShownDateDivider(rowIndex) && row.rowDate" v-bind="bodyRowDateDividerAttrs">
163
- <td v-bind="bodyCellDateDividerAttrs" :colspan="colsCount">
164
- <UDivider
165
- size="xs"
166
- :label="getDateDividerLabel(row.rowDate)"
167
- v-bind="bodyDateDividerAttrs"
168
- />
169
- </td>
170
- </tr>
171
-
172
- <UTableRow
173
- v-model:selected-rows="selectedRows"
174
- :selectable="selectable"
175
- :data-test="`${dataTest}-row`"
176
- :row="row"
177
- :columns="columns"
178
- :config="config"
179
- :attrs="keysAttrs"
180
- :empty-cell-label="emptyCellLabel"
181
- @click="onClickRow"
182
- @click-cell="onClickCell"
183
- @toggle-row-visibility="onToggleRowVisibility"
184
- >
185
- <template
186
- v-for="(value, key, index) in getFilteredRow(row, columns)"
187
- :key="index"
188
- #[`cell-${key}`]="slotValues"
189
- >
190
- <!--
191
- @slot Use it to customise needed table cell.
192
- @binding {string} value
193
- @binding {object} row
194
- @binding {number} index
195
- -->
196
- <slot
197
- :name="`cell-${key}`"
198
- :value="slotValues.value"
199
- :row="slotValues.row"
200
- :index="index"
201
- />
202
- </template>
203
- <template #nested-content>
204
- <!--
205
- @slot Use it to add nested content inside a row.
206
- @binding {object} row
207
- -->
208
- <slot v-if="row" name="nested-content" :row="row" />
209
- </template>
210
- </UTableRow>
211
-
212
- <tr
213
- v-if="rowIndex === lastRow && hasSlotContent($slots['after-last-row'])"
214
- v-bind="bodyRowAfterAttrs"
215
- >
216
- <td :colspan="colsCount" v-bind="bodyRowAfterCellAttrs">
217
- <!-- @slot Use it to add something after last row. -->
218
- <slot name="after-last-row" />
219
- </td>
220
- </tr>
221
- </template>
222
- </tbody>
223
-
224
- <tbody v-else>
225
- <tr>
226
- <td :colspan="colsCount">
227
- <!-- @slot Use it to add custom empty state. -->
228
- <slot name="empty-state">
229
- <UEmpty
230
- size="md"
231
- :description="currentLocale.noData"
232
- :data-test="`${dataTest}-empty`"
233
- v-bind="bodyEmptyStateAttrs"
234
- />
235
- </slot>
236
- </td>
237
- </tr>
238
- </tbody>
239
-
240
- <tfoot v-if="hasSlotContent($slots['footer'])" v-bind="footerAttrs">
241
- <tr ref="footer-row" v-bind="footerRowAttrs">
242
- <td v-if="selectable" />
243
-
244
- <!--
245
- @slot Use it to add something into the table footer.
246
- @binding {number} cols-count
247
- -->
248
- <slot name="footer" :cols-count="colsCount" />
249
- </tr>
250
-
251
- <tr ref="sticky-footer-row" :style="tableRowWidthStyle" v-bind="stickyFooterRowAttrs">
252
- <td v-if="selectable" />
253
-
254
- <!--
255
- @slot Use it to add something into the table footer.
256
- @binding {number} cols-count
257
- -->
258
- <slot name="footer" :cols-count="colsCount" />
259
- </tr>
260
- </tfoot>
261
- </table>
262
- </div>
263
- </div>
264
- </template>
265
-
266
- <script setup>
1
+ <script setup lang="ts">
267
2
  import {
268
3
  ref,
269
4
  computed,
@@ -286,112 +21,37 @@ import UTableRow from "./UTableRow.vue";
286
21
 
287
22
  import { getDefault, cx } from "../utils/ui.ts";
288
23
 
289
- import defaultConfig from "./config.js";
24
+ import defaultConfig from "./config.ts";
290
25
  import {
291
26
  normalizeColumns,
292
- getFilteredRow,
27
+ mapRowColumns,
293
28
  syncRowCheck,
294
29
  toggleRowVisibility,
295
30
  switchRowCheck,
296
31
  getFlatRows,
297
32
  addRowId,
298
- } from "./utilTable.js";
33
+ } from "./utilTable.ts";
299
34
 
300
35
  import { PX_IN_REM } from "../constants.js";
301
- import { UTable } from "./constants.js";
302
- import useAttrs from "./useAttrs.js";
36
+ import { UTable } from "./constants.ts";
37
+ import useAttrs from "./useAttrs.ts";
303
38
  import { useLocale } from "../composables/useLocale.ts";
304
39
 
305
- defineOptions({ inheritAttrs: false });
40
+ import type { Cell, Row, RowId, UTableProps, UTableRowAttrs } from "./types.ts";
41
+ import type { Ref, RendererElement, ComputedRef } from "vue";
306
42
 
307
- const props = defineProps({
308
- /**
309
- * Table columns (headers).
310
- */
311
- columns: {
312
- type: Array,
313
- required: true,
314
- },
315
-
316
- /**
317
- * Table rows data.
318
- */
319
- rows: {
320
- type: Array,
321
- required: true,
322
- },
323
-
324
- /**
325
- * Label to display for empty cell values.
326
- */
327
- emptyCellLabel: {
328
- type: String,
329
- default: getDefault(defaultConfig, UTable).emptyCellLabel,
330
- },
331
-
332
- /**
333
- * Show date divider line between dates.
334
- */
335
- dateDivider: {
336
- type: [Boolean, Array],
337
- default: getDefault(defaultConfig, UTable).dateDivider,
338
- },
339
-
340
- /**
341
- * Allow rows selecting.
342
- */
343
- selectable: {
344
- type: Boolean,
345
- default: getDefault(defaultConfig, UTable).selectable,
346
- },
347
-
348
- /**
349
- * Makes the table compact (fewer spacings).
350
- */
351
- compact: {
352
- type: Boolean,
353
- default: getDefault(defaultConfig, UTable).compact,
354
- },
355
-
356
- /**
357
- * Set header sticky.
358
- */
359
- stickyHeader: {
360
- type: Boolean,
361
- default: getDefault(defaultConfig, UTable).stickyHeader,
362
- },
363
-
364
- /**
365
- * Set footer sticky.
366
- */
367
- stickyFooter: {
368
- type: Boolean,
369
- default: getDefault(defaultConfig, UTable).stickyFooter,
370
- },
371
-
372
- /**
373
- * Set table loader state.
374
- */
375
- loading: {
376
- type: Boolean,
377
- default: getDefault(defaultConfig, UTable).loading,
378
- },
379
-
380
- /**
381
- * Component config object.
382
- */
383
- config: {
384
- type: Object,
385
- default: () => ({}),
386
- },
43
+ defineOptions({ inheritAttrs: false });
387
44
 
388
- /**
389
- * Data-test attribute for automated testing.
390
- */
391
- dataTest: {
392
- type: String,
393
- default: "",
394
- },
45
+ const props = withDefaults(defineProps<UTableProps>(), {
46
+ emptyCellLabel: getDefault<UTableProps>(defaultConfig, UTable).emptyCellLabel,
47
+ dateDivider: () => getDefault<UTableProps>(defaultConfig, UTable).dateDivider || false,
48
+ selectable: getDefault<UTableProps>(defaultConfig, UTable).selectable,
49
+ compact: getDefault<UTableProps>(defaultConfig, UTable).compact,
50
+ stickyHeader: getDefault<UTableProps>(defaultConfig, UTable).stickyHeader,
51
+ stickyFooter: getDefault<UTableProps>(defaultConfig, UTable).stickyFooter,
52
+ loading: getDefault<UTableProps>(defaultConfig, UTable).loading,
53
+ dataTest: "",
54
+ config: () => ({}),
395
55
  });
396
56
 
397
57
  const emit = defineEmits([
@@ -419,29 +79,31 @@ const { tm } = useLocale();
419
79
 
420
80
  const selectAll = ref(false);
421
81
  const canSelectAll = ref(true);
422
- const selectedRows = ref([]);
423
- const tableRows = ref([]);
82
+ const selectedRows: Ref<RowId[]> = ref([]);
83
+ const tableRows: Ref<Row[]> = ref([]);
424
84
  const firstRow = ref(0);
425
85
  const tableWidth = ref(0);
426
86
  const tableHeight = ref(0);
427
87
  const pagePositionY = ref(0);
428
88
 
429
- const headerRowRef = useTemplateRef("header-row");
430
- const footerRowRef = useTemplateRef("footer-row");
431
- const tableWrapperRef = useTemplateRef("table-wrapper");
432
- const stickyFooterRowRef = useTemplateRef("sticky-footer-row");
433
- const stickyHeaderRowRef = useTemplateRef("sticky-header-row");
89
+ const headerRowRef = useTemplateRef<HTMLTableRowElement>("header-row");
90
+ const footerRowRef = useTemplateRef<HTMLTableRowElement>("footer-row");
91
+ const tableWrapperRef = useTemplateRef<HTMLDivElement>("table-wrapper");
92
+ const stickyFooterRowRef = useTemplateRef<HTMLTableRowElement>("sticky-footer-row");
93
+ const stickyHeaderRowRef = useTemplateRef<HTMLDivElement>("sticky-header-row");
434
94
 
435
95
  const i18nGlobal = tm(UTable);
436
96
  const currentLocale = computed(() => merge(defaultConfig.i18n, i18nGlobal, props.config.i18n));
437
97
 
438
- const sortedRows = computed(() => {
439
- const headerKeys = props.columns.map((column) => column.key);
98
+ const sortedRows: ComputedRef<Row[]> = computed(() => {
99
+ const headerKeys = props.columns.map((column) =>
100
+ typeof column === "object" ? column.key : column,
101
+ );
440
102
 
441
103
  return tableRows.value.map((row) => {
442
104
  const rowEntries = Object.entries(row);
443
105
 
444
- const sortedEntries = new Array(rowEntries.length);
106
+ const sortedEntries: typeof rowEntries = new Array(rowEntries.length);
445
107
 
446
108
  rowEntries.forEach((entry) => {
447
109
  const [key] = entry;
@@ -458,7 +120,7 @@ const sortedRows = computed(() => {
458
120
 
459
121
  const sortedRow = Object.fromEntries(sortedEntries.filter((value) => value));
460
122
 
461
- return sortedRow;
123
+ return sortedRow as Row;
462
124
  });
463
125
  });
464
126
 
@@ -477,7 +139,7 @@ const visibleColumns = computed(() => {
477
139
  });
478
140
 
479
141
  const colsCount = computed(() => {
480
- return props.columns.length + 1;
142
+ return normalizedColumns.value.length + 1;
481
143
  });
482
144
 
483
145
  const lastRow = computed(() => {
@@ -490,14 +152,15 @@ const isShownActionsHeader = computed(
490
152
 
491
153
  const isHeaderSticky = computed(() => {
492
154
  const positionForFixHeader =
493
- headerRowRef.value?.getBoundingClientRect().top + window.scrollY || 0;
155
+ Number(headerRowRef.value?.getBoundingClientRect()?.top) + window.scrollY || 0;
494
156
 
495
157
  return positionForFixHeader <= pagePositionY.value && props.stickyHeader;
496
158
  });
497
159
 
498
160
  const isShownFooterPosition = computed(() => {
499
161
  const pageBottom = pagePositionY.value + window.innerHeight;
500
- const positionForFixFooter = footerRowRef.value?.getBoundingClientRect().bottom + window.scrollY;
162
+ const positionForFixFooter =
163
+ Number(footerRowRef.value?.getBoundingClientRect()?.bottom) + window.scrollY;
501
164
 
502
165
  return pageBottom >= positionForFixFooter;
503
166
  });
@@ -509,9 +172,16 @@ const isCheckedMoreOneTableItems = computed(() => {
509
172
  const tableRowWidthStyle = computed(() => ({ width: `${tableWidth.value / PX_IN_REM}rem` }));
510
173
 
511
174
  const hasSlotContentBeforeFirstRow = computed(() => {
512
- return hasSlotContent(slots["before-first-row"])
513
- ? slots["before-first-row"]()?.some((item) => !!item.type?.render)
514
- : false;
175
+ if (
176
+ hasSlotContent(slots["before-first-row"]) &&
177
+ typeof slots["before-first-row"] === "function"
178
+ ) {
179
+ return slots["before-first-row"]()?.some((item) =>
180
+ Boolean((item.type as RendererElement)?.render),
181
+ );
182
+ }
183
+
184
+ return false;
515
185
  });
516
186
 
517
187
  const isSelectedAllRows = computed(() => {
@@ -522,7 +192,6 @@ const isSelectedAllRows = computed(() => {
522
192
 
523
193
  const {
524
194
  config,
525
- keysAttrs,
526
195
  wrapperAttrs,
527
196
  stickyHeaderCellAttrs,
528
197
  stickyHeaderAttrs,
@@ -553,6 +222,16 @@ const {
553
222
  stickyFooterRowAttrs,
554
223
  hasSlotContent,
555
224
  headerAttrs,
225
+ bodyCellContentAttrs,
226
+ bodyCellCheckboxAttrs,
227
+ bodyCheckboxAttrs,
228
+ bodyCellNestedAttrs,
229
+ bodyCellNestedExpandIconAttrs,
230
+ bodyCellNestedCollapseIconAttrs,
231
+ bodyCellBaseAttrs,
232
+ bodyCellNestedExpandIconWrapperAttrs,
233
+ bodyRowCheckedAttrs,
234
+ bodyRowAttrs,
556
235
  } = useAttrs(props, {
557
236
  tableRows,
558
237
  isShownActionsHeader,
@@ -560,6 +239,19 @@ const {
560
239
  isFooterSticky,
561
240
  });
562
241
 
242
+ const tableRowAttrs = computed(() => ({
243
+ bodyCellContentAttrs,
244
+ bodyCellCheckboxAttrs,
245
+ bodyCheckboxAttrs,
246
+ bodyCellNestedAttrs,
247
+ bodyCellNestedExpandIconAttrs,
248
+ bodyCellNestedCollapseIconAttrs,
249
+ bodyCellBaseAttrs,
250
+ bodyCellNestedExpandIconWrapperAttrs,
251
+ bodyRowCheckedAttrs,
252
+ bodyRowAttrs,
253
+ }));
254
+
563
255
  watch(selectAll, onChangeSelectAll, { deep: true });
564
256
  watch(selectedRows, onChangeSelectedRows, { deep: true });
565
257
  watch(tableRows, () => emit("update:rows", toValue(tableRows)), { deep: true });
@@ -588,8 +280,8 @@ onMounted(() => {
588
280
  });
589
281
 
590
282
  onUpdated(() => {
591
- tableHeight.value = tableWrapperRef.value?.offsetHeight;
592
- tableWidth.value = tableWrapperRef.value?.offsetWidth;
283
+ tableHeight.value = Number(tableWrapperRef.value?.offsetHeight);
284
+ tableWidth.value = Number(tableWrapperRef.value?.offsetWidth);
593
285
  });
594
286
 
595
287
  onBeforeUnmount(() => {
@@ -605,31 +297,31 @@ function onWindowResize() {
605
297
  setFooterCellWidth();
606
298
  }
607
299
 
608
- function getDateDividerLabel(rowDate) {
300
+ function getDateDividerLabel(rowDate: string | Date) {
609
301
  return Array.isArray(props.dateDivider)
610
- ? props.dateDivider.find((dateItem) => dateItem.date === rowDate)?.label || rowDate
611
- : rowDate;
302
+ ? props.dateDivider.find((dateItem) => dateItem.date === rowDate)?.label || String(rowDate)
303
+ : String(rowDate);
612
304
  }
613
305
 
614
- function setFooterCellWidth(width) {
306
+ function setFooterCellWidth(zero?: null) {
615
307
  const ZERO_WIDTH = 0;
616
308
 
617
- if (!props.stickyFooter) return;
309
+ if (!props.stickyFooter || !footerRowRef.value || !stickyFooterRowRef.value) return;
618
310
 
619
- const mainFooterItems = [...footerRowRef.value.children];
620
- const stickyFooterItems = [...stickyFooterRowRef.value.children];
311
+ const mainFooterItems = [...footerRowRef.value.children] as HTMLElement[];
312
+ const stickyFooterItems = [...stickyFooterRowRef.value.children] as HTMLElement[];
621
313
 
622
314
  stickyFooterItems.forEach((item, index) => {
623
315
  item.style.width =
624
- width === null ? `${ZERO_WIDTH}rem` : `${mainFooterItems[index].offsetWidth / PX_IN_REM}rem`;
316
+ zero === null ? `${ZERO_WIDTH}rem` : `${mainFooterItems[index].offsetWidth / PX_IN_REM}rem`;
625
317
  });
626
318
  }
627
319
 
628
320
  function setHeaderCellWidth() {
629
- if (selectedRows.value.length) return;
321
+ if (selectedRows.value.length || !footerRowRef.value || !stickyFooterRowRef.value) return;
630
322
 
631
- const mainHeaderItems = [...(headerRowRef.value?.children || [])];
632
- const stickyHeaderItems = [...(stickyHeaderRowRef.value?.children || [])];
323
+ const mainHeaderItems = [...(headerRowRef.value?.children || [])] as HTMLElement[];
324
+ const stickyHeaderItems = [...(stickyHeaderRowRef.value?.children || [])] as HTMLDivElement[];
633
325
 
634
326
  stickyHeaderItems.forEach((item, index) => {
635
327
  item.style.width = `${mainHeaderItems[index]?.offsetWidth / PX_IN_REM}rem`;
@@ -640,91 +332,356 @@ function onScroll() {
640
332
  pagePositionY.value = window.scrollY;
641
333
  }
642
334
 
643
- function synchronizeTableItemsWithProps() {
644
- if (!props.rows.length || props.rows.length !== tableRows.value.length) {
645
- selectedRows.value = [];
646
- }
335
+ function synchronizeTableItemsWithProps() {
336
+ if (!props.rows.length || props.rows.length !== tableRows.value.length) {
337
+ selectedRows.value = [];
338
+ }
339
+
340
+ tableRows.value = props.rows;
341
+ }
342
+
343
+ function updateSelectedRows() {
344
+ selectedRows.value = tableRows.value.filter((row) => row.isChecked).map((row) => row.id);
345
+ }
346
+
347
+ function onKeyupEsc(event: KeyboardEvent) {
348
+ if (event.code === "Escape" && props.selectable) {
349
+ selectedRows.value = [];
350
+ }
351
+ }
352
+
353
+ function isShownDateDivider(rowIndex: number) {
354
+ const prevIndex = rowIndex ? rowIndex - 1 : rowIndex;
355
+ const nextIndex = rowIndex ? rowIndex + 1 : rowIndex;
356
+ const prevItem = tableRows.value[prevIndex];
357
+ const nextItem = tableRows.value[nextIndex];
358
+ const currentItem = tableRows.value[rowIndex];
359
+
360
+ if (rowIndex === 0) {
361
+ return hasSlotContentBeforeFirstRow.value;
362
+ }
363
+
364
+ const isPrevSameDate = prevItem?.rowDate === currentItem?.rowDate;
365
+ const isNextSameDate = nextItem?.rowDate === currentItem?.rowDate;
366
+
367
+ return isPrevSameDate && !isNextSameDate && props.dateDivider;
368
+ }
369
+
370
+ function onClickRow(row: Row) {
371
+ emit("clickRow", row);
372
+ }
373
+
374
+ function onClickCell(cell: Cell, row: Row) {
375
+ emit("clickCell", cell, row);
376
+ }
377
+
378
+ function onChangeSelectAll(selectAll: boolean) {
379
+ if (selectAll && canSelectAll.value) {
380
+ selectedRows.value = getFlatRows(tableRows.value).map((row) => row.id);
381
+
382
+ tableRows.value.forEach((row) => switchRowCheck(row, true));
383
+ } else if (!selectAll) {
384
+ selectedRows.value = [];
385
+
386
+ tableRows.value.forEach((row) => switchRowCheck(row, false));
387
+ }
388
+
389
+ canSelectAll.value = true;
390
+ }
391
+
392
+ function onChangeSelectedRows(selectedRows: RowId[]) {
393
+ if (selectedRows.length) {
394
+ canSelectAll.value = false;
395
+
396
+ isCheckedMoreOneTableItems.value && setFooterCellWidth();
397
+ } else {
398
+ nextTick(setHeaderCellWidth);
399
+ }
400
+
401
+ selectAll.value = !!selectedRows.length;
402
+ }
403
+
404
+ function clearSelectedItems() {
405
+ selectedRows.value = [];
406
+ }
407
+
408
+ function onToggleRowVisibility(rowId: string | number) {
409
+ // TODO: Use map instead of forEach to get rid of implicit array mutation.
410
+ tableRows.value.forEach((row) => toggleRowVisibility(row, rowId));
411
+ }
412
+
413
+ defineExpose({
414
+ /**
415
+ * Allows to clear selected rows.
416
+ * @property {Function}
417
+ */
418
+ clearSelectedItems,
419
+ });
420
+ </script>
421
+
422
+ <template>
423
+ <div :data-test="dataTest" v-bind="wrapperAttrs">
424
+ <div
425
+ v-show="isHeaderSticky || isShownActionsHeader"
426
+ ref="sticky-header-row"
427
+ :style="tableRowWidthStyle"
428
+ v-bind="stickyHeaderAttrs"
429
+ >
430
+ <template v-if="isShownActionsHeader">
431
+ <div v-bind="stickyHeaderCellAttrs">
432
+ <UCheckbox
433
+ v-if="selectable"
434
+ v-model="selectAll"
435
+ :partial="!isSelectedAllRows"
436
+ :data-test="`${dataTest}-select-all`"
437
+ v-bind="stickyHeaderActionsCheckboxAttrs"
438
+ />
439
+ </div>
440
+
441
+ <div
442
+ v-if="selectedRows.length"
443
+ v-bind="stickyHeaderActionsCounterAttrs"
444
+ v-text="selectedRows.length"
445
+ />
446
+
447
+ <!--
448
+ @slot Use it to add action buttons within the actions header, which appear when rows are selected.
449
+ @binding {array} selected-rows
450
+ -->
451
+ <slot name="header-actions" :selected-rows="selectedRows" />
452
+ </template>
453
+
454
+ <template v-else>
455
+ <div v-bind="stickyHeaderCellAttrs">
456
+ <UCheckbox
457
+ v-if="selectable"
458
+ v-model="selectAll"
459
+ :partial="!isSelectedAllRows"
460
+ :data-test="`${dataTest}-select-all`"
461
+ v-bind="stickyHeaderCheckboxAttrs"
462
+ />
463
+
464
+ <div
465
+ v-if="selectedRows.length"
466
+ v-bind="stickyHeaderCounterAttrs"
467
+ v-text="selectedRows.length"
468
+ />
469
+ </div>
647
470
 
648
- tableRows.value = props.rows;
649
- }
471
+ <!-- TODO: Remove any when key attrs are typed-->
472
+ <div
473
+ v-for="(column, index) in normalizedColumns"
474
+ :key="index"
475
+ v-bind="stickyHeaderCellAttrs"
476
+ :class="cx([(stickyHeaderCellAttrs as any).class, column.thClass])"
477
+ >
478
+ <template v-if="hasSlotContent($slots[`header-${column.key}`])">
479
+ <!--
480
+ @slot Use it to customise needed header cell.
481
+ @binding {object} column
482
+ @binding {number} index
483
+ -->
484
+ <slot :name="`header-${column.key}`" :column="column" :index="index" />
485
+ </template>
650
486
 
651
- function updateSelectedRows() {
652
- selectedRows.value = tableRows.value.filter((row) => row.isChecked).map((row) => row.id);
653
- }
487
+ <template v-else>
488
+ {{ column.label }}
489
+ </template>
654
490
 
655
- function onKeyupEsc(event) {
656
- const escKeyCode = 27;
491
+ <!--
492
+ @slot Use it to add something after the needed header cell.
493
+ @binding {object} column
494
+ @binding {number} index
495
+ -->
496
+ <slot :name="`header-${column.key}-after`" :column="column" :index="index" />
497
+ </div>
498
+ </template>
657
499
 
658
- if (event.keyCode === escKeyCode && props.selectable) {
659
- selectedRows.value = [];
660
- }
661
- }
500
+ <ULoaderProgress v-if="isHeaderSticky" :loading="loading" v-bind="stickyHeaderLoaderAttrs" />
501
+ </div>
662
502
 
663
- function isShownDateDivider(rowIndex) {
664
- const prevIndex = rowIndex ? rowIndex - 1 : rowIndex;
665
- const nextIndex = rowIndex ? rowIndex + 1 : rowIndex;
666
- const prevItem = tableRows.value[prevIndex];
667
- const nextItem = tableRows.value[nextIndex];
668
- const currentItem = tableRows.value[rowIndex];
503
+ <div ref="table-wrapper" v-bind="tableWrapperAttrs">
504
+ <table v-bind="tableAttrs">
505
+ <thead v-bind="headerAttrs" :style="tableRowWidthStyle">
506
+ <tr v-if="hasSlotContent($slots['before-header'])" v-bind="headerRowAttrs">
507
+ <td
508
+ v-if="hasSlotContent($slots['before-header'])"
509
+ :colspan="colsCount"
510
+ v-bind="headerCellBaseAttrs"
511
+ >
512
+ <!--
513
+ @slot Use it to add something before header row.
514
+ @binding {number} cols-count
515
+ -->
516
+ <slot name="before-header" :cols-count="colsCount" />
517
+ </td>
518
+ </tr>
669
519
 
670
- if (rowIndex === 0) {
671
- return hasSlotContentBeforeFirstRow.value;
672
- }
520
+ <tr v-if="hasSlotContent($slots['before-header'])" v-bind="headerRowAttrs"></tr>
673
521
 
674
- const isPrevSameDate = prevItem?.rowDate === currentItem?.rowDate;
675
- const isNextSameDate = nextItem?.rowDate === currentItem?.rowDate;
522
+ <tr ref="header-row" v-bind="headerRowAttrs">
523
+ <th v-if="selectable" v-bind="headerCellCheckboxAttrs">
524
+ <UCheckbox
525
+ v-model="selectAll"
526
+ :partial="!isSelectedAllRows"
527
+ :data-test="`${dataTest}-select-all`"
528
+ v-bind="headerCheckboxAttrs"
529
+ />
676
530
 
677
- return isPrevSameDate && !isNextSameDate && props.dateDivider;
678
- }
531
+ <div
532
+ v-if="selectedRows.length"
533
+ v-bind="headerCounterAttrs"
534
+ v-text="selectedRows.length"
535
+ />
536
+ </th>
679
537
 
680
- function onClickRow(row) {
681
- emit("clickRow", row);
682
- }
538
+ <th
539
+ v-for="(column, index) in visibleColumns"
540
+ :key="index"
541
+ v-bind="headerCellBaseAttrs"
542
+ :class="cx([(headerCellBaseAttrs as any).class, column.thClass])"
543
+ >
544
+ <!--
545
+ @slot Use it to customise needed header cell.
546
+ @binding {object} column
547
+ @binding {number} index
548
+ -->
549
+ <slot
550
+ v-if="hasSlotContent($slots[`header-${column.key}`])"
551
+ :name="`header-${column.key}`"
552
+ :column="column"
553
+ :index="index"
554
+ />
683
555
 
684
- function onClickCell(cell, row) {
685
- emit("clickCell", cell, row);
686
- }
556
+ <template v-else>
557
+ {{ column.label }}
558
+ </template>
687
559
 
688
- function onChangeSelectAll(selectAll) {
689
- if (selectAll && canSelectAll.value) {
690
- selectedRows.value = getFlatRows(tableRows.value).map((row) => row.id);
560
+ <!--
561
+ @slot Use it to add something after the needed header cell.
562
+ @binding {object} column
563
+ @binding {number} index
564
+ -->
565
+ <slot :name="`header-${column.key}-after`" :column="column" :index="index" />
566
+ </th>
567
+ </tr>
691
568
 
692
- tableRows.value.forEach((row) => switchRowCheck(row, true));
693
- } else if (!selectAll) {
694
- selectedRows.value = [];
569
+ <ULoaderProgress :loading="loading" v-bind="headerLoaderAttrs" />
570
+ </thead>
695
571
 
696
- tableRows.value.forEach((row) => switchRowCheck(row, false));
697
- }
572
+ <tbody v-if="tableRows.length" v-bind="bodyAttrs">
573
+ <template v-for="(row, rowIndex) in sortedRows" :key="row.id">
574
+ <tr
575
+ v-if="rowIndex === firstRow && hasSlotContent($slots['before-first-row'])"
576
+ v-bind="bodyRowBeforeAttrs"
577
+ >
578
+ <td :colspan="colsCount" v-bind="bodyRowBeforeCellAttrs">
579
+ <!-- @slot Use it to add something before first row. -->
580
+ <slot name="before-first-row" />
581
+ </td>
582
+ </tr>
698
583
 
699
- canSelectAll.value = true;
700
- }
584
+ <tr v-if="isShownDateDivider(rowIndex) && row.rowDate" v-bind="bodyRowDateDividerAttrs">
585
+ <td v-bind="bodyCellDateDividerAttrs" :colspan="colsCount">
586
+ <UDivider
587
+ size="xs"
588
+ :label="getDateDividerLabel(row.rowDate)"
589
+ v-bind="bodyDateDividerAttrs"
590
+ />
591
+ </td>
592
+ </tr>
701
593
 
702
- function onChangeSelectedRows(selectedRows) {
703
- if (selectedRows.length) {
704
- canSelectAll.value = false;
594
+ <UTableRow
595
+ v-model:selected-rows="selectedRows"
596
+ :selectable="selectable"
597
+ :data-test="`${dataTest}-row`"
598
+ :row="row"
599
+ :columns="normalizedColumns"
600
+ :config="config"
601
+ :attrs="tableRowAttrs as unknown as UTableRowAttrs"
602
+ :nested-level="0"
603
+ :empty-cell-label="emptyCellLabel"
604
+ @click="onClickRow"
605
+ @click-cell="onClickCell"
606
+ @toggle-row-visibility="onToggleRowVisibility"
607
+ >
608
+ <template
609
+ v-for="(value, key, index) in mapRowColumns(row, normalizedColumns)"
610
+ :key="index"
611
+ #[`cell-${key}`]="slotValues"
612
+ >
613
+ <!--
614
+ @slot Use it to customise needed table cell.
615
+ @binding {string} value
616
+ @binding {object} row
617
+ @binding {number} index
618
+ -->
619
+ <slot
620
+ :name="`cell-${key}`"
621
+ :value="slotValues.value"
622
+ :row="slotValues.row"
623
+ :index="index"
624
+ />
625
+ </template>
626
+ <template #nested-content>
627
+ <!--
628
+ @slot Use it to add nested content inside a row.
629
+ @binding {object} row
630
+ -->
631
+ <slot v-if="row" name="nested-content" :row="row" />
632
+ </template>
633
+ </UTableRow>
705
634
 
706
- isCheckedMoreOneTableItems.value && setFooterCellWidth();
707
- } else {
708
- nextTick(setHeaderCellWidth);
709
- }
635
+ <tr
636
+ v-if="rowIndex === lastRow && hasSlotContent($slots['after-last-row'])"
637
+ v-bind="bodyRowAfterAttrs"
638
+ >
639
+ <td :colspan="colsCount" v-bind="bodyRowAfterCellAttrs">
640
+ <!-- @slot Use it to add something after last row. -->
641
+ <slot name="after-last-row" />
642
+ </td>
643
+ </tr>
644
+ </template>
645
+ </tbody>
710
646
 
711
- selectAll.value = !!selectedRows.length;
712
- }
647
+ <tbody v-else>
648
+ <tr>
649
+ <td :colspan="colsCount">
650
+ <!-- @slot Use it to add custom empty state. -->
651
+ <slot name="empty-state">
652
+ <UEmpty
653
+ size="md"
654
+ :description="currentLocale.noData"
655
+ :data-test="`${dataTest}-empty`"
656
+ v-bind="bodyEmptyStateAttrs"
657
+ />
658
+ </slot>
659
+ </td>
660
+ </tr>
661
+ </tbody>
713
662
 
714
- function clearSelectedItems() {
715
- selectedRows.value = [];
716
- }
663
+ <tfoot v-if="hasSlotContent($slots['footer'])" v-bind="footerAttrs">
664
+ <tr ref="footer-row" v-bind="footerRowAttrs">
665
+ <td v-if="selectable" />
717
666
 
718
- function onToggleRowVisibility(rowId) {
719
- // TODO: Use map instead of forEach to get rid of implicit array mutation.
720
- tableRows.value.forEach((row) => toggleRowVisibility(row, rowId));
721
- }
667
+ <!--
668
+ @slot Use it to add something into the table footer.
669
+ @binding {number} cols-count
670
+ -->
671
+ <slot name="footer" :cols-count="colsCount" />
672
+ </tr>
722
673
 
723
- defineExpose({
724
- /**
725
- * Allows to clear selected rows.
726
- * @property {Function}
727
- */
728
- clearSelectedItems,
729
- });
730
- </script>
674
+ <tr ref="sticky-footer-row" :style="tableRowWidthStyle" v-bind="stickyFooterRowAttrs">
675
+ <td v-if="selectable" />
676
+
677
+ <!--
678
+ @slot Use it to add something into the table footer.
679
+ @binding {number} cols-count
680
+ -->
681
+ <slot name="footer" :cols-count="colsCount" />
682
+ </tr>
683
+ </tfoot>
684
+ </table>
685
+ </div>
686
+ </div>
687
+ </template>