@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.
- package/README.md +23 -0
- package/cosmoz-omnitable-column-amount.js +89 -320
- package/cosmoz-omnitable-column-autocomplete.js +36 -47
- package/cosmoz-omnitable-column-boolean.js +107 -209
- package/cosmoz-omnitable-column-date.js +89 -102
- package/cosmoz-omnitable-column-datetime.js +86 -119
- package/cosmoz-omnitable-column-list-data.js +4 -1
- package/cosmoz-omnitable-column-list-horizontal.js +20 -38
- package/cosmoz-omnitable-column-list-mixin.js +133 -140
- package/cosmoz-omnitable-column-list.js +19 -28
- package/cosmoz-omnitable-column-mixin.js +69 -447
- package/cosmoz-omnitable-column-number.js +91 -183
- package/cosmoz-omnitable-column-time.js +77 -162
- package/cosmoz-omnitable-column.js +49 -93
- package/cosmoz-omnitable-group-row.js +1 -5
- package/cosmoz-omnitable-header-row.js +9 -6
- package/cosmoz-omnitable-item-expand.js +0 -3
- package/cosmoz-omnitable-item-row.js +5 -8
- package/cosmoz-omnitable-styles.js +1 -5
- package/cosmoz-omnitable.js +73 -811
- package/lib/cosmoz-omnitable-amount-range-input.js +295 -0
- package/{cosmoz-omnitable-column-date-mixin.js → lib/cosmoz-omnitable-date-input-mixin.js} +4 -26
- package/lib/cosmoz-omnitable-date-range-input.js +81 -0
- package/lib/cosmoz-omnitable-datetime-range-input.js +75 -0
- package/lib/cosmoz-omnitable-number-range-input.js +159 -0
- package/{cosmoz-omnitable-column-range-mixin.js → lib/cosmoz-omnitable-range-input-mixin.js} +45 -129
- package/lib/cosmoz-omnitable-settings.js +22 -4
- package/lib/cosmoz-omnitable-time-range-input.js +130 -0
- package/lib/generic-sorter.js +35 -0
- package/lib/invoke.js +1 -0
- package/lib/memoize.js +54 -0
- package/lib/polymer-haunted-render-mixin.js +19 -0
- package/lib/save-as-csv-action.js +32 -0
- package/lib/save-as-xlsx-action.js +25 -0
- package/lib/use-canvas-width.js +1 -1
- package/lib/use-dom-columns.js +138 -0
- package/lib/use-hash-state.js +59 -0
- package/lib/use-layout.js +1 -1
- package/lib/use-omnitable.js +31 -4
- package/lib/use-processed-items.js +132 -0
- package/lib/use-saved-settings.js +2 -2
- package/lib/use-sort-and-group-options.js +30 -0
- package/lib/utils-amount.js +147 -0
- package/lib/utils-data.js +36 -0
- package/lib/utils-date.js +204 -0
- package/lib/utils-datetime.js +71 -0
- package/lib/utils-number.js +112 -0
- package/lib/utils-time.js +115 -0
- package/package.json +1 -1
- package/lib/use-force-render.js +0 -8
- package/lib/use-render-on-column-updates.js +0 -18
package/{cosmoz-omnitable-column-range-mixin.js → lib/cosmoz-omnitable-range-input-mixin.js}
RENAMED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/* eslint-disable max-lines */
|
|
2
|
-
import { dedupingMixin } from '@polymer/polymer/lib/utils/mixin.js';
|
|
3
2
|
import { Debouncer } from '@polymer/polymer/lib/utils/debounce.js';
|
|
4
3
|
import { timeOut } from '@polymer/polymer/lib/utils/async.js';
|
|
5
4
|
import { enqueueDebouncer } from '@polymer/polymer/lib/utils/flush.js';
|
|
@@ -15,7 +14,7 @@ const getCloseableParent = el =>
|
|
|
15
14
|
* @param {class} base The base class
|
|
16
15
|
* @returns {class} The base class with the mixin applied
|
|
17
16
|
*/
|
|
18
|
-
export const
|
|
17
|
+
export const rangeInputMixin = base => // eslint-disable-line max-lines-per-function
|
|
19
18
|
/**
|
|
20
19
|
* @polymer
|
|
21
20
|
* @mixinClass
|
|
@@ -23,10 +22,9 @@ export const rangeColumnMixin = dedupingMixin(base => // eslint-disable-line max
|
|
|
23
22
|
class extends base {
|
|
24
23
|
static get properties() { // eslint-disable-line max-lines-per-function
|
|
25
24
|
return {
|
|
26
|
-
|
|
27
|
-
type:
|
|
28
|
-
|
|
29
|
-
value: true
|
|
25
|
+
filter: {
|
|
26
|
+
type: Object,
|
|
27
|
+
notify: true
|
|
30
28
|
},
|
|
31
29
|
|
|
32
30
|
values: {
|
|
@@ -36,6 +34,11 @@ export const rangeColumnMixin = dedupingMixin(base => // eslint-disable-line max
|
|
|
36
34
|
}
|
|
37
35
|
},
|
|
38
36
|
|
|
37
|
+
headerFocused: {
|
|
38
|
+
type: Boolean,
|
|
39
|
+
notify: true
|
|
40
|
+
},
|
|
41
|
+
|
|
39
42
|
min: {
|
|
40
43
|
type: Number,
|
|
41
44
|
value: null
|
|
@@ -118,13 +121,13 @@ export const rangeColumnMixin = dedupingMixin(base => // eslint-disable-line max
|
|
|
118
121
|
}
|
|
119
122
|
|
|
120
123
|
/**
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
124
|
+
* Converts a value to number optionaly limiting it.
|
|
125
|
+
*
|
|
126
|
+
* @param {Number|*} value The value to convert to number
|
|
127
|
+
* @param {Number|*} limit The value used to limit the number
|
|
128
|
+
* @param {Function} limitFunc The function used to limit the number (Math.min|Math.max)
|
|
129
|
+
* @returns {Number|void} Value converted to Number or void
|
|
130
|
+
*/
|
|
128
131
|
toNumber(value, limit, limitFunc) {
|
|
129
132
|
if (value == null || value === '') {
|
|
130
133
|
return;
|
|
@@ -149,11 +152,11 @@ export const rangeColumnMixin = dedupingMixin(base => // eslint-disable-line max
|
|
|
149
152
|
|
|
150
153
|
/**
|
|
151
154
|
* Get the comparable value of an item.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
*
|
|
156
|
+
* @param {Object} item Item to be processed
|
|
157
|
+
* @param {String} valuePath Property path
|
|
158
|
+
* @returns {Number|void} Valid value or void
|
|
159
|
+
*/
|
|
157
160
|
getComparableValue(item, valuePath) {
|
|
158
161
|
if (item == null) {
|
|
159
162
|
return;
|
|
@@ -164,27 +167,6 @@ export const rangeColumnMixin = dedupingMixin(base => // eslint-disable-line max
|
|
|
164
167
|
}
|
|
165
168
|
return this.toValue(value);
|
|
166
169
|
}
|
|
167
|
-
|
|
168
|
-
toXlsxValue(item, valuePath = this.valuePath) {
|
|
169
|
-
const value = this.getComparableValue(item, valuePath);
|
|
170
|
-
if (value == null) {
|
|
171
|
-
return '';
|
|
172
|
-
}
|
|
173
|
-
return value;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
getString(item, valuePath = this.valuePath) {
|
|
177
|
-
if (valuePath === undefined) {
|
|
178
|
-
// eslint-disable-next-line no-console
|
|
179
|
-
console.error(this, 'has undefined valuePath', valuePath, 'for item', item);
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
const value = this.get(valuePath, item);
|
|
183
|
-
if (value == null) {
|
|
184
|
-
return '';
|
|
185
|
-
}
|
|
186
|
-
return this.renderValue(value);
|
|
187
|
-
}
|
|
188
170
|
renderValue() {
|
|
189
171
|
//overrideable
|
|
190
172
|
}
|
|
@@ -195,15 +177,15 @@ export const rangeColumnMixin = dedupingMixin(base => // eslint-disable-line max
|
|
|
195
177
|
}
|
|
196
178
|
|
|
197
179
|
/**
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
180
|
+
* Computes min/max range from values.
|
|
181
|
+
*
|
|
182
|
+
* @param {Object} change `values` property changes
|
|
183
|
+
* @returns {Object} Computed min/max
|
|
184
|
+
*/
|
|
203
185
|
_computeRange(change) {
|
|
204
186
|
const allValues = change.base,
|
|
205
187
|
values = Array.isArray(allValues) && allValues.length
|
|
206
|
-
|
|
188
|
+
&& allValues.map(v => this.toValue(v)).filter(n => n != null);
|
|
207
189
|
|
|
208
190
|
if (!values || values.length < 1) {
|
|
209
191
|
return {
|
|
@@ -237,31 +219,6 @@ export const rangeColumnMixin = dedupingMixin(base => // eslint-disable-line max
|
|
|
237
219
|
};
|
|
238
220
|
}
|
|
239
221
|
|
|
240
|
-
getFilterFn() {
|
|
241
|
-
const min = this.getComparableValue(this.filter, 'min'),
|
|
242
|
-
max = this.getComparableValue(this.filter, 'max');
|
|
243
|
-
|
|
244
|
-
if (min == null && max == null) {
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
return this._applySingleFilter.bind(this, this.filter);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
_applySingleFilter(filter, item) {
|
|
251
|
-
const value = this.getComparableValue(item, this.valuePath),
|
|
252
|
-
minValue = this.getComparableValue(filter, 'min'),
|
|
253
|
-
maxValue = this.getComparableValue(filter, 'max');
|
|
254
|
-
|
|
255
|
-
if (value < minValue) {
|
|
256
|
-
return false;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
if (value > maxValue) {
|
|
260
|
-
return false;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return true;
|
|
264
|
-
}
|
|
265
222
|
|
|
266
223
|
_computeFilterText(change) {
|
|
267
224
|
if (change.base == null) {
|
|
@@ -309,12 +266,12 @@ export const rangeColumnMixin = dedupingMixin(base => // eslint-disable-line max
|
|
|
309
266
|
}
|
|
310
267
|
|
|
311
268
|
/**
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
269
|
+
* Observes changes of _filterInput, saves the path, debounces _limitInput.
|
|
270
|
+
*
|
|
271
|
+
* @param {Object} change '_filterInput' property changes
|
|
272
|
+
* @param {Boolean} autoupdate whether to auto-update on value changes
|
|
273
|
+
* @returns {void}
|
|
274
|
+
*/
|
|
318
275
|
_filterInputChanged(change, autoupdate) {
|
|
319
276
|
const path = change.path.split('.')[1];
|
|
320
277
|
this.__inputChangePath = path || null;
|
|
@@ -397,9 +354,10 @@ export const rangeColumnMixin = dedupingMixin(base => // eslint-disable-line max
|
|
|
397
354
|
|
|
398
355
|
|
|
399
356
|
/**
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
357
|
+
* Debounced function called by `_filterInputChanged` when `_filterInput` changes.
|
|
358
|
+
*
|
|
359
|
+
* @returns {void}
|
|
360
|
+
*/
|
|
403
361
|
_limitInput() {
|
|
404
362
|
const input = this._filterInput,
|
|
405
363
|
path = this.__inputChangePath,
|
|
@@ -438,15 +396,12 @@ export const rangeColumnMixin = dedupingMixin(base => // eslint-disable-line max
|
|
|
438
396
|
max = this._fromInputString(input.max, 'max');
|
|
439
397
|
|
|
440
398
|
if (this.getComparableValue(min) === this.getComparableValue(filter, 'min')
|
|
441
|
-
|
|
399
|
+
&& this.getComparableValue(max) === this.getComparableValue(filter, 'max')
|
|
442
400
|
) {
|
|
443
401
|
return;
|
|
444
402
|
}
|
|
445
403
|
|
|
446
|
-
this.set('filter', {
|
|
447
|
-
min,
|
|
448
|
-
max
|
|
449
|
-
});
|
|
404
|
+
this.set('filter', { min, max });
|
|
450
405
|
}
|
|
451
406
|
|
|
452
407
|
_filterChanged(change) {
|
|
@@ -459,7 +414,7 @@ export const rangeColumnMixin = dedupingMixin(base => // eslint-disable-line max
|
|
|
459
414
|
max = this._fromInputString(input.max, 'max');
|
|
460
415
|
|
|
461
416
|
if (this.getComparableValue(min) === this.getComparableValue(filter, 'min')
|
|
462
|
-
|
|
417
|
+
&& this.getComparableValue(max) === this.getComparableValue(filter, 'max')
|
|
463
418
|
) {
|
|
464
419
|
return;
|
|
465
420
|
}
|
|
@@ -474,53 +429,14 @@ export const rangeColumnMixin = dedupingMixin(base => // eslint-disable-line max
|
|
|
474
429
|
}
|
|
475
430
|
|
|
476
431
|
hasFilter() {
|
|
477
|
-
const filter = this.filter
|
|
478
|
-
|
|
479
|
-
if (!isSet) {
|
|
432
|
+
const filter = this.filter;
|
|
433
|
+
if (filter == null) {
|
|
480
434
|
return false;
|
|
481
435
|
}
|
|
482
436
|
return this.toValue(filter.min) != null || this.toValue(filter.max) != null;
|
|
483
437
|
}
|
|
484
438
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
return;
|
|
488
|
-
}
|
|
489
|
-
const min = this.toValue(filter.min),
|
|
490
|
-
max = this.toValue(filter.max);
|
|
491
|
-
|
|
492
|
-
if (min == null && max == null) {
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
|
-
return this._toHashString(min) + '~' + this._toHashString(max);
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
_deserializeFilter(obj) {
|
|
499
|
-
if (obj == null || obj === '') {
|
|
500
|
-
return null;
|
|
501
|
-
}
|
|
502
|
-
const matches = obj.match(/^([^~]+)?~([^~]+)?/iu);
|
|
503
|
-
|
|
504
|
-
if (!Array.isArray(matches)) {
|
|
505
|
-
return null;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
return {
|
|
509
|
-
min: this._fromHashString(matches[1], 'min'),
|
|
510
|
-
max: this._fromHashString(matches[2], 'max')
|
|
511
|
-
};
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
_toHashString(value) {
|
|
515
|
-
const string = this._toInputString(value);
|
|
516
|
-
if (string == null) {
|
|
517
|
-
return '';
|
|
518
|
-
}
|
|
519
|
-
return string;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
_fromHashString(value, property) {
|
|
523
|
-
return this._fromInputString(value, property);
|
|
439
|
+
resetFilter() {
|
|
440
|
+
this.filter = this._getDefaultFilter();
|
|
524
441
|
}
|
|
525
|
-
}
|
|
526
|
-
);
|
|
442
|
+
};
|
|
@@ -5,6 +5,7 @@ import { portal } from '@neovici/cosmoz-utils/lib/directives/portal';
|
|
|
5
5
|
import { useMeta } from '@neovici/cosmoz-utils/lib/hooks/use-meta';
|
|
6
6
|
import { checkbox } from '../cosmoz-omnitable-styles';
|
|
7
7
|
import { nothing } from 'lit-html';
|
|
8
|
+
import { isEmpty } from '@neovici/cosmoz-utils/lib/template';
|
|
8
9
|
const settingsStyles = `
|
|
9
10
|
:host {
|
|
10
11
|
position: fixed;
|
|
@@ -54,6 +55,9 @@ const settingsStyles = `
|
|
|
54
55
|
.title {
|
|
55
56
|
flex: 1;
|
|
56
57
|
}
|
|
58
|
+
.title[has-filter] {
|
|
59
|
+
font-weight: bold;
|
|
60
|
+
}
|
|
57
61
|
${ checkbox }
|
|
58
62
|
.footer {
|
|
59
63
|
display: flex;
|
|
@@ -91,6 +95,8 @@ const settingsStyles = `
|
|
|
91
95
|
onSave: host.onSave,
|
|
92
96
|
onReset: host.onReset,
|
|
93
97
|
|
|
98
|
+
filters: host.filters,
|
|
99
|
+
|
|
94
100
|
onDown: useCallback(e => {
|
|
95
101
|
if (!e.target.closest('.sort')) {
|
|
96
102
|
return;
|
|
@@ -166,7 +172,7 @@ const settingsStyles = `
|
|
|
166
172
|
};
|
|
167
173
|
},
|
|
168
174
|
|
|
169
|
-
renderItem = ({ onDragStart, onDragEnter, onDragOver, onDragLeave, onDrop, onDown, onToggle, collapsed }) => (column, i) => {
|
|
175
|
+
renderItem = ({ onDragStart, onDragEnter, onDragOver, onDragLeave, onDrop, onDown, onToggle, collapsed, filters }) => (column, i) => {
|
|
170
176
|
const indeterminate = collapsed?.includes(column.name),
|
|
171
177
|
checked = !column.disabled && !indeterminate;
|
|
172
178
|
return html`
|
|
@@ -177,7 +183,7 @@ const settingsStyles = `
|
|
|
177
183
|
<button class="sort" mousedown="">
|
|
178
184
|
<svg viewBox="0 0 24 24" width="24"><path d="M20 9H4v2h16V9zM4 15h16v-2H4v2z"></path></svg>
|
|
179
185
|
</button>
|
|
180
|
-
<label class="title">${ column.title }</label>
|
|
186
|
+
<label class="title" ?has-filter=${ !isEmpty(filters[column.name]?.filter) }>${ column.title }</label>
|
|
181
187
|
<input class="checkbox" type="checkbox" .checked=${ checked } @click=${ onToggle }
|
|
182
188
|
.indeterminate=${ indeterminate } .windeterminate=${ indeterminate }
|
|
183
189
|
>
|
|
@@ -201,7 +207,7 @@ const settingsStyles = `
|
|
|
201
207
|
},
|
|
202
208
|
|
|
203
209
|
Settings = host => {
|
|
204
|
-
const { settings, onSettings, onSave, onReset, collapsed, settingsId, hasChanges } = host,
|
|
210
|
+
const { settings, onSettings, onSave, onReset, collapsed, settingsId, hasChanges, badge, filters } = host,
|
|
205
211
|
{ active, onFocus, onToggle } = useFocus(host),
|
|
206
212
|
anchor = useCallback(() => host.shadowRoot.querySelector('.anchor'), []);
|
|
207
213
|
|
|
@@ -216,19 +222,31 @@ const settingsStyles = `
|
|
|
216
222
|
outline: none;
|
|
217
223
|
background: transparent;
|
|
218
224
|
cursor: pointer;
|
|
225
|
+
position: relative;
|
|
219
226
|
}
|
|
220
227
|
.anchor * { pointer-events: none; }
|
|
221
228
|
.anchor:hover {
|
|
222
229
|
background: #eee;
|
|
223
230
|
}
|
|
231
|
+
.badge {
|
|
232
|
+
position: absolute;
|
|
233
|
+
top: 1px;
|
|
234
|
+
right: -4px;
|
|
235
|
+
background-color: var(--paper-checkbox-checked-color, var(--primary-color));
|
|
236
|
+
width: 8px;
|
|
237
|
+
height: 8px;
|
|
238
|
+
border-radius: 100%;
|
|
239
|
+
}
|
|
224
240
|
</style>
|
|
225
|
-
|
|
241
|
+
<button class="anchor" @focus=${ onFocus } @blur=${ onFocus } @click=${ onToggle } @mousedown=${ preventDefault }>
|
|
226
242
|
<svg viewBox="0 0 24 24" width="24"><path d="M10 18h5V5h-5v13zm-6 0h5V5H4v13zM16 5v13h5V5h-5z"></path></svg>
|
|
243
|
+
${ badge ? html`<div class="badge"></div>` : nothing }
|
|
227
244
|
</button>
|
|
228
245
|
${ active
|
|
229
246
|
? portal(html`<cosmoz-omnitable-settings-ui
|
|
230
247
|
tabindex="-1" .anchor=${ anchor } .settings=${ settings } .onSettings=${ onSettings } .collapsed=${ collapsed }
|
|
231
248
|
.settingsId=${ settingsId } .hasChanges=${ hasChanges } .onSave=${ onSave } .onReset=${ onReset }
|
|
249
|
+
.filters=${ filters }
|
|
232
250
|
@focusin=${ onFocus } @focusout=${ onFocus } data-dropdown>`)
|
|
233
251
|
: [] }
|
|
234
252
|
`;
|
|
@@ -0,0 +1,130 @@
|
|
|
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 TimeRangeInput 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
|
+
>
|
|
29
|
+
<div class="dropdown-content" slot="dropdown-content" style="padding: 15px; min-width: 100px;">
|
|
30
|
+
<h3 style="margin: 0;">${ this.title }</h3>
|
|
31
|
+
<paper-input
|
|
32
|
+
type="time"
|
|
33
|
+
label=${ _('From time') }
|
|
34
|
+
step=${ this.filterStep }
|
|
35
|
+
.value=${ this._filterInput.min }
|
|
36
|
+
@value-changed=${ event => this.set('_filterInput.min', event.detail.value) }
|
|
37
|
+
></paper-input>
|
|
38
|
+
<paper-input
|
|
39
|
+
type="time"
|
|
40
|
+
label=${ _('Until time') }
|
|
41
|
+
step=${ this.filterStep }
|
|
42
|
+
.value=${ this._filterInput.max }
|
|
43
|
+
@value-changed=${ event => this.set('_filterInput.max', event.detail.value) }
|
|
44
|
+
></paper-input>
|
|
45
|
+
</div>
|
|
46
|
+
</paper-dropdown-menu>
|
|
47
|
+
`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get _fixedDate() {
|
|
51
|
+
return '1970-01-01';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Converts time to date optionaly limiting it.
|
|
56
|
+
*
|
|
57
|
+
* @param {Date|Number} value Date or Timestamp ( miliseconds since property _fixedDate ) to be converted
|
|
58
|
+
* @param {Date|Number} limit Optional value to limit the date.
|
|
59
|
+
* @param {Function} limitFunc Function used to limit the date (Math.min|Math.max)
|
|
60
|
+
* @returns {Date|void} Value converted to date optionaly limitated
|
|
61
|
+
*/
|
|
62
|
+
toDate(value, limit, limitFunc) {
|
|
63
|
+
// Most browsers use local timezone when no timezone is specified
|
|
64
|
+
// but Safari uses UTC, so we set it implicitly
|
|
65
|
+
// TODO: Consider removing this when/if Safari handles local timezone correctly
|
|
66
|
+
const date = typeof value === 'string' && value.length > 3 && value.length <= 9
|
|
67
|
+
? this.getAbsoluteISOString(this._fixedDate + 'T' + value)
|
|
68
|
+
: value;
|
|
69
|
+
return super.toDate(date, limit, limitFunc);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
_toInputString(value) {
|
|
73
|
+
const date = this.toValue(value);
|
|
74
|
+
if (date == null) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
return this._toLocalISOString(date).slice(11, 19);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get the comparable value of an item.
|
|
81
|
+
*
|
|
82
|
+
* @param {Object} item Item to be processed
|
|
83
|
+
* @param {String} valuePath Property path
|
|
84
|
+
* @returns {Number|void} Valid value or void
|
|
85
|
+
*/
|
|
86
|
+
getComparableValue(item, valuePath) {
|
|
87
|
+
if (item == null) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
let value = this._toInputString(valuePath == null ? item : this.get(valuePath, item));
|
|
91
|
+
if (value == null) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
value = this.toValue(this.getAbsoluteISOString(this._fixedDate + 'T' + value));
|
|
95
|
+
if (value == null) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
return this.toNumber(value.getTime());
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
_timeValueChanged(e) {
|
|
102
|
+
const input = e.target,
|
|
103
|
+
timeString = input.value,
|
|
104
|
+
item = e.model.item,
|
|
105
|
+
oldTime = this.toDate(item.date),
|
|
106
|
+
newTime = this.toDate(oldTime != null
|
|
107
|
+
? oldTime.toISOString().slice(0, 10) + 'T' + timeString
|
|
108
|
+
: timeString),
|
|
109
|
+
formatFn = value => value;
|
|
110
|
+
if (newTime != null) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this.set(this.valuePath, newTime, item);
|
|
114
|
+
this._fireItemChangeEvent(item, this.valuePath, oldTime, formatFn.bind(this));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// OVERRIDES
|
|
118
|
+
|
|
119
|
+
_computeFormatter(locale) {
|
|
120
|
+
const timeFormatOption = {
|
|
121
|
+
hour: 'numeric',
|
|
122
|
+
minute: 'numeric',
|
|
123
|
+
second: 'numeric'
|
|
124
|
+
};
|
|
125
|
+
return new Intl.DateTimeFormat(locale || undefined, timeFormatOption);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
customElements.define('cosmoz-omnitable-time-range-input', TimeRangeInput);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// eslint-disable-next-line max-statements
|
|
2
|
+
export const genericSorter = (a, b) => {
|
|
3
|
+
if (a === b) {
|
|
4
|
+
return 0;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
if (a == null) {
|
|
8
|
+
return -1;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (b == null) {
|
|
12
|
+
return 1;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (typeof a === 'object' && typeof b === 'object') {
|
|
16
|
+
// HACK(pasleq): worst case, compare using values converted to string
|
|
17
|
+
return a.toString() < b.toString() ? -1 : 1;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (typeof a === 'number' && typeof b === 'number') {
|
|
21
|
+
return a - b;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (typeof a === 'string' && typeof b === 'string') {
|
|
25
|
+
return a < b ? -1 : 1;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (typeof a === 'boolean' && typeof b === 'boolean') {
|
|
29
|
+
return a ? -1 : 1;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// eslint-disable-next-line no-console
|
|
33
|
+
console.warn('unsupported sort', typeof a, a, typeof b, b);
|
|
34
|
+
return 0;
|
|
35
|
+
};
|
package/lib/invoke.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const invoke = (fn, ...args) => typeof fn === 'function' ? fn(...args) : fn;
|
package/lib/memoize.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export const
|
|
2
|
+
memoize = fn => {
|
|
3
|
+
let
|
|
4
|
+
lastArg,
|
|
5
|
+
lastResult;
|
|
6
|
+
return function (arg) {
|
|
7
|
+
if (lastArg === arg) {
|
|
8
|
+
return lastResult;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const result = fn(arg);
|
|
12
|
+
lastResult = result;
|
|
13
|
+
lastArg = arg;
|
|
14
|
+
return result;
|
|
15
|
+
};
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
memooize = fn => {
|
|
19
|
+
let
|
|
20
|
+
lastArg1,
|
|
21
|
+
lastArg2,
|
|
22
|
+
lastResult;
|
|
23
|
+
return function (arg1, arg2) {
|
|
24
|
+
if (lastArg1 === arg1 && lastArg2 === arg2) {
|
|
25
|
+
return lastResult;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const result = fn(arg1, arg2);
|
|
29
|
+
lastResult = result;
|
|
30
|
+
lastArg1 = arg1;
|
|
31
|
+
lastArg2 = arg2;
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
memoooize = fn => {
|
|
37
|
+
let
|
|
38
|
+
lastArg1,
|
|
39
|
+
lastArg2,
|
|
40
|
+
lastArg3,
|
|
41
|
+
lastResult;
|
|
42
|
+
return function (arg1, arg2, arg3) {
|
|
43
|
+
if (lastArg1 === arg1 && lastArg2 === arg2 && lastArg3 === arg3) {
|
|
44
|
+
return lastResult;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const result = fn(arg1, arg2, arg3);
|
|
48
|
+
lastResult = result;
|
|
49
|
+
lastArg1 = arg1;
|
|
50
|
+
lastArg2 = arg2;
|
|
51
|
+
lastArg3 = arg3;
|
|
52
|
+
return result;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
import { html } from '@polymer/polymer';
|
|
3
|
+
import { render } from 'lit-html';
|
|
4
|
+
|
|
5
|
+
export const polymerHauntedRender = base => class extends base {
|
|
6
|
+
static get template() {
|
|
7
|
+
return html`<div id="output" style="position:relative;"></div>`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
connectedCallback() {
|
|
11
|
+
super.connectedCallback();
|
|
12
|
+
render(this.render(), this.$.output);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
_propertiesChanged(currentProps, changedProps, oldProps) {
|
|
16
|
+
super._propertiesChanged(currentProps, changedProps, oldProps);
|
|
17
|
+
requestAnimationFrame(() => render(this.render(), this.$.output));
|
|
18
|
+
}
|
|
19
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { saveAs } from 'file-saver-es';
|
|
2
|
+
|
|
3
|
+
const
|
|
4
|
+
makeCsvField = str => {
|
|
5
|
+
const result = str.replace(/"/gu, '""');
|
|
6
|
+
if (result.search(/("|,|\n)/gu) >= 0) {
|
|
7
|
+
return '"' + result + '"';
|
|
8
|
+
}
|
|
9
|
+
return str;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const
|
|
13
|
+
saveAsCsvAction = (columns, selectedItems, csvFilename) => {
|
|
14
|
+
const separator = ';',
|
|
15
|
+
lf = '\n',
|
|
16
|
+
header = columns.map(col => makeCsvField(col.title)).join(separator) + lf,
|
|
17
|
+
rows = selectedItems.map(item => {
|
|
18
|
+
return columns.map(column => {
|
|
19
|
+
const cell = column.getString(column, item);
|
|
20
|
+
if (cell === undefined || cell === null) {
|
|
21
|
+
return '';
|
|
22
|
+
}
|
|
23
|
+
return makeCsvField(String(cell));
|
|
24
|
+
}).join(separator) + lf;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
rows.unshift(header);
|
|
28
|
+
|
|
29
|
+
saveAs(new File(rows, csvFilename, {
|
|
30
|
+
type: 'text/csv;charset=utf-8'
|
|
31
|
+
}));
|
|
32
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { saveAs } from 'file-saver-es';
|
|
2
|
+
import { NullXlsx } from '@neovici/nullxlsx';
|
|
3
|
+
|
|
4
|
+
export const
|
|
5
|
+
prepareXlsxData = (columns, selectedItems) => {
|
|
6
|
+
const headers = columns.map(col => col.title),
|
|
7
|
+
data = selectedItems.map(item =>
|
|
8
|
+
columns.map(column => {
|
|
9
|
+
const value = column.toXlsxValue(column, item);
|
|
10
|
+
return value == null ? '' : value;
|
|
11
|
+
})
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
data.unshift(headers);
|
|
15
|
+
return data;
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
saveAsXlsxAction = (column, selectedItems, xlsxFilename, xlsxSheetname) => {
|
|
19
|
+
const data = prepareXlsxData(column, selectedItems),
|
|
20
|
+
xlsx = new NullXlsx(xlsxFilename).addSheetFromData(data, xlsxSheetname).generate();
|
|
21
|
+
|
|
22
|
+
saveAs(new File([xlsx], xlsxFilename, {
|
|
23
|
+
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
24
|
+
}));
|
|
25
|
+
};
|
package/lib/use-canvas-width.js
CHANGED
|
@@ -2,7 +2,7 @@ import { useState } from 'haunted';
|
|
|
2
2
|
import { useTrackSize } from './use-track-size';
|
|
3
3
|
|
|
4
4
|
export const useCanvasWidth = host => {
|
|
5
|
-
const [canvasWidth, setCanvasWidth] = useState(
|
|
5
|
+
const [canvasWidth, setCanvasWidth] = useState(() => host.getBoundingClientRect().width);
|
|
6
6
|
|
|
7
7
|
useTrackSize(host, setCanvasWidth);
|
|
8
8
|
|