@itfin/components 2.0.46 → 2.0.48

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": "2.0.46",
3
+ "version": "2.0.48",
4
4
  "author": "Vitalii Savchuk <esvit666@gmail.com>",
5
5
  "scripts": {
6
6
  "serve": "vue-cli-service serve",
@@ -20,17 +20,25 @@
20
20
  </div>
21
21
  </div>
22
22
  <div class="facets-list">
23
- <div v-for="(val, g) of groupedList" :key="g" :style="{paddingLeft: `${((val.level || 0) + 0.5) * 1}rem`}"
24
- class="dropdown-item" :class="{'active': val.isSelected, 'active': isGroupSelected(val)}" @click="onFilterClick(val)">
25
- <span class="facet-name text-dark d-flex align-items-center">
26
- <itf-checkbox ungrouped :value="val.isSelected" class="m-0" />
27
- <itf-icon new v-if="val.isGroup && groupIcon" :name="groupIcon" class="me-1" />
28
- <div class="w-100 text-truncate">{{ val.label }} <span v-if="val.description" class="small"><br/>{{ val.description }}</span></div>
29
- </span>
30
- <span v-if="val.count" class="facet-stat">
31
- {{ val.count }}
32
- <span class="facet-bar"><span :style="{'--bar-width': `${getPercent(val)}%`}" class="facet-bar-progress" /></span>
33
- </span>
23
+ <div v-for="(group, g) of groupedList">
24
+ <div v-if="group.group" class="dropdown-item ps-1 d-flex align-items-center"
25
+ :class="{'active': isGroupSelected(group.items)}" @click="groupSelected(!isGroupSelected(group.items), group.items)">
26
+ <span class="facet-name text-dark d-flex align-items-center">
27
+ <itf-checkbox ungrouped :value="isGroupSelected(group.items)" @input="groupSelected($event, group.items)" class="m-0" />
28
+ <div class="w-100 text-truncate">{{ group.group }}</div>
29
+ </span>
30
+ </div>
31
+
32
+ <div v-for="(val, n) of group.items" :key="n" class="dropdown-item ps-1" :class="{'active': val.isSelected, 'ps-4': group.group}" @click="onFilterClick(val)">
33
+ <span class="facet-name text-dark d-flex align-items-center">
34
+ <itf-checkbox ungrouped :value="val.isSelected" class="m-0" />
35
+ <div class="w-100 text-truncate">{{ val.label }} <span v-if="val.description" class="small"><br/>{{ val.description }}</span></div>
36
+ </span>
37
+ <span v-if="val.count" class="facet-stat">
38
+ {{ val.count }}
39
+ <span class="facet-bar"><span :style="{'--bar-width': `${getPercent(val)}%`}" class="facet-bar-progress" /></span>
40
+ </span>
41
+ </div>
34
42
  </div>
35
43
  </div>
36
44
 
@@ -115,10 +123,8 @@
115
123
  <script>
116
124
  import uniq from 'lodash/uniq';
117
125
  import sortBy from 'lodash/sortBy';
118
- import groupBy from 'lodash/groupBy';
119
126
  import { Vue, Prop, Model, Component } from 'vue-property-decorator';
120
127
  import itfTextField from '../text-field/TextField.vue';
121
- import itfIcon from '../icon/Icon';
122
128
  import itfButton from '../button/Button';
123
129
  import itfCheckbox from '../checkbox/Checkbox.vue';
124
130
 
@@ -126,7 +132,6 @@ export default @Component({
126
132
  name: 'FilterFacetsList',
127
133
  components: {
128
134
  itfCheckbox,
129
- itfIcon,
130
135
  itfButton,
131
136
  itfTextField
132
137
  }
@@ -136,7 +141,6 @@ class FilterFacetsList extends Vue {
136
141
  @Prop() items;
137
142
  @Prop() item;
138
143
  @Prop() total;
139
- @Prop() options;
140
144
  @Prop({ type: Number, default: 5 }) limit;
141
145
  @Prop(Boolean) multiple;
142
146
  @Prop(Boolean) showAll;
@@ -150,44 +154,6 @@ class FilterFacetsList extends Vue {
150
154
  this.showMore = !this.showMore;
151
155
  }
152
156
 
153
- get groupIcon() {
154
- return this.options?.groupIcon;
155
- }
156
-
157
- get flatList() {
158
- return treeToFlat(this.items || []);
159
-
160
- function getIdsDeep(items, mapFunc) {
161
- return (items ?? []).reduce((acc, item) => {
162
- const id = mapFunc(item);
163
- if (id) {
164
- acc.push(id);
165
- }
166
- if (item.items && item.items.length) {
167
- acc.push(...getIdsDeep(item.items, mapFunc));
168
- }
169
- return acc;
170
- }, []);
171
- }
172
- function treeToFlat(items, level = 0) {
173
- return (items ?? []).reduce((acc, item) => {
174
- acc.push({
175
- ...item,
176
- level,
177
- ids: [item.isGroup ? `g:${item.value}` : item.value].concat(
178
- getIdsDeep(item.items, (item) => item.isGroup ? `g:${item.value}` : item.value)
179
- )
180
- .filter(Boolean),
181
- });
182
- if (item.items && item.items.length) {
183
- // acc.push({ ...item, group: item.label, level });
184
- acc.push(...treeToFlat(item.items, level + 1));
185
- }
186
- return acc;
187
- }, []);
188
- }
189
- }
190
-
191
157
  get hasMore() {
192
158
  if (this.showAll) {
193
159
  return false;
@@ -196,27 +162,26 @@ class FilterFacetsList extends Vue {
196
162
  }
197
163
 
198
164
  get hasGroups() {
199
- const groups = uniq(this.flatList && this.flatList.map(item => item.group).filter(Boolean));
165
+ const groups = uniq(this.items && this.items.map(item => item.group).filter(Boolean));
200
166
  return groups.length > 1
201
167
  }
202
168
 
203
- isGroupSelected(val) {
204
- return (val.ids ?? []).every(id => this.value.includes(`${id}`));
169
+ isGroupSelected(items) {
170
+ return items.every(item => item.isSelected);
205
171
  }
206
172
 
207
- groupSelected(value, ids) {
173
+ groupSelected(value, items) {
208
174
  let newVal = this.value ? [...Array.isArray(this.value) ? this.value : [this.value]] : [];
209
- console.info(value, ids);
210
175
  if (value) {
211
- ids.forEach((id) => {
212
- const itemValue = `${id}`;
176
+ items.forEach((item) => {
177
+ const itemValue = `${item.value}`;
213
178
  if (!newVal.includes(itemValue)) {
214
179
  newVal.push(itemValue);
215
180
  }
216
181
  });
217
182
  } else {
218
- ids.forEach((id) => {
219
- const itemValue = `${id}`;
183
+ items.forEach((item) => {
184
+ const itemValue = `${item.value}`;
220
185
  newVal = newVal.filter(val => val !== itemValue);
221
186
  });
222
187
  }
@@ -224,7 +189,7 @@ class FilterFacetsList extends Vue {
224
189
  }
225
190
 
226
191
  get visibleList() {
227
- let list = this.flatList.map(val => {
192
+ let list = this.items.map(val => {
228
193
  const isSelected = this.multiple
229
194
  ? Array.isArray(this.value) && this.value.map(String).includes(`${val.value}`)
230
195
  : `${this.value}` === `${val.value}`;
@@ -237,15 +202,22 @@ class FilterFacetsList extends Vue {
237
202
  if (this.isShowSelected) {
238
203
  return list.filter((val) => val.isSelected);
239
204
  }
240
- return this.hasGroups ? sortBy(list, (item) => item.group || item.label) : list;//sortBy(list, (item) => this.hasGroups ? item.group || item.label : item.label);
205
+ return sortBy(list, (item) => this.hasGroups ? item.group || item.label : item.label);
241
206
  }
242
207
 
243
208
  get groupedList() {
244
209
  if (!this.hasGroups) {
245
- return this.mappedValues;
210
+ return [{ items: this.mappedValues }];
246
211
  }
247
- const groups = groupBy(this.mappedValues, (item) => item.group || '');
248
- return Object.entries(groups).reduce((acc, [group, items]) => [...acc, { label: group, isGroup: true }, ...(items.map(item => ({ ...item, level: 1 })))], []);
212
+ const groups = {};
213
+ this.mappedValues.forEach((item) => {
214
+ const group = item.group || '';
215
+ if (!groups[group]) {
216
+ groups[group] = [];
217
+ }
218
+ groups[group].push(item);
219
+ });
220
+ return Object.entries(groups).map(([group, items]) => ({ group, items }));
249
221
  }
250
222
 
251
223
  get mappedValues() {
@@ -261,10 +233,6 @@ class FilterFacetsList extends Vue {
261
233
  }
262
234
 
263
235
  onFilterClick(val) {
264
- if (val.isGroup) {
265
- this.groupSelected(!val.isSelected, val.ids);
266
- return;
267
- }
268
236
  const value = `${val.value}`;
269
237
  if (!this.multiple) {
270
238
  return this.$emit('input', `${this.value}` === value ? null : value);
@@ -0,0 +1,305 @@
1
+ <template>
2
+ <div>
3
+ <div class="px-1">
4
+ <div class="facets-filter-header">
5
+ <div v-if="title">{{title}}</div>
6
+ </div>
7
+
8
+ <itf-text-field small v-model="query" class="mb-2" :placeholder="$t('components.filter.search')" clearable />
9
+
10
+ <div class="d-flex justify-content-between small mb-2">
11
+ <a v-if="isSelectedAll" href="" @click.stop.prevent="onSelectAll(false)">{{$t('components.filter.deselectAll')}}</a>
12
+ <a v-else href="" @click.stop.prevent="onSelectAll(true)">{{$t('components.filter.selectAll')}}</a>
13
+
14
+ <span class="text-muted" v-if="!isHasSelected" href="" @click.stop.prevent="onShowSelected(true)">{{$t('components.filter.showSelected')}}</span>
15
+ <a v-else-if="!isShowSelected" href="" @click.stop.prevent="onShowSelected(true)">{{$t('components.filter.showSelected')}}</a>
16
+ <a v-else href="" @click.stop.prevent="onShowSelected(false)">{{$t('components.filter.showAll')}}</a>
17
+ </div>
18
+ <div v-if="!mappedValues.length">
19
+ <div class="text-muted text-center py-4">{{ $t('components.filter.noResults') }}</div>
20
+ </div>
21
+ </div>
22
+ <div class="facets-list">
23
+ <div v-for="(val, g) of groupedList" :key="g" :style="{paddingLeft: `${((val.level || 0) + 0.5) * 1}rem`}"
24
+ class="dropdown-item" :class="{'active': val.isSelected, 'active': isGroupSelected(val)}" @click="onFilterClick(val)">
25
+ <span class="facet-name text-dark d-flex align-items-center">
26
+ <itf-checkbox ungrouped :value="val.isSelected" class="m-0" />
27
+ <itf-icon new v-if="val.isGroup && groupIcon" :name="groupIcon" class="me-1" />
28
+ <div class="w-100 text-truncate">{{ val.label }} <span v-if="val.description" class="small"><br/>{{ val.description }}</span></div>
29
+ </span>
30
+ <span v-if="val.count" class="facet-stat">
31
+ {{ val.count }}
32
+ <span class="facet-bar"><span :style="{'--bar-width': `${getPercent(val)}%`}" class="facet-bar-progress" /></span>
33
+ </span>
34
+ </div>
35
+ </div>
36
+
37
+ <itf-button default class="mt-1" v-if="hasMore" small block @click="toggleMore">
38
+ <span v-if="showMore">{{ $t('components.filter.hideMore', { count: visibleList.length }) }}</span>
39
+ <span v-else>{{ $t('components.filter.showMore', { count: visibleList.length }) }}</span>
40
+ </itf-button>
41
+ </div>
42
+ </template>
43
+ <style lang="scss" scoped>
44
+ .facets-filter-header {
45
+ border-bottom: 1px solid var(--bs-border-color-translucent);
46
+ color: #A5A5A9;
47
+ padding: 0 0.75rem .5rem;
48
+ margin: 0 -.75rem .75rem;
49
+ }
50
+ .facets-list {
51
+ max-height: 50vh;
52
+ overflow: auto;
53
+ }
54
+ .dropdown-item {
55
+ --bs-dropdown-link-active-bg: rgba(var(--bs-primary-rgb), .25);
56
+
57
+ cursor: pointer;
58
+ display: inline-flex;
59
+ -webkit-box-align: center;
60
+ align-items: center;
61
+ -webkit-box-pack: justify;
62
+ justify-content: space-between;
63
+ position: relative;
64
+ box-sizing: border-box;
65
+ min-height: 1.75rem;
66
+ width: 100%;
67
+ font-size: 0.875rem;
68
+ line-height: 1.25rem;
69
+ font-weight: 400;
70
+ white-space: normal;
71
+ user-select: none;
72
+ border-radius: 0.25rem;
73
+ border-width: 1px;
74
+ border-style: solid;
75
+ border-color: transparent;
76
+ border-image: initial;
77
+ transition: none 0s ease 0s;
78
+ margin: 1px 0;
79
+ &.active {
80
+ .facet-bar-progress {
81
+ background-color: var(--bs-blue);
82
+ }
83
+ }
84
+ .facet-name {
85
+ min-width: 0;
86
+ text-align: left;
87
+ line-height: 100%;
88
+ white-space: nowrap;
89
+
90
+ .itf-checkbox {
91
+ min-height: 1.25rem;
92
+ }
93
+ }
94
+ .facet-stat {
95
+ display: flex;
96
+ -webkit-box-align: center;
97
+ align-items: center;
98
+ margin-left: 0.25rem;
99
+ }
100
+ .facet-bar {
101
+ display: inline-block;
102
+ margin-left: 0.5rem;
103
+ width: 60px;
104
+ }
105
+ .facet-bar-progress {
106
+ display: block;
107
+ width: var(--bar-width);
108
+ min-width: 5px;
109
+ height: 10px;
110
+ background-color: rgba(var(--bs-blue-rgb), 50%);
111
+ transition: width 0.3s ease 0s;
112
+ }
113
+ }
114
+ </style>
115
+ <script>
116
+ import uniq from 'lodash/uniq';
117
+ import sortBy from 'lodash/sortBy';
118
+ import groupBy from 'lodash/groupBy';
119
+ import { Vue, Prop, Model, Component } from 'vue-property-decorator';
120
+ import itfTextField from '../text-field/TextField.vue';
121
+ import itfIcon from '../icon/Icon';
122
+ import itfButton from '../button/Button';
123
+ import itfCheckbox from '../checkbox/Checkbox.vue';
124
+
125
+ export default @Component({
126
+ name: 'FilterFacetsList',
127
+ components: {
128
+ itfCheckbox,
129
+ itfIcon,
130
+ itfButton,
131
+ itfTextField
132
+ }
133
+ })
134
+ class FilterFacetsList extends Vue {
135
+ @Model('input') value;
136
+ @Prop() items;
137
+ @Prop() item;
138
+ @Prop() total;
139
+ @Prop() options;
140
+ @Prop({ type: Number, default: 5 }) limit;
141
+ @Prop(Boolean) multiple;
142
+ @Prop(Boolean) showAll;
143
+ @Prop(String) title;
144
+
145
+ query = '';
146
+ showMore = false;
147
+ isShowSelected = false;
148
+
149
+ toggleMore() {
150
+ this.showMore = !this.showMore;
151
+ }
152
+
153
+ get groupIcon() {
154
+ return this.options?.groupIcon;
155
+ }
156
+
157
+ get flatList() {
158
+ return treeToFlat(this.items || []);
159
+
160
+ function getIdsDeep(items, mapFunc) {
161
+ return (items ?? []).reduce((acc, item) => {
162
+ const id = mapFunc(item);
163
+ if (id) {
164
+ acc.push(id);
165
+ }
166
+ if (item.items && item.items.length) {
167
+ acc.push(...getIdsDeep(item.items, mapFunc));
168
+ }
169
+ return acc;
170
+ }, []);
171
+ }
172
+ function treeToFlat(items, level = 0) {
173
+ return (items ?? []).reduce((acc, item) => {
174
+ acc.push({
175
+ ...item,
176
+ level,
177
+ ids: [item.isGroup ? `g:${item.value}` : item.value].concat(
178
+ getIdsDeep(item.items, (item) => item.isGroup ? `g:${item.value}` : item.value)
179
+ )
180
+ .filter(Boolean),
181
+ });
182
+ if (item.items && item.items.length) {
183
+ // acc.push({ ...item, group: item.label, level });
184
+ acc.push(...treeToFlat(item.items, level + 1));
185
+ }
186
+ return acc;
187
+ }, []);
188
+ }
189
+ }
190
+
191
+ get hasMore() {
192
+ if (this.showAll) {
193
+ return false;
194
+ }
195
+ return this.visibleList.length > this.limit;
196
+ }
197
+
198
+ get hasGroups() {
199
+ const groups = uniq(this.flatList && this.flatList.map(item => item.group).filter(Boolean));
200
+ return groups.length > 1
201
+ }
202
+
203
+ isGroupSelected(val) {
204
+ return (val.ids ?? []).every(id => this.value.includes(`${id}`));
205
+ }
206
+
207
+ groupSelected(value, ids) {
208
+ let newVal = this.value ? [...Array.isArray(this.value) ? this.value : [this.value]] : [];
209
+ console.info(value, ids);
210
+ if (value) {
211
+ ids.forEach((id) => {
212
+ const itemValue = `${id}`;
213
+ if (!newVal.includes(itemValue)) {
214
+ newVal.push(itemValue);
215
+ }
216
+ });
217
+ } else {
218
+ ids.forEach((id) => {
219
+ const itemValue = `${id}`;
220
+ newVal = newVal.filter(val => val !== itemValue);
221
+ });
222
+ }
223
+ this.$emit('input', this.multiple ? newVal : (newVal.length > 0 ? newVal[0] : null));
224
+ }
225
+
226
+ get visibleList() {
227
+ let list = this.flatList.map(val => {
228
+ const isSelected = this.multiple
229
+ ? Array.isArray(this.value) && this.value.map(String).includes(`${val.value}`)
230
+ : `${this.value}` === `${val.value}`;
231
+
232
+ return { ...val, isSelected };
233
+ });
234
+ if (this.query) {
235
+ list = list.filter((val) => val.label.toLowerCase().includes(this.query.toLowerCase()));
236
+ }
237
+ if (this.isShowSelected) {
238
+ return list.filter((val) => val.isSelected);
239
+ }
240
+ return this.hasGroups ? sortBy(list, (item) => item.group || item.label) : list;//sortBy(list, (item) => this.hasGroups ? item.group || item.label : item.label);
241
+ }
242
+
243
+ get groupedList() {
244
+ if (!this.hasGroups) {
245
+ return this.mappedValues;
246
+ }
247
+ const groups = groupBy(this.mappedValues, (item) => item.group || '');
248
+ return Object.entries(groups).reduce((acc, [group, items]) => [...acc, { label: group, isGroup: true }, ...(items.map(item => ({ ...item, level: 1 })))], []);
249
+ }
250
+
251
+ get mappedValues() {
252
+ const list = this.visibleList;
253
+ if (!this.showMore && !this.showAll) {
254
+ return list.slice(0, this.limit);
255
+ }
256
+ return list;
257
+ }
258
+
259
+ getPercent(item) {
260
+ return this.total ? Math.round((item.count / this.total) * 100) : 0;
261
+ }
262
+
263
+ onFilterClick(val) {
264
+ if (val.isGroup) {
265
+ this.groupSelected(!val.isSelected, val.ids);
266
+ return;
267
+ }
268
+ const value = `${val.value}`;
269
+ if (!this.multiple) {
270
+ return this.$emit('input', `${this.value}` === value ? null : value);
271
+ }
272
+ const newVal = [...Array.isArray(this.value) ? [...this.value] : []].map((s) => s.toString());
273
+ if (newVal.includes(value)) {
274
+ newVal.splice(newVal.indexOf(value), 1);
275
+ } else {
276
+ newVal.push(value);
277
+ }
278
+ this.$emit('input', newVal);
279
+ }
280
+
281
+ get isSelectedAll() {
282
+ return this.mappedValues.every(o => o.isSelected);
283
+ }
284
+
285
+ onSelectAll(isSelect = false) {
286
+ if (isSelect) {
287
+ this.$emit('input', this.visibleList.map((val) => `${val.value}`));
288
+ } else {
289
+ this.$emit('input', []);
290
+ }
291
+ this.isShowSelected = false;
292
+ }
293
+
294
+ get isHasSelected() {
295
+ return this.value && this.value.length > 0 && !this.query; // тільки коли не пошук
296
+ }
297
+
298
+ onShowSelected(isShow = false) {
299
+ if (!this.value.length && isShow) { // не показувати, якщо нічого не вибрано
300
+ return;
301
+ }
302
+ this.isShowSelected = isShow;
303
+ }
304
+ }
305
+ </script>