kempo 2.9.4 → 2.10.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 (116) hide show
  1. package/dist/components/Dialog.js +1 -1
  2. package/dist/components/Icon.js +1 -1
  3. package/dist/components/Search.js +1 -1
  4. package/dist/components/Sortable.js +1 -0
  5. package/dist/components/Table.js +1 -1
  6. package/dist/components/tableControls/DeleteRecord.js +1 -0
  7. package/dist/components/tableControls/DeleteSelected.js +1 -1
  8. package/dist/components/tableControls/Edit.js +1 -0
  9. package/dist/components/tableControls/ExportCSV.js +1 -0
  10. package/dist/components/tableControls/ExportJson.js +1 -0
  11. package/dist/components/tableControls/FieldSortHide.js +1 -0
  12. package/dist/components/tableControls/Filters.js +1 -0
  13. package/dist/components/tableControls/FirstPage.js +1 -0
  14. package/dist/components/tableControls/HiddenCount.js +1 -0
  15. package/dist/components/tableControls/Hide.js +1 -0
  16. package/dist/components/tableControls/LastPage.js +1 -0
  17. package/dist/components/tableControls/NextPage.js +1 -0
  18. package/dist/components/tableControls/PageSelect.js +1 -1
  19. package/dist/components/tableControls/PageSize.js +1 -1
  20. package/dist/components/tableControls/PrevPage.js +1 -0
  21. package/dist/components/tableControls/Search.js +1 -0
  22. package/dist/components/tableControls/SelectCount.js +1 -1
  23. package/dist/components/tableControls/ShowAll.js +1 -0
  24. package/dist/components/tableControls/ShowHideFields.js +1 -0
  25. package/dist/components/tableControls/TableControl.js +1 -0
  26. package/dist/index.js +1 -1
  27. package/dist/kempo-styles.css +1 -1
  28. package/dist/kempo-vars.css +1 -1
  29. package/docs/components/sortable.html +96 -0
  30. package/docs/components/table.html +108 -182
  31. package/docs/components/tableControls.html +77 -0
  32. package/docs/components/tableCustomFields.html +112 -0
  33. package/docs/components/tableFetchRecords.html +114 -0
  34. package/docs/components/tableFieldSortHide.html +75 -0
  35. package/docs/components/tablePagination.html +100 -0
  36. package/docs/components/tableRecordEditing.html +144 -0
  37. package/docs/components/tableRecordFiltering.html +75 -0
  38. package/docs/components/tableRecordHiding.html +80 -0
  39. package/docs/components/tableRecordSearching.html +75 -0
  40. package/docs/components/tableRecordSelection.html +75 -0
  41. package/docs/components/tableRowControls.html +78 -0
  42. package/docs/components/tableSorting.html +73 -0
  43. package/docs/icons/arrow-down.svg +1 -0
  44. package/docs/icons/arrow-up.svg +1 -0
  45. package/docs/icons/check.svg +1 -0
  46. package/docs/icons/drag-handle.svg +1 -0
  47. package/docs/icons/export-file.svg +1 -0
  48. package/docs/icons/first.svg +1 -0
  49. package/docs/icons/hide.svg +1 -0
  50. package/docs/icons/last.svg +1 -0
  51. package/docs/icons/show.svg +1 -0
  52. package/docs/icons/table-visibility.svg +1 -0
  53. package/docs/kempo/components/Dialog.js +1 -1
  54. package/docs/kempo/components/Icon.js +1 -1
  55. package/docs/kempo/components/Search.js +1 -1
  56. package/docs/kempo/components/Sortable.js +1 -0
  57. package/docs/kempo/components/Table.js +1 -1
  58. package/docs/kempo/components/tableControls/DeleteRecord.js +1 -0
  59. package/docs/kempo/components/tableControls/DeleteSelected.js +1 -1
  60. package/docs/kempo/components/tableControls/Edit.js +1 -0
  61. package/docs/kempo/components/tableControls/ExportCSV.js +1 -0
  62. package/docs/kempo/components/tableControls/ExportJson.js +1 -0
  63. package/docs/kempo/components/tableControls/FieldSortHide.js +1 -0
  64. package/docs/kempo/components/tableControls/Filters.js +1 -0
  65. package/docs/kempo/components/tableControls/FirstPage.js +1 -0
  66. package/docs/kempo/components/tableControls/HiddenCount.js +1 -0
  67. package/docs/kempo/components/tableControls/Hide.js +1 -0
  68. package/docs/kempo/components/tableControls/LastPage.js +1 -0
  69. package/docs/kempo/components/tableControls/NextPage.js +1 -0
  70. package/docs/kempo/components/tableControls/PageSelect.js +1 -1
  71. package/docs/kempo/components/tableControls/PageSize.js +1 -1
  72. package/docs/kempo/components/tableControls/PrevPage.js +1 -0
  73. package/docs/kempo/components/tableControls/Search.js +1 -0
  74. package/docs/kempo/components/tableControls/SelectCount.js +1 -1
  75. package/docs/kempo/components/tableControls/ShowAll.js +1 -0
  76. package/docs/kempo/components/tableControls/ShowHideFields.js +1 -0
  77. package/docs/kempo/components/tableControls/TableControl.js +1 -0
  78. package/docs/kempo/index.js +1 -1
  79. package/docs/kempo/kempo-styles.css +1 -1
  80. package/docs/kempo/kempo-vars.css +1 -1
  81. package/icons/arrow-down.svg +1 -0
  82. package/icons/arrow-up.svg +1 -0
  83. package/icons/check.svg +1 -0
  84. package/icons/drag-handle.svg +1 -0
  85. package/icons/export-file.svg +1 -0
  86. package/icons/first.svg +1 -0
  87. package/icons/hide.svg +1 -0
  88. package/icons/last.svg +1 -0
  89. package/icons/show.svg +1 -0
  90. package/icons/table-visibility.svg +1 -0
  91. package/package.json +1 -1
  92. package/src/components/Dialog.js +12 -4
  93. package/src/components/Icon.js +3 -1
  94. package/src/components/Sortable.js +169 -0
  95. package/src/components/Table.js +703 -247
  96. package/src/components/tableControls/DeleteRecord.js +43 -0
  97. package/src/components/tableControls/DeleteSelected.js +36 -24
  98. package/src/components/tableControls/Edit.js +84 -0
  99. package/src/components/tableControls/ExportCSV.js +91 -0
  100. package/src/components/tableControls/ExportJson.js +62 -0
  101. package/src/components/tableControls/FieldSortHide.js +77 -0
  102. package/src/components/tableControls/Filters.js +129 -0
  103. package/src/components/tableControls/FirstPage.js +69 -0
  104. package/src/components/tableControls/HiddenCount.js +37 -0
  105. package/src/components/tableControls/Hide.js +40 -0
  106. package/src/components/tableControls/LastPage.js +67 -0
  107. package/src/components/tableControls/NextPage.js +69 -0
  108. package/src/components/tableControls/PageSelect.js +55 -34
  109. package/src/components/tableControls/PageSize.js +31 -17
  110. package/src/components/tableControls/PrevPage.js +69 -0
  111. package/src/components/tableControls/Search.js +49 -0
  112. package/src/components/tableControls/SelectCount.js +5 -10
  113. package/src/components/tableControls/ShowAll.js +37 -0
  114. package/src/components/tableControls/TableControl.js +51 -0
  115. package/src/kempo-styles.css +7 -0
  116. package/src/kempo-vars.css +2 -0
@@ -1,106 +1,118 @@
1
1
  import Component from './Component.js';
2
- import Icon from './Icon.js';
3
- import SelectCount from './tableControls/SelectCount.js';
4
- import PageSelect from './tableControls/PageSelect.js';
5
- import PageSize from './tableControls/PageSize.js';
6
- import DeleteSelected from './tableControls/DeleteSelected.js';
7
2
  import { toTitleCase } from '../utils/string.js';
8
- import { onEvent, dispatchEvent } from '../utils/element.js';
3
+ import { onEvent, offEvent, dispatchEvent } from '../utils/element.js';
9
4
 
10
- export default class Table extends Component {
11
- static controls = {
12
- prevPage: {
13
- render: (table) => {
14
- const $button = document.createElement('button');
15
- $button.classList.add('pq', 'no-btn');
16
- $button.appendChild(new Icon('chevron-left'));
17
- onEvent($button, 'click', () => table.prevPage());
18
-
19
- // Disable button if on the first page
20
- onEvent(table, 'pageChange', () => {
21
- $button.disabled = table.getCurrentPage() === 1;
22
- });
23
-
24
- return $button;
25
- }
26
- },
27
- nextPage: {
28
- render: (table) => {
29
- const $button = document.createElement('button');
30
- $button.classList.add('pq', 'no-btn');
31
- $button.appendChild(new Icon('chevron-right'));
32
- onEvent($button, 'click', () => table.nextPage());
33
-
34
- // Disable button if on the last page
35
- onEvent(table, 'pageChange', () => {
36
- $button.disabled = table.getCurrentPage() === table.getTotalPages();
37
- });
38
-
39
- return $button;
40
- }
41
- },
42
- pageSelect: {
43
- render: (table) => new PageSelect(table)
44
- },
45
- pageSize: {
46
- render: (table) => new PageSize(table)
47
- },
48
- selectCount: {
49
- render: (table) => new SelectCount(table)
50
- },
51
- deleteSelected: {
52
- render: (table) => new DeleteSelected(table)
53
- },
54
- spacer: {
55
- html: '<div class="flex"></div>'
56
- }
57
- };
5
+ const selected = Symbol('selected');
6
+ const hidden = Symbol('hidden');
7
+ const index = Symbol('index');
8
+ const editing = Symbol('editing');
58
9
 
10
+ export default class Table extends Component {
59
11
  constructor({
60
12
  records = [],
61
13
  fields = [],
62
- controls = { before: [], after: [], top: [], bottom: [] },
63
- pageSize = 100,
14
+ filters = [],
15
+ enablePages = false,
16
+ pageSize = 50,
64
17
  pageSizeOptions = [10, 25, 50, 100, 500],
65
18
  currentPage = 1,
66
19
  enableSelection = false,
20
+ enableSorting = false
67
21
  } = {}) {
68
22
  super();
69
23
 
70
- this.registerProps({
71
- fields: fields,
72
- records: records,
73
- controls: controls,
24
+ this.registerAttributes({
25
+ enablePages,
74
26
  pageSize,
75
- pageSizeOptions,
76
27
  currentPage,
28
+ pageSizeOptions,
77
29
  enableSelection,
78
- selectedIndexes: [], // Renamed property
30
+ enableSorting
79
31
  });
80
32
 
81
- /* Init */
82
- //
33
+ this.registerProps({
34
+ fields: fields,
35
+ records: records,
36
+ filters: filters,
37
+ sort: [],
38
+ columnSizes: {},
39
+ fetchPending: false
40
+ });
83
41
  }
84
42
  async render(force){
85
43
  if(await super.render(force)){
86
44
  this.setData({
87
45
  records: this.records,
88
46
  fields: this.fields,
89
- controls: this.controls,
90
- pageSize: this.pageSize,
91
- pageSizeOptions: this.pageSizeOptions,
92
- currentPage: this.currentPage
47
+ filters: this.filters
93
48
  });
94
49
  return true;
95
50
  }
96
51
  return false;
97
52
  }
53
+
54
+
55
+ /*
56
+ Render Functions
57
+ */
58
+
98
59
  renderFields(){
99
60
  if(!this.rendered) return;
100
- const beforeControls = this.controls?.before?.length ? '<th></th>' : '';
101
- const afterControls = this.controls?.after?.length ? '<th></th>' : '';
102
- const selectionControl = this.enableSelection ? '<th class="selection"><input type="checkbox" id="select-all"></th>' : '';
103
- this.shadowRoot.getElementById('fields').innerHTML = `${selectionControl}${beforeControls}${this.fields.map(({label})=>`<th>${label}</th>`).join('')}${afterControls}`;
61
+ this.calculateColumnSizes();
62
+ this.hasTopControls()?this.setAttribute('top-controls', 'true'):this.removeAttribute('top-controls');
63
+ this.hasBottomControls()?this.setAttribute('bottom-controls', 'true'):this.removeAttribute('bottom-controls');
64
+
65
+ const $fields = this.shadowRoot.getElementById('fields');
66
+ $fields.innerHTML = '';
67
+
68
+ if(this.enableSelection){
69
+ const $selectDiv = document.createElement('div');
70
+ $selectDiv.classList.add('field', 'controls', 'cell', 'field-select');
71
+ $selectDiv.style.width = '40px';
72
+ $selectDiv.innerHTML = `<input type="checkbox" id="select-all" />`;
73
+ $fields.appendChild($selectDiv);
74
+ }
75
+ if(this.hasBeforeControls()){
76
+ const $beforeDiv = document.createElement('div');
77
+ $beforeDiv.classList.add('field', 'cell', 'field-before-controls');
78
+ $beforeDiv.style.width = this.columnSizes.beforeControls + 'px';
79
+ $fields.appendChild($beforeDiv);
80
+ }
81
+ this.fields.forEach( ({name, label, hidden}) => {
82
+ if (hidden) return; // Skip hidden fields
83
+ const $fieldDiv = document.createElement('div');
84
+ $fieldDiv.classList.add('field', 'cell');
85
+ $fieldDiv.style.width = this.columnSizes[name] + 'px';
86
+ $fieldDiv.innerHTML = label;
87
+ if (this.enableSorting) {
88
+ $fieldDiv.style.cursor = 'pointer';
89
+ const currentSort = this.sort.find(s => s.name === name);
90
+ if (currentSort) {
91
+ $fieldDiv.classList.add(currentSort.asc ? 'sort-asc' : 'sort-desc');
92
+ const $icon = document.createElement('k-icon');
93
+ $icon.classList.add('icon-sort');
94
+ $icon.setAttribute('name', currentSort.asc ? 'arrow-down' : 'arrow-up');
95
+ $fieldDiv.appendChild($icon);
96
+ }
97
+ $fieldDiv.addEventListener('click', () => {
98
+ const currentSort = this.sort.find(s => s.name === name);
99
+ const asc = currentSort ? !currentSort.asc : true;
100
+ this.sortBy(name, asc);
101
+ });
102
+ }
103
+ $fields.appendChild($fieldDiv);
104
+ });
105
+ if(this.hasAfterControls()){
106
+ const $afterDiv = document.createElement('div');
107
+ $afterDiv.classList.add('field', 'cell', 'field-after-controls');
108
+ $afterDiv.style.width = this.columnSizes.afterControls + 'px';
109
+ $fields.appendChild($afterDiv);
110
+ }
111
+
112
+ $fields.style.width =
113
+ this.shadowRoot.getElementById('top').style.width =
114
+ this.shadowRoot.getElementById('bottom').style.width =
115
+ this.shadowRoot.getElementById('records').style.width = this.columnSizes.total + 'px';
104
116
 
105
117
  if (this.enableSelection) {
106
118
  const selectAllCheckbox = this.shadowRoot.getElementById('select-all');
@@ -120,126 +132,173 @@ export default class Table extends Component {
120
132
  });
121
133
  }
122
134
  }
123
- renderRecords(){
124
- if(!this.rendered) return;
135
+
136
+ renderRecords() {
137
+ if (!this.rendered) return;
125
138
  const $records = this.shadowRoot.getElementById('records');
126
139
  $records.innerHTML = '';
127
- const start = (this.currentPage - 1) * this.pageSize;
128
- const end = start + this.pageSize;
129
- const paginatedRecords = this.records.slice(start, end);
130
- let fetchRecords = false;
131
- paginatedRecords.forEach((record, index) => {
132
- const $tr = document.createElement('tr');
133
- if (this.enableSelection) {
134
- const $td = document.createElement('td');
135
- $td.classList.add('selection');
136
- const $checkbox = document.createElement('input');
137
- $checkbox.type = 'checkbox';
138
- $checkbox.checked = this.selectedIndexes.includes(start + index);
139
- $checkbox.addEventListener('change', () => {
140
- if ($checkbox.checked) {
141
- this.selectedIndexes.push(start + index);
142
- } else {
143
- this.selectedIndexes = this.selectedIndexes.filter(i => i !== start + index);
144
- }
145
- dispatchEvent(this, 'selectionChange', { selectedIndexes: this.selectedIndexes });
146
- });
147
- $td.appendChild($checkbox);
148
- $tr.appendChild($td);
149
- }
150
- if(this.controls?.before?.length){
151
- $tr.appendChild(this.renderRowControls(this.controls.before, record, start + index));
152
- }
153
- if(record === null){
154
- const $td = document.createElement('td');
155
- $td.colSpan = this.fields.length;
156
- $td.innerHTML = '<i>Loading...</i>';
157
- fetchRecords = true;
158
- $tr.appendChild($td);
140
+
141
+ let displayedRecords = this.getDisplayedRecords(),
142
+ start = 0,
143
+ end = this.pageSize;
144
+
145
+ if(this.enablePages){
146
+ start = (this.currentPage - 1) * this.pageSize;
147
+ end = start + this.pageSize;
148
+ displayedRecords = displayedRecords.slice(start, end);
149
+ }
150
+
151
+ let fetchStart,
152
+ fetchCount = 0;
153
+ displayedRecords.forEach((record, index) => {
154
+ if(record !== null){
155
+ $records.appendChild(this.renderRecord(record));
159
156
  } else {
160
- this.fields.forEach(({name, formatter, calculator}, index) => {
161
- const $td = document.createElement('td');
162
- let value;
163
- if(calculator){
164
- value = calculator(record, this);
165
- } else {
166
- value = record[name] || '';
167
- if (Array.isArray(value)) {
168
- if(formatter){
169
- value = value.map(formatter).join(', ');
170
- } else {
171
- value = value.join(', ');
172
- }
173
- } else {
174
- if(formatter){
175
- value = formatter(value);
176
- }
177
- }
178
- }
179
- $td.innerHTML = value;
180
- $tr.appendChild($td);
181
- });
182
- }
183
- if(this.controls?.after?.length){
184
- $tr.appendChild(this.renderRowControls(this.controls.after, record, start + index));
157
+ if(!fetchStart) fetchStart = start + index;
158
+ fetchCount++;
159
+ const $div = document.createElement('div');
160
+ $div.classList.add('record', 'fetching');
161
+ $div.innerHTML = '<div class="cell">Loading...</div>';
162
+ $records.appendChild($div);
185
163
  }
186
- $records.appendChild($tr);
187
164
  });
188
- if(fetchRecords){
189
- dispatchEvent(this, 'fetchRecords', { start, end });
165
+ if(fetchStart && !this.fetchPending){
166
+ dispatchEvent(this, 'fetchRecords', {
167
+ start: fetchStart,
168
+ count: fetchCount
169
+ });
190
170
  }
191
171
  }
192
-
193
- renderRowControls(controls = [], record, index) {
194
- if(!this.rendered) return;
195
- const $td = document.createElement('td');
196
- controls.forEach(({ html, icon, action, render }) => {
197
- if(html){
198
- $td.appendChild(document.createRange().createContextualFragment(html));
199
- } else if(render && typeof(render) === 'function'){
200
- const rendered = render(this, record, index);
201
- if(rendered instanceof HTMLElement){
202
- $td.appendChild(rendered);
203
- } else if(typeof(rendered) === 'string'){
204
- $td.appendChild(document.createRange().createContextualFragment(rendered));
172
+
173
+ renderRecord(record){
174
+ const $record = document.createElement('div');
175
+ $record.classList.add('record');
176
+ $record.dataset.index = record[index];
177
+ if (this.enableSelection) {
178
+ const $selectionDiv = document.createElement('div');
179
+ $selectionDiv.style.width = '40px';
180
+ $selectionDiv.classList.add('cell', 'selection', 'controls');
181
+ const $checkbox = document.createElement('input');
182
+ $checkbox.type = 'checkbox';
183
+ $checkbox.checked = record[selected];
184
+ $checkbox.addEventListener('change', (event) => {
185
+ record[selected] = !!event.target.checked;
186
+ dispatchEvent(this, 'selectionChange');
187
+ });
188
+ $selectionDiv.appendChild($checkbox);
189
+ $record.appendChild($selectionDiv);
190
+ }
191
+ if (this.hasBeforeControls()) {
192
+ $record.appendChild(this.renderBeforeControls());
193
+ }
194
+ this.fields.forEach(({ name, formatter, calculator, type, editor, hidden }) => {
195
+ if (hidden) return; // Skip hidden fields
196
+ const $div = document.createElement('div');
197
+ $div.classList.add('cell');
198
+ $div.dataset.field = name;
199
+ $div.style.width = this.columnSizes[name] + 'px';
200
+ let value = record[name] || '';
201
+ if(record[editing]){
202
+ $record.setAttribute('editing', true);
203
+ if (calculator) {
204
+ $div.appendChild(Table.editors.calculated(calculator(record, this)));
205
+ } else if(editor){
206
+ $div.appendChild(editor(value));
207
+ } else {
208
+ let editorGen = Table.editors[type || typeof value];
209
+ if(!editorGen){
210
+ editorGen = Table.editors.string;
211
+ }
212
+ $div.appendChild(editorGen(value));
205
213
  }
206
- } else if(icon){
207
- const $button = document.createElement('button');
208
- $button.classList.add('pq', 'no-btn');
209
- $button.appendChild(new Icon(icon));
210
- if (action) {
211
- onEvent($button, 'click', () => action(this, record, index));
214
+ } else {
215
+ if (calculator) {
216
+ $div.innerHTML = calculator(record, this);
217
+ } else if (formatter) {
218
+ $div.innerHTML = formatter(value);
219
+ } else {
220
+ $div.innerHTML = value;
212
221
  }
213
- $td.appendChild($button);
214
222
  }
223
+ $record.appendChild($div);
215
224
  });
216
- return $td;
225
+ if (this.hasAfterControls()) {
226
+ $record.appendChild(this.renderAfterControls());
227
+ }
228
+ return $record;
217
229
  }
218
230
 
219
- renderControls(controls = [], position) {
220
- if(!this.rendered) return;
221
- const $container = this.shadowRoot.getElementById(position);
222
- $container.innerHTML = '';
223
- controls.forEach(({ html, icon, action, render }) => {
224
- if(html){
225
- $container.appendChild(document.createRange().createContextualFragment(html));
226
- } else if(render && typeof(render) === 'function'){
227
- const rendered = render(this);
228
- if(rendered instanceof HTMLElement){
229
- $container.appendChild(rendered);
230
- } else if(typeof(rendered) === 'string'){
231
- $container.appendChild(document.createRange().createContextualFragment(rendered));
232
- }
233
- } else if(icon){
234
- const $button = document.createElement('button');
235
- $button.classList.add('pq', 'no-btn');
236
- $button.appendChild(new Icon(icon));
237
- if (action) {
238
- onEvent($button, 'click', () => action(this));
231
+ renderBeforeControls(){
232
+ const $div = document.createElement('div');
233
+ $div.style.width = this.columnSizes.beforeControls + 'px';
234
+ $div.classList.add('cell', 'controls', 'controls-before');
235
+ this.querySelectorAll('[slot="before"]').forEach($control => {
236
+ $div.appendChild($control.cloneNode(true));
237
+ });
238
+ return $div;
239
+ }
240
+
241
+ renderAfterControls(){
242
+ const $div = document.createElement('div');
243
+ $div.style.width = this.columnSizes.afterControls + 'px';
244
+ $div.classList.add('cell', 'controls', 'controls-after');
245
+ this.querySelectorAll('[slot="after"]').forEach($control => {
246
+ $div.appendChild($control.cloneNode(true));
247
+ });
248
+ return $div;
249
+ }
250
+
251
+
252
+
253
+
254
+
255
+ hasBeforeControls(){
256
+ return !!this.querySelector('[slot="before"]');
257
+ }
258
+
259
+ hasAfterControls(){
260
+ return !!this.querySelector('[slot="after"]');
261
+ }
262
+
263
+ hasTopControls(){
264
+ return !!this.querySelector('[slot="top"]');
265
+ }
266
+
267
+ hasBottomControls(){
268
+ return !!this.querySelector(':scope > :not([slot])'); // bottom is default slot, detect direct child with no slot
269
+ }
270
+
271
+ editRecord(record){
272
+ record[editing] = true;
273
+ const $currentRecord = this.shadowRoot.querySelector(`.record[data-index="${record[index]}"]`);
274
+ const $newRecord = this.renderRecord(record);
275
+ $currentRecord.replaceWith($newRecord);
276
+ dispatchEvent(this, 'editingChange');
277
+ }
278
+
279
+ saveEditedRecord(record){
280
+ record[editing] = false;
281
+ const $currentRecord = this.shadowRoot.querySelector(`.record[data-index="${record[index]}"]`);
282
+ $currentRecord.querySelectorAll('.cell').forEach(($cell) => {
283
+ const field = $cell.dataset.field;
284
+ const fieldDef = this.fields.find(f => f.name === field);
285
+ if(fieldDef && !fieldDef.calculator){ // calculated fields cannot be udated
286
+ const $input = $cell.children.length === 1 ? $cell.firstChild : $cell.querySelector('input, select');
287
+ if ($input) {
288
+ record[field] = $input.value;
239
289
  }
240
- $container.appendChild($button);
241
290
  }
242
291
  });
292
+ $currentRecord.replaceWith(this.renderRecord(record));
293
+ }
294
+
295
+ cancelEditedRecord(record){
296
+ record[editing] = false;
297
+ this.shadowRoot.querySelector(`.record[data-index="${record[index]}"]`).replaceWith(this.renderRecord(record));
298
+ }
299
+
300
+ recordIsEditing(record){
301
+ return record[editing];
243
302
  }
244
303
 
245
304
  getCurrentPage() {
@@ -247,7 +306,7 @@ export default class Table extends Component {
247
306
  }
248
307
 
249
308
  getTotalPages() {
250
- return Math.ceil(this.records.length / this.pageSize);
309
+ return Math.ceil(this.getDisplayedRecords().length / this.pageSize);
251
310
  }
252
311
 
253
312
  setPage(page) {
@@ -258,6 +317,12 @@ export default class Table extends Component {
258
317
  dispatchEvent(this, 'pageChange');
259
318
  }
260
319
 
320
+ firstPage(){
321
+ if(this.currentPage !== 1){
322
+ this.setPage(1);
323
+ }
324
+ }
325
+
261
326
  nextPage() {
262
327
  if (this.currentPage < this.getTotalPages()) {
263
328
  this.setPage(this.currentPage + 1);
@@ -270,6 +335,12 @@ export default class Table extends Component {
270
335
  }
271
336
  }
272
337
 
338
+ lastPage() {
339
+ if (this.currentPage !== this.getTotalPages()) {
340
+ this.setPage(this.getTotalPages());
341
+ }
342
+ }
343
+
273
344
  setPageSize(pageSize) {
274
345
  this.pageSize = pageSize;
275
346
  this.currentPage = 1;
@@ -281,16 +352,22 @@ export default class Table extends Component {
281
352
  setData({
282
353
  records = false,
283
354
  fields = false,
284
- controls = false,
285
355
  pageSize = false,
286
356
  pageSizeOptions = false,
287
357
  currentPage = false,
288
- enableSelection = false // Add enableSelection property
358
+ enableSelection
289
359
  } = {}) {
290
- let rerender = false;
291
- let rerenderControls = false;
360
+ let rerender = false,
361
+ pageCountBefore = this.getTotalPages(),
362
+ pageBefore = this.currentPage;
292
363
  if(records){
293
- this.records = records;
364
+ this.records = records.map(r=>({...r}));
365
+ this.records.forEach((record, idx) => {
366
+ record[index] = idx;
367
+ record[selected] = false;
368
+ record[hidden] = false;
369
+ record[editing] = false;
370
+ });
294
371
  this.fields = fields || Table.extractFieldsFromRecords(this.records);
295
372
  rerender = true;
296
373
  }
@@ -301,10 +378,6 @@ export default class Table extends Component {
301
378
  if (pageSizeOptions) {
302
379
  this.pageSizeOptions = pageSizeOptions;
303
380
  }
304
- if (controls) {
305
- this.controls = controls;
306
- rerenderControls = true;
307
- }
308
381
  if(currentPage){
309
382
  this.currentPage = currentPage;
310
383
  rerender = true;
@@ -317,17 +390,37 @@ export default class Table extends Component {
317
390
  this.renderFields();
318
391
  this.renderRecords();
319
392
  }
320
- if(rerenderControls){
321
- this.renderControls(this.controls.top, 'top');
322
- this.renderControls(this.controls.bottom, 'bottom');
393
+ const newPageCount = this.getTotalPages();
394
+ if(newPageCount !== pageCountBefore){
395
+ dispatchEvent(this, 'pageCountChanged', { totalPages: this.getTotalPages() });
396
+ }
397
+ if(pageBefore > newPageCount){
398
+ this.setPage(newPageCount);
323
399
  }
324
400
  }
325
401
 
326
- setRecords(records) {
327
- this.records = records;
328
- this.currentPage = 1;
402
+ setRecords(records, fields) {
403
+ let pageCountBefore = this.getTotalPages(),
404
+ pageBefore = this.currentPage;
405
+
406
+ this.records = records.map(r=>({...r}));
407
+ this.records.forEach((record, idx) => {
408
+ record[index] = idx;
409
+ record[selected] = false;
410
+ record[hidden] = false;
411
+ record[editing] = false;
412
+ });
413
+ this.fields = fields || Table.extractFieldsFromRecords(this.records);
414
+ this.renderFields();
329
415
  this.renderRecords();
330
416
  dispatchEvent(this, 'recordsSet', { records });
417
+ const newPageCount = this.getTotalPages();
418
+ if(newPageCount !== pageCountBefore){
419
+ dispatchEvent(this, 'pageCountChanged', { totalPages: this.getTotalPages() });
420
+ }
421
+ if(pageBefore > newPageCount){
422
+ this.setPage(newPageCount);
423
+ }
331
424
  }
332
425
 
333
426
  setupFetchRecords(totalRecords, callback) {
@@ -338,141 +431,504 @@ export default class Table extends Component {
338
431
  }
339
432
 
340
433
  onEvent(this, 'fetchRecords', async (event) => {
341
- const { start, end } = event.detail;
342
- const records = await callback(start, end - start);
434
+ if(this.fetchPending) return;
435
+ this.fetchPending = true;
436
+ const { start, count } = event.detail;
437
+ const records = await callback(start, count);
343
438
  this.records.splice(start, records.length, ...records);
439
+ this.fetchPending = false;
344
440
  this.renderRecords();
345
441
  });
346
442
  }
347
443
 
348
444
  addRecord(record) {
445
+ record[selected] = false;
446
+ record[hidden] = false;
447
+ record[index] = this.records.length;
349
448
  this.records.push(record);
350
449
  this.renderRecords();
351
450
  dispatchEvent(this, 'recordAdded', { record });
352
451
  }
353
452
 
354
- updateRecord(index, newRecord) {
355
- this.records[index] = newRecord;
356
- const start = (this.currentPage - 1) * this.pageSize;
357
- const end = start + this.pageSize;
358
- if (index >= start && index < end) {
359
- this.renderRecords();
453
+ updateRecord(record, newData) {
454
+ let updated = false;
455
+ let originalRecord = this.records.find(r => r === record);
456
+ if (!originalRecord && record[index] !== undefined) {
457
+ originalRecord = this.records[record[index]];
458
+ }
459
+ Object.keys(newData).forEach(key => {
460
+ if (originalRecord.hasOwnProperty(key)) {
461
+ originalRecord[key] = newData[key];
462
+ updated = true;
463
+ }
464
+ });
465
+ if(updated){
466
+ const start = (this.currentPage - 1) * this.pageSize;
467
+ const end = start + this.pageSize;
468
+ if (
469
+ !this.enablePages ||
470
+ (originalRecord[index] >= start && originalRecord[index] < end)
471
+ ) {
472
+ this.renderRecords();
473
+ }
360
474
  }
361
475
  }
362
476
 
363
- deleteRecord(index) {
364
- if (index >= 0 && index < this.records.length) {
365
- // Deselect the record before deleting
366
- this.selectedIndexes = this.selectedIndexes.filter(i => i !== index);
367
- dispatchEvent(this, 'selectionChange', { selectedIndexes: this.selectedIndexes });
368
- this.records.splice(index, 1);
477
+ deleteRecord(record) {
478
+ let originalRecord = this.records.find(r => r === record),
479
+ totalPagesBefore = this.getTotalPages();
480
+ if (!originalRecord && record[index] !== undefined) {
481
+ originalRecord = this.records[record[index]];
482
+ }
483
+ if (originalRecord) {
484
+ const recordIndex = this.records.indexOf(originalRecord);
485
+ this.records.splice(recordIndex, 1);
486
+ // Update the index of remaining records
487
+ this.records.forEach((rec, idx) => {
488
+ rec[index] = idx;
489
+ });
369
490
  this.renderRecords();
370
- dispatchEvent(this, 'recordDeleted', { index });
491
+ dispatchEvent(this, 'selectionChange');
492
+ dispatchEvent(this, 'recordDeleted', { index: recordIndex });
493
+ const totalPages = this.getTotalPages();
494
+ if (this.currentPage > totalPages) {
495
+ this.setPage(totalPages);
496
+ }
497
+ if(totalPages !== totalPagesBefore){
498
+ dispatchEvent(this, 'pageCountChanged', { totalPages });
499
+ }
371
500
  }
372
501
  }
373
502
 
503
+ deleteSelected() {
504
+ let totalPagesBefore = this.getTotalPages();
505
+ const selectedRecords = this.getSelectedRecords();
506
+ selectedRecords.forEach(record => {
507
+ let originalRecord = this.records.find(r => r === record);
508
+ if (!originalRecord && record[index] !== undefined) {
509
+ originalRecord = this.records[record[index]];
510
+ }
511
+ if (originalRecord) {
512
+ const recordIndex = this.records.indexOf(originalRecord);
513
+ this.records.splice(recordIndex, 1);
514
+ }
515
+ });
516
+ this.records.forEach((rec, idx) => {
517
+ rec[index] = idx;
518
+ });
519
+ this.renderRecords();
520
+ const totalPages = this.getTotalPages();
521
+ if (this.currentPage > totalPages) {
522
+ this.setPage(totalPages);
523
+ }
524
+ if(totalPages !== totalPagesBefore){
525
+ dispatchEvent(this, 'pageCountChanged', { totalPages });
526
+ }
527
+ dispatchEvent(this, 'selectionChange');
528
+ }
529
+
374
530
  getSelectedRecords() {
375
- return this.selectedIndexes.map(index => this.records[index]);
531
+ const selectedRecords = this.records.filter(record => {
532
+ const isSelected = record[selected];
533
+ return isSelected;
534
+ });
535
+ return selectedRecords;
376
536
  }
377
537
 
378
538
  selectAllOnPage() {
379
539
  const start = (this.currentPage - 1) * this.pageSize;
380
540
  const end = Math.min(start + this.pageSize, this.records.length);
381
541
  for (let i = start; i < end; i++) {
382
- if (!this.selectedIndexes.includes(i)) {
383
- this.selectedIndexes.push(i);
384
- }
542
+ this.records[i][selected] = true;
385
543
  }
386
544
  this.renderRecords();
387
- dispatchEvent(this, 'selectionChange', { selectedIndexes: this.selectedIndexes });
545
+ setTimeout(() => {
546
+ dispatchEvent(this, 'selectionChange');
547
+ }, 0);
388
548
  }
389
549
 
390
550
  deselectAllOnPage() {
391
551
  const start = (this.currentPage - 1) * this.pageSize;
392
552
  const end = Math.min(start + this.pageSize, this.records.length);
393
- this.selectedIndexes = this.selectedIndexes.filter(index => index < start || index >= end);
553
+ for (let i = start; i < end; i++) {
554
+ this.records[i][selected] = false;
555
+ }
394
556
  this.renderRecords();
395
- dispatchEvent(this, 'selectionChange', { selectedIndexes: this.selectedIndexes });
557
+ setTimeout(() => {
558
+ dispatchEvent(this, 'selectionChange');
559
+ }, 0);
396
560
  }
397
561
 
398
562
  allOnPageSelected() {
399
563
  const start = (this.currentPage - 1) * this.pageSize;
400
564
  const end = Math.min(start + this.pageSize, this.records.length);
401
565
  for (let i = start; i < end; i++) {
402
- if (!this.selectedIndexes.includes(i)) {
566
+ if (!this.records[i][selected]) {
403
567
  return false;
404
568
  }
405
569
  }
406
570
  return true;
407
571
  }
408
572
 
573
+ sortBy(field, asc = true) {
574
+ this.sort = this.sort.filter(item => item.name !== field);
575
+ this.sort.push({ name: field, asc });
576
+ this.renderFields(); // Re-render fields to update sort classes and icons
577
+ this.renderRecords();
578
+ }
579
+
580
+ hideRecord(record) {
581
+ let originalRecord = this.records.find(r => r === record);
582
+ if (!originalRecord && record[index] !== undefined) {
583
+ originalRecord = this.records[record[index]];
584
+ }
585
+ if (originalRecord) {
586
+ originalRecord[hidden] = true;
587
+ this.renderRecords();
588
+ dispatchEvent(this, 'recordHidden');
589
+ }
590
+ }
591
+
592
+ showRecord(record) {
593
+ let originalRecord = this.records.find(r => r === record);
594
+ if (!originalRecord && record[index] !== undefined) {
595
+ originalRecord = this.records[record[index]];
596
+ }
597
+ if (originalRecord) {
598
+ originalRecord[hidden] = false;
599
+ this.renderRecords();
600
+ dispatchEvent(this, 'recordShown');
601
+ }
602
+ }
603
+
604
+ showAllRecords() {
605
+ this.records.forEach(record => {
606
+ record[hidden] = false;
607
+ });
608
+ if(this.filters.length){
609
+ this.filters = [];
610
+ dispatchEvent(this, 'filterRemoved filterChange');
611
+ }
612
+ this.renderRecords();
613
+ dispatchEvent(this, 'recordShown allRecordsShown');
614
+ }
615
+
616
+ addFilter(field, condition, value) {
617
+ this.filters.push({ field, condition, value });
618
+ dispatchEvent(this, 'filterAdded filterChange');
619
+ this.renderRecords();
620
+ }
621
+
622
+ removeFilter(field, condition, value, rerender = true) {
623
+ const filterIndex = this.filters.findIndex(f => f.field === field && f.condition === condition && f.value === value);
624
+ if (filterIndex !== -1) {
625
+ this.records.forEach(record => {
626
+ if(!this.testFilter(record, field, condition, value)){
627
+ record[hidden] = false;
628
+ }
629
+ });
630
+ this.filters.splice(filterIndex, 1);
631
+ dispatchEvent(this, 'filterRemoved filterChange');
632
+ if(rerender) this.renderRecords();
633
+ }
634
+ }
635
+
636
+ testFilter(record, field, condition, value) {
637
+ const recordValue = record[field];
638
+ switch (condition) {
639
+ case 'equals':
640
+ return recordValue === value;
641
+ case 'not-equals':
642
+ return recordValue !== value;
643
+ case 'contains':
644
+ return recordValue.includes(value);
645
+ case 'not-contains':
646
+ return !recordValue.includes(value);
647
+ case 'greater-than':
648
+ return recordValue > value;
649
+ case 'less-than':
650
+ return recordValue < value;
651
+ case 'greater-than-or-equal':
652
+ return recordValue >= value;
653
+ case 'less-than-or-equal':
654
+ return recordValue <= value;
655
+ default:
656
+ return true;
657
+ }
658
+ }
659
+
660
+ removeAllFilters() {
661
+ if(this.filters.length){
662
+ this.filters.forEach(({ field, condition, value }) => {
663
+ this.removeFilter(field, condition, value, false);
664
+ });
665
+ this.renderRecords();
666
+ }
667
+ }
668
+
669
+ search(term) {
670
+ const t = term.trim().toLowerCase();
671
+ let changed = false;
672
+ this.records.forEach(record => {
673
+ if (record[hidden]) return;
674
+ let match = false;
675
+ this.fields.forEach(({ name }) => {
676
+ const val = record[name]?.toString().toLowerCase() || '';
677
+ if (val.includes(t)) {
678
+ match = true;
679
+ }
680
+ });
681
+ if (record[hidden] !== !match) {
682
+ record[hidden] = !match;
683
+ changed = true;
684
+ }
685
+ });
686
+ if (changed) {
687
+ dispatchEvent(this, 'recordHidden');
688
+ this.renderRecords();
689
+ }
690
+ dispatchEvent(this, 'search', { term });
691
+ }
692
+
693
+ getDisplayedRecords() {
694
+ this.filters.forEach(({ field, condition, value }) => {
695
+ this.records.forEach(record => {
696
+ if (!this.testFilter(record, field, condition, value)) {
697
+ record[hidden] = true;
698
+ }
699
+ });
700
+ });
701
+
702
+ let displayedRecords = this.records.filter(record => record === null || !record[hidden]);
703
+
704
+ this.sort.forEach(({ name, asc }) => {
705
+ displayedRecords.sort((a, b) => {
706
+ if (a[name] < b[name]) return asc ? -1 : 1;
707
+ if (a[name] > b[name]) return asc ? 1 : -1;
708
+ return 0;
709
+ });
710
+ });
711
+
712
+ return displayedRecords;
713
+ }
714
+
715
+ getHiddenRecords() {
716
+ return this.records.filter(record => record[hidden]);
717
+ }
718
+
719
+ calculateColumnSizes() {
720
+ this.columnSizes = {};
721
+ this.columnSizes.total = 0;
722
+ if(this.enableSelection) this.columnSizes.total += 40;
723
+ this.columnSizes.beforeControls = Array.from(this.querySelectorAll('[slot="before"]')).reduce((total, el) => total + (el.maxWidth || 40), 0);
724
+ this.columnSizes.afterControls = Array.from(this.querySelectorAll('[slot="after"]')).reduce((total, el) => total + (el.maxWidth || 40), 0);
725
+ if(this.hasBeforeControls()) this.columnSizes.total += this.columnSizes.beforeControls;
726
+ if(this.hasAfterControls()) this.columnSizes.total += this.columnSizes.afterControls;
727
+
728
+ this.fields.forEach(field => {
729
+ if (field.size) {
730
+ this.columnSizes[field.name] = field.size;
731
+ } else {
732
+ let maxLength = 0;
733
+ this.records.slice(0, 100).forEach(record => {
734
+ let value = record[field.name];
735
+ if (field.calculator) {
736
+ value = field.calculator(record, this);
737
+ }
738
+ if (field.formatter) {
739
+ value = field.formatter(value);
740
+ }
741
+ if (value && value.toString().length > maxLength) {
742
+ maxLength = value.toString().length;
743
+ }
744
+ });
745
+ this.columnSizes[field.name] = Math.max((maxLength * 10 + 32), 128);
746
+ if(!field.hidden) this.columnSizes.total += this.columnSizes[field.name];
747
+ }
748
+ });
749
+ return this.columnSizes;
750
+ }
751
+
752
+ setFieldHiddenState(fieldName, hidden) {
753
+ const field = this.fields.find(f => f.name === fieldName);
754
+ if (field) {
755
+ field.hidden = hidden;
756
+ this.calculateColumnSizes();
757
+ this.renderFields();
758
+ this.renderRecords();
759
+ dispatchEvent(this, 'fieldVisibilityChanged ' + (hidden ? 'fieldHidden' : 'fieldShown'), { field });
760
+ }
761
+ }
762
+
763
+ hideField(fieldName) {
764
+ this.setFieldHiddenState(fieldName, true);
765
+ }
766
+
767
+ showField(fieldName) {
768
+ this.setFieldHiddenState(fieldName, false);
769
+ }
770
+
771
+ reorderFields(newOrder) {
772
+ const newFields = [];
773
+ newOrder.forEach(fieldName => {
774
+ const field = this.fields.find(f => f.name === fieldName);
775
+ if (field) {
776
+ newFields.push(field);
777
+ }
778
+ });
779
+ this.fields = newFields;
780
+ this.renderFields();
781
+ this.renderRecords();
782
+ }
783
+
784
+ /* Shadow DOM */
409
785
  get shadowTemplate(){
410
786
  return /*html*/`
411
- ${super.shadowTemplate}
412
- <div class="responsive-table">
413
- <div id="top"></div>
414
- <table class="b0">
415
- <thead id="fields"></thead>
416
- <tbody id="records"></tbody>
417
- </table>
418
- <div id="bottom"></div>
787
+ <div id="wrapper">
788
+ <div id="top"><slot name="top"></slot></div>
789
+ <div id="table">
790
+ <div id="fields"></div>
791
+ <div id="records"></div>
792
+ </div>
793
+ <div id="bottom">${super.shadowTemplate}</div>
794
+ </div>
795
+ <div style="display: none">
796
+ <slot name="before"></slot>
797
+ <slot name="after"></slot>
419
798
  </div>
420
799
  `;
421
800
  }
801
+
422
802
  get shadowStyles(){
423
803
  return /*css*/`
424
804
  ${super.shadowStyles}
425
805
  :host {
426
806
  display: block;
807
+ width: 100%;
808
+ overflow: auto;
427
809
  margin-bottom: var(--spacer);
810
+ }
811
+ #wrapper {
812
+ width: min-content;
428
813
  border: 1px solid var(--c_border);
429
814
  border-radius: var(--radius);
430
815
  }
431
- #top, #bottom {
432
- display: flex;
816
+ #table {
817
+ width: min-content;
433
818
  }
434
- th {
435
- border-top: none;
819
+ #fields,
820
+ .record {
821
+ display: flex;
436
822
  }
437
- tr:last-child td:first-child {
438
- border-bottom-left-radius: 0;
823
+ #fields {
824
+ background-color: var(--c_bg__alt);
825
+ border-bottom: 1px solid var(--c_border);
439
826
  }
440
- tr:last-child td:last-child {
441
- border-bottom-right-radius: 0;
827
+ .record:not([editing="true"]) .cell:not(.controls),
828
+ #fields .cell:not(.controls) {
829
+ padding: calc(0.5 * var(--spacer)) var(--spacer);
442
830
  }
443
- tr:last-child td {
444
- border-bottom: none;
831
+ .cell:not(:first-child) {
832
+ border-left: 1px solid var(--c_border);
445
833
  }
446
- th:first-child, td:first-child {
447
- border-left: none;
834
+ .record:not(:last-child) .cell {
835
+ border-bottom: 1px solid var(--c_border);
448
836
  }
449
- th:last-child, td:last-child {
450
- border-right: none;
837
+ #top, #bottom {
838
+ display: flex;
839
+ width: 100%;
451
840
  }
452
- #top:not(:empty) {
841
+ #top slot {
842
+ display: block;
843
+ width: 100%;
453
844
  border-bottom: 1px solid var(--c_border);
454
845
  }
455
- #bottom:not(:empty) {
846
+ #bottom slot {
847
+ display: block;
848
+ width: 100%;
456
849
  border-top: 1px solid var(--c_border);
457
850
  }
458
- th.selection, td.selection {
459
- width: 50px;
851
+ :host(:not([top-controls])) #top,
852
+ :host(:not([bottom-controls])) #bottom {
853
+ display: none;
854
+ }
855
+ .field-select,
856
+ .selection {
857
+ display: flex;
858
+ justify-content: center;
859
+ align-items: center;
860
+ }
861
+ .field-select input,
862
+ .selection input {
863
+ width: 1.25rem;
864
+ height: 1.25rem;
460
865
  }
461
- th.selection input,
462
- td.selection input {
463
- margin: 0;
464
- width: 1.35rem;
465
- height: 1.35rem;
866
+ .icon-sort {
867
+ float: right;
868
+ opacity: 0.5;
466
869
  }
467
870
  `;
468
871
  }
469
872
 
873
+
874
+ /* Static Methods */
470
875
  static extractFieldsFromRecords(records, recordLimit = 100){
471
876
  const names = new Set();
472
877
  records.slice(0, recordLimit).forEach( record => {
473
878
  Object.keys(record).forEach( name => names.add(name) )
474
879
  });
475
880
  return [...names].map(name=>({name,label:toTitleCase(name)}));
476
- }
881
+ };
882
+
883
+ /* Static Members */
884
+ static format(value){
885
+ const f = Array.isArray(value) ? Table.formatters.array : Table.formatters[typeof value];
886
+ return f(value);
887
+ };
888
+
889
+ static formatters = {
890
+ string: v=>v,
891
+ number: v=>`${v}`,
892
+ date: v=>v.toLocaleDateString(),
893
+ boolean: v=>v ? 'True' : 'False',
894
+ array: v=>v.map(i=>Table.format(i)).join(', '),
895
+ 'undefined': v=>'',
896
+ 'null': v=>'<code>null</code>'
897
+ };
898
+
899
+ static editors = {
900
+ string: (value) => {
901
+ const $i = document.createElement('input');
902
+ $i.value = value;
903
+ return $i;
904
+ },
905
+ number: (value) => {
906
+ const $i = document.createElement('input');
907
+ $i.type = 'number';
908
+ $i.value = value;
909
+ return $i;
910
+ },
911
+ date: (value) => {
912
+ const $i = document.createElement('input');
913
+ $i.type = 'date';
914
+ $i.value = value;
915
+ return $i;
916
+ },
917
+ boolean: (value) => {
918
+ const $i = document.createElement('select');
919
+ $i.innerHTML = `
920
+ <option value="true" ${value ? 'selected' : ''}>True</option>
921
+ <option value="false" ${!value ? 'selected' : ''}>False</option>
922
+ `;
923
+ $i.value = value;
924
+ return $i;
925
+ },
926
+ calculated: (value) => {
927
+ const $i = document.createElement('input');
928
+ $i.disabled = true;
929
+ $i.value = value;
930
+ return $i;
931
+ }
932
+ };
477
933
  }
478
934
  window.customElements.define('k-table', Table);