@neovici/cosmoz-omnitable 7.2.0 → 8.0.0-beta.10

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.
Files changed (52) hide show
  1. package/cosmoz-omnitable-column-amount.js +89 -320
  2. package/cosmoz-omnitable-column-autocomplete.js +38 -47
  3. package/cosmoz-omnitable-column-boolean.js +107 -209
  4. package/cosmoz-omnitable-column-date.js +89 -102
  5. package/cosmoz-omnitable-column-datetime.js +86 -119
  6. package/cosmoz-omnitable-column-list-data.js +4 -1
  7. package/cosmoz-omnitable-column-list-horizontal.js +20 -38
  8. package/cosmoz-omnitable-column-list-mixin.js +135 -140
  9. package/cosmoz-omnitable-column-list.js +19 -28
  10. package/cosmoz-omnitable-column-mixin.js +70 -447
  11. package/cosmoz-omnitable-column-number.js +91 -183
  12. package/cosmoz-omnitable-column-time.js +77 -162
  13. package/cosmoz-omnitable-column.js +49 -93
  14. package/cosmoz-omnitable-group-row.js +1 -5
  15. package/cosmoz-omnitable-header-row.js +9 -6
  16. package/cosmoz-omnitable-item-expand.js +0 -3
  17. package/cosmoz-omnitable-item-row.js +5 -8
  18. package/cosmoz-omnitable-styles.js +4 -8
  19. package/cosmoz-omnitable.js +74 -771
  20. package/lib/cosmoz-omnitable-amount-range-input.js +295 -0
  21. package/{cosmoz-omnitable-column-date-mixin.js → lib/cosmoz-omnitable-date-input-mixin.js} +4 -26
  22. package/lib/cosmoz-omnitable-date-range-input.js +81 -0
  23. package/lib/cosmoz-omnitable-datetime-range-input.js +75 -0
  24. package/lib/cosmoz-omnitable-number-range-input.js +159 -0
  25. package/{cosmoz-omnitable-column-range-mixin.js → lib/cosmoz-omnitable-range-input-mixin.js} +45 -129
  26. package/lib/cosmoz-omnitable-settings.js +8 -5
  27. package/lib/cosmoz-omnitable-time-range-input.js +130 -0
  28. package/lib/generic-sorter.js +2 -2
  29. package/lib/invoke.js +1 -0
  30. package/lib/memoize.js +54 -0
  31. package/lib/normalize-settings.js +2 -5
  32. package/lib/polymer-haunted-render-mixin.js +19 -0
  33. package/lib/save-as-csv-action.js +32 -0
  34. package/lib/save-as-xlsx-action.js +25 -0
  35. package/lib/use-canvas-width.js +1 -1
  36. package/lib/use-dom-columns.js +143 -0
  37. package/lib/use-fast-layout.js +23 -18
  38. package/lib/use-hash-state.js +59 -0
  39. package/lib/use-layout.js +1 -1
  40. package/lib/use-omnitable.js +81 -22
  41. package/lib/use-processed-items.js +133 -0
  42. package/lib/use-resizable-columns.js +1 -2
  43. package/lib/use-sort-and-group-options.js +30 -0
  44. package/lib/utils-amount.js +147 -0
  45. package/lib/utils-data.js +41 -0
  46. package/lib/utils-date.js +204 -0
  47. package/lib/utils-datetime.js +71 -0
  48. package/lib/utils-number.js +112 -0
  49. package/lib/utils-time.js +115 -0
  50. package/package.json +1 -2
  51. package/lib/use-force-render.js +0 -8
  52. package/lib/use-render-on-column-updates.js +0 -18
@@ -7,255 +7,153 @@ import '@neovici/cosmoz-autocomplete';
7
7
  import {
8
8
  html, nothing
9
9
  } from 'lit-html';
10
+ import { get } from '@polymer/polymer/lib/utils/path';
11
+ import { memooize } from './lib/memoize';
10
12
 
11
- /**
12
- * @polymer
13
- * @customElement
14
- * @appliesMixin columnMixin
15
- */
16
- class OmnitableColumnBoolean extends columnMixin(PolymerElement) {
17
- static get is() {
18
- return 'cosmoz-omnitable-column-boolean';
19
- }
13
+ const
14
+ computeValue = (value, source) =>
15
+ source.find(({ value: valueProp }) => value === valueProp),
20
16
 
21
- static get properties() {
22
- // eslint-disable-line max-lines-per-function
23
- return {
24
- filter: {
25
- type: Boolean,
26
- notify: true,
27
- value: undefined
28
- },
17
+ computeTooltip = (title, value, source) => {
18
+ const val = computeValue(value, source);
19
+ return val ? val.text : title;
20
+ },
21
+
22
+ computeItemTooltip = (title, item, valuePath, source) => computeTooltip(
23
+ title,
24
+ get(item, valuePath),
25
+ source
26
+ ),
27
+
28
+ computeItemValue = ({ valuePath }, item, source) => computeValue(
29
+ get(item, valuePath),
30
+ source
31
+ ),
32
+
33
+ onChange = setState => selection => {
34
+ setState(state => ({ ...state, filter: selection?.[0]?.value ?? null }));
35
+ },
29
36
 
30
- trueLabel: {
31
- type: String,
32
- value: 'True'
33
- },
37
+ onFocus = setState => focused => {
38
+ setState(state => ({ ...state, headerFocused: focused }));
39
+ },
34
40
 
35
- falseLabel: {
36
- type: String,
37
- value: 'False'
38
- },
41
+ onText = setState => text => {
42
+ setState(state => ({ ...state, query: text }));
43
+ },
39
44
 
40
- _source: {
41
- type: Array,
42
- computed: '_computeSource(trueLabel, falseLabel)'
43
- },
45
+ onEditableChange = onItemChange => selection => onItemChange(selection?.[0]?.value),
44
46
 
45
- templatetemplateWidth: {
46
- type: String,
47
- value: '60px'
48
- },
47
+ getString = ({ valuePath, trueLabel, falseLabel }, item) => {
48
+ const value = get(item, valuePath);
49
+ return value ? trueLabel : falseLabel;
50
+ },
49
51
 
50
- /**
51
- * No need to grow, as the values in a boolean column should have known fixed width
52
- * @returns {String} Default flex
53
- */
54
- flex: {
55
- type: String,
56
- value: '0'
57
- },
58
- /**
59
- * The value of the `cosmoz-autocomplete` input.
60
- */
61
- query: {
62
- type: String,
63
- notify: true
64
- },
52
+ applySingleFilter = ({ valuePath }, filter) => item => get(item, valuePath) === filter,
65
53
 
66
- cellClass: {
67
- type: String,
68
- value: 'boolean-cell'
69
- },
54
+ computeSource = memooize((trueLabel, falseLabel) => [
55
+ { text: trueLabel, value: true },
56
+ { text: falseLabel, value: false }
57
+ ]),
58
+
59
+ toXlsxValue = ({ valuePath, trueLabel, falseLabel }, item) => {
60
+ if (!valuePath) {
61
+ return '';
62
+ }
63
+ return get(item, valuePath) ? trueLabel : falseLabel;
64
+ },
70
65
 
71
- _textProperty: {
72
- value: 'text'
73
- },
66
+ deserializeFilter = (column, filter) => {
67
+ try {
68
+ return JSON.parse(filter);
69
+ } catch (e) {
70
+ return null;
71
+ }
72
+ };
74
73
 
75
- _limit: { value: 1 }
74
+ /**
75
+ * @polymer
76
+ * @customElement
77
+ * @appliesMixin columnMixin
78
+ */
79
+ class OmnitableColumnBoolean extends columnMixin(PolymerElement) {
80
+ static get properties() {
81
+ return {
82
+ trueLabel: { type: String, value: 'True' },
83
+ falseLabel: { type: String, value: 'False' },
84
+ flex: { type: String, value: '0' },
85
+ cellClass: { type: String, value: 'boolean-cell' }
76
86
  };
77
87
  }
78
88
 
79
- constructor() {
80
- super();
81
- this._onFocus = this._onFocus.bind(this);
82
- this._onChange = this._onChange.bind(this);
83
- this._onText = this._onText.bind(this);
89
+ getString(column, item) {
90
+ return getString(column, item);
84
91
  }
85
92
 
86
93
  renderCell(column, { item }) {
87
- return column.getString(item, column.valuePath);
94
+ return getString(column, item);
88
95
  }
89
96
 
90
- renderEditCell(column, { item }) {
91
- const spinner = column.loading
92
- ? html`<paper-spinner-lite style="width: 20px; height: 20px;" suffix slot="suffix" active></paper-spinner-lite>`
93
- : nothing;
97
+ cellTitleFn(column, item) {
98
+ return getString(column, item);
99
+ }
100
+
101
+ renderEditCell(column, { item }, onItemChange) {
102
+ const
103
+ { trueLabel, falseLabel } = column,
104
+ spinner = column.loading
105
+ ? html`<paper-spinner-lite style="width: 20px; height: 20px;" suffix slot="suffix" active></paper-spinner-lite>`
106
+ : nothing;
94
107
 
95
108
  return html`<cosmoz-autocomplete
96
109
  no-label-float
97
- .title=${ column._computeItemTooltip(column.title, item, column.valuePath) }
98
- .source=${ column._source }
99
- .textProperty=${ column._textProperty }
100
- .value=${ column._computeItemValue(item, column.valuePath, column._source) }
101
- .onChange=${ column._computeItemChange(item, column.valuePath) }
102
- .limit=${ column._limit }
110
+ .title=${ computeItemTooltip(column.title, item, column.valuePath, computeSource(trueLabel, falseLabel)) }
111
+ .source=${ computeSource(trueLabel, falseLabel) }
112
+ .textProperty=${ 'text' }
113
+ .value=${ computeItemValue(column, item, computeSource(trueLabel, falseLabel)) }
114
+ .onChange=${ onEditableChange(onItemChange) }
115
+ .limit=${ 1 }
103
116
  >${ spinner }</cosmoz-autocomplete-ui>`;
104
117
  }
105
118
 
106
- renderHeader(column) {
119
+ renderHeader(column, { filter, query }, setState, source) {
107
120
  const spinner = column.loading
108
121
  ? html`<paper-spinner-lite style="width: 20px; height: 20px;" suffix slot="suffix" active></paper-spinner-lite>`
109
122
  : nothing;
110
123
 
111
124
  return html`<cosmoz-autocomplete-ui
112
125
  .label=${ column.title }
113
- .title=${ column._computeItemTooltip(column.title, column.filter) }
114
- .source=${ column._source }
115
- .textProperty=${ column._textProperty }
116
- .value=${ column._computeValue(column.filter, column._source) }
117
- .text=${ column.query }
118
- .onChange=${ column._onChange }
119
- .onFocus=${ column._onFocus }
120
- .onText=${ column._onText }
121
- .limit=${ column._limit }
126
+ .title=${ computeItemTooltip(column.title, filter, column.valuePath, source) }
127
+ .source=${ source }
128
+ .textProperty=${ 'text' }
129
+ .value=${ computeValue(filter, source) }
130
+ .text=${ query }
131
+ .onChange=${ onChange(setState) }
132
+ .onFocus=${ onFocus(setState) }
133
+ .onText=${ onText(setState) }
134
+ .limit=${ 1 }
122
135
  >${ spinner }</cosmoz-autocomplete-ui>`;
123
136
  }
124
137
 
125
- /**
126
- * Get column represented as a string.
127
- * @param {object} item Column data.
128
- * @param {string} valuePath Value path in column data.
129
- * @returns {void|string} Column in string format.
130
- */
131
- getString(item, valuePath) {
132
- const value = this.get(valuePath || this.valuePath, item);
133
- return value ? this.trueLabel : this.falseLabel;
138
+ computeSource({ trueLabel, falseLabel }) {
139
+ return computeSource(trueLabel, falseLabel);
134
140
  }
135
141
 
136
- /**
137
- * Get a filter function for the column.
138
- * @returns {void|function} Filter function.
139
- */
140
- getFilterFn() {
141
- if (this.filter == null) {
142
+ getFilterFn(column, filter) {
143
+ if (filter == null) {
142
144
  return;
143
145
  }
144
- return this._applySingleFilter.bind(this, this.filter);
145
- }
146
-
147
- /**
148
- * Determine if a filter should be enabled or not.
149
- * @param {string} filter Filter text.
150
- * @param {object} item Column data.
151
- * @returns {boolean} Whether the filter should be enabled or not.
152
- */
153
- _applySingleFilter(filter, item) {
154
- return this.get(this.valuePath, item) === filter;
155
- }
156
-
157
- _serializeFilter(filter = this.filter) {
158
- if (filter == null || filter === '') {
159
- return null;
160
- }
161
- return this._serializeValue(filter ? 'true' : 'false');
162
- }
163
-
164
- _deserializeFilter(obj) {
165
- const value = this._deserializeValue(obj, String);
166
-
167
- if (value === 'true') {
168
- return true;
169
- }
170
-
171
- if (value === 'false') {
172
- return false;
173
- }
174
-
175
- return null;
176
- }
177
-
178
- toXlsxValue(item, valuePath = this.valuePath) {
179
- if (!valuePath) {
180
- return '';
181
- }
182
- return this.get(valuePath, item) ? this.trueLabel : this.falseLabel;
183
- }
184
-
185
- /**
186
- * Get a list of suggestions for the column header.
187
- * @param {string} trueLabel True label.
188
- * @param {string} falseLabel False label.
189
- * @returns {array} Suggestions remapped for the column header.
190
- */
191
- _computeSource(trueLabel, falseLabel) {
192
- return [
193
- {
194
- text: trueLabel,
195
- value: true
196
- },
197
- {
198
- text: falseLabel,
199
- value: false
200
- }
201
- ];
202
- }
203
-
204
- _computeValue(value, source = this._source) {
205
- return source.find(({ value: valueProp }) => value === valueProp);
146
+ return applySingleFilter(column, filter);
206
147
  }
207
148
 
208
- _computeItemValue(item, valuePath = this.valuePath, source) {
209
- return this._computeValue(
210
- this.get(valuePath, item),
211
- source
212
- );
149
+ toXlsxValue(column, item) {
150
+ return toXlsxValue(column, item);
213
151
  }
214
152
 
215
- _computeTooltip(title, value, source = this._source) {
216
- const val = this._computeValue(value, source);
217
- return val ? val.text : title;
218
- }
219
-
220
- _computeItemTooltip(title, item, valuePath, source) {
221
- return this._computeTooltip(
222
- title,
223
- this.get(valuePath || this.valuePath, item),
224
- source
225
- );
226
- }
227
-
228
- _computeItemChange(item, valuePath = this.valuePath) {
229
- return selection => {
230
- const value = selection?.[0]?.value,
231
- oldValue = this.get(valuePath, item),
232
- formatFn = value => {
233
- return value ? this.trueLabel : this.falseLabel;
234
- };
235
- if (value === oldValue) {
236
- return;
237
- }
238
- this.set(valuePath, value, item);
239
- this._fireItemChangeEvent(
240
- item,
241
- this.valuePath,
242
- oldValue,
243
- formatFn.bind(this)
244
- );
245
- };
153
+ deserializeFilter(column, filter) {
154
+ return deserializeFilter(column, filter);
246
155
  }
247
-
248
- _onChange(selection) {
249
- this.filter = selection?.[0]?.value ?? null;
250
- }
251
-
252
- _onFocus(focused) {
253
- this.headerFocused = focused;
254
- }
255
-
256
- _onText(text) {
257
- this.query = text;
258
- }
259
-
260
156
  }
261
- customElements.define(OmnitableColumnBoolean.is, OmnitableColumnBoolean);
157
+ customElements.define('cosmoz-omnitable-column-boolean', OmnitableColumnBoolean);
158
+
159
+ export { getString, computeItemValue, computeSource, toXlsxValue, onChange, deserializeFilter };
@@ -7,131 +7,118 @@ import './ui-helpers/cosmoz-clear-button';
7
7
  import { PolymerElement } from '@polymer/polymer/polymer-element';
8
8
  import { html } from 'lit-html';
9
9
 
10
- import {
11
- translatable, _
12
- } from '@neovici/cosmoz-i18next';
13
- import { dateColumnMixin } from './cosmoz-omnitable-column-date-mixin';
14
10
  import { columnMixin } from './cosmoz-omnitable-column-mixin';
15
- import { ifDefined } from 'lit-html/directives/if-defined';
16
-
17
- class OmnitableColumnDate extends dateColumnMixin(columnMixin(translatable(
18
- PolymerElement
19
- ))) {
20
- static get is() {
21
- return 'cosmoz-omnitable-column-date';
22
- }
11
+ import './lib/cosmoz-omnitable-date-range-input';
12
+ import { defaultComputeSource } from './lib/utils-data';
13
+ import { getString, getComparableValue, toDate, toHashString, toXlsxValue, applySingleFilter, getInputString, fromInputString } from './lib/utils-date';
23
14
 
15
+ class OmnitableColumnDate extends columnMixin(PolymerElement) {
24
16
  static get properties() {
25
17
  return {
26
- headerCellClass: {
27
- type: String,
28
- value: 'date-header-cell'
29
- },
30
- minWidth: {
31
- type: String,
32
- value: '82px'
33
- },
34
- editMinWidth: {
35
- type: String,
36
- value: '82px'
37
- }
18
+ min: { type: Number, value: null, notify: true },
19
+ max: { type: Number, value: null, notify: true },
20
+ locale: { type: String, value: null, notify: true },
21
+ headerCellClass: { type: String, value: 'date-header-cell' },
22
+ width: { type: String, value: '100px' },
23
+ minWidth: { type: String, value: '82px' },
24
+ flex: { type: String, value: '0' }
38
25
  };
39
26
  }
40
27
 
41
- renderCell(column, { item }) {
42
- return column.getString(item, column.valuePath, column.formatter);
28
+ getFilterFn(column, filter) {
29
+ const
30
+ min = getComparableValue({ ...column, valuePath: 'min' }, filter),
31
+ max = getComparableValue({ ...column, valuePath: 'max' }, filter);
32
+
33
+ if (min == null && max == null) {
34
+ return;
35
+ }
36
+ return applySingleFilter(column, filter);
43
37
  }
44
38
 
45
- renderEditCell(column, { item }) {
46
- const onChange = event => {
47
- event.model = { item };
48
- return column._dateValueChanged(event);
49
- };
39
+ getString(column, item) {
40
+ return getString(column, item);
41
+ }
50
42
 
51
- return html`<paper-input
52
- no-label-float
53
- type="date"
54
- @change=${ onChange }
55
- .value=${ column.getInputString(item, column.valuePath) }
56
- ></paper-input>`;
43
+ toXlsxValue(column, item) {
44
+ return toXlsxValue(column, item);
57
45
  }
58
46
 
59
- renderHeader(column) {
60
- return html`
61
- <style>
62
- paper-dropdown-menu {
63
- --iron-icon-width: 0;
64
- }
65
- </style>
66
- <cosmoz-clear-button @click=${ event => column.resetFilter(event) } ?visible=${ column.hasFilter() }></cosmoz-clear-button>
67
- <paper-dropdown-menu
68
- label=${ column.title }
69
- placeholder=${ ifDefined(column._filterText) }
70
- class="external-values-${ column.externalValues }"
71
- title=${ column._tooltip }
72
- horizontal-align=${ column.preferredDropdownHorizontalAlign }
73
- ?opened=${ column.headerFocused }
74
- @opened-changed=${ event => column.headerFocused = event.detail.value }>
75
- <div class="dropdown-content" slot="dropdown-content" style="padding: 15px; min-width: 100px;">
76
- <h3 style="margin: 0;">${ column.title }</h3>
77
- <paper-input
78
- type="date"
79
- label=${ _('From date') }
80
- min=${ column._toInputString(column._limit.fromMin) }
81
- max=${ column._toInputString(column._limit.fromMax) }
82
- .value=${ column._filterInput.min }
83
- @value-changed=${ event => column.set('_filterInput.min', event.detail.value) }
84
- ></paper-input>
85
- <paper-input
86
- type="date"
87
- label=${ _('Until date') }
88
- min=${ column._toInputString(column._limit.toMin) }
89
- max=${ column._toInputString(column._limit.toMax) }
90
- .value=${ column._filterInput.max }
91
- @value-changed=${ event => column.set('_filterInput.max', event.detail.value) }
92
- ></paper-input>
93
- </div>
94
- </paper-dropdown-menu>
95
- `;
47
+ cellTitleFn(column, item) {
48
+ return getString(column, item);
96
49
  }
97
50
 
98
- _fromInputString(value, property) {
99
- const date = this.toDate(value);
100
- if (date == null) {
51
+ getComparableValue(column, item) {
52
+ return getComparableValue(column, item);
53
+ }
54
+
55
+ serializeFilter(column, filter) {
56
+ if (filter == null) {
101
57
  return;
102
58
  }
103
- if (property === 'min') {
104
- date.setHours(0, 0, 0, 0);
105
- }
106
- if (property === 'max') {
107
- date.setHours(23, 59, 59);
59
+ const min = toDate(filter.min),
60
+ max = toDate(filter.max);
61
+
62
+ if (min == null && max == null) {
63
+ return;
108
64
  }
109
- return date;
65
+ return toHashString(min) + '~' + toHashString(max);
110
66
  }
111
67
 
112
- toXlsxValue(item, valuePath = this.valuePath) {
113
- if (!valuePath) {
114
- return '';
68
+ deserializeFilter(column, filter) {
69
+ if (filter == null || filter === '') {
70
+ return null;
115
71
  }
116
- const date = this.toValue(this.get(valuePath, item));
117
- if (!date) {
118
- return '';
72
+ const matches = filter.match(/^([^~]+)?~([^~]+)?/iu);
73
+
74
+ if (!Array.isArray(matches)) {
75
+ return null;
119
76
  }
120
- const localDate = this.toValue(this._toLocalISOString(date));
121
- localDate.setHours(0, 0, 0, 0);
122
- return localDate;
77
+
78
+ return {
79
+ min: toDate(matches[1]),
80
+ max: toDate(matches[2])
81
+ };
123
82
  }
124
83
 
125
- _filterInputChanged(change, autoupdate) {
126
- const path = change.path.split('.')[1],
127
- value = path && change.value;
84
+ renderCell(column, { item }) {
85
+ return getString(column, item);
86
+ }
128
87
 
129
- // don't trigger change when date input begins with 0; Year (starting from 0000) was limited before the needed value was typed.
130
- if (value && value.match(/^0+/u)) {
131
- this._limitInputDebouncer.cancel();
132
- return;
133
- }
134
- super._filterInputChanged(change, autoupdate);
88
+ renderEditCell(column, { item }, onItemChange) {
89
+ const onChange = event => onItemChange(fromInputString(event.target.value));
90
+
91
+ return html`<paper-input
92
+ no-label-float
93
+ type="date"
94
+ @change=${ onChange }
95
+ .value=${ getInputString(column, item) }
96
+ ></paper-input>`;
97
+ }
98
+
99
+ renderHeader(
100
+ { title,
101
+ min,
102
+ max,
103
+ locale },
104
+ { filter },
105
+ setState,
106
+ source
107
+ ) {
108
+ return html`<cosmoz-omnitable-date-range-input
109
+ .title=${ title }
110
+ .filter=${ filter }
111
+ .values=${ source }
112
+ .min=${ min }
113
+ .max=${ max }
114
+ .locale=${ locale }
115
+ @filter-changed=${ ({ detail: { value }}) => setState(state => ({ ...state, filter: value })) }
116
+ @header-focused-changed=${ ({ detail: { value }}) => setState(state => ({ ...state, headerFocused: value })) }
117
+ ></cosmoz-omnitable-date-range-input>`;
118
+ }
119
+
120
+ computeSource(column, data) {
121
+ return defaultComputeSource(column, data);
135
122
  }
136
123
  }
137
- customElements.define(OmnitableColumnDate.is, OmnitableColumnDate);
124
+ customElements.define('cosmoz-omnitable-column-date', OmnitableColumnDate);