@itfin/components 1.2.87 → 1.2.88
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 +1 -1
- package/src/assets/scss/components/_icon.scss +3 -1
- package/src/components/customize/Inline/Date.vue +86 -0
- package/src/components/customize/Inline/Multiselect.vue +55 -0
- package/src/components/customize/Inline/Text.vue +91 -0
- package/src/components/customize/PropertiesEditMenu.vue +110 -8
- package/src/components/customize/PropertiesList.vue +78 -106
- package/src/components/customize/PropertiesPopupMenu.vue +5 -4
- package/src/components/customize/PropertyInlineEdit.vue +26 -28
- package/src/components/customize/PropertyItem.vue +191 -0
- package/src/components/customize/constants.js +9 -0
- package/src/components/customize/index.stories.js +3 -3
- package/src/components/dropdown/Dropdown.vue +4 -1
- package/src/components/modal/DeleteConfirmModal.vue +3 -1
- package/src/components/popover/IconPopover.vue +7 -6
- package/src/components/select/Select.vue +2 -2
- package/src/helpers/formatters.js +3 -0
- package/src/locales/en.js +6 -0
package/package.json
CHANGED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="itf-inline-edit">
|
|
3
|
+
<div ref="input" class="input" :role="editable ? 'button' : 'textbox'" tabindex="1" @keydown.space="onEnter" @keydown.enter="onEnter" @focus="$emit('focus')" @blur="$emit('blur')">
|
|
4
|
+
<span v-if="value">{{ value | formatDate }}</span>
|
|
5
|
+
<span v-else class="text-muted">Empty</span>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<div style="display: none">
|
|
9
|
+
<div ref="dropdown" class="itf-datepicker__dropdown">
|
|
10
|
+
<itf-date-picker-inline
|
|
11
|
+
:value="value"
|
|
12
|
+
start-view="days"
|
|
13
|
+
display-format="MM/dd/yyyy"
|
|
14
|
+
value-format="yyyy-MM-dd"
|
|
15
|
+
@input="selectInlineDate"
|
|
16
|
+
></itf-date-picker-inline>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
<style lang="scss" scoped>
|
|
22
|
+
.itf-inline-edit .input {
|
|
23
|
+
border: 0 none;
|
|
24
|
+
margin: 0;
|
|
25
|
+
outline: 0;
|
|
26
|
+
resize: none;
|
|
27
|
+
background: transparent;
|
|
28
|
+
}
|
|
29
|
+
</style>
|
|
30
|
+
<script>
|
|
31
|
+
import {Vue, Component, Model, Watch, Prop} from 'vue-property-decorator';
|
|
32
|
+
import itfTextField from '../../text-field/TextField.vue';
|
|
33
|
+
import itfDatePickerInline from '../../datepicker/DatePickerInline.vue';
|
|
34
|
+
import tippy from 'tippy.js';
|
|
35
|
+
import { formatDate } from '../../../helpers/formatters';
|
|
36
|
+
import PropertyInlineEdit from "@/components/customize/PropertyInlineEdit.vue";
|
|
37
|
+
|
|
38
|
+
export default @Component({
|
|
39
|
+
name: 'itfCustomizeInlineDate',
|
|
40
|
+
components: {
|
|
41
|
+
PropertyInlineEdit,
|
|
42
|
+
itfTextField,
|
|
43
|
+
itfDatePickerInline
|
|
44
|
+
},
|
|
45
|
+
filters: {
|
|
46
|
+
formatDate
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
class itfCustomizeInlineText extends Vue {
|
|
51
|
+
@Model('input') value;
|
|
52
|
+
@Prop({ type: String, default: 'bottom-start' }) placement;
|
|
53
|
+
@Prop({ type: Boolean, default: false }) editable;
|
|
54
|
+
|
|
55
|
+
tooltip = null;
|
|
56
|
+
|
|
57
|
+
mounted() {
|
|
58
|
+
if (!this.editable) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const context = this.$el.closest('.itf-append-context') || document.body;
|
|
62
|
+
this.tooltip = tippy(this.$refs.input, {
|
|
63
|
+
interactiveBorder: 30,
|
|
64
|
+
interactiveDebounce: 75,
|
|
65
|
+
animation: 'scale',
|
|
66
|
+
arrow: true,
|
|
67
|
+
content: this.$refs.dropdown,
|
|
68
|
+
allowHTML: true,
|
|
69
|
+
trigger: 'click',
|
|
70
|
+
interactive: true,
|
|
71
|
+
placement: this.placement,
|
|
72
|
+
appendTo: context
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
selectInlineDate(date) {
|
|
77
|
+
this.$emit('input', date);
|
|
78
|
+
this.tooltip.hide();
|
|
79
|
+
this.isFocused = false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
onEnter() {
|
|
83
|
+
this.tooltip.show();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
</script>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="itf-inline-edit">
|
|
3
|
+
<itf-select
|
|
4
|
+
ref="input"
|
|
5
|
+
class="input"
|
|
6
|
+
role="button"
|
|
7
|
+
multiple
|
|
8
|
+
:placeholder="focused ? 'Search for an option...' : 'Empty'"
|
|
9
|
+
:tabindex="1"
|
|
10
|
+
:disabled="!editable"
|
|
11
|
+
:options="field && field.Options"
|
|
12
|
+
:reduce="option => option.Name"
|
|
13
|
+
:get-option-label="option => option.Name"
|
|
14
|
+
@search:focus="$emit('focus')"
|
|
15
|
+
@search:blur="$emit('blur')">
|
|
16
|
+
<template #option="{ option }">
|
|
17
|
+
<div>{{ option.Name }}</div>
|
|
18
|
+
</template>
|
|
19
|
+
</itf-select>
|
|
20
|
+
</div>
|
|
21
|
+
</template>
|
|
22
|
+
<style lang="scss">
|
|
23
|
+
.itf-inline-edit .itf-select .form-control {
|
|
24
|
+
border: 0 none;
|
|
25
|
+
margin: 0;
|
|
26
|
+
outline: 0;
|
|
27
|
+
resize: none;
|
|
28
|
+
min-height: auto;
|
|
29
|
+
padding: 0.1rem 0;
|
|
30
|
+
font-family: var(--bs-body-font-family);
|
|
31
|
+
font-size: var(--bs-body-font-size);
|
|
32
|
+
box-shadow: none !important;
|
|
33
|
+
background: transparent !important;
|
|
34
|
+
}
|
|
35
|
+
</style>
|
|
36
|
+
<script>
|
|
37
|
+
import {Vue, Component, Model, Watch, Prop} from 'vue-property-decorator';
|
|
38
|
+
import itfTextField from '../../text-field/TextField.vue';
|
|
39
|
+
import itfSelect from '../../select/Select.vue';
|
|
40
|
+
|
|
41
|
+
export default @Component({
|
|
42
|
+
name: 'itfCustomizeInlineMultiselect',
|
|
43
|
+
components: {
|
|
44
|
+
itfTextField,
|
|
45
|
+
itfSelect
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
class itfCustomizeInlineMultiselect extends Vue {
|
|
50
|
+
@Model('input') value;
|
|
51
|
+
@Prop(Boolean) focused;
|
|
52
|
+
@Prop(Boolean) editable;
|
|
53
|
+
@Prop() field;
|
|
54
|
+
}
|
|
55
|
+
</script>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="itf-inline-edit">
|
|
3
|
+
<div
|
|
4
|
+
:role="editable ? 'button' : 'textbox'"
|
|
5
|
+
tabindex="1"
|
|
6
|
+
ref="input"
|
|
7
|
+
@focus="onFocus"
|
|
8
|
+
@blur="onBlur"
|
|
9
|
+
@keydown.enter="onEnter"
|
|
10
|
+
@keydown.esc="onCancel"
|
|
11
|
+
class="notranslate"
|
|
12
|
+
spellcheck="true"
|
|
13
|
+
placeholder=" "
|
|
14
|
+
:contenteditable="editable"
|
|
15
|
+
v-html="value">
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
</template>
|
|
19
|
+
<style lang="scss" scoped>
|
|
20
|
+
.itf-inline-edit {
|
|
21
|
+
margin: -5px;
|
|
22
|
+
padding: 5px;
|
|
23
|
+
}
|
|
24
|
+
.notranslate {
|
|
25
|
+
border: 0 none;
|
|
26
|
+
margin: 0;
|
|
27
|
+
outline: 0;
|
|
28
|
+
resize: none;
|
|
29
|
+
background: transparent;
|
|
30
|
+
max-width: 100%;
|
|
31
|
+
width: 100%;
|
|
32
|
+
white-space: pre-wrap;
|
|
33
|
+
word-break: break-word;
|
|
34
|
+
caret-color: var(--bs-body-color);
|
|
35
|
+
}
|
|
36
|
+
</style>
|
|
37
|
+
<script>
|
|
38
|
+
import { Vue, Component, Model, Prop } from 'vue-property-decorator';
|
|
39
|
+
import itfTextField from '../../text-field/TextField.vue';
|
|
40
|
+
import PropertyInlineEdit from "@/components/customize/PropertyInlineEdit.vue";
|
|
41
|
+
|
|
42
|
+
export default @Component({
|
|
43
|
+
name: 'itfCustomizeInlineText',
|
|
44
|
+
components: {
|
|
45
|
+
PropertyInlineEdit,
|
|
46
|
+
itfTextField
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
class itfCustomizeInlineText extends Vue {
|
|
51
|
+
@Model('input') value;
|
|
52
|
+
@Prop(Boolean) editable;
|
|
53
|
+
|
|
54
|
+
mounted() {
|
|
55
|
+
this.$refs.input.addEventListener('paste', function(e) {
|
|
56
|
+
// cancel paste
|
|
57
|
+
e.preventDefault();
|
|
58
|
+
|
|
59
|
+
// get text representation of clipboard
|
|
60
|
+
var text = (e.originalEvent || e).clipboardData.getData('text/plain');
|
|
61
|
+
|
|
62
|
+
// insert text manually
|
|
63
|
+
document.execCommand("insertHTML", false, text);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
onEnter(event) {
|
|
68
|
+
if (event.shiftKey) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
event.preventDefault();
|
|
72
|
+
event.stopPropagation();
|
|
73
|
+
|
|
74
|
+
this.$refs.input.blur();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
onBlur() {
|
|
78
|
+
this.$emit('input', this.$refs.input.innerHTML);
|
|
79
|
+
this.$emit('blur');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
onCancel(event) {
|
|
83
|
+
event.target.value = this.value;
|
|
84
|
+
this.$refs.input.blur();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
onFocus() {
|
|
88
|
+
this.$emit('focus');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
</script>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="itf-append-context">
|
|
2
|
+
<div class="itf-append-context" style="min-width: 300px">
|
|
3
3
|
<div class="d-flex align-items-center px-3 py-2">
|
|
4
4
|
<div class="me-1">
|
|
5
5
|
<itf-icon-popover title="Icon" removable :value="value.Icon" @input="setParam({ Icon: $event })">
|
|
@@ -10,20 +10,77 @@
|
|
|
10
10
|
</div>
|
|
11
11
|
|
|
12
12
|
<div class="flex-grow-1">
|
|
13
|
-
<itf-text-field :value="value.Name" @input="setParam({ Name: $event })" />
|
|
13
|
+
<itf-text-field ref="input" :value="value.Name" @input="setParam({ Name: $event })" />
|
|
14
14
|
</div>
|
|
15
15
|
</div>
|
|
16
16
|
<div>
|
|
17
17
|
<a class="dropdown-item d-flex justify-content-between" @click.prevent="$emit('edit')">
|
|
18
18
|
<span>{{ $t('components.customize.type') }}</span>
|
|
19
|
-
<span class="text-muted">{{
|
|
19
|
+
<span class="text-muted">{{ itemType }} ></span>
|
|
20
20
|
</a>
|
|
21
|
+
<ul class="itf-dropdown__menu shadow dropdown-menu dropdown-submenu">
|
|
22
|
+
<li><h6 class="dropdown-header">{{ $t('components.customize.type') }}</h6></li>
|
|
23
|
+
<li v-for="(type) of types">
|
|
24
|
+
<a class="dropdown-item d-flex align-items-center" href="" @click.prevent="setParam({ Type: type.Type })">
|
|
25
|
+
<itf-icon :name="type.Icon" :size="16" class="me-1" />
|
|
26
|
+
<span v-text="type.Name"></span>
|
|
27
|
+
<itf-icon v-if="value.Type === type.Type" name="check" />
|
|
28
|
+
</a>
|
|
29
|
+
</li>
|
|
30
|
+
</ul>
|
|
21
31
|
</div>
|
|
32
|
+
<div v-if="value.Type === 'multiselect'" sortable-skip>
|
|
33
|
+
<div class="d-flex justify-content-between pe-2">
|
|
34
|
+
<h6 class="dropdown-header">{{ $t('components.customize.options') }}</h6>
|
|
35
|
+
<div v-if="value.Options && value.Options.length">
|
|
36
|
+
<itf-button icon small @click.prevent="addNewOption">
|
|
37
|
+
<itf-icon name="plus" />
|
|
38
|
+
</itf-button>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div v-if="isNewOptionVisible" class="px-3">
|
|
43
|
+
<itf-text-field
|
|
44
|
+
ref="newOptionInput"
|
|
45
|
+
v-model="newOption"
|
|
46
|
+
placeholder="Type a new option..."
|
|
47
|
+
@blur="onBlurOption"
|
|
48
|
+
@keydown.enter="submitNewOption"
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
51
|
+
<div v-else-if="!value.Options || !value.Options.length" class="px-3">
|
|
52
|
+
<itf-button small block class="text-start" @click="addNewOption">
|
|
53
|
+
<itf-icon name="plus" />
|
|
54
|
+
{{ $t('components.customize.addOption') }}
|
|
55
|
+
</itf-button>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<sortable
|
|
59
|
+
:value="value.Options"
|
|
60
|
+
@input="setParam({ Options: $event })"
|
|
61
|
+
axis="y"
|
|
62
|
+
require-sortable-attribute
|
|
63
|
+
auto-scroll="window"
|
|
64
|
+
>
|
|
65
|
+
<div v-for="option of value.Options" class="dropdown-item d-flex justify-content-between" sortable>
|
|
66
|
+
<div>
|
|
67
|
+
<itf-icon class="dragHandle" name="drag_vertical" />
|
|
68
|
+
<span>{{ option.Name }}</span>
|
|
69
|
+
</div>
|
|
70
|
+
<span class="text-muted">></span>
|
|
71
|
+
</div>
|
|
72
|
+
</sortable>
|
|
73
|
+
</div>
|
|
74
|
+
<div><hr class="dropdown-divider"></div>
|
|
75
|
+
<div><a class="dropdown-item" @click.prevent="$emit('duplicate')"><itf-icon name="duplicate" /> {{ $t('components.customize.duplicateProperty') }}</a></div>
|
|
22
76
|
<div>
|
|
23
|
-
<
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
77
|
+
<itf-delete-confirm-modal class="me-1" @delete="$emit('delete')">
|
|
78
|
+
<template #activator="{ on }">
|
|
79
|
+
<a class="dropdown-item text-danger" @click.prevent="on.click"><itf-icon name="trash" /> {{ $t('components.customize.deleteProperty') }}</a>
|
|
80
|
+
</template>
|
|
81
|
+
<h5 class="mb-0">{{$t('components.popover.confirmDelete')}}</h5>
|
|
82
|
+
<p class="mb-0">{{ $t('components.customize.areYouSureYouWantToDeleteThisField') }}</p>
|
|
83
|
+
</itf-delete-confirm-modal>
|
|
27
84
|
</div>
|
|
28
85
|
</div>
|
|
29
86
|
</template>
|
|
@@ -32,22 +89,67 @@ import { Vue, Component, Model } from 'vue-property-decorator';
|
|
|
32
89
|
import itfTextField from '../text-field/TextField.vue';
|
|
33
90
|
import itfIcon from '../icon/Icon';
|
|
34
91
|
import itfButton from '../button/Button';
|
|
92
|
+
import Sortable from '../sortable/Sortable';
|
|
35
93
|
import itfIconPopover from '../popover/IconPopover.vue';
|
|
94
|
+
import {INLINE_TYPES} from "@/components/customize/constants";
|
|
95
|
+
import itfDeleteConfirmModal from "@/components/modal/DeleteConfirmModal.vue";
|
|
96
|
+
import PropertyItem from "@/components/customize/PropertyItem.vue";
|
|
36
97
|
|
|
37
98
|
export default @Component({
|
|
38
99
|
components: {
|
|
100
|
+
PropertyItem,
|
|
101
|
+
itfDeleteConfirmModal,
|
|
39
102
|
itfButton,
|
|
40
103
|
itfIcon,
|
|
41
104
|
itfTextField,
|
|
42
|
-
itfIconPopover
|
|
105
|
+
itfIconPopover,
|
|
106
|
+
Sortable
|
|
43
107
|
}
|
|
44
108
|
})
|
|
45
109
|
|
|
46
110
|
class PropertiesEditMenu extends Vue {
|
|
47
111
|
@Model('input') value;
|
|
48
112
|
|
|
113
|
+
isNewOptionVisible = false;
|
|
114
|
+
newOption = '';
|
|
115
|
+
|
|
116
|
+
get types() {
|
|
117
|
+
return INLINE_TYPES;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
get itemType() {
|
|
121
|
+
const type = INLINE_TYPES.find((type) => type.Type === this.value.Type);
|
|
122
|
+
return type ? type.Name : '';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
mounted() {
|
|
126
|
+
this.$nextTick(() => {
|
|
127
|
+
this.$refs.input.focus();
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
49
131
|
setParam(obj) {
|
|
50
132
|
this.$emit('input', { ...this.value, ...obj });
|
|
51
133
|
}
|
|
134
|
+
|
|
135
|
+
onBlurOption() {
|
|
136
|
+
this.isNewOptionVisible = false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
addNewOption() {
|
|
140
|
+
this.isNewOptionVisible = true;
|
|
141
|
+
this.$nextTick(() => {
|
|
142
|
+
this.$refs.newOptionInput.focus();
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
submitNewOption() {
|
|
147
|
+
const newOption = this.newOption;
|
|
148
|
+
if (newOption) {
|
|
149
|
+
this.setParam({ Options: [{ Name: newOption }, ...(this.value.Options || [])] });
|
|
150
|
+
this.newOption = '';
|
|
151
|
+
this.isNewOptionVisible = false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
52
154
|
}
|
|
53
155
|
</script>
|
|
@@ -4,55 +4,37 @@
|
|
|
4
4
|
require-sortable-attribute
|
|
5
5
|
v-model="list"
|
|
6
6
|
axis="y"
|
|
7
|
+
:read-only="!editable"
|
|
7
8
|
auto-scroll="window"
|
|
8
9
|
>
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
<div style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" v-text="field.Name" />
|
|
23
|
-
</div>
|
|
24
|
-
</template>
|
|
25
|
-
<properties-edit-menu
|
|
26
|
-
v-if="isEditMode"
|
|
27
|
-
sortable-skip
|
|
28
|
-
:value="field"
|
|
29
|
-
:loading="loading"
|
|
30
|
-
@input="onChange($event, n)"
|
|
31
|
-
/>
|
|
32
|
-
<properties-popup-menu
|
|
33
|
-
v-else
|
|
34
|
-
sortable-skip
|
|
35
|
-
:field="field"
|
|
36
|
-
:loading="loading"
|
|
37
|
-
@edit="isEditMode = true"
|
|
38
|
-
/>
|
|
39
|
-
</itf-dropdown>
|
|
40
|
-
</div>
|
|
41
|
-
<div class="b-properties-list__value" sortable-skip>
|
|
42
|
-
<div role="button" tabindex="1">
|
|
43
|
-
<property-inline-edit :value="value[field.Id]" @input="onValueSet(field, $event)" />
|
|
44
|
-
<span v-if="value && value[field.Id]">{{ value[field.Id] }}</span>
|
|
45
|
-
<span v-else class="text-muted"> ({{ $t('notSet') }})</span>
|
|
46
|
-
</div>
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
10
|
+
<property-item
|
|
11
|
+
v-for="(field, n) of list"
|
|
12
|
+
:key="n"
|
|
13
|
+
ref="properties"
|
|
14
|
+
:field="field"
|
|
15
|
+
:editable="editable"
|
|
16
|
+
:value="value[field.Id]"
|
|
17
|
+
@input="$emit('input', { ...value, [field.Id]: $event })"
|
|
18
|
+
@delete="onDelete(field)"
|
|
19
|
+
@duplicate="onDuplicate(field)"
|
|
20
|
+
@update:field="onChange($event, n)"
|
|
21
|
+
/>
|
|
49
22
|
</sortable>
|
|
50
23
|
|
|
51
|
-
<div class="text-start">
|
|
52
|
-
<itf-
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
24
|
+
<div v-if="editable" class="text-start">
|
|
25
|
+
<itf-dropdown ref="newItemDd" autoclose="outside" class="flex-grow-1 mw-100" shadow :button-options="{ class: 'text-muted', small: true }">
|
|
26
|
+
<template #button>
|
|
27
|
+
<itf-icon name="plus" />
|
|
28
|
+
Add property
|
|
29
|
+
</template>
|
|
30
|
+
<div><h6 class="dropdown-header">{{ $t('components.customize.type') }}</h6></div>
|
|
31
|
+
<div v-for="type of types" :keys="type.Type">
|
|
32
|
+
<a class="dropdown-item d-flex align-items-center" @click.prevent="createNewField(type)">
|
|
33
|
+
<itf-icon :name="type.Icon" :size="16" class="me-1" />
|
|
34
|
+
{{ type.Name }}
|
|
35
|
+
</a>
|
|
36
|
+
</div>
|
|
37
|
+
</itf-dropdown>
|
|
56
38
|
</div>
|
|
57
39
|
</div>
|
|
58
40
|
</template>
|
|
@@ -79,55 +61,6 @@
|
|
|
79
61
|
display: flex;
|
|
80
62
|
flex-direction: column;
|
|
81
63
|
padding: 5px 3px 5px 15px;
|
|
82
|
-
|
|
83
|
-
&__draghandler {
|
|
84
|
-
user-select: none;
|
|
85
|
-
transition: opacity 150ms ease-in 0s;
|
|
86
|
-
cursor: grab;
|
|
87
|
-
display: flex;
|
|
88
|
-
align-items: center;
|
|
89
|
-
justify-content: center;
|
|
90
|
-
width: 18px;
|
|
91
|
-
height: 24px;
|
|
92
|
-
opacity: 0;
|
|
93
|
-
border-radius: 3px;
|
|
94
|
-
fill: rgba(55, 53, 47, 0.35);
|
|
95
|
-
}
|
|
96
|
-
&__inner:hover &__draghandler {
|
|
97
|
-
opacity: 1;
|
|
98
|
-
}
|
|
99
|
-
&__inner {
|
|
100
|
-
display: flex;
|
|
101
|
-
padding-bottom: 4px;
|
|
102
|
-
width: 100%;
|
|
103
|
-
}
|
|
104
|
-
&__name, &__value {
|
|
105
|
-
cursor: pointer;
|
|
106
|
-
display: flex;
|
|
107
|
-
align-items: center;
|
|
108
|
-
height: 100%;
|
|
109
|
-
width: 100%;
|
|
110
|
-
border-radius: 3px;
|
|
111
|
-
padding: 0 6px;
|
|
112
|
-
}
|
|
113
|
-
&__name {
|
|
114
|
-
display: flex;
|
|
115
|
-
align-items: center;
|
|
116
|
-
height: 34px;
|
|
117
|
-
width: 160px;
|
|
118
|
-
flex: 0 0 auto;
|
|
119
|
-
}
|
|
120
|
-
&__icon {
|
|
121
|
-
width: 22px;
|
|
122
|
-
margin-left: -22px;
|
|
123
|
-
opacity: 1;
|
|
124
|
-
}
|
|
125
|
-
&__value {
|
|
126
|
-
display: flex;
|
|
127
|
-
margin-left: 4px;
|
|
128
|
-
height: 100%;
|
|
129
|
-
min-width: 0;
|
|
130
|
-
}
|
|
131
64
|
}
|
|
132
65
|
</style>
|
|
133
66
|
<script>
|
|
@@ -138,11 +71,11 @@ import itfDropdown from '../dropdown/Dropdown.vue';
|
|
|
138
71
|
import itfLabel from '../form/Label.vue';
|
|
139
72
|
import itfTextField from '../text-field/TextField.vue';
|
|
140
73
|
import Sortable from '../sortable/Sortable';
|
|
141
|
-
import
|
|
142
|
-
import
|
|
143
|
-
import PropertyInlineEdit from './PropertyInlineEdit.vue';
|
|
74
|
+
import PropertyItem from './PropertyItem';
|
|
75
|
+
import { INLINE_TYPES } from './constants';
|
|
144
76
|
|
|
145
77
|
export default @Component({
|
|
78
|
+
name: 'itfPropertiesList',
|
|
146
79
|
components: {
|
|
147
80
|
itfIcon,
|
|
148
81
|
itfDropdown,
|
|
@@ -150,20 +83,17 @@ export default @Component({
|
|
|
150
83
|
itfLabel,
|
|
151
84
|
itfButton,
|
|
152
85
|
Sortable,
|
|
153
|
-
|
|
154
|
-
PropertiesEditMenu,
|
|
155
|
-
PropertyInlineEdit
|
|
86
|
+
PropertyItem
|
|
156
87
|
}
|
|
157
88
|
})
|
|
158
89
|
class itfPropertiesList extends Vue {
|
|
159
90
|
@Model('input') value;
|
|
160
91
|
@PropSync('fields') list;
|
|
161
92
|
@Prop({ type: Boolean, default: false }) loading;
|
|
93
|
+
@Prop({ type: Boolean, default: false }) editable;
|
|
162
94
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
onClose() {
|
|
166
|
-
this.isEditMode = false;
|
|
95
|
+
get types() {
|
|
96
|
+
return INLINE_TYPES;
|
|
167
97
|
}
|
|
168
98
|
|
|
169
99
|
onChange(item, index) {
|
|
@@ -172,8 +102,50 @@ class itfPropertiesList extends Vue {
|
|
|
172
102
|
this.$emit('update:fields', list);
|
|
173
103
|
}
|
|
174
104
|
|
|
175
|
-
|
|
176
|
-
|
|
105
|
+
onDelete(item) {
|
|
106
|
+
const list = [...this.list].filter((i) => i.Id !== item.Id);
|
|
107
|
+
this.$emit('update:fields', list);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
onDuplicate(item) {
|
|
111
|
+
const list = [...this.list];
|
|
112
|
+
const newItem = { ...item };
|
|
113
|
+
newItem.Id = `new-${Math.random().toString(36).substr(2, 9)}`;
|
|
114
|
+
const index = newItem.Name.match(/\((\d+)\)$/);
|
|
115
|
+
// додає вкінці номер (1), (2) і т.д.
|
|
116
|
+
newItem.Name = newItem.Name.replace(/\s\(\d+\)$/, '');
|
|
117
|
+
newItem.Name = `${newItem.Name}${index ? ` (${parseInt(index[1], 10) + 1})` : ' (1)'}`;
|
|
118
|
+
list.push(newItem);
|
|
119
|
+
this.$emit('update:fields', list);
|
|
120
|
+
this.$refs.newItemDd.hide();
|
|
121
|
+
|
|
122
|
+
this.$nextTick(() => {
|
|
123
|
+
for (const prop of this.$refs.properties) {
|
|
124
|
+
prop.hideEditMenu();
|
|
125
|
+
}
|
|
126
|
+
setTimeout(() => {
|
|
127
|
+
this.$refs.properties[list.length - 1].showEditMenu();
|
|
128
|
+
}, 10);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
createNewField(type) {
|
|
133
|
+
const list = [...this.list];
|
|
134
|
+
list.push({
|
|
135
|
+
Id: `new-${Math.random().toString(36).substr(2, 9)}`,
|
|
136
|
+
Name: type.Name,
|
|
137
|
+
Type: type.Type,
|
|
138
|
+
Icon: type.Icon,
|
|
139
|
+
Visible: 'show'
|
|
140
|
+
});
|
|
141
|
+
this.$emit('update:fields', list);
|
|
142
|
+
this.$refs.newItemDd.hide();
|
|
143
|
+
|
|
144
|
+
this.$nextTick(() => {
|
|
145
|
+
setTimeout(() => {
|
|
146
|
+
this.$refs.properties[list.length - 1].showEditMenu();
|
|
147
|
+
}, 10);
|
|
148
|
+
});
|
|
177
149
|
}
|
|
178
150
|
}
|
|
179
151
|
</script>
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
<div>
|
|
6
6
|
<a class="dropdown-item" @click.prevent><itf-icon name="eye_no" /> {{ $t('components.customize.hideProperty') }}</a>
|
|
7
7
|
<ul class="itf-dropdown__menu shadow dropdown-menu dropdown-submenu">
|
|
8
|
-
<li><a class="dropdown-item" href="
|
|
9
|
-
<li><a class="dropdown-item" href="
|
|
10
|
-
<li><a class="dropdown-item" href="
|
|
8
|
+
<li><a class="dropdown-item" href="" @click.prevent="$emit('visibility', 'show')">{{ $t('components.customize.alwaysShow') }}<itf-icon v-if="field.Visible === 'show'" name="check" /></a></li>
|
|
9
|
+
<li><a class="dropdown-item" href="" @click.prevent="$emit('visibility', 'filled')">{{ $t('components.customize.hideWhenEmpty') }}<itf-icon v-if="field.Visible === 'filled'" name="check" /></a></li>
|
|
10
|
+
<li><a class="dropdown-item" href="" @click.prevent="$emit('visibility', 'hide')">{{ $t('components.customize.alwaysHide') }}<itf-icon v-if="field.Visible === 'hide'" name="check" /></a></li>
|
|
11
11
|
</ul>
|
|
12
12
|
</div>
|
|
13
13
|
<div><a class="dropdown-item" @click.prevent="$emit('duplicate')"><itf-icon name="duplicate" /> {{ $t('components.customize.duplicateProperty') }}</a></div>
|
|
14
14
|
<div>
|
|
15
|
-
<itf-delete-confirm-modal class="me-1"
|
|
15
|
+
<itf-delete-confirm-modal class="me-1" @delete="$emit('delete')">
|
|
16
16
|
<template #activator="{ on }">
|
|
17
17
|
<a class="dropdown-item text-danger" @click.prevent="on.click"><itf-icon name="trash" /> {{ $t('components.customize.deleteProperty') }}</a>
|
|
18
18
|
</template>
|
|
@@ -42,5 +42,6 @@ export default @Component({
|
|
|
42
42
|
})
|
|
43
43
|
|
|
44
44
|
class PropertiesEditMenu extends Vue {
|
|
45
|
+
@Prop() field;
|
|
45
46
|
}
|
|
46
47
|
</script>
|
|
@@ -1,46 +1,44 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
3
|
-
<
|
|
2
|
+
<div>
|
|
3
|
+
<component
|
|
4
|
+
v-if="component"
|
|
5
|
+
:is="component"
|
|
6
|
+
:value="value"
|
|
7
|
+
:field="field"
|
|
8
|
+
:focused="focused"
|
|
9
|
+
:editable="editable"
|
|
10
|
+
@input="$emit('input', $event)"
|
|
11
|
+
@focus="$emit('focus')"
|
|
12
|
+
@blur="$emit('blur')"
|
|
13
|
+
/>
|
|
4
14
|
</div>
|
|
5
15
|
</template>
|
|
6
|
-
<style lang="scss">
|
|
7
|
-
.itf-inline-edit {
|
|
8
|
-
textarea {
|
|
9
|
-
border: 0 none;
|
|
10
|
-
margin: 0;
|
|
11
|
-
outline: 0;
|
|
12
|
-
resize: none;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
</style>
|
|
16
16
|
<script>
|
|
17
|
-
import { Vue, Component, Model,
|
|
18
|
-
import itfTextField from '../text-field/TextField.vue';
|
|
17
|
+
import { Vue, Component, Model, Prop } from 'vue-property-decorator';
|
|
19
18
|
import itfIcon from '../icon/Icon';
|
|
20
19
|
import itfButton from '../button/Button';
|
|
21
|
-
import
|
|
20
|
+
import { INLINE_TYPES } from './constants';
|
|
22
21
|
|
|
23
22
|
export default @Component({
|
|
23
|
+
name: 'itfPropertyInlineEdit',
|
|
24
24
|
components: {
|
|
25
|
-
itfButton,
|
|
26
25
|
itfIcon,
|
|
27
|
-
|
|
28
|
-
itfDeleteConfirmModal
|
|
26
|
+
itfButton,
|
|
29
27
|
}
|
|
30
28
|
})
|
|
31
29
|
|
|
32
|
-
class
|
|
30
|
+
class itfPropertyInlineEdit extends Vue {
|
|
33
31
|
@Model('input') value;
|
|
32
|
+
@Prop() field;
|
|
33
|
+
@Prop(Boolean) focused;
|
|
34
|
+
@Prop(Boolean) editable;
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
onFocus() {
|
|
43
|
-
this.isFocused = true;
|
|
36
|
+
get component() {
|
|
37
|
+
if (!this.field) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const type = INLINE_TYPES.find(({ Type }) => Type === this.field.Type);
|
|
41
|
+
return type ? type.Component : null;
|
|
44
42
|
}
|
|
45
43
|
}
|
|
46
44
|
</script>
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="b-properties-list__inner">
|
|
3
|
+
<div class="b-properties-list__name" :class="{'editable': editable}">
|
|
4
|
+
<div class="b-properties-list__icon">
|
|
5
|
+
<div v-if="editable" role="button" tabindex="-1" class="b-properties-list__draghandler" sortable>
|
|
6
|
+
<itf-icon class="dragHandle" name="drag_vertical" />
|
|
7
|
+
</div>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<itf-dropdown :disabled="!editable" ref="editDd" autoclose="outside" class="flex-grow-1 mw-100" shadow :button-options="{ icon: true, block: true }" @close="onClose">
|
|
11
|
+
<template #button>
|
|
12
|
+
<div class="d-flex align-items-center">
|
|
13
|
+
<itf-icon :name="field.Icon" :size="16" class="me-1" />
|
|
14
|
+
|
|
15
|
+
<div style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" v-text="field.Name" />
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
18
|
+
<properties-edit-menu
|
|
19
|
+
v-if="isEditMode"
|
|
20
|
+
sortable-skip
|
|
21
|
+
:value="field"
|
|
22
|
+
:loading="loading"
|
|
23
|
+
@input="onChange($event)"
|
|
24
|
+
@delete="$emit('delete')"
|
|
25
|
+
@duplicate="$emit('duplicate')"
|
|
26
|
+
/>
|
|
27
|
+
<properties-popup-menu
|
|
28
|
+
v-else
|
|
29
|
+
sortable-skip
|
|
30
|
+
:field="field"
|
|
31
|
+
:loading="loading"
|
|
32
|
+
@edit="isEditMode = true"
|
|
33
|
+
@delete="$emit('delete')"
|
|
34
|
+
@duplicate="$emit('duplicate')"
|
|
35
|
+
@customize="$emit('customize')"
|
|
36
|
+
@visibility="onVisibilityChange(field, $event)"
|
|
37
|
+
/>
|
|
38
|
+
</itf-dropdown>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="b-properties-list__value rounded-2" :class="{'editable': editable, 'active shadow rounded-2 bg-body': focusId === field.Id}" sortable-skip>
|
|
41
|
+
<property-inline-edit
|
|
42
|
+
@focus="onFocus(field.Id)"
|
|
43
|
+
@blur="onBlur(field.Id)"
|
|
44
|
+
class="flex-grow-1 b-properties-list__inline-editor"
|
|
45
|
+
:field="field"
|
|
46
|
+
:value="value"
|
|
47
|
+
:editable="editable"
|
|
48
|
+
:focused="focusId === field.Id"
|
|
49
|
+
@input="$emit('input', $event)"
|
|
50
|
+
/>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</template>
|
|
54
|
+
<style lang="scss">
|
|
55
|
+
.b-properties-list {
|
|
56
|
+
&__draghandler {
|
|
57
|
+
user-select: none;
|
|
58
|
+
transition: opacity 150ms ease-in 0s;
|
|
59
|
+
cursor: grab;
|
|
60
|
+
display: flex;
|
|
61
|
+
align-items: center;
|
|
62
|
+
justify-content: center;
|
|
63
|
+
width: 18px;
|
|
64
|
+
height: 24px;
|
|
65
|
+
opacity: 0;
|
|
66
|
+
border-radius: 3px;
|
|
67
|
+
fill: rgba(55, 53, 47, 0.35);
|
|
68
|
+
}
|
|
69
|
+
&__inner:hover &__draghandler {
|
|
70
|
+
opacity: 1;
|
|
71
|
+
}
|
|
72
|
+
&__inner {
|
|
73
|
+
display: flex;
|
|
74
|
+
padding-bottom: 4px;
|
|
75
|
+
width: 100%;
|
|
76
|
+
}
|
|
77
|
+
&__name, &__value {
|
|
78
|
+
display: flex;
|
|
79
|
+
align-items: center;
|
|
80
|
+
height: 100%;
|
|
81
|
+
width: 100%;
|
|
82
|
+
border-radius: 3px;
|
|
83
|
+
padding: 0 6px;
|
|
84
|
+
|
|
85
|
+
&.editable {
|
|
86
|
+
cursor: pointer;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
&__name {
|
|
90
|
+
display: flex;
|
|
91
|
+
align-items: center;
|
|
92
|
+
height: 34px;
|
|
93
|
+
width: 250px;
|
|
94
|
+
flex: 0 0 auto;
|
|
95
|
+
}
|
|
96
|
+
&__icon {
|
|
97
|
+
width: 22px;
|
|
98
|
+
margin-left: -22px;
|
|
99
|
+
opacity: 1;
|
|
100
|
+
}
|
|
101
|
+
&__value {
|
|
102
|
+
display: flex;
|
|
103
|
+
margin-left: 4px;
|
|
104
|
+
height: 100%;
|
|
105
|
+
min-width: 0;
|
|
106
|
+
padding: 0.25rem;
|
|
107
|
+
min-height: 34px;
|
|
108
|
+
|
|
109
|
+
&.editable &:hover, &.editable.active {
|
|
110
|
+
background-color: rgba(55, 53, 47, 0.08);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
</style>
|
|
115
|
+
<script>
|
|
116
|
+
import { Vue, Component, Model, Prop, PropSync } from 'vue-property-decorator';
|
|
117
|
+
import itfIcon from '../icon/Icon';
|
|
118
|
+
import itfButton from '../button/Button';
|
|
119
|
+
import itfDropdown from '../dropdown/Dropdown.vue';
|
|
120
|
+
import itfLabel from '../form/Label.vue';
|
|
121
|
+
import itfTextField from '../text-field/TextField.vue';
|
|
122
|
+
import Sortable from '../sortable/Sortable';
|
|
123
|
+
import PropertiesPopupMenu from './PropertiesPopupMenu.vue';
|
|
124
|
+
import PropertiesEditMenu from './PropertiesEditMenu.vue';
|
|
125
|
+
import PropertyInlineEdit from './PropertyInlineEdit.vue';
|
|
126
|
+
import { INLINE_TYPES } from './constants';
|
|
127
|
+
|
|
128
|
+
export default @Component({
|
|
129
|
+
name: 'itfPropertyItem',
|
|
130
|
+
components: {
|
|
131
|
+
itfIcon,
|
|
132
|
+
itfDropdown,
|
|
133
|
+
itfTextField,
|
|
134
|
+
itfLabel,
|
|
135
|
+
itfButton,
|
|
136
|
+
Sortable,
|
|
137
|
+
PropertiesPopupMenu,
|
|
138
|
+
PropertiesEditMenu,
|
|
139
|
+
PropertyInlineEdit
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
class itfPropertyItem extends Vue {
|
|
143
|
+
@Model('input') value;
|
|
144
|
+
@Prop() field;
|
|
145
|
+
@Prop({ type: Boolean, default: false }) loading;
|
|
146
|
+
@Prop({ type: Boolean, default: false }) editable;
|
|
147
|
+
|
|
148
|
+
isEditMode = false;
|
|
149
|
+
focusId = null;
|
|
150
|
+
|
|
151
|
+
get types() {
|
|
152
|
+
return INLINE_TYPES;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
onFocus(id) {
|
|
156
|
+
if (!this.editable) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
this.focusId = id;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
onBlur() {
|
|
163
|
+
this.focusId = null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
onClose() {
|
|
167
|
+
this.isEditMode = false;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
onChange(item) {
|
|
171
|
+
this.$emit('update:field', item);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
onVisibilityChange(field, value) {
|
|
175
|
+
const newField = { ...field };
|
|
176
|
+
newField.Visible = value;
|
|
177
|
+
this.$emit('update:field', newField);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
hideEditMenu() {
|
|
181
|
+
this.$refs.editDd.hide();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
showEditMenu() {
|
|
185
|
+
this.$refs.editDd.show();
|
|
186
|
+
this.$nextTick(() => {
|
|
187
|
+
this.isEditMode = true;
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
</script>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import InlineText from './Inline/Text';
|
|
2
|
+
import InlineDate from './Inline/Date';
|
|
3
|
+
import InlineMultiselect from './Inline/Multiselect.vue';
|
|
4
|
+
|
|
5
|
+
export const INLINE_TYPES = [
|
|
6
|
+
{ Type: 'text', Name: 'Text', Icon: 'type_text', Component: InlineText },
|
|
7
|
+
{ Type: 'date', Name: 'Date', Icon: 'type_date', Component: InlineDate },
|
|
8
|
+
{ Type: 'multiselect', Name: 'Multiselect', Icon: 'type_multiselect', Component: InlineMultiselect },
|
|
9
|
+
];
|
|
@@ -27,8 +27,8 @@ storiesOf('Common', module)
|
|
|
27
27
|
'2021-10-21': { text: '🎉', class: 'test' }
|
|
28
28
|
},
|
|
29
29
|
list: [
|
|
30
|
-
{ Id: 'test1', Name: '
|
|
31
|
-
{ Id: 'test2', Name: 'Test2', Icon: 'eye' },
|
|
30
|
+
{ Id: 'test1', Name: 'Last perfomance appraisal date', Icon: 'eye', Type: 'text' },
|
|
31
|
+
{ Id: 'test2', Name: 'Test2', Icon: 'eye', Type: 'date' },
|
|
32
32
|
],
|
|
33
33
|
value: {
|
|
34
34
|
test1: 'test1'
|
|
@@ -58,7 +58,7 @@ storiesOf('Common', module)
|
|
|
58
58
|
|
|
59
59
|
{{value}}
|
|
60
60
|
|
|
61
|
-
<itf-properties-list :fields.sync="list" v-model="value"></itf-properties-list>
|
|
61
|
+
<itf-properties-list editable :fields.sync="list" v-model="value"></itf-properties-list>
|
|
62
62
|
|
|
63
63
|
</itf-app>
|
|
64
64
|
</div>`,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
|
|
3
3
|
<div class="itf-dropdown" :class="`drop${placement}`">
|
|
4
|
+
<div v-if="disabled"><slot name="button">{{label}}</slot></div>
|
|
4
5
|
<itf-button
|
|
6
|
+
v-else
|
|
5
7
|
:class="{ 'dropdown-toggle': toggle }"
|
|
6
8
|
v-bind="buttonOptions"
|
|
7
9
|
ref="toggle"
|
|
@@ -41,6 +43,7 @@ class itfDropdown extends Vue {
|
|
|
41
43
|
@Prop({ type: Boolean }) right;
|
|
42
44
|
@Prop({ type: Boolean }) toggle;
|
|
43
45
|
@Prop({ type: Boolean }) shadow;
|
|
46
|
+
@Prop({ type: Boolean }) disabled;
|
|
44
47
|
@Prop({ validator: (value) => [true, false, 'inside', 'outside'].includes(value), default: true }) autoclose;
|
|
45
48
|
@Prop({ type: Object, default: () => ({}) }) buttonOptions;
|
|
46
49
|
|
|
@@ -69,7 +72,7 @@ class itfDropdown extends Vue {
|
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
async mounted() {
|
|
72
|
-
if (typeof window === 'undefined') {
|
|
75
|
+
if (typeof window === 'undefined' || !this.$refs.toggle) {
|
|
73
76
|
return;
|
|
74
77
|
}
|
|
75
78
|
const { default: Dropdown } = await import('bootstrap/js/src/dropdown.js');
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<itf-popover :visible.sync="
|
|
2
|
+
<itf-popover :visible.sync="isVisible" ref="popover" :trigger="trigger" custom-class="nopadding" :placement="placement">
|
|
3
3
|
<template #activator="{ on }">
|
|
4
4
|
<slot name="activator" :on="on"></slot>
|
|
5
5
|
</template>
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
</itf-button>
|
|
28
28
|
</div>
|
|
29
29
|
|
|
30
|
-
<div class="itf-select-popover__scroll text-start">
|
|
30
|
+
<div v-if="isVisible" class="itf-select-popover__scroll text-start">
|
|
31
31
|
<template v-if="!search && recentIcons.length">
|
|
32
32
|
<div class="py-1 text-muted">Recent</div>
|
|
33
33
|
<div class="d-flex align-items-start flex-wrap flex-grow-0">
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
}
|
|
71
71
|
</style>
|
|
72
72
|
<script>
|
|
73
|
-
import { Vue, Component, Prop, PropSync } from 'vue-property-decorator';
|
|
73
|
+
import { Vue, Component, Model, Prop, PropSync } from 'vue-property-decorator';
|
|
74
74
|
import itfPopover from './Popover';
|
|
75
75
|
import itfIcon from '../icon/Icon';
|
|
76
76
|
import iconsList from '../icon/icons';
|
|
@@ -81,7 +81,7 @@ import tooltip from '../../directives/tooltip';
|
|
|
81
81
|
const RECENT_ITEMS_KEY = 'itfSelectPopoverRecent';
|
|
82
82
|
|
|
83
83
|
export default @Component({
|
|
84
|
-
name: '
|
|
84
|
+
name: 'itfIconPopover',
|
|
85
85
|
directives: {
|
|
86
86
|
tooltip
|
|
87
87
|
},
|
|
@@ -92,13 +92,14 @@ export default @Component({
|
|
|
92
92
|
itfTextField
|
|
93
93
|
},
|
|
94
94
|
})
|
|
95
|
-
class
|
|
96
|
-
@
|
|
95
|
+
class itfIconPopover extends Vue {
|
|
96
|
+
@Model('input') value;
|
|
97
97
|
@Prop({ type: String, default: '' }) title;
|
|
98
98
|
@Prop({ type: Boolean, default: '' }) removable;
|
|
99
99
|
@Prop({ type: String, default: 'bottom', validator: (value) => ['bottom', 'left', 'right', 'top'].includes(value) }) placement;
|
|
100
100
|
@Prop({ type: String, default: 'click', validator: (value) => ['click', 'focus', 'hover', 'manual'].includes(value) }) trigger;
|
|
101
101
|
|
|
102
|
+
isVisible = false;
|
|
102
103
|
search = '';
|
|
103
104
|
recentIcons = [];
|
|
104
105
|
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
</slot>
|
|
83
83
|
|
|
84
84
|
<slot name="spinner" v-bind="scope.spinner">
|
|
85
|
-
<div v-show="mutableLoading" class="itf-spinner itf-select__loader">
|
|
85
|
+
<div v-show="mutableLoading" class="itf-spinner itf-select__loader">{{ $t('components.select.loading') }}</div>
|
|
86
86
|
</slot>
|
|
87
87
|
</div>
|
|
88
88
|
</div>
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
</li>
|
|
128
128
|
<li v-if="filteredOptions.length === 0" class="vs__no-options text-muted">
|
|
129
129
|
<slot name="no-options" v-bind="scope.noOptions"
|
|
130
|
-
><span v-if="!mutableLoading">
|
|
130
|
+
><span v-if="!mutableLoading">{{ $t('components.select.noOptions') }}</span></slot
|
|
131
131
|
>
|
|
132
132
|
</li>
|
|
133
133
|
<slot name="list-footer" v-bind="scope.listFooter" />
|
|
@@ -33,6 +33,9 @@ export function parseHours (str, { hoursInDay } = { hoursInDay: 8 }) {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export function formatDate (date, inputFormat = 'yyyy-MM-dd', toFormat) {
|
|
36
|
+
if (!date) {
|
|
37
|
+
return date;
|
|
38
|
+
}
|
|
36
39
|
return DateTime.fromFormat(date, inputFormat).toFormat(toFormat || ITFSettings.defaultDisplayDateFormat);
|
|
37
40
|
}
|
|
38
41
|
|
package/src/locales/en.js
CHANGED
|
@@ -57,6 +57,10 @@ module.exports = {
|
|
|
57
57
|
close: 'Close',
|
|
58
58
|
wholeYear: 'Whole year',
|
|
59
59
|
|
|
60
|
+
select: {
|
|
61
|
+
loading: 'Loading...',
|
|
62
|
+
noOptions: 'Select an option or create a new one',
|
|
63
|
+
},
|
|
60
64
|
modal: {
|
|
61
65
|
delete: 'Delete',
|
|
62
66
|
cancel: 'Cancel',
|
|
@@ -83,5 +87,7 @@ module.exports = {
|
|
|
83
87
|
areYouSureYouWantToDeleteThisField: 'Are you sure you want to delete this property?',
|
|
84
88
|
customizePage: 'Customize page',
|
|
85
89
|
type: 'Type',
|
|
90
|
+
options: 'Options',
|
|
91
|
+
addOption: 'Add an option',
|
|
86
92
|
}
|
|
87
93
|
};
|