@neovici/cosmoz-omnitable 7.2.1 → 8.0.0-beta.1

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 (50) 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 +72 -763
  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 -123
  27. package/lib/cosmoz-omnitable-settings.js +7 -4
  28. package/lib/cosmoz-omnitable-time-range-input.js +130 -0
  29. package/lib/generic-sorter.js +2 -2
  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 +133 -0
  37. package/lib/use-hash-state.js +59 -0
  38. package/lib/use-layout.js +1 -1
  39. package/lib/use-omnitable.js +26 -14
  40. package/lib/use-processed-items.js +132 -0
  41. package/lib/use-sort-and-group-options.js +30 -0
  42. package/lib/utils-amount.js +147 -0
  43. package/lib/utils-data.js +36 -0
  44. package/lib/utils-date.js +204 -0
  45. package/lib/utils-datetime.js +71 -0
  46. package/lib/utils-number.js +112 -0
  47. package/lib/utils-time.js +115 -0
  48. package/package.json +1 -1
  49. package/lib/use-force-render.js +0 -8
  50. package/lib/use-render-on-column-updates.js +0 -18
@@ -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 rangeColumnMixin = dedupingMixin(base => // eslint-disable-line max-lines-per-function
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
- bindValues: {
27
- type: Boolean,
28
- readOnly: true,
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
- * Converts a value to number optionaly limiting it.
122
- *
123
- * @param {Number|*} value The value to convert to number
124
- * @param {Number|*} limit The value used to limit the number
125
- * @param {Function} limitFunc The function used to limit the number (Math.min|Math.max)
126
- * @returns {Number|void} Value converted to Number or void
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
- * @param {Object} item Item to be processed
154
- * @param {String} valuePath Property path
155
- * @returns {Number|void} Valid value or void
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
- * Computes min/max range from values.
199
- *
200
- * @param {Object} change `values` property changes
201
- * @returns {Object} Computed min/max
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
- && allValues.map(v => this.toValue(v)).filter(n => n != null);
188
+ && allValues.map(v => this.toValue(v)).filter(n => n != null);
207
189
 
208
190
  if (!values || values.length < 1) {
209
191
  return {
@@ -237,25 +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
- if (value == null) {
253
- return false;
254
- }
255
- const min = this.getComparableValue(filter, 'min'),
256
- max = this.getComparableValue(filter, 'max');
257
- return !(value < min || value > max);
258
- }
259
222
 
260
223
  _computeFilterText(change) {
261
224
  if (change.base == null) {
@@ -303,12 +266,12 @@ export const rangeColumnMixin = dedupingMixin(base => // eslint-disable-line max
303
266
  }
304
267
 
305
268
  /**
306
- * Observes changes of _filterInput, saves the path, debounces _limitInput.
307
- *
308
- * @param {Object} change '_filterInput' property changes
309
- * @param {Boolean} autoupdate whether to auto-update on value changes
310
- * @returns {void}
311
- */
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
+ */
312
275
  _filterInputChanged(change, autoupdate) {
313
276
  const path = change.path.split('.')[1];
314
277
  this.__inputChangePath = path || null;
@@ -391,9 +354,10 @@ export const rangeColumnMixin = dedupingMixin(base => // eslint-disable-line max
391
354
 
392
355
 
393
356
  /**
394
- * Debounced function called by `_filterInputChanged` when `_filterInput` changes.
395
- * @returns {void}
396
- */
357
+ * Debounced function called by `_filterInputChanged` when `_filterInput` changes.
358
+ *
359
+ * @returns {void}
360
+ */
397
361
  _limitInput() {
398
362
  const input = this._filterInput,
399
363
  path = this.__inputChangePath,
@@ -432,15 +396,12 @@ export const rangeColumnMixin = dedupingMixin(base => // eslint-disable-line max
432
396
  max = this._fromInputString(input.max, 'max');
433
397
 
434
398
  if (this.getComparableValue(min) === this.getComparableValue(filter, 'min')
435
- && this.getComparableValue(max) === this.getComparableValue(filter, 'max')
399
+ && this.getComparableValue(max) === this.getComparableValue(filter, 'max')
436
400
  ) {
437
401
  return;
438
402
  }
439
403
 
440
- this.set('filter', {
441
- min,
442
- max
443
- });
404
+ this.set('filter', { min, max });
444
405
  }
445
406
 
446
407
  _filterChanged(change) {
@@ -453,7 +414,7 @@ export const rangeColumnMixin = dedupingMixin(base => // eslint-disable-line max
453
414
  max = this._fromInputString(input.max, 'max');
454
415
 
455
416
  if (this.getComparableValue(min) === this.getComparableValue(filter, 'min')
456
- && this.getComparableValue(max) === this.getComparableValue(filter, 'max')
417
+ && this.getComparableValue(max) === this.getComparableValue(filter, 'max')
457
418
  ) {
458
419
  return;
459
420
  }
@@ -468,53 +429,14 @@ export const rangeColumnMixin = dedupingMixin(base => // eslint-disable-line max
468
429
  }
469
430
 
470
431
  hasFilter() {
471
- const filter = this.filter,
472
- isSet = super.hasFilter.apply(this, arguments);
473
- if (!isSet) {
432
+ const filter = this.filter;
433
+ if (filter == null) {
474
434
  return false;
475
435
  }
476
436
  return this.toValue(filter.min) != null || this.toValue(filter.max) != null;
477
437
  }
478
438
 
479
- _serializeFilter(filter = this.filter) {
480
- if (filter == null) {
481
- return;
482
- }
483
- const min = this.toValue(filter.min),
484
- max = this.toValue(filter.max);
485
-
486
- if (min == null && max == null) {
487
- return;
488
- }
489
- return this._toHashString(min) + '~' + this._toHashString(max);
490
- }
491
-
492
- _deserializeFilter(obj) {
493
- if (obj == null || obj === '') {
494
- return null;
495
- }
496
- const matches = obj.match(/^([^~]+)?~([^~]+)?/iu);
497
-
498
- if (!Array.isArray(matches)) {
499
- return null;
500
- }
501
-
502
- return {
503
- min: this._fromHashString(matches[1], 'min'),
504
- max: this._fromHashString(matches[2], 'max')
505
- };
506
- }
507
-
508
- _toHashString(value) {
509
- const string = this._toInputString(value);
510
- if (string == null) {
511
- return '';
512
- }
513
- return string;
514
- }
515
-
516
- _fromHashString(value, property) {
517
- return this._fromInputString(value, property);
439
+ resetFilter() {
440
+ this.filter = this._getDefaultFilter();
518
441
  }
519
- }
520
- );
442
+ };
@@ -5,7 +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 { columnSymbol } from './normalize-settings';
8
+ import { isEmpty } from '@neovici/cosmoz-utils/lib/template';
9
9
  const settingsStyles = `
10
10
  :host {
11
11
  position: fixed;
@@ -95,6 +95,8 @@ const settingsStyles = `
95
95
  onSave: host.onSave,
96
96
  onReset: host.onReset,
97
97
 
98
+ filters: host.filters,
99
+
98
100
  onDown: useCallback(e => {
99
101
  if (!e.target.closest('.sort')) {
100
102
  return;
@@ -170,7 +172,7 @@ const settingsStyles = `
170
172
  };
171
173
  },
172
174
 
173
- renderItem = ({ onDragStart, onDragEnter, onDragOver, onDragLeave, onDrop, onDown, onToggle, collapsed }) => (column, i) => {
175
+ renderItem = ({ onDragStart, onDragEnter, onDragOver, onDragLeave, onDrop, onDown, onToggle, collapsed, filters }) => (column, i) => {
174
176
  const indeterminate = collapsed?.includes(column.name),
175
177
  checked = !column.disabled && !indeterminate;
176
178
  return html`
@@ -181,7 +183,7 @@ const settingsStyles = `
181
183
  <button class="sort" mousedown="">
182
184
  <svg viewBox="0 0 24 24" width="24"><path d="M20 9H4v2h16V9zM4 15h16v-2H4v2z"></path></svg>
183
185
  </button>
184
- <label class="title" ?has-filter=${ !!column[columnSymbol].getFilterFn() }>${ column.title }</label>
186
+ <label class="title" ?has-filter=${ !isEmpty(filters[column.name]?.filter) }>${ column.title }</label>
185
187
  <input class="checkbox" type="checkbox" .checked=${ checked } @click=${ onToggle }
186
188
  .indeterminate=${ indeterminate } .windeterminate=${ indeterminate }
187
189
  >
@@ -205,7 +207,7 @@ const settingsStyles = `
205
207
  },
206
208
 
207
209
  Settings = host => {
208
- const { settings, onSettings, onSave, onReset, collapsed, settingsId, hasChanges, badge } = host,
210
+ const { settings, onSettings, onSave, onReset, collapsed, settingsId, hasChanges, badge, filters } = host,
209
211
  { active, onFocus, onToggle } = useFocus(host),
210
212
  anchor = useCallback(() => host.shadowRoot.querySelector('.anchor'), []);
211
213
 
@@ -244,6 +246,7 @@ const settingsStyles = `
244
246
  ? portal(html`<cosmoz-omnitable-settings-ui
245
247
  tabindex="-1" .anchor=${ anchor } .settings=${ settings } .onSettings=${ onSettings } .collapsed=${ collapsed }
246
248
  .settingsId=${ settingsId } .hasChanges=${ hasChanges } .onSave=${ onSave } .onReset=${ onReset }
249
+ .filters=${ filters }
247
250
  @focusin=${ onFocus } @focusout=${ onFocus } data-dropdown>`)
248
251
  : [] }
249
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);
@@ -4,11 +4,11 @@ export const genericSorter = (a, b) => {
4
4
  return 0;
5
5
  }
6
6
 
7
- if (a === undefined) {
7
+ if (a == null) {
8
8
  return -1;
9
9
  }
10
10
 
11
- if (b === undefined) {
11
+ if (b == null) {
12
12
  return 1;
13
13
  }
14
14
 
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
+ };
@@ -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(0);
5
+ const [canvasWidth, setCanvasWidth] = useState(() => host.getBoundingClientRect().width);
6
6
 
7
7
  useTrackSize(host, setCanvasWidth);
8
8