@itfin/components 1.4.10 → 1.4.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itfin/components",
3
- "version": "1.4.10",
3
+ "version": "1.4.12",
4
4
  "author": "Vitalii Savchuk <esvit666@gmail.com>",
5
5
  "scripts": {
6
6
  "serve": "vue-cli-service serve",
@@ -11,85 +11,27 @@
11
11
  :placeholder="placeholder"
12
12
  />
13
13
  <div style="display: none">
14
- <div ref="dropdown" class="itf-periodpicker__dropdown">
15
- <div class="row mb-3">
16
- <div class="col">
17
- <itf-button @click="setYear(prevYear)">
18
- <itf-icon name="chevron_left" />
19
- </itf-button>
20
- </div>
21
- <div class="col">
22
- <itf-button block @click="setYear(prevYear)">
23
- {{prevYear}}
24
- </itf-button>
25
- </div>
26
- <div class="col">
27
- <itf-button color="outline-primary" block>
28
- {{year}}
29
- </itf-button>
30
- </div>
31
- <div class="col">
32
- <itf-button block @click="setYear(nextYear)">
33
- {{nextYear}}
34
- </itf-button>
35
- </div>
36
- <div class="col text-end">
37
- <itf-button @click="setYear(nextYear)">
38
- <itf-icon name="chevron_right" />
39
- </itf-button>
40
- </div>
41
- </div>
42
-
43
- <itf-button block class="mb-3" :class="{'btn-whole': !isCurrentYear()}" :primary="isCurrentYear()" @click="onYearSelect(year)">
44
- {{ $t('components.wholeYear') }}
45
- </itf-button>
46
-
47
- <div class="itf-periodpicker__quarters">
48
- <div
49
- v-for="quarter of quarters"
50
- :key="quarter.Number"
51
- class="itf-periodpicker__quarter"
52
- :class="{ 'active': isOnlyQuarter(quarter.Number), 'active-inside': isCurrentQuarter(quarter.Number) }">
53
- <div class="px-3 pt-2" @click="onQuarterSelect([quarter.Months[0], quarter.Months[2]])">
54
- <small><b>QUARTER</b></small><br>
55
- <span class="quarter-number">{{ quarter.Number }}</span>
56
- </div>
57
-
58
- <div class="itf-periodpicker__months">
59
- <itf-button
60
- class="itf-periodpicker__month px-1"
61
- v-for="month of quarter.Months"
62
- :key="month"
63
- :primary="isCurrentMonth(month)"
64
- @click="onMonthSelect(month)"
65
- >
66
- {{ month | formatMonth }}
67
- </itf-button>
68
- </div>
69
- </div>
70
- </div>
71
- </div>
14
+ <itf-period-picker-inline
15
+ ref="dropdown"
16
+ class="itf-periodpicker__dropdown"
17
+ v-model="value"
18
+ :value-format="valueFormat"
19
+ @input="onInput"
20
+ />
72
21
  </div>
73
22
  </div>
74
23
  </template>
75
24
  <script>
76
25
  import { Vue, Component, Prop, Inject } from 'vue-property-decorator';
77
- import { DateTime } from 'luxon';
78
26
  import tippy from 'tippy.js';
79
- import itfIcon from '../icon/Icon';
80
- import itfButton from '../button/Button';
27
+ import itfPeriodPickerInline from "@/components/datepicker/PeriodPickerInline.vue";
28
+ import {DateTime} from "luxon";
81
29
  import ITFSettings from '../../ITFSettings';
82
30
 
83
31
  export default @Component({
84
32
  name: 'itfPeriodPicker',
85
33
  components: {
86
- itfIcon,
87
- itfButton
88
- },
89
- filters: {
90
- formatMonth(month) {
91
- return DateTime.local().set({ month }).toFormat('MMM');
92
- }
34
+ itfPeriodPickerInline
93
35
  }
94
36
  })
95
37
  class itfPeriodPicker extends Vue {
@@ -97,51 +39,12 @@ class itfPeriodPicker extends Vue {
97
39
 
98
40
  @Prop({ type: Array }) value;
99
41
  @Prop({ type: String, default: 'ISO' }) valueFormat;
100
- @Prop({ type: String }) displayFormat;
101
42
  @Prop({ type: String, default: 'bottom-start' }) placement;
102
- @Prop({ type: String, default: 'days', validator: (value) => ['days', 'months', 'years'].includes(value) }) startView;
103
- @Prop({ type: Boolean, default: false }) onlyCalendar;
104
43
  @Prop({ type: String, default: '' }) placeholder;
105
-
106
- year = null;
44
+ @Prop({ type: String }) displayFormat;
107
45
 
108
46
  focused = false;
109
47
 
110
- tooltip = null;
111
-
112
- get quarters() {
113
- return [
114
- { Name: `${this.$t('components.quarter')} 1`, Months: [1, 2, 3], Number: 1 },
115
- { Name: `${this.$t('components.quarter')} 2`, Months: [4, 5, 6], Number: 2 },
116
- { Name: `${this.$t('components.quarter')} 3`, Months: [7, 8, 9], Number: 3 },
117
- { Name: `${this.$t('components.quarter')} 4`, Months: [10, 11, 12], Number: 4 },
118
- ];
119
- }
120
-
121
- get dateFormat() {
122
- return this.displayFormat || ITFSettings.defaultDisplayDateFormat;
123
- }
124
-
125
- get nextYear() {
126
- return this.year + 1;
127
- }
128
-
129
- get prevYear() {
130
- return this.year - 1;
131
- }
132
-
133
- setYear(year) {
134
- this.year = year;
135
- }
136
-
137
- isInvalid() {
138
- return this.itemLabel && this.itemLabel.isHasError();
139
- }
140
-
141
- isSuccess() {
142
- return this.itemLabel && this.itemLabel.isHasSuccess();
143
- }
144
-
145
48
  mounted() {
146
49
  const context = this.$el.closest('.itf-append-context') || document.body;
147
50
  this.tooltip = tippy(this.$refs.input, {
@@ -149,30 +52,17 @@ class itfPeriodPicker extends Vue {
149
52
  interactiveDebounce: 75,
150
53
  animation: 'scale',
151
54
  arrow: true,
152
- content: this.$refs.dropdown,
55
+ content: this.$refs.dropdown.$el,
153
56
  allowHTML: true,
154
57
  trigger: 'click',
155
58
  interactive: true,
156
59
  placement: this.placement,
157
60
  appendTo: context,
158
61
  });
159
- this.year = (this.value && this.value[0]) ? this.valueAsLuxon[0].year : DateTime.local().year;
160
62
  }
161
63
 
162
- get valueAsLuxon() {
163
- if (!this.value || this.value.length < 2) {
164
- return null;
165
- }
166
- if (this.valueFormat === 'ISO') {
167
- return [
168
- DateTime.fromISO(this.value[0]),
169
- DateTime.fromISO(this.value[1]),
170
- ];
171
- }
172
- return [
173
- DateTime.fromFormat(this.value[0], this.valueFormat),
174
- DateTime.fromFormat(this.value[1], this.valueFormat),
175
- ];
64
+ get dateFormat() {
65
+ return this.displayFormat || ITFSettings.defaultDisplayDateFormat;
176
66
  }
177
67
 
178
68
  get displayText() {
@@ -183,12 +73,29 @@ class itfPeriodPicker extends Vue {
183
73
  if (!this.valueAsLuxon || this.valueAsLuxon.length < 2) {
184
74
  return [];
185
75
  }
76
+ console.info(this.valueAsLuxon);
186
77
  return [
187
78
  this.valueAsLuxon[0].toFormat(this.dateFormat),
188
79
  this.valueAsLuxon[1].toFormat(this.dateFormat)
189
80
  ];
190
81
  }
191
82
 
83
+ get valueAsLuxon() {
84
+ if (!this.value || this.value.length < 2) {
85
+ return null;
86
+ }
87
+ if (this.valueFormat === 'ISO') {
88
+ return [
89
+ DateTime.fromISO(this.value[0]),
90
+ DateTime.fromISO(this.value[1]),
91
+ ];
92
+ }
93
+ return [
94
+ DateTime.fromFormat(this.value[0], this.valueFormat),
95
+ DateTime.fromFormat(this.value[1], this.valueFormat),
96
+ ];
97
+ }
98
+
192
99
  onFocus() {
193
100
  this.focused = true;
194
101
  }
@@ -197,71 +104,21 @@ class itfPeriodPicker extends Vue {
197
104
  this.focused = false;
198
105
  }
199
106
 
200
- selectInlineDate(date) {
201
- this.$emit('input', date);
202
- this.tooltip.hide();
203
- }
204
-
205
- isCurrentMonth(month) {
206
- if (!this.valueAsLuxon) {
207
- return false;
208
- }
209
- return Number(this.valueAsLuxon[0].toFormat('M')) === month && Number(this.valueAsLuxon[1].toFormat('M')) === month;
210
- }
211
-
212
- isOnlyQuarter(quarter) {
213
- if (!this.valueAsLuxon) {
214
- return false;
215
- }
216
- return this.isCurrentQuarter(quarter) && this.valueAsLuxon[0].month !== this.valueAsLuxon[1].month;
107
+ isInvalid() {
108
+ return this.itemLabel && this.itemLabel.isHasError();
217
109
  }
218
110
 
219
- isCurrentQuarter(quarter) {
220
- if (!this.valueAsLuxon) {
221
- return false;
222
- }
223
- return this.valueAsLuxon[0].quarter === quarter && this.valueAsLuxon[1].quarter === quarter;
111
+ isSuccess() {
112
+ return this.itemLabel && this.itemLabel.isHasSuccess();
224
113
  }
225
114
 
226
- isCurrentYear() {
227
- if (!this.valueAsLuxon) {
228
- return false;
229
- }
230
- return this.valueAsLuxon[0].hasSame(this.valueAsLuxon[0].startOf('year'), 'day')
231
- && this.valueAsLuxon[1].hasSame(this.valueAsLuxon[0].endOf('year'), 'day');
115
+ selectInlineDate(date) {
116
+ this.$emit('input', date);
232
117
  }
233
118
 
234
- updateValue(start, end) {
235
- if (!start) {
236
- this.$emit('input', []);
237
- return;
238
- }
239
- this.$emit('input', [
240
- (this.valueFormat === 'ISO') ? start.toISO() : start.toFormat(this.valueFormat),
241
- (this.valueFormat === 'ISO') ? end.toISO() : end.toFormat(this.valueFormat),
242
- ]);
119
+ onInput(val) {
120
+ this.$emit('input', val);
243
121
  this.tooltip.hide();
244
122
  }
245
-
246
- onYearSelect(year) {
247
- this.updateValue(
248
- DateTime.local().set({ year }).startOf('year'),
249
- DateTime.local().set({ year }).endOf('year'),
250
- );
251
- }
252
-
253
- onMonthSelect(month) {
254
- this.updateValue(
255
- DateTime.local().set({ month, year: this.year }).startOf('month'),
256
- DateTime.local().set({ month, year: this.year }).endOf('month'),
257
- );
258
- }
259
-
260
- onQuarterSelect([startMonth, endMonth]) {
261
- this.updateValue(
262
- DateTime.local().set({ month: startMonth, year: this.year }).startOf('quarter'),
263
- DateTime.local().set({ month: endMonth, year: this.year }).endOf('quarter'),
264
- );
265
- }
266
123
  }
267
124
  </script>
@@ -0,0 +1,187 @@
1
+ <template>
2
+ <div>
3
+ <div class="row mb-3">
4
+ <div class="col">
5
+ <itf-button @click="setYear(prevYear)">
6
+ <itf-icon name="chevron_left" />
7
+ </itf-button>
8
+ </div>
9
+ <div class="col">
10
+ <itf-button block @click="setYear(prevYear)">
11
+ {{prevYear}}
12
+ </itf-button>
13
+ </div>
14
+ <div class="col">
15
+ <itf-button color="outline-primary" block>
16
+ {{year}}
17
+ </itf-button>
18
+ </div>
19
+ <div class="col">
20
+ <itf-button block @click="setYear(nextYear)">
21
+ {{nextYear}}
22
+ </itf-button>
23
+ </div>
24
+ <div class="col text-end">
25
+ <itf-button @click="setYear(nextYear)">
26
+ <itf-icon name="chevron_right" />
27
+ </itf-button>
28
+ </div>
29
+ </div>
30
+
31
+ <itf-button block class="mb-3" :class="{'btn-whole': !isCurrentYear()}" :primary="isCurrentYear()" @click="onYearSelect(year)">
32
+ {{ $t('components.wholeYear') }}
33
+ </itf-button>
34
+
35
+ <div class="itf-periodpicker__quarters">
36
+ <div
37
+ v-for="quarter of quarters"
38
+ :key="quarter.Number"
39
+ class="itf-periodpicker__quarter"
40
+ :class="{ 'active': isOnlyQuarter(quarter.Number), 'active-inside': isCurrentQuarter(quarter.Number) }">
41
+ <div class="px-3 pt-2" @click="onQuarterSelect([quarter.Months[0], quarter.Months[2]])">
42
+ <small><b>{{ $t('components.quarter') }}</b></small><br>
43
+ <span class="quarter-number">{{ quarter.Number }}</span>
44
+ </div>
45
+
46
+ <div class="itf-periodpicker__months">
47
+ <itf-button
48
+ class="itf-periodpicker__month px-1"
49
+ v-for="month of quarter.Months"
50
+ :key="month"
51
+ :primary="isCurrentMonth(month)"
52
+ @click="onMonthSelect(month)"
53
+ >
54
+ {{ month | formatMonth }}
55
+ </itf-button>
56
+ </div>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ </template>
61
+ <script>
62
+ import { Vue, Component, Prop } from 'vue-property-decorator';
63
+ import { DateTime } from 'luxon';
64
+ import itfIcon from '../icon/Icon';
65
+ import itfButton from '../button/Button';
66
+
67
+ export default @Component({
68
+ name: 'itfPeriodPickerInline',
69
+ components: {
70
+ itfIcon,
71
+ itfButton
72
+ },
73
+ filters: {
74
+ formatMonth(month) {
75
+ return DateTime.local().set({ month }).toFormat('MMM');
76
+ }
77
+ }
78
+ })
79
+ class itfPeriodPickerInline extends Vue {
80
+ @Prop({ type: Array }) value;
81
+ @Prop({ type: String, default: 'ISO' }) valueFormat;
82
+
83
+ year = null;
84
+
85
+ get quarters() {
86
+ return [
87
+ { Name: `${this.$t('components.quarter')} 1`, Months: [1, 2, 3], Number: 1 },
88
+ { Name: `${this.$t('components.quarter')} 2`, Months: [4, 5, 6], Number: 2 },
89
+ { Name: `${this.$t('components.quarter')} 3`, Months: [7, 8, 9], Number: 3 },
90
+ { Name: `${this.$t('components.quarter')} 4`, Months: [10, 11, 12], Number: 4 },
91
+ ];
92
+ }
93
+
94
+ get nextYear() {
95
+ return this.year + 1;
96
+ }
97
+
98
+ get prevYear() {
99
+ return this.year - 1;
100
+ }
101
+
102
+ setYear(year) {
103
+ this.year = year;
104
+ }
105
+
106
+ mounted() {
107
+ this.year = (this.value && this.value[0]) ? this.valueAsLuxon[0].year : DateTime.local().year;
108
+ }
109
+
110
+ get valueAsLuxon() {
111
+ if (!this.value || this.value.length < 2) {
112
+ return null;
113
+ }
114
+ if (this.valueFormat === 'ISO') {
115
+ return [
116
+ DateTime.fromISO(this.value[0]),
117
+ DateTime.fromISO(this.value[1]),
118
+ ];
119
+ }
120
+ return [
121
+ DateTime.fromFormat(this.value[0], this.valueFormat),
122
+ DateTime.fromFormat(this.value[1], this.valueFormat),
123
+ ];
124
+ }
125
+
126
+ isCurrentMonth(month) {
127
+ if (!this.valueAsLuxon) {
128
+ return false;
129
+ }
130
+ return Number(this.valueAsLuxon[0].toFormat('M')) === month && Number(this.valueAsLuxon[1].toFormat('M')) === month;
131
+ }
132
+
133
+ isOnlyQuarter(quarter) {
134
+ if (!this.valueAsLuxon) {
135
+ return false;
136
+ }
137
+ return this.isCurrentQuarter(quarter) && this.valueAsLuxon[0].month !== this.valueAsLuxon[1].month;
138
+ }
139
+
140
+ isCurrentQuarter(quarter) {
141
+ if (!this.valueAsLuxon) {
142
+ return false;
143
+ }
144
+ return this.valueAsLuxon[0].quarter === quarter && this.valueAsLuxon[1].quarter === quarter;
145
+ }
146
+
147
+ isCurrentYear() {
148
+ if (!this.valueAsLuxon) {
149
+ return false;
150
+ }
151
+ return this.valueAsLuxon[0].hasSame(this.valueAsLuxon[0].startOf('year'), 'day')
152
+ && this.valueAsLuxon[1].hasSame(this.valueAsLuxon[0].endOf('year'), 'day');
153
+ }
154
+
155
+ updateValue(start, end) {
156
+ if (!start) {
157
+ this.$emit('input', []);
158
+ return;
159
+ }
160
+ this.$emit('input', [
161
+ (this.valueFormat === 'ISO') ? start.toISO() : start.toFormat(this.valueFormat),
162
+ (this.valueFormat === 'ISO') ? end.toISO() : end.toFormat(this.valueFormat),
163
+ ]);
164
+ }
165
+
166
+ onYearSelect(year) {
167
+ this.updateValue(
168
+ DateTime.local().set({ year }).startOf('year'),
169
+ DateTime.local().set({ year }).endOf('year'),
170
+ );
171
+ }
172
+
173
+ onMonthSelect(month) {
174
+ this.updateValue(
175
+ DateTime.local().set({ month, year: this.year }).startOf('month'),
176
+ DateTime.local().set({ month, year: this.year }).endOf('month'),
177
+ );
178
+ }
179
+
180
+ onQuarterSelect([startMonth, endMonth]) {
181
+ this.updateValue(
182
+ DateTime.local().set({ month: startMonth, year: this.year }).startOf('quarter'),
183
+ DateTime.local().set({ month: endMonth, year: this.year }).endOf('quarter'),
184
+ );
185
+ }
186
+ }
187
+ </script>
@@ -2,6 +2,7 @@ import { storiesOf } from '@storybook/vue';
2
2
  import itfDatePicker from './DatePicker.vue';
3
3
  import itfMonthPicker from './MonthPicker.vue';
4
4
  import itfPeriodPicker from './PeriodPicker';
5
+ import itfPeriodPickerInline from './PeriodPickerInline';
5
6
  import itfDatePickerInline from './DatePickerInline.vue';
6
7
  import itfDateRangePicker from './DateRangePicker.vue';
7
8
  import itfDateRangePickerInline from './DateRangePickerInline.vue';
@@ -13,6 +14,7 @@ storiesOf('Common', module)
13
14
  itfApp,
14
15
  itfDatePicker,
15
16
  itfPeriodPicker,
17
+ itfPeriodPickerInline,
16
18
  itfMonthPicker,
17
19
  itfDateRangePicker,
18
20
  itfDatePickerInline,
@@ -86,6 +88,8 @@ storiesOf('Common', module)
86
88
  <h2>Period</h2>
87
89
 
88
90
  <itf-period-picker :value="dateRange" v-model.lazy="dateRange"></itf-period-picker>
91
+
92
+ <itf-period-picker-inline :value="dateRange" v-model.lazy="dateRange"></itf-period-picker-inline>
89
93
  </itf-app>
90
94
  </div>`,
91
95
  }));
@@ -29,8 +29,8 @@
29
29
  @input="onFilterChange({ value: $event })"
30
30
  />
31
31
  </template>
32
- <template v-else-if="type === 'period-selector'">
33
- <itf-period-picker
32
+ <template v-else-if="type === 'timeframe'">
33
+ <itf-period-picker-inline
34
34
  style="margin: -.5rem"
35
35
  :value="value.value"
36
36
  value-format="yyyy-MM-dd"
@@ -69,6 +69,7 @@
69
69
  <style lang="scss">
70
70
  :root {
71
71
  --filter-badge__default-color: #475266;
72
+ --filter-badge__icon-color: #A7AFBB;
72
73
  --filter-badge__default-border-color: #0000001A;
73
74
  --filter-badge__default-bg-color: transparent;
74
75
  --filter-badge__default-bg-color-hover: #1A4A970D;
@@ -95,6 +96,7 @@
95
96
 
96
97
  .icon {
97
98
  margin: -2px;
99
+ color: var(--filter-badge__icon-color);
98
100
  }
99
101
  &:hover {
100
102
  background-color: var(--filter-badge__default-bg-color-hover);
@@ -154,7 +156,7 @@ import itfButton from '../button/Button';
154
156
  import itfDropdown from '../dropdown/Dropdown.vue';
155
157
  import itfDatePickerInline from '../datepicker/DatePickerInline.vue';
156
158
  import itfDateRangePickerInline from '../datepicker/DateRangePickerInline.vue';
157
- import itfPeriodPicker from '../datepicker/PeriodPicker.vue'
159
+ import itfPeriodPickerInline from '../datepicker/PeriodPickerInline.vue'
158
160
  import itfTextField from '../text-field/TextField.vue';
159
161
  import FilterFacetsList from './FilterFacetsList';
160
162
  import FilterAmountRange from './FilterAmountRange.vue';
@@ -166,7 +168,7 @@ export default @Component({
166
168
  itfDropdown,
167
169
  itfDatePickerInline,
168
170
  itfDateRangePickerInline,
169
- itfPeriodPicker,
171
+ itfPeriodPickerInline,
170
172
  itfTextField,
171
173
  FilterFacetsList,
172
174
  FilterAmountRange
@@ -11,7 +11,7 @@
11
11
  prepend-icon="search"
12
12
  :delay-input="250"
13
13
  clearable
14
- :value="filterValue.query"
14
+ :value="filterValueQuery"
15
15
  @input="(e) => onFilterChange({ type: 'text', name: 'query' }, { value: e })"
16
16
  />
17
17
  </div>
@@ -27,20 +27,29 @@
27
27
  <slot name="after-filter-btn"></slot>
28
28
  </div>
29
29
  </div>
30
- <div class="d-flex align-items-center justify-content-between w-100">
31
- <div v-if="showFilters && showFilter" class="d-flex gap-2 flex-nowrap filters-row">
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
32
  <filter-badge
33
33
  v-for="(facet, n) in visibleFilters"
34
34
  :key="n"
35
+ class="itf-filter-panel__badge"
36
+ :ref="'item-' + n"
35
37
  v-model="filter[facet.name]"
36
38
  :is-default="filter[facet.name].isDefault"
37
39
  :text="filter[facet.name].label"
38
40
  :type="facet.type"
39
41
  :icon="facet.icon"
40
42
  :options="facet.options"
43
+ :class="{ hidden: !visibleItems.has(n) && !isFilterExpanded }"
41
44
  @change="onFilterChange(facet, $event)"
42
45
  />
43
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>
44
53
  <slot name="after-filters"></slot>
45
54
  </div>
46
55
  <div v-if="loading">
@@ -51,6 +60,18 @@
51
60
  </template>
52
61
  <style lang="scss">
53
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
+ }
54
75
  .itf-text-field:not(.is-valid):not(.is-invalid) .itf-icon {
55
76
  color: #8E97A5;
56
77
  }
@@ -69,18 +90,28 @@
69
90
  }
70
91
 
71
92
  .filters-row {
72
- @media (max-width: 768px) {
73
- overflow: auto;
74
- width: 100%;
75
- padding: 2px;
76
- margin: -2px;
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
+ }
77
108
  }
78
109
  }
79
110
  }
80
111
  </style>
81
112
  <script>
82
113
  import { DateTime } from 'luxon';
83
- import { Vue, Model, Prop, Component } from 'vue-property-decorator';
114
+ import { Vue, Watch, Model, Prop, Component } from 'vue-property-decorator';
84
115
  import tooltip from '../../directives/tooltip';
85
116
  import itfIcon from '../icon/Icon';
86
117
  import itfButton from '../button/Button';
@@ -115,12 +146,63 @@ class FilterPanel extends Vue {
115
146
  @Prop({ type: String, default: function() { return this.$t('components.filter.search'); } }) searchPlaceholder;
116
147
 
117
148
  filter = {};
118
- filterValue = {};
149
+ filterValue = null;
119
150
  filters = [];
120
151
  loading = false;
121
152
  showFilters = true;
153
+ isFilterExpanded = false;
154
+ observer = null;
122
155
 
123
156
  periodFilters = ['period', 'period-selector'];
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
+ if (entry.isIntersecting) {
181
+ this.visibleItems.add(index); // Додаємо, якщо елемент у полі зору
182
+ } else {
183
+ this.visibleItems.delete(index); // Видаляємо, якщо вийшов за межі
184
+ }
185
+ this.$forceUpdate(); // Оновлюємо Vue, бо Set не є реактивним
186
+ });
187
+ },
188
+ { root: this.$refs.container, threshold: 1.0 }
189
+ );
190
+
191
+ // Спостерігаємо за кожним елементом
192
+ this.$nextTick(() => {
193
+ for (const index in this.visibleFilters) {
194
+ const item = this.$refs[`item-${index}`][0];
195
+ if (item) {
196
+ item.$el.dataset.index = index; // Зберігаємо індекс у dataset
197
+ this.observer.observe(item.$el);
198
+ }
199
+ }
200
+ });
201
+ }
202
+
203
+ get filterValueQuery() {
204
+ return this.filterValue?.query ?? '';
205
+ }
124
206
 
125
207
  get visibleFilters() {
126
208
  if (this.mini) {
@@ -137,6 +219,12 @@ class FilterPanel extends Vue {
137
219
  return `filter-panel-${this.stateName}-filters`;
138
220
  }
139
221
 
222
+ @Watch('staticFilters', { deep: true })
223
+ onStaticFiltersUpdate() {
224
+ this.filters = this.staticFilters ?? [];
225
+ this.loadFiltersValue();
226
+ }
227
+
140
228
  async mounted() {
141
229
  if (this.stateName) {
142
230
  const item = localStorage.getItem(this.localstorageKey);
@@ -188,13 +276,25 @@ class FilterPanel extends Vue {
188
276
  if (this.search) {
189
277
  filterValue.query = payload.query;
190
278
  }
191
- const prevFilter = JSON.stringify(this.filter).concat(JSON.stringify(this.filterValue));
192
- const newFilter = JSON.stringify(filter).concat(JSON.stringify(filterValue));
193
- if (prevFilter !== newFilter) {
279
+ const prevFilter = JSON.stringify(this.getVisibleFilters(this.filterValue));//.concat(JSON.stringify(this.filterValue));
280
+ const newFilter = JSON.stringify(this.getVisibleFilters(filterValue));//.concat(JSON.stringify(filterValue));
281
+ if (prevFilter !== newFilter || !this.filterValue) {
194
282
  this.filter = filter;
195
283
  this.filterValue = filterValue;
196
284
  this.$emit('input', this.filterValue);
285
+ this.initObserver();
286
+ }
287
+ }
288
+
289
+ getVisibleFilters(filter) {
290
+ const result = [];
291
+ const facets = Object.values(this.filter);
292
+ for (const facet of facets) {
293
+ if (!facet.isDefault && !facet.hidden) {
294
+ result.push(filter[facet.name]);
295
+ }
197
296
  }
297
+ return result.filter(Boolean);
198
298
  }
199
299
 
200
300
  setFilter(field, value) {
@@ -34,7 +34,7 @@
34
34
  :class="confirmClass"
35
35
  @click="onConfirm"
36
36
  >
37
- <span v-html="$t('components.popover.yesDelete')"></span>
37
+ <span v-html="deleteCaption"></span>
38
38
  </button>
39
39
  </div>
40
40
  </div>
@@ -59,8 +59,8 @@ class itfDeleteConfirmModal extends Vue {
59
59
  @Prop(Boolean) loading;
60
60
  @Prop({ type: Boolean, default: false }) disabled;
61
61
  @Prop({ type: String, default: 'text-danger' }) confirmClass;
62
- @Prop({ type: String, default () { return this.$t('noKeepIt'); } }) cancelCaption;
63
- @Prop({ type: String, default () { return this.$t('yesDelete'); } }) deleteCaption;
62
+ @Prop({ type: String, default () { return this.$t('components.noKeepIt'); } }) cancelCaption;
63
+ @Prop({ type: String, default () { return this.$t('components.yesDelete'); } }) deleteCaption;
64
64
 
65
65
  isModalShown = false;
66
66
 
@@ -159,8 +159,8 @@ $double-an-time: $an-time * 2;
159
159
  </style>
160
160
  <script lang="ts">
161
161
  import { Vue, Component, Prop } from 'vue-property-decorator';
162
- import Panel from './Panel.vue';
163
- import {hashToStack, stackToHash} from "@itfin/components/src/components/panels/helpers";
162
+ import Panel from './Panel';
163
+ import {hashToStack, stackToHash} from "./helpers";
164
164
 
165
165
  interface VisualOptions {
166
166
  title: string;
@@ -88,7 +88,6 @@ import itfTableGroup from './TableGroup.vue';
88
88
  import itfTableHeader from './TableHeader.vue';
89
89
  import itfNoticePopout from '../popover/NoticePopout.vue';
90
90
  import './table2.scss';
91
- import itfTableBody from '@itfin/components/src/components/table/TableBody.vue';
92
91
 
93
92
  export default @Component({
94
93
  name: 'itfTable2',
@@ -96,7 +95,6 @@ export default @Component({
96
95
  return { tableEl: this }; // do not use Provide from vue-property-decorator
97
96
  },
98
97
  components: {
99
- itfTableBody,
100
98
  itfCheckboxGroup,
101
99
  itfTableHeader,
102
100
  itfButton,
@@ -95,11 +95,11 @@
95
95
 
96
96
  <!-- Лінія додати нову -->
97
97
  <div v-if="isShowTable && addNewRows"
98
- class="table-row-template d-flex align-items-stretch">
98
+ class="table-row-template table-row-template__new-row d-flex align-items-stretch">
99
99
  <div class="shadow-area"></div>
100
100
  <a href="" @click.prevent="$emit('new', title)" data-test="table-add-new-item"
101
101
  class="d-flex align-items-center flex-grow-1 table-add-new-item text-decoration-none">
102
- <span class="d-sticky d-flex align-items-center py-1">
102
+ <span class="d-sticky d-flex align-items-center py-1 px-2 small">
103
103
  <itf-icon name="plus"/>
104
104
  <span>{{ newLabel }}</span>
105
105
  </span>
@@ -268,7 +268,11 @@
268
268
  min-height: var(--table-small-row-size);
269
269
  }
270
270
 
271
+ .table-row-template.table-row-template__new-row {
272
+ min-height: 2rem;
273
+ }
271
274
  .table-add-new-item {
275
+ background-color: var(--itf-table-header-bg);
272
276
  border-right:var(--itf-table-border-base-width) solid var(--itf-table-border-base-color);
273
277
  border-left:var(--itf-table-border-base-width) solid var(--itf-table-border-base-color);
274
278
  border-bottom: var(--itf-table-border-base-width) solid var(--itf-table-border-base-color);
@@ -277,7 +281,7 @@
277
281
  border-bottom-right-radius: var(--itf-table-table-border-radius);
278
282
 
279
283
  & > span {
280
- left: var(--shadow-area-width);
284
+ left: calc(var(--shadow-area-width) + 4px);
281
285
  position: sticky;
282
286
  padding-left: var(--shadow-area-width);
283
287
  //border-left: var(--itf-table-border-base-width) solid var(--itf-table-border-base-color);
@@ -60,7 +60,7 @@ body[data-theme="dark"] {
60
60
  height: 100%;
61
61
  }
62
62
  .scroller {
63
- margin-bottom: .5rem;
63
+ //margin-bottom: .5rem;
64
64
  }
65
65
  .scrollable-x {
66
66
  overflow-x: scroll;
@@ -42,12 +42,11 @@
42
42
  </itf-dropdown>
43
43
 
44
44
  <itf-segmented-control
45
- v-if="tabs.length > 1"
45
+ v-if="tabs.length"
46
46
  class="small"
47
- :value="currentTab"
47
+ v-model="currentTab"
48
48
  item-key="value"
49
49
  :items="tabs"
50
- @input="updateTabs"
51
50
  >
52
51
  <template #item="{ item }">
53
52
  <div class="d-flex align-items-center">
@@ -63,39 +62,41 @@
63
62
  <slot v-else-if="currentTab === 'board'" name="kanban-view"></slot>
64
63
  <slot v-else-if="currentTab === 'calendar'" name="calendar-view"></slot>
65
64
  <slot v-else name="table-view">
66
- <div class="flex-grow-1 px-3 d-flex flex-column">
67
- <div class="position-relative flex-grow-1">
68
- <itf-table
69
- ref="table"
70
- style="--shadow-area-width: 0px;"
71
- absolute
72
- striped
73
- clickable
74
- column-sorting
75
- column-resizing
76
- :indicator-type="indicatorType"
77
- class="permanent-checkboxes"
78
- :state-name="stateName"
79
- id-property="id"
80
- :sort-as-string="sortAsString"
81
- :rows="items"
82
- :schema="tableSchema"
83
- :sorting="sorting"
84
- :active="activeIds"
85
- :show-actions="showActions"
86
- v-model="selectedIds"
87
- @row-click="$emit('open', $event)"
88
- @update:sorting="updateSorting($event)"
89
- >
90
- <template v-for="(_, name) in $slots" #[name]="slotData">
91
- <slot :name="name" v-bind="slotData || {}"/>
92
- </template>
93
- <template v-for="(_, name) in $scopedSlots" #[name]="slotData">
94
- <slot :name="name" v-bind="slotData || {}"/>
95
- </template>
96
- </itf-table>
65
+ <div class="flex-grow-1 px-3 d-flex flex-column">
66
+ <div class="position-relative flex-grow-1">
67
+ <itf-table
68
+ ref="table"
69
+ style="--shadow-area-width: 0px;"
70
+ absolute
71
+ striped
72
+ clickable
73
+ column-sorting
74
+ column-resizing
75
+ :indicator-type="indicatorType"
76
+ class="permanent-checkboxes"
77
+ :state-name="stateName"
78
+ id-property="id"
79
+ :sort-as-string="sortAsString"
80
+ :rows="items"
81
+ :schema="tableSchema"
82
+ :sorting="sorting"
83
+ :active="activeIds"
84
+ :no-select-all="noSelectAll"
85
+ :show-actions="showActions"
86
+ :indicator-width="indicatorWidth"
87
+ v-model="selectedIds"
88
+ @row-click="$emit('open', $event)"
89
+ @update:sorting="updateSorting($event)"
90
+ >
91
+ <template v-for="(_, name) in $slots" #[name]="slotData">
92
+ <slot :name="name" v-bind="slotData || {}"/>
93
+ </template>
94
+ <template v-for="(_, name) in $scopedSlots" #[name]="slotData">
95
+ <slot :name="name" v-bind="slotData || {}"/>
96
+ </template>
97
+ </itf-table>
98
+ </div>
97
99
  </div>
98
- </div>
99
100
  </slot>
100
101
 
101
102
  <itf-pagination
@@ -117,7 +118,7 @@
117
118
 
118
119
  </template>
119
120
  <script>
120
- import {Vue, ModelSync, Component, Prop, Inject, PropSync, Watch} from 'vue-property-decorator';
121
+ import { Vue, ModelSync, Component, Prop, Inject } from 'vue-property-decorator';
121
122
  import loading from '../../directives/loading';
122
123
  import itfTable from '../table/Table2.vue';
123
124
  import itfFilterPanel from '../filter/FilterPanel.vue';
@@ -125,13 +126,14 @@ import itfPagination from '../pagination/Pagination2.vue';
125
126
  import itfTableBody from "../table/TableBody.vue";
126
127
  import itfIcon from "../icon/Icon.vue";
127
128
  import itfDropdown from "../dropdown/Dropdown.vue";
128
- import itfSegmentedControl from '@itfin/components/src/components/segmented-control/SegmentedControl.vue';
129
+ import itfSegmentedControl from '../segmented-control/SegmentedControl.vue';
129
130
 
130
131
  export default @Component({
131
132
  name: 'itfView',
132
133
  components: {
133
134
  itfSegmentedControl,
134
- itfDropdown, itfIcon,
135
+ itfDropdown,
136
+ itfIcon,
135
137
  itfPagination,
136
138
  itfFilterPanel,
137
139
  itfTableBody,
@@ -147,7 +149,10 @@ class itfView extends Vue {
147
149
 
148
150
  @Prop({ type: Boolean }) loading;
149
151
  @Prop({ type: Array }) filters;
152
+ // @Prop({ type: Object, required: true }) schema;
150
153
  @Prop({ type: Object }) schema;
154
+ // @Prop({ default: 20 }) size;
155
+ // @Prop({ default: 1 }) page;
151
156
  @Prop(String) defaultSorting;
152
157
  @Prop(String) endpoint;
153
158
  @Prop(String) filtersEndpoint;
@@ -163,9 +168,11 @@ class itfView extends Vue {
163
168
  @Prop(Boolean) listViewEnabled;
164
169
  @Prop(Boolean) kanbanViewEnabled;
165
170
  @Prop(Boolean) calendarViewEnabled;
171
+ @Prop(Boolean) noSelectAll;
166
172
  @Prop({ type: Boolean, default: true }) tableViewEnabled;
167
173
  @Prop(Boolean) sortAsString;
168
174
  @Prop(Boolean) oldFormat;
175
+ @Prop({ default: 45 }) indicatorWidth;
169
176
  @Prop({type: Function, default: null }) onSplitSlectedIds // якщо потрібно розділяти вибрані рядки в таблиці на дві групи
170
177
 
171
178
  page = 1;
@@ -190,6 +197,10 @@ class itfView extends Vue {
190
197
  return this.tab;
191
198
  }
192
199
 
200
+ set currentTab(val) {
201
+ this.$emit('update:tab', val);
202
+ }
203
+
193
204
  get tabs() {
194
205
  const views = [];
195
206
  if (this.listViewEnabled) {
@@ -208,6 +219,7 @@ class itfView extends Vue {
208
219
  }
209
220
 
210
221
  get tableSchema() {
222
+ // return this.tableColumns || this.schema;
211
223
  if (this.tableColumns) {
212
224
  return {
213
225
  properties: this.tableColumns
@@ -220,12 +232,13 @@ class itfView extends Vue {
220
232
  }
221
233
 
222
234
  created() {
223
- const defaultSize = localStorage.getItem('sizePerPage') ?? 20;
224
-
225
- const { page, size, sorting } = this.panel.getPayload() ?? {};
226
- this.page = page ?? 1;
227
- this.size = size ?? defaultSize;
228
- this.sorting = sorting ?? this.defaultSorting;
235
+ // const defaultSize = localStorage.getItem('sizePerPage') ?? 20;
236
+ //
237
+ // const { page, size, sorting } = this.panel.getPayload() ?? {};
238
+ // this.page = page ?? 1;
239
+ // this.size = size ?? defaultSize;
240
+ // this.sorting = sorting ?? this.defaultSorting;
241
+ this.sorting = this.defaultSorting;
229
242
  }
230
243
 
231
244
  mounted() {
package/src/locales/en.js CHANGED
@@ -99,7 +99,7 @@ module.exports = {
99
99
  copyingToClipboardWasSuccessful: 'Copying to clipboard was successful',
100
100
  },
101
101
  table: {
102
- new: 'New',
102
+ new: 'New row',
103
103
  noResults: 'No items',
104
104
  sortAscending: 'Sort ascending',
105
105
  sortDescending: 'Sort descending',
package/src/locales/uk.js CHANGED
@@ -104,7 +104,7 @@ module.exports = {
104
104
  next: 'Наступна',
105
105
  },
106
106
  table: {
107
- new: 'Додати',
107
+ new: 'Додати рядок',
108
108
  noResults: 'Немає записів',
109
109
  sortAscending: 'Сортувати за зростанням',
110
110
  sortDescending: 'Сортувати за спаданням',