@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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itfin/components",
3
- "version": "1.2.97",
3
+ "version": "1.2.99",
4
4
  "author": "Vitalii Savchuk <esvit666@gmail.com>",
5
5
  "scripts": {
6
6
  "serve": "vue-cli-service serve",
@@ -26,21 +26,22 @@
26
26
  "@vue/cli-service": "^5.0.1",
27
27
  "@vue/composition-api": "^1.7.1",
28
28
  "air-datepicker": "^3.3.5",
29
- "bootstrap": "=5.2.3",
30
- "core-js": "^3.31.1",
29
+ "bootstrap": "^5.2.3",
30
+ "core-js": "^3.7.0",
31
31
  "debug": "^4.2.0",
32
32
  "intersection-observer": "^0.12.2",
33
33
  "lodash": "^4.17.20",
34
34
  "luxon": "^3.3.0",
35
- "pdfjs-dist": "^3.8.162",
35
+ "pdfjs-dist": "^2.10.377",
36
36
  "tippy.js": "^6.3.2",
37
+ "vue": "^2.6.12",
37
38
  "vue-imask": "^6.6.3",
38
39
  "vue-property-decorator": "^9.1.2",
39
40
  "vue-swatches": "^2.1.1",
40
41
  "vue-virtual-scroller": "^1.1.2"
41
42
  },
42
43
  "devDependencies": {
43
- "@babel/eslint-parser": "^7.22.9",
44
+ "@babel/eslint-parser": "^7.19.1",
44
45
  "@babel/plugin-proposal-numeric-separator": "^7.18.6",
45
46
  "@babel/plugin-syntax-numeric-separator": "^7.10.4",
46
47
  "@storybook/addon-docs": "=6.3.8",
@@ -52,15 +53,14 @@
52
53
  "@vue/eslint-config-airbnb": "^7.0.0",
53
54
  "@vue/test-utils": "^1.1.1",
54
55
  "babel-eslint": "^10.1.0",
55
- "eslint": "^8.45.0",
56
- "eslint-plugin-import": "^2.27.5",
56
+ "eslint": "^8.30.0",
57
+ "eslint-plugin-import": "^2.22.1",
57
58
  "eslint-plugin-prettier": "^4.2.1",
58
- "eslint-plugin-vue": "^9.15.1",
59
+ "eslint-plugin-vue": "^9.9.0",
59
60
  "fibers": "^5.0.0",
60
61
  "marked": "^4.2.5",
61
62
  "sass": "^1.29.0",
62
63
  "sass-loader": "^10.0.5",
63
- "vue": "^2.6.12",
64
64
  "vue-class-component": "^7.2.6",
65
65
  "vue-eslint-parser": "^9.1.0",
66
66
  "vue-template-compiler": "=2.6.14"
@@ -9,10 +9,21 @@ $color-outcome: #b91e1e;
9
9
  --color-outcome: #{$color-outcome};
10
10
  --color-primary: #{$primary};
11
11
  --body-bg: #{$body-bg};
12
+
13
+ .modal-backdrop {
14
+ --bs-backdrop-bg: #{$body-bg};
15
+ --bs-backdrop-opacity: 0.75;
16
+ }
12
17
  }
13
18
 
14
19
  [data-theme="dark"] {
15
20
  --color-primary: #{$dark-primary};
16
21
  --body-bg: #{$dark-body-bg};
22
+ --bs-backdrop-bg: #{$dark-body-bg};
17
23
  --color-primary-hover: #{darken($dark-primary, 10%)};
24
+
25
+ .modal-backdrop {
26
+ --bs-backdrop-opacity: 0.5;
27
+ --bs-backdrop-bg: #{$dark-body-bg};
28
+ }
18
29
  }
@@ -44,6 +44,9 @@ body[data-theme="dark"] {
44
44
  &:hover, &:active, &:focus {
45
45
  color: $dark-body-color;
46
46
  }
47
+ &.btn-secondary {
48
+ //color: #000;
49
+ }
47
50
  }
48
51
  .modal-backdrop {
49
52
  background-color: #000;
@@ -57,7 +57,7 @@ $popover-border-width: 2px;
57
57
  // dropdown
58
58
 
59
59
  // Dark theme
60
- $dark-body-bg: #2e3136;
60
+ $dark-body-bg: #232529;// #2e3136;
61
61
  $dark-border-color: #4a4a4a;
62
62
  $dark-body-color: #ddd;
63
63
  $dark-input-bg: #383b41;
@@ -21,6 +21,7 @@
21
21
  :unmask="false"
22
22
  :lazy="!focused"
23
23
  :placeholder="placeholder"
24
+ :disabled="disabled"
24
25
  />
25
26
 
26
27
  <div class="addon-end" v-if="clearable && value">
@@ -85,6 +86,7 @@ class itfDatePicker extends Vue {
85
86
  @Prop({ type: String, default: 'bottom-start' }) placement;
86
87
  @Prop({ type: [String, Date], default: '' }) minDate;
87
88
  @Prop(Boolean) clearable;
89
+ @Prop(Boolean) disabled;
88
90
 
89
91
  focused = false;
90
92
 
@@ -117,6 +119,9 @@ class itfDatePicker extends Vue {
117
119
  }
118
120
 
119
121
  mounted() {
122
+ if (this.disabled) {
123
+ return;
124
+ }
120
125
  // якщо в модалці, то контекст модалки, якщо ні, то аплікейшена
121
126
  const context = this.$el.closest('.itf-append-context') || document.body;
122
127
  this.tooltip = tippy(this.$refs.input.$el, {
@@ -183,10 +188,16 @@ class itfDatePicker extends Vue {
183
188
  }
184
189
 
185
190
  onFocus() {
191
+ if (this.disabled) {
192
+ return;
193
+ }
186
194
  this.focused = true;
187
195
  }
188
196
 
189
197
  onBlur(e) {
198
+ if (this.disabled) {
199
+ return;
200
+ }
190
201
  this.focused = false;
191
202
  this.updateValue(e.target.value, !!e.target.value);
192
203
  }
@@ -19,6 +19,7 @@
19
19
  :mask="Date"
20
20
  :pattern="dateFormat"
21
21
  :blocks="blocks"
22
+ :disabled="disabled"
22
23
  :format="format"
23
24
  :parse="parse"
24
25
  :unmask="false"
@@ -105,6 +106,7 @@ class itfDateRangePicker extends Vue {
105
106
  @Prop({ type: String, default: 'bottom-start' }) placement;
106
107
  @Prop({ type: [String, Date], default: '' }) minDate;
107
108
  @Prop({ type: [String, Date], default: ''}) maxDate;
109
+ @Prop(Boolean) disabled;
108
110
 
109
111
  focused = false;
110
112
 
@@ -3,7 +3,7 @@
3
3
  <div class="itf-dropdown" :class="`drop${placement}`">
4
4
  <div v-if="disabled"><slot name="button">{{label}}</slot></div>
5
5
  <itf-button
6
- v-else
6
+ v-else-if="!text"
7
7
  :class="{ 'dropdown-toggle': toggle }"
8
8
  v-bind="buttonOptions"
9
9
  ref="toggle"
@@ -13,6 +13,9 @@
13
13
  >
14
14
  <slot name="button">{{label}}</slot>
15
15
  </itf-button>
16
+ <div v-else :class="{ 'dropdown-toggle': toggle }" ref="toggle" :id="modalId" data-bs-toggle="dropdown" aria-expanded="false">
17
+ <slot name="button">{{label}}</slot>
18
+ </div>
16
19
  <div
17
20
  class="itf-dropdown__menu dropdown-menu"
18
21
  :class="{'dropdown-menu-end': right, 'shadow': shadow}"
@@ -44,6 +47,8 @@ class itfDropdown extends Vue {
44
47
  @Prop({ type: Boolean }) toggle;
45
48
  @Prop({ type: Boolean }) shadow;
46
49
  @Prop({ type: Boolean }) disabled;
50
+ @Prop({ type: Boolean }) text;
51
+ @Prop({ type: Boolean }) appendToBody;
47
52
  @Prop({ validator: (value) => [true, false, 'inside', 'outside'].includes(value), default: true }) autoclose;
48
53
  @Prop({ type: Object, default: () => ({}) }) buttonOptions;
49
54
 
@@ -80,6 +85,12 @@ class itfDropdown extends Vue {
80
85
  reference: 'toggle',
81
86
  autoClose: this.autoclose
82
87
  });
88
+ let context = document.body;
89
+ if (this.appendToBody && this.$refs.dropdown instanceof Node && this.$refs.dropdown.parentNode) {
90
+ this.$refs.dropdown.parentNode.removeChild(this.$refs.dropdown);
91
+ context.appendChild(this.$refs.dropdown); // should append only to body
92
+ }
93
+
83
94
  this.$el.addEventListener('shown.bs.dropdown', () => {
84
95
  setTimeout(() => {
85
96
  this.$emit('open');
@@ -0,0 +1,39 @@
1
+ <template>
2
+ <div class="itf-editable-button">
3
+ <slot></slot>
4
+ <itf-button secondary icon small class="itf-editable-button__button" @click="$emit('click')">
5
+ <itf-icon name="pen" />
6
+ </itf-button>
7
+ </div>
8
+ </template>
9
+ <style lang="scss" scoped>
10
+ .itf-editable-button {
11
+ position: relative;
12
+
13
+ &__button {
14
+ position: absolute !important;
15
+ right: 0;
16
+ top: 0;
17
+ opacity: 0;
18
+ transition: opacity 0.2s ease-in-out;
19
+ }
20
+ &:hover &__button {
21
+ transition: opacity 0.2s ease-in-out;
22
+ opacity: 1;
23
+ }
24
+ }
25
+ </style>
26
+ <script>
27
+ import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
28
+ import itfIcon from '../icon/Icon';
29
+ import itfButton from '../button/Button';
30
+
31
+ export default @Component({
32
+ components: {
33
+ itfIcon,
34
+ itfButton,
35
+ }
36
+ })
37
+ class EditButton extends Vue {
38
+ }
39
+ </script>
@@ -0,0 +1,112 @@
1
+ <template>
2
+
3
+ <div>
4
+ <div ref="element">
5
+ <slot name="activator" :open="open">
6
+ <div @click.prevent.stop="open">
7
+ <slot :open="open" :hide="hide">
8
+ </slot>
9
+ </div>
10
+ </slot>
11
+ </div>
12
+ </div>
13
+
14
+ </template>
15
+ <style lang="scss">
16
+ .itf-editable-element {
17
+ z-index: 1060;
18
+ position: fixed;
19
+ }
20
+ </style>
21
+ <script>
22
+ import { Vue, Component, Prop, PropSync } from 'vue-property-decorator';
23
+ import itfButton from '../button/Button.vue';
24
+ import itfIcon from '../icon/Icon.vue';
25
+ import FocusTrap from "bootstrap/js/src/util/focustrap";
26
+
27
+ export default @Component({
28
+ name: 'itfEditableElement',
29
+ components: {
30
+ itfButton,
31
+ itfIcon,
32
+ }
33
+ })
34
+ class itfEditableElement extends Vue {
35
+ _backdrop = null;
36
+ _context = null;
37
+ _focusTrap = null;
38
+ _elContainer = null;
39
+ _tempContainer = null;
40
+
41
+ isOpen = false;
42
+
43
+ async mounted() {
44
+ this._context = this.$el.closest('.itf-append-context:not(.modal-content)') || document.body;
45
+
46
+ const { default: Backdrop } = await import('../modal/backdrop');
47
+ this._backdrop = new Backdrop({
48
+ rootElement: this._context,
49
+ isVisible: true,
50
+ isAnimated: true,
51
+ clickCallback: () => {
52
+ this.hide();
53
+ }
54
+ });
55
+ this._focusTrap = new FocusTrap({
56
+ trapElement: this.$el
57
+ });
58
+ }
59
+
60
+ open() {
61
+ if (this.isOpen) {
62
+ return;
63
+ }
64
+ this.isOpen = true;
65
+
66
+ this.$emit('open');
67
+ this._backdrop.show();
68
+ this._focusTrap.activate();
69
+
70
+ this._tempContainer = document.createElement('div');
71
+ this.applyPosition(this._tempContainer, this.$refs.element)
72
+ this._tempContainer.classList.add('itf-editable-element');
73
+ this.$el.removeChild(this.$refs.element);
74
+ this._tempContainer.appendChild(this.$refs.element);
75
+ this._context.appendChild(this._tempContainer);
76
+ }
77
+
78
+ applyPosition(container, el) {
79
+ const box = el.getBoundingClientRect();
80
+ const left = box.left + document.body.scrollLeft;
81
+ const top = box.top + document.body.scrollTop;
82
+ container.style.left = `${left}px`;
83
+ container.style.top = `${top}px`;
84
+ container.style.width = `${box.width}px`;
85
+ container.style.height = `${box.height}px`;
86
+ this.$el.style.width = `${box.width}px`;
87
+ this.$el.style.height = `${box.height}px`;
88
+ }
89
+
90
+ hide() {
91
+ if (!this.isOpen) {
92
+ return;
93
+ }
94
+ this._focusTrap.deactivate();
95
+ this._backdrop.hide(() => {
96
+ this._tempContainer.removeChild(this.$refs.element);
97
+ this.$el.appendChild(this.$refs.element);
98
+ this._context.removeChild(this._tempContainer); // remove the temporary div
99
+
100
+ this.$el.style.width = null;
101
+ this.$el.style.height = null;
102
+ this.isOpen = false;
103
+ this.$emit('hide');
104
+ });
105
+ }
106
+
107
+ beforeDestroy() {
108
+ this._backdrop.dispose();
109
+ this._focusTrap.deactivate();
110
+ }
111
+ }
112
+ </script>
@@ -0,0 +1,53 @@
1
+ import { storiesOf } from '@storybook/vue';
2
+ import itfButton from '../button/Button.vue';
3
+ import itfEditableElement from './EditableElement.vue';
4
+
5
+ storiesOf('Common', module)
6
+ .add('Editable element', () => ({
7
+ components: {
8
+ itfButton,
9
+ itfEditableElement
10
+ },
11
+ data() {
12
+ return {}
13
+ },
14
+ template: `<div>
15
+ <p>You need wrap whole application with this tag</p>
16
+
17
+ <h2>Usage</h2>
18
+
19
+ <pre>
20
+ &lt;itf-table
21
+ :columns="columns"
22
+ :rows="list"
23
+ >
24
+ &lt;template #column.Employee="&#123; item }">&lt;/template>
25
+ &lt;/itf-table>
26
+ </pre>
27
+
28
+ <h3>Example</h3>
29
+
30
+ <itf-editable-element>
31
+ <template v-slot="{ hide }">
32
+
33
+ <div class="card" style="width: 300px">
34
+ <div class="card-body">
35
+ asdadas
36
+
37
+ <a href="#" @click="hide">Close</a>
38
+ </div>
39
+ </div>
40
+ </template>
41
+ </itf-editable-element>
42
+
43
+ <!--itf-table
44
+ :columns="columns"
45
+ :rows="list"
46
+ >
47
+ <template #column.Employee="{ item }">
48
+ {{item.Employee}}
49
+ </template>
50
+ </itf-table-->
51
+
52
+ </div>`,
53
+ }));
@@ -0,0 +1,230 @@
1
+ <template>
2
+ <div class="itf-board">
3
+
4
+ <div ref="container" class="itf-board-titles">
5
+ <div v-for="(column, index) of columns"
6
+ :data-column="index"
7
+ @mouseover="columnHighlight(index, 'enter')"
8
+ @mouseout="columnHighlight(index, 'leave')"
9
+ :key="index" class="itf-board-column itf-board-column__title p-2">
10
+ <div accept-group="board-columns"
11
+ class="itf-board-header-space"
12
+ @enter="columnHighlight(index, 'enter', 'drop')"
13
+ @leave="columnHighlight(index, 'leave', 'drop')"
14
+ v-dropzone="{ payload: { index, column } }">
15
+ <div class="itf-board-header-dropzone"></div>
16
+ </div>
17
+ <itf-edit-button
18
+ accept-group="board-columns"
19
+ @enter="columnHighlight(index, 'enter')"
20
+ @leave="columnHighlight(index, 'leave')"
21
+ v-dropzone="{ payload: { index, column } }"
22
+ class="d-flex align-items-center">
23
+ <div
24
+ group="board-columns"
25
+ @drop="reorderColumns"
26
+ v-draggable="{ handle: true, payload: { index, column }, mirror: { yAxis:false } }"
27
+ class="flex-grow-1 d-flex align-items-center itf-board-column__header justify-content-between">
28
+ <div><slot name="header" :column="column">{{ column[columnNameKey] }}</slot></div>
29
+ <div class="text-muted me-1"><slot name="header-addon" :column="column">{{ (groupedItems[column.Id] || []).length }}</slot></div>
30
+ </div>
31
+ </itf-edit-button>
32
+ <div v-if="index === columns.length - 1"
33
+ @enter="columnHighlightLast('enter', 'drop')"
34
+ @leave="columnHighlightLast('leave', 'drop')"
35
+ accept-group="board-columns"
36
+ class="itf-board-header-space right"
37
+ v-dropzone="{ payload:{ last: true } }">
38
+ <div class="itf-board-header-dropzone"></div>
39
+ </div>
40
+ </div>
41
+ <div class="align-items-end d-flex pb-1">
42
+ <slot name="add-column">
43
+ <itf-button icon small>
44
+ <itf-icon name="plus" />
45
+ </itf-button>
46
+ </slot>
47
+ </div>
48
+ </div>
49
+
50
+ <div class="itf-board-columns flex-grow-1">
51
+ <div v-for="(column, index) of columns"
52
+ :key="`column-${index}`"
53
+ :data-column="index"
54
+ class="itf-board-column px-2 pb-2 pt-2" :class="{'empty': !groupedItems[column.Id]}">
55
+ <slot name="column" :column="column" :index="index" :items="groupedItems[column.Id] || []"></slot>
56
+ </div>
57
+ </div>
58
+ </div>
59
+ </template>
60
+ <script>
61
+ import './styles.scss';
62
+ import { Component, Prop, Vue } from 'vue-property-decorator';
63
+ import itfIcon from '../icon/Icon';
64
+ import itfButton from '../button/Button';
65
+ import loading from '../../directives/loading';
66
+ import itfForm from '../form/Form';
67
+ import itfEditButton from '../editable/EditButton.vue';
68
+ import createDraggable from '../sortable/draggable';
69
+ import { debounce } from '../../helpers/debounce';
70
+
71
+ const { Node, ...draggableDirectives } = createDraggable();
72
+
73
+ export default @Component({
74
+ components: {
75
+ itfIcon,
76
+ itfEditButton,
77
+ itfForm,
78
+ itfButton
79
+ },
80
+ directives: {
81
+ ...draggableDirectives,
82
+ loading
83
+ },
84
+ provide() {
85
+ return { board: this }; // do not use Provide from vue-property-decorator
86
+ }
87
+ })
88
+ class Board extends Vue {
89
+ @Prop(Array) columns;
90
+ @Prop(Array) items;
91
+ @Prop(Array) columnOrders;
92
+ @Prop({ type: Function, default: (item) => item.Status?.Id }) groupFunc;
93
+ @Prop({ type: Function, default: (item, value) => { item.Status = value; } }) updateFunc;
94
+ @Prop({ type: String, default: 'Name' }) columnNameKey;
95
+ @Prop(Boolean) columnSorting;
96
+
97
+ beforeDestroy() {
98
+ if (this.columnSorting) {
99
+ Node.removeContainer(this.$refs.container);
100
+ }
101
+ }
102
+
103
+ mounted() {
104
+ this.itemStatusChanged = debounce(this.emitItemStatusChanged, 10);
105
+ if (this.columnSorting) {
106
+ Node.addContainer(this.$refs.container);
107
+ }
108
+ }
109
+
110
+ get groupedItems() {
111
+ const grouped = {};
112
+ for (const column of this.columns) {
113
+ grouped[column.Id] = this.orderItemsInColumn(column);
114
+ }
115
+ return grouped;
116
+ }
117
+
118
+ orderItemsInColumn(column) {
119
+ const columnOrdering = this.columnOrders.find((c) => c.Id === column.Id) || { ItemIds: [] };
120
+ let items = this.items.filter((item) => column.Id === this.groupFunc(item));
121
+ items = items.sort((a, b) => {
122
+ const aIndex = columnOrdering.ItemIds.findIndex((c) => c === a.Id);
123
+ const bIndex = columnOrdering.ItemIds.findIndex((c) => c === b.Id);
124
+ if (aIndex === -1 && bIndex === -1) {
125
+ return 0;
126
+ }
127
+ if (aIndex === -1) {
128
+ return 1;
129
+ }
130
+ if (bIndex === -1) {
131
+ return -1;
132
+ }
133
+ return aIndex - bIndex;
134
+ });
135
+ return items;
136
+ }
137
+
138
+ columnHighlight(index, state, className = 'over') {
139
+ Array.from(this.$el.querySelectorAll(`[data-column="${index}"]`)).forEach(t=>{
140
+ state === 'enter' ? t.classList.add(className) : t.classList.remove(className)
141
+ });
142
+ }
143
+ columnHighlightLast(state, className = 'over') {
144
+ Array.from(this.$el.querySelectorAll(`[data-column="${this.columns.length - 1}"]`)).forEach(t=>{
145
+ if (state === "enter") {
146
+ t.classList.add(className);
147
+ t.classList.add('right');
148
+ } else {
149
+ t.classList.remove(className);
150
+ t.classList.remove('right');
151
+ }
152
+ });
153
+ }
154
+
155
+ reorderColumns({ detail }) {
156
+ const { index: fromIndex } = detail.draggablePayload;
157
+ const { index: toIndex, last } = detail.dropzonePayload;
158
+ const newValue = [...this.columns];
159
+ const [removed] = newValue.splice(fromIndex, 1);
160
+
161
+ if (last) {
162
+ newValue.push(removed);
163
+ } else {
164
+ newValue.splice((fromIndex < toIndex) ? toIndex - 1 : toIndex, 0, removed);
165
+ }
166
+ this.$emit('update:columns', newValue);
167
+ if (last) {
168
+ this.columnHighlightLast('leave');
169
+ this.columnHighlightLast('leave', 'drop');
170
+ } else {
171
+ this.columnHighlight(toIndex, 'leave');
172
+ this.columnHighlight(toIndex, 'leave', 'drop');
173
+ }
174
+ // for (const dropdown of this.$refs.dropdown) {
175
+ // dropdown.hide();
176
+ // }
177
+ }
178
+
179
+ reorderCards({ detail }) {
180
+ const { item, column: fromColumn } = detail.draggablePayload;
181
+ const { index: toIndex, last, column } = detail.dropzonePayload;
182
+
183
+ const items = [...this.items];
184
+ const itemIndex = items.findIndex(t => t === item);
185
+
186
+ const newItem = { ...items[itemIndex] };
187
+ this.updateFunc(newItem, column);
188
+
189
+ items[itemIndex] = newItem;
190
+
191
+ this.lastUpdatedIndex = typeof this.lastUpdatedIndex === 'undefined' ? toIndex : this.lastUpdatedIndex; // запамятовуємо індекс, бо виклики може бути два, коли колонка і карточка обробляє
192
+ this.itemStatusChanged(items[itemIndex], column, items, () => {
193
+ const toIndex = this.lastUpdatedIndex; // беремо індекс з пам'яті, бо викликів може бути два, але індекс тільки в картці, якщо нема, то це колонка
194
+ this.lastUpdatedIndex = undefined; // обнуляємо, щоб при наступному перетягувані вже не брало його
195
+ const newOrders = [...this.columnOrders];
196
+ let sorting = newOrders.find((c) => c.Id === column.Id);
197
+ if (!sorting) {
198
+ sorting = { Id: column.Id, ItemIds: [] };
199
+ newOrders.push(sorting)
200
+ }
201
+ let fromSorting = newOrders.find((c) => c.Id === fromColumn.Id);
202
+ if (!fromSorting) {
203
+ fromSorting = { Id: fromColumn.Id, ItemIds: [] };
204
+ newOrders.push(fromSorting)
205
+ }
206
+ // update from
207
+ fromSorting.ItemIds = this.groupedItems[fromColumn.Id].map((i) => i.Id).filter((t) => t !== newItem.Id);
208
+
209
+ // update to
210
+ const items = [...this.groupedItems[column.Id]];
211
+ let placeIndex = last ? items.length : toIndex;
212
+ if (typeof placeIndex === 'undefined') { // ідекса може не бути, якщо на колонку переносять, тоді в кінець
213
+ placeIndex = items.length;
214
+ }
215
+ const itemsInColumn = items.map((t) => t.Id).filter((t) => t !== newItem.Id);
216
+ itemsInColumn.splice(placeIndex, 0, newItem.Id);
217
+
218
+ sorting.ItemIds = itemsInColumn;
219
+ this.$emit('update:columnOrders', newOrders);
220
+ });
221
+ }
222
+
223
+ emitItemStatusChanged(item, column, items, updateOrder) {
224
+ updateOrder();
225
+
226
+ this.$emit('update:item', [item, column]);
227
+ this.$emit('update:items', items);
228
+ }
229
+ }
230
+ </script>
@@ -0,0 +1,41 @@
1
+ <template>
2
+ <div class="itf-board-card shadow-sm">
3
+ <slot name="header"></slot>
4
+
5
+ <div class="itf-board-card-inner d-flex">
6
+ <div class="flex-grow-1">
7
+ <slot>{{text}}</slot>
8
+ </div>
9
+ <div>
10
+ <itf-button icon small>
11
+ <itf-icon name="menu_horizontal" />
12
+ </itf-button>
13
+ </div>
14
+ </div>
15
+
16
+ <slot name="footer"></slot>
17
+ </div>
18
+ </template>
19
+ <script>
20
+ import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
21
+ import itfIcon from '../icon/Icon';
22
+ import itfButton from '../button/Button';
23
+ import loading from '../../directives/loading';
24
+ import itfForm from '../form/Form';
25
+ import itfEditButton from '../editable/EditButton';
26
+
27
+ export default @Component({
28
+ components: {
29
+ itfIcon,
30
+ itfForm,
31
+ itfEditButton,
32
+ itfButton
33
+ },
34
+ directives: {
35
+ loading
36
+ }
37
+ })
38
+ class BoardColumn extends Vue {
39
+ @Prop() text;
40
+ }
41
+ </script>