@itfin/components 1.4.33 → 1.4.35

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.33",
3
+ "version": "1.4.35",
4
4
  "author": "Vitalii Savchuk <esvit666@gmail.com>",
5
5
  "scripts": {
6
6
  "serve": "vue-cli-service serve",
@@ -74,9 +74,12 @@ class itfApp extends Vue {
74
74
  try {
75
75
  await func();
76
76
  } catch (err) {
77
- this.showError(err.message);
78
- if (errFunc) {
79
- errFunc(err);
77
+ if (err.code !== 'ERR_CANCELED') {
78
+ console.error(err);
79
+ this.showError(err.message);
80
+ if (errFunc) {
81
+ errFunc(err);
82
+ }
80
83
  }
81
84
  }
82
85
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  <div class="itf-checkbox form-check" :class="{ 'form-switch': this.switch, 'itf-checkbox__large': large, 'itf-checkbox__medium': medium }">
4
4
  <input class="form-check-input" ref="input" :id="id" type="checkbox" name="checkbox" v-model="isChecked" :disabled="isDisabled" />
5
- <label :for="id" class="form-check-label">
5
+ <label :for="id" class="form-check-label w-100" :class="{ 'disabled': isDisabled }">
6
6
  <slot name="label">
7
7
  {{label}}
8
8
  <slot name="icon"></slot>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="itf-radio-box form-check card" :class="{ 'itf-radio__large': large, 'itf-radio__medium': medium, 'active': isChecked, 'right': right, 'left': !right }">
2
+ <div class="itf-radio-box form-check card" :class="{ 'disabled': disabled, 'itf-radio__large': large, 'itf-radio__medium': medium, 'active': isChecked, 'right': right, 'left': !right }">
3
3
  <input class="form-check-input" :id="id" type="radio" :name="radioName" v-model="isChecked" :value="true" :disabled="disabled" />
4
4
  <label :for="id" slot="label" class="form-check-label card-body">
5
5
 
@@ -18,6 +18,17 @@
18
18
  position: relative;
19
19
  cursor: pointer;
20
20
 
21
+ &:not(.disabled) {
22
+ cursor: pointer;
23
+
24
+ .form-check-label {
25
+ cursor: pointer;
26
+ }
27
+ }
28
+
29
+ .form-check-label {
30
+ cursor: not-allowed;
31
+ }
21
32
  &.left {
22
33
  padding: 0 0 0 2.5rem;
23
34
 
@@ -38,13 +49,9 @@
38
49
  &.active {
39
50
  background-color: rgba(var(--bs-primary-rgb),.1) !important;
40
51
  }
41
- &:hover {
52
+ &:hover:not(.disabled) {
42
53
  background-color: rgba(0,0,0,.05);
43
54
  }
44
-
45
- .form-check-label {
46
- cursor: pointer;
47
- }
48
55
  }
49
56
  </style>
50
57
  <script>
@@ -11,9 +11,9 @@
11
11
  <button
12
12
  v-for="(item, n) in daysList"
13
13
  :key="n"
14
- :disabled="!isEnabled(item.date())"
15
- :class="{'active': isSelected(item.date())}"
16
- @click="selectDate(item.date())"
14
+ :disabled="!isEnabled(item.date)"
15
+ :class="{'active': isSelected(item.date)}"
16
+ @click="selectDate(item.date)"
17
17
  class="btn btn-outline-primary btn-sm text-start">
18
18
  {{item.title}}
19
19
  </button>
@@ -30,31 +30,29 @@ export default @Component({
30
30
  name: 'itfDatePickerInline',
31
31
  })
32
32
  class itfDatePickerInline extends Vue {
33
- @Prop() value;
33
+ @Prop({ type: String }) value;
34
34
  @Prop({ type: String, default: 'yyyy-MM-dd' }) valueFormat;
35
35
  @Prop({ type: String, default: ITFSettings.defaultDisplayDateFormat }) displayFormat;
36
+ @Prop({ type: String, default: 'days', validator: (value) => ['days', 'months', 'years'].includes(value) }) startView;
37
+ @Prop({ type: String, default: 'days', validator: (value) => ['days', 'months', 'years'].includes(value) }) minView;
36
38
  @Prop({ type: Boolean, default: false }) onlyCalendar;
39
+ @Prop({ type: Boolean, default: false }) range;
37
40
  @Prop({ type: Object, default: () => ({}) }) customDays;
38
41
  @Prop({ type: [String, Date], default: '' }) minDate;
39
42
  @Prop({ type: [String, Date], default: '' }) maxDate;
40
- @Prop({ type: String, default: 'days' }) minView;
41
43
  @Prop({
42
44
  type: Array,
43
45
  default: function () {
44
- return [// { title: 'Today', date: () => [DateTime.local(), DateTime.local()] },
45
- { title: this.$t('components.thisWeek'), date: () => [DateTime.local().startOf('week'), DateTime.local().endOf('week')] },
46
- { title: this.$t('components.lastWeek'), date: () => [DateTime.local().minus({ week: 1 }).startOf('week'), DateTime.local().minus({ week: 1 }).endOf('week')] },
47
- { title: this.$t('components.thisMonth'), date: () => [DateTime.local().startOf('month'), DateTime.local().endOf('month')] },
48
- { title: this.$t('components.lastMonth'), date: () => [DateTime.local().minus({ months: 1 }).startOf('month'), DateTime.local().minus({ months: 1 }).endOf('month')] },
49
- { title: this.$t('components.thisQuarter'), date: () => [DateTime.local().startOf('quarter'), DateTime.local().endOf('quarter')] },
50
- { title: this.$t('components.lastQuarter'), date: () => [DateTime.local().minus({ quarter: 1 }).startOf('quarter'), DateTime.local().minus({ quarter: 1 }).endOf('quarter')] },
51
- { title: this.$t('components.thisYear'), date: () => [DateTime.local().startOf('year'), DateTime.local().endOf('year')] },
52
- { title: this.$t('components.lastYear'), date: () => [DateTime.local().minus({ year: 1 }).startOf('year'), DateTime.local().minus({ year: 1 }).endOf('year')] },
46
+ return [
47
+ { title: this.$t('components.today'), date: {} },
48
+ { title: this.$t('components.tomorrow'), date: { days: 1 } },
49
+ { title: this.$t('components.inAWeek'), date: { week: 1 } },
50
+ { title: this.$t('components.inAMonth'), date: { month: 1 } },
51
+ { title: this.$t('components.inAHalfYear'), date: { month: 6 } },
52
+ { title: this.$t('components.inAYear'), date: { year: 1 } },
53
53
  ];
54
54
  }
55
55
  }) daysList;
56
- @Prop(Boolean) compare;
57
- @Prop({ type: Array }) range; // for compare mode
58
56
 
59
57
  calendar = null;
60
58
 
@@ -62,17 +60,12 @@ class itfDatePickerInline extends Vue {
62
60
  this.createCalendar();
63
61
  }
64
62
 
65
- compareMode(value) {
66
- return this.calendar.compareMode(value);
67
- }
68
-
69
63
  @Watch('minDate')
70
- @Watch('maxDate')
71
- @Watch('minView')
72
64
  async createCalendar() {
73
65
  if (this.calendar) {
74
66
  this.destroyCalendar();
75
67
  }
68
+
76
69
  const [
77
70
  { default: AirDatepicker },
78
71
  { default: localeEn },
@@ -90,35 +83,28 @@ class itfDatePickerInline extends Vue {
90
83
  uk: localeUk,
91
84
  de: localeDe,
92
85
  };
93
- const opts = {
86
+ this.calendar = new AirDatepicker(this.$refs.calendar, {
94
87
  locale: locales[this.$i18n.locale] || locales.en,
95
88
  firstDay: 1,
96
89
  altFieldDateFormat: this.valueFormat,
90
+ range: this.range,
91
+ view: (this.valueAsLuxon && !this.minView) ? 'days' : this.startView,
92
+ minView: this.minView,
97
93
  minDate: this.minDate,
98
94
  maxDate: this.maxDate,
99
- view: this.minView,
100
- minView: this.minView,
101
- range: true,
102
- compareRange: true,
103
- dynamicRange: true,
104
- toggleSelected: function({ date, datepicker }) {
105
- if (datepicker.selectedDates.length > 1) {
106
- datepicker.selectDate(date);
107
- }
108
- return false;
109
- },
110
- selectedDates: this.valueAsLuxon
111
- ? [this.valueAsLuxon[0].toJSDate(), this.valueAsLuxon[1].toJSDate()]
112
- : [],
113
- onSelect: () => {
114
- if (!this.calendar.rangeDateTo) {
95
+ selectedDates: this.valueAsLuxon ? [this.valueAsLuxon.toJSDate()] : [],
96
+ onSelect: ({ date }) => {
97
+ if (this.range && !this.calendar.rangeDateTo) {
115
98
  return;
116
99
  }
117
- this.updateValue(
118
- DateTime.fromJSDate(this.calendar.rangeDateFrom).toFormat(this.displayFormat),
119
- DateTime.fromJSDate(this.calendar.rangeDateTo).toFormat(this.displayFormat),
120
- false,
121
- );
100
+ if (this.range) {
101
+ this.updateValue(
102
+ DateTime.fromJSDate(this.calendar.rangeDateFrom).toFormat(this.displayFormat),
103
+ false
104
+ );
105
+ } else {
106
+ this.updateValue(DateTime.fromJSDate(date).toFormat(this.displayFormat));
107
+ }
122
108
  },
123
109
  onRenderCell: ({ date }) => {
124
110
  const strDate = DateTime.fromJSDate(date).toFormat('yyyy-MM-dd');
@@ -127,22 +113,13 @@ class itfDatePickerInline extends Vue {
127
113
  }
128
114
  return {
129
115
  html: this.customDays[strDate].text || false,
130
- classes: this.customDays[strDate].class || false,
116
+ classes: this.customDays[strDate].class || false
131
117
  };
132
118
  },
133
- };
134
- if (this.compare) {
135
- opts.compareRange = true;
136
- }
137
-
138
- this.calendar = new AirDatepicker(this.$refs.calendar, opts);
119
+ });
139
120
  if (this.valueAsLuxon) {
140
- this.calendar.setViewDate(this.valueAsLuxon[0].toJSDate());
141
- }
142
- if (this.compare) {
143
- this.calendar.compareMode(true);
121
+ this.calendar.setViewDate(this.valueAsLuxon.toJSDate());
144
122
  }
145
- this.onRangeChange();
146
123
  }
147
124
 
148
125
  beforeDestroy() {
@@ -158,97 +135,59 @@ class itfDatePickerInline extends Vue {
158
135
  }
159
136
 
160
137
  get valueAsLuxon() {
161
- if (!this.value || this.value.length < 2 || !this.value[0] || !this.value[1]) {
138
+ if (!this.value) {
162
139
  return null;
163
140
  }
164
141
  if (this.valueFormat === 'ISO') {
165
- return [
166
- DateTime.fromISO(this.value[0]),
167
- DateTime.fromISO(this.value[1]),
168
- ];
142
+ return DateTime.fromISO(this.value);
143
+ }
144
+ return DateTime.fromFormat(this.value, this.valueFormat);
145
+ }
146
+
147
+ get displayValue() {
148
+ if (!this.valueAsLuxon) {
149
+ return '';
169
150
  }
170
- return [
171
- DateTime.fromFormat(this.value[0], this.valueFormat),
172
- DateTime.fromFormat(this.value[1], this.valueFormat),
173
- ];
151
+ return this.valueAsLuxon.toFormat(this.displayFormat);
174
152
  }
175
153
 
176
- updateValue(date1, date2, emitEmpty = false) {
177
- const val = date1 && DateTime.fromFormat(date1, this.displayFormat);
178
- const val2 = date2 && DateTime.fromFormat(date2, this.displayFormat);
179
- if (!val || !val.isValid || !val2 || !val2.isValid) {
154
+ updateValue(value, emitEmpty = false) {
155
+ const val = value && DateTime.fromFormat(value, this.displayFormat);
156
+ if (!val || !val.isValid) {
180
157
  if (emitEmpty) {
181
158
  this.$emit('input', null);
182
159
  }
183
160
  return;
184
161
  }
185
- if ((this.minDateLuxon && (val < this.minDateLuxon || val2 < this.minDateLuxon)) ||
186
- (this.maxDateLuxon && (val > this.maxDateLuxon || val2 > this.maxDateLuxon))
187
- ) {
188
- return;
189
- }
190
-
191
162
  if (this.valueFormat === 'ISO') {
192
- return this.$emit('input', [val.toISO(), val2.toISO()]);
163
+ return this.$emit('input', val.toISO());
193
164
  }
194
- this.$emit('input', [
195
- val.toFormat(this.valueFormat),
196
- val2.toFormat(this.valueFormat),
197
- ]);
165
+ this.$emit('input', val.toFormat(this.valueFormat));
198
166
  }
199
167
 
200
- get minDateLuxon() {
201
- return this.minDate && this.dateLuxon(this.minDate);
202
- }
203
- get maxDateLuxon() {
204
- return this.maxDate && this.dateLuxon(this.maxDate);
168
+ isSelected(date) {
169
+ if (!this.valueAsLuxon) {
170
+ return false;
171
+ }
172
+ return this.valueAsLuxon.hasSame(DateTime.local().plus(date), 'day');
205
173
  }
206
174
 
207
- dateLuxon(date) {
208
- return typeof date === 'string' ? DateTime.fromISO(date) : DateTime.fromJSDate(date);
175
+ get minDateLuxon() {
176
+ return this.minDate && (typeof this.minDate === 'string' ? DateTime.fromISO(this.minDate) : DateTime.fromJSDate(this.minDate));
209
177
  }
210
178
 
211
179
  isEnabled(date) {
212
- const lxDate1 = date[0].set({ hours: 0, minutes: 0, seconds: 0 });
213
- const lxDate2 = date[1].set({ hours: 23, minutes: 59, seconds: 59 });
214
-
215
- if (this.minDateLuxon) {
216
- if (this.maxDateLuxon) {
217
- return lxDate1 > this.minDateLuxon && lxDate2 > this.minDateLuxon && lxDate1 < this.maxDateLuxon && lxDate2 < this.maxDateLuxon;
218
- } else {
219
- return lxDate1 > this.minDateLuxon && lxDate2 > this.minDateLuxon;
220
- }
221
- } else if (this.maxDateLuxon) {
222
- return lxDate1 < this.maxDateLuxon && lxDate2 < this.maxDateLuxon;
223
- }
224
-
225
- return true;
226
- }
227
-
228
- isSelected(date) {
229
- if (!this.valueAsLuxon || this.valueAsLuxon.length < 2) {
230
- return false;
231
- }
232
- return this.valueAsLuxon[0].hasSame(date[0], 'day') &&
233
- this.valueAsLuxon[1].hasSame(date[1], 'day');
180
+ const lxDate = DateTime.local().plus(date);
181
+ return !(this.minDateLuxon && lxDate < this.minDateLuxon);
234
182
  }
235
183
 
236
184
  selectDate(date) {
237
- const lxDate1 = date[0].set({ hours: 0, minutes: 0, seconds: 0 });
238
- const lxDate2 = date[1].set({ hours: 23, minutes: 59, seconds: 59 });
239
- this.updateValue(lxDate1.toFormat(this.displayFormat), lxDate2.toFormat(this.displayFormat));
240
- this.calendar.selectDate([lxDate1.toJSDate(), lxDate2.toJSDate()], { silent: true });
241
- }
242
-
243
- @Watch('range')
244
- onRangeChange() {
245
- if (!this.calendar) {
185
+ let lxDate = DateTime.local().plus(date);
186
+ if (this.minDateLuxon && lxDate < this.minDateLuxon) {
246
187
  return;
247
188
  }
248
- this.calendar.selectCompareRange(this.range ? [
249
- DateTime.fromFormat(this.range[0], this.valueFormat).toJSDate(),
250
- DateTime.fromFormat(this.range[1], this.valueFormat).toJSDate(),
251
- ] : []);
189
+ this.calendar.selectDate(lxDate.toJSDate());
190
+ this.updateValue(lxDate.toFormat(this.displayFormat));
252
191
  }
253
192
 
254
193
  @Watch('value')
@@ -257,13 +196,7 @@ class itfDatePickerInline extends Vue {
257
196
  this.calendar && this.calendar.clear({ silent: true });
258
197
  return;
259
198
  }
260
- const date1 = this.calendar.rangeDateFrom && DateTime.fromJSDate(this.calendar.rangeDateFrom).toFormat('yyyy-MM-dd')
261
- const date2 = this.calendar.rangeDateTo && DateTime.fromJSDate(this.calendar.rangeDateTo).toFormat('yyyy-MM-dd')
262
- if (date1 !== this.valueAsLuxon[0].toFormat('yyyy-MM-dd') || date2 !== this.valueAsLuxon[1].toFormat('yyyy-MM-dd')) {
263
- this.$nextTick(() => {
264
- this.calendar.selectDate(this.valueAsLuxon && [this.valueAsLuxon[0].toJSDate(), this.valueAsLuxon[1].toJSDate()], { silent: true });
265
- });
266
- }
199
+ this.calendar.selectDate(this.valueAsLuxon.toJSDate(), { silent: true });
267
200
  }
268
201
  }
269
202
  </script>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="itf-filter-panel d-flex flex-column align-items-start" :class="{'gap-3': !filtersOnly}">
2
+ <div class="itf-filter-panel d-flex flex-column gap-3 align-items-start">
3
3
  <div v-if="!filtersOnly" class="d-flex gap-2 justify-content-between w-100">
4
4
  <slot name="search">
5
5
  <div>
@@ -35,8 +35,8 @@
35
35
  class="itf-filter-panel__badge"
36
36
  :ref="'item-' + n"
37
37
  v-model="filter[facet.name]"
38
- :is-default="filter[facet.name] && filter[facet.name].isDefault"
39
- :text="filter[facet.name] && filter[facet.name].label"
38
+ :is-default="filter[facet.name].isDefault"
39
+ :text="filter[facet.name].label"
40
40
  :type="facet.type"
41
41
  :icon="facet.icon"
42
42
  :options="facet.options"
@@ -52,7 +52,7 @@
52
52
  </div>
53
53
  <slot name="after-filters"></slot>
54
54
  </div>
55
- <div v-if="loading && !visibleFilters.length">
55
+ <div v-if="loading">
56
56
  <span class="itf-spinner"></span>
57
57
  {{$t('loading')}}
58
58
  </div>
@@ -177,8 +177,6 @@ class FilterPanel extends Vue {
177
177
  (entries) => {
178
178
  entries.forEach(entry => {
179
179
  const index = parseInt(entry.target.dataset.index);
180
- const filter = this.filters[index];
181
- const value = this.filter[filter.name];
182
180
  if (entry.isIntersecting) {
183
181
  this.visibleItems.add(index); // Додаємо, якщо елемент у полі зору
184
182
  } else {
@@ -208,7 +206,7 @@ class FilterPanel extends Vue {
208
206
 
209
207
  get visibleFilters() {
210
208
  if (this.mini) {
211
- return sortBy(this.filters, (f) => this.filter[f.name].isDefault).filter(f => !f.options?.hidden);
209
+ return sortBy(this.filters, (f) => this.filter[f.name].isDefault).filter(f => !f.options?.hidden).slice(0, 2);
212
210
  }
213
211
  return this.filters.filter(f => !f.options?.hidden);
214
212
  }
@@ -235,7 +233,15 @@ class FilterPanel extends Vue {
235
233
 
236
234
  this.filters = this.staticFilters ?? [];
237
235
  if (this.endpoint) {
238
- this.loadData();
236
+ this.loading = true;
237
+ await this.$try(async () => {
238
+ const payload = this.panel ? this.panel.getPayload() : {};
239
+ const {filters, tableSchema} = await this.$axios.$get(this.endpoint, { params: payload });
240
+ this.filters = filters;
241
+ this.$emit('set-table-schema', tableSchema);
242
+ this.loadFiltersValue();
243
+ });
244
+ this.loading = false;
239
245
  } else {
240
246
  this.loadFiltersValue();
241
247
  }
@@ -244,23 +250,6 @@ class FilterPanel extends Vue {
244
250
  }
245
251
  }
246
252
 
247
- async loadData() {
248
- this.loading = true;
249
- await this.$try(async () => {
250
- const payload = this.panel ? this.panel.getPayload() : {};
251
- const {filters, tableSchema} = await this.$axios.$get(this.endpoint, {
252
- preventRaceCondition: true,
253
- params: payload
254
- });
255
- this.filters = filters;
256
- this.loading = false;
257
- this.$emit('set-table-schema', tableSchema);
258
- this.loadFiltersValue();
259
- }, () => {
260
- this.loading = false;
261
- });
262
- }
263
-
264
253
  toggleFilters() {
265
254
  this.showFilters = !this.showFilters;
266
255
  if (this.stateName) {
@@ -280,7 +269,7 @@ class FilterPanel extends Vue {
280
269
  filterValue.to = payload.to;
281
270
  } else {
282
271
  filter[item.name] = payload[item.name] ? this.formatValue(item, { value: payload[item.name] }) : { isDefault: true, ...item.options.defaultValue };
283
- filterValue[item.name] = payload[item.name];
272
+ filterValue[item.name] = payload[item.name] ?? filter[item.name].value;
284
273
  }
285
274
  }
286
275
  }
@@ -293,7 +282,6 @@ class FilterPanel extends Vue {
293
282
  this.filter = filter;
294
283
  this.filterValue = filterValue;
295
284
  this.$emit('input', this.filterValue);
296
- this.$emit('loaded', this.filterValue);
297
285
  this.initObserver();
298
286
  }
299
287
  }
@@ -330,7 +318,6 @@ class FilterPanel extends Vue {
330
318
  this.panel.setPayload({ ...payload, ...this.filterValue });
331
319
  }
332
320
  this.$emit('input', this.filterValue);
333
- this.$emit('change', this.filterValue);
334
321
  }
335
322
 
336
323
  get daysList() {
@@ -376,11 +363,7 @@ class FilterPanel extends Vue {
376
363
  }
377
364
  } else if (facet.type === 'date') {
378
365
  const date = DateTime.fromISO(value.value);
379
- value.label = (date.isValid ? date : DateTime.fromISO(facet.options.defaultValue.value ?? DateTime.now().toISO())).toFormat('dd MMM yyyy');
380
- value.isDefault = facet.options.defaultValue ? value.value === facet.options.defaultValue.value : false;
381
- } else if (facet.type === 'month') {
382
- const date = DateTime.fromISO(value.value);
383
- value.label = capitalizeFirstLetter((date.isValid ? date : DateTime.fromISO(facet.options.defaultValue.value)).toFormat('LLLL yyyy'));
366
+ value.label = (date.isValid ? value.value : DateTime.fromISO(facet.options.defaultValue.value ?? DateTime.now())).toFormat('dd MMM yyyy');
384
367
  value.isDefault = facet.options.defaultValue ? value.value === facet.options.defaultValue.value : false;
385
368
  } else if (facet.type === 'facets-list') {
386
369
  const firstItem = facet.options.items.find(item => item.value === (Array.isArray(value.value) ? value.value[0] : value.value));
@@ -419,10 +402,6 @@ class FilterPanel extends Vue {
419
402
  }
420
403
  value.hidden = facet.options?.hidden ?? false;
421
404
  return value;
422
-
423
- function capitalizeFirstLetter(string) {
424
- return string.charAt(0).toUpperCase() + string.slice(1);
425
- }
426
405
  }
427
406
  }
428
407
  </script>
@@ -0,0 +1,5 @@
1
+ <template><svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M10 14L9 14C7.11438 14 6.17157 14 5.58579 13.4142C5 12.8284 5 11.8856 5 10L5 9C5 7.11438 5 6.17157 5.58579 5.58579C6.17157 5 7.11438 5 9 5L10 5C11.8856 5 12.8284 5 13.4142 5.58579C14 6.17157 14 7.11438 14 9L14 10C14 11.8856 14 12.8284 13.4142 13.4142C12.8284 14 11.8856 14 10 14Z" fill="currentColor"/>
3
+ <path d="M9.40039 14C9.40039 13.0742 9.39868 12.3195 9.47852 11.7256C9.56068 11.1144 9.73818 10.5841 10.1611 10.1611C10.5841 9.73818 11.1144 9.56068 11.7256 9.47852C12.3195 9.39868 13.0742 9.40039 14 9.40039L15 9.40039C15.9258 9.40039 16.6805 9.39868 17.2744 9.47852C17.8856 9.56068 18.4159 9.73818 18.8389 10.1611C19.2618 10.5841 19.4393 11.1144 19.5215 11.7256C19.6013 12.3195 19.5996 13.0742 19.5996 14L19.5996 15C19.5996 15.9258 19.6013 16.6805 19.5215 17.2744C19.4393 17.8856 19.2618 18.4159 18.8389 18.8389C18.4159 19.2618 17.8856 19.4393 17.2744 19.5215C16.6805 19.6013 15.9258 19.5996 15 19.5996L14 19.5996C13.0742 19.5996 12.3195 19.6013 11.7256 19.5215C11.1144 19.4393 10.5841 19.2618 10.1611 18.8389C9.73818 18.4159 9.56068 17.8856 9.47852 17.2744C9.39868 16.6805 9.40039 15.9258 9.40039 15L9.40039 14Z" fill="currentColor" stroke="white" stroke-width="1.2"/>
4
+ </svg>
5
+ </template>
@@ -1,19 +1,19 @@
1
1
  <template>
2
- <div v-loading="loading" class="px-3 pt-2 h-100">
2
+ <div v-loading="loading" class="px-3 pt-2 h-100 d-flex flex-column">
3
3
  <itf-form
4
4
  ref="editForm"
5
- class="d-flex flex-column justify-content-between h-100"
5
+ class="d-flex flex-column justify-content-between flex-grow-1"
6
6
  @keydown.native.shift.enter.stop.prevent="onSaveClick"
7
7
  @keydown.native.esc.stop.prevent="$emit('cancel')"
8
8
  >
9
9
  <slot></slot>
10
10
  <div class="py-3 justify-content-end d-flex align-items-center sticky-container">
11
11
  <div v-if="!hideFooter">
12
- <itf-button v-tooltip.delay="'Hot key: Esc'" secondary :loading="loading" :disabled="loading" @click="$emit('cancel')">
13
- <span>{{ $t('components.modal.cancel') }}</span>
12
+ <itf-button v-tooltip.delay="'Hot key: Esc'" secondary squircle :loading="loading" :disabled="loading" @click="$emit('cancel')">
13
+ <span>{{ cancelBtnText }}</span>
14
14
  </itf-button>
15
- <itf-button v-tooltip.delay="'Hot key: Shift + Enter'" primary :loading="loading" :disabled="loading" @click="onSaveClick">
16
- <span>{{ $t('components.modal.save') }}</span>
15
+ <itf-button v-tooltip.delay="'Hot key: Shift + Enter'" primary squircle :loading="loading" :disabled="loading" @click="onSaveClick">
16
+ <span>{{ saveBtnText }}</span>
17
17
  </itf-button>
18
18
  </div>
19
19
  </div>
@@ -51,6 +51,8 @@ import itfButton from '../button/Button.vue';
51
51
  export default class PanelItemEdit extends Vue {
52
52
  @Prop(Boolean) loading;
53
53
  @Prop(Boolean) hideFooter;
54
+ @Prop({ type: String, default: function() { return this.$t('components.modal.save') } }) saveBtnText;
55
+ @Prop({ type: String, default: function() { return this.$t('components.modal.cancel') } }) cancelBtnText;
54
56
 
55
57
  onSaveClick() {
56
58
  if (this.$refs.editForm && !this.$refs.editForm.doValidation()) {
@@ -5,6 +5,7 @@
5
5
  import { Vue, Component, Inject, Prop } from 'vue-property-decorator';
6
6
  import { IPanel } from './PanelList.vue';
7
7
  import {stackToHash} from "@itfin/components/src/components/panels/helpers";
8
+ import {getRootPanelList} from "@itfin/components/src/components/panels";
8
9
 
9
10
  @Component({
10
11
  components: {
@@ -15,7 +16,6 @@ import {stackToHash} from "@itfin/components/src/components/panels/helpers";
15
16
  }
16
17
  })
17
18
  export default class PanelLink extends Vue {
18
- @Inject({ default: null }) panelList;
19
19
  @Inject({ default: null }) currentPanel;
20
20
 
21
21
  @Prop(Boolean) global: boolean;
@@ -25,6 +25,7 @@ export default class PanelLink extends Vue {
25
25
  @Prop() list;
26
26
  @Prop({ type: String, default: 'active' }) activeClass: string;
27
27
  @Prop(Boolean) append: boolean;
28
+ @Prop(Boolean) replace: boolean;
28
29
 
29
30
  get on() {
30
31
  const handlers = {};
@@ -35,7 +36,7 @@ export default class PanelLink extends Vue {
35
36
  }
36
37
 
37
38
  get activeList() {
38
- return this.list ?? this.panelList;
39
+ return this.list ?? getRootPanelList();
39
40
  }
40
41
 
41
42
  get isActive() {
@@ -51,6 +52,9 @@ export default class PanelLink extends Vue {
51
52
  if (!this.append) {
52
53
  stack = stack.splice(0, this.currentPanel?.index + 1);
53
54
  }
55
+ if (this.replace) {
56
+ stack = [];
57
+ }
54
58
  const hash = stackToHash([
55
59
  ...stack,
56
60
  {
@@ -62,13 +66,18 @@ export default class PanelLink extends Vue {
62
66
  }
63
67
 
64
68
  onClick(e) {
65
- console.info(this.activeList);
69
+ e.preventDefault();
70
+ e.stopPropagation();
71
+ const index = this.replace ? 0 : (this.append ? undefined : this.currentPanel?.index + 1);
72
+ this.$emit('open', {
73
+ panel: this.panel,
74
+ payload: this.payload || {},
75
+ index
76
+ });
66
77
  if (!this.activeList) {
67
78
  return;
68
79
  }
69
- e.preventDefault();
70
- e.stopPropagation();
71
- this.activeList.openPanel(this.panel, this.payload || {}, this.append ? undefined : this.currentPanel?.index + 1);
80
+ this.activeList.openPanel(this.panel, this.payload || {}, index);
72
81
  }
73
82
  }
74
83
  </script>
@@ -218,7 +218,6 @@ export default class PanelList extends Vue {
218
218
  @Prop() panels: Record<string, Component>;
219
219
  @Prop({ default: () => {} }) searchPanel: (type: string) => boolean;
220
220
  @Prop({ type: String, default: 'path' }) routeType: string;
221
- @Prop({ type: String, default: '' }) routePrefix: string;
222
221
 
223
222
  panelsStack:IPanel[] = [];
224
223
 
@@ -229,7 +228,6 @@ export default class PanelList extends Vue {
229
228
  if (this.firstPanel) {
230
229
  this.internalOpenPanel(this.firstPanel.type, this.firstPanel.payload);
231
230
  }
232
- console.info('created');
233
231
  this.parsePanelHash(); // щоб панелі змінювались при перезавантаженні
234
232
  window.addEventListener('popstate', this.handlePopState); // щоб панелі змінювались при навігації
235
233
  }
@@ -468,9 +466,9 @@ export default class PanelList extends Vue {
468
466
  }
469
467
 
470
468
  setPanelHash() {
471
- const hash = stackToHash(this.panelsStack, this.routePrefix).replace(/^#/, '');
469
+ const hash = stackToHash(this.panelsStack, this.routeType === 'path').replace(/^#/, '');
472
470
  if (this.routeType === 'path') {
473
- this.$router.push({ path: hash });
471
+ this.$router.push({ path: `/${hash}` });
474
472
  } else {
475
473
  this.$router.push({ hash });
476
474
  }
@@ -480,7 +478,7 @@ export default class PanelList extends Vue {
480
478
  async parsePanelHash() {
481
479
  const hash = this.routeType === 'path' ? location.pathname : location.hash;
482
480
  if (hash) {
483
- const panels = hashToStack(hash, this.routePrefix);
481
+ const panels = hashToStack(hash, this.routeType === 'path');
484
482
  const newStack = [];
485
483
  this.panelsStack = [];
486
484
  for (const panelIndex in panels) {
@@ -500,14 +498,12 @@ export default class PanelList extends Vue {
500
498
  }
501
499
  }
502
500
  this.panelsStack = newStack;
503
- console.info('set', newStack);
504
501
  this.emitEvent('panels.changed', this.panelsStack);
505
502
  this.updateTitle();
506
503
  }
507
504
  }
508
505
 
509
506
  handlePopState() {
510
- console.info('handlePopState')
511
507
  this.parsePanelHash();
512
508
  // виправляє проблему відкритої панелі при натисканні кнопки "назад" до першої панелі
513
509
  if (this.panelsStack.length === 2) {