bfg-common 1.2.211 → 1.2.213

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,1491 +1,1518 @@
1
- <template>
2
- <div class="relative" style="display: flex">
3
- <div
4
- :id="outerWrapper"
5
- :class="[
6
- 'datagrid-outer-wrapper',
7
- {
8
- 'datagrid-outer-wrapper-opened-info': isShowHiddenInfo,
9
- },
10
- ]"
11
- >
12
- <div class="datagrid-inner-wrapper">
13
- <div
14
- :class="[
15
- 'datagrid',
16
- {
17
- 'datagrid-opened-info': isShowHiddenInfo,
18
- },
19
- ]"
20
- >
21
- <div :id="tableWrapperId" class="datagrid-table-wrapper">
22
- <div class="datagrid-table">
23
- <div class="datagrid-header">
24
- <div class="datagrid-row">
25
- <div class="datagrid-row-master datagrid-row-flex">
26
- <div v-if="props.type" class="datagrid-row-sticky">
27
- <div
28
- :id="expandableCaret"
29
- class="datagrid-column datagrid-expandable-caret datagrid-fixed-column flex-align-center flex-justify-center"
30
- >
31
- <template v-if="props.type === 'checkbox'">
32
- <div
33
- class="clr-checkbox-wrapper flex-align-center flex-justify-center w-100"
34
- >
35
- <input
36
- :id="`${props.testId}-${inputId}-all`"
37
- v-model="selectedAll"
38
- type="checkbox"
39
- class="checkbox-btn"
40
- :value="-1"
41
- @change="changeAll"
42
- />
43
- <label
44
- :for="`${props.testId}-${inputId}-all`"
45
- class="clr-control-label"
46
- />
47
- </div>
48
- <div class="datagrid-column-separator"></div>
49
- </template>
50
- </div>
51
- <div
52
- v-if="$slots.toggleBlock"
53
- class="datagrid-toggle-block datagrid-column datagrid-fixed-width"
54
- >
55
- <div class="datagrid-column-flex">
56
- <div class="datagrid-column-separator">
57
- <span class="clr-sr-only" />
58
- <div class="datagrid-column-resize-tracker" />
59
- </div>
60
- </div>
61
- </div>
62
- </div>
63
- <div class="datagrid-row-scrollable">
64
- <div
65
- v-for="(item, key) in headItemsPresent"
66
- :key="key"
67
- :style="{
68
- width: columnsWidth[key][0],
69
- minWidth: columnsWidth[key][1],
70
- }"
71
- class="datagrid-column datagrid-fixed-width"
72
- >
73
- <div class="datagrid-column-flex">
74
- <button
75
- :id="`${props.testId}-sort-column-${key}`"
76
- :class="[
77
- 'datagrid-column-title',
78
- {
79
- 'default text-decoration-none':
80
- !item.sortColumn,
81
- },
82
- ]"
83
- @click="sortTable(item.sortColumn)"
84
- >
85
- <slot :key="key" name="th" :item="item">
86
- <span
87
- :title="item.text"
88
- :style="{
89
- width:
90
- columnsWidth[key][0]?.slice(0, -2) -
91
- (item.hasFilter ? 65 : 40) +
92
- 'px',
93
- }"
94
- class="title-column"
95
- >
96
- {{ item.text }}
97
- </span>
98
- </slot>
99
- <span
100
- v-if="item.sortColumn"
101
- class="sort-arrow-wrap"
102
- >
103
- <atoms-the-icon
104
- v-show="sortInfo[0] === item.sortColumn"
105
- :class="['sort-arrow', { down: sortInfo[1] }]"
106
- name="sort-arrow"
107
- />
108
- </span>
109
- </button>
110
- <button
111
- v-if="item.hasFilter"
112
- :id="`${props.testId}-filter-icon-${key}`"
113
- class="datagrid-filter-toggle clr-anchor clr-smart-open-close"
114
- @click="filterShow[key] = !filterShow[key]"
115
- >
116
- <atoms-the-icon
117
- v-if="filterTerm[key]"
118
- class="filter-icon"
119
- name="filter-solid"
120
- style="fill: #0079b8"
121
- />
122
- <atoms-the-icon
123
- v-else
124
- class="filter-icon"
125
- name="filter"
126
- />
127
- </button>
128
- <atoms-popup-simple-popup
129
- v-model="filterShow[key]"
130
- :test-id="`${props.testId}-filter`"
131
- top="100%"
132
- :left="key === 0 && 0"
133
- :right="key !== 0 && 0"
134
- >
135
- <input
136
- :id="`${props.testId}-filter-input-${key}`"
137
- v-model="filterTerm[key]"
138
- type="text"
139
- @input="filtering"
140
- />
141
- </atoms-popup-simple-popup>
142
- <div
143
- v-show="
144
- !isShowHiddenInfo ||
145
- (isShowHiddenInfo && key === 0)
146
- "
147
- :class="[
148
- 'datagrid-column-separator',
149
- {
150
- 'double-arrow-separator':
151
- key === 0 && props.withInfo,
152
- },
153
- ]"
154
- @mousedown="setGrab(key, $event)"
155
- >
156
- <button
157
- :id="`${props.testId}-resize-tracker-${key}`"
158
- class="datagrid-column-handle drag-handle draggable"
159
- />
160
- <span class="clr-sr-only" />
161
- <div class="datagrid-column-resize-tracker" />
162
- </div>
163
- </div>
164
- </div>
165
- </div>
166
- </div>
167
- </div>
168
- </div>
169
-
170
- <template v-if="bodyItemsPresent.length">
171
- <div
172
- v-for="(item, key) in bodyItemsPresent"
173
- :key="item.id"
174
- :class="[
175
- 'datagrid-body-row datagrid-row animation',
176
- {
177
- 'datagrid-selected active':
178
- !props.withInfo && checkIsSelectedRow(item[0].id),
179
- 'datagrid-selected-info':
180
- props.withInfo &&
181
- isShowHiddenInfo &&
182
- checkIsSelectedRow(item[0].id),
183
- },
184
- ]"
185
- :[props.rowAttributeIdName]="item[0].dataId"
186
- >
187
- <div>
188
- <div
189
- class="datagrid-row-master datagrid-row-flex"
190
- @click.prevent.stop="selectRowById(item[0].id)"
191
- >
192
- <div
193
- v-if="props.type"
194
- class="datagrid-row-sticky"
195
- @click.stop
196
- >
197
- <div
198
- :id="expandableCaret"
199
- :class="[
200
- 'datagrid-expandable-caret flex-justify-center datagrid-cell',
201
- {
202
- 'datagrid-fixed-column': !props.withInfo,
203
- 'datagrid-fixed-column-without-separator':
204
- props.withInfo,
205
- },
206
- ]"
207
- >
208
- <slot :key="key" name="type" :item="item">
209
- <div
210
- :class="`clr-${props.type}-wrapper flex-justify-center flex-align-center`"
211
- >
212
- <input
213
- :id="`${props.testId}-${inputId}-${item[0].id}`"
214
- v-model="selectedRowLocal"
215
- :key="item[0].id"
216
- :type="props.type"
217
- :class="`${props.type}-btn`"
218
- :value="item[0].id"
219
- name="selected-store"
220
- @change="changeSelectedRow($event, item[0].id)"
221
- />
222
- <label
223
- :for="`${props.testId}-${inputId}-${item[0].id}`"
224
- class="clr-control-label"
225
- />
226
- </div>
227
- </slot>
228
- </div>
229
- <div
230
- v-if="$slots.toggleBlock"
231
- class="datagrid-cell datagrid-fixed-width"
232
- >
233
- <div class="flex-align-center">
234
- <button
235
- :id="`${props.testId}-toggle-icon-${key}`"
236
- class="datagrid-expandable-caret-button reset-btn"
237
- @click="toggle(key)"
238
- >
239
- <atoms-the-icon
240
- :class="[
241
- 'datagrid-expandable-caret-icon',
242
- { toggle: toggedItems[key] },
243
- ]"
244
- name="angle"
245
- />
246
- </button>
247
- </div>
248
- </div>
249
- </div>
250
- <div class="datagrid-row-scrollable">
251
- <div class="datagrid-scrolling-cells">
252
- <div
253
- v-for="(item2, key2) in item"
254
- v-show="
255
- !isShowHiddenInfo ||
256
- item2.key === 'col1' ||
257
- item2.key === 'icon'
258
- "
259
- :key="key2"
260
- :style="{
261
- width: columnsWidth[key2][0],
262
- minWidth: columnsWidth[key2][1],
263
- }"
264
- :class="[
265
- 'datagrid-cell datagrid-fixed-width',
266
- {
267
- 'hidden-text-triangle':
268
- isShowHiddenInfo &&
269
- item2.data?.name === 'hidden-text-triangle',
270
- },
271
- ]"
272
- >
273
- <div
274
- :class="[
275
- 'flex-align-center h-100',
276
- {
277
- 'double-arrow-width':
278
- item2.data?.name === 'doubleArrows',
279
- },
280
- ]"
281
- >
282
- <div
283
- v-if="item2.data?.name === 'doubleArrows'"
284
- :id="`show-info-${item[1].text}`"
285
- :class="[
286
- 'double-arrow-wrap',
287
- {
288
- 'arrow-selected':
289
- isShowHiddenInfo &&
290
- item2.id === selectedRow,
291
- },
292
- ]"
293
- @click.prevent.stop="
294
- doubleArrowClick(item2, item)
295
- "
296
- >
297
- <atoms-the-icon
298
- v-if="item2.data?.name === 'doubleArrows'"
299
- name="doubleArrows"
300
- :class="[
301
- 'double-arrow',
302
- {
303
- 'arrow-selected':
304
- isShowHiddenInfo &&
305
- item2.id === selectedRow,
306
- },
307
- ]"
308
- />
309
- </div>
310
- <span
311
- v-if="item2.text && item2.data?.iconClassName"
312
- class="vertical-divider"
313
- />
314
- <slot :name="item2.key" :item="item2">
315
- <span class="text-ellipsis" :title="item2.text"
316
- >{{ item2.text }}
317
- </span>
318
- </slot>
319
- </div>
320
- </div>
321
- </div>
322
- <div
323
- v-show="toggedItems[key]"
324
- class="datagrid-row-flex datagrid-row-detail datagrid-container"
325
- >
326
- <slot name="toggleBlock" />
327
- </div>
328
- </div>
329
- </div>
330
- </div>
331
- </div>
332
- </template>
333
- <template v-else>
334
- <div class="datagrid-placeholder-container">
335
- <div class="datagrid-placeholder datagrid-empty">
336
- <div class="datagrid-placeholder-image"></div>
337
-
338
- <span>No items found</span>
339
- </div>
340
- </div>
341
- </template>
342
- </div>
343
- </div>
344
- </div>
345
-
346
- <div
347
- v-if="!hideFooter"
348
- :class="[
349
- 'datagrid-footer',
350
- {
351
- 'datagrid-footer-opened-info': isShowHiddenInfo,
352
- },
353
- ]"
354
- >
355
- <div
356
- v-if="props.type === 'checkbox' && props.selectedRow?.length"
357
- class="clr-form-control-disabled"
358
- >
359
- <div class="clr-checkbox-wrapper">
360
- <input
361
- :id="`${props.testId}-check-all`"
362
- type="checkbox"
363
- checked
364
- />
365
- <label class="clr-control-label">
366
- {{ props.selectedRow?.length }}
367
- </label>
368
- </div>
369
- </div>
370
- <div class="datagrid-footer-description flex-align-center">
371
- <atoms-table-data-grid-column-switch
372
- v-if="columnKeysLocal"
373
- v-model:column-keys="columnKeysLocal"
374
- :test-id="`${props.testId}-footer-description`"
375
- class="switch-icon"
376
- />
377
-
378
- <div v-if="$slots.action" class="datagrid-footer__action">
379
- <slot name="action" />
380
- </div>
381
- </div>
382
-
383
- <atoms-table-data-grid-pagination
384
- test-id="props.testId"
385
- :page-size="props.pageSize"
386
- :page="props.page"
387
- :options="mergeOptions"
388
- :total-items="props.totalItems"
389
- :total-pages="props.totalPages"
390
- :hide-pagination="props.hidePagination"
391
- :hide-page-size="props.hidePageSize"
392
- :show-page-info="props.showPageInfo"
393
- :page-items-count="props.bodyItems.length"
394
- :is-show-hidden-info="isShowHiddenInfo"
395
- @change-page-size="changePageSize"
396
- @change-page="changePage"
397
- />
398
- </div>
399
-
400
- <atoms-loader v-show="props.loading" class="datagrid-spinner" />
401
- </div>
402
- </div>
403
- <div
404
- v-if="isShowInfo"
405
- id="hidden-features"
406
- :class="[
407
- 'hidden-features',
408
- {
409
- 'show-hidden-info': isShowHiddenInfo,
410
- },
411
- ]"
412
- >
413
- <div class="header">
414
- <slot name="hiddenInfoHeader" :item="showedInfoCol" />
415
- <button
416
- :id="`${props.testId}-hidden-info-toggle-icon-${showedInfoRow?.key}-${showedInfoRow?.data.name}`"
417
- class="signpost-action close"
418
- @click="isShowHiddenInfo = false"
419
- >
420
- <atoms-the-icon class="close-icon" name="close" />
421
- </button>
422
- </div>
423
- <slot name="hiddenInfoBody" />
424
- </div>
425
- </div>
426
- </template>
427
-
428
- <script setup lang="ts">
429
- import { UI_I_HTMLSelectElement } from '~/models/interfaces'
430
- import { itemsPerPage } from '~/components/atoms/table/dataGrid/config/itemsPerPage'
431
- import {
432
- UI_I_HeadItem,
433
- UI_I_BodyItem,
434
- UI_I_ColumnKey,
435
- } from '~/components/atoms/table/dataGrid/models/interfaces'
436
- import { UI_I_OptionItem } from '~/components/atoms/models/interfaces'
437
- import {
438
- UI_T_ColumnKeys,
439
- UI_T_DataGridType,
440
- UI_T_SelectedRow,
441
- } from '~/components/atoms/table/dataGrid/models/types'
442
-
443
- const props = withDefaults(
444
- defineProps<{
445
- headItems: UI_I_HeadItem[]
446
- bodyItems: UI_I_BodyItem[][]
447
- pageSize: number
448
- page: number
449
- totalItems: number
450
- totalPages: number
451
- loading?: boolean
452
- columnKeys?: UI_I_ColumnKey[]
453
- selectedRow?: UI_T_SelectedRow
454
- type?: UI_T_DataGridType
455
- itemsPerPage?: UI_I_OptionItem[]
456
- offSelectByRow?: boolean
457
- hidePageSize?: boolean
458
- showPageInfo?: boolean
459
- serverOff?: boolean
460
- fillWidth?: boolean
461
- hideFooter?: boolean
462
- hidePagination?: boolean
463
- testId?: string
464
- withInfo?: boolean
465
- rowAttributeIdName?: string
466
- }>(),
467
- {
468
- columnKeys: undefined,
469
- selectedRow: undefined,
470
- type: undefined,
471
- itemsPerPage: undefined,
472
- fillWidth: true,
473
- testId: 'ui-data-grid',
474
- withInfo: false,
475
- rowAttributeIdName: 'data-id',
476
- }
477
- )
478
-
479
- const emits = defineEmits<{
480
- (event: 'update:column-keys', value: UI_T_ColumnKeys): void
481
- (event: 'update:selected-row', value: UI_T_SelectedRow): void
482
- (event: 'update:page-size', value: number): void
483
- (event: 'update:page', value: number): void
484
- (event: 'filtering', value: string[][]): void
485
- (event: 'sorting', value: [string, boolean]): void
486
- (event: 'change', value: any): void
487
- (event: 'row-detail', value: number): void
488
- }>()
489
-
490
- const mergeOptions = computed<UI_I_OptionItem[]>(
491
- () => props.itemsPerPage || itemsPerPage
492
- )
493
-
494
- const inputId = ref<string>(`radio-input-${useUniqueId()}`)
495
-
496
- const columnKeysLocal = computed<UI_T_ColumnKeys>({
497
- get() {
498
- return props.columnKeys
499
- },
500
- set(newValue) {
501
- emits('update:column-keys', newValue)
502
- },
503
- })
504
-
505
- const selectedAll = ref<boolean>(false)
506
- const changeAll = (e: UI_I_HTMLSelectElement): void => {
507
- const newSelectedRow: (number | string)[] = []
508
- if (e.target.checked) {
509
- bodyItemsPresent.value.forEach((row) => {
510
- newSelectedRow.push(row[0].id)
511
- })
512
- }
513
-
514
- emits('update:selected-row', newSelectedRow)
515
- }
516
-
517
- const selectedRowLocal = ref<UI_T_SelectedRow>([])
518
- const isShowHiddenInfo = ref<boolean>(false)
519
- const tableWrapperId = `table-wrapper-${useUniqueId()}`
520
- let memoryWidth = '0'
521
-
522
- const showedInfoRow = ref<UI_I_BodyItem | null>(null)
523
- const showedInfoCol = ref<UI_I_BodyItem[] | null>(null)
524
-
525
- const isShowInfo = computed(
526
- () =>
527
- showedInfoRow.value &&
528
- showedInfoRow.value.id === props.selectedRow &&
529
- !showedInfoRow.value.data?.text &&
530
- isShowHiddenInfo.value
531
- )
532
-
533
- const setShowedInfoData = (
534
- item: UI_I_BodyItem | null,
535
- items: UI_I_BodyItem[] | null,
536
- reset: boolean = false
537
- ): void => {
538
- showedInfoRow.value = item
539
- showedInfoCol.value = items
540
-
541
- reset && (isShowHiddenInfo.value = false)
542
- item && selectRowById(item.id)
543
- reset && (isShowHiddenInfo.value = true)
544
- }
545
-
546
- const doubleArrowClick = (
547
- item: UI_I_BodyItem,
548
- items: UI_I_BodyItem[]
549
- ): void => {
550
- if (!isShowHiddenInfo.value) {
551
- setShowedInfoData(item, items)
552
- } else {
553
- if (showedInfoRow.value?.id !== item.id) {
554
- setShowedInfoData(item, items, true)
555
-
556
- return
557
- } else {
558
- setShowedInfoData(null, null)
559
- }
560
- }
561
-
562
- isShowHiddenInfo.value = !isShowHiddenInfo.value
563
- }
564
-
565
- watch(
566
- () => props.selectedRow,
567
- (newValue) => {
568
- if (newValue === undefined) return
569
-
570
- if (Array.isArray(newValue) && !newValue.length) selectedAll.value = false
571
-
572
- selectedRowLocal.value = newValue
573
- },
574
- { immediate: true }
575
- )
576
- const selectRowById = (id: number | string): void => {
577
- if (props.offSelectByRow || isShowHiddenInfo.value) return
578
-
579
- if (props.type === 'checkbox' && Array.isArray(selectedRowLocal.value)) {
580
- const has = selectedRowLocal.value.includes(id)
581
- if (has) {
582
- selectedRowLocal.value = selectedRowLocal.value.filter(
583
- (rowId: string | number) => rowId !== id
584
- )
585
- } else {
586
- selectedRowLocal.value.push(id)
587
- }
588
-
589
- emits('change', selectedRowLocal.value)
590
- emits('update:selected-row', selectedRowLocal.value)
591
-
592
- changeSelectedAll(!has)
593
- return
594
- }
595
-
596
- emits('change', id)
597
- emits('update:selected-row', id)
598
- }
599
- const changeSelectedRow = (
600
- e: UI_I_HTMLSelectElement,
601
- id: number | string
602
- ): void => {
603
- if (props.type === 'checkbox' && Array.isArray(selectedRowLocal.value)) {
604
- const newSelectedRow = [...selectedRowLocal.value]
605
- emits('change', newSelectedRow)
606
- emits('update:selected-row', newSelectedRow)
607
-
608
- changeSelectedAll(e.target.checked)
609
- return
610
- }
611
-
612
- emits('change', id)
613
- emits('update:selected-row', id)
614
- }
615
- const changeSelectedAll = (checked: boolean): void => {
616
- if (props.type === 'checkbox' && Array.isArray(selectedRowLocal.value)) {
617
- const newSelectedRow = [...selectedRowLocal.value]
618
-
619
- if (!checked) {
620
- selectedAll.value = false
621
- } else if (newSelectedRow.length === bodyItemsPresent.value.length) {
622
- selectedAll.value = true
623
- }
624
- }
625
- }
626
-
627
- const checkIsSelectedRow = (id: number | string): boolean => {
628
- if (props.type === 'checkbox' && Array.isArray(props.selectedRow)) {
629
- return props.selectedRow.includes(id)
630
- }
631
- return props.selectedRow === id
632
- }
633
-
634
- // Sorting
635
- const sortInfo = ref<[string, boolean]>(['', true])
636
- const sortedColumnIndex = computed<number>(() => {
637
- let index = -1
638
- headItemsPresent.value.forEach((item, key) => {
639
- if (item.sortColumn === sortInfo.value[0]) index = key
640
- })
641
-
642
- return index
643
- })
644
- const sortTable = (sortName: string): void => {
645
- if (!sortName) return
646
-
647
- emits('update:selected-row', selectedRowLocal.value)
648
-
649
- if (sortInfo.value[0] === sortName) {
650
- sortInfo.value[1] = !sortInfo.value[1]
651
-
652
- emits('sorting', sortInfo.value)
653
- return
654
- }
655
- sortInfo.value[0] = sortName
656
- sortInfo.value[1] = true
657
-
658
- emits('sorting', sortInfo.value)
659
- }
660
-
661
- // Filtering
662
- const filterShow = ref<boolean[]>([])
663
- const filterTerm = ref<string[]>([])
664
- const filtering = (): void => {
665
- const filter: string[][] = []
666
- headItemsPresent.value.forEach((item, key) => {
667
- if (filterTerm.value[key]) filter.push([item.key, filterTerm.value[key]])
668
- })
669
-
670
- emits('filtering', filter)
671
- }
672
-
673
- // Pagination
674
- const changePageSize = (value: number): void => {
675
- emits('update:page-size', value)
676
- }
677
- const changePage = (value: number): void => {
678
- emits('update:page', value)
679
- }
680
-
681
- const headItemsPresent = computed<UI_I_HeadItem[]>(() => {
682
- if (!columnKeysLocal.value) {
683
- return props.headItems
684
- }
685
-
686
- return props.headItems.filter((item, key) => {
687
- return columnKeysLocal.value?.find(
688
- (item2, key2) => item2.key === item.key && key === key2
689
- )?.show
690
- })
691
- })
692
-
693
- const bodyItemsPresent = computed<UI_I_BodyItem[][]>(() => {
694
- let items: UI_I_BodyItem[][] = props.bodyItems
695
-
696
- // Filtering by column
697
- if (columnKeysLocal.value) {
698
- items = items.map((arr) => {
699
- arr = arr.filter((item, key) => {
700
- return columnKeysLocal.value?.find(
701
- (item2, key2) => item2.key === item.key && key === key2
702
- )?.show
703
- })
704
- return arr
705
- })
706
- }
707
-
708
- if (props.serverOff) {
709
- // Sorting
710
- if (sortInfo.value[0] !== '') {
711
- items = useDeepCopy(items).sort(
712
- (a: UI_I_BodyItem[], b: UI_I_BodyItem[]) => {
713
- const first = a[sortedColumnIndex.value].text.toString().toLowerCase()
714
- const second = b[sortedColumnIndex.value].text
715
- .toString()
716
- .toLowerCase()
717
-
718
- if (sortInfo.value[1]) {
719
- return first > second ? 1 : first < second ? -1 : 0
720
- } else {
721
- return first < second ? 1 : first > second ? -1 : 0
722
- }
723
- }
724
- )
725
- }
726
- // Filtering
727
- const hasFilter = headItemsPresent.value.some(
728
- (_, key) => filterTerm.value[key]
729
- )
730
- if (hasFilter) {
731
- items = items.filter((row) => {
732
- // 0 - начальное значение
733
- // 1 - найден элемент
734
- // -1 - ненайден элемент
735
- let hasItem = 0
736
- headItemsPresent.value.forEach((item, key) => {
737
- if (!filterTerm.value[key]) return
738
- const term = filterTerm.value[key].toLowerCase()
739
-
740
- row.forEach((col) => {
741
- if (
742
- col.key === item.key &&
743
- typeof col.text === 'string' &&
744
- hasItem != -1
745
- ) {
746
- hasItem = col.text.toLowerCase().includes(term) ? 1 : -1
747
- }
748
- })
749
- })
750
-
751
- return hasItem === 1
752
- })
753
- }
754
- }
755
-
756
- return items
757
- })
758
-
759
- const toggedItems = ref<boolean[]>([])
760
-
761
- const toggle = (key: number): void => {
762
- toggedItems.value[key] = !toggedItems.value[key]
763
- if (!toggedItems.value[key]) return
764
- emits('row-detail', key)
765
- }
766
-
767
- const outerWrapper = `datagrid-outer-wrapper${useUniqueId()}`
768
- const expandableCaret = `datagrid-expandable-caret${useUniqueId()}`
769
-
770
- // Resize
771
- let helper = false
772
- let setColumnWidth = (): void => {
773
- if (!helper) {
774
- columnsWidth.value = []
775
- headItemsPresent.value.forEach((item) => {
776
- columnsWidth.value.push([
777
- item.width, // width
778
- item.width, // min-width
779
- ])
780
- })
781
-
782
- helper = true
783
- setTimeout(() => setColumnWidth(), 0)
784
- return
785
- }
786
- // Не продолжаем, чтобы ширина калонок была так как заданно в headItemsPresent
787
- if (!props.fillWidth) return
788
-
789
- const dataGridWidth = document.getElementById(outerWrapper)?.clientWidth || 0
790
- const leftColumnWidth =
791
- document.getElementById(expandableCaret)?.clientWidth || 0
792
- const bordersWidth = 2
793
- const columnWidth =
794
- (dataGridWidth - leftColumnWidth - bordersWidth) /
795
- headItemsPresent.value.length
796
-
797
- headItemsPresent.value.forEach((item) => {
798
- columnsWidth.value.push([
799
- `${columnWidth}px`, // width
800
- item.width, // min-width
801
- ])
802
- })
803
- }
804
- setColumnWidth = useThrottle(setColumnWidth)
805
-
806
- const columnsWidth = ref<[string, string][]>([])
807
- watch(
808
- bodyItemsPresent,
809
- (newValue: UI_I_BodyItem[][]) => {
810
- newValue.forEach(() => {
811
- toggedItems.value.push(false)
812
- })
813
- setColumnWidth()
814
- },
815
- {
816
- immediate: true,
817
- deep: true,
818
- }
819
- )
820
-
821
- watch(
822
- headItemsPresent,
823
- (newValue: UI_I_HeadItem[]) => {
824
- filterShow.value = newValue.map(() => false)
825
- filterTerm.value = newValue.map(() => '')
826
- },
827
- {
828
- immediate: true,
829
- deep: true,
830
- }
831
- )
832
-
833
- const grabColumn = ref<number>(-1)
834
- const grabStartX = ref<number>(0)
835
- const setGrab = (key: number, e: MouseEvent): void => {
836
- if (props.withInfo && key === 0) return
837
-
838
- if (!isShowHiddenInfo.value) {
839
- grabColumn.value = key
840
- const currentWidth = parseInt(columnsWidth.value[grabColumn.value][0])
841
- grabStartX.value = e.clientX - currentWidth
842
- }
843
- }
844
-
845
- const isDrag = ref<boolean>(false)
846
-
847
- const mouseup = (): void => {
848
- if (grabColumn.value === -1) {
849
- return
850
- }
851
-
852
- grabColumn.value = -1
853
- isDrag.value = false
854
- }
855
-
856
- let resizeObserve: ResizeObserver
857
- let mousemove = (e: MouseEvent): void => {
858
- if (grabColumn.value === -1) return
859
-
860
- e.preventDefault()
861
- isDrag.value = true
862
- resizeObserve.disconnect()
863
-
864
- const minWidth = parseInt(columnsWidth.value[grabColumn.value][1])
865
- const changeX = Math.max(e.clientX - grabStartX.value, minWidth)
866
-
867
- columnsWidth.value[grabColumn.value] = [`${changeX}px`, `${minWidth}px`]
868
- }
869
- mousemove = useThrottle(mousemove)
870
-
871
- const setResizeObserve = (): void => {
872
- const el = document.getElementById(outerWrapper)
873
-
874
- if (!el) {
875
- setTimeout(setResizeObserve, 0)
876
- return
877
- }
878
-
879
- resizeObserve = new ResizeObserver(setColumnWidth)
880
- resizeObserve.observe(el)
881
- }
882
-
883
- let resizeObserveWithInfo: ResizeObserver
884
-
885
- const setColumnWidthWithInfo = (ent: ResizeObserverEntry[]): void => {
886
- if (isShowHiddenInfo.value) {
887
- memoryWidth = columnsWidth.value[1][0]
888
- columnsWidth.value[1][0] = `${ent[0].contentBoxSize[0].inlineSize - 86}px`
889
- } else {
890
- columnsWidth.value[1][0] = memoryWidth
891
- }
892
- }
893
-
894
- const setResizeObserveWithInfo = (): void => {
895
- const el = document.querySelector('.datagrid')
896
-
897
- if (!el) {
898
- setTimeout(setResizeObserveWithInfo, 0)
899
- return
900
- }
901
-
902
- resizeObserveWithInfo = new ResizeObserver(setColumnWidthWithInfo)
903
- resizeObserveWithInfo.observe(el)
904
- }
905
-
906
- onMounted(() => {
907
- window.addEventListener('mousemove', mousemove)
908
- window.addEventListener('mouseup', mouseup)
909
-
910
- if (props.withInfo) {
911
- setResizeObserveWithInfo()
912
- }
913
-
914
- setResizeObserve()
915
- })
916
- onUnmounted(() => {
917
- window.removeEventListener('mouseup', mouseup)
918
- window.removeEventListener('mousemove', mousemove)
919
- })
920
- </script>
921
-
922
- <style scoped lang="scss">
923
- @import '~/assets/scss/common/mixins';
924
- .datagrid-outer-wrapper {
925
- @include flex($dir: row);
926
- padding-top: 12px;
927
- flex-grow: 1;
928
- overflow: auto;
929
-
930
- &.datagrid-outer-wrapper-opened-info {
931
- width: 35%;
932
- min-width: 195px;
933
- }
934
-
935
- & * {
936
- box-sizing: border-box;
937
- }
938
-
939
- & svg {
940
- fill: var(--table-color);
941
- }
942
-
943
- .datagrid-inner-wrapper {
944
- @include flex($dir: column);
945
- flex-grow: 1;
946
- overflow: auto;
947
-
948
- .datagrid {
949
- overflow: auto;
950
- flex: 1 1 auto;
951
- border-radius: 0.125rem 0.125rem 0 0;
952
- background-color: var(--block-view-bg-color);
953
- border-color: var(--global-border-color);
954
-
955
- &.datagrid-opened-info {
956
- border-radius: 0.125rem 0 0 0;
957
- overflow-x: hidden;
958
- }
959
-
960
- .datagrid-table-wrapper {
961
- min-width: max-content;
962
- display: flex;
963
- flex: 1 1 auto;
964
- min-height: 100%;
965
-
966
- .datagrid-table {
967
- @include flex($dir: column);
968
- flex: 1 1 auto;
969
- align-content: flex-start;
970
- position: relative;
971
-
972
- .datagrid-header {
973
- position: sticky;
974
- top: 0;
975
- z-index: 501;
976
- width: auto;
977
-
978
- .datagrid-row-scrollable {
979
- flex-direction: row;
980
-
981
- .datagrid-column-separator {
982
- user-select: none;
983
-
984
- &::after {
985
- cursor: col-resize;
986
- }
987
- &.double-arrow-separator .datagrid-column-handle {
988
- cursor: unset;
989
- }
990
- &.double-arrow-separator::after {
991
- cursor: unset;
992
- }
993
- }
994
- }
995
-
996
- .datagrid-row {
997
- color: var(--table-color);
998
- background-color: var(--block-view-bg-color);
999
-
1000
- &.datagrid-selected {
1001
- background-color: var(--row-selected-bg-color);
1002
- border-bottom: 1px solid var(--global-border-color);
1003
-
1004
- & :deep(*) {
1005
- color: var(--table-active-color);
1006
- }
1007
- }
1008
-
1009
- .datagrid-column {
1010
- background-color: var(--block-view-bg-color);
1011
- border-bottom: 1px solid var(--global-border-color);
1012
- padding: 6px 12px 5px;
1013
- position: relative;
1014
- //flex: 1 1 auto;
1015
-
1016
- &:last-child {
1017
- flex: 1 1 auto;
1018
- }
1019
-
1020
- &.datagrid-toggle-block {
1021
- width: 38px;
1022
- min-width: 38px;
1023
-
1024
- .datagrid-column-separator {
1025
- cursor: default;
1026
-
1027
- &::after {
1028
- cursor: default;
1029
- }
1030
- }
1031
- }
1032
- }
1033
-
1034
- .datagrid-column-title {
1035
- align-self: auto;
1036
- color: var(--table-color);
1037
- background-color: var(--block-view-bg-color);
1038
- border-bottom-color: var(--global-border-color);
1039
- font-size: 11px;
1040
- @include flex($align: center);
1041
- display: flex;
1042
- flex-direction: row;
1043
- justify-content: flex-start;
1044
- align-items: center;
1045
- //line-height: 24px;
1046
- line-height: normal;
1047
-
1048
- .title-column {
1049
- font-weight: bold;
1050
- font-size: 11px;
1051
- overflow: hidden;
1052
- text-wrap: none;
1053
- text-overflow: ellipsis;
1054
- }
1055
-
1056
- .sort-arrow-wrap {
1057
- margin-left: auto;
1058
- height: 14px;
1059
- width: 14px;
1060
- min-height: 14px;
1061
- min-width: 14px;
1062
- vertical-align: middle;
1063
-
1064
- .sort-arrow {
1065
- &.down {
1066
- transform: rotate(180deg);
1067
- }
1068
- }
1069
- }
1070
- .title-icon {
1071
- width: 24px;
1072
- height: 24px;
1073
- }
1074
- }
1075
-
1076
- .datagrid-filter-toggle {
1077
- background: unset;
1078
- margin-top: -4px;
1079
- align-self: center;
1080
-
1081
- .filter-icon {
1082
- width: 16px;
1083
- height: 16px;
1084
- }
1085
- }
1086
- }
1087
- }
1088
-
1089
- .datagrid-placeholder-container {
1090
- .datagrid-placeholder.datagrid-empty {
1091
- background: var(--block-view-bg-color);
1092
- }
1093
- span {
1094
- font-size: 16px;
1095
- color: var(--global-font-color4);
1096
- }
1097
- }
1098
-
1099
- .datagrid-row {
1100
- width: 100%;
1101
- min-width: max-content;
1102
- border-top: none;
1103
- color: var(--table-color);
1104
- background-color: var(--block-view-bg-color);
1105
- border-bottom: 1px solid var(--global-border-color);
1106
-
1107
- &.datagrid-selected {
1108
- & :deep(*) {
1109
- color: var(--table-active-color);
1110
- }
1111
-
1112
- .datagrid-cell {
1113
- background-color: var(--row-selected-bg-color);
1114
- border-bottom: 1px solid var(--global-border-color);
1115
- position: relative;
1116
- line-height: 1rem;
1117
- }
1118
-
1119
- .datagrid-row-detail {
1120
- background-color: var(--row-selected-bg-color);
1121
- border-bottom: 1px solid var(--global-border-color);
1122
- }
1123
- }
1124
-
1125
- .datagrid-cell {
1126
- min-width: 38px;
1127
- min-height: 28px;
1128
- padding: 2px 12px 3px 12px;
1129
- position: relative;
1130
-
1131
- &:has(.double-arrow-wrap) {
1132
- padding-left: 4px;
1133
- }
1134
-
1135
- &:has(.double-arrow-wrap)::after {
1136
- content: '';
1137
- width: 0.5px;
1138
- height: calc(100% - 6px);
1139
- position: absolute;
1140
- right: 2px;
1141
- top: 3px;
1142
- background-color: var(--global-border-color);
1143
- }
1144
-
1145
- &:last-child {
1146
- flex: 1 1 auto;
1147
- }
1148
-
1149
- button {
1150
- height: 0.9rem;
1151
- width: 0.9rem;
1152
-
1153
- .arrow-icon {
1154
- width: 16px;
1155
- height: 16px;
1156
- transform: rotate(90deg);
1157
- }
1158
- }
1159
-
1160
- .datagrid-cell-icon {
1161
- display: inline-block;
1162
- width: 16px;
1163
- height: 16px;
1164
- }
1165
-
1166
- .datagrid-expandable-caret-button {
1167
- cursor: pointer;
1168
- width: 100%;
1169
-
1170
- .datagrid-expandable-caret-icon {
1171
- width: 16px;
1172
- height: 16px;
1173
- transform: rotate(90deg);
1174
- fill: var(--table-caret-color);
1175
-
1176
- &.toggle {
1177
- transform: rotate(180deg);
1178
- }
1179
- }
1180
- }
1181
- }
1182
-
1183
- &.datagrid-selected-info {
1184
- width: 100%;
1185
- min-width: max-content;
1186
- border-top: none;
1187
- color: var(--table-color);
1188
- background-color: var(--block-view-bg-color);
1189
-
1190
- .datagrid-cell {
1191
- position: relative;
1192
-
1193
- &.hidden-text-triangle::after {
1194
- content: '';
1195
- position: absolute;
1196
- transform: rotate(225deg);
1197
- border-bottom: 15px solid transparent;
1198
- border-right: 15px solid var(--global-bg-color);
1199
- bottom: 26%;
1200
- right: -16px;
1201
- z-index: 1000;
1202
- }
1203
-
1204
- &.hidden-text-triangle::before {
1205
- content: '';
1206
- transform: rotate(-135deg);
1207
- position: absolute;
1208
- border-bottom: 16px solid transparent;
1209
- border-right: 16px solid var(--global-border-color);
1210
- bottom: 23%;
1211
- right: -17px;
1212
- z-index: 999;
1213
- }
1214
- }
1215
- }
1216
- }
1217
-
1218
- .datagrid-row-sticky {
1219
- @include flex($w: nowrap);
1220
- position: sticky;
1221
- left: 0;
1222
- z-index: 500;
1223
- background-color: var(--block-view-bg-color);
1224
-
1225
- .datagrid-column {
1226
- color: var(--table-color);
1227
- background-color: var(--block-view-bg-color);
1228
- border-bottom-color: var(--global-border-color);
1229
- text-align: left;
1230
- display: flex;
1231
- flex: 1 1 auto;
1232
- vertical-align: top;
1233
- border: none;
1234
-
1235
- &.datagrid-expandable-caret {
1236
- .datagrid-column-separator {
1237
- display: block;
1238
-
1239
- &::after {
1240
- top: 0;
1241
- height: 18px;
1242
- }
1243
- }
1244
-
1245
- .radio-btn {
1246
- width: 16px;
1247
- height: 16px;
1248
- }
1249
- }
1250
-
1251
- .datagrid-column-separator {
1252
- position: relative;
1253
- //left: 0.6rem;
1254
- flex: 0 0 auto;
1255
- width: 0.05rem;
1256
- order: 100;
1257
- margin-left: auto;
1258
- height: 100%;
1259
- }
1260
- }
1261
-
1262
- .datagrid-fixed-column-without-separator {
1263
- background-color: var(--block-view-bg-color);
1264
-
1265
- &::after {
1266
- background-color: transparent;
1267
- }
1268
- }
1269
-
1270
- .datagrid-fixed-column {
1271
- background-color: var(--block-view-bg-color);
1272
-
1273
- &::after {
1274
- content: '';
1275
- width: 0.05rem;
1276
- height: calc(100% - 0.5rem);
1277
- position: absolute;
1278
- right: 0;
1279
- top: 0.25rem;
1280
- background-color: var(--global-border-color);
1281
- }
1282
- }
1283
- }
1284
-
1285
- .datagrid-row-scrollable {
1286
- @include flex($ff: row nowrap);
1287
- flex: 1 1 auto;
1288
-
1289
- .datagrid-scrolling-cells {
1290
- @include flex($ff: row nowrap);
1291
- flex: 1 1 auto;
1292
- }
1293
- }
1294
- .datagrid-body-row {
1295
- .datagrid-row-scrollable {
1296
- @include flex($ff: column nowrap);
1297
-
1298
- .datagrid-row-detail {
1299
- flex: 1;
1300
- }
1301
- }
1302
-
1303
- &:not(.datagrid-selected):hover {
1304
- border-bottom: 1px solid var(--table-hover-row-border-color);
1305
- background-color: var(--table-hover-row-background-color);
1306
- color: var(--table-hover-row-color);
1307
-
1308
- .datagrid-fixed-column {
1309
- background-color: var(--table-hover-row-background-color);
1310
- }
1311
- .datagrid-cell {
1312
- background-color: var(--table-hover-row-background-color);
1313
- }
1314
- }
1315
- }
1316
- }
1317
- }
1318
- }
1319
-
1320
- .datagrid-footer {
1321
- flex: 0 0 auto;
1322
- @include flex($ff: row nowrap, $just: space-between, $align: stretch);
1323
- font-size: 0.55rem;
1324
- background-color: var(--block-view-bg-color);
1325
- border: 0.05rem solid var(--global-border-color);
1326
- border-top: none;
1327
- border-radius: 0 0 0.15rem 0.15rem;
1328
- padding: 0 0.6rem;
1329
- height: 28px;
1330
-
1331
- &.datagrid-footer-opened-info {
1332
- border-radius: 0 0 0 0.15rem;
1333
- flex-direction: row-reverse;
1334
- }
1335
-
1336
- .clr-form-control-disabled {
1337
- opacity: 1;
1338
-
1339
- .clr-checkbox-wrapper {
1340
- label {
1341
- color: #8c8c8c;
1342
- margin-right: 9px;
1343
- padding-left: 22px;
1344
-
1345
- &:before {
1346
- position: absolute;
1347
- top: 4px;
1348
- left: 0;
1349
- content: '';
1350
- display: inline-block;
1351
- height: 16px;
1352
- width: 16px;
1353
- border: none;
1354
- border-radius: 3px;
1355
- background-color: var(--table-checkbox-color);
1356
- }
1357
-
1358
- &:after {
1359
- position: absolute;
1360
- content: '';
1361
- display: inline-block;
1362
- height: 5px;
1363
- width: 8px;
1364
- border-left: 2px solid #fff;
1365
- border-bottom: 2px solid #fff;
1366
- top: 4px;
1367
- left: 4px;
1368
- transform: translateY(4px) rotate(-45deg);
1369
- }
1370
- }
1371
- }
1372
- }
1373
- }
1374
-
1375
- .datagrid-spinner {
1376
- top: 0.6rem;
1377
- height: calc(100% - 0.6rem);
1378
- }
1379
-
1380
- .datagrid-footer-description {
1381
- flex: 1 1 auto;
1382
-
1383
- :deep(svg) {
1384
- fill: var(--table-switch-color);
1385
- }
1386
- }
1387
-
1388
- .clr-radio-wrapper,
1389
- .clr-checkbox-wrapper {
1390
- height: 18px;
1391
- }
1392
-
1393
- .clr-radio-wrapper label,
1394
- .clr-checkbox-wrapper label {
1395
- padding-left: 16px;
1396
- }
1397
- }
1398
- }
1399
- //.vertical-divider {
1400
- // width: 1px;
1401
- // height: 22px;
1402
- // background: #bbb;
1403
- // margin: 2px 2px 0 -5px;
1404
- //}
1405
-
1406
- .hidden-features {
1407
- padding: 9px 14px;
1408
- background-color: var(--global-bg-color);
1409
- color: var(--global-font-color2);
1410
- border: 1px solid var(--global-border-color);
1411
- border-left: none;
1412
- overflow-y: auto;
1413
- overflow-x: hidden;
1414
- width: 65%;
1415
-
1416
- .header {
1417
- display: flex;
1418
- align-items: center;
1419
- justify-content: space-between;
1420
- background-color: inherit;
1421
-
1422
- h3 {
1423
- font-size: 1rem;
1424
- color: var(--global-font-color2) !important;
1425
- line-height: 1.7rem;
1426
- }
1427
- button {
1428
- line-height: 24px;
1429
-
1430
- .close-icon {
1431
- margin-left: 10px;
1432
- width: 24px;
1433
- height: 24px;
1434
- }
1435
- }
1436
- }
1437
- }
1438
- .double-arrow-width {
1439
- width: 25px;
1440
- }
1441
- .double-arrow-wrap {
1442
- width: 30px;
1443
- height: 20px;
1444
- padding-top: 1px;
1445
- padding-left: 5px;
1446
- cursor: pointer;
1447
- background-color: transparent;
1448
-
1449
- .double-arrow {
1450
- background-color: transparent;
1451
- transform: rotate(90deg);
1452
- width: 16px;
1453
- height: 16px;
1454
- }
1455
-
1456
- .double-arrow.arrow-selected {
1457
- transform: rotate(-90deg) !important;
1458
- background-color: #0079b8;
1459
- }
1460
- }
1461
- .double-arrow-wrap.arrow-selected {
1462
- background-color: #0079b8;
1463
- border-radius: 3px;
1464
- }
1465
- </style>
1466
-
1467
- <style>
1468
- :root {
1469
- --table-color: #565656;
1470
- --table-active-color: #fff;
1471
- --table-checkbox-color: #454545;
1472
- --table-caret-color: #454545;
1473
- --table-switch-color: #575757;
1474
- --table-hover-row-border-color: #666;
1475
- --table-hover-row-background-color: #e8e8e8;
1476
- --table-hover-row-color: #454545;
1477
- --table-spinner-bg-color: #ffffff99;
1478
- }
1479
-
1480
- :root.dark-theme {
1481
- --table-color: #fff;
1482
- --table-active-color: #565656;
1483
- --table-checkbox-color: #49aeda;
1484
- --table-caret-color: #ffffff;
1485
- --table-switch-color: #b3b3b3;
1486
- --table-hover-row-border-color: #fff;
1487
- --table-hover-row-background-color: #324f61;
1488
- --table-hover-row-color: #e9ecef;
1489
- --table-spinner-bg-color: rgba(0, 0, 0, 0.5);
1490
- }
1491
- </style>
1
+ <template>
2
+ <div class="relative" style="display: flex">
3
+ <div
4
+ :id="outerWrapper"
5
+ :class="[
6
+ 'datagrid-outer-wrapper',
7
+ {
8
+ 'datagrid-outer-wrapper-opened-info': isShowHiddenInfo,
9
+ },
10
+ ]"
11
+ >
12
+ <div class="datagrid-inner-wrapper">
13
+ <div
14
+ :class="[
15
+ 'datagrid',
16
+ {
17
+ 'datagrid-opened-info': isShowHiddenInfo,
18
+ },
19
+ ]"
20
+ >
21
+ <div :id="tableWrapperId" class="datagrid-table-wrapper">
22
+ <div class="datagrid-table">
23
+ <div class="datagrid-header">
24
+ <div class="datagrid-row">
25
+ <div class="datagrid-row-master datagrid-row-flex">
26
+ <div v-if="props.type" class="datagrid-row-sticky">
27
+ <div
28
+ :id="expandableCaret"
29
+ class="datagrid-column datagrid-expandable-caret datagrid-fixed-column flex-align-center flex-justify-center"
30
+ >
31
+ <template v-if="props.type === 'checkbox'">
32
+ <div
33
+ class="clr-checkbox-wrapper flex-align-center flex-justify-center w-100"
34
+ >
35
+ <input
36
+ :id="`${props.testId}-${inputId}-all`"
37
+ v-model="selectedAll"
38
+ type="checkbox"
39
+ class="checkbox-btn"
40
+ :value="-1"
41
+ @change="changeAll"
42
+ />
43
+ <label
44
+ :for="`${props.testId}-${inputId}-all`"
45
+ class="clr-control-label"
46
+ />
47
+ </div>
48
+ <div class="datagrid-column-separator"></div>
49
+ </template>
50
+ </div>
51
+ <div
52
+ v-if="$slots.toggleBlock"
53
+ class="datagrid-toggle-block datagrid-column datagrid-fixed-width"
54
+ >
55
+ <div class="datagrid-column-flex">
56
+ <div class="datagrid-column-separator">
57
+ <span class="clr-sr-only" />
58
+ <div class="datagrid-column-resize-tracker" />
59
+ </div>
60
+ </div>
61
+ </div>
62
+ </div>
63
+ <div class="datagrid-row-scrollable">
64
+ <div
65
+ v-for="(item, key) in headItemsPresent"
66
+ :key="key"
67
+ :style="{
68
+ width: columnsWidth[key][0],
69
+ minWidth: columnsWidth[key][1],
70
+ }"
71
+ class="datagrid-column datagrid-fixed-width"
72
+ >
73
+ <div class="datagrid-column-flex">
74
+ <button
75
+ :id="`${props.testId}-sort-column-${key}`"
76
+ :class="[
77
+ 'datagrid-column-title',
78
+ {
79
+ 'default text-decoration-none':
80
+ !item.sortColumn,
81
+ },
82
+ ]"
83
+ @click="sortTable(item.sortColumn)"
84
+ >
85
+ <slot :key="key" name="th" :item="item">
86
+ <span
87
+ :title="item.text"
88
+ :style="{
89
+ width:
90
+ columnsWidth[key][0]?.slice(0, -2) -
91
+ (item.hasFilter ? 65 : 40) +
92
+ 'px',
93
+ }"
94
+ class="title-column"
95
+ >
96
+ {{ item.text }}
97
+ </span>
98
+ </slot>
99
+ <span
100
+ v-if="item.sortColumn"
101
+ class="sort-arrow-wrap"
102
+ >
103
+ <atoms-the-icon
104
+ v-show="sortInfo[0] === item.sortColumn"
105
+ :class="['sort-arrow', { down: sortInfo[1] }]"
106
+ name="sort-arrow"
107
+ />
108
+ </span>
109
+ </button>
110
+ <button
111
+ v-if="item.hasFilter"
112
+ :id="`${props.testId}-filter-icon-${key}`"
113
+ class="datagrid-filter-toggle clr-anchor clr-smart-open-close"
114
+ @click="filterShow[key] = !filterShow[key]"
115
+ >
116
+ <atoms-the-icon
117
+ v-if="filterTerm[key]"
118
+ class="filter-icon"
119
+ name="filter-solid"
120
+ style="fill: #0079b8"
121
+ />
122
+ <atoms-the-icon
123
+ v-else
124
+ class="filter-icon"
125
+ name="filter"
126
+ />
127
+ </button>
128
+ <atoms-popup-simple-popup
129
+ v-model="filterShow[key]"
130
+ :test-id="`${props.testId}-filter`"
131
+ top="100%"
132
+ :left="key === 0 && 0"
133
+ :right="key !== 0 && 0"
134
+ >
135
+ <input
136
+ :id="`${props.testId}-filter-input-${key}`"
137
+ v-model="filterTerm[key]"
138
+ type="text"
139
+ @input="filtering"
140
+ />
141
+ </atoms-popup-simple-popup>
142
+ <div
143
+ v-show="
144
+ !isShowHiddenInfo ||
145
+ (isShowHiddenInfo && key === 0)
146
+ "
147
+ :class="[
148
+ 'datagrid-column-separator',
149
+ {
150
+ 'double-arrow-separator':
151
+ key === 0 && props.withInfo,
152
+ },
153
+ ]"
154
+ @mousedown="setGrab(key, $event)"
155
+ >
156
+ <button
157
+ :id="`${props.testId}-resize-tracker-${key}`"
158
+ class="datagrid-column-handle drag-handle draggable"
159
+ />
160
+ <span class="clr-sr-only" />
161
+ <div class="datagrid-column-resize-tracker" />
162
+ </div>
163
+ </div>
164
+ </div>
165
+ </div>
166
+ </div>
167
+ </div>
168
+ </div>
169
+
170
+ <template v-if="bodyItemsPresent.length">
171
+ <div
172
+ v-for="(item, key) in bodyItemsPresent"
173
+ :key="item.id"
174
+ :class="[
175
+ 'datagrid-body-row datagrid-row animation',
176
+ {
177
+ 'datagrid-selected active':
178
+ !props.withInfo && checkIsSelectedRow(item[0].id),
179
+ 'datagrid-selected-info':
180
+ props.withInfo &&
181
+ isShowHiddenInfo &&
182
+ checkIsSelectedRow(item[0].id),
183
+ },
184
+ ]"
185
+ :[props.rowAttributeIdName]="item[0].dataId"
186
+ >
187
+ <div>
188
+ <div
189
+ class="datagrid-row-master datagrid-row-flex"
190
+ @click.prevent.stop="selectRowById(item[0].id)"
191
+ >
192
+ <div
193
+ v-if="props.type || $slots.toggleBlock"
194
+ class="datagrid-row-sticky"
195
+ @click.stop
196
+ >
197
+ <div
198
+ :id="expandableCaret"
199
+ :class="[
200
+ 'datagrid-expandable-caret flex-justify-center datagrid-cell',
201
+ {
202
+ 'datagrid-fixed-column': !props.withInfo,
203
+ 'datagrid-fixed-column-without-separator':
204
+ props.withInfo,
205
+ },
206
+ ]"
207
+ >
208
+ <!--TODO refactoring (duplicate)-->
209
+ <div
210
+ v-if="$slots.toggleBlock && !props.type"
211
+ class="datagrid-cell datagrid-fixed-width"
212
+ >
213
+ <div class="flex-align-center">
214
+ <button
215
+ :id="`${props.testId}-toggle-icon-${key}`"
216
+ class="datagrid-expandable-caret-button reset-btn"
217
+ @click="toggle(key)"
218
+ >
219
+ <atoms-the-icon
220
+ :class="[
221
+ 'datagrid-expandable-caret-icon',
222
+ { toggle: toggedItems[key] },
223
+ ]"
224
+ name="angle"
225
+ />
226
+ </button>
227
+ </div>
228
+ </div>
229
+ <slot
230
+ v-if="props.type"
231
+ :key="key"
232
+ name="type"
233
+ :item="item"
234
+ >
235
+ <div
236
+ :class="`clr-${props.type}-wrapper flex-justify-center flex-align-center`"
237
+ >
238
+ <input
239
+ :id="`${props.testId}-${inputId}-${item[0].id}`"
240
+ v-model="selectedRowLocal"
241
+ :key="item[0].id"
242
+ :type="props.type"
243
+ :class="`${props.type}-btn`"
244
+ :value="item[0].id"
245
+ name="selected-store"
246
+ @change="changeSelectedRow($event, item[0].id)"
247
+ />
248
+ <label
249
+ :for="`${props.testId}-${inputId}-${item[0].id}`"
250
+ class="clr-control-label"
251
+ />
252
+ </div>
253
+ </slot>
254
+ </div>
255
+ <!--TODO refactoring (duplicate)-->
256
+ <div
257
+ v-if="$slots.toggleBlock && props.type"
258
+ class="datagrid-cell datagrid-fixed-width"
259
+ >
260
+ <div class="flex-align-center">
261
+ <button
262
+ :id="`${props.testId}-toggle-icon-${key}`"
263
+ class="datagrid-expandable-caret-button reset-btn"
264
+ @click="toggle(key)"
265
+ >
266
+ <atoms-the-icon
267
+ :class="[
268
+ 'datagrid-expandable-caret-icon',
269
+ { toggle: toggedItems[key] },
270
+ ]"
271
+ name="angle"
272
+ />
273
+ </button>
274
+ </div>
275
+ </div>
276
+ </div>
277
+ <div class="datagrid-row-scrollable">
278
+ <div class="datagrid-scrolling-cells">
279
+ <div
280
+ v-for="(item2, key2) in item"
281
+ v-show="
282
+ !isShowHiddenInfo ||
283
+ item2.key === 'col1' ||
284
+ item2.key === 'icon'
285
+ "
286
+ :key="key2"
287
+ :style="{
288
+ width: columnsWidth[key2][0],
289
+ minWidth: columnsWidth[key2][1],
290
+ }"
291
+ :class="[
292
+ 'datagrid-cell datagrid-fixed-width',
293
+ {
294
+ 'hidden-text-triangle':
295
+ isShowHiddenInfo &&
296
+ item2.data?.name === 'hidden-text-triangle',
297
+ },
298
+ ]"
299
+ >
300
+ <div
301
+ :class="[
302
+ 'flex-align-center h-100',
303
+ {
304
+ 'double-arrow-width':
305
+ item2.data?.name === 'doubleArrows',
306
+ },
307
+ ]"
308
+ >
309
+ <div
310
+ v-if="item2.data?.name === 'doubleArrows'"
311
+ :id="`show-info-${item[1].text}`"
312
+ :class="[
313
+ 'double-arrow-wrap',
314
+ {
315
+ 'arrow-selected':
316
+ isShowHiddenInfo &&
317
+ item2.id === selectedRow,
318
+ },
319
+ ]"
320
+ @click.prevent.stop="
321
+ doubleArrowClick(item2, item)
322
+ "
323
+ >
324
+ <atoms-the-icon
325
+ v-if="item2.data?.name === 'doubleArrows'"
326
+ name="doubleArrows"
327
+ :class="[
328
+ 'double-arrow',
329
+ {
330
+ 'arrow-selected':
331
+ isShowHiddenInfo &&
332
+ item2.id === selectedRow,
333
+ },
334
+ ]"
335
+ />
336
+ </div>
337
+ <span
338
+ v-if="item2.text && item2.data?.iconClassName"
339
+ class="vertical-divider"
340
+ />
341
+ <slot :name="item2.key" :item="item2">
342
+ <span class="text-ellipsis" :title="item2.text"
343
+ >{{ item2.text }}
344
+ </span>
345
+ </slot>
346
+ </div>
347
+ </div>
348
+ </div>
349
+ <div
350
+ v-show="toggedItems[key]"
351
+ class="datagrid-row-flex datagrid-row-detail datagrid-container"
352
+ >
353
+ <slot name="toggleBlock" />
354
+ </div>
355
+ </div>
356
+ </div>
357
+ </div>
358
+ </div>
359
+ </template>
360
+ <template v-else>
361
+ <div class="datagrid-placeholder-container">
362
+ <div class="datagrid-placeholder datagrid-empty">
363
+ <div class="datagrid-placeholder-image"></div>
364
+
365
+ <span>No items found</span>
366
+ </div>
367
+ </div>
368
+ </template>
369
+ </div>
370
+ </div>
371
+ </div>
372
+
373
+ <div
374
+ v-if="!hideFooter"
375
+ :class="[
376
+ 'datagrid-footer',
377
+ {
378
+ 'datagrid-footer-opened-info': isShowHiddenInfo,
379
+ },
380
+ ]"
381
+ >
382
+ <div
383
+ v-if="props.type === 'checkbox' && props.selectedRow?.length"
384
+ class="clr-form-control-disabled"
385
+ >
386
+ <div class="clr-checkbox-wrapper">
387
+ <input
388
+ :id="`${props.testId}-check-all`"
389
+ type="checkbox"
390
+ checked
391
+ />
392
+ <label class="clr-control-label">
393
+ {{ props.selectedRow?.length }}
394
+ </label>
395
+ </div>
396
+ </div>
397
+ <div class="datagrid-footer-description flex-align-center">
398
+ <atoms-table-data-grid-column-switch
399
+ v-if="columnKeysLocal"
400
+ v-model:column-keys="columnKeysLocal"
401
+ :test-id="`${props.testId}-footer-description`"
402
+ class="switch-icon"
403
+ />
404
+
405
+ <div v-if="$slots.action" class="datagrid-footer__action">
406
+ <slot name="action" />
407
+ </div>
408
+ </div>
409
+
410
+ <atoms-table-data-grid-pagination
411
+ test-id="props.testId"
412
+ :page-size="props.pageSize"
413
+ :page="props.page"
414
+ :options="mergeOptions"
415
+ :total-items="props.totalItems"
416
+ :total-pages="props.totalPages"
417
+ :hide-pagination="props.hidePagination"
418
+ :hide-page-size="props.hidePageSize"
419
+ :show-page-info="props.showPageInfo"
420
+ :page-items-count="props.bodyItems.length"
421
+ :is-show-hidden-info="isShowHiddenInfo"
422
+ @change-page-size="changePageSize"
423
+ @change-page="changePage"
424
+ />
425
+ </div>
426
+
427
+ <atoms-loader v-show="props.loading" class="datagrid-spinner" />
428
+ </div>
429
+ </div>
430
+ <div
431
+ v-if="isShowInfo"
432
+ id="hidden-features"
433
+ :class="[
434
+ 'hidden-features',
435
+ {
436
+ 'show-hidden-info': isShowHiddenInfo,
437
+ },
438
+ ]"
439
+ >
440
+ <div class="header">
441
+ <slot name="hiddenInfoHeader" :item="showedInfoCol" />
442
+ <button
443
+ :id="`${props.testId}-hidden-info-toggle-icon-${showedInfoRow?.key}-${showedInfoRow?.data.name}`"
444
+ class="signpost-action close"
445
+ @click="isShowHiddenInfo = false"
446
+ >
447
+ <atoms-the-icon class="close-icon" name="close" />
448
+ </button>
449
+ </div>
450
+ <slot name="hiddenInfoBody" />
451
+ </div>
452
+ </div>
453
+ </template>
454
+
455
+ <script setup lang="ts">
456
+ import { UI_I_HTMLSelectElement } from '~/models/interfaces'
457
+ import { itemsPerPage } from '~/components/atoms/table/dataGrid/config/itemsPerPage'
458
+ import {
459
+ UI_I_HeadItem,
460
+ UI_I_BodyItem,
461
+ UI_I_ColumnKey,
462
+ } from '~/components/atoms/table/dataGrid/models/interfaces'
463
+ import { UI_I_OptionItem } from '~/components/atoms/models/interfaces'
464
+ import {
465
+ UI_T_ColumnKeys,
466
+ UI_T_DataGridType,
467
+ UI_T_SelectedRow,
468
+ } from '~/components/atoms/table/dataGrid/models/types'
469
+
470
+ const props = withDefaults(
471
+ defineProps<{
472
+ headItems: UI_I_HeadItem[]
473
+ bodyItems: UI_I_BodyItem[][]
474
+ pageSize: number
475
+ page: number
476
+ totalItems: number
477
+ totalPages: number
478
+ loading?: boolean
479
+ columnKeys?: UI_I_ColumnKey[]
480
+ selectedRow?: UI_T_SelectedRow
481
+ type?: UI_T_DataGridType
482
+ itemsPerPage?: UI_I_OptionItem[]
483
+ offSelectByRow?: boolean
484
+ hidePageSize?: boolean
485
+ showPageInfo?: boolean
486
+ serverOff?: boolean
487
+ fillWidth?: boolean
488
+ hideFooter?: boolean
489
+ hidePagination?: boolean
490
+ testId?: string
491
+ withInfo?: boolean
492
+ rowAttributeIdName?: string
493
+ }>(),
494
+ {
495
+ columnKeys: undefined,
496
+ selectedRow: undefined,
497
+ type: undefined,
498
+ itemsPerPage: undefined,
499
+ fillWidth: true,
500
+ testId: 'ui-data-grid',
501
+ withInfo: false,
502
+ rowAttributeIdName: 'data-id',
503
+ }
504
+ )
505
+
506
+ const emits = defineEmits<{
507
+ (event: 'update:column-keys', value: UI_T_ColumnKeys): void
508
+ (event: 'update:selected-row', value: UI_T_SelectedRow): void
509
+ (event: 'update:page-size', value: number): void
510
+ (event: 'update:page', value: number): void
511
+ (event: 'filtering', value: string[][]): void
512
+ (event: 'sorting', value: [string, boolean]): void
513
+ (event: 'change', value: any): void
514
+ (event: 'row-detail', value: number): void
515
+ }>()
516
+
517
+ const mergeOptions = computed<UI_I_OptionItem[]>(
518
+ () => props.itemsPerPage || itemsPerPage
519
+ )
520
+
521
+ const inputId = ref<string>(`radio-input-${useUniqueId()}`)
522
+
523
+ const columnKeysLocal = computed<UI_T_ColumnKeys>({
524
+ get() {
525
+ return props.columnKeys
526
+ },
527
+ set(newValue) {
528
+ emits('update:column-keys', newValue)
529
+ },
530
+ })
531
+
532
+ const selectedAll = ref<boolean>(false)
533
+ const changeAll = (e: UI_I_HTMLSelectElement): void => {
534
+ const newSelectedRow: (number | string)[] = []
535
+ if (e.target.checked) {
536
+ bodyItemsPresent.value.forEach((row) => {
537
+ newSelectedRow.push(row[0].id)
538
+ })
539
+ }
540
+
541
+ emits('update:selected-row', newSelectedRow)
542
+ }
543
+
544
+ const selectedRowLocal = ref<UI_T_SelectedRow>([])
545
+ const isShowHiddenInfo = ref<boolean>(false)
546
+ const tableWrapperId = `table-wrapper-${useUniqueId()}`
547
+ let memoryWidth = '0'
548
+
549
+ const showedInfoRow = ref<UI_I_BodyItem | null>(null)
550
+ const showedInfoCol = ref<UI_I_BodyItem[] | null>(null)
551
+
552
+ const isShowInfo = computed(
553
+ () =>
554
+ showedInfoRow.value &&
555
+ showedInfoRow.value.id === props.selectedRow &&
556
+ !showedInfoRow.value.data?.text &&
557
+ isShowHiddenInfo.value
558
+ )
559
+
560
+ const setShowedInfoData = (
561
+ item: UI_I_BodyItem | null,
562
+ items: UI_I_BodyItem[] | null,
563
+ reset: boolean = false
564
+ ): void => {
565
+ showedInfoRow.value = item
566
+ showedInfoCol.value = items
567
+
568
+ reset && (isShowHiddenInfo.value = false)
569
+ item && selectRowById(item.id)
570
+ reset && (isShowHiddenInfo.value = true)
571
+ }
572
+
573
+ const doubleArrowClick = (
574
+ item: UI_I_BodyItem,
575
+ items: UI_I_BodyItem[]
576
+ ): void => {
577
+ if (!isShowHiddenInfo.value) {
578
+ setShowedInfoData(item, items)
579
+ } else {
580
+ if (showedInfoRow.value?.id !== item.id) {
581
+ setShowedInfoData(item, items, true)
582
+
583
+ return
584
+ } else {
585
+ setShowedInfoData(null, null)
586
+ }
587
+ }
588
+
589
+ isShowHiddenInfo.value = !isShowHiddenInfo.value
590
+ }
591
+
592
+ watch(
593
+ () => props.selectedRow,
594
+ (newValue) => {
595
+ if (newValue === undefined) return
596
+
597
+ if (Array.isArray(newValue) && !newValue.length) selectedAll.value = false
598
+
599
+ selectedRowLocal.value = newValue
600
+ },
601
+ { immediate: true }
602
+ )
603
+ const selectRowById = (id: number | string): void => {
604
+ if (props.offSelectByRow || isShowHiddenInfo.value) return
605
+
606
+ if (props.type === 'checkbox' && Array.isArray(selectedRowLocal.value)) {
607
+ const has = selectedRowLocal.value.includes(id)
608
+ if (has) {
609
+ selectedRowLocal.value = selectedRowLocal.value.filter(
610
+ (rowId: string | number) => rowId !== id
611
+ )
612
+ } else {
613
+ selectedRowLocal.value.push(id)
614
+ }
615
+
616
+ emits('change', selectedRowLocal.value)
617
+ emits('update:selected-row', selectedRowLocal.value)
618
+
619
+ changeSelectedAll(!has)
620
+ return
621
+ }
622
+
623
+ emits('change', id)
624
+ emits('update:selected-row', id)
625
+ }
626
+ const changeSelectedRow = (
627
+ e: UI_I_HTMLSelectElement,
628
+ id: number | string
629
+ ): void => {
630
+ if (props.type === 'checkbox' && Array.isArray(selectedRowLocal.value)) {
631
+ const newSelectedRow = [...selectedRowLocal.value]
632
+ emits('change', newSelectedRow)
633
+ emits('update:selected-row', newSelectedRow)
634
+
635
+ changeSelectedAll(e.target.checked)
636
+ return
637
+ }
638
+
639
+ emits('change', id)
640
+ emits('update:selected-row', id)
641
+ }
642
+ const changeSelectedAll = (checked: boolean): void => {
643
+ if (props.type === 'checkbox' && Array.isArray(selectedRowLocal.value)) {
644
+ const newSelectedRow = [...selectedRowLocal.value]
645
+
646
+ if (!checked) {
647
+ selectedAll.value = false
648
+ } else if (newSelectedRow.length === bodyItemsPresent.value.length) {
649
+ selectedAll.value = true
650
+ }
651
+ }
652
+ }
653
+
654
+ const checkIsSelectedRow = (id: number | string): boolean => {
655
+ if (props.type === 'checkbox' && Array.isArray(props.selectedRow)) {
656
+ return props.selectedRow.includes(id)
657
+ }
658
+ return props.selectedRow === id
659
+ }
660
+
661
+ // Sorting
662
+ const sortInfo = ref<[string, boolean]>(['', true])
663
+ const sortedColumnIndex = computed<number>(() => {
664
+ let index = -1
665
+ headItemsPresent.value.forEach((item, key) => {
666
+ if (item.sortColumn === sortInfo.value[0]) index = key
667
+ })
668
+
669
+ return index
670
+ })
671
+ const sortTable = (sortName: string): void => {
672
+ if (!sortName) return
673
+
674
+ emits('update:selected-row', selectedRowLocal.value)
675
+
676
+ if (sortInfo.value[0] === sortName) {
677
+ sortInfo.value[1] = !sortInfo.value[1]
678
+
679
+ emits('sorting', sortInfo.value)
680
+ return
681
+ }
682
+ sortInfo.value[0] = sortName
683
+ sortInfo.value[1] = true
684
+
685
+ emits('sorting', sortInfo.value)
686
+ }
687
+
688
+ // Filtering
689
+ const filterShow = ref<boolean[]>([])
690
+ const filterTerm = ref<string[]>([])
691
+ const filtering = (): void => {
692
+ const filter: string[][] = []
693
+ headItemsPresent.value.forEach((item, key) => {
694
+ if (filterTerm.value[key]) filter.push([item.key, filterTerm.value[key]])
695
+ })
696
+
697
+ emits('filtering', filter)
698
+ }
699
+
700
+ // Pagination
701
+ const changePageSize = (value: number): void => {
702
+ emits('update:page-size', value)
703
+ }
704
+ const changePage = (value: number): void => {
705
+ emits('update:page', value)
706
+ }
707
+
708
+ const headItemsPresent = computed<UI_I_HeadItem[]>(() => {
709
+ if (!columnKeysLocal.value) {
710
+ return props.headItems
711
+ }
712
+
713
+ return props.headItems.filter((item, key) => {
714
+ return columnKeysLocal.value?.find(
715
+ (item2, key2) => item2.key === item.key && key === key2
716
+ )?.show
717
+ })
718
+ })
719
+
720
+ const bodyItemsPresent = computed<UI_I_BodyItem[][]>(() => {
721
+ let items: UI_I_BodyItem[][] = props.bodyItems
722
+
723
+ // Filtering by column
724
+ if (columnKeysLocal.value) {
725
+ items = items.map((arr) => {
726
+ arr = arr.filter((item, key) => {
727
+ return columnKeysLocal.value?.find(
728
+ (item2, key2) => item2.key === item.key && key === key2
729
+ )?.show
730
+ })
731
+ return arr
732
+ })
733
+ }
734
+
735
+ if (props.serverOff) {
736
+ // Sorting
737
+ if (sortInfo.value[0] !== '') {
738
+ items = useDeepCopy(items).sort(
739
+ (a: UI_I_BodyItem[], b: UI_I_BodyItem[]) => {
740
+ const first = a[sortedColumnIndex.value].text.toString().toLowerCase()
741
+ const second = b[sortedColumnIndex.value].text
742
+ .toString()
743
+ .toLowerCase()
744
+
745
+ if (sortInfo.value[1]) {
746
+ return first > second ? 1 : first < second ? -1 : 0
747
+ } else {
748
+ return first < second ? 1 : first > second ? -1 : 0
749
+ }
750
+ }
751
+ )
752
+ }
753
+ // Filtering
754
+ const hasFilter = headItemsPresent.value.some(
755
+ (_, key) => filterTerm.value[key]
756
+ )
757
+ if (hasFilter) {
758
+ items = items.filter((row) => {
759
+ // 0 - начальное значение
760
+ // 1 - найден элемент
761
+ // -1 - ненайден элемент
762
+ let hasItem = 0
763
+ headItemsPresent.value.forEach((item, key) => {
764
+ if (!filterTerm.value[key]) return
765
+ const term = filterTerm.value[key].toLowerCase()
766
+
767
+ row.forEach((col) => {
768
+ if (
769
+ col.key === item.key &&
770
+ typeof col.text === 'string' &&
771
+ hasItem != -1
772
+ ) {
773
+ hasItem = col.text.toLowerCase().includes(term) ? 1 : -1
774
+ }
775
+ })
776
+ })
777
+
778
+ return hasItem === 1
779
+ })
780
+ }
781
+ }
782
+
783
+ return items
784
+ })
785
+
786
+ const toggedItems = ref<boolean[]>([])
787
+
788
+ const toggle = (key: number): void => {
789
+ toggedItems.value[key] = !toggedItems.value[key]
790
+ if (!toggedItems.value[key]) return
791
+ emits('row-detail', key)
792
+ }
793
+
794
+ const outerWrapper = `datagrid-outer-wrapper${useUniqueId()}`
795
+ const expandableCaret = `datagrid-expandable-caret${useUniqueId()}`
796
+
797
+ // Resize
798
+ let helper = false
799
+ let setColumnWidth = (): void => {
800
+ if (!helper) {
801
+ columnsWidth.value = []
802
+ headItemsPresent.value.forEach((item) => {
803
+ columnsWidth.value.push([
804
+ item.width, // width
805
+ item.width, // min-width
806
+ ])
807
+ })
808
+
809
+ helper = true
810
+ setTimeout(() => setColumnWidth(), 0)
811
+ return
812
+ }
813
+ // Не продолжаем, чтобы ширина калонок была так как заданно в headItemsPresent
814
+ if (!props.fillWidth) return
815
+
816
+ const dataGridWidth = document.getElementById(outerWrapper)?.clientWidth || 0
817
+ const leftColumnWidth =
818
+ document.getElementById(expandableCaret)?.clientWidth || 0
819
+ const bordersWidth = 2
820
+ const columnWidth =
821
+ (dataGridWidth - leftColumnWidth - bordersWidth) /
822
+ headItemsPresent.value.length
823
+
824
+ headItemsPresent.value.forEach((item) => {
825
+ columnsWidth.value.push([
826
+ `${columnWidth}px`, // width
827
+ item.width, // min-width
828
+ ])
829
+ })
830
+ }
831
+ setColumnWidth = useThrottle(setColumnWidth)
832
+
833
+ const columnsWidth = ref<[string, string][]>([])
834
+ watch(
835
+ bodyItemsPresent,
836
+ (newValue: UI_I_BodyItem[][]) => {
837
+ newValue.forEach(() => {
838
+ toggedItems.value.push(false)
839
+ })
840
+ setColumnWidth()
841
+ },
842
+ {
843
+ immediate: true,
844
+ deep: true,
845
+ }
846
+ )
847
+
848
+ watch(
849
+ headItemsPresent,
850
+ (newValue: UI_I_HeadItem[]) => {
851
+ filterShow.value = newValue.map(() => false)
852
+ filterTerm.value = newValue.map(() => '')
853
+ },
854
+ {
855
+ immediate: true,
856
+ deep: true,
857
+ }
858
+ )
859
+
860
+ const grabColumn = ref<number>(-1)
861
+ const grabStartX = ref<number>(0)
862
+ const setGrab = (key: number, e: MouseEvent): void => {
863
+ if (props.withInfo && key === 0) return
864
+
865
+ if (!isShowHiddenInfo.value) {
866
+ grabColumn.value = key
867
+ const currentWidth = parseInt(columnsWidth.value[grabColumn.value][0])
868
+ grabStartX.value = e.clientX - currentWidth
869
+ }
870
+ }
871
+
872
+ const isDrag = ref<boolean>(false)
873
+
874
+ const mouseup = (): void => {
875
+ if (grabColumn.value === -1) {
876
+ return
877
+ }
878
+
879
+ grabColumn.value = -1
880
+ isDrag.value = false
881
+ }
882
+
883
+ let resizeObserve: ResizeObserver
884
+ let mousemove = (e: MouseEvent): void => {
885
+ if (grabColumn.value === -1) return
886
+
887
+ e.preventDefault()
888
+ isDrag.value = true
889
+ resizeObserve.disconnect()
890
+
891
+ const minWidth = parseInt(columnsWidth.value[grabColumn.value][1])
892
+ const changeX = Math.max(e.clientX - grabStartX.value, minWidth)
893
+
894
+ columnsWidth.value[grabColumn.value] = [`${changeX}px`, `${minWidth}px`]
895
+ }
896
+ mousemove = useThrottle(mousemove)
897
+
898
+ const setResizeObserve = (): void => {
899
+ const el = document.getElementById(outerWrapper)
900
+
901
+ if (!el) {
902
+ setTimeout(setResizeObserve, 0)
903
+ return
904
+ }
905
+
906
+ resizeObserve = new ResizeObserver(setColumnWidth)
907
+ resizeObserve.observe(el)
908
+ }
909
+
910
+ let resizeObserveWithInfo: ResizeObserver
911
+
912
+ const setColumnWidthWithInfo = (ent: ResizeObserverEntry[]): void => {
913
+ if (isShowHiddenInfo.value) {
914
+ memoryWidth = columnsWidth.value[1][0]
915
+ columnsWidth.value[1][0] = `${ent[0].contentBoxSize[0].inlineSize - 86}px`
916
+ } else {
917
+ columnsWidth.value[1][0] = memoryWidth
918
+ }
919
+ }
920
+
921
+ const setResizeObserveWithInfo = (): void => {
922
+ const el = document.querySelector('.datagrid')
923
+
924
+ if (!el) {
925
+ setTimeout(setResizeObserveWithInfo, 0)
926
+ return
927
+ }
928
+
929
+ resizeObserveWithInfo = new ResizeObserver(setColumnWidthWithInfo)
930
+ resizeObserveWithInfo.observe(el)
931
+ }
932
+
933
+ onMounted(() => {
934
+ window.addEventListener('mousemove', mousemove)
935
+ window.addEventListener('mouseup', mouseup)
936
+
937
+ if (props.withInfo) {
938
+ setResizeObserveWithInfo()
939
+ }
940
+
941
+ setResizeObserve()
942
+ })
943
+ onUnmounted(() => {
944
+ window.removeEventListener('mouseup', mouseup)
945
+ window.removeEventListener('mousemove', mousemove)
946
+ })
947
+ </script>
948
+
949
+ <style scoped lang="scss">
950
+ @import '~/assets/scss/common/mixins';
951
+ .datagrid-outer-wrapper {
952
+ @include flex($dir: row);
953
+ padding-top: 12px;
954
+ flex-grow: 1;
955
+ overflow: auto;
956
+
957
+ &.datagrid-outer-wrapper-opened-info {
958
+ width: 35%;
959
+ min-width: 195px;
960
+ }
961
+
962
+ & * {
963
+ box-sizing: border-box;
964
+ }
965
+
966
+ & svg {
967
+ fill: var(--table-color);
968
+ }
969
+
970
+ .datagrid-inner-wrapper {
971
+ @include flex($dir: column);
972
+ flex-grow: 1;
973
+ overflow: auto;
974
+
975
+ .datagrid {
976
+ overflow: auto;
977
+ flex: 1 1 auto;
978
+ border-radius: 0.125rem 0.125rem 0 0;
979
+ background-color: var(--block-view-bg-color);
980
+ border-color: var(--global-border-color);
981
+
982
+ &.datagrid-opened-info {
983
+ border-radius: 0.125rem 0 0 0;
984
+ overflow-x: hidden;
985
+ }
986
+
987
+ .datagrid-table-wrapper {
988
+ min-width: max-content;
989
+ display: flex;
990
+ flex: 1 1 auto;
991
+ min-height: 100%;
992
+
993
+ .datagrid-table {
994
+ @include flex($dir: column);
995
+ flex: 1 1 auto;
996
+ align-content: flex-start;
997
+ position: relative;
998
+
999
+ .datagrid-header {
1000
+ position: sticky;
1001
+ top: 0;
1002
+ z-index: 501;
1003
+ width: auto;
1004
+
1005
+ .datagrid-row-scrollable {
1006
+ flex-direction: row;
1007
+
1008
+ .datagrid-column-separator {
1009
+ user-select: none;
1010
+
1011
+ &::after {
1012
+ cursor: col-resize;
1013
+ }
1014
+ &.double-arrow-separator .datagrid-column-handle {
1015
+ cursor: unset;
1016
+ }
1017
+ &.double-arrow-separator::after {
1018
+ cursor: unset;
1019
+ }
1020
+ }
1021
+ }
1022
+
1023
+ .datagrid-row {
1024
+ color: var(--table-color);
1025
+ background-color: var(--block-view-bg-color);
1026
+
1027
+ &.datagrid-selected {
1028
+ background-color: var(--row-selected-bg-color);
1029
+ border-bottom: 1px solid var(--global-border-color);
1030
+
1031
+ & :deep(*) {
1032
+ color: var(--table-active-color);
1033
+ }
1034
+ }
1035
+
1036
+ .datagrid-column {
1037
+ background-color: var(--block-view-bg-color);
1038
+ border-bottom: 1px solid var(--global-border-color);
1039
+ padding: 6px 12px 5px;
1040
+ position: relative;
1041
+ //flex: 1 1 auto;
1042
+
1043
+ &:last-child {
1044
+ flex: 1 1 auto;
1045
+ }
1046
+
1047
+ &.datagrid-toggle-block {
1048
+ width: 38px;
1049
+ min-width: 38px;
1050
+
1051
+ .datagrid-column-separator {
1052
+ cursor: default;
1053
+
1054
+ &::after {
1055
+ cursor: default;
1056
+ }
1057
+ }
1058
+ }
1059
+ }
1060
+
1061
+ .datagrid-column-title {
1062
+ align-self: auto;
1063
+ color: var(--table-color);
1064
+ background-color: var(--block-view-bg-color);
1065
+ border-bottom-color: var(--global-border-color);
1066
+ font-size: 11px;
1067
+ @include flex($align: center);
1068
+ display: flex;
1069
+ flex-direction: row;
1070
+ justify-content: flex-start;
1071
+ align-items: center;
1072
+ //line-height: 24px;
1073
+ line-height: normal;
1074
+
1075
+ .title-column {
1076
+ font-weight: bold;
1077
+ font-size: 11px;
1078
+ overflow: hidden;
1079
+ text-wrap: none;
1080
+ text-overflow: ellipsis;
1081
+ }
1082
+
1083
+ .sort-arrow-wrap {
1084
+ margin-left: auto;
1085
+ height: 14px;
1086
+ width: 14px;
1087
+ min-height: 14px;
1088
+ min-width: 14px;
1089
+ vertical-align: middle;
1090
+
1091
+ .sort-arrow {
1092
+ &.down {
1093
+ transform: rotate(180deg);
1094
+ }
1095
+ }
1096
+ }
1097
+ .title-icon {
1098
+ width: 24px;
1099
+ height: 24px;
1100
+ }
1101
+ }
1102
+
1103
+ .datagrid-filter-toggle {
1104
+ background: unset;
1105
+ margin-top: -4px;
1106
+ align-self: center;
1107
+
1108
+ .filter-icon {
1109
+ width: 16px;
1110
+ height: 16px;
1111
+ }
1112
+ }
1113
+ }
1114
+ }
1115
+
1116
+ .datagrid-placeholder-container {
1117
+ .datagrid-placeholder.datagrid-empty {
1118
+ background: var(--block-view-bg-color);
1119
+ }
1120
+ span {
1121
+ font-size: 16px;
1122
+ color: var(--global-font-color4);
1123
+ }
1124
+ }
1125
+
1126
+ .datagrid-row {
1127
+ width: 100%;
1128
+ min-width: max-content;
1129
+ border-top: none;
1130
+ color: var(--table-color);
1131
+ background-color: var(--block-view-bg-color);
1132
+ border-bottom: 1px solid var(--global-border-color);
1133
+
1134
+ &.datagrid-selected {
1135
+ & :deep(*) {
1136
+ color: var(--table-active-color);
1137
+ }
1138
+
1139
+ .datagrid-cell {
1140
+ background-color: var(--row-selected-bg-color);
1141
+ border-bottom: 1px solid var(--global-border-color);
1142
+ position: relative;
1143
+ line-height: 1rem;
1144
+ }
1145
+
1146
+ .datagrid-row-detail {
1147
+ background-color: var(--row-selected-bg-color);
1148
+ border-bottom: 1px solid var(--global-border-color);
1149
+ }
1150
+ }
1151
+
1152
+ .datagrid-cell {
1153
+ min-width: 38px;
1154
+ min-height: 28px;
1155
+ padding: 2px 12px 3px 12px;
1156
+ position: relative;
1157
+
1158
+ &:has(.double-arrow-wrap) {
1159
+ padding-left: 4px;
1160
+ }
1161
+
1162
+ &:has(.double-arrow-wrap)::after {
1163
+ content: '';
1164
+ width: 0.5px;
1165
+ height: calc(100% - 6px);
1166
+ position: absolute;
1167
+ right: 2px;
1168
+ top: 3px;
1169
+ background-color: var(--global-border-color);
1170
+ }
1171
+
1172
+ &:last-child {
1173
+ flex: 1 1 auto;
1174
+ }
1175
+
1176
+ button {
1177
+ height: 0.9rem;
1178
+ width: 0.9rem;
1179
+
1180
+ .arrow-icon {
1181
+ width: 16px;
1182
+ height: 16px;
1183
+ transform: rotate(90deg);
1184
+ }
1185
+ }
1186
+
1187
+ .datagrid-cell-icon {
1188
+ display: inline-block;
1189
+ width: 16px;
1190
+ height: 16px;
1191
+ }
1192
+
1193
+ .datagrid-expandable-caret-button {
1194
+ cursor: pointer;
1195
+ width: 100%;
1196
+
1197
+ .datagrid-expandable-caret-icon {
1198
+ width: 16px;
1199
+ height: 16px;
1200
+ transform: rotate(90deg);
1201
+ fill: var(--table-caret-color);
1202
+
1203
+ &.toggle {
1204
+ transform: rotate(180deg);
1205
+ }
1206
+ }
1207
+ }
1208
+ }
1209
+
1210
+ &.datagrid-selected-info {
1211
+ width: 100%;
1212
+ min-width: max-content;
1213
+ border-top: none;
1214
+ color: var(--table-color);
1215
+ background-color: var(--block-view-bg-color);
1216
+
1217
+ .datagrid-cell {
1218
+ position: relative;
1219
+
1220
+ &.hidden-text-triangle::after {
1221
+ content: '';
1222
+ position: absolute;
1223
+ transform: rotate(225deg);
1224
+ border-bottom: 15px solid transparent;
1225
+ border-right: 15px solid var(--global-bg-color);
1226
+ bottom: 26%;
1227
+ right: -16px;
1228
+ z-index: 1000;
1229
+ }
1230
+
1231
+ &.hidden-text-triangle::before {
1232
+ content: '';
1233
+ transform: rotate(-135deg);
1234
+ position: absolute;
1235
+ border-bottom: 16px solid transparent;
1236
+ border-right: 16px solid var(--global-border-color);
1237
+ bottom: 23%;
1238
+ right: -17px;
1239
+ z-index: 999;
1240
+ }
1241
+ }
1242
+ }
1243
+ }
1244
+
1245
+ .datagrid-row-sticky {
1246
+ @include flex($w: nowrap);
1247
+ position: sticky;
1248
+ left: 0;
1249
+ z-index: 500;
1250
+ background-color: var(--block-view-bg-color);
1251
+
1252
+ .datagrid-column {
1253
+ color: var(--table-color);
1254
+ background-color: var(--block-view-bg-color);
1255
+ border-bottom-color: var(--global-border-color);
1256
+ text-align: left;
1257
+ display: flex;
1258
+ flex: 1 1 auto;
1259
+ vertical-align: top;
1260
+ border: none;
1261
+
1262
+ &.datagrid-expandable-caret {
1263
+ .datagrid-column-separator {
1264
+ display: block;
1265
+
1266
+ &::after {
1267
+ top: 0;
1268
+ height: 18px;
1269
+ }
1270
+ }
1271
+
1272
+ .radio-btn {
1273
+ width: 16px;
1274
+ height: 16px;
1275
+ }
1276
+ }
1277
+
1278
+ .datagrid-column-separator {
1279
+ position: relative;
1280
+ //left: 0.6rem;
1281
+ flex: 0 0 auto;
1282
+ width: 0.05rem;
1283
+ order: 100;
1284
+ margin-left: auto;
1285
+ height: 100%;
1286
+ }
1287
+ }
1288
+
1289
+ .datagrid-fixed-column-without-separator {
1290
+ background-color: var(--block-view-bg-color);
1291
+
1292
+ &::after {
1293
+ background-color: transparent;
1294
+ }
1295
+ }
1296
+
1297
+ .datagrid-fixed-column {
1298
+ background-color: var(--block-view-bg-color);
1299
+
1300
+ &::after {
1301
+ content: '';
1302
+ width: 0.05rem;
1303
+ height: calc(100% - 0.5rem);
1304
+ position: absolute;
1305
+ right: 0;
1306
+ top: 0.25rem;
1307
+ background-color: var(--global-border-color);
1308
+ }
1309
+ }
1310
+ }
1311
+
1312
+ .datagrid-row-scrollable {
1313
+ @include flex($ff: row nowrap);
1314
+ flex: 1 1 auto;
1315
+
1316
+ .datagrid-scrolling-cells {
1317
+ @include flex($ff: row nowrap);
1318
+ flex: 1 1 auto;
1319
+ }
1320
+ }
1321
+ .datagrid-body-row {
1322
+ .datagrid-row-scrollable {
1323
+ @include flex($ff: column nowrap);
1324
+
1325
+ .datagrid-row-detail {
1326
+ flex: 1;
1327
+ }
1328
+ }
1329
+
1330
+ &:not(.datagrid-selected):hover {
1331
+ border-bottom: 1px solid var(--table-hover-row-border-color);
1332
+ background-color: var(--table-hover-row-background-color);
1333
+ color: var(--table-hover-row-color);
1334
+
1335
+ .datagrid-fixed-column {
1336
+ background-color: var(--table-hover-row-background-color);
1337
+ }
1338
+ .datagrid-cell {
1339
+ background-color: var(--table-hover-row-background-color);
1340
+ }
1341
+ }
1342
+ }
1343
+ }
1344
+ }
1345
+ }
1346
+
1347
+ .datagrid-footer {
1348
+ flex: 0 0 auto;
1349
+ @include flex($ff: row nowrap, $just: space-between, $align: stretch);
1350
+ font-size: 0.55rem;
1351
+ background-color: var(--block-view-bg-color);
1352
+ border: 0.05rem solid var(--global-border-color);
1353
+ border-top: none;
1354
+ border-radius: 0 0 0.15rem 0.15rem;
1355
+ padding: 0 0.6rem;
1356
+ height: 28px;
1357
+
1358
+ &.datagrid-footer-opened-info {
1359
+ border-radius: 0 0 0 0.15rem;
1360
+ flex-direction: row-reverse;
1361
+ }
1362
+
1363
+ .clr-form-control-disabled {
1364
+ opacity: 1;
1365
+
1366
+ .clr-checkbox-wrapper {
1367
+ label {
1368
+ color: #8c8c8c;
1369
+ margin-right: 9px;
1370
+ padding-left: 22px;
1371
+
1372
+ &:before {
1373
+ position: absolute;
1374
+ top: 4px;
1375
+ left: 0;
1376
+ content: '';
1377
+ display: inline-block;
1378
+ height: 16px;
1379
+ width: 16px;
1380
+ border: none;
1381
+ border-radius: 3px;
1382
+ background-color: var(--table-checkbox-color);
1383
+ }
1384
+
1385
+ &:after {
1386
+ position: absolute;
1387
+ content: '';
1388
+ display: inline-block;
1389
+ height: 5px;
1390
+ width: 8px;
1391
+ border-left: 2px solid #fff;
1392
+ border-bottom: 2px solid #fff;
1393
+ top: 4px;
1394
+ left: 4px;
1395
+ transform: translateY(4px) rotate(-45deg);
1396
+ }
1397
+ }
1398
+ }
1399
+ }
1400
+ }
1401
+
1402
+ .datagrid-spinner {
1403
+ top: 0.6rem;
1404
+ height: calc(100% - 0.6rem);
1405
+ }
1406
+
1407
+ .datagrid-footer-description {
1408
+ flex: 1 1 auto;
1409
+
1410
+ :deep(svg) {
1411
+ fill: var(--table-switch-color);
1412
+ }
1413
+ }
1414
+
1415
+ .clr-radio-wrapper,
1416
+ .clr-checkbox-wrapper {
1417
+ height: 18px;
1418
+ }
1419
+
1420
+ .clr-radio-wrapper label,
1421
+ .clr-checkbox-wrapper label {
1422
+ padding-left: 16px;
1423
+ }
1424
+ }
1425
+ }
1426
+ //.vertical-divider {
1427
+ // width: 1px;
1428
+ // height: 22px;
1429
+ // background: #bbb;
1430
+ // margin: 2px 2px 0 -5px;
1431
+ //}
1432
+
1433
+ .hidden-features {
1434
+ padding: 9px 14px;
1435
+ background-color: var(--global-bg-color);
1436
+ color: var(--global-font-color2);
1437
+ border: 1px solid var(--global-border-color);
1438
+ border-left: none;
1439
+ overflow-y: auto;
1440
+ overflow-x: hidden;
1441
+ width: 65%;
1442
+
1443
+ .header {
1444
+ display: flex;
1445
+ align-items: center;
1446
+ justify-content: space-between;
1447
+ background-color: inherit;
1448
+
1449
+ h3 {
1450
+ font-size: 1rem;
1451
+ color: var(--global-font-color2) !important;
1452
+ line-height: 1.7rem;
1453
+ }
1454
+ button {
1455
+ line-height: 24px;
1456
+
1457
+ .close-icon {
1458
+ margin-left: 10px;
1459
+ width: 24px;
1460
+ height: 24px;
1461
+ }
1462
+ }
1463
+ }
1464
+ }
1465
+ .double-arrow-width {
1466
+ width: 25px;
1467
+ }
1468
+ .double-arrow-wrap {
1469
+ width: 30px;
1470
+ height: 20px;
1471
+ padding-top: 1px;
1472
+ padding-left: 5px;
1473
+ cursor: pointer;
1474
+ background-color: transparent;
1475
+
1476
+ .double-arrow {
1477
+ background-color: transparent;
1478
+ transform: rotate(90deg);
1479
+ width: 16px;
1480
+ height: 16px;
1481
+ }
1482
+
1483
+ .double-arrow.arrow-selected {
1484
+ transform: rotate(-90deg) !important;
1485
+ background-color: #0079b8;
1486
+ }
1487
+ }
1488
+ .double-arrow-wrap.arrow-selected {
1489
+ background-color: #0079b8;
1490
+ border-radius: 3px;
1491
+ }
1492
+ </style>
1493
+
1494
+ <style>
1495
+ :root {
1496
+ --table-color: #565656;
1497
+ --table-active-color: #fff;
1498
+ --table-checkbox-color: #454545;
1499
+ --table-caret-color: #454545;
1500
+ --table-switch-color: #575757;
1501
+ --table-hover-row-border-color: #666;
1502
+ --table-hover-row-background-color: #e8e8e8;
1503
+ --table-hover-row-color: #454545;
1504
+ --table-spinner-bg-color: #ffffff99;
1505
+ }
1506
+
1507
+ :root.dark-theme {
1508
+ --table-color: #fff;
1509
+ --table-active-color: #565656;
1510
+ --table-checkbox-color: #49aeda;
1511
+ --table-caret-color: #ffffff;
1512
+ --table-switch-color: #b3b3b3;
1513
+ --table-hover-row-border-color: #fff;
1514
+ --table-hover-row-background-color: #324f61;
1515
+ --table-hover-row-color: #e9ecef;
1516
+ --table-spinner-bg-color: rgba(0, 0, 0, 0.5);
1517
+ }
1518
+ </style>