@itfin/components 1.2.106 → 1.2.108

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.106",
3
+ "version": "1.2.108",
4
4
  "author": "Vitalii Savchuk <esvit666@gmail.com>",
5
5
  "scripts": {
6
6
  "serve": "vue-cli-service serve",
@@ -0,0 +1,67 @@
1
+ <template>
2
+ <itf-button icon small @click.stop="copy">
3
+ <slot>
4
+ <itf-icon name="clipboard_copy" />
5
+ </slot>
6
+ </itf-button>
7
+ </template>
8
+ <script>
9
+ import { Vue, Component, Prop } from 'vue-property-decorator';
10
+ import itfIcon from '../icon/Icon';
11
+ import itfButton from '../button/Button';
12
+
13
+ export default @Component({
14
+ components: {
15
+ itfIcon,
16
+ itfButton
17
+ }
18
+ })
19
+ class CopyToClipboard extends Vue {
20
+ @Prop(String) value;
21
+
22
+ async copy () {
23
+ const showSuccess = this.$showSuccess;
24
+ const showError = this.$showError;
25
+ const $t = this.$t.bind(this);
26
+
27
+ function fallbackCopyTextToClipboard (text) {
28
+ const textArea = document.createElement('textarea');
29
+ textArea.value = text;
30
+
31
+ // Avoid scrolling to bottom
32
+ textArea.style.top = '0';
33
+ textArea.style.left = '0';
34
+ textArea.style.position = 'fixed';
35
+
36
+ document.body.appendChild(textArea);
37
+ textArea.focus();
38
+ textArea.select();
39
+
40
+ try {
41
+ if (document.execCommand('copy')) {
42
+ showSuccess($t('components.copyToClipboard.copyingToClipboardWasSuccessful'));
43
+ }
44
+ } catch (err) {
45
+ showError(err.message);
46
+ }
47
+
48
+ document.body.removeChild(textArea);
49
+ }
50
+ async function copyTextToClipboard (text) {
51
+ if (!navigator.clipboard) {
52
+ fallbackCopyTextToClipboard(text);
53
+ return;
54
+ }
55
+ try {
56
+ await navigator.clipboard.writeText(text);
57
+
58
+ showSuccess($t('components.copyToClipboard.copyingToClipboardWasSuccessful'));
59
+ } catch (err) {
60
+ showError(err.message);
61
+ }
62
+ }
63
+
64
+ await copyTextToClipboard(this.value);
65
+ }
66
+ }
67
+ </script>
@@ -2,17 +2,20 @@
2
2
  <div class="itf-inline-edit">
3
3
  <itf-select
4
4
  ref="input"
5
- class="input"
5
+ class="input flex-grow-1"
6
6
  role="button"
7
7
  multiple
8
8
  :placeholder="focused ? 'Search for an option...' : 'Empty'"
9
9
  :tabindex="1"
10
10
  :disabled="!editable"
11
- :options="field && field.Options"
11
+ :options="options"
12
12
  :reduce="option => option.Name"
13
13
  :get-option-label="option => option.Name"
14
14
  @search:focus="$emit('focus')"
15
- @search:blur="$emit('blur')">
15
+ @search:blur="$emit('blur')"
16
+ :value="itemsValue"
17
+ @input="onInput"
18
+ >
16
19
  <template #option="{ option }">
17
20
  <div>{{ option.Name }}</div>
18
21
  </template>
@@ -51,5 +54,20 @@ class itfCustomizeInlineMultiselect extends Vue {
51
54
  @Prop(Boolean) focused;
52
55
  @Prop(Boolean) editable;
53
56
  @Prop() field;
57
+
58
+ get itemsValue () {
59
+ return (this.value || '').toString().split(';;;').filter(v => v.length);
60
+ }
61
+
62
+ get options() {
63
+ const list = (this.field && this.field.Options) || [];
64
+ const notExists = this.itemsValue.filter(v => !list.find(o => o.Name === v));
65
+
66
+ return [...list, ...notExists.map(v => ({ Name: v }))];
67
+ }
68
+
69
+ onInput(val) {
70
+ this.$emit('input', val.join(';;;'));
71
+ }
54
72
  }
55
73
  </script>
@@ -10,7 +10,10 @@
10
10
  :reduce="option => option.Name"
11
11
  :get-option-label="option => option.Name"
12
12
  @search:focus="$emit('focus')"
13
- @search:blur="$emit('blur')">
13
+ @search:blur="$emit('blur')"
14
+ :value="value"
15
+ @input="$emit('input', $event)"
16
+ >
14
17
  <template #option="{ option }">
15
18
  <div>{{ option.Name }}</div>
16
19
  </template>
@@ -14,6 +14,7 @@
14
14
  :contenteditable="editable"
15
15
  v-html="value">
16
16
  </div>
17
+ <itf-copy-to-clipboard :value="value" />
17
18
  </div>
18
19
  </template>
19
20
  <style lang="scss" scoped>
@@ -36,12 +37,16 @@
36
37
  </style>
37
38
  <script>
38
39
  import { Vue, Component, Model, Prop } from 'vue-property-decorator';
40
+ import itfIcon from '../../icon/Icon.vue';
39
41
  import itfTextField from '../../text-field/TextField.vue';
42
+ import itfCopyToClipboard from '../../copyToClipboard/CopyToClipboard';
40
43
 
41
44
  export default @Component({
42
45
  name: 'itfCustomizeInlineText',
43
46
  components: {
44
- itfTextField
47
+ itfIcon,
48
+ itfTextField,
49
+ itfCopyToClipboard
45
50
  }
46
51
  })
47
52
 
@@ -1,14 +1,13 @@
1
1
  <template>
2
2
  <div class="itf-append-context" style="min-width: 300px">
3
- <div class="d-flex align-items-center px-3 py-2">
4
- <div class="me-1">
3
+ <div class="d-flex align-items-center px-3 py-2" sortable-skip>
4
+ <!--div class="me-1">
5
5
  <itf-icon-popover title="Icon" removable :value="value.Icon" @input="setParam({ Icon: $event })">
6
6
  <itf-button slot="activator" icon>
7
7
  <itf-icon :name="value.Icon" />
8
8
  </itf-button>
9
9
  </itf-icon-popover>
10
- </div>
11
-
10
+ </div-->
12
11
  <div class="flex-grow-1">
13
12
  <itf-text-field ref="input" :value="value.Name" @input="setParam({ Name: $event })" />
14
13
  </div>
@@ -29,7 +28,7 @@
29
28
  </li>
30
29
  </ul>
31
30
  </div>
32
- <div v-if="value.Type === 'multiselect'">
31
+ <div v-if="isListType">
33
32
  <div class="d-flex justify-content-between pe-2">
34
33
  <h6 class="dropdown-header">{{ $t('components.customize.options') }}</h6>
35
34
  <div v-if="value.Options && value.Options.length">
@@ -39,7 +38,7 @@
39
38
  </div>
40
39
  </div>
41
40
 
42
- <div v-if="isNewOptionVisible" class="px-3">
41
+ <div v-if="isNewOptionVisible" class="px-3" sortable-skip>
43
42
  <itf-text-field
44
43
  ref="newOptionInput"
45
44
  v-model="newOption"
@@ -63,21 +62,43 @@
63
62
  :read-only="false"
64
63
  auto-scroll="window"
65
64
  >
66
- <div v-for="option of value.Options" class="dropdown-item d-flex justify-content-between">
67
- <div>
68
- <itf-icon sortable class="dragHandle" name="drag_vertical" />
69
- <span>{{ option.Name }}</span>
65
+ <div v-for="(option, i) of value.Options" :key="`options${i}`">
66
+ <div class="dropdown-item d-flex justify-content-between">
67
+ <div>
68
+ <itf-icon sortable class="dragHandle" name="drag_vertical" />
69
+ <span>{{ option.Name }}</span>
70
+ </div>
71
+ <span class="text-muted">></span>
70
72
  </div>
71
- <span class="text-muted">></span>
73
+ <ul class="itf-dropdown__menu shadow dropdown-menu dropdown-submenu">
74
+ <!--li class="p-2">
75
+ <itf-text-field
76
+ ref="newOptionName"
77
+ :value="option.Name"
78
+ placeholder="Option name..."
79
+ @blur="onBlurOption"
80
+ @keydown.enter="submitNewOption"
81
+ />
82
+ </li-->
83
+ <div>
84
+ <itf-delete-confirm-modal @delete="removeItem(option)">
85
+ <template #activator="{ on }">
86
+ <a class="dropdown-item text-danger align-items-center d-flex" @click.prevent="on.click"><itf-icon name="trash" class="me-1" /> {{ $t('components.customize.deleteItem') }}</a>
87
+ </template>
88
+ <h5 class="mb-0">{{$t('components.popover.confirmDelete')}}</h5>
89
+ <p class="mb-0">{{ $t('components.customize.areYouSureYouWantToDeleteThisItem') }}</p>
90
+ </itf-delete-confirm-modal>
91
+ </div>
92
+ </ul>
72
93
  </div>
73
94
  </sortable>
74
95
  </div>
75
96
  <div><hr class="dropdown-divider"></div>
76
97
  <div><a class="dropdown-item" @click.prevent="$emit('duplicate')"><itf-icon name="duplicate" /> {{ $t('components.customize.duplicateProperty') }}</a></div>
77
98
  <div>
78
- <itf-delete-confirm-modal class="me-1" @delete="$emit('delete')">
99
+ <itf-delete-confirm-modal @delete="$emit('delete')">
79
100
  <template #activator="{ on }">
80
- <a class="dropdown-item text-danger" @click.prevent="on.click"><itf-icon name="trash" /> {{ $t('components.customize.deleteProperty') }}</a>
101
+ <a class="dropdown-item text-danger align-items-center d-flex" @click.prevent="on.click"><itf-icon name="trash" class="me-1" /> {{ $t('components.customize.deleteProperty') }}</a>
81
102
  </template>
82
103
  <h5 class="mb-0">{{$t('components.popover.confirmDelete')}}</h5>
83
104
  <p class="mb-0">{{ $t('components.customize.areYouSureYouWantToDeleteThisField') }}</p>
@@ -95,7 +116,7 @@ import itfIconPopover from '../popover/IconPopover.vue';
95
116
  import { INLINE_TYPES } from './constants';
96
117
  import itfDeleteConfirmModal from '../modal/DeleteConfirmModal.vue';
97
118
  import PropertyItem from './PropertyItem.vue';
98
- import PropertiesPopupMenu from "@/components/customize/PropertiesPopupMenu.vue";
119
+ import PropertiesPopupMenu from './PropertiesPopupMenu.vue';
99
120
 
100
121
  export default @Component({
101
122
  components: {
@@ -125,6 +146,10 @@ class PropertiesEditMenu extends Vue {
125
146
  return type ? type.Name : '';
126
147
  }
127
148
 
149
+ get isListType() {
150
+ return ['select', 'multiselect'].includes(this.value.Type);
151
+ }
152
+
128
153
  mounted() {
129
154
  this.$nextTick(() => {
130
155
  this.$refs.input.focus();
@@ -147,6 +172,11 @@ class PropertiesEditMenu extends Vue {
147
172
  });
148
173
  }
149
174
 
175
+ removeItem(option) {
176
+ const newOptions = this.value.Options.filter((item) => item !== option);
177
+ this.setParam({ Options: newOptions });
178
+ }
179
+
150
180
  submitNewOption() {
151
181
  const newOption = this.newOption;
152
182
  if (newOption) {
@@ -7,23 +7,25 @@
7
7
  :read-only="!editable"
8
8
  auto-scroll="window"
9
9
  >
10
- <property-item
11
- v-for="(field, n) of list"
12
- :key="n"
13
- ref="properties"
14
- :field="field"
15
- :editable="editable"
16
- :lock-fields="lockFields"
17
- :value="value[field.Id]"
18
- @input="$emit('input', { ...value, [field.Id]: $event })"
19
- @delete="onDelete(field)"
20
- @duplicate="onDuplicate(field)"
21
- @update:field="onChange($event, n)"
22
- />
10
+ <template v-for="(field, n) of listToHide">
11
+ <property-item
12
+ v-if="field.isVisible || isShowAll"
13
+ :key="n"
14
+ ref="properties"
15
+ :field="field"
16
+ :editable="editable"
17
+ :lock-fields="lockFields"
18
+ :value="value[field.Id]"
19
+ @input="$emit('input', { ...value, [field.Id]: $event })"
20
+ @delete="onDelete(field)"
21
+ @duplicate="onDuplicate(field)"
22
+ @update:field="onChange($event, n)"
23
+ />
24
+ </template>
23
25
  </sortable>
24
26
 
25
27
  <div v-if="editable && !lockFields" class="text-start">
26
- <div v-if="isShowAll">
28
+ <div v-if="isShowAll || !hiddenItemsCount">
27
29
  <itf-dropdown ref="newItemDd" autoclose="outside" class="flex-grow-1 mw-100" shadow :button-options="{ small: true }">
28
30
  <template #button>
29
31
  <span class="text-muted d-flex align-items-center">
@@ -41,9 +43,9 @@
41
43
  </itf-dropdown>
42
44
  </div>
43
45
 
44
- <itf-button small class="text-muted" @click="toggleShowAll">
46
+ <itf-button v-if="hiddenItemsCount" small class="text-muted" @click="toggleShowAll">
45
47
  <itf-icon :name="isShowAll ? 'chevron_up' : 'chevron_down'" />
46
- {{isShowAll ? $tc('components.customize.hideMoreProperties', 1, { n: 1 }) : $tc('components.customize.showMoreProperties', 1, { n: 1 })}}
48
+ {{isShowAll ? $tc('components.customize.hideMoreProperties', hiddenItemsCount, { n: hiddenItemsCount }) : $tc('components.customize.showMoreProperties', hiddenItemsCount, { n: hiddenItemsCount })}}
47
49
  </itf-button>
48
50
  </div>
49
51
  </div>
@@ -113,6 +115,29 @@ class itfPropertiesList extends Vue {
113
115
  return INLINE_TYPES;
114
116
  }
115
117
 
118
+ get hiddenItemsCount() {
119
+ return this.listToHide.filter((item) => !item.isVisible).length;
120
+ }
121
+
122
+ get listToHide() {
123
+ return this.list.map((item) => {
124
+ return {
125
+ ...item,
126
+ isVisible: this.isVisible(item)
127
+ }
128
+ });
129
+ }
130
+
131
+ isVisible(field) {
132
+ if (field.Visible === 'show') {
133
+ return true;
134
+ }
135
+ if (field.Visible === 'hide') {
136
+ return false;
137
+ }
138
+ return !!this.value[field.Id];
139
+ }
140
+
116
141
  onChange(item, index) {
117
142
  const list = [...this.list];
118
143
  list[index] = item;
@@ -140,7 +165,9 @@ class itfPropertiesList extends Vue {
140
165
  prop.hideEditMenu();
141
166
  }
142
167
  setTimeout(() => {
143
- this.$refs.properties[list.length - 1].showEditMenu();
168
+ if (this.$refs.properties && this.$refs.properties[list.length - 1]) {
169
+ this.$refs.properties[list.length - 1].showEditMenu();
170
+ }
144
171
  }, 10);
145
172
  });
146
173
  }
@@ -161,7 +188,9 @@ class itfPropertiesList extends Vue {
161
188
 
162
189
  this.$nextTick(() => {
163
190
  setTimeout(() => {
164
- this.$refs.properties[list.length - 1].showEditMenu();
191
+ if (this.$refs.properties && this.$refs.properties[list.length - 1]) {
192
+ this.$refs.properties[list.length - 1].showEditMenu();
193
+ }
165
194
  }, 10);
166
195
  });
167
196
  }
@@ -2,6 +2,7 @@
2
2
  <div>
3
3
  <div>
4
4
  <div><a class="dropdown-item" @click.prevent="$emit('edit')"><itf-icon name="pen" /> {{ $t('components.customize.editProperty') }}</a></div>
5
+ <div><hr class="dropdown-divider"></div>
5
6
  <div>
6
7
  <a class="dropdown-item" @click.prevent><itf-icon name="eye_no" /> {{ $t('components.customize.hideProperty') }}</a>
7
8
  <ul class="itf-dropdown__menu shadow dropdown-menu dropdown-submenu">
@@ -1,20 +1,20 @@
1
1
  <template>
2
2
  <div class="b-properties-list__inner">
3
- <div class="b-properties-list__name" :class="{'editable': editable}">
3
+ <div class="b-properties-list__name" :class="{'editable': editable && !lockFields}">
4
4
  <div class="b-properties-list__icon">
5
5
  <div v-if="editable && !lockFields" role="button" tabindex="-1" class="b-properties-list__draghandler" sortable>
6
6
  <itf-icon class="dragHandle" name="drag_vertical" />
7
7
  </div>
8
8
  </div>
9
9
 
10
- <itf-dropdown :disabled="!editable || lockFields" ref="editDd" autoclose="outside" class="flex-grow-1 mw-100" shadow :button-options="{ icon: true, block: true }" @close="onClose">
11
- <template #button>
10
+ <itf-dropdown text :disabled="!editable || lockFields" ref="editDd" autoclose="outside" class="flex-grow-1 mw-100 editable-field" shadow @close="onClose">
11
+ <div slot="button">
12
12
  <div class="d-flex align-items-center">
13
- <itf-icon :name="field.Icon" :size="16" class="me-1" />
13
+ <!-- <itf-icon :name="field.Icon" :size="16" class="me-1" />-->
14
14
 
15
15
  <div style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" v-text="field.Name" />
16
16
  </div>
17
- </template>
17
+ </div>
18
18
  <properties-edit-menu
19
19
  v-if="isEditMode"
20
20
  :value="field"
@@ -39,7 +39,7 @@
39
39
  <property-inline-edit
40
40
  @focus="onFocus(field.Id)"
41
41
  @blur="onBlur(field.Id)"
42
- class="flex-grow-1 b-properties-list__inline-editor"
42
+ class="flex-grow-1 b-properties-list__inline-editor editable-field"
43
43
  :field="field"
44
44
  :value="value"
45
45
  :editable="editable"
@@ -51,6 +51,17 @@
51
51
  </template>
52
52
  <style lang="scss">
53
53
  .b-properties-list {
54
+ .editable .editable-field {
55
+ &.dropdown {
56
+ min-height: 24px;
57
+ padding: 5px;
58
+ font-size: 14px;
59
+ color: rgba(55, 53, 47, 0.65);
60
+ }
61
+ &:hover {
62
+ background: rgba(0, 0, 0, 0.05);
63
+ }
64
+ }
54
65
  &__draghandler {
55
66
  user-select: none;
56
67
  transition: opacity 150ms ease-in 0s;
@@ -32,9 +32,8 @@
32
32
  <itf-button
33
33
  icon
34
34
  small
35
- v-if="multiple"
35
+ v-if="multiple && !disabled"
36
36
  ref="deselectButtons"
37
- :disabled="disabled"
38
37
  class="vs__deselect"
39
38
  :title="`Deselect ${getOptionLabel(option)}`"
40
39
  :aria-label="`Deselect ${getOptionLabel(option)}`"
package/src/locales/en.js CHANGED
@@ -91,5 +91,10 @@ module.exports = {
91
91
  addOption: 'Add an option',
92
92
  showMoreProperties: '{n} more property|{n} more properties',
93
93
  hideMoreProperties: 'Hide {n} property|Hide {n} properties',
94
+ deleteItem: 'Delete',
95
+ areYouSureYouWantToDeleteThisItem: 'Are you sure you want to remove this option?',
96
+ },
97
+ copyToClipboard: {
98
+ copyingToClipboardWasSuccessful: 'Copying to clipboard was successful',
94
99
  }
95
100
  };
package/src/locales/uk.js CHANGED
@@ -91,5 +91,10 @@ module.exports = {
91
91
  addOption: 'Додати опцію',
92
92
  showMoreProperties: 'Ще {n} поле|Ще {n} полів',
93
93
  hideMoreProperties: 'Приховати {n} поле|Приховати {n} полів',
94
+ deleteItem: 'Видалити',
95
+ areYouSureYouWantToDeleteThisItem: 'Ви впевнені, що хочете видалити цю опцію?',
96
+ },
97
+ copyToClipboard: {
98
+ copyingToClipboardWasSuccessful: 'Скопійовано в буфер',
94
99
  }
95
100
  };