@itfin/components 1.3.86 → 1.3.87
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/_button.scss +0 -1
- package/src/assets/scss/components/_range.scss +276 -0
- package/src/assets/scss/main.scss +1 -0
- package/src/components/filter/FilterAmountRange.vue +93 -0
- package/src/components/filter/FilterBadge.vue +182 -0
- package/src/components/filter/FilterFacetsList.vue +206 -0
- package/src/components/filter/FilterPanel.vue +184 -0
- package/src/components/panels/PanelList.vue +36 -11
- package/src/components/range/Range.vue +1261 -0
- package/src/components/range/utils.js +74 -0
- package/src/components/text-field/TextField.vue +2 -1
- package/src/locales/en.js +10 -0
- package/src/locales/uk.js +10 -0
- package/src/components/filter/ConditionGroup.vue +0 -196
- package/src/components/filter/FacetFilter.vue +0 -138
- package/src/components/filter/FacetItem.vue +0 -162
- package/src/components/filter/FilterInput.vue +0 -64
- package/src/components/filter/FilterItem.vue +0 -423
- package/src/components/filter/constants.js +0 -20
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Unsharp text [#166](https://github.com/NightCatSama/vue-slider-component/issues/166)
|
|
2
|
+
export const roundToDPR = (function () {
|
|
3
|
+
const r = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1
|
|
4
|
+
return value => Math.round(value * r) / r
|
|
5
|
+
})()
|
|
6
|
+
|
|
7
|
+
export const isMobile = (() => {
|
|
8
|
+
const userAgentInfo = navigator.userAgent.toLowerCase()
|
|
9
|
+
const agents = ["Android", "iPhone",
|
|
10
|
+
"SymbianOS", "Windows Phone",
|
|
11
|
+
"iPad", "iPod"]
|
|
12
|
+
let flag = false
|
|
13
|
+
for (let v = 0; v < agents.length; v++) {
|
|
14
|
+
if (userAgentInfo.indexOf(agents[v].toLowerCase()) > 0) {
|
|
15
|
+
flag = true
|
|
16
|
+
break
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return flag
|
|
20
|
+
})()
|
|
21
|
+
|
|
22
|
+
export function isArray(input) {
|
|
23
|
+
if (Array.prototype.isArray) {
|
|
24
|
+
return Array.isArray(input)
|
|
25
|
+
}
|
|
26
|
+
return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function isDiff(a, b) {
|
|
30
|
+
if (Object.prototype.toString.call(a) !== Object.prototype.toString.call(b)) {
|
|
31
|
+
return true
|
|
32
|
+
} else if (isArray(a) && a.length === b.length) {
|
|
33
|
+
return a.some((v, i) => v !== b[i])
|
|
34
|
+
}
|
|
35
|
+
return a !== b
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let elementStyle = document.createElement('div').style
|
|
39
|
+
let vendor = (() => {
|
|
40
|
+
let transformNames = {
|
|
41
|
+
webkit: 'webkitTransform',
|
|
42
|
+
Moz: 'MozTransform',
|
|
43
|
+
O: 'OTransform',
|
|
44
|
+
ms: 'msTransform',
|
|
45
|
+
standard: 'transform'
|
|
46
|
+
}
|
|
47
|
+
for (let key in transformNames) {
|
|
48
|
+
if (elementStyle[transformNames[key]] !== undefined) {
|
|
49
|
+
return key
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return false
|
|
53
|
+
})()
|
|
54
|
+
|
|
55
|
+
export function prefixStyle(style) {
|
|
56
|
+
if (vendor === false) {
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
59
|
+
if (vendor === 'standard') {
|
|
60
|
+
if (style === 'transitionEnd') {
|
|
61
|
+
return 'transitionend'
|
|
62
|
+
}
|
|
63
|
+
return style
|
|
64
|
+
}
|
|
65
|
+
return vendor + style.charAt(0).toUpperCase() + style.substr(1)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function addEvent(el, type, fn, capture) {
|
|
69
|
+
el.addEventListener(type, fn, {passive: false, capture: !!capture})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function removeEvent(el, type, fn, capture) {
|
|
73
|
+
el.removeEventListener(type, fn, {passive: false, capture: !!capture})
|
|
74
|
+
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
ref="input"
|
|
12
12
|
autocomplete="off"
|
|
13
13
|
:placeholder="placeholder"
|
|
14
|
-
:class="{ 'is-invalid': isInvalid(), 'is-valid': isSuccess() }"
|
|
14
|
+
:class="{ 'is-invalid': isInvalid(), 'is-valid': isSuccess(), 'form-control-sm': small }"
|
|
15
15
|
class="itf-text-field__input form-control"
|
|
16
16
|
:type="type"
|
|
17
17
|
:value="value"
|
|
@@ -67,6 +67,7 @@ class itfTextField extends Vue {
|
|
|
67
67
|
@Prop(Boolean) clearable;
|
|
68
68
|
@Prop(Boolean) disabled;
|
|
69
69
|
@Prop(Boolean) readonly;
|
|
70
|
+
@Prop(Boolean) small;
|
|
70
71
|
@Prop({ type: String, default: 'text' }) type;
|
|
71
72
|
@Prop({ type: [Number, String], default: 0 }) delayInput;
|
|
72
73
|
|
package/src/locales/en.js
CHANGED
|
@@ -125,5 +125,15 @@ module.exports = {
|
|
|
125
125
|
itemsPerPage: 'Items per page',
|
|
126
126
|
previous: 'Previous',
|
|
127
127
|
next: 'Next'
|
|
128
|
+
},
|
|
129
|
+
filter: {
|
|
130
|
+
search: 'Search',
|
|
131
|
+
deselectAll: 'Deselect all',
|
|
132
|
+
selectAll: 'Select all',
|
|
133
|
+
showSelected: 'Show selected',
|
|
134
|
+
showAll: 'Show all',
|
|
135
|
+
noResults: 'No results',
|
|
136
|
+
showMore: 'show all ({count})',
|
|
137
|
+
hideMore: 'hide',
|
|
128
138
|
}
|
|
129
139
|
};
|
package/src/locales/uk.js
CHANGED
|
@@ -125,5 +125,15 @@ module.exports = {
|
|
|
125
125
|
calculateCountNotEmpty: 'Кількість не порожніх',
|
|
126
126
|
calculatePercentEmpty: 'Відсоток порожніх',
|
|
127
127
|
calculatePercentNotEmpty: 'Відсоток не порожніх',
|
|
128
|
+
},
|
|
129
|
+
filter: {
|
|
130
|
+
search: 'Пошук',
|
|
131
|
+
deselectAll: 'Скасувати вибір',
|
|
132
|
+
selectAll: 'Вибрати все',
|
|
133
|
+
showSelected: 'Показати вибрані',
|
|
134
|
+
showAll: 'Показати все',
|
|
135
|
+
noResults: 'Немає результатів',
|
|
136
|
+
showMore: 'показати всі ({count})',
|
|
137
|
+
hideMore: 'сховати',
|
|
128
138
|
}
|
|
129
139
|
};
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
|
|
3
|
-
<div class="b-condition-group-input w-100 pt-1 pb-2 px-2">
|
|
4
|
-
<div class="d-flex align-items-start">
|
|
5
|
-
<div v-if="value.conditions.length > 1" class="condition-group-type">
|
|
6
|
-
<itf-select
|
|
7
|
-
:value="value.link"
|
|
8
|
-
:reduce="(item) => item.value"
|
|
9
|
-
:options="[{ label: 'AND', value: 'and' }, { label: 'OR', value: 'or' }]"
|
|
10
|
-
@input="onLinkUpdate"
|
|
11
|
-
/>
|
|
12
|
-
</div>
|
|
13
|
-
<div class="flex-1 pt-1">
|
|
14
|
-
<div v-for="(filter, n) in value.conditions" :key="n" :class="{'tree-node': value.conditions.length > 1}">
|
|
15
|
-
<div class="d-flex align-items-start">
|
|
16
|
-
<div class="flex-grow-1 py-2 me-2">
|
|
17
|
-
<itf-condition-group
|
|
18
|
-
v-if="filter.link"
|
|
19
|
-
:level="level + 1"
|
|
20
|
-
class="rounded"
|
|
21
|
-
:value="filter"
|
|
22
|
-
@input="onFilterUpdate(n, $event)"
|
|
23
|
-
:class="{'bg-group1': level === 0, 'bg-group2': level === 1}"
|
|
24
|
-
/>
|
|
25
|
-
<filter-input
|
|
26
|
-
v-else
|
|
27
|
-
:value="filter"
|
|
28
|
-
@input="onFilterUpdate(n, $event)"
|
|
29
|
-
/>
|
|
30
|
-
</div>
|
|
31
|
-
<div class="pt-2 mt-1">
|
|
32
|
-
<itf-button small icon @click="removeField(n)">
|
|
33
|
-
<itf-icon name="trash" />
|
|
34
|
-
</itf-button>
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
37
|
-
</div>
|
|
38
|
-
</div>
|
|
39
|
-
</div>
|
|
40
|
-
|
|
41
|
-
<itf-button small secondary @click="addCondition">
|
|
42
|
-
<itf-icon name="plus" />
|
|
43
|
-
Add condition
|
|
44
|
-
</itf-button>
|
|
45
|
-
<itf-button v-if="level < maxDepth" small secondary @click="addConditionGroup">
|
|
46
|
-
<itf-icon name="plus" />
|
|
47
|
-
Add group
|
|
48
|
-
</itf-button>
|
|
49
|
-
</div>
|
|
50
|
-
|
|
51
|
-
</template>
|
|
52
|
-
<style>
|
|
53
|
-
.b-condition-group-input {
|
|
54
|
-
--b-filterGroupBorder: rgba(0,0,0,.05);
|
|
55
|
-
--b-filterGroup1Bg: #f9fafb;
|
|
56
|
-
--b-filterGroup2Bg: #f1f2f4;
|
|
57
|
-
|
|
58
|
-
[data-theme="dark"] & {
|
|
59
|
-
--b-filterGroup1Bg: #303436;
|
|
60
|
-
--b-filterGroup2Bg: #3a3e41;
|
|
61
|
-
--b-filterGroupBorder: hsla(0,0%,100%,.03);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
.bg-group1 {
|
|
65
|
-
background: var(--b-filterGroup1Bg);
|
|
66
|
-
border: 1px solid var(--b-filterGroupBorder);
|
|
67
|
-
}
|
|
68
|
-
.bg-group2 {
|
|
69
|
-
background: var(--b-filterGroup2Bg);
|
|
70
|
-
border: 1px solid var(--b-filterGroupBorder);
|
|
71
|
-
}
|
|
72
|
-
.condition-group-type {
|
|
73
|
-
width: 85px;
|
|
74
|
-
z-index: 10;
|
|
75
|
-
margin-right: 1rem;
|
|
76
|
-
margin-top: .5rem;
|
|
77
|
-
margin-bottom: .5rem;
|
|
78
|
-
position: relative;
|
|
79
|
-
}
|
|
80
|
-
.flex-1 {
|
|
81
|
-
flex: 1 1 0%;
|
|
82
|
-
}
|
|
83
|
-
.tree-node {
|
|
84
|
-
--h-line-position: 1.5rem;
|
|
85
|
-
--line-color: #D2D5DF;
|
|
86
|
-
--line-width: 1px;
|
|
87
|
-
--line-radius: 10px;
|
|
88
|
-
--line-connection-position: -3.5rem;
|
|
89
|
-
--node-space: .5rem;
|
|
90
|
-
margin-bottom: var(--node-space);
|
|
91
|
-
position: relative;
|
|
92
|
-
|
|
93
|
-
&:first-child:before {
|
|
94
|
-
border-left: solid var(--line-width) var(--line-color);
|
|
95
|
-
top: var(--h-line-position);
|
|
96
|
-
height: calc(100% - var(--h-line-position) + var(--node-space));
|
|
97
|
-
width: var(--line-radius);
|
|
98
|
-
}
|
|
99
|
-
&:before {
|
|
100
|
-
display: inline-block;
|
|
101
|
-
border-left: solid var(--line-width) var(--line-color);
|
|
102
|
-
content: "";
|
|
103
|
-
height: calc(100% + var(--node-space));
|
|
104
|
-
position: absolute;
|
|
105
|
-
left: var(--line-connection-position);
|
|
106
|
-
}
|
|
107
|
-
&:first-child:after {
|
|
108
|
-
height: var(--line-radius);
|
|
109
|
-
}
|
|
110
|
-
&:after {
|
|
111
|
-
display: inline-block;
|
|
112
|
-
border-top: solid var(--line-width) var(--line-color);
|
|
113
|
-
content: "";
|
|
114
|
-
width: calc(var(--line-connection-position)* -1);
|
|
115
|
-
position: absolute;
|
|
116
|
-
top: var(--h-line-position);
|
|
117
|
-
left: var(--line-connection-position);
|
|
118
|
-
}
|
|
119
|
-
&:last-child:before {
|
|
120
|
-
border-color: var(--line-color);
|
|
121
|
-
top: 0%;
|
|
122
|
-
height: var(--h-line-position);
|
|
123
|
-
width: var(--line-radius);
|
|
124
|
-
border-bottom-left-radius: var(--line-radius);
|
|
125
|
-
}
|
|
126
|
-
&:last-child:after {
|
|
127
|
-
border-top: none;
|
|
128
|
-
border-bottom: solid var(--line-width) var(--line-color);
|
|
129
|
-
border-left: solid var(--line-width) var(--line-color);
|
|
130
|
-
height: var(--line-radius);
|
|
131
|
-
border-bottom-left-radius: var(--line-radius);
|
|
132
|
-
top: calc(var(--h-line-position) - var(--line-radius));
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
</style>
|
|
136
|
-
<script>
|
|
137
|
-
import { Component, Prop, Model, Vue, Watch } from 'vue-property-decorator';
|
|
138
|
-
import itfButton from '@itfin/components/src/components/button/Button.vue';
|
|
139
|
-
import itfTextField from '@itfin/components/src/components/text-field/TextField.vue';
|
|
140
|
-
import itfDropdown from '@itfin/components/src/components/dropdown/Dropdown.vue';
|
|
141
|
-
import itfSelect from '@itfin/components/src/components/select/Select.vue';
|
|
142
|
-
import itfIcon from '@itfin/components/src/components/icon/Icon.vue';
|
|
143
|
-
import FilterInput from '~/components/Panels/FilterInput.vue';
|
|
144
|
-
|
|
145
|
-
export default @Component({
|
|
146
|
-
name: 'itfConditionGroup',
|
|
147
|
-
components: {
|
|
148
|
-
itfTextField,
|
|
149
|
-
itfSelect,
|
|
150
|
-
itfDropdown,
|
|
151
|
-
itfButton,
|
|
152
|
-
itfIcon,
|
|
153
|
-
FilterInput
|
|
154
|
-
}
|
|
155
|
-
})
|
|
156
|
-
class ConditionGroup extends Vue {
|
|
157
|
-
@Model('input', { default: () => ({ link: 'and', conditions: [] }) }) value;
|
|
158
|
-
@Prop({ default: 0 }) level;
|
|
159
|
-
@Prop({ default: 1 }) maxDepth;
|
|
160
|
-
|
|
161
|
-
onLinkUpdate(link) {
|
|
162
|
-
this.$emit('input', { ...this.value, link });
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
onFilterUpdate(index, filter) {
|
|
166
|
-
this.applyValue((conditions) => {
|
|
167
|
-
conditions[index] = filter;
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
applyValue(func) {
|
|
172
|
-
const newValue = { ...this.value, conditions: [...this.value.conditions] };
|
|
173
|
-
func(newValue.conditions);
|
|
174
|
-
this.$emit('input', newValue);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
addCondition() {
|
|
178
|
-
this.applyValue((conditions) => {
|
|
179
|
-
conditions.push({ operator: 'is', values: [] });
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
addConditionGroup() {
|
|
184
|
-
this.applyValue((conditions) => {
|
|
185
|
-
conditions.push({ link: 'and', conditions: [] });
|
|
186
|
-
});
|
|
187
|
-
// this.filters.push({ type: 'group' });
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
removeField(index) {
|
|
191
|
-
this.applyValue((conditions) => {
|
|
192
|
-
conditions.splice(index, 1);
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
</script>
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<itf-form class="facets-panel" :class="{ horizontal }">
|
|
3
|
-
<div v-for="(item, n) of filters" :key="n" class="mb-3">
|
|
4
|
-
<template v-if="item.facet && ((facets[item.facet] && !horizontal) || horizontal)">
|
|
5
|
-
<div><strong>{{ item.label }}</strong></div>
|
|
6
|
-
<div class="facets-container">
|
|
7
|
-
<div v-if="horizontal" class="facets-search">
|
|
8
|
-
<input type="text" :placeholder="$t('search')" :value="query[item.facet]" @input="(e) => setQuery(e, item.facet)">
|
|
9
|
-
</div>
|
|
10
|
-
<div v-if="facets[item.facet]" class="facets-container__scroll">
|
|
11
|
-
<facet-item
|
|
12
|
-
:query="query[item.facet]"
|
|
13
|
-
:facet="facets[item.facet]"
|
|
14
|
-
:item="item"
|
|
15
|
-
:value="filter"
|
|
16
|
-
:show-all="horizontal"
|
|
17
|
-
@input="setFilterValue"
|
|
18
|
-
/>
|
|
19
|
-
</div>
|
|
20
|
-
</div>
|
|
21
|
-
</template>
|
|
22
|
-
<filter-item
|
|
23
|
-
v-else-if="!item.facet"
|
|
24
|
-
:type="item.type"
|
|
25
|
-
:filter="item"
|
|
26
|
-
:value="filter"
|
|
27
|
-
:prepend-icon="item.id && item.id.includes('custom_') ? null : 'search'"
|
|
28
|
-
:labels="labels"
|
|
29
|
-
:loading-labels="loadingLabels"
|
|
30
|
-
@input="setFilterValue"
|
|
31
|
-
/>
|
|
32
|
-
</div>
|
|
33
|
-
|
|
34
|
-
<div v-for="(facet, n) of customFields" :key="`cf_${n}`" class="mb-3">
|
|
35
|
-
<div><strong>{{ facet.Name }}</strong></div>
|
|
36
|
-
<div class="facets-container">
|
|
37
|
-
<facet-item
|
|
38
|
-
:facet="facet"
|
|
39
|
-
:item="{ id: facet.Id, title: facet.Name }"
|
|
40
|
-
:value="filter"
|
|
41
|
-
:show-all="horizontal"
|
|
42
|
-
@input="setFilterValue"
|
|
43
|
-
/>
|
|
44
|
-
</div>
|
|
45
|
-
</div>
|
|
46
|
-
</itf-form>
|
|
47
|
-
</template>
|
|
48
|
-
<style lang="scss" scoped>
|
|
49
|
-
.facets-panel {
|
|
50
|
-
&.horizontal {
|
|
51
|
-
display: grid;
|
|
52
|
-
grid-template-columns: repeat(7, clamp(150px, 13.75%, 15%));
|
|
53
|
-
grid-gap: .5rem;
|
|
54
|
-
|
|
55
|
-
.facets-container {
|
|
56
|
-
border: 1px solid var(--bs-border-color);
|
|
57
|
-
max-height: 100%;
|
|
58
|
-
min-height: 90%;
|
|
59
|
-
|
|
60
|
-
.facets-search {
|
|
61
|
-
border-bottom: 1px solid var(--bs-border-color);
|
|
62
|
-
|
|
63
|
-
input {
|
|
64
|
-
padding: .5rem;
|
|
65
|
-
border: 0;
|
|
66
|
-
width: 100%;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
&::v-deep .facet-bar {
|
|
70
|
-
display: none;
|
|
71
|
-
}
|
|
72
|
-
&__scroll {
|
|
73
|
-
max-height: 200px;
|
|
74
|
-
overflow: auto;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
</style>
|
|
80
|
-
<script>
|
|
81
|
-
import { Vue, Prop, Model, Component } from 'vue-property-decorator';
|
|
82
|
-
import itfButton from '@itfin/components/src/components/button/Button';
|
|
83
|
-
import itfForm from '@itfin/components/src/components/form/Form';
|
|
84
|
-
import FilterItem from './FilterItem.vue';
|
|
85
|
-
import FacetItem from './FacetItem.vue';
|
|
86
|
-
|
|
87
|
-
export default @Component({
|
|
88
|
-
name: 'FacetFilters',
|
|
89
|
-
components: {
|
|
90
|
-
itfForm,
|
|
91
|
-
itfButton,
|
|
92
|
-
FilterItem,
|
|
93
|
-
FacetItem
|
|
94
|
-
}
|
|
95
|
-
})
|
|
96
|
-
class FacetFilters extends Vue {
|
|
97
|
-
@Model('input') value;
|
|
98
|
-
@Prop(String) type;
|
|
99
|
-
@Prop() filter;
|
|
100
|
-
@Prop() labels;
|
|
101
|
-
@Prop() loadingLabels;
|
|
102
|
-
@Prop() facets;
|
|
103
|
-
@Prop(Boolean) horizontal;
|
|
104
|
-
@Prop(Array) filters;
|
|
105
|
-
|
|
106
|
-
query = {};
|
|
107
|
-
|
|
108
|
-
setQuery(e, facet) {
|
|
109
|
-
this.query = {
|
|
110
|
-
...this.query,
|
|
111
|
-
[facet]: e.target.value
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
get customFields() {
|
|
116
|
-
if (!this.facets) {
|
|
117
|
-
return [];
|
|
118
|
-
}
|
|
119
|
-
const fields = [];
|
|
120
|
-
for (const key in this.facets) {
|
|
121
|
-
const item = this.facets[key];
|
|
122
|
-
if (item.IsCustomField) {
|
|
123
|
-
fields.push({
|
|
124
|
-
...item,
|
|
125
|
-
Id: key
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return fields;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
setFilterValue (newFilter = {}) {
|
|
133
|
-
const filter = { ...newFilter };
|
|
134
|
-
|
|
135
|
-
this.$emit('update:filter', filter);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
</script>
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div>
|
|
3
|
-
<button v-for="(val, n) of mappedValues" :key="n" class="facet-item" :class="{'active': val.isSelected}" @click="onFilterClick(val)">
|
|
4
|
-
<span class="facet-name text-dark">{{ val.Text }}</span>
|
|
5
|
-
<span class="facet-stat text-muted">
|
|
6
|
-
{{ val.Count }}
|
|
7
|
-
<span class="facet-bar"><span :style="{'--bar-width': `${getPercent(val)}%`}" class="facet-bar-progress" /></span>
|
|
8
|
-
</span>
|
|
9
|
-
</button>
|
|
10
|
-
|
|
11
|
-
<itf-button v-if="hasMore" small block @click="toggleMore">
|
|
12
|
-
<span v-if="showMore">{{ $t('common.hideMore', { count: facet.Values.length }) }}</span>
|
|
13
|
-
<span v-else>{{ $t('common.showMore', { count: facet.Values.length }) }}</span>
|
|
14
|
-
</itf-button>
|
|
15
|
-
</div>
|
|
16
|
-
</template>
|
|
17
|
-
<style lang="scss" scoped>
|
|
18
|
-
.facet-item {
|
|
19
|
-
background: transparent;
|
|
20
|
-
cursor: pointer;
|
|
21
|
-
display: inline-flex;
|
|
22
|
-
-webkit-box-align: center;
|
|
23
|
-
align-items: center;
|
|
24
|
-
-webkit-box-pack: justify;
|
|
25
|
-
justify-content: space-between;
|
|
26
|
-
position: relative;
|
|
27
|
-
box-sizing: border-box;
|
|
28
|
-
height: 1.75rem;
|
|
29
|
-
width: 100%;
|
|
30
|
-
padding-left: 0.25rem;
|
|
31
|
-
padding-right: 0.25rem;
|
|
32
|
-
font-size: 0.875rem;
|
|
33
|
-
line-height: 1.25rem;
|
|
34
|
-
font-weight: 400;
|
|
35
|
-
white-space: normal;
|
|
36
|
-
user-select: none;
|
|
37
|
-
border-radius: 0.25rem;
|
|
38
|
-
border-width: 1px;
|
|
39
|
-
border-style: solid;
|
|
40
|
-
border-color: transparent;
|
|
41
|
-
border-image: initial;
|
|
42
|
-
transition: none 0s ease 0s;
|
|
43
|
-
&.active {
|
|
44
|
-
background-color: rgba(var(--bs-primary-rgb), 25%);
|
|
45
|
-
}
|
|
46
|
-
&:hover, &:focus, &.active {
|
|
47
|
-
border-color: var(--bs-primary);
|
|
48
|
-
}
|
|
49
|
-
.facet-name {
|
|
50
|
-
text-align: left;
|
|
51
|
-
white-space: nowrap;
|
|
52
|
-
overflow: hidden;
|
|
53
|
-
text-overflow: ellipsis;
|
|
54
|
-
}
|
|
55
|
-
.facet-stat {
|
|
56
|
-
display: flex;
|
|
57
|
-
-webkit-box-align: center;
|
|
58
|
-
align-items: center;
|
|
59
|
-
margin-left: 0.25rem;
|
|
60
|
-
}
|
|
61
|
-
.facet-bar {
|
|
62
|
-
display: inline-block;
|
|
63
|
-
margin-left: 0.5rem;
|
|
64
|
-
width: 60px;
|
|
65
|
-
}
|
|
66
|
-
.facet-bar-progress {
|
|
67
|
-
display: block;
|
|
68
|
-
width: var(--bar-width);
|
|
69
|
-
min-width: 5px;
|
|
70
|
-
height: 10px;
|
|
71
|
-
background-color: rgb(197, 205, 223);
|
|
72
|
-
transition: width 0.3s ease 0s;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
</style>
|
|
76
|
-
<script>
|
|
77
|
-
import { Vue, Prop, Model, Component } from 'vue-property-decorator';
|
|
78
|
-
import itfButton from '@itfin/components/src/components/button/Button';
|
|
79
|
-
import { DateTime } from 'luxon';
|
|
80
|
-
|
|
81
|
-
export default @Component({
|
|
82
|
-
name: 'FacetItem',
|
|
83
|
-
components: {
|
|
84
|
-
itfButton
|
|
85
|
-
}
|
|
86
|
-
})
|
|
87
|
-
class FacetItem extends Vue {
|
|
88
|
-
@Model('input') value;
|
|
89
|
-
@Prop() facet;
|
|
90
|
-
@Prop() item;
|
|
91
|
-
@Prop() query;
|
|
92
|
-
@Prop(Boolean) showAll;
|
|
93
|
-
|
|
94
|
-
showMore = false;
|
|
95
|
-
|
|
96
|
-
toggleMore() {
|
|
97
|
-
this.showMore = !this.showMore;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
get hasMore() {
|
|
101
|
-
if (this.showAll) {
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
|
-
return this.facet.Values.length > 5;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
get mappedValues() {
|
|
108
|
-
const { id, items = [], multiple } = this.item || {};
|
|
109
|
-
let list = this.facet.Values.map(val => {
|
|
110
|
-
const item = items.find(i => `${i.value}` === `${val.Value}`);
|
|
111
|
-
const isSelected = multiple
|
|
112
|
-
? Array.isArray(this.value[id]) && this.value[id].map(String).includes(`${val.Value}`)
|
|
113
|
-
: `${this.value[id]}` === `${val.Value}`;
|
|
114
|
-
|
|
115
|
-
if (item) {
|
|
116
|
-
// якщо знайдено item, ЗАТРЕ Text в val і замінить його на item.title
|
|
117
|
-
return { ...val, Text: item.title, isSelected };
|
|
118
|
-
}
|
|
119
|
-
return { ...val, isSelected };
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
list = list.map(val => val.Text ? { ...val, Text: this.getFormattedItemTitle(val.Text) } : val);
|
|
123
|
-
|
|
124
|
-
if (this.query) {
|
|
125
|
-
list = list.filter((val) => val.Text.toLowerCase().includes(this.query.toLowerCase()));
|
|
126
|
-
}
|
|
127
|
-
if (!this.showMore && !this.showAll) {
|
|
128
|
-
return list.slice(0, 5);
|
|
129
|
-
}
|
|
130
|
-
return list;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
getFormattedItemTitle(title) {
|
|
134
|
-
return this.isValueDateInStandartFormat(title) ? DateTime.fromFormat(title, 'yyyy-MM-dd', { zone: 'utc' }).toFormat('MM/dd/yyyy') : title;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
isValueDateInStandartFormat(value) {
|
|
138
|
-
return typeof value === 'string' ? DateTime.fromFormat(value, 'yyyy-MM-dd', { zone: 'utc' }).isValid : false;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
getPercent(item) {
|
|
142
|
-
return this.facet.Total ? Math.round((item.Count / this.facet.Total) * 100) : 0;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
onFilterClick(val) {
|
|
146
|
-
const newVal = { ...this.value };
|
|
147
|
-
if (this.item.multiple) {
|
|
148
|
-
newVal[this.item.id] = [...Array.isArray(newVal[this.item.id]) ? newVal[this.item.id] : []].map((s) => s.toString());
|
|
149
|
-
if (newVal[this.item.id].includes(`${val.Value}`)) {
|
|
150
|
-
newVal[this.item.id].splice(newVal[this.item.id].indexOf(`${val.Value}`), 1);
|
|
151
|
-
} else {
|
|
152
|
-
newVal[this.item.id].push(`${val.Value}`);
|
|
153
|
-
}
|
|
154
|
-
} else if (`${newVal[this.item.id]}` === `${val.Value}`) {
|
|
155
|
-
newVal[this.item.id] = null;
|
|
156
|
-
} else {
|
|
157
|
-
newVal[this.item.id] = val.Value;
|
|
158
|
-
}
|
|
159
|
-
this.$emit('input', newVal);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
</script>
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
|
|
3
|
-
<div class="d-flex">
|
|
4
|
-
<div class="w-25">
|
|
5
|
-
<itf-select :options="fields" />
|
|
6
|
-
</div>
|
|
7
|
-
<div class="w-25 px-1">
|
|
8
|
-
<itf-select
|
|
9
|
-
:reduce="(item) => item.id"
|
|
10
|
-
:get-option-label="(item) => item.title"
|
|
11
|
-
:options="conditions"
|
|
12
|
-
>
|
|
13
|
-
<template #selected-option="{ option }">
|
|
14
|
-
<span class="sign-box"> {{option.title}}</span>
|
|
15
|
-
</template>
|
|
16
|
-
<template #option="{ option }">
|
|
17
|
-
{{option.title}}
|
|
18
|
-
</template>
|
|
19
|
-
</itf-select>
|
|
20
|
-
</div>
|
|
21
|
-
<div class="w-50">
|
|
22
|
-
<itf-text-field />
|
|
23
|
-
</div>
|
|
24
|
-
</div>
|
|
25
|
-
|
|
26
|
-
</template>
|
|
27
|
-
<style>
|
|
28
|
-
</style>
|
|
29
|
-
<script>
|
|
30
|
-
import { Component, Prop, Model, Vue, Watch } from 'vue-property-decorator';
|
|
31
|
-
import itfButton from '@itfin/components/src/components/button/Button.vue';
|
|
32
|
-
import itfTextField from '@itfin/components/src/components/text-field/TextField.vue';
|
|
33
|
-
import itfSelect from '@itfin/components/src/components/select/Select.vue';
|
|
34
|
-
import itfDropdown from '@itfin/components/src/components/dropdown/Dropdown.vue';
|
|
35
|
-
import itfIcon from '@itfin/components/src/components/icon/Icon.vue';
|
|
36
|
-
|
|
37
|
-
const conditions = [
|
|
38
|
-
{ id: 'eq', title: 'equal', sign: '=' },
|
|
39
|
-
{ id: 'notEq', title: 'not equal', sign: '≠' },
|
|
40
|
-
{ id: 'contains', title: 'contains', sign: '∋' },
|
|
41
|
-
{ id: 'noContains', title: 'not contains', sign: '∌' },
|
|
42
|
-
{ id: 'startsWith', title: 'starts with', sign: 'A…' },
|
|
43
|
-
{ id: 'endsWith', title: 'ends with', sign: '…Z' },
|
|
44
|
-
// { id: 'exists', title: 'exists', sign: '∃' }
|
|
45
|
-
];
|
|
46
|
-
|
|
47
|
-
export default @Component({
|
|
48
|
-
name: 'itfFilterInput',
|
|
49
|
-
components: {
|
|
50
|
-
itfTextField,
|
|
51
|
-
itfSelect,
|
|
52
|
-
itfDropdown,
|
|
53
|
-
itfButton,
|
|
54
|
-
itfIcon
|
|
55
|
-
}
|
|
56
|
-
})
|
|
57
|
-
class FilterInput extends Vue {
|
|
58
|
-
@Prop() fields;
|
|
59
|
-
|
|
60
|
-
get conditions() {
|
|
61
|
-
return conditions;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
</script>
|