@itfin/components 1.3.91 → 1.3.94

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.3.91",
3
+ "version": "1.3.94",
4
4
  "author": "Vitalii Savchuk <esvit666@gmail.com>",
5
5
  "scripts": {
6
6
  "serve": "vue-cli-service serve",
@@ -31,6 +31,7 @@
31
31
  justify-content: space-between;
32
32
  align-items: end;
33
33
  gap: .25rem;
34
+ border-bottom: 1px solid #AAA;
34
35
 
35
36
  & > div {
36
37
  background: var(--bs-primary);
@@ -81,11 +81,12 @@ class FilterPanel extends Vue {
81
81
  const filter = {};
82
82
  const filterValue = {};
83
83
  for (const item of this.filters) {
84
- filter[item.name] = payload[item.name] ? this.formatValue(item, { value: payload[item.name] }) : { isDefault: true, ...item.options.defaultValue };
85
84
  if (item.type === 'period') {
85
+ filter[item.name] = payload.from ? this.formatValue(item, { value: [payload.from, payload.to] }) : { isDefault: true, ...item.options.defaultValue };
86
86
  filterValue.from = payload.from;
87
87
  filterValue.to = payload.to;
88
88
  } else {
89
+ filter[item.name] = payload[item.name] ? this.formatValue(item, { value: payload[item.name] }) : { isDefault: true, ...item.options.defaultValue };
89
90
  filterValue[item.name] = payload[item.name];
90
91
  }
91
92
  }
@@ -97,6 +98,14 @@ class FilterPanel extends Vue {
97
98
  this.$emit('input', this.filterValue);
98
99
  }
99
100
 
101
+ setFilter(field, value) {
102
+ const facet = this.filters.find(facet => facet.name === field);
103
+ if (!facet) {
104
+ throw new Error(`Facet ${field} not found`);
105
+ }
106
+ this.onFilterChange(facet, { value });
107
+ }
108
+
100
109
  onFilterChange(facet, value) {
101
110
  this.filter[facet.name] = this.formatValue(facet, value);
102
111
  if (facet.type === 'period') {
@@ -128,11 +137,11 @@ class FilterPanel extends Vue {
128
137
  formatValue(facet, value) {
129
138
  if (facet.type === 'period') {
130
139
  if (value.value) {
131
- let from = DateTime.fromISO(value.value[0]);
132
- let to = DateTime.fromISO(value.value[1]);
140
+ let from = DateTime.fromFormat(value.value[0], 'yyyy-MM-dd');
141
+ let to = DateTime.fromFormat(value.value[1], 'yyyy-MM-dd');
133
142
  if (!from.isValid || !to.isValid) {
134
- from = DateTime.fromISO(facet.options.defaultValue.value[0]);
135
- to = DateTime.fromISO(facet.options.defaultValue.value[1]);
143
+ from = DateTime.fromFormat(facet.options.defaultValue.value[0], 'yyyy-MM-dd');
144
+ to = DateTime.fromFormat(facet.options.defaultValue.value[1], 'yyyy-MM-dd');
136
145
  }
137
146
  const namedItem = this.daysList.find(item => {
138
147
  const [start, end] = item.date();
@@ -141,7 +150,7 @@ class FilterPanel extends Vue {
141
150
  if (namedItem) {
142
151
  value.label = namedItem.title;
143
152
  } else {
144
- value.label = formatRangeDates(from, to);
153
+ value.label = formatRangeDates(from.toJSDate(), to.toJSDate());
145
154
  }
146
155
  }
147
156
  if (facet.options.defaultValue?.value) {
@@ -0,0 +1,48 @@
1
+ <template>
2
+ <div class="notice p-2" ref="notice">
3
+ <slot></slot>
4
+ </div>
5
+ </template>
6
+ <style scoped lang="scss">
7
+ .notice {
8
+ position: absolute;
9
+ bottom: 10px;
10
+ left: 50%;
11
+ max-width: 740px;
12
+ z-index: 10;
13
+ opacity: 0;
14
+ transition: all 0.3s ease;
15
+ transform: translateX(-50%) translateY(100%) translateZ(0px);
16
+ background: var(--bs-body-bg);
17
+ box-shadow: 0 2px 10px 0 hsl(0 calc(1 * 0%) 0% /.1);
18
+ overflow: hidden;
19
+ border-radius: 5px;
20
+ pointer-events: none;
21
+
22
+ &.show {
23
+ opacity: 1;
24
+ pointer-events: all;
25
+ transform: translate3d(-50%, 0%, 0px);
26
+ }
27
+ }
28
+ </style>
29
+ <script>
30
+ import { Vue, Prop, Watch, Component } from 'vue-property-decorator';
31
+
32
+ export default @Component({
33
+ components: {
34
+ }
35
+ })
36
+ class NoticePopout extends Vue {
37
+ @Prop(Boolean) visible;
38
+
39
+ @Watch('visible')
40
+ toggle(show = true) {
41
+ if (show) {
42
+ this.$refs.notice.classList.add('show');
43
+ } else {
44
+ this.$refs.notice.classList.remove('show');
45
+ }
46
+ }
47
+ }
48
+ </script>
@@ -1,11 +1,20 @@
1
1
  <template>
2
2
 
3
- <div class="itf-table2 scrollable scrollable-x" :class="{
3
+ <div class="itf-table2" :class="{
4
4
  'table-striped': striped,
5
5
  'table-absolute': absolute,
6
6
  'table-clickable': clickable,
7
7
  'permanent-checkboxes': selectedIds.length
8
8
  }" :style="{ '--indicator-area-width': `${indicatorType === 'none' ? 1 : indicatorWidth}px` }">
9
+ <itf-notice-popout :visible="showGroupOperations" class="rounded-pill bg-dark text-light">
10
+ <div class="d-flex gap-3 px-3 align-items-center">
11
+ <div><strong>{{selectedIds.length}}</strong> selected</div>
12
+ <div>
13
+ <slot name="group-operations"></slot>
14
+ </div>
15
+ </div>
16
+ </itf-notice-popout>
17
+ <div class="scrollable scrollable-x">
9
18
  <itf-checkbox-group v-model="selectedIds">
10
19
  <template v-for="(group, index) in groups">
11
20
  <div class="table-view-body">
@@ -58,6 +67,7 @@
58
67
  </div>
59
68
  </template>
60
69
  </itf-checkbox-group>
70
+ </div>
61
71
  </div>
62
72
 
63
73
  </template>
@@ -68,6 +78,7 @@ import itfButton from '../button/Button.vue';
68
78
  import itfIcon from '../icon/Icon.vue';
69
79
  import itfTableGroup from './TableGroup.vue';
70
80
  import itfTableHeader from './TableHeader.vue';
81
+ import itfNoticePopout from '../popover/NoticePopout.vue';
71
82
  import './table2.scss';
72
83
 
73
84
  export default @Component({
@@ -80,7 +91,8 @@ export default @Component({
80
91
  itfTableHeader,
81
92
  itfButton,
82
93
  itfIcon,
83
- itfTableGroup
94
+ itfTableGroup,
95
+ itfNoticePopout
84
96
  }
85
97
  })
86
98
  class itfTable2 extends Vue {
@@ -97,7 +109,7 @@ class itfTable2 extends Vue {
97
109
  @Prop({ type: String, default: null }) stateName; // save state to storage
98
110
  @Prop({ type: Object, default: () => ({}) }) schema;
99
111
  @ModelSync('value', 'input', { type: Array, default: () => ([]) }) selectedIds;
100
- @PropSync('sorting', { type: Object, default: () => ({}) }) _sorting;
112
+ @PropSync('sorting') _sorting;
101
113
  @Prop({ type: Array, default: () => [] }) expandedIds;
102
114
  @Prop() currency;
103
115
  @Prop() currencies;
@@ -123,6 +135,10 @@ class itfTable2 extends Vue {
123
135
  columns: []
124
136
  };
125
137
 
138
+ get showGroupOperations() {
139
+ return !!this.$slots['group-operations'] && this.selectedIds.length > 0;
140
+ }
141
+
126
142
  getTableState() {
127
143
  const list = this.schema?.properties || [];
128
144
  let state = this.stateName ? JSON.parse(localStorage.getItem(this.stateKey) || 'null') : null;
@@ -163,6 +179,7 @@ class itfTable2 extends Vue {
163
179
  }
164
180
 
165
181
  mounted() {
182
+ console.info(this.$scopedSlots)
166
183
  this.onSchemaUpdate();
167
184
  }
168
185
 
@@ -114,6 +114,7 @@
114
114
  }
115
115
 
116
116
  .on-hover {
117
+ opacity: 1;
117
118
  display: block;
118
119
  }
119
120
  }
@@ -38,6 +38,7 @@
38
38
  :column-sorting="columnSorting"
39
39
  :show-add-column="showAddColumn"
40
40
  :show-actions="showActions"
41
+ :id-property="idProperty"
41
42
  :rows="rows"
42
43
  :schema="schema"
43
44
  :editable="editable"
@@ -362,7 +363,7 @@ class itfTableGroup extends Vue {
362
363
  @Prop(Boolean) stickyHeader;
363
364
  @Prop() indicatorWidth;
364
365
  @Prop() cssProperty;
365
- @PropSync('sorting', { type: Object, default: () => ({}) }) _sorting;
366
+ @PropSync('sorting') _sorting;
366
367
  @Prop({ type: String, default: null }) indicatorType;
367
368
  @Prop({type: String, default: function() { return this.$t('components.table.new'); } }) newLabel;
368
369
  @Prop({type: Object, default: () => ({})}) schema;
@@ -38,7 +38,7 @@
38
38
  {{getTitle(column.title)}}
39
39
  <div v-if="column.prefix" class="itf-table2__subtitle" v-text="column.prefix" />
40
40
  </span>
41
- <itf-icon v-if="_sorting[column.property]" :name="_sorting[column.property] === 'asc' ? 'arrow_up' : 'arrow_down'" :size="16" class="ms-1" />
41
+ <itf-icon v-if="sortColumnParams[column.property]" :name="sortColumnParams[column.property] === 'asc' ? 'arrow_up' : 'arrow_down'" :size="16" class="ms-1" />
42
42
  </template>
43
43
 
44
44
  <div v-if="column.sortable">
@@ -174,6 +174,7 @@ class itfTableHeader extends Vue {
174
174
  @Prop(Boolean) noColumnMenu;
175
175
  @Prop(Boolean) noSelectAll;
176
176
  @Prop(Boolean) editable;
177
+ @Prop() idProperty;
177
178
  @Prop() indicatorType;
178
179
 
179
180
  @Watch('selectedIds')
@@ -186,6 +187,14 @@ class itfTableHeader extends Vue {
186
187
  }
187
188
  }
188
189
 
190
+ get sortColumnParams() {
191
+ if (typeof this._sorting === 'string'){
192
+ return this._sorting[0] === '-' ? {[this._sorting.substring(1)]: 'desc'} : {[this._sorting]: 'asc'};
193
+ } else {
194
+ return this._sorting;
195
+ }
196
+ }
197
+
189
198
  getTitle(title) {
190
199
  if (typeof title === 'string') {
191
200
  return title;
@@ -203,7 +212,7 @@ class itfTableHeader extends Vue {
203
212
 
204
213
  set selectAll(val) {
205
214
  if (val) {
206
- this.$emit('update:selectedIds', this.rows.map(r => r.Id));
215
+ this.$emit('update:selectedIds', this.rows.map(r => r[this.idProperty]));
207
216
  } else {
208
217
  this.$emit('update:selectedIds', []);
209
218
  }
@@ -350,8 +359,9 @@ class itfTableHeader extends Vue {
350
359
  }
351
360
 
352
361
  sortBy(column, order) {
353
- console.info({ [column.property]: order });
354
- this.$emit('update:sorting', { [column.property]: order });
362
+ let sort = order === 'desc' ? `-${column.property}` : column.property;
363
+ console.info(sort);
364
+ this.$emit('update:sorting', sort);
355
365
  }
356
366
  }
357
367
  </script>
@@ -21,7 +21,7 @@
21
21
  </div>
22
22
  </div>
23
23
  <div v-if="indicatorType !== 'none'" class="indicator sticky">
24
- <div class="fill table-view-row-count" :class="{'on-rest': !noSelectAll}">
24
+ <div class="fill table-view-row-count" :class="{'on-rest': indicatorType !== 'checkbox'}">
25
25
  <span v-if="indicatorType === 'order'">{{ (n + 1) }}</span>
26
26
  <span v-else-if="indicatorType === 'property'">{{ item[idProperty] }}</span>
27
27
  <a href="" @click.prevent.stop="$emit('toggle', item)" v-else-if="indicatorType === 'toggle'">
@@ -41,7 +41,7 @@
41
41
  </template>
42
42
  </a>
43
43
  </div>
44
- <div v-if="!noSelectAll" class="fill on-hover">
44
+ <div class="fill" :class="{'on-hover': indicatorType !== 'checkbox'}">
45
45
  <itf-checkbox :value="item[idProperty]" />
46
46
  </div>
47
47
  </div>
@@ -365,4 +365,19 @@ body[data-theme="dark"] {
365
365
  bottom: 0;
366
366
  }
367
367
  }
368
+
369
+ .table-group-operations {
370
+ background: red;
371
+ position: absolute;
372
+ bottom: 1rem;
373
+ left: 1rem;
374
+ right: 1rem;
375
+ height: 2rem;
376
+ transform: rotateX(90deg);
377
+ transition: transform 0.3s;
378
+
379
+ &.visible {
380
+ transform: rotateX(0deg);
381
+ }
382
+ }
368
383
  }
@@ -67,7 +67,7 @@
67
67
 
68
68
  select {
69
69
  position: absolute;
70
- top: 0;
70
+ height: 100%;
71
71
  right: 0;
72
72
  opacity: 0;
73
73
  left: 0;
@@ -8,7 +8,7 @@ const LINKED_IN_REGEXP = /(http(s)?:\/\/)?([\w]+\.)?linkedin\.com\/(pub|in|profi
8
8
  const SPECIAL_CHARS_REGEXP = /^[\w_\-+~,/\\:'"().&*|[\]?# ]+$/i;
9
9
  const GREETINGS_REGEXP = /\b(dr|mr|mister|mrs|ms|miss|sir|hello|hi)\b/i;
10
10
  const PHONE_REGEXP = /(\+?\(?\+?[0-9]{1,3}\)?[-. ]+([0-9]{2,4})[-. ]?([0-9]{3,5}))|\+?[0-9]{7,}/gi;
11
- const DOUBLE_REGEXP = /^-?\d{0,11}(\.\d{0,4}){0,1}$/;
11
+ const DOUBLE_REGEXP = /^-?\d{0,11}(\.\d{0,8}){0,1}$/;
12
12
  const EMAIL_REGEXP = /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/g;
13
13
  const EMAIL_LIST_REGEXP = /^(\w+((-\w+)|(\.\w+))*@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]{2,4}\s*?,?\s*?)+$/g;
14
14
  const HEX_REGEXP = /[0-9A-Fa-f]{6}/;