kempo 2.9.2 → 2.10.0

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 (125) hide show
  1. package/dist/components/Accordion.js +1 -1
  2. package/dist/components/Dialog.js +1 -1
  3. package/dist/components/DirectoryViewer.js +1 -1
  4. package/dist/components/FocusCapture.js +1 -1
  5. package/dist/components/Icon.js +1 -1
  6. package/dist/components/Search.js +1 -1
  7. package/dist/components/Table.js +1 -1
  8. package/dist/components/Timestamp.js +1 -1
  9. package/dist/components/Toggle.js +1 -1
  10. package/dist/components/tableControls/DeleteSelected.js +1 -1
  11. package/dist/components/tableControls/Edit.js +1 -0
  12. package/dist/components/tableControls/Filters.js +1 -0
  13. package/dist/components/tableControls/HiddenCount.js +1 -0
  14. package/dist/components/tableControls/Hide.js +1 -0
  15. package/dist/components/tableControls/PageSelect.js +1 -1
  16. package/dist/components/tableControls/PageSize.js +1 -1
  17. package/dist/components/tableControls/ShowAll.js +1 -0
  18. package/dist/index.js +1 -1
  19. package/dist/kempo-styles.css +1 -1
  20. package/dist/kempo-vars.css +1 -1
  21. package/dist/utils/formatTimestamp.js +1 -1
  22. package/docs/components/_template.html +9 -0
  23. package/docs/components/accordion.html +52 -12
  24. package/docs/components/card.html +68 -8
  25. package/docs/components/component.html +6 -0
  26. package/docs/components/content-slider.html +8 -0
  27. package/docs/components/dialog.html +145 -5
  28. package/docs/components/directory-viewer.html +51 -40
  29. package/docs/components/focus-capture.html +68 -0
  30. package/docs/components/icon.html +76 -5
  31. package/docs/components/import.html +47 -0
  32. package/docs/components/lazy-component.html +45 -15
  33. package/docs/components/reactive-component.html +69 -0
  34. package/docs/components/reactive-lazy-component.html +69 -0
  35. package/docs/components/resize.html +52 -3
  36. package/docs/components/search.html +102 -0
  37. package/docs/components/show-more.html +54 -5
  38. package/docs/components/{sidemenu.html → side-menu.html} +56 -3
  39. package/docs/components/split.html +57 -16
  40. package/docs/components/table.html +90 -189
  41. package/docs/components/tableControls.html +91 -0
  42. package/docs/components/tableCustomFields.html +112 -0
  43. package/docs/components/tablePagination.html +92 -0
  44. package/docs/components/tableRecordEditing.html +144 -0
  45. package/docs/components/tableRecordFiltering.html +82 -0
  46. package/docs/components/tableRecordHiding.html +84 -0
  47. package/docs/components/tableRecordSelection.html +86 -0
  48. package/docs/components/tableRowControls.html +103 -0
  49. package/docs/components/tabs.html +72 -4
  50. package/docs/components/tags.html +94 -0
  51. package/docs/components/theme-switcher.html +74 -0
  52. package/docs/components/timestamp.html +86 -0
  53. package/docs/components/toggle.html +37 -34
  54. package/docs/icons/check.svg +1 -0
  55. package/docs/icons/hide.svg +1 -0
  56. package/docs/icons/show.svg +1 -0
  57. package/docs/index.html +5 -0
  58. package/docs/kempo/components/Accordion.js +1 -1
  59. package/docs/kempo/components/Dialog.js +1 -1
  60. package/docs/kempo/components/DirectoryViewer.js +1 -1
  61. package/docs/kempo/components/FocusCapture.js +1 -1
  62. package/docs/kempo/components/Icon.js +1 -1
  63. package/docs/kempo/components/Search.js +1 -1
  64. package/docs/kempo/components/Table.js +1 -1
  65. package/docs/kempo/components/Timestamp.js +1 -1
  66. package/docs/kempo/components/Toggle.js +1 -1
  67. package/docs/kempo/components/tableControls/DeleteSelected.js +1 -1
  68. package/docs/kempo/components/tableControls/Edit.js +1 -0
  69. package/docs/kempo/components/tableControls/Filters.js +1 -0
  70. package/docs/kempo/components/tableControls/HiddenCount.js +1 -0
  71. package/docs/kempo/components/tableControls/Hide.js +1 -0
  72. package/docs/kempo/components/tableControls/PageSelect.js +1 -1
  73. package/docs/kempo/components/tableControls/PageSize.js +1 -1
  74. package/docs/kempo/components/tableControls/ShowAll.js +1 -0
  75. package/docs/kempo/index.js +1 -1
  76. package/docs/kempo/kempo-styles.css +1 -1
  77. package/docs/kempo/kempo-vars.css +1 -1
  78. package/docs/kempo/utils/formatTimestamp.js +1 -1
  79. package/docs/nav-1.inc.html +16 -2
  80. package/docs/nav.inc.html +10 -2
  81. package/docs/search.json +281 -23
  82. package/docs/styles/_template.html +6 -0
  83. package/docs/styles/borders-spacing.html +6 -0
  84. package/docs/styles/buttons.html +6 -0
  85. package/docs/styles/colors.html +6 -0
  86. package/docs/styles/displays-layouts.html +6 -0
  87. package/docs/styles/embed-1.html +6 -0
  88. package/docs/styles/inputs.html +6 -0
  89. package/docs/styles/tables.html +6 -0
  90. package/docs/styles/typography.html +6 -0
  91. package/docs/{components/stacks.html → utils/_template.html} +11 -30
  92. package/docs/utils/cli.html +84 -0
  93. package/docs/utils/cookie.html +87 -0
  94. package/docs/utils/debounce.html +61 -0
  95. package/docs/utils/drag.html +85 -0
  96. package/docs/utils/element.html +124 -0
  97. package/docs/utils/format-timestamp.html +64 -0
  98. package/docs/utils/fuzzy-match.html +60 -0
  99. package/icons/check.svg +1 -0
  100. package/icons/hide.svg +1 -0
  101. package/icons/show.svg +1 -0
  102. package/package.json +1 -1
  103. package/src/components/Accordion.js +0 -1
  104. package/src/components/Dialog.js +15 -6
  105. package/src/components/DirectoryViewer.js +21 -92
  106. package/src/components/FocusCapture.js +2 -1
  107. package/src/components/Icon.js +3 -1
  108. package/src/components/Search.js +4 -1
  109. package/src/components/Table.js +469 -139
  110. package/src/components/Timestamp.js +7 -4
  111. package/src/components/Toggle.js +3 -0
  112. package/src/components/tableControls/DeleteSelected.js +36 -22
  113. package/src/components/tableControls/Edit.js +88 -0
  114. package/src/components/tableControls/Filters.js +128 -0
  115. package/src/components/tableControls/HiddenCount.js +42 -0
  116. package/src/components/tableControls/Hide.js +43 -0
  117. package/src/components/tableControls/PageSelect.js +54 -30
  118. package/src/components/tableControls/PageSize.js +33 -16
  119. package/src/components/tableControls/ShowAll.js +40 -0
  120. package/src/kempo-styles.css +7 -0
  121. package/src/kempo-vars.css +2 -0
  122. package/src/utils/formatTimestamp.js +26 -10
  123. package/docs/table.html +0 -0
  124. package/src/components/Stacks.js +0 -79
  125. package/src/utils/files.js +0 -15
@@ -4,62 +4,26 @@ import SelectCount from './tableControls/SelectCount.js';
4
4
  import PageSelect from './tableControls/PageSelect.js';
5
5
  import PageSize from './tableControls/PageSize.js';
6
6
  import DeleteSelected from './tableControls/DeleteSelected.js';
7
+ import Edit from './tableControls/Edit.js';
8
+ import Hide from './tableControls/Hide.js';
9
+ import HiddenCount from './tableControls/HiddenCount.js';
10
+ import ShowAll from './tableControls/ShowAll.js';
11
+ import Filters from './tableControls/Filters.js';
7
12
  import { toTitleCase } from '../utils/string.js';
8
- import { onEvent, dispatchEvent } from '../utils/element.js';
13
+ import { onEvent, offEvent, dispatchEvent } from '../utils/element.js';
9
14
 
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
- };
15
+ const selected = Symbol('selected');
16
+ const hidden = Symbol('hidden');
17
+ const index = Symbol('index');
18
+ const editing = Symbol('editing');
58
19
 
20
+ export default class Table extends Component {
59
21
  constructor({
60
22
  records = [],
61
23
  fields = [],
24
+ filters = [],
62
25
  controls = { before: [], after: [], top: [], bottom: [] },
26
+ enablePages = false,
63
27
  pageSize = 100,
64
28
  pageSizeOptions = [10, 25, 50, 100, 500],
65
29
  currentPage = 1,
@@ -71,11 +35,13 @@ export default class Table extends Component {
71
35
  fields: fields,
72
36
  records: records,
73
37
  controls: controls,
38
+ filters: filters,
74
39
  pageSize,
75
40
  pageSizeOptions,
76
41
  currentPage,
77
42
  enableSelection,
78
- selectedIndexes: [], // Renamed property
43
+ enablePages,
44
+ sort: []
79
45
  });
80
46
 
81
47
  /* Init */
@@ -87,6 +53,7 @@ export default class Table extends Component {
87
53
  records: this.records,
88
54
  fields: this.fields,
89
55
  controls: this.controls,
56
+ filters: this.filters,
90
57
  pageSize: this.pageSize,
91
58
  pageSizeOptions: this.pageSizeOptions,
92
59
  currentPage: this.currentPage
@@ -95,6 +62,7 @@ export default class Table extends Component {
95
62
  }
96
63
  return false;
97
64
  }
65
+
98
66
  renderFields(){
99
67
  if(!this.rendered) return;
100
68
  const beforeControls = this.controls?.before?.length ? '<th></th>' : '';
@@ -120,84 +88,128 @@ export default class Table extends Component {
120
88
  });
121
89
  }
122
90
  }
123
- renderRecords(){
124
- if(!this.rendered) return;
91
+
92
+ renderRecords() {
93
+ if (!this.rendered) return;
125
94
  const $records = this.shadowRoot.getElementById('records');
126
95
  $records.innerHTML = '';
127
96
  const start = (this.currentPage - 1) * this.pageSize;
128
97
  const end = start + this.pageSize;
129
- const paginatedRecords = this.records.slice(start, end);
98
+ const paginatedRecords = this.getDisplayedRecords().slice(start, end);
130
99
  let fetchRecords = false;
131
- paginatedRecords.forEach((record, index) => {
132
- const $tr = document.createElement('tr');
133
- if (this.enableSelection) {
100
+ paginatedRecords.forEach((record) => {
101
+ $records.appendChild(this.renderRecord(record));
102
+ });
103
+ if (fetchRecords) {
104
+ dispatchEvent(this, 'fetchRecords', { start, end });
105
+ }
106
+ }
107
+
108
+ renderRecord(record){
109
+ const $tr = document.createElement('tr');
110
+ $tr.dataset.index = record[index];
111
+ if (this.enableSelection) {
112
+ const $td = document.createElement('td');
113
+ $td.classList.add('selection');
114
+ const $checkbox = document.createElement('input');
115
+ $checkbox.type = 'checkbox';
116
+ $checkbox.checked = record[selected];
117
+ $checkbox.addEventListener('change', (event) => {
118
+ record[selected] = !!event.target.checked;
119
+ dispatchEvent(this, 'selectionChange');
120
+ });
121
+ $td.appendChild($checkbox);
122
+ $tr.appendChild($td);
123
+ }
124
+ if (this.controls?.before?.length) {
125
+ $tr.appendChild(this.renderRowControls(this.controls.before, record));
126
+ }
127
+ if (record === null) {
128
+ const $td = document.createElement('td');
129
+ $td.colSpan = this.fields.length;
130
+ $td.innerHTML = '<i>Loading...</i>';
131
+ fetchRecords = true;
132
+ $tr.appendChild($td);
133
+ } else {
134
+ this.fields.forEach(({ name, formatter, calculator, type, editor }) => {
134
135
  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);
136
+ $td.dataset.field = name;
137
+ let value = record[name] || '';
138
+ if(record[editing]){
139
+ $tr.setAttribute('editing', true);
140
+ if (calculator) {
141
+ $td.appendChild(Table.editors.calculated(calculator(record, this)));
142
+ } else if(editor){
143
+ $td.appendChild(editor(value));
142
144
  } else {
143
- this.selectedIndexes = this.selectedIndexes.filter(i => i !== start + index);
145
+ let editorGen = Table.editors[type || typeof value];
146
+ if(!editorGen){
147
+ editorGen = Table.editors.string;
148
+ }
149
+ $td.appendChild(editorGen(value));
144
150
  }
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);
159
- } 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);
151
+ } else {
152
+ if (calculator) {
153
+ $td.innerHTML = calculator(record, this);
154
+ } else if (formatter) {
155
+ $td.innerHTML = formatter(value);
165
156
  } 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
- }
157
+ $td.innerHTML = value;
178
158
  }
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));
159
+ }
160
+ $tr.appendChild($td);
161
+ });
162
+ }
163
+ if (this.controls?.after?.length) {
164
+ $tr.appendChild(this.renderRowControls(this.controls.after, record));
165
+ }
166
+ return $tr;
167
+ }
168
+
169
+ editRecord(record){
170
+ record[editing] = true;
171
+ const $currentTr = this.shadowRoot.querySelector(`tr[data-index="${record[index]}"]`);
172
+ const $newTr = this.renderRecord(record);
173
+ $currentTr.replaceWith($newTr);
174
+ dispatchEvent(this, 'editingChange');
175
+ }
176
+
177
+ saveEditedRecord(record){
178
+ record[editing] = false;
179
+ const $currentTr = this.shadowRoot.querySelector(`tr[data-index="${record[index]}"]`);
180
+ $currentTr.querySelectorAll('td').forEach(($td) => {
181
+ const field = $td.dataset.field;
182
+ const fieldDef = this.fields.find(f => f.name === field);
183
+ if(fieldDef && !fieldDef.calculator){ // calculated fields cannot be udated
184
+ const $input = $td.children.length === 1 ? $td.firstChild : $td.querySelector('input, select');
185
+ if ($input) {
186
+ record[field] = $input.value;
187
+ }
185
188
  }
186
- $records.appendChild($tr);
187
189
  });
188
- if(fetchRecords){
189
- dispatchEvent(this, 'fetchRecords', { start, end });
190
- }
190
+ const $newTr = this.renderRecord(record);
191
+ $currentTr.replaceWith($newTr);
192
+ }
193
+
194
+ cancelEditedRecord(record){
195
+ record[editing] = false;
196
+ const $currentTr = this.shadowRoot.querySelector(`tr[data-index="${record[index]}"]`);
197
+ const $newTr = this.renderRecord(record);
198
+ $currentTr.replaceWith($newTr);
199
+ }
200
+
201
+ recordIsEditing(record){
202
+ return record[editing];
191
203
  }
192
204
 
193
- renderRowControls(controls = [], record, index) {
205
+ renderRowControls(controls = [], record) {
194
206
  if(!this.rendered) return;
195
207
  const $td = document.createElement('td');
196
- controls.forEach(({ html, icon, action, render }) => {
208
+ controls.filter(c=>!!c).forEach(({ html, icon, action, render }) => {
197
209
  if(html){
198
210
  $td.appendChild(document.createRange().createContextualFragment(html));
199
211
  } else if(render && typeof(render) === 'function'){
200
- const rendered = render(this, record, index);
212
+ const rendered = render(this, record);
201
213
  if(rendered instanceof HTMLElement){
202
214
  $td.appendChild(rendered);
203
215
  } else if(typeof(rendered) === 'string'){
@@ -208,7 +220,7 @@ export default class Table extends Component {
208
220
  $button.classList.add('pq', 'no-btn');
209
221
  $button.appendChild(new Icon(icon));
210
222
  if (action) {
211
- onEvent($button, 'click', () => action(this, record, index));
223
+ onEvent($button, 'click', () => action(this, record));
212
224
  }
213
225
  $td.appendChild($button);
214
226
  }
@@ -220,7 +232,7 @@ export default class Table extends Component {
220
232
  if(!this.rendered) return;
221
233
  const $container = this.shadowRoot.getElementById(position);
222
234
  $container.innerHTML = '';
223
- controls.forEach(({ html, icon, action, render }) => {
235
+ controls.filter(c=>!!c).forEach(({ html, icon, action, render }) => {
224
236
  if(html){
225
237
  $container.appendChild(document.createRange().createContextualFragment(html));
226
238
  } else if(render && typeof(render) === 'function'){
@@ -247,7 +259,7 @@ export default class Table extends Component {
247
259
  }
248
260
 
249
261
  getTotalPages() {
250
- return Math.ceil(this.records.length / this.pageSize);
262
+ return Math.ceil(this.getDisplayedRecords().length / this.pageSize);
251
263
  }
252
264
 
253
265
  setPage(page) {
@@ -287,10 +299,18 @@ export default class Table extends Component {
287
299
  currentPage = false,
288
300
  enableSelection = false // Add enableSelection property
289
301
  } = {}) {
290
- let rerender = false;
291
- let rerenderControls = false;
302
+ let rerender = false,
303
+ rerenderControls = false,
304
+ pageCountBefore = this.getTotalPages(),
305
+ pageBefore = this.currentPage;
292
306
  if(records){
293
- this.records = records;
307
+ this.records = records.map(r=>({...r}));
308
+ this.records.forEach((record, idx) => {
309
+ record[index] = idx;
310
+ record[selected] = false;
311
+ record[hidden] = false;
312
+ record[editing] = false;
313
+ });
294
314
  this.fields = fields || Table.extractFieldsFromRecords(this.records);
295
315
  rerender = true;
296
316
  }
@@ -321,13 +341,34 @@ export default class Table extends Component {
321
341
  this.renderControls(this.controls.top, 'top');
322
342
  this.renderControls(this.controls.bottom, 'bottom');
323
343
  }
344
+ const newPageCount = this.getTotalPages();
345
+ if(newPageCount !== pageCountBefore){
346
+ dispatchEvent(this, 'pageCountChanged', { totalPages: this.getTotalPages() });
347
+ }
348
+ if(pageBefore > newPageCount){
349
+ this.setPage(newPageCount);
350
+ }
324
351
  }
325
352
 
326
353
  setRecords(records) {
327
- this.records = records;
328
- this.currentPage = 1;
354
+ let pageCountBefore = this.getTotalPages(),
355
+ pageBefore = this.currentPage;
356
+ this.records = records.map(r=>({...r}));
357
+ this.records.forEach((record, idx) => {
358
+ record[index] = idx;
359
+ record[selected] = false;
360
+ record[hidden] = false;
361
+ record[editing] = false;
362
+ });
329
363
  this.renderRecords();
330
364
  dispatchEvent(this, 'recordsSet', { records });
365
+ const newPageCount = this.getTotalPages();
366
+ if(newPageCount !== pageCountBefore){
367
+ dispatchEvent(this, 'pageCountChanged', { totalPages: this.getTotalPages() });
368
+ }
369
+ if(pageBefore > newPageCount){
370
+ this.setPage(newPageCount);
371
+ }
331
372
  }
332
373
 
333
374
  setupFetchRecords(totalRecords, callback) {
@@ -346,66 +387,232 @@ export default class Table extends Component {
346
387
  }
347
388
 
348
389
  addRecord(record) {
390
+ record[selected] = false;
391
+ record[hidden] = false;
392
+ record[index] = this.records.length;
349
393
  this.records.push(record);
350
394
  this.renderRecords();
351
395
  dispatchEvent(this, 'recordAdded', { record });
352
396
  }
353
397
 
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();
398
+ updateRecord(record, newData) {
399
+ let updated = false;
400
+ let originalRecord = this.records.find(r => r === record);
401
+ if (!originalRecord && record[index] !== undefined) {
402
+ originalRecord = this.records[record[index]];
403
+ }
404
+ Object.keys(newData).forEach(key => {
405
+ if (originalRecord.hasOwnProperty(key)) {
406
+ originalRecord[key] = newData[key];
407
+ updated = true;
408
+ }
409
+ });
410
+ if(updated){
411
+ const start = (this.currentPage - 1) * this.pageSize;
412
+ const end = start + this.pageSize;
413
+ if (
414
+ !this.enablePages ||
415
+ (originalRecord[index] >= start && originalRecord[index] < end)
416
+ ) {
417
+ this.renderRecords();
418
+ }
360
419
  }
361
420
  }
362
421
 
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);
422
+ deleteRecord(record) {
423
+ let originalRecord = this.records.find(r => r === record),
424
+ totalPagesBefore = this.getTotalPages();
425
+ if (!originalRecord && record[index] !== undefined) {
426
+ originalRecord = this.records[record[index]];
427
+ }
428
+ if (originalRecord) {
429
+ const recordIndex = this.records.indexOf(originalRecord);
430
+ this.records.splice(recordIndex, 1);
431
+ // Update the index of remaining records
432
+ this.records.forEach((rec, idx) => {
433
+ rec[index] = idx;
434
+ });
369
435
  this.renderRecords();
370
- dispatchEvent(this, 'recordDeleted', { index });
436
+ dispatchEvent(this, 'selectionChange');
437
+ dispatchEvent(this, 'recordDeleted', { index: recordIndex });
438
+ const totalPages = this.getTotalPages();
439
+ if (this.currentPage > totalPages) {
440
+ this.setPage(totalPages);
441
+ }
442
+ if(totalPages !== totalPagesBefore){
443
+ dispatchEvent(this, 'pageCountChanged', { totalPages });
444
+ }
371
445
  }
372
446
  }
373
447
 
448
+ deleteSelected() {
449
+ let totalPagesBefore = this.getTotalPages();
450
+ const selectedRecords = this.getSelectedRecords();
451
+ selectedRecords.forEach(record => {
452
+ let originalRecord = this.records.find(r => r === record);
453
+ if (!originalRecord && record[index] !== undefined) {
454
+ originalRecord = this.records[record[index]];
455
+ }
456
+ if (originalRecord) {
457
+ const recordIndex = this.records.indexOf(originalRecord);
458
+ this.records.splice(recordIndex, 1);
459
+ }
460
+ });
461
+ this.records.forEach((rec, idx) => {
462
+ rec[index] = idx;
463
+ });
464
+ this.renderRecords();
465
+ const totalPages = this.getTotalPages();
466
+ if (this.currentPage > totalPages) {
467
+ this.setPage(totalPages);
468
+ }
469
+ if(totalPages !== totalPagesBefore){
470
+ dispatchEvent(this, 'pageCountChanged', { totalPages });
471
+ }
472
+ dispatchEvent(this, 'selectionChange');
473
+ }
474
+
374
475
  getSelectedRecords() {
375
- return this.selectedIndexes.map(index => this.records[index]);
476
+ const selectedRecords = this.records.filter(record => {
477
+ const isSelected = record[selected];
478
+ return isSelected;
479
+ });
480
+ return selectedRecords;
376
481
  }
377
482
 
378
483
  selectAllOnPage() {
379
484
  const start = (this.currentPage - 1) * this.pageSize;
380
485
  const end = Math.min(start + this.pageSize, this.records.length);
381
486
  for (let i = start; i < end; i++) {
382
- if (!this.selectedIndexes.includes(i)) {
383
- this.selectedIndexes.push(i);
384
- }
487
+ this.records[i][selected] = true;
385
488
  }
386
489
  this.renderRecords();
387
- dispatchEvent(this, 'selectionChange', { selectedIndexes: this.selectedIndexes });
490
+ setTimeout(() => {
491
+ dispatchEvent(this, 'selectionChange');
492
+ }, 0);
388
493
  }
389
494
 
390
495
  deselectAllOnPage() {
391
496
  const start = (this.currentPage - 1) * this.pageSize;
392
497
  const end = Math.min(start + this.pageSize, this.records.length);
393
- this.selectedIndexes = this.selectedIndexes.filter(index => index < start || index >= end);
498
+ for (let i = start; i < end; i++) {
499
+ this.records[i][selected] = false;
500
+ }
394
501
  this.renderRecords();
395
- dispatchEvent(this, 'selectionChange', { selectedIndexes: this.selectedIndexes });
502
+ setTimeout(() => {
503
+ dispatchEvent(this, 'selectionChange');
504
+ }, 0);
396
505
  }
397
506
 
398
507
  allOnPageSelected() {
399
508
  const start = (this.currentPage - 1) * this.pageSize;
400
509
  const end = Math.min(start + this.pageSize, this.records.length);
401
510
  for (let i = start; i < end; i++) {
402
- if (!this.selectedIndexes.includes(i)) {
511
+ if (!this.records[i][selected]) {
403
512
  return false;
404
513
  }
405
514
  }
406
515
  return true;
407
516
  }
408
517
 
518
+ sortBy(field, asc = true) {
519
+ this.sort = this.sort.filter(item => item.name !== field);
520
+ this.sort.push({ name: field, asc });
521
+ this.renderRecords();
522
+ }
523
+
524
+ hideRecord(record) {
525
+ let originalRecord = this.records.find(r => r === record);
526
+ if (!originalRecord && record[index] !== undefined) {
527
+ originalRecord = this.records[record[index]];
528
+ }
529
+ if (originalRecord) {
530
+ originalRecord[hidden] = true;
531
+ this.renderRecords();
532
+ dispatchEvent(this, 'recordHidden');
533
+ }
534
+ }
535
+
536
+ showRecord(record) {
537
+ let originalRecord = this.records.find(r => r === record);
538
+ if (!originalRecord && record[index] !== undefined) {
539
+ originalRecord = this.records[record[index]];
540
+ }
541
+ if (originalRecord) {
542
+ originalRecord[hidden] = false;
543
+ this.renderRecords();
544
+ dispatchEvent(this, 'recordShown');
545
+ }
546
+ }
547
+
548
+ showAllRecords() {
549
+ this.records.forEach(record => {
550
+ record[hidden] = false;
551
+ });
552
+ this.renderRecords();
553
+ dispatchEvent(this, 'recordShown allRecordsShown');
554
+ }
555
+
556
+ addFilter(field, condition, value) {
557
+ this.filters.push({ field, condition, value });
558
+ dispatchEvent(this, 'filterAdded filterChange');
559
+ this.renderRecords();
560
+ }
561
+
562
+ removeFilter(field, condition, value) {
563
+ this.filters = this.filters.filter(f => f.field !== field || f.condition !== condition || f.value !== value);
564
+ dispatchEvent(this, 'filterRemoved filterChange');
565
+ this.renderRecords();
566
+ }
567
+
568
+ getDisplayedRecords() {
569
+ let displayedRecords = this.records.filter(record => !record[hidden]);
570
+
571
+ this.filters.forEach(({ field, condition, value }) => {
572
+ displayedRecords = displayedRecords.filter(record => {
573
+ if (condition === 'eq') {
574
+ return record[field] === value;
575
+ }
576
+ if (condition === 'neq') {
577
+ return record[field] !== value;
578
+ }
579
+ if (condition === 'gt') {
580
+ return record[field] > value;
581
+ }
582
+ if (condition === 'lt') {
583
+ return record[field] < value;
584
+ }
585
+ if (condition === 'gte') {
586
+ return record[field] >= value;
587
+ }
588
+ if (condition === 'lte') {
589
+ return record[field] <= value;
590
+ }
591
+ if (condition === 'contains') {
592
+ return record[field].includes(value);
593
+ }
594
+ if (condition === 'ncontains') {
595
+ return !record[field].includes(value);
596
+ }
597
+ return true;
598
+ });
599
+ });
600
+
601
+ this.sort.forEach(({ name, asc }) => {
602
+ displayedRecords.sort((a, b) => {
603
+ if (a[name] < b[name]) return asc ? -1 : 1;
604
+ if (a[name] > b[name]) return asc ? 1 : -1;
605
+ return 0;
606
+ });
607
+ });
608
+
609
+ return displayedRecords;
610
+ }
611
+
612
+ getHiddenRecords() {
613
+ return this.records.filter(record => record[hidden]);
614
+ }
615
+
409
616
  get shadowTemplate(){
410
617
  return /*html*/`
411
618
  ${super.shadowTemplate}
@@ -419,6 +626,7 @@ export default class Table extends Component {
419
626
  </div>
420
627
  `;
421
628
  }
629
+
422
630
  get shadowStyles(){
423
631
  return /*css*/`
424
632
  ${super.shadowStyles}
@@ -464,6 +672,15 @@ export default class Table extends Component {
464
672
  width: 1.35rem;
465
673
  height: 1.35rem;
466
674
  }
675
+ tr[editing] td {
676
+ padding: 0;
677
+ }
678
+ tr[editing] input,
679
+ tr[editing] select,
680
+ tr[editing] .input {
681
+ padding: calc(0.75 * var(--spacer)) var(--spacer) !important;
682
+ width: 100%;
683
+ }
467
684
  `;
468
685
  }
469
686
 
@@ -473,6 +690,119 @@ export default class Table extends Component {
473
690
  Object.keys(record).forEach( name => names.add(name) )
474
691
  });
475
692
  return [...names].map(name=>({name,label:toTitleCase(name)}));
476
- }
693
+ };
694
+
695
+ static controls = {
696
+ prevPage: {
697
+ render: (table) => {
698
+ const $button = document.createElement('button');
699
+ $button.classList.add('pq', 'no-btn');
700
+ $button.appendChild(new Icon('chevron-left'));
701
+ onEvent($button, 'click', () => table.prevPage());
702
+
703
+ // Disable button if on the first page
704
+ onEvent(table, 'pageChange', () => {
705
+ $button.disabled = table.getCurrentPage() === 1;
706
+ });
707
+
708
+ return $button;
709
+ }
710
+ },
711
+ nextPage: {
712
+ render: (table) => {
713
+ const $button = document.createElement('button');
714
+ $button.classList.add('pq', 'no-btn');
715
+ $button.appendChild(new Icon('chevron-right'));
716
+ onEvent($button, 'click', () => table.nextPage());
717
+
718
+ // Disable button if on the last page
719
+ onEvent(table, 'pageChange', () => {
720
+ $button.disabled = table.getCurrentPage() === table.getTotalPages();
721
+ });
722
+
723
+ return $button;
724
+ }
725
+ },
726
+ pageSelect: {
727
+ render: (table) => new PageSelect(table)
728
+ },
729
+ pageSize: {
730
+ render: (table) => new PageSize(table)
731
+ },
732
+ selectCount: {
733
+ render: (table) => new SelectCount(table)
734
+ },
735
+ deleteSelected: {
736
+ render: (table) => new DeleteSelected(table)
737
+ },
738
+ spacer: {
739
+ html: '<div class="flex"></div>'
740
+ },
741
+ edit: {
742
+ render: (table, record) => new Edit(table, record)
743
+ },
744
+ hide: {
745
+ render: (table, record) => new Hide(table, record)
746
+ },
747
+ hiddenCount: {
748
+ render: (table) => new HiddenCount(table)
749
+ },
750
+ showAll: {
751
+ render: (table) => new ShowAll(table)
752
+ },
753
+ filters: {
754
+ render: (table) => new Filters(table)
755
+ }
756
+ };
757
+
758
+ static format(value){
759
+ const f = Array.isArray(value) ? Table.formatters.array : Table.formatters[typeof value];
760
+ return f(value);
761
+ };
762
+
763
+ static formatters = {
764
+ string: v=>v,
765
+ number: v=>`${v}`,
766
+ date: v=>v.toLocaleDateString(),
767
+ boolean: v=>v ? 'True' : 'False',
768
+ array: v=>v.map(i=>Table.format(i)).join(', '),
769
+ 'undefined': v=>'',
770
+ 'null': v=>'<code>null</code>'
771
+ };
772
+
773
+ static editors = {
774
+ string: (value) => {
775
+ const $i = document.createElement('input');
776
+ $i.value = value;
777
+ return $i;
778
+ },
779
+ number: (value) => {
780
+ const $i = document.createElement('input');
781
+ $i.type = 'number';
782
+ $i.value = value;
783
+ return $i;
784
+ },
785
+ date: (value) => {
786
+ const $i = document.createElement('input');
787
+ $i.type = 'date';
788
+ $i.value = value;
789
+ return $i;
790
+ },
791
+ boolean: (value) => {
792
+ const $i = document.createElement('select');
793
+ $i.innerHTML = `
794
+ <option value="true" ${value ? 'selected' : ''}>True</option>
795
+ <option value="false" ${!value ? 'selected' : ''}>False</option>
796
+ `;
797
+ $i.value = value;
798
+ return $i;
799
+ },
800
+ calculated: (value) => {
801
+ const $i = document.createElement('input');
802
+ $i.disabled = true;
803
+ $i.value = value;
804
+ return $i;
805
+ }
806
+ };
477
807
  }
478
808
  window.customElements.define('k-table', Table);