@itfin/components 1.2.97 → 1.2.99

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.
@@ -0,0 +1,57 @@
1
+ const CANCELED = Symbol("canceled");
2
+
3
+ export
4
+ class CancelableEvent {
5
+ static type = "event";
6
+
7
+ static cancelable = false;
8
+
9
+ constructor(f) {
10
+ this[CANCELED] = false,
11
+ this.data = f
12
+ }
13
+
14
+ get type() {
15
+ return this.constructor.type
16
+ }
17
+
18
+ get cancelable() {
19
+ return this.constructor.cancelable
20
+ }
21
+
22
+ cancel() {
23
+ this[CANCELED] = true
24
+ }
25
+
26
+ canceled() {
27
+ return !!this[CANCELED]
28
+ }
29
+
30
+ clone(f) {
31
+ return new this.constructor({
32
+ ...this.data,
33
+ ...f
34
+ })
35
+ }
36
+ }
37
+
38
+ export default
39
+ class DraggableEvent extends CancelableEvent {
40
+ static type = "vue:drag:move";
41
+
42
+ get sourceComponent() {
43
+ return this.data.sourceComponent
44
+ }
45
+
46
+ get group() {
47
+ return this.data.group
48
+ }
49
+
50
+ get item() {
51
+ return this.data.item
52
+ }
53
+
54
+ get draggablePayload() {
55
+ return this.data.draggablePayload
56
+ }
57
+ }
@@ -0,0 +1,288 @@
1
+ import Vue from 'vue';
2
+ import { Draggable } from './draggable'
3
+ import DraggableEvent from './event'
4
+
5
+
6
+ Vue.directive("sortable-item", {
7
+ inserted(el, { value }) {
8
+ el.sortableItemPayload = value?.payload;
9
+ el.classList.add(draggableNode.options.draggableClass);
10
+ el.getAttribute("with-handle") && el.classList.add(draggableNode.options.dragHandleClass);
11
+ value?.mirror && (el.dataset.draggableMirror = JSON.stringify(value.mirror));
12
+ },
13
+ update(el, {value}) {
14
+ el.sortableItemPayload = value?.payload;
15
+ el.classList.add(draggableNode.options.draggableClass);
16
+ el.getAttribute("with-handle") && el.classList.add(draggableNode.options.dragHandleClass);
17
+ value?.mirror && (el.dataset.draggableMirror = JSON.stringify(value.mirror))
18
+ }
19
+ });
20
+
21
+
22
+ const SORT_TAG = "sort";
23
+ function someFunc({ source, over, overContainer, children }) {
24
+ const isDelete = !children.length
25
+ , isInContainer = source.parentNode !== overContainer
26
+ , Q = over && !isInContainer;
27
+ if (isDelete) {
28
+ return moveToContainer(source, overContainer)
29
+ }
30
+ return Q ? $(source, over) : isInContainer ? insertBefore(source, over, overContainer) : null
31
+ }
32
+
33
+ function moveToContainer(el, newContainer) {
34
+ const oldContainer = el.parentNode;
35
+ newContainer.appendChild(el);
36
+ return {
37
+ oldContainer,
38
+ newContainer
39
+ };
40
+ }
41
+ function $(el, container) {
42
+ console.info('$')
43
+ const tt = indexOf(el)
44
+ , ot = indexOf(container);
45
+ if (tt < ot) {
46
+ el.parentNode.insertBefore(el, container.nextElementSibling);
47
+ } else {
48
+ el.parentNode.insertBefore(el, container);
49
+ }
50
+ return {
51
+ oldContainer: el.parentNode,
52
+ newContainer: el.parentNode
53
+ };
54
+ }
55
+ function indexOf(A) {
56
+ return Array.prototype.indexOf.call(A.parentNode.children, A)
57
+ }
58
+ function insertBefore(A, H, tt) {
59
+ console.info('insertBefore')
60
+ const ot = A.parentNode;
61
+ return H ? H.parentNode.insertBefore(A, H) : tt.appendChild(A),
62
+ {
63
+ oldContainer: ot,
64
+ newContainer: A.parentNode
65
+ }
66
+ }
67
+ function c(A, H, tt) {
68
+ const ot = [...A.slice(0, H), ...A.slice(H + 1, A.length)];
69
+ return [...ot.slice(0, tt), A[H], ...ot.slice(tt, ot.length)]
70
+ }
71
+
72
+ export
73
+ const Sortable = {
74
+ name: 'Sortable',
75
+ render: function() {
76
+ return this._self._c(this.tag, {
77
+ ref: "sortable",
78
+ tag: "component"
79
+ }, [this._t("default", null, {
80
+ items: this.value
81
+ })], 2)
82
+ },
83
+ props: {
84
+ value: {
85
+ default: ()=>[],
86
+ type: Array
87
+ },
88
+ group: {
89
+ type: String,
90
+ default: "defaultGroup"
91
+ },
92
+ tag: {
93
+ type: String,
94
+ default: "div"
95
+ }
96
+ },
97
+ mounted() {
98
+ Draggable.addContainer(this.$refs.sortable);
99
+ this.$refs.sortable.dataset.group = this.group;
100
+ this.handler = event=>{
101
+ const H = [...this.value];
102
+ H.splice(event.detail.newIndex, 0, event.detail.item),
103
+ this.$emit("input", H),
104
+ this.$emit("receive", {
105
+ ...event.detail,
106
+ newItems: H
107
+ })
108
+ };
109
+
110
+ this.$refs.sortable.addEventListener(SORT_TAG, this.handler);
111
+ Draggable.on("drag:start", this.onDragStart);
112
+ },
113
+ beforeDestroy() {
114
+ this.$refs.sortable.removeEventListener(SORT_TAG, this.handler)
115
+ },
116
+ destroyed() {
117
+ Draggable.removeContainer(this.$refs.sortable),
118
+ Draggable.off("drag:start", this.onDragStart),
119
+ this.destroyMirror()
120
+ },
121
+ methods: {
122
+ stopListening() {
123
+ Draggable
124
+ .off("drag:move", this.dragMove)
125
+ .off("drag:over:container", this.onDragOverContainer)
126
+ .off("drag:out:container", this.onDragOutContainer)
127
+ .off("drag:over", this.onDragOver)
128
+ .off("drag:stop", this.onDragStop)
129
+ .off("mirror:created", this.createMirror)
130
+ .off("mirror:destroy", this.destroyMirror);
131
+ this.destroyMirror();
132
+ },
133
+ createMirror(el) {
134
+ console.info(el, this.dragging)
135
+ if (this.dragging && this.$scopedSlots.mirror) {
136
+ const H = this.$scopedSlots.mirror({
137
+ item: this.source.item
138
+ });
139
+ const tt = Vue.ZP.extend({
140
+ parent: this,
141
+ render() {
142
+ return H
143
+ }
144
+ });
145
+ const Y = new tt().$mount();
146
+ this.mirrorComponent = Y;
147
+ el.mirror.innerHTML = "";
148
+ el.mirror.appendChild(Y.$el);
149
+ }
150
+ },
151
+ destroyMirror() {
152
+ if (this.mirrorComponent) {
153
+ this.mirrorComponent.$destroy();
154
+ this.mirrorComponent = null;
155
+ }
156
+ },
157
+ getDraggableElementsForContainer(el) {
158
+ return [...el.children].filter(item => item !== Draggable.originalSource && item !== Draggable.mirror)
159
+ },
160
+ index(el) {
161
+ return this.getDraggableElementsForContainer(el.parentNode).indexOf(el)
162
+ },
163
+ onDragStart(event) {
164
+ if (event.originalEvent.target.tagName === "INPUT") {
165
+ event.cancel();
166
+ return
167
+ }
168
+ if (event.sourceContainer !== this.$refs.sortable) {
169
+ return;
170
+ }
171
+ Draggable
172
+ .on("drag:move", this.dragMove)
173
+ .on("drag:over:container", this.onDragOverContainer)
174
+ .on("drag:out:container", this.onDragOutContainer)
175
+ .on("drag:over", this.onDragOver)
176
+ .on("drag:stop", this.onDragStop)
177
+ .on("mirror:created", this.createMirror)
178
+ .on("mirror:destroy", this.destroyMirror);
179
+ const oldIndex = this.index(event.source);
180
+ this.$emit("start");
181
+ this.oldIndex = oldIndex;
182
+ this.source = {
183
+ oldIndex,
184
+ item: this.value[oldIndex]
185
+ };
186
+ },
187
+ dragMove(el) {
188
+ Draggable.trigger(new DraggableEvent({
189
+ ...el.data,
190
+ sourceComponent: this.$refs.sortable,
191
+ group: this.group,
192
+ draggablePayload: {
193
+ item: this.value[this.oldIndex],
194
+ ...el.originalSource.sortableItemPayload
195
+ }
196
+ }))
197
+ },
198
+ onDragOverContainer(event) {
199
+ const {source, over, overContainer} = event;
200
+ if (overContainer.dataset.group !== this.group) {
201
+ return;
202
+ }
203
+ const children = this.getDraggableElementsForContainer(overContainer);
204
+ this.destinationContainer = overContainer;
205
+
206
+ console.info('onDragOverContainer', overContainer, children)
207
+ someFunc({
208
+ source,
209
+ over,
210
+ overContainer,
211
+ children
212
+ });
213
+ },
214
+ onDragOutContainer(el) {
215
+ this.destinationContainer = null;
216
+ const overContainer = this.$refs.sortable
217
+ , children = this.getDraggableElementsForContainer(overContainer)
218
+ , over = children[this.source.oldIndex];
219
+ someFunc({
220
+ source: el.source,
221
+ over,
222
+ overContainer,
223
+ children
224
+ })
225
+ },
226
+ onDragOver(A) {
227
+ const { source, over, overContainer } = A;
228
+ if (overContainer.dataset.group !== this.group || over === A.originalSource || over === source) {
229
+ return;
230
+ }
231
+ const Y = this.getDraggableElementsForContainer(overContainer);
232
+ if (Y.includes(over)) {
233
+ someFunc({
234
+ source: source,
235
+ over: over,
236
+ overContainer: overContainer,
237
+ children: Y
238
+ });
239
+ }
240
+ },
241
+ onDragStop(item) {
242
+ this.stopListening();
243
+ if (!this.destinationContainer || this.destinationContainer.dataset.group !== this.group) {
244
+ return;
245
+ }
246
+ const { source } = this;
247
+ source.newIndex = this.index(item.source);
248
+ source.sortableItemPayload = { ...item.originalSource.sortableItemPayload };
249
+ console.info(item.source, item.originalSource)
250
+ item.originalSource.parentNode.insertBefore(item.source, item.originalSource);
251
+ if (item.sourceContainer === this.destinationContainer) {
252
+ this.onSortItems(source);
253
+ } else {
254
+ this.onRemoveItem(source);
255
+ this.onReceiveItem(source, this.destinationContainer);
256
+ }
257
+ this.$emit("stop");
258
+ },
259
+ onSortItems(item) {
260
+ if (item.oldIndex === item.newIndex) {
261
+ return;
262
+ }
263
+ const newItems = c(this.value, item.oldIndex, item.newIndex);
264
+ this.$emit("input", newItems);
265
+ this.$emit("move", {
266
+ newItems,
267
+ ...item
268
+ });
269
+ },
270
+ onReceiveItem(el, H) {
271
+ H.dispatchEvent(new CustomEvent(SORT_TAG,{
272
+ detail: {
273
+ item: el.item,
274
+ newIndex: el.newIndex,
275
+ oldIndex: el.oldIndex,
276
+ sortableItemPayload: el.sortableItemPayload
277
+ }
278
+ }))
279
+ },
280
+ onRemoveItem(el) {
281
+ const H = this.value.filter(tt=>tt !== el.item);
282
+ this.$emit("input", H)
283
+ }
284
+ }
285
+ };
286
+
287
+
288
+ Vue.component("Sortable", Sortable)
@@ -1,40 +1,78 @@
1
1
  <template>
2
2
 
3
3
  <div class="scrollable scrollable-x big-scrollbar">
4
- <itf-table-group
5
- :columns="columns"
6
- :rows="rows"
7
- :add-new-rows="addNewRows"
8
- >
9
- <template v-for="(_, name) in $slots" #[name]="slotData">
10
- <slot :name="name" v-bind="slotData || {}" />
11
- </template>
12
- <template v-for="(_, name) in $scopedSlots" #[name]="slotData">
13
- <slot :name="name" v-bind="slotData || {}" />
14
- </template>
15
- </itf-table-group>
4
+ <template v-for="(group, index) in groups">
5
+ <div class="table-view-body">
6
+ <itf-table-group
7
+ :key="index"
8
+ :style="`--table-row-height: ${rowHeight}px`"
9
+ :columns="columns"
10
+ @update:columns="onColumnsUpdate"
11
+ :rows="group.rows"
12
+ :title="group.name"
13
+ :add-new-rows="addNewRows"
14
+ :column-sorting="columnSorting"
15
+ :column-resizing="columnResizing"
16
+ :show-grouping="showGrouping"
17
+ :show-summary="showSummary"
18
+ :show-add-column="showAddColumn"
19
+ :row-height="rowHeight"
20
+ :show-header="index === 0"
21
+ @add-column="$emit('add-column', $event)"
22
+ >
23
+ <template v-for="(_, name) in $slots" #[name]="slotData">
24
+ <slot :name="name" v-bind="slotData || {}" />
25
+ </template>
26
+ <template v-for="(_, name) in $scopedSlots" #[name]="slotData">
27
+ <slot :name="name" v-bind="slotData || {}" />
28
+ </template>
29
+ </itf-table-group>
30
+ </div>
31
+ </template>
16
32
  </div>
17
33
 
18
34
  </template>
19
35
  <style lang="scss">
20
- //.scrollable {
21
- // -webkit-overflow-scrolling: touch;
22
- // overflow: hidden auto;
23
- //
24
- // &.scrollable-x {
25
- // overflow-x: auto;
26
- // }
27
- //}
36
+ .scrollable {
37
+ --itf-table-hover-bg: #f2f2f2;
38
+ --table-min-width: 45px;
39
+
40
+ body[data-theme="dark"] & {
41
+ --itf-table-hover-bg: #393b41;
42
+ }
43
+
44
+ -webkit-overflow-scrolling: touch;
45
+ //overflow: hidden auto;
46
+ //
47
+ //&.scrollable-x {
48
+ // overflow-x: auto;
49
+ //}
50
+ }
51
+ .last-sticky-column:after {
52
+ content: '';
53
+ background: linear-gradient(to right,#dfe0e2,rgba(255,0,0,0));
54
+ height: 100%;
55
+ width: 6px;
56
+ position: absolute;
57
+ bottom: 0;
58
+ right: -7px;
59
+
60
+ body[data-theme="dark"] & {
61
+ background: linear-gradient(to right, #707070,rgba(255,0,0,0));
62
+ }
63
+ }
28
64
  </style>
29
65
  <script>
30
66
  import { Vue, Component, Prop, PropSync } from 'vue-property-decorator';
31
67
  import itfButton from '../button/Button.vue';
32
68
  import itfIcon from '../icon/Icon.vue';
33
69
  import itfTableGroup from './TableGroup.vue';
70
+ import itfTableHeader from './TableHeader.vue';
34
71
 
35
72
  export default @Component({
36
73
  name: 'itfTable2',
37
74
  components: {
75
+ itfTableHeader,
38
76
  itfButton,
39
77
  itfIcon,
40
78
  itfTableGroup
@@ -43,6 +81,33 @@ export default @Component({
43
81
  class itfTable2 extends Vue {
44
82
  @Prop({ required: true, type: Array }) columns;
45
83
  @Prop({ required: true, type: Array }) rows;
84
+ @Prop({ type: Number, default: 40 }) rowHeight;
85
+ @Prop({ type: String, default: null }) groupBy;
46
86
  @Prop(Boolean) addNewRows;
87
+ @Prop(Boolean) columnSorting;
88
+ @Prop(Boolean) columnResizing;
89
+ @Prop(Boolean) showAddColumn;
90
+ @Prop(Boolean) showGrouping;
91
+ @Prop(Boolean) showSummary;
92
+
93
+ get groups() {
94
+ const { groupBy, rows } = this;
95
+ if (!groupBy) {
96
+ return [];
97
+ }
98
+ const groups = rows.reduce((acc, row) => {
99
+ const group = row[groupBy];
100
+ if (!acc[group]) {
101
+ acc[group] = [];
102
+ }
103
+ acc[group].push(row);
104
+ return acc;
105
+ }, {});
106
+ return Object.entries(groups).map(([name, rows]) => ({ name, rows }));
107
+ }
108
+
109
+ onColumnsUpdate(columns) {
110
+ this.$emit('update:columns', columns);
111
+ }
47
112
  }
48
113
  </script>
@@ -4,7 +4,7 @@
4
4
  class="scroller"
5
5
  page-mode
6
6
  :items="rows"
7
- :item-size="36"
7
+ :item-size="rowHeight"
8
8
  key-field="Id"
9
9
  v-slot="{ item }"
10
10
  direction="vertical"
@@ -22,7 +22,7 @@
22
22
  </a>
23
23
  </div>
24
24
  </div>
25
- <div class="indicator sticky tw-border-gray dark:tw-border-gray-invert tw-border-r tw-bg-white dark:tw-bg-white-invert tw-border-b ">
25
+ <div class="indicator sticky">
26
26
  <div class="fill on-rest table-view-row-count">
27
27
  <span>{{ item.Id }}</span>
28
28
  </div>
@@ -37,17 +37,23 @@
37
37
  </div>
38
38
  </div>
39
39
  </div>
40
- <div accept-group="items" class="hbox table-item-inner">
41
- <div v-for="(column, n) in columns" :data-column="n" :style="`width: ${column.width}px`" class="table-view-item-value d-flex position-relative h-100 align-items-stretch px-2">
42
- <slot :name="`column.${column.name}`" :item="item" :column="column">
43
- <template v-if="editTypes[column.Type]">
44
- <property-inline-edit :value="item[column.field]" :field="column" editable focused />
45
- </template>
46
- <div v-else v-text="item.Name" />
47
- </slot>
48
- </div>
49
-
50
- <div class="table-view-item-value extra"></div>
40
+ <div accept-group="items" class="table-item-inner">
41
+ <template v-for="(column, n) in visibleAttributes">
42
+ <div
43
+ v-if="column.visible !== false"
44
+ :data-column="n"
45
+ :style="`width: ${column.width}px; left: ${column.left}px;`"
46
+ :class="{'sticky': column.pinned, 'last-sticky-column': n === lastPinnedIndex}"
47
+ class="table-view-item-value d-flex h-100 align-items-stretch px-2">
48
+ <slot :name="`column.${column.name}`" :item="item" :column="column">
49
+ <template v-if="editTypes[column.Type]">
50
+ <property-inline-edit :value="item[column.field]" :field="column" editable focused />
51
+ </template>
52
+ <div v-else v-text="item.Name" />
53
+ </slot>
54
+ </div>
55
+ </template>
56
+ <div v-if="showAddColumn" class="table-view-item-value extra bg-light"></div>
51
57
  <div class="boundary top"></div>
52
58
  <div class="boundary right"></div>
53
59
  <div class="boundary bottom"></div>
@@ -63,7 +69,6 @@
63
69
  height: 100%;
64
70
  align-items: stretch;
65
71
  }
66
-
67
72
  .table-item-inner {
68
73
  height: 100%;
69
74
  flex-grow: 1;
@@ -74,24 +79,49 @@
74
79
 
75
80
  .table-view-item-value {
76
81
  text-overflow: ellipsis;
77
- background-color: rgb(255 255 255 / 1);
78
- border-right: 1px solid rgb(238 238 238 / 1);
79
- border-bottom: 1px solid rgb(238 238 238 / 1);
82
+ background-color: var(--bs-body-bg);
83
+ border-right: 1px solid var(--bs-border-color);
84
+ border-bottom: 1px solid var(--bs-border-color);
80
85
  align-items: stretch;
81
86
  height: 100%;
82
87
  display: flex;
83
88
  position: relative;
84
89
  line-height: 35px;
85
-
90
+ min-width: var(--table-min-width);
91
+
92
+ &.highlight-drop-column {
93
+ position: relative;
94
+
95
+ &:after {
96
+ content: "";
97
+ height: 100%;
98
+ z-index: 100;
99
+ width: 2px;
100
+ background: #47beff;
101
+ margin-left: -1px;
102
+ position: absolute;
103
+ left: 0;
104
+ }
105
+ &.right:after {
106
+ left: auto;
107
+ right: 0;
108
+ }
109
+ }
86
110
  &:hover {
87
- background-color: rgb(250 251 252 / 1);
111
+ background-color: var(--itf-table-hover-bg);
112
+ }
113
+
114
+ &.sticky {
115
+ z-index: 4;
116
+ position: -webkit-sticky;
117
+ position: sticky;
88
118
  }
89
119
  }
90
120
 
91
121
  .table-item-inner .extra {
92
122
  height: 100%;
93
123
  flex-grow: 1;
94
- border-color: rgb(238 238 238 / 1);
124
+ border-color: var(--bs-border-color);
95
125
  }
96
126
 
97
127
  .indicator {
@@ -101,12 +131,13 @@
101
131
  z-index: 4;
102
132
  position: -webkit-sticky;
103
133
  position: sticky;
104
- background-color: rgb(255 255 255 / 1);
105
- border-right: 1px solid rgb(238 238 238 / 1);
106
- border-bottom: 1px solid rgb(238 238 238 / 1);
134
+ background-color: var(--bs-body-bg);
135
+ border-right: 1px solid var(--bs-border-color);
136
+ border-bottom: 1px solid var(--bs-border-color);
107
137
  display: flex;
108
138
  align-items: center;
109
139
  justify-content: center;
140
+ min-width: var(--table-min-width);
110
141
  }
111
142
 
112
143
  .table-item-inner .boundary {
@@ -141,7 +172,7 @@
141
172
  bottom: 0;
142
173
  }
143
174
  .table-small-row .table-view-item {
144
- height: 36px;
175
+ height: var(--table-row-height);
145
176
  }
146
177
  .vue-recycle-scroller {
147
178
  position: relative;
@@ -164,8 +195,9 @@
164
195
  <script>
165
196
  import { Vue, Component, Prop } from 'vue-property-decorator';
166
197
  import { RecycleScroller } from 'vue-virtual-scroller'
198
+ import sortBy from 'lodash/sortBy';
167
199
  import PropertyInlineEdit from '../customize/PropertyInlineEdit.vue';
168
- import { INLINE_TYPES } from '@/components/customize/constants';
200
+ import { INLINE_TYPES } from '../customize/constants';
169
201
 
170
202
  export default @Component({
171
203
  name: 'itfTableBody',
@@ -177,9 +209,19 @@ export default @Component({
177
209
  class itfTableBody extends Vue {
178
210
  @Prop() columns;
179
211
  @Prop() rows;
212
+ @Prop(Boolean) showAddColumn;
213
+ @Prop({ type: Number, default: 40 }) rowHeight;
180
214
 
181
215
  editTypes = {};
182
216
 
217
+ get visibleAttributes() {
218
+ return this.columns;
219
+ }
220
+
221
+ get lastPinnedIndex() {
222
+ return this.columns.findIndex((column) => column.lastPinned);
223
+ }
224
+
183
225
  mounted() {
184
226
  for (const { Type } of INLINE_TYPES) {
185
227
  this.editTypes[Type] = true;