@itfin/components 2.0.16 → 2.0.18
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/components/datepicker/DateGranularityPicker.vue +2 -1
- package/src/components/datepicker/PeriodPicker.vue +39 -182
- package/src/components/datepicker/PeriodPickerInline.vue +187 -0
- package/src/components/datepicker/index.stories.js +4 -0
- package/src/components/filter/FilterBadge.vue +22 -3
- package/src/components/filter/FilterFacetsList.vue +14 -5
- package/src/components/filter/FilterPanel.vue +155 -41
- package/src/components/icon/components/nomi-export.vue +4 -0
- package/src/components/icon/components/nomi-lock.vue +4 -0
- package/src/components/icon/icons.js +292 -292
|
@@ -1,41 +1,56 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="itf-filter-panel d-flex flex-column gap-3 align-items-start">
|
|
3
|
-
<div v-if="
|
|
3
|
+
<div v-if="!filtersOnly" class="d-flex gap-2 justify-content-between w-100">
|
|
4
4
|
<slot name="search">
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
<div>
|
|
6
|
+
<itf-text-field
|
|
7
|
+
v-if="search"
|
|
8
|
+
style="width: 300px"
|
|
9
|
+
small
|
|
10
|
+
:placeholder="searchPlaceholder"
|
|
11
|
+
prepend-icon="search"
|
|
12
|
+
:delay-input="250"
|
|
13
|
+
clearable
|
|
14
|
+
:value="filterValueQuery"
|
|
15
|
+
@input="(e) => onFilterChange({ type: 'text', name: 'query' }, { value: e })"
|
|
16
|
+
/>
|
|
17
|
+
</div>
|
|
15
18
|
</slot>
|
|
16
19
|
<div class="d-flex gap-2">
|
|
17
|
-
|
|
20
|
+
<!--itf-button v-if="showFilter" default icon class="position-relative" @click="toggleFilters" :class="{'active': showFilters}">
|
|
18
21
|
<itf-icon new name="filter" />
|
|
19
22
|
<span v-if="activeFiltersCount" class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-primary">
|
|
20
23
|
{{activeFiltersCount}}
|
|
21
24
|
<span class="visually-hidden">active filters</span>
|
|
22
25
|
</span>
|
|
23
|
-
</itf-button
|
|
26
|
+
</itf-button-->
|
|
24
27
|
<slot name="after-filter-btn"></slot>
|
|
25
28
|
</div>
|
|
26
29
|
</div>
|
|
27
|
-
<div
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
<div class="d-flex align-items-start justify-content-between w-100">
|
|
31
|
+
<div v-show="showFilters && showFilter" class="gap-2 filters-row" ref="container" :class="{'expanded': isFilterExpanded}">
|
|
32
|
+
<filter-badge
|
|
33
|
+
v-for="(facet, n) in visibleFilters"
|
|
34
|
+
:key="n"
|
|
35
|
+
class="itf-filter-panel__badge"
|
|
36
|
+
:ref="'item-' + n"
|
|
37
|
+
v-model="filter[facet.name]"
|
|
38
|
+
:is-default="filter[facet.name].isDefault"
|
|
39
|
+
:text="filter[facet.name].label"
|
|
40
|
+
:type="facet.type"
|
|
41
|
+
:icon="facet.icon"
|
|
42
|
+
:options="facet.options"
|
|
43
|
+
:class="{ hidden: !visibleItems.has(n) && !isFilterExpanded }"
|
|
44
|
+
@change="onFilterChange(facet, $event)"
|
|
45
|
+
/>
|
|
46
|
+
</div>
|
|
47
|
+
<div v-if="showFilters && showFilter && (visibleItems.size < visibleFilters.length || (isFilterExpanded && visibleItems.size === visibleFilters.length))">
|
|
48
|
+
<itf-button icon default small class="itf-filter-panel__filters" @click="toggleExpandFilter">
|
|
49
|
+
<itf-icon v-if="isFilterExpanded" name="minus" />
|
|
50
|
+
<itf-icon v-else name="plus" />
|
|
51
|
+
</itf-button>
|
|
52
|
+
</div>
|
|
53
|
+
<slot name="after-filters"></slot>
|
|
39
54
|
</div>
|
|
40
55
|
<div v-if="loading">
|
|
41
56
|
<span class="itf-spinner"></span>
|
|
@@ -45,6 +60,18 @@
|
|
|
45
60
|
</template>
|
|
46
61
|
<style lang="scss">
|
|
47
62
|
.itf-filter-panel {
|
|
63
|
+
&__badge {
|
|
64
|
+
transition: opacity 0.3s ease-in-out;
|
|
65
|
+
|
|
66
|
+
&.hidden {
|
|
67
|
+
opacity: 0;
|
|
68
|
+
pointer-events: none;
|
|
69
|
+
visibility: hidden;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
&__filters {
|
|
73
|
+
outline: 1px solid var(--filter-badge__default-border-color);
|
|
74
|
+
}
|
|
48
75
|
.itf-text-field:not(.is-valid):not(.is-invalid) .itf-icon {
|
|
49
76
|
color: #8E97A5;
|
|
50
77
|
}
|
|
@@ -63,18 +90,28 @@
|
|
|
63
90
|
}
|
|
64
91
|
|
|
65
92
|
.filters-row {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
93
|
+
display: flex;
|
|
94
|
+
overflow: hidden;
|
|
95
|
+
width: 100%;
|
|
96
|
+
padding: 2px;
|
|
97
|
+
margin: -2px;
|
|
98
|
+
flex-wrap: nowrap;
|
|
99
|
+
|
|
100
|
+
&.expanded {
|
|
101
|
+
flex-wrap: wrap;
|
|
102
|
+
|
|
103
|
+
.itf-filter-panel__badge.hidden {
|
|
104
|
+
opacity: 1;
|
|
105
|
+
visibility: visible;
|
|
106
|
+
pointer-events: all;
|
|
107
|
+
}
|
|
71
108
|
}
|
|
72
109
|
}
|
|
73
110
|
}
|
|
74
111
|
</style>
|
|
75
112
|
<script>
|
|
76
113
|
import { DateTime } from 'luxon';
|
|
77
|
-
import { Vue, Model, Prop, Component } from 'vue-property-decorator';
|
|
114
|
+
import { Vue, Watch, Model, Prop, Component } from 'vue-property-decorator';
|
|
78
115
|
import tooltip from '../../directives/tooltip';
|
|
79
116
|
import itfIcon from '../icon/Icon';
|
|
80
117
|
import itfButton from '../button/Button';
|
|
@@ -103,18 +140,75 @@ class FilterPanel extends Vue {
|
|
|
103
140
|
@Prop() panel;
|
|
104
141
|
@Prop(String) stateName;
|
|
105
142
|
@Prop(Boolean) search;
|
|
143
|
+
@Prop({ type: Boolean, default: true }) showFilter;
|
|
144
|
+
@Prop({ type: Boolean, default: false }) filtersOnly;
|
|
106
145
|
@Prop(Boolean) mini;
|
|
107
146
|
@Prop({ type: String, default: function() { return this.$t('components.filter.search'); } }) searchPlaceholder;
|
|
108
147
|
|
|
109
148
|
filter = {};
|
|
110
|
-
filterValue =
|
|
149
|
+
filterValue = null;
|
|
111
150
|
filters = [];
|
|
112
151
|
loading = false;
|
|
113
152
|
showFilters = true;
|
|
153
|
+
isFilterExpanded = false;
|
|
154
|
+
observer = null;
|
|
155
|
+
|
|
156
|
+
periodFilters = ['period', 'timeframe'];
|
|
157
|
+
visibleItems = new Set();
|
|
158
|
+
|
|
159
|
+
toggleExpandFilter() {
|
|
160
|
+
this.isFilterExpanded = !this.isFilterExpanded;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
beforeDestroy() {
|
|
164
|
+
if (this.observer) {
|
|
165
|
+
this.observer.disconnect();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
initObserver() {
|
|
170
|
+
if (this.observer) {
|
|
171
|
+
this.observer.disconnect();
|
|
172
|
+
}
|
|
173
|
+
if (!this.$refs.container) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
this.observer = new IntersectionObserver(
|
|
177
|
+
(entries) => {
|
|
178
|
+
entries.forEach(entry => {
|
|
179
|
+
const index = parseInt(entry.target.dataset.index);
|
|
180
|
+
const filter = this.filters[index];
|
|
181
|
+
const value = this.filter[filter.name];
|
|
182
|
+
if (entry.isIntersecting) {
|
|
183
|
+
this.visibleItems.add(index); // Додаємо, якщо елемент у полі зору
|
|
184
|
+
} else {
|
|
185
|
+
this.visibleItems.delete(index); // Видаляємо, якщо вийшов за межі
|
|
186
|
+
}
|
|
187
|
+
this.$forceUpdate(); // Оновлюємо Vue, бо Set не є реактивним
|
|
188
|
+
});
|
|
189
|
+
},
|
|
190
|
+
{ root: this.$refs.container, threshold: 1.0 }
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// Спостерігаємо за кожним елементом
|
|
194
|
+
this.$nextTick(() => {
|
|
195
|
+
for (const index in this.visibleFilters) {
|
|
196
|
+
const item = this.$refs[`item-${index}`][0];
|
|
197
|
+
if (item) {
|
|
198
|
+
item.$el.dataset.index = index; // Зберігаємо індекс у dataset
|
|
199
|
+
this.observer.observe(item.$el);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
get filterValueQuery() {
|
|
206
|
+
return this.filterValue?.query ?? '';
|
|
207
|
+
}
|
|
114
208
|
|
|
115
209
|
get visibleFilters() {
|
|
116
210
|
if (this.mini) {
|
|
117
|
-
return sortBy(this.filters, (f) => this.filter[f.name].isDefault).filter(f => !f.options?.hidden)
|
|
211
|
+
return sortBy(this.filters, (f) => this.filter[f.name].isDefault).filter(f => !f.options?.hidden);
|
|
118
212
|
}
|
|
119
213
|
return this.filters.filter(f => !f.options?.hidden);
|
|
120
214
|
}
|
|
@@ -127,6 +221,12 @@ class FilterPanel extends Vue {
|
|
|
127
221
|
return `filter-panel-${this.stateName}-filters`;
|
|
128
222
|
}
|
|
129
223
|
|
|
224
|
+
@Watch('staticFilters', { deep: true })
|
|
225
|
+
onStaticFiltersUpdate() {
|
|
226
|
+
this.filters = this.staticFilters ?? [];
|
|
227
|
+
this.loadFiltersValue();
|
|
228
|
+
}
|
|
229
|
+
|
|
130
230
|
async mounted() {
|
|
131
231
|
if (this.stateName) {
|
|
132
232
|
const item = localStorage.getItem(this.localstorageKey);
|
|
@@ -137,8 +237,10 @@ class FilterPanel extends Vue {
|
|
|
137
237
|
if (this.endpoint) {
|
|
138
238
|
this.loading = true;
|
|
139
239
|
await this.$try(async () => {
|
|
140
|
-
const
|
|
240
|
+
const payload = this.panel ? this.panel.getPayload() : {};
|
|
241
|
+
const {filters, tableSchema} = await this.$axios.$get(this.endpoint, { params: payload });
|
|
141
242
|
this.filters = filters;
|
|
243
|
+
this.$emit('set-table-schema', tableSchema);
|
|
142
244
|
this.loadFiltersValue();
|
|
143
245
|
});
|
|
144
246
|
this.loading = false;
|
|
@@ -163,7 +265,7 @@ class FilterPanel extends Vue {
|
|
|
163
265
|
const filterValue = {};
|
|
164
266
|
if (this.filters) {
|
|
165
267
|
for (const item of this.filters) {
|
|
166
|
-
if (item.type
|
|
268
|
+
if (this.periodFilters.includes(item.type)) {
|
|
167
269
|
filter[item.name] = payload.from ? this.formatValue(item, { value: [payload.from, payload.to] }) : { isDefault: true, ...item.options.defaultValue };
|
|
168
270
|
filterValue.from = payload.from;
|
|
169
271
|
filterValue.to = payload.to;
|
|
@@ -176,13 +278,25 @@ class FilterPanel extends Vue {
|
|
|
176
278
|
if (this.search) {
|
|
177
279
|
filterValue.query = payload.query;
|
|
178
280
|
}
|
|
179
|
-
const prevFilter = JSON.stringify(this.
|
|
180
|
-
const newFilter = JSON.stringify(
|
|
181
|
-
if (prevFilter !== newFilter) {
|
|
281
|
+
const prevFilter = JSON.stringify(this.getVisibleFilters(this.filterValue));//.concat(JSON.stringify(this.filterValue));
|
|
282
|
+
const newFilter = JSON.stringify(this.getVisibleFilters(filterValue));//.concat(JSON.stringify(filterValue));
|
|
283
|
+
if (prevFilter !== newFilter || !this.filterValue) {
|
|
182
284
|
this.filter = filter;
|
|
183
285
|
this.filterValue = filterValue;
|
|
184
286
|
this.$emit('input', this.filterValue);
|
|
287
|
+
this.initObserver();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
getVisibleFilters(filter) {
|
|
292
|
+
const result = [];
|
|
293
|
+
const facets = Object.values(this.filter);
|
|
294
|
+
for (const facet of facets) {
|
|
295
|
+
if (!facet.isDefault && !facet.hidden) {
|
|
296
|
+
result.push(filter[facet.name]);
|
|
297
|
+
}
|
|
185
298
|
}
|
|
299
|
+
return result.filter(Boolean);
|
|
186
300
|
}
|
|
187
301
|
|
|
188
302
|
setFilter(field, value) {
|
|
@@ -195,7 +309,7 @@ class FilterPanel extends Vue {
|
|
|
195
309
|
|
|
196
310
|
onFilterChange(facet, value) {
|
|
197
311
|
this.filter[facet.name] = this.formatValue(facet, value);
|
|
198
|
-
if (facet.type
|
|
312
|
+
if (this.periodFilters.includes(facet.type)) {
|
|
199
313
|
this.filterValue.from = this.filter[facet.name].isDefault ? undefined : value.value[0];
|
|
200
314
|
this.filterValue.to = this.filter[facet.name].isDefault ? undefined : value.value[1];
|
|
201
315
|
} else {
|
|
@@ -226,7 +340,7 @@ class FilterPanel extends Vue {
|
|
|
226
340
|
}
|
|
227
341
|
|
|
228
342
|
formatValue(facet, value) {
|
|
229
|
-
if (facet.type
|
|
343
|
+
if (this.periodFilters.includes(facet.type)) {
|
|
230
344
|
if (value.value) {
|
|
231
345
|
let from = DateTime.fromFormat(value.value[0], 'yyyy-MM-dd');
|
|
232
346
|
let to = DateTime.fromFormat(value.value[1], 'yyyy-MM-dd');
|
|
@@ -285,7 +399,7 @@ class FilterPanel extends Vue {
|
|
|
285
399
|
value.isDefault = facet.options.defaultValue ? JSON.stringify(value.value) === JSON.stringify(facet.options.defaultValue.value) : false;
|
|
286
400
|
value.label = item ? item.label : facet.options.defaultValue.label;
|
|
287
401
|
} else if (facet.type === 'text') {
|
|
288
|
-
value.value = value.value.length ? value.value : undefined;
|
|
402
|
+
value.value = value.value.length ? value.value : (facet.options?.defaultValue ?? undefined);
|
|
289
403
|
value.isDefault = !value.value;
|
|
290
404
|
}
|
|
291
405
|
value.hidden = facet.options?.hidden ?? false;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<template><svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.4 5V8C11.4 8.88366 12.1163 9.6 13 9.6H16V15.7372L15.8527 15.5899C15.2279 14.9651 14.2148 14.9651 13.59 15.5899C12.9651 16.2148 12.9651 17.2278 13.59 17.8527L14.7373 19H7C6.44772 19 6 18.5523 6 18V6C6 5.44772 6.44772 5 7 5H11.4ZM12.6 5.6V8C12.6 8.22091 12.7791 8.4 13 8.4H15.4L12.6 5.6ZM18.1456 20.1456C18.0881 20.2031 18.0218 20.2465 17.951 20.2758C17.8803 20.3051 17.8027 20.3213 17.7213 20.3213C17.6401 20.323 17.4416 20.2901 17.2971 20.1456L14.2971 17.1456C14.0627 16.9113 14.0627 16.5314 14.2971 16.297C14.5314 16.0627 14.9113 16.0627 15.1456 16.297L17.1213 18.2728V13.7213C17.1213 13.3899 17.39 13.1213 17.7213 13.1213C18.0527 13.1213 18.3213 13.3899 18.3213 13.7213V18.2728L20.2971 16.297C20.5314 16.0627 20.9113 16.0627 21.1456 16.297C21.3799 16.5314 21.3799 16.9113 21.1456 17.1456L18.1456 20.1456Z" fill="currentColor"/>
|
|
3
|
+
</svg>
|
|
4
|
+
</template>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<template><svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 4.3999C13.9882 4.3999 15.6 6.01168 15.6 7.9999V10.0489C16.033 10.1018 16.3329 10.2114 16.5607 10.4392C17 10.8786 17 11.5857 17 12.9999V14.9999C17 16.4141 17 17.1212 16.5607 17.5606C16.1213 17.9999 15.4142 17.9999 14 17.9999H10C8.58579 17.9999 7.87868 17.9999 7.43934 17.5606C7 17.1212 7 16.4141 7 14.9999V12.9999C7 11.5857 7 10.8786 7.43934 10.4392C7.66715 10.2114 7.96695 10.1018 8.4 10.0489L8.4 7.9999C8.4 6.01168 10.0118 4.3999 12 4.3999ZM14.4 7.9999V10.0003C14.2733 9.99991 14.1401 9.99991 14 9.99991H10C9.85987 9.99991 9.72668 9.99991 9.6 10.0003L9.6 7.9999C9.6 6.67442 10.6745 5.5999 12 5.5999C13.3255 5.5999 14.4 6.67442 14.4 7.9999ZM12.6 12.9999C12.6 12.6685 12.3314 12.3999 12 12.3999C11.6686 12.3999 11.4 12.6685 11.4 12.9999V14.9999C11.4 15.3313 11.6686 15.5999 12 15.5999C12.3314 15.5999 12.6 15.3313 12.6 14.9999V12.9999Z" fill="currentColor"/>
|
|
3
|
+
</svg>
|
|
4
|
+
</template>
|