@itfin/components 1.2.103 → 1.2.105
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div class="itf-board">
|
|
3
3
|
|
|
4
4
|
<div ref="container" class="itf-board-titles">
|
|
5
|
-
<div v-for="(column, index) of
|
|
5
|
+
<div v-for="(column, index) of orderColumnsInBoard"
|
|
6
6
|
:data-column="index"
|
|
7
7
|
@mouseover="columnHighlight(index, 'enter')"
|
|
8
8
|
@mouseout="columnHighlight(index, 'leave')"
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
accept-group="board-columns"
|
|
19
19
|
@enter="columnHighlight(index, 'enter')"
|
|
20
20
|
@leave="columnHighlight(index, 'leave')"
|
|
21
|
+
@click="$emit('update:columnSettings', column)"
|
|
21
22
|
v-dropzone="{ payload: { index, column } }"
|
|
22
23
|
class="d-flex align-items-center">
|
|
23
24
|
<div
|
|
@@ -25,8 +26,8 @@
|
|
|
25
26
|
@drop="reorderColumns"
|
|
26
27
|
v-draggable="{ handle: true, payload: { index, column }, mirror: { yAxis:false } }"
|
|
27
28
|
class="flex-grow-1 d-flex align-items-center itf-board-column__header justify-content-between">
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
<div><slot name="header" :column="column">{{ column[columnNameKey] }}</slot></div>
|
|
30
|
+
<div class="text-muted me-1"><slot name="header-addon" :column="column">{{ (groupedItems[column.Id] || []).length }}</slot></div>
|
|
30
31
|
</div>
|
|
31
32
|
</itf-edit-button>
|
|
32
33
|
<div v-if="index === columns.length - 1"
|
|
@@ -39,7 +40,7 @@
|
|
|
39
40
|
</div>
|
|
40
41
|
</div>
|
|
41
42
|
<div class="align-items-end d-flex pb-1">
|
|
42
|
-
<slot name="add-column">
|
|
43
|
+
<slot name="add-column" @click="$emit('addColumn')">
|
|
43
44
|
<itf-button icon small>
|
|
44
45
|
<itf-icon name="plus" />
|
|
45
46
|
</itf-button>
|
|
@@ -48,7 +49,7 @@
|
|
|
48
49
|
</div>
|
|
49
50
|
|
|
50
51
|
<div class="itf-board-columns flex-grow-1">
|
|
51
|
-
<div v-for="(column, index) of
|
|
52
|
+
<div v-for="(column, index) of orderColumnsInBoard"
|
|
52
53
|
:key="`column-${index}`"
|
|
53
54
|
:data-column="index"
|
|
54
55
|
class="itf-board-column px-2 pb-2 pt-2" :class="{'empty': !groupedItems[column.Id]}">
|
|
@@ -66,6 +67,7 @@ import loading from '../../directives/loading';
|
|
|
66
67
|
import itfForm from '../form/Form';
|
|
67
68
|
import itfEditButton from '../editable/EditButton.vue';
|
|
68
69
|
import createDraggable from '../sortable/draggable';
|
|
70
|
+
import { debounce } from '../../helpers/debounce';
|
|
69
71
|
|
|
70
72
|
const { Node, ...draggableDirectives } = createDraggable();
|
|
71
73
|
|
|
@@ -87,6 +89,7 @@ export default @Component({
|
|
|
87
89
|
class Board extends Vue {
|
|
88
90
|
@Prop(Array) columns;
|
|
89
91
|
@Prop(Array) items;
|
|
92
|
+
@Prop(Array) boardColumnsOrder
|
|
90
93
|
@Prop(Array) columnOrders;
|
|
91
94
|
@Prop({ type: Function, default: (item) => item.Status?.Id }) groupFunc;
|
|
92
95
|
@Prop({ type: Function, default: (item, value) => { item.Status = value; } }) updateFunc;
|
|
@@ -100,6 +103,7 @@ class Board extends Vue {
|
|
|
100
103
|
}
|
|
101
104
|
|
|
102
105
|
mounted() {
|
|
106
|
+
this.itemStatusChanged = debounce(this.emitItemStatusChanged, 10);
|
|
103
107
|
if (this.columnSorting) {
|
|
104
108
|
Node.addContainer(this.$refs.container);
|
|
105
109
|
}
|
|
@@ -113,12 +117,21 @@ class Board extends Vue {
|
|
|
113
117
|
return grouped;
|
|
114
118
|
}
|
|
115
119
|
|
|
120
|
+
get orderColumnsInBoard() {
|
|
121
|
+
return this.sortByOrdering(this.columns, this.boardColumnsOrder || []);
|
|
122
|
+
}
|
|
123
|
+
|
|
116
124
|
orderItemsInColumn(column) {
|
|
117
|
-
const columnOrdering = this.columnOrders.find((c) => c.Id === column.Id) || { ItemIds: [] };
|
|
125
|
+
const columnOrdering = (this.columnOrders.find((c) => c.Id === column.Id) || { ItemIds: [] }).ItemIds;
|
|
118
126
|
let items = this.items.filter((item) => column.Id === this.groupFunc(item));
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
127
|
+
return this.sortByOrdering(items, columnOrdering);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
sortByOrdering (arr, ordering) {
|
|
131
|
+
const arrCopy = arr.slice();
|
|
132
|
+
return arrCopy.sort((a, b) => {
|
|
133
|
+
const aIndex = ordering.findIndex((id) => id === a.Id);
|
|
134
|
+
const bIndex = ordering.findIndex((id) => id === b.Id);
|
|
122
135
|
if (aIndex === -1 && bIndex === -1) {
|
|
123
136
|
return 0;
|
|
124
137
|
}
|
|
@@ -130,7 +143,6 @@ class Board extends Vue {
|
|
|
130
143
|
}
|
|
131
144
|
return aIndex - bIndex;
|
|
132
145
|
});
|
|
133
|
-
return items;
|
|
134
146
|
}
|
|
135
147
|
|
|
136
148
|
columnHighlight(index, state, className = 'over') {
|
|
@@ -153,7 +165,7 @@ class Board extends Vue {
|
|
|
153
165
|
reorderColumns({ detail }) {
|
|
154
166
|
const { index: fromIndex } = detail.draggablePayload;
|
|
155
167
|
const { index: toIndex, last } = detail.dropzonePayload;
|
|
156
|
-
const newValue = [...this.
|
|
168
|
+
const newValue = [...this.orderColumnsInBoard];
|
|
157
169
|
const [removed] = newValue.splice(fromIndex, 1);
|
|
158
170
|
|
|
159
171
|
if (last) {
|
|
@@ -186,36 +198,41 @@ class Board extends Vue {
|
|
|
186
198
|
|
|
187
199
|
items[itemIndex] = newItem;
|
|
188
200
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
newOrders.
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
newOrders.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
placeIndex =
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
201
|
+
this.lastUpdatedIndex = typeof this.lastUpdatedIndex === 'undefined' ? toIndex : this.lastUpdatedIndex; // запамятовуємо індекс, бо виклики може бути два, коли колонка і карточка обробляє
|
|
202
|
+
this.itemStatusChanged(items[itemIndex], column, items, () => {
|
|
203
|
+
const toIndex = this.lastUpdatedIndex; // беремо індекс з пам'яті, бо викликів може бути два, але індекс тільки в картці, якщо нема, то це колонка
|
|
204
|
+
this.lastUpdatedIndex = undefined; // обнуляємо, щоб при наступному перетягувані вже не брало його
|
|
205
|
+
const newOrders = [...this.columnOrders];
|
|
206
|
+
let sorting = newOrders.find((c) => c.Id === column.Id);
|
|
207
|
+
if (!sorting) {
|
|
208
|
+
sorting = { Id: column.Id, ItemIds: [] };
|
|
209
|
+
newOrders.push(sorting)
|
|
210
|
+
}
|
|
211
|
+
let fromSorting = newOrders.find((c) => c.Id === fromColumn.Id);
|
|
212
|
+
if (!fromSorting) {
|
|
213
|
+
fromSorting = { Id: fromColumn.Id, ItemIds: [] };
|
|
214
|
+
newOrders.push(fromSorting)
|
|
215
|
+
}
|
|
216
|
+
// update from
|
|
217
|
+
fromSorting.ItemIds = this.groupedItems[fromColumn.Id].map((i) => i.Id).filter((t) => t !== newItem.Id);
|
|
218
|
+
// update to
|
|
219
|
+
const items = [...this.groupedItems[column.Id]];
|
|
220
|
+
let placeIndex = last ? items.length : toIndex;
|
|
221
|
+
if (typeof placeIndex === 'undefined') { // ідекса може не бути, якщо на колонку переносять, тоді в кінець
|
|
222
|
+
placeIndex = items.length;
|
|
223
|
+
}
|
|
224
|
+
const itemsInColumn = items.map((t) => t.Id).filter((t) => t !== newItem.Id);
|
|
225
|
+
itemsInColumn.splice(placeIndex, 0, newItem.Id);
|
|
226
|
+
sorting.ItemIds = itemsInColumn;
|
|
227
|
+
this.$emit('update:columnOrders', newOrders);
|
|
228
|
+
});
|
|
229
|
+
}
|
|
212
230
|
|
|
213
|
-
|
|
231
|
+
emitItemStatusChanged(item, column, items, updateOrder) {
|
|
232
|
+
updateOrder();
|
|
214
233
|
|
|
215
234
|
this.$emit('update:item', [item, column]);
|
|
216
235
|
this.$emit('update:items', items);
|
|
217
|
-
|
|
218
|
-
this.$emit('update:columnOrders', newOrders);
|
|
219
236
|
}
|
|
220
237
|
}
|
|
221
238
|
</script>
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="itf-board-card shadow-sm">
|
|
2
|
+
<div class="itf-board-card shadow-sm" @click="$emit('click')" tabindex="0">
|
|
3
3
|
<slot name="header"></slot>
|
|
4
4
|
|
|
5
5
|
<div class="itf-board-card-inner d-flex">
|
|
6
|
-
<div class="flex-grow-1">
|
|
6
|
+
<div class="flex-grow-1 itf-board-card-inner-content">
|
|
7
7
|
<slot>{{text}}</slot>
|
|
8
8
|
</div>
|
|
9
|
-
<div>
|
|
10
|
-
<itf-button icon small>
|
|
9
|
+
<div v-if="dropdown">
|
|
10
|
+
<itf-button icon small @click.prevent.stop="">
|
|
11
11
|
<itf-icon name="menu_horizontal" />
|
|
12
12
|
</itf-button>
|
|
13
13
|
</div>
|
|
14
14
|
</div>
|
|
15
15
|
|
|
16
|
-
<
|
|
16
|
+
<div class="itf-board-card-footer pt-1 d-flex">
|
|
17
|
+
<slot name="footer"></slot>
|
|
18
|
+
</div>
|
|
17
19
|
</div>
|
|
18
20
|
</template>
|
|
19
21
|
<script>
|
|
@@ -37,5 +39,6 @@ export default @Component({
|
|
|
37
39
|
})
|
|
38
40
|
class BoardColumn extends Vue {
|
|
39
41
|
@Prop() text;
|
|
42
|
+
@Prop(Boolean) dropdown;
|
|
40
43
|
}
|
|
41
44
|
</script>
|
|
@@ -5,18 +5,17 @@
|
|
|
5
5
|
--itf-board-placeholder-border-color: #0567eb;
|
|
6
6
|
--itf-board-placeholder-bg-color: #ebeffe;
|
|
7
7
|
--itf-board-card-bg-color: #fff;
|
|
8
|
+
--itf-board-card-hover-color: rgba(0, 0, 0, .05);
|
|
9
|
+
--itf-board-card-border-color: #ccc;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
[data-theme="dark"] {
|
|
11
|
-
--itf-board-column-hover-color:
|
|
13
|
+
--itf-board-column-hover-color: rgba(255, 255, 255, .05);
|
|
12
14
|
--itf-board-placeholder-color: #FFCC00;
|
|
13
15
|
--itf-board-placeholder-border-color: #FFCC00;
|
|
14
16
|
--itf-board-placeholder-bg-color: #383b41;
|
|
15
17
|
--itf-board-card-bg-color: #313337;
|
|
16
|
-
|
|
17
|
-
.itf-board {
|
|
18
|
-
--bs-border-color: #424448;
|
|
19
|
-
}
|
|
18
|
+
--itf-board-card-border-color: #424448;
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
.itf-board {
|
|
@@ -108,7 +107,6 @@
|
|
|
108
107
|
.itf-board-column {
|
|
109
108
|
position: relative;
|
|
110
109
|
width: var(--itf-board-column-width);
|
|
111
|
-
min-width: var(--itf-board-column-width);
|
|
112
110
|
border-radius: 0 0 var(--bs-border-radius) var(--bs-border-radius);
|
|
113
111
|
|
|
114
112
|
&__title {
|
|
@@ -152,7 +150,7 @@
|
|
|
152
150
|
}
|
|
153
151
|
|
|
154
152
|
.itf-board-titles {
|
|
155
|
-
border-bottom: 1px solid var(--
|
|
153
|
+
border-bottom: 1px solid var(--itf-board-card-border-color);
|
|
156
154
|
}
|
|
157
155
|
|
|
158
156
|
.itf-board-columns {
|
|
@@ -212,18 +210,32 @@
|
|
|
212
210
|
.itf-board-card {
|
|
213
211
|
background-color: var(--itf-board-card-bg-color);
|
|
214
212
|
border-radius: var(--bs-border-radius);
|
|
215
|
-
border: 1px solid var(--
|
|
213
|
+
border: 1px solid var(--itf-board-card-border-color);
|
|
216
214
|
margin-bottom: .75rem;
|
|
217
215
|
overflow: hidden;
|
|
218
216
|
user-select: none;
|
|
217
|
+
padding: .5rem;
|
|
218
|
+
cursor: pointer;
|
|
219
|
+
|
|
220
|
+
&:hover, &:focus {
|
|
221
|
+
background-color: var(--itf-board-card-hover-bg-color);
|
|
222
|
+
}
|
|
219
223
|
|
|
220
224
|
.itf-board-card-inner {
|
|
221
|
-
padding: .5rem;
|
|
222
225
|
font-size: .875rem;
|
|
223
226
|
word-wrap: break-word;
|
|
224
227
|
-webkit-line-clamp: unset;
|
|
225
|
-
overflow
|
|
228
|
+
overflow: hidden;
|
|
226
229
|
word-break: break-word;
|
|
230
|
+
|
|
231
|
+
.itf-board-card-inner-content {
|
|
232
|
+
padding-right: 0.2rem;
|
|
233
|
+
overflow-x: hidden;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.itf-board-card-footer {
|
|
238
|
+
overflow-x: hidden;
|
|
227
239
|
}
|
|
228
240
|
}
|
|
229
241
|
|
|
@@ -232,6 +244,7 @@
|
|
|
232
244
|
flex-direction: column;
|
|
233
245
|
max-height: 100%;
|
|
234
246
|
flex-grow: 1;
|
|
247
|
+
max-width: 100%;
|
|
235
248
|
}
|
|
236
249
|
|
|
237
250
|
.itf-board-cards-wrapper {
|
|
@@ -243,7 +256,7 @@
|
|
|
243
256
|
.draggable-source--is-dragging {
|
|
244
257
|
position: relative !important;
|
|
245
258
|
.itf-board-card {
|
|
246
|
-
background-color: var(--
|
|
259
|
+
background-color: var(--itf-board-card-border-color);
|
|
247
260
|
|
|
248
261
|
& > div {
|
|
249
262
|
opacity: 0;
|