@itfin/components 1.3.34 → 1.3.35

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,10 +1,12 @@
1
1
  <template>
2
2
 
3
- <div class="itf-table-header">
3
+ <div class="itf-table-header" :class="{'no-header': !visibleHeader}">
4
4
  <div ref="container" class="table-row-template">
5
5
  <div accept-group="items" class="table-view-body-space" v-dropzone="{ payload: 0 }"></div>
6
6
  <div class="shadow-area"></div>
7
- <div class="table-view-header-value reserved sticky"></div>
7
+ <div class="table-view-header-value reserved sticky">
8
+ <itf-checkbox v-if="visibleHeader" ungrouped value="all" v-model="selectAll" ref="selectAll" />
9
+ </div>
8
10
 
9
11
  <template v-for="(column, n) in visibleAttributes">
10
12
  <div
@@ -16,36 +18,38 @@
16
18
  :class="{'sticky': column.pinned, 'last-sticky-column': n === lastPinnedIndex, 'flex-grow-1': column.grow, [`justify-content-${column.align || 'start'}`]: true}"
17
19
  class="table-view-header-value"
18
20
  :style="`width: ${column.width}px; left: ${column.left}px;`">
19
- <div accept-group="tablecolumns"
21
+ <!-- Не треба видаляти колонки, бо вони потрібні для збереження ширини -->
22
+ <div v-if="visibleHeader" accept-group="tablecolumns"
20
23
  class="table-view-header-space"
21
24
  @enter="columnHighlight(n, 'enter')"
22
25
  @leave="columnHighlight(n, 'leave')"
23
26
  v-dropzone="{ payload: { index: n, attribute: column } }">
24
27
  <div class="table-view-header-dropzone"></div>
25
28
  </div>
26
- <div group="tablecolumns"
29
+ <div v-if="visibleHeader" group="tablecolumns"
27
30
  class="table-header"
28
31
  @drop="reorderColumns"
29
32
  v-drag-handle
30
33
  v-draggable="{ handle: true, payload: { index: n, item: column }, mirror: {yAxis:false} }">
31
- <itf-dropdown text append-to-body shadow ref="dropdown" class="w-100" disabled>
34
+ <itf-dropdown text append-to-body shadow ref="dropdown" class="w-100">
32
35
  <template #button>
33
- <span :title="column.text">
34
- <!-- <itf-icon name="type_select" :size="16" />-->
35
- {{column.text}}
36
+ <span :title="column.title[locale] || column.title['en_US']">
37
+ <span v-if="column.icon" :class="column.icon"></span>
38
+ {{column.title[locale] || column.title['en_US']}}
36
39
  </span>
40
+ <!-- <itf-icon name="arrow_up" :size="16" class="ms-1" />-->
37
41
  </template>
38
42
 
39
43
  <div v-if="column.sortable">
40
44
  <a class="dropdown-item d-flex align-items-center" href="javascript:;">
41
45
  <itf-icon name="arrow_up" :size="16" class="me-1" />
42
- Sort By Asc
46
+ Sort ascending
43
47
  </a>
44
48
  </div>
45
49
  <div v-if="column.sortable">
46
50
  <a class="dropdown-item d-flex align-items-center" href="javascript:;">
47
51
  <itf-icon name="arrow_down" :size="16" class="me-1" />
48
- Sort By Desc
52
+ Sort descending
49
53
  </a>
50
54
  </div>
51
55
  <div v-if="column.groupable">
@@ -57,19 +61,19 @@
57
61
  <div>
58
62
  <a class="dropdown-item d-flex align-items-center" href="javascript:;" @click="togglePinned(n)">
59
63
  <itf-icon :name="column.pinned ? 'checkbox_checked' : 'checkbox_empty'" :size="16" class="me-1" />
60
- <span v-if="column.pinned">Unpin Column</span>
61
- <span v-else>Pin Column</span>
64
+ <span v-if="column.pinned">Unfreeze column</span>
65
+ <span v-else>Freeze up to column</span>
62
66
  </a>
63
67
  </div>
64
68
  <div>
65
69
  <a class="dropdown-item d-flex align-items-center" href="javascript:;" @click="hideColumn(n)">
66
70
  <itf-icon name="eye_no" :size="16" class="me-1" />
67
- Hide
71
+ Hide in view
68
72
  </a>
69
73
  </div>
70
74
  </itf-dropdown>
71
75
  </div>
72
- <div v-if="n === sortedColumns.length - 1"
76
+ <div v-if="visibleHeader && n === sortedColumns.length - 1"
73
77
  @enter="columnHighlightLast('enter')"
74
78
  @leave="columnHighlightLast('leave')"
75
79
  accept-group="tablecolumns"
@@ -77,13 +81,47 @@
77
81
  v-dropzone="{payload:{ last: true }}">
78
82
  <div class="table-view-header-dropzone"></div>
79
83
  </div>
80
- <div v-if="columnResizing" ref="resizeHandle" class="resize-handle"></div>
84
+ <div v-if="visibleHeader && columnResizing" ref="resizeHandle" class="resize-handle"></div>
81
85
  </div>
82
86
  </template>
83
- <div v-if="showAddColumn" class="table-view-header-value">
84
- <span class="table-header table-header-add-column" data-test="table-header-add-column" @click.prevent="$emit('add-column')">
85
- <itf-icon name="plus" />
86
- </span>
87
+ <div v-if="showAddColumn" class="table-view-header-value flex-grow-1 justify-content-start">
88
+ <itf-dropdown v-if="visibleHeader" ref="newDd" text append-to-body shadow autoclose="outside" class="table-header table-header-add-column" data-test="table-header-add-column">
89
+ <template #button>
90
+ <span class="nom-layout-three-columns"></span>
91
+ </template>
92
+
93
+ <itf-sortable :value="sortedColumns" @input="onSortColumns">
94
+ <template v-for="(column, k) in sortedColumns">
95
+ <div :key="`column${k}`" class="d-flex align-items-center justify-content-between px-2 py-1">
96
+ <template v-if="column.visible !== false">
97
+ <div class="d-flex justify-content-between flex-grow-1">
98
+ <div>
99
+ <span class="nom-grip-vertical"></span>
100
+ <span v-if="column.icon" :class="column.icon"></span>
101
+ {{column.title[locale] || column.title['en_US']}}
102
+ </div>
103
+ <span v-if="column.pinned" class="nom-pin-fill"></span>
104
+ </div>
105
+ <a sortable-skip href="" class="text-decoration-none" @click.prevent="hideColumn(k)">
106
+ <span class="nom-trash"></span>
107
+ </a>
108
+ </template>
109
+ </div>
110
+ </template>
111
+ </itf-sortable>
112
+
113
+ <div v-if="invisibleColumns.length">
114
+ <div class="dropdown-header">Add column</div>
115
+ <template v-for="(column, k) in invisibleColumns">
116
+ <a href="" @click.prevent="addColumn(column)" :key="`inv-column${k}`" class="dropdown-item d-flex align-items-center justify-content-between px-2 py-1">
117
+ <div>
118
+ <span class="nom-plus"></span>
119
+ {{column.title[locale] || column.title['en_US']}}
120
+ </div>
121
+ </a>
122
+ </template>
123
+ </div>
124
+ </itf-dropdown>
87
125
  </div>
88
126
  </div>
89
127
  </div>
@@ -95,6 +133,15 @@
95
133
  top: 0;
96
134
  z-index: calc(var(--row-count) + 1);
97
135
 
136
+ &.no-header {
137
+ --table-row-height: 0px;
138
+ --table-small-row-size: 0px;
139
+
140
+ .table-view-header-value {
141
+ border-top: 0 none;
142
+ border-bottom: 0 none;
143
+ }
144
+ }
98
145
  .table-header.draggable-mirror{
99
146
  z-index: 100000001;
100
147
  background: rgba(235,237,239,0.75);
@@ -108,10 +155,11 @@
108
155
  .table-view-header-value {
109
156
  cursor: pointer;
110
157
  align-items: center;
158
+ justify-content: center;
111
159
  height: var(--table-row-height);
112
160
  white-space: nowrap;
113
161
  //border-top: 0;
114
- border-top: 1px solid var(--bs-border-color);
162
+ border-top: 1px solid transparent;
115
163
  border-left: 0;
116
164
  border-right-width: thin;
117
165
  border-bottom-width: thin;
@@ -119,12 +167,15 @@
119
167
  position: relative;
120
168
  display: flex;
121
169
  min-width: var(--itf-table-min-width);
122
- border-right-color: var(--bs-border-color);
170
+ border-right-color: transparent;
123
171
  border-bottom-color: var(--bs-border-color);
124
172
  background-color: var(--bs-body-bg);
125
173
  //background-color: rgb(238 238 238 / 1);
126
174
  font-weight: 500;
127
175
 
176
+ &.last-sticky-column {
177
+ border-right-color: var(--bs-border-color);
178
+ }
128
179
  &.reserved {
129
180
  left: var(--shadow-area-width);
130
181
  width: var(--indicator-area-width);
@@ -140,11 +191,16 @@
140
191
  overflow: hidden;
141
192
  width: 100%;
142
193
  text-overflow: ellipsis;
143
- margin-right: -5px;
144
194
  padding-left: 0.75rem;
145
195
  padding-right: 0.75rem;
146
196
  gap: 0.25rem;
147
197
  align-items: center;
198
+ & > div {
199
+ min-height: var(--table-row-height);
200
+ align-items: center;
201
+ justify-content: space-between;
202
+ display: flex;
203
+ }
148
204
 
149
205
  &.table-header-add-column {
150
206
  text-overflow: clip;
@@ -220,7 +276,7 @@
220
276
  left: -50px;
221
277
  }
222
278
  .table-view-header-value:not(.reserved):hover {
223
- background-color: rgb(218 218 218 / 1);
279
+ background-color: var(--bs-tertiary-bg);
224
280
  }
225
281
  .draggable-source--is-dragging {
226
282
  z-index: 100;
@@ -228,30 +284,82 @@
228
284
  }
229
285
  </style>
230
286
  <script>
231
- import { Vue, Component, Prop, PropSync } from 'vue-property-decorator';
287
+ import {Vue, Component, Prop, PropSync, Watch, Inject} from 'vue-property-decorator';
288
+ import itfCheckbox from '../checkbox/Checkbox.vue';
232
289
  import itfButton from '../button/Button.vue';
290
+ import itfPopover from '../popover/Popover.vue';
233
291
  import itfIcon from '../icon/Icon.vue';
234
292
  import createDraggable from '../sortable/draggable';
235
293
  import itfDropdown from '../dropdown/Dropdown.vue';
294
+ import itfSortable from '../sortable/Sortable.vue';
295
+ import settings from "../../ITFSettings";
236
296
 
237
297
  const { Node, ...draggableDirectives } = createDraggable();
238
298
 
239
299
  export default @Component({
240
300
  name: 'itfTableHeader',
241
301
  components: {
302
+ itfSortable,
303
+ itfCheckbox,
242
304
  itfDropdown,
243
305
  itfButton,
306
+ itfPopover,
244
307
  itfIcon
245
308
  },
246
309
  directives: {
247
310
  ...draggableDirectives
248
311
  }
249
312
  })
250
- class itfTable extends Vue {
313
+ class itfTableHeader extends Vue {
314
+ @Inject() tableEl;
315
+
251
316
  @PropSync('columns', { type: Array, default: () => ([]) }) sortedColumns;
317
+ @Prop({ type: Array, default: () => ([]) }) rows;
318
+ @Prop({ type: Array, default: () => ([]) }) selectedIds;
319
+ @Prop({ type: Object, default: () => ({}) }) schema;
252
320
  @Prop(Boolean) columnSorting;
253
321
  @Prop(Boolean) columnResizing;
254
322
  @Prop(Boolean) showAddColumn;
323
+ @Prop(Boolean) visibleHeader;
324
+
325
+ @Watch('selectedIds')
326
+ @Watch('rows')
327
+ onSelectedIdChanged() {
328
+ if (this.$refs.selectAll && this.selectedIds.length && this.selectedIds.length < this.rows.length) {
329
+ this.$refs.selectAll.input.indeterminate = true;
330
+ } else if (this.$refs.selectAll && this.$refs.selectAll.input) {
331
+ this.$refs.selectAll.input.indeterminate = false;
332
+ }
333
+ }
334
+
335
+ get locale() {
336
+ return settings.defaultLocale;
337
+ }
338
+
339
+ get selectAll() {
340
+ return this.selectedIds.length === this.rows.length;
341
+ }
342
+
343
+ set selectAll(val) {
344
+ if (val) {
345
+ this.$emit('update:selectedIds', this.rows.map(r => r.Id));
346
+ } else {
347
+ this.$emit('update:selectedIds', []);
348
+ }
349
+ }
350
+
351
+ addColumn(column) {
352
+ const index = this.sortedColumns.findIndex((c) => c === column);
353
+ const newValue = [...this.sortedColumns];
354
+ newValue[index].visible = true;
355
+ this.$emit('update:columns', newValue);
356
+ console.info(this.$refs);
357
+ this.$refs.newDd.hide();
358
+ }
359
+
360
+ get invisibleColumns() {
361
+ return this.sortedColumns.filter((column) => column.visible === false);
362
+ }
255
363
 
256
364
  get visibleAttributes() {
257
365
  return this.sortedColumns;
@@ -261,10 +369,6 @@ class itfTable extends Vue {
261
369
  return this.sortedColumns.findIndex((column) => column.lastPinned);
262
370
  }
263
371
 
264
- onSort(items) {
265
- console.info(items);
266
- }
267
-
268
372
  reorderColumns({ detail }) {
269
373
  const { index: fromIndex } = detail.draggablePayload;
270
374
  const { index: toIndex, last } = detail.dropzonePayload;
@@ -286,18 +390,16 @@ class itfTable extends Vue {
286
390
  } else {
287
391
  this.columnHighlight(toIndex, 'leave');
288
392
  }
289
- for (const dropdown of this.$refs.dropdown) {
290
- dropdown.hide();
291
- }
292
393
  }
293
394
 
294
395
  columnHighlight(index, state) {
295
- Array.from(document.querySelectorAll(`[data-column="${index}"]`)).forEach(t=>{
396
+ Array.from(this.tableEl.$el.querySelectorAll(`[data-column="${index}"]`)).forEach(t=>{
296
397
  state === "enter" ? t.classList.add("highlight-drop-column") : t.classList.remove("highlight-drop-column")
297
398
  });
298
399
  }
400
+
299
401
  columnHighlightLast(state) {
300
- Array.from(document.querySelectorAll(`[data-column="${this.sortedColumns.length - 1}"]`)).forEach(t=>{
402
+ Array.from(this.tableEl.$el.querySelectorAll(`[data-column="${this.sortedColumns.length - 1}"]`)).forEach(t=>{
301
403
  if (state === "enter") {
302
404
  t.classList.add("highlight-drop-column");
303
405
  t.classList.add("right");
@@ -310,7 +412,14 @@ class itfTable extends Vue {
310
412
 
311
413
  initResizing() {
312
414
  const resizeHandle = this.$refs.resizeHandle;
415
+ if (!resizeHandle) {
416
+ return;
417
+ }
313
418
  resizeHandle.forEach((handle) => {
419
+ if (handle.binded) {
420
+ return;
421
+ }
422
+ handle.binded = true;
314
423
  handle.addEventListener('mousedown', (event) => {
315
424
  const column = event.target.closest('.table-view-header-value');
316
425
  const body = event.target.closest('.table-view-body');
@@ -348,12 +457,16 @@ class itfTable extends Vue {
348
457
  Node.addContainer(this.$refs.container);
349
458
  }
350
459
  if (this.columnResizing) {
351
- this.initResizing();
460
+ this.onColumnsChanged();
352
461
  }
353
462
  }
354
463
 
464
+ @Watch('columns')
465
+ onColumnsChanged() {
466
+ this.$nextTick(() => this.initResizing());
467
+ }
468
+
355
469
  changeColumn(index, params) {
356
- this.$refs.dropdown[index].hide();
357
470
  let newValue = [...this.sortedColumns];
358
471
  const newItem = { ...newValue[index] };
359
472
  Object.assign(newItem, params);
@@ -368,5 +481,9 @@ class itfTable extends Vue {
368
481
  togglePinned(index) {
369
482
  this.changeColumn(index, { pinned: !this.sortedColumns[index].pinned });
370
483
  }
484
+
485
+ onSortColumns(items) {
486
+ this.$emit('update:columns', items);
487
+ }
371
488
  }
372
489
  </script>
@@ -90,6 +90,97 @@ storiesOf('Common', module)
90
90
  text: `Рахунок ❌`,
91
91
  Name: `Item #${i}`
92
92
  }))),
93
+ columns2: [
94
+ {
95
+ text: 'Рахунок',
96
+ name: 'Account',
97
+ width: 200,
98
+ min: 250,
99
+ max: 250
100
+ },
101
+ {
102
+ text: 'Рахунок',
103
+ name: 'Account',
104
+ width: 200,
105
+ min: 250,
106
+ max: 250
107
+ }
108
+ ],
109
+ list2: [
110
+ {
111
+ Id: 1,
112
+ summary: true,
113
+ Description: 'Total',
114
+ EmployeeId: 'Vitalii Savchuk',
115
+ Total: 100,
116
+ FTE: 1.0,
117
+ Position: { Title: 'Developer', Id: 1 },
118
+ MinutesInternal: '10h 20m',
119
+ MinutesExternal: '10h 20m',
120
+ AmountInternal: 1000,
121
+ AmountExternal: 2000,
122
+ AmountShare: 200,
123
+ Notes: 'Test',
124
+ Rate: '10',
125
+ Time: '10h 00m',
126
+ Amount: 2000
127
+ }
128
+ ],
129
+ schema: {
130
+ "properties": [
131
+ {
132
+ "property": "Description",
133
+ "title": { "en_US": "Description", "uk_UA": "Прізвище" },
134
+ "type": "text",
135
+ "editable": true,
136
+ "sortable": false,
137
+ width: 200,
138
+ minWidth: 100,
139
+ maxWidth: 300
140
+ },
141
+ {
142
+ "property": "EmployeeId",
143
+ "title": { "en_US": "Employee", "uk_UA": "Імʼя" },
144
+ "type": "employee",
145
+ "copy": true,
146
+ "editable": true,
147
+ "sortable": false,
148
+ icon: 'nom-person',
149
+ width: 200,
150
+ minWidth: 100,
151
+ maxWidth: 300
152
+ },
153
+ {
154
+ "property": "Rate",
155
+ "title": { "en_US": "Rate", "uk_UA": "Прізвище" },
156
+ "type": "money",
157
+ "editable": true,
158
+ "sortable": false,
159
+ width: 200,
160
+ minWidth: 100,
161
+ maxWidth: 300
162
+ },
163
+ {
164
+ "property": "Time",
165
+ "title": { "en_US": "Time", "uk_UA": "Прізвище" },
166
+ "type": "time",
167
+ "editable": true,
168
+ "sortable": false,
169
+ width: 200,
170
+ minWidth: 100,
171
+ maxWidth: 300
172
+ },
173
+ {
174
+ "property": "Amount",
175
+ "title": { "en_US": "Amount", "uk_UA": "Прізвище" },
176
+ "type": "money",
177
+ "sortable": false,
178
+ width: 200,
179
+ minWidth: 100,
180
+ maxWidth: 300
181
+ }
182
+ ]
183
+ },
93
184
  columns: [{
94
185
  text: 'Рахунок',
95
186
  name: 'Account',
@@ -184,8 +275,8 @@ storiesOf('Common', module)
184
275
  }
185
276
  },
186
277
  methods: {
187
- onAdd() {
188
- console.info('add')
278
+ onAdd(title) {
279
+ this.list2.push({ Id: Math.random() });
189
280
  }
190
281
  },
191
282
  template: `<div>
@@ -202,9 +293,51 @@ storiesOf('Common', module)
202
293
  &lt;/itf-table>
203
294
  </pre>
204
295
 
296
+ schema.json
297
+ <pre>
298
+ {
299
+ "properties": [
300
+ {
301
+ "property": "FirstName",
302
+ "title": { "en": "FirstName", "uk": "Імʼя" },
303
+ "type": "text",
304
+ "sortable": true
305
+ },
306
+ {
307
+ "property": "LastName",
308
+ "title": { "en": "LastName", "uk": "Прізвище" },
309
+ "type": "text",
310
+ "sortable": true
311
+ },
312
+ {
313
+ "property": "PositionId",
314
+ "title": { "en": "Position", "uk": "Прізвище" },
315
+ "type": "position",
316
+ "sortable": true
317
+ }
318
+ ]
319
+ }
320
+ </pre>
205
321
  <h3>Example</h3>
206
-
207
322
  <itf-table2
323
+ add-new-rows
324
+ @new="onAdd"
325
+ state-name="test"
326
+ :schema="schema"
327
+ :columns.sync="columns2" :rows="list2" column-sorting column-resizing show-add-column show-grouping @add-column="onAdd">
328
+ <template #format.employee="{ item }">
329
+ {{item.EmployeeId}}
330
+ </template>
331
+ <template #edit.employee="{ item }">
332
+ {{item.EmployeeId}}
333
+ </template>
334
+ </itf-table2>
335
+
336
+ <br />
337
+ <br />
338
+ <br />
339
+ <!--itf-table2
340
+ add-new-rows
208
341
  group-by="text"
209
342
  :columns.sync="columns" :rows="list" column-sorting column-resizing show-add-column show-grouping @add-column="onAdd">
210
343
  <template #column.Account="{ item }">
@@ -213,7 +346,7 @@ storiesOf('Common', module)
213
346
  <template #column.FTE="{ item }">
214
347
  {{item}}
215
348
  </template>
216
- </itf-table2>
349
+ </itf-table2-->
217
350
 
218
351
  <!--itf-table
219
352
  :columns="columns"
@@ -301,6 +434,21 @@ storiesOf('Common', module)
301
434
  min: 80,
302
435
  max: 80
303
436
  }],
437
+ list2: [
438
+ {
439
+ summary: true,
440
+ Employee: 'Total',
441
+ Total: 100,
442
+ FTE: 1.0,
443
+ Position: 'Developer',
444
+ MinutesInternal: '10h 20m',
445
+ MinutesExternal: '10h 20m',
446
+ AmountInternal: 1000,
447
+ AmountExternal: 2000,
448
+ AmountShare: 200,
449
+ Notes: 'Test'
450
+ }
451
+ ],
304
452
  list: [
305
453
  {
306
454
  summary: true,