@neovici/cosmoz-omnitable 7.1.1 → 8.0.0-beta.2

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 (51) hide show
  1. package/README.md +23 -0
  2. package/cosmoz-omnitable-column-amount.js +89 -320
  3. package/cosmoz-omnitable-column-autocomplete.js +36 -47
  4. package/cosmoz-omnitable-column-boolean.js +107 -209
  5. package/cosmoz-omnitable-column-date.js +89 -102
  6. package/cosmoz-omnitable-column-datetime.js +86 -119
  7. package/cosmoz-omnitable-column-list-data.js +4 -1
  8. package/cosmoz-omnitable-column-list-horizontal.js +20 -38
  9. package/cosmoz-omnitable-column-list-mixin.js +133 -140
  10. package/cosmoz-omnitable-column-list.js +19 -28
  11. package/cosmoz-omnitable-column-mixin.js +69 -447
  12. package/cosmoz-omnitable-column-number.js +91 -183
  13. package/cosmoz-omnitable-column-time.js +77 -162
  14. package/cosmoz-omnitable-column.js +49 -93
  15. package/cosmoz-omnitable-group-row.js +1 -5
  16. package/cosmoz-omnitable-header-row.js +9 -6
  17. package/cosmoz-omnitable-item-expand.js +0 -3
  18. package/cosmoz-omnitable-item-row.js +5 -8
  19. package/cosmoz-omnitable-styles.js +1 -5
  20. package/cosmoz-omnitable.js +73 -811
  21. package/lib/cosmoz-omnitable-amount-range-input.js +295 -0
  22. package/{cosmoz-omnitable-column-date-mixin.js → lib/cosmoz-omnitable-date-input-mixin.js} +4 -26
  23. package/lib/cosmoz-omnitable-date-range-input.js +81 -0
  24. package/lib/cosmoz-omnitable-datetime-range-input.js +75 -0
  25. package/lib/cosmoz-omnitable-number-range-input.js +159 -0
  26. package/{cosmoz-omnitable-column-range-mixin.js → lib/cosmoz-omnitable-range-input-mixin.js} +45 -129
  27. package/lib/cosmoz-omnitable-settings.js +22 -4
  28. package/lib/cosmoz-omnitable-time-range-input.js +130 -0
  29. package/lib/generic-sorter.js +35 -0
  30. package/lib/invoke.js +1 -0
  31. package/lib/memoize.js +54 -0
  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 +138 -0
  37. package/lib/use-hash-state.js +59 -0
  38. package/lib/use-layout.js +1 -1
  39. package/lib/use-omnitable.js +31 -4
  40. package/lib/use-processed-items.js +132 -0
  41. package/lib/use-saved-settings.js +2 -2
  42. package/lib/use-sort-and-group-options.js +30 -0
  43. package/lib/utils-amount.js +147 -0
  44. package/lib/utils-data.js +36 -0
  45. package/lib/utils-date.js +204 -0
  46. package/lib/utils-datetime.js +71 -0
  47. package/lib/utils-number.js +112 -0
  48. package/lib/utils-time.js +115 -0
  49. package/package.json +1 -1
  50. package/lib/use-force-render.js +0 -8
  51. package/lib/use-render-on-column-updates.js +0 -18
@@ -0,0 +1,295 @@
1
+ import { _ } from '@neovici/cosmoz-i18next';
2
+ import { PolymerElement } from '@polymer/polymer';
3
+ import { html } from 'lit-html';
4
+ import { ifDefined } from 'lit-html/directives/if-defined';
5
+ import { rangeInputMixin } from './cosmoz-omnitable-range-input-mixin';
6
+ import { polymerHauntedRender } from './polymer-haunted-render-mixin';
7
+
8
+ class AmountRangeInput extends rangeInputMixin(polymerHauntedRender(PolymerElement)) {
9
+ static get properties() {
10
+ return {
11
+ /**
12
+ * Base Currency used in filters
13
+ */
14
+ currency: { type: String },
15
+
16
+ /**
17
+ * If this is set true then currency property will be the currency with highest occurrence in values
18
+ */
19
+ autodetect: { type: Boolean, value: false },
20
+
21
+ /**
22
+ * Exchange rates of currencies. Example: {"EUR": 1, "USD":0.8169982616, "AUD":0.6529827192, "SEK": 0.1019271438}'
23
+ * Default exchange rate is 1 and it is used for every currency that is present on column values but missing from exchange rates object.
24
+ */
25
+ rates: { type: Object },
26
+ // trigger filter updates manually
27
+ autoupdate: { type: String, value: false },
28
+ _filterText: { type: String, computed: '_computeFilterText(filter.*, _formatters)' }
29
+ };
30
+ }
31
+
32
+ static get observers() {
33
+ return [
34
+ '_valuesChanged(autodetect, currency, values)'
35
+ ];
36
+ }
37
+
38
+ // eslint-disable-next-line max-lines-per-function
39
+ render() {
40
+ const onOpenedChanged = event => {
41
+ this.headerFocused = event.detail.value;
42
+ this._onDropdownOpenedChanged(event);
43
+ };
44
+
45
+ return html`
46
+ <style>
47
+ paper-dropdown-menu {
48
+ --iron-icon-width: 0;
49
+ display: block;
50
+ }
51
+ </style>
52
+ <cosmoz-clear-button @click=${ () => this.resetFilter() } ?visible=${ this.hasFilter() }></cosmoz-clear-button>
53
+ <paper-dropdown-menu
54
+ label=${ this.title }
55
+ placeholder=${ ifDefined(this._filterText) }
56
+ class="external-values-${ this.externalValues }"
57
+ title=${ this._tooltip }
58
+ horizontal-align="right"
59
+ ?opened=${ this.headerFocused }
60
+ @opened-changed=${ onOpenedChanged }
61
+ >
62
+ <div class="dropdown-content" slot="dropdown-content" style="padding: 15px; min-width: 150px;">
63
+ <h3 style="margin: 0;">${ this.title }</h3>
64
+ <paper-input
65
+ class=${ this._fromClasses }
66
+ type="number"
67
+ label=${ _('Min amount') }
68
+ title=${ _('Minimum amount') }
69
+ .value=${ this._filterInput?.min }
70
+ @value-changed=${ event => {
71
+ this.set('_filterInput.min', event.detail.value);
72
+ } }
73
+ @blur=${ event => this._onBlur(event) }
74
+ @keydown=${ event => this._onKeyDown(event) }
75
+ min=${ this._toInputStringAmount(this._limit.fromMin) }
76
+ max=${ this._toInputStringAmount(this._limit.fromMax) }
77
+ ><div slot="suffix" suffix>${ this.filter?.min?.currency }</div></paper-input>
78
+ <paper-input
79
+ class=${ this._toClasses }
80
+ type="number"
81
+ label=${ _('Max amount') }
82
+ title=${ _('Maximum amount') }
83
+ .value=${ this._filterInput?.max }
84
+ @value-changed=${ event => {
85
+ this.set('_filterInput.max', event.detail.value);
86
+ } }
87
+ @blur=${ event => this._onBlur(event) }
88
+ @keydown=${ event => this._onKeyDown(event) }
89
+ min=${ this._toInputStringAmount(this._limit.toMin) }
90
+ max=${ this._toInputStringAmount(this._limit.toMax) }
91
+ ><div slot="suffix" suffix>${ this.filter?.max?.currency }</div></paper-input>
92
+ </div>
93
+ </paper-dropdown-menu>
94
+ `;
95
+ }
96
+
97
+ /**
98
+ * Auto-detect currency property: Most frequently occurring currency
99
+ * @param {boolean} autodetect Whether the currency property will be the currency with highest occurrence in values or not.
100
+ * @param {string} currency Base currency used in filters.
101
+ * @param {Array} values Array of amounts
102
+ * @returns {void}
103
+ */
104
+ _valuesChanged(autodetect, currency, values) {
105
+ if (!Array.isArray(values) || values.length < 1) {
106
+ return;
107
+ }
108
+ if (!autodetect && currency) {
109
+ return;
110
+ }
111
+ // array with currencies as keys and occurence as value
112
+ const currencies = values.reduce((p, n) => {
113
+ if (n.currency) {
114
+ const c = n.currency;
115
+ p[c] = (p[c] || 0) + 1;
116
+ }
117
+ return p;
118
+ }, {});
119
+
120
+ let key = Object.keys(currencies)[0];
121
+ Object.keys(currencies).reduce((p, n) => {
122
+ const max = Math.max(p, currencies[n]);
123
+ if (max === currencies[n]) {
124
+ key = n;
125
+ }
126
+ return max;
127
+ }, 0);
128
+ this.set('currency', key);
129
+ }
130
+
131
+ /**
132
+ * Converts a value to an amount object optionaly limiting it.
133
+ *
134
+ * @param {Object} value The value to convert to number
135
+ * @param {Object} limit The value used to limit the number
136
+ * @param {Function} limitFunc The function used to limit the number (Math.min|Math.max)
137
+ * @returns {Object|void} Value converted to Number or void
138
+ */
139
+ toAmount(value, limit, limitFunc) { // eslint-disable-line max-statements
140
+ if (value == null || value === '') {
141
+ return;
142
+ }
143
+
144
+ if (typeof value !== 'object' || value.currency == null || value.currency === '') {
145
+ return null;
146
+ }
147
+
148
+ const number = this.toNumber(value.amount);
149
+ if (number == null || Number.isNaN(number)) {
150
+ return null;
151
+ }
152
+ const amount = {
153
+ currency: value.currency,
154
+ amount: number
155
+ };
156
+
157
+ if (limitFunc == null || limit == null) {
158
+ return amount;
159
+ }
160
+ const lAmount = this.toAmount(limit);
161
+ if (lAmount == null) {
162
+ return amount;
163
+ }
164
+
165
+ // calculate value and limit amounts with rates
166
+ const rates = this.rates || {},
167
+ valAmount = amount.amount * (rates[amount.currency] || 1),
168
+ limAmount = lAmount.amount * (rates[lAmount.currency] || 1),
169
+ lNumber = this.toNumber(valAmount, limAmount, limitFunc);
170
+ return lNumber === valAmount ? amount : lAmount;
171
+ }
172
+
173
+ toValue() {
174
+ return this.toAmount.apply(this, arguments);
175
+ }
176
+
177
+ /**
178
+ * Get the comparable value of an item.
179
+ *
180
+ * @param {Object} item Item to be processed
181
+ * @param {String} valuePath Property path
182
+ * @returns {Number|void} Valid value or void
183
+ */
184
+ getComparableValue(item, valuePath) {
185
+ const value = super.getComparableValue(item, valuePath);
186
+
187
+ if (value == null) {
188
+ return;
189
+ }
190
+ const amount = this.toNumber(value.amount),
191
+ rates = this.rates;
192
+
193
+ if (rates == null) {
194
+ return amount;
195
+ }
196
+
197
+ return amount * (rates[value.currency] || 1);
198
+ }
199
+ getString(item, valuePath = this.valuePath) {
200
+ const value = this.toValue(this.get(valuePath, item));
201
+ if (value === undefined) {
202
+ return '';
203
+ }
204
+ if (value === null) {
205
+ return 'Invalid value';
206
+ }
207
+ return this.renderValue(value);
208
+ }
209
+
210
+ getCurrency(item, valuePath) {
211
+ const value = this.get(valuePath, item);
212
+ return value && value.currency;
213
+ }
214
+
215
+ getFormatter(currency, locale) {
216
+ const id = locale ? locale : '',
217
+ key = currency + id || '',
218
+ formatters = this._formatters = this._formatters || {};
219
+ if (formatters[key]) {
220
+ return formatters[key];
221
+ }
222
+
223
+ formatters[key] = new Intl.NumberFormat(locale || undefined, {
224
+ style: 'currency',
225
+ currency
226
+ });
227
+
228
+ return formatters[key];
229
+ }
230
+ /**
231
+ * Converts an amount to symbol and value to be rendered.
232
+ *
233
+ * @param {Object} value Amount to be formated
234
+ * @param {Object} _formatters = this._formatters All possible formatters.
235
+ * @returns {String} Formated value or empty string.
236
+ */
237
+ renderValue(value) {
238
+ const amount = this.toAmount(value);
239
+ if (amount == null) {
240
+ return '';
241
+ }
242
+ return this.getFormatter(amount.currency, this.locale)
243
+ .format(value.amount);
244
+ }
245
+
246
+ _amountValueChanged(event) {
247
+ const input = event.target,
248
+ value = input.value,
249
+ item = event.model.item,
250
+ originalValue = this.get(this.valuePath, item),
251
+ amountValue = Number(value),
252
+ newValue = {
253
+ amount: amountValue,
254
+ currency: originalValue.currency
255
+ };
256
+
257
+ this.set(this.valuePath, newValue, item);
258
+ this._fireItemChangeEvent(item, this.valuePath, originalValue, this.renderValue.bind(this));
259
+ }
260
+
261
+ _toInputString(value) {
262
+ const amount = this.toValue(value);
263
+ if (amount == null) {
264
+ return null; //Need null to clear input
265
+ }
266
+ return this.toNumber(amount.amount);
267
+ }
268
+
269
+ _toInputStringAmount(value) {
270
+ const rates = this.rates;
271
+ if (rates == null) {
272
+ return this._toInputString(value);
273
+ }
274
+ const amount = this.toValue(value);
275
+ if (amount == null) {
276
+ return null;
277
+ }
278
+ const toCurrency = this.toNumber(amount.amount) * (rates[amount.currency] || 1) / (rates[this.currency] || 1);
279
+ return toCurrency.toFixed(2);
280
+ }
281
+
282
+ _fromInputString(value, property) {
283
+ const number = this.toNumber(value);
284
+ if (number == null) {
285
+ return;
286
+ }
287
+ return this.toValue({
288
+ amount: number,
289
+ currency: property && this.get(['filter', property, 'currency']) || this.currency
290
+ });
291
+ }
292
+ }
293
+
294
+
295
+ customElements.define('cosmoz-omnitable-amount-range-input', AmountRangeInput);
@@ -1,22 +1,19 @@
1
- import { dedupingMixin } from '@polymer/polymer/lib/utils/mixin';
2
1
  import { toLocalISOString } from '@neovici/cosmoz-utils/lib/date';
3
-
4
- import { rangeColumnMixin } from './cosmoz-omnitable-column-range-mixin';
2
+ import { rangeInputMixin } from './cosmoz-omnitable-range-input-mixin';
5
3
 
6
4
  /**
7
5
  * @polymer
8
6
  * @mixinFunction
9
- * @appliesMixin rangeColumnMixin
10
7
 
11
8
  * @param {class} base The base class
12
9
  * @returns {class} The base class with the mixin applied
13
10
  */
14
- export const dateColumnMixin = dedupingMixin(base => // eslint-disable-line max-lines-per-function
11
+ export const dateInputMixin = base => // eslint-disable-line max-lines-per-function
15
12
  /**
16
13
  * @polymer
17
14
  * @mixinClass
18
15
  */
19
- class extends rangeColumnMixin(base) {
16
+ class extends rangeInputMixin(base) {
20
17
  static get properties() {
21
18
  return {
22
19
  max: {
@@ -34,24 +31,6 @@ export const dateColumnMixin = dedupingMixin(base => // eslint-disable-line max-
34
31
  computed: '_computeFilterText(filter.*, formatter)'
35
32
  },
36
33
 
37
- width: {
38
- type: String,
39
- value: '100px'
40
- },
41
-
42
- editWidth: {
43
- type: String,
44
- value: '150px'
45
- },
46
-
47
- /**
48
- * No need to grow, as the values in a date column should have known fixed width
49
- * @returns {String} Default flex
50
- */
51
- flex: {
52
- type: String,
53
- value: '0'
54
- },
55
34
  formatter: {
56
35
  type: Object,
57
36
  computed: '_computeFormatter(locale)'
@@ -201,5 +180,4 @@ export const dateColumnMixin = dedupingMixin(base => // eslint-disable-line max-
201
180
  _toLocalISOString(date) {
202
181
  return toLocalISOString(date);
203
182
  }
204
- }
205
- );
183
+ };
@@ -0,0 +1,81 @@
1
+ import { _ } from '@neovici/cosmoz-i18next';
2
+ import { PolymerElement } from '@polymer/polymer';
3
+ import { html } from 'lit-html';
4
+ import { ifDefined } from 'lit-html/directives/if-defined';
5
+ import { dateInputMixin } from './cosmoz-omnitable-date-input-mixin';
6
+ import { polymerHauntedRender } from './polymer-haunted-render-mixin';
7
+
8
+ class DateRangeInput extends dateInputMixin(polymerHauntedRender(PolymerElement)) {
9
+ // eslint-disable-next-line max-lines-per-function
10
+ render() {
11
+ return html`
12
+ <style>
13
+ paper-dropdown-menu {
14
+ --iron-icon-width: 0;
15
+ display: block;
16
+ }
17
+ </style>
18
+ <cosmoz-clear-button @click=${ () => this.resetFilter() } ?visible=${ this.hasFilter() }></cosmoz-clear-button>
19
+ <paper-dropdown-menu
20
+ label=${ this.title }
21
+ placeholder=${ ifDefined(this._filterText) }
22
+ class="external-values-${ this.externalValues }"
23
+ title=${ this._tooltip }
24
+ horizontal-align="right"
25
+ ?opened=${ this.headerFocused }
26
+ @opened-changed=${ event => {
27
+ // TODO: check ots integration
28
+ this.headerFocused = event.detail.value;
29
+ } }>
30
+ <div class="dropdown-content" slot="dropdown-content" style="padding: 15px; min-width: 100px;">
31
+ <h3 style="margin: 0;">${ this.title }</h3>
32
+ <paper-input
33
+ type="date"
34
+ label=${ _('From date') }
35
+ min=${ this._toInputString(this._limit.fromMin) }
36
+ max=${ this._toInputString(this._limit.fromMax) }
37
+ .value=${ this._filterInput?.min }
38
+ @value-changed=${ event => this.set('_filterInput.min', event.detail.value) }
39
+ ></paper-input>
40
+ <paper-input
41
+ type="date"
42
+ label=${ _('Until date') }
43
+ min=${ this._toInputString(this._limit.toMin) }
44
+ max=${ this._toInputString(this._limit.toMax) }
45
+ .value=${ this._filterInput?.max }
46
+ @value-changed=${ event => this.set('_filterInput.max', event.detail.value) }
47
+ ></paper-input>
48
+ </div>
49
+ </paper-dropdown-menu>
50
+ `;
51
+ }
52
+
53
+ _fromInputString(value, property) {
54
+ const date = this.toDate(value);
55
+ if (date == null) {
56
+ return;
57
+ }
58
+ if (property === 'min') {
59
+ date.setHours(0, 0, 0, 0);
60
+ }
61
+ if (property === 'max') {
62
+ date.setHours(23, 59, 59);
63
+ }
64
+ return date;
65
+ }
66
+
67
+ _filterInputChanged(change, autoupdate) {
68
+ const path = change.path.split('.')[1],
69
+ value = path && change.value;
70
+
71
+ // don't trigger change when date input begins with 0; Year (starting from 0000) was limited before the needed value was typed.
72
+ if (value && value.match(/^0+/u)) {
73
+ this._limitInputDebouncer.cancel();
74
+ return;
75
+ }
76
+ super._filterInputChanged(change, autoupdate);
77
+ }
78
+ }
79
+
80
+
81
+ customElements.define('cosmoz-omnitable-date-range-input', DateRangeInput);
@@ -0,0 +1,75 @@
1
+ import { _ } from '@neovici/cosmoz-i18next';
2
+ import { PolymerElement } from '@polymer/polymer';
3
+ import { html } from 'lit-html';
4
+ import { ifDefined } from 'lit-html/directives/if-defined';
5
+ import { dateInputMixin } from './cosmoz-omnitable-date-input-mixin';
6
+ import { polymerHauntedRender } from './polymer-haunted-render-mixin';
7
+
8
+ class DatetimeRangeInput extends dateInputMixin(polymerHauntedRender(PolymerElement)) {
9
+ // eslint-disable-next-line max-lines-per-function
10
+ render() {
11
+ return html`
12
+ <style>
13
+ paper-dropdown-menu {
14
+ --iron-icon-width: 0;
15
+ display: block;
16
+ }
17
+ </style>
18
+
19
+ <cosmoz-clear-button @click=${ () => this.resetFilter() } ?visible=${ this.hasFilter() }></cosmoz-clear-button>
20
+ <paper-dropdown-menu
21
+ label=${ this.title }
22
+ placeholder=${ ifDefined(this._filterText) }
23
+ class="external-values-${ this.externalValues }"
24
+ title=${ this._tooltip }
25
+ horizontal-align="right"
26
+ ?opened=${ this.headerFocused }
27
+ @opened-changed=${ event => this.set('headerFocused', event.detail.value) }>
28
+ <div class="dropdown-content" slot="dropdown-content" style="padding: 15px; min-width: 100px;">
29
+ <h3 style="margin: 0;">${ this.title }</h3>
30
+ <cosmoz-datetime-input
31
+ date-label=${ _('From date') }
32
+ time-label=${ _('From time') }
33
+ min=${ this._toInputString(this._limit.fromMin) }
34
+ max=${ this._toInputString(this._limit.fromMax) }
35
+ step=${ this.filterStep }
36
+ .value=${ this._filterInput?.min }
37
+ @value-changed=${ event => this.set('_filterInput.min', event.detail.value) }
38
+ ></cosmoz-datetime-input>
39
+ <cosmoz-datetime-input
40
+ date-label=${ _('To date') }
41
+ time-label=${ _('To time') }
42
+ min=${ this._toInputString(this._limit.toMin) }
43
+ max=${ this._toInputString(this._limit.toMax) }
44
+ step=${ this.filterStep }
45
+ .value=${ this._filterInput?.max }
46
+ @value-changed=${ event => this.set('_filterInput.max', event.detail.value) }
47
+ ></cosmoz-datetime-input>
48
+ </div>
49
+ </paper-dropdown-menu>
50
+ `;
51
+ }
52
+
53
+ _toInputString(value) {
54
+ const date = this.toValue(value);
55
+ if (date == null) {
56
+ return;
57
+ }
58
+ return this._toLocalISOString(date).slice(0, 19);
59
+ }
60
+
61
+ // OVERRIDES
62
+ _computeFormatter(locale) {
63
+ const timeFormatOption = {
64
+ year: 'numeric',
65
+ month: 'numeric',
66
+ day: 'numeric',
67
+ hour: 'numeric',
68
+ minute: 'numeric'
69
+ };
70
+ return new Intl.DateTimeFormat(locale || undefined, timeFormatOption);
71
+ }
72
+ }
73
+
74
+
75
+ customElements.define('cosmoz-omnitable-datetime-range-input', DatetimeRangeInput);
@@ -0,0 +1,159 @@
1
+ import { _ } from '@neovici/cosmoz-i18next';
2
+ import { PolymerElement } from '@polymer/polymer';
3
+ import { html } from 'lit-html';
4
+ import { ifDefined } from 'lit-html/directives/if-defined';
5
+ import { rangeInputMixin } from './cosmoz-omnitable-range-input-mixin';
6
+ import { polymerHauntedRender } from './polymer-haunted-render-mixin';
7
+
8
+ class NumberRangeInput extends rangeInputMixin(polymerHauntedRender(PolymerElement)) {
9
+ static get properties() {
10
+ return {
11
+ maximumFractionDigits: { type: Number, value: null },
12
+ minimumFractionDigits: { type: Number, value: null }, // browser default 0 for numbers, currency-specific or 2 for currency
13
+ formatter: {
14
+ type: Object,
15
+ computed: '_computeFormatter(locale, minimumFractionDigits, maximumFractionDigits)'
16
+ },
17
+ // trigger filter updates manually
18
+ autoupdate: {
19
+ type: String,
20
+ value: false
21
+ },
22
+ _filterText: {
23
+ type: String,
24
+ computed: '_computeFilterText(filter.*, formatter)'
25
+ }
26
+ };
27
+ }
28
+
29
+ // eslint-disable-next-line max-lines-per-function
30
+ render() {
31
+ const onOpenedChanged = event => {
32
+ this.headerFocused = event.detail.value;
33
+ this._onDropdownOpenedChanged(event);
34
+ };
35
+
36
+ return html`
37
+ <style>
38
+ paper-dropdown-menu {
39
+ --iron-icon-width: 0;
40
+ display: block;
41
+ }
42
+ </style>
43
+
44
+ <cosmoz-clear-button @click=${ () => this.resetFilter() } ?visible=${ this.hasFilter() }></cosmoz-clear-button>
45
+ <paper-dropdown-menu
46
+ label=${ this.title }
47
+ placeholder=${ ifDefined(this._filterText) }
48
+ class="external-values-${ this.externalValues }"
49
+ title=${ this._tooltip }
50
+ horizontal-align="right"
51
+ ?opened=${ this.headerFocused }
52
+ @opened-changed=${ onOpenedChanged }
53
+ >
54
+ <div class="dropdown-content" slot="dropdown-content" style="padding: 15px; min-width: 100px;">
55
+ <h3 style="margin: 0;">${ this.title }</h3>
56
+ <paper-input
57
+ class=${ this._fromClasses }
58
+ type="number"
59
+ label=${ _('From') }
60
+ .value=${ this._filterInput?.min }
61
+ @value-changed=${ event => {
62
+ this.set('_filterInput.min', event.detail.value);
63
+ } }
64
+ @input=${ event => this.onBadInputFloatLabel(event) }
65
+ @blur=${ event => this._onBlur(event) }
66
+ @keydown=${ event => this._onKeyDown(event) }
67
+ min=${ this._toInputString(this._limit.fromMin) }
68
+ max=${ this._toInputString(this._limit.fromMax) }
69
+ ></paper-input>
70
+ <paper-input
71
+ class=${ this._toClasses }
72
+ type="number"
73
+ label=${ _('To') }
74
+ .value=${ this._filterInput?.max }
75
+ @value-changed=${ event => {
76
+ this.set('_filterInput.max', event.detail.value);
77
+ } }
78
+ @input=${ event => this.onBadInputFloatLabel(event) }
79
+ @blur=${ event => this._onBlur(event) }
80
+ @keydown=${ event => this._onKeyDown(event) }
81
+ min=${ this._toInputString(this._limit.toMin) }
82
+ max=${ this._toInputString(this._limit.toMax) }
83
+ ></paper-input>
84
+ </div>
85
+ </paper-dropdown-menu>
86
+ `;
87
+ }
88
+
89
+ _computeFormatter(locale, minimumFractionDigits, maximumFractionDigits) {
90
+ const options = {
91
+ localeMatcher: 'best fit' // chrome expects this when using custom options
92
+ };
93
+ if (minimumFractionDigits !== null) {
94
+ options.minimumFractionDigits = minimumFractionDigits;
95
+ }
96
+ if (maximumFractionDigits !== null) {
97
+ options.maximumFractionDigits = maximumFractionDigits;
98
+ }
99
+ return new Intl.NumberFormat(locale || undefined, options);
100
+ }
101
+
102
+ /**
103
+ * Check if label should float based on validity
104
+ *
105
+ * Number inputs can have allowed characters that aren't numbers (-,e) and won't
106
+ * trigger a value change and thus not float the label.
107
+ * However, the validity will report badInput so we can trigger a label float by
108
+ * setting it to something truthy but still not visible.
109
+ * Fixed in paper-input 3.x
110
+ *
111
+ * @param {Event} event KeyboardEvent
112
+ * @returns {void}
113
+ */
114
+ onBadInputFloatLabel(event) {
115
+ const paperInput = event.currentTarget;
116
+ if (paperInput == null || paperInput.tagName !== 'PAPER-INPUT') {
117
+ return;
118
+ }
119
+ paperInput.placeholder = paperInput.$.nativeInput.validity.badInput ? ' ' : '';
120
+ }
121
+
122
+ /**
123
+ * Get the comparable value of an item.
124
+ *
125
+ * @param {Object} item Item to be processed
126
+ * @param {String} valuePath Property path
127
+ * @returns {Number|void} Valid value or void
128
+ */
129
+ getComparableValue(item, valuePath) {
130
+ if (item == null) {
131
+ return;
132
+ }
133
+ let value = item;
134
+ if (valuePath != null) {
135
+ value = this.get(valuePath, item);
136
+ }
137
+ value = this.toValue(value);
138
+ if (value == null) {
139
+ return;
140
+ }
141
+
142
+ const decimals = this.maximumFractionDigits;
143
+ if (decimals !== null) {
144
+ return this.toValue(value.toFixed(decimals));
145
+ }
146
+ return value;
147
+ }
148
+
149
+ renderValue(value, formatter = this.formatter) {
150
+ const number = this.toNumber(value);
151
+ if (number == null) {
152
+ return;
153
+ }
154
+ return formatter.format(number);
155
+ }
156
+ }
157
+
158
+
159
+ customElements.define('cosmoz-omnitable-number-range-input', NumberRangeInput);