kempo-ui 0.0.77 → 0.0.79

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.
@@ -17,6 +17,8 @@ export default class Table extends ShadowComponent {
17
17
  enableSelection: { type: Boolean, reflect: true, converter: boolExists, attribute: 'enable-selection' },
18
18
  enableSorting: { type: Boolean, reflect: true, converter: boolExists, attribute: 'enable-sorting' },
19
19
  caseSensitiveFilters: { type: Boolean, reflect: true, converter: boolExists, attribute: 'case-sensitive-filters' },
20
+ requestEdit: { type: Boolean, reflect: true, converter: boolExists, attribute: 'request-edit' },
21
+ requestDelete: { type: Boolean, reflect: true, converter: boolExists, attribute: 'request-delete' },
20
22
  fields: { type: Array },
21
23
  records: { type: Array },
22
24
  filters: { type: Array },
@@ -38,6 +40,8 @@ export default class Table extends ShadowComponent {
38
40
  if(this.sort === undefined) this.sort = [];
39
41
  if(this.columnSizes === undefined) this.columnSizes = {};
40
42
  if(this.fetchPending === undefined) this.fetchPending = false;
43
+ if(this.requestEdit === undefined) this.requestEdit = false;
44
+ if(this.requestDelete === undefined) this.requestDelete = false;
41
45
  }
42
46
 
43
47
  /*
@@ -80,86 +84,72 @@ export default class Table extends ShadowComponent {
80
84
 
81
85
  updated(changedProperties) {
82
86
  super.updated(changedProperties);
83
-
84
- // Update select-all checkbox state when selection or page changes
85
- if (this.enableSelection) {
87
+
88
+ if(this.enableSelection) {
86
89
  const selectAllCheckbox = this.shadowRoot.getElementById('select-all');
87
- if (selectAllCheckbox) {
90
+ if(selectAllCheckbox) {
88
91
  selectAllCheckbox.checked = this.allOnPageSelected();
89
92
  }
90
93
  }
91
-
92
- // Update container widths
93
- this.updateContainerWidths();
94
- }
95
-
96
- updateContainerWidths() {
97
- const totalWidth = this.columnSizes.total + 'px';
98
- const fieldsEl = this.shadowRoot.getElementById('fields');
99
- const topEl = this.shadowRoot.getElementById('top');
100
- const bottomEl = this.shadowRoot.getElementById('bottom');
101
- const recordsEl = this.shadowRoot.getElementById('records');
102
-
103
- if (fieldsEl) fieldsEl.style.width = totalWidth;
104
- if (topEl) topEl.style.width = totalWidth;
105
- if (bottomEl) bottomEl.style.width = totalWidth;
106
- if (recordsEl) recordsEl.style.width = totalWidth;
107
94
  }
108
95
 
109
96
  /*
110
97
  Rendering Functions
111
98
  */
112
99
 
113
- renderFieldsTemplate() {
114
- this.calculateColumnSizes();
115
- this.hasTopControls() ? this.setAttribute('top-controls', 'true') : this.removeAttribute('top-controls');
116
- this.hasBottomControls() ? this.setAttribute('bottom-controls', 'true') : this.removeAttribute('bottom-controls');
100
+ renderColgroupTemplate() {
101
+ const cols = [];
102
+ if(this.enableSelection) cols.push(html`<col style="width: 40px" />`);
103
+ if(this.hasBeforeControls()) cols.push(html`<col style="width: ${this.columnSizes.beforeControls}px" />`);
104
+ this.fields.forEach(({ size, hidden }) => {
105
+ if(hidden) return;
106
+ cols.push(size ? html`<col style="width: ${size}px" />` : html`<col />`);
107
+ });
108
+ if(this.hasAfterControls()) cols.push(html`<col style="width: ${this.columnSizes.afterControls}px" />`);
109
+ return cols;
110
+ }
117
111
 
118
- const fieldCells = [];
112
+ getColumnCount() {
113
+ let count = 0;
114
+ if(this.enableSelection) count++;
115
+ if(this.hasBeforeControls()) count++;
116
+ this.fields.forEach(({ hidden }) => { if(!hidden) count++; });
117
+ if(this.hasAfterControls()) count++;
118
+ return count;
119
+ }
119
120
 
120
- if (this.enableSelection) {
121
- fieldCells.push(html`
122
- <div class="field controls cell field-select" style="width: 40px">
123
- <input
124
- type="checkbox"
125
- id="select-all"
126
- @change=${this.handleSelectAllChange}
127
- />
128
- </div>
121
+ renderFieldsTemplate() {
122
+ const headers = [];
123
+ if(this.enableSelection) {
124
+ headers.push(html`
125
+ <th class="controls field-select">
126
+ <input type="checkbox" id="select-all" @change=${this.handleSelectAllChange} />
127
+ </th>
129
128
  `);
130
129
  }
131
-
132
- if (this.hasBeforeControls()) {
133
- fieldCells.push(html`
134
- <div class="field cell field-before-controls" style="width: ${this.columnSizes.beforeControls}px"></div>
135
- `);
130
+ if(this.hasBeforeControls()) {
131
+ headers.push(html`<th class="controls field-before-controls"></th>`);
136
132
  }
137
-
138
133
  this.fields.forEach(({ name, label, hidden }) => {
139
- if (hidden) return;
134
+ if(hidden) return;
140
135
  const sortItem = this.sort.find(item => item.name === name);
141
136
  const isCurrentSort = this.sort.length > 0 && this.sort[this.sort.length - 1].name === name;
142
137
  const sortClass = sortItem ? (sortItem.asc ? 'sort-asc' : 'sort-desc') : '';
143
-
144
- fieldCells.push(html`
145
- <div
146
- class="field cell ${sortClass}"
147
- style="width: ${this.columnSizes[name]}px; ${this.enableSorting ? 'cursor: pointer;' : ''}"
138
+ headers.push(html`
139
+ <th
140
+ class="${sortClass}"
141
+ style="${this.enableSorting ? 'cursor: pointer;' : ''}"
148
142
  @click=${this.enableSorting ? () => this.handleFieldClick(name) : null}
149
143
  >
150
144
  ${label}
151
145
  ${isCurrentSort ? html`<k-icon name="arrow" direction="${sortItem.asc ? 'down' : 'up'}" class="icon-sort"></k-icon>` : ''}
152
- </div>
146
+ </th>
153
147
  `);
154
148
  });
155
-
156
- if (this.hasAfterControls()) {
157
- fieldCells.push(html`
158
- <div class="field cell field-after-controls" style="width: ${this.columnSizes.afterControls}px"></div>
159
- `);
149
+ if(this.hasAfterControls()) {
150
+ headers.push(html`<th class="controls field-after-controls"></th>`);
160
151
  }
161
-
162
- return fieldCells;
152
+ return headers;
163
153
  }
164
154
 
165
155
  renderRecordsTemplate() {
@@ -182,7 +172,7 @@ export default class Table extends ShadowComponent {
182
172
  } else {
183
173
  if (fetchStart === null) fetchStart = start + idx;
184
174
  fetchCount++;
185
- return html`<div class="record fetching"><div class="cell">Loading...</div></div>`;
175
+ return html`<tr class="record fetching"><td class="cell" colspan="${this.getColumnCount()}">Loading...</td></tr>`;
186
176
  }
187
177
  });
188
178
 
@@ -202,42 +192,41 @@ export default class Table extends ShadowComponent {
202
192
 
203
193
  renderRecordTemplate(record) {
204
194
  const recordCells = [];
205
-
206
- if (this.enableSelection) {
195
+
196
+ if(this.enableSelection) {
207
197
  recordCells.push(html`
208
- <div class="cell selection controls" style="width: 40px">
209
- <input
210
- type="checkbox"
198
+ <td class="cell selection controls">
199
+ <input
200
+ type="checkbox"
211
201
  .checked=${record[selected]}
212
202
  @change=${(e) => this.handleRecordSelectionChange(record, e)}
213
203
  />
214
- </div>
204
+ </td>
215
205
  `);
216
206
  }
217
-
218
- if (this.hasBeforeControls()) {
207
+
208
+ if(this.hasBeforeControls()) {
219
209
  recordCells.push(this.renderBeforeControlsTemplate());
220
210
  }
221
-
211
+
222
212
  this.fields.forEach(({ name, formatter, calculator, type, editor, hidden }) => {
223
- if (hidden) return;
213
+ if(hidden) return;
224
214
  let value = record[name] || '';
225
-
226
215
  recordCells.push(html`
227
- <div class="cell" data-field=${name} style="width: ${this.columnSizes[name]}px">
216
+ <td class="cell" data-field=${name}>
228
217
  ${record[editing] ? this.renderEditingCell(record, name, value, calculator, editor, type) : this.renderDisplayCell(record, name, value, calculator, formatter)}
229
- </div>
218
+ </td>
230
219
  `);
231
220
  });
232
-
233
- if (this.hasAfterControls()) {
221
+
222
+ if(this.hasAfterControls()) {
234
223
  recordCells.push(this.renderAfterControlsTemplate());
235
224
  }
236
-
225
+
237
226
  return html`
238
- <div class="record ${record[editing] ? 'editing' : ''}" data-index=${record[index]}>
227
+ <tr class="record ${record[editing] ? 'editing' : ''}" data-index=${record[index]}>
239
228
  ${recordCells}
240
- </div>
229
+ </tr>
241
230
  `;
242
231
  }
243
232
 
@@ -280,55 +269,37 @@ export default class Table extends ShadowComponent {
280
269
 
281
270
  renderBeforeControlsTemplate() {
282
271
  const controls = [];
283
-
284
272
  this.querySelectorAll('[slot="before"]').forEach(control => {
285
273
  const tagName = control.tagName.toLowerCase();
286
274
  const newControl = document.createElement(tagName);
287
-
288
275
  Array.from(control.attributes).forEach(attr => {
289
- if(attr.name !== 'slot'){
290
- newControl.setAttribute(attr.name, attr.value);
291
- }
276
+ if(attr.name !== 'slot') newControl.setAttribute(attr.name, attr.value);
292
277
  });
293
-
294
- if(control.innerHTML){
295
- newControl.innerHTML = control.innerHTML;
296
- }
297
-
278
+ if(control.innerHTML) newControl.innerHTML = control.innerHTML;
298
279
  controls.push(newControl);
299
280
  });
300
-
301
281
  return html`
302
- <div class="cell controls controls-before" style="width: ${this.columnSizes.beforeControls}px">
282
+ <td class="cell controls controls-before">
303
283
  ${controls}
304
- </div>
284
+ </td>
305
285
  `;
306
286
  }
307
287
 
308
288
  renderAfterControlsTemplate() {
309
289
  const controls = [];
310
-
311
290
  this.querySelectorAll('[slot="after"]').forEach(control => {
312
291
  const tagName = control.tagName.toLowerCase();
313
292
  const newControl = document.createElement(tagName);
314
-
315
293
  Array.from(control.attributes).forEach(attr => {
316
- if(attr.name !== 'slot'){
317
- newControl.setAttribute(attr.name, attr.value);
318
- }
294
+ if(attr.name !== 'slot') newControl.setAttribute(attr.name, attr.value);
319
295
  });
320
-
321
- if(control.innerHTML){
322
- newControl.innerHTML = control.innerHTML;
323
- }
324
-
296
+ if(control.innerHTML) newControl.innerHTML = control.innerHTML;
325
297
  controls.push(newControl);
326
298
  });
327
-
328
299
  return html`
329
- <div class="cell controls controls-after" style="width: ${this.columnSizes.afterControls}px">
300
+ <td class="cell controls controls-after">
330
301
  ${controls}
331
- </div>
302
+ </td>
332
303
  `;
333
304
  }
334
305
 
@@ -391,39 +362,59 @@ export default class Table extends ShadowComponent {
391
362
 
392
363
  saveEditedRecord(record) {
393
364
  const recordEl = this.shadowRoot.querySelector(`.record[data-index="${record[index]}"]`);
365
+ const newData = {};
394
366
  if(recordEl){
395
367
  recordEl.querySelectorAll('.cell[data-field]').forEach($cell => {
396
368
  const field = $cell.dataset.field;
397
369
  const fieldDef = this.fields.find(f => f.name === field);
398
370
  if(fieldDef && !fieldDef.calculator){
399
371
  const $input = $cell.querySelector('input, select');
400
- if($input){
401
- record[field] = $input.value;
402
- }
372
+ if($input) newData[field] = $input.value;
403
373
  }
404
374
  });
375
+ }
376
+ const commit = () => {
377
+ Object.assign(record, newData);
405
378
  record[editing] = false;
406
- recordEl.classList.remove('editing');
407
- recordEl.removeAttribute('editing');
408
- recordEl.querySelectorAll('.cell[data-field]').forEach($cell => {
409
- const field = $cell.dataset.field;
410
- const fieldDef = this.fields.find(f => f.name === field);
411
- if(fieldDef){
412
- const value = record[field] || '';
413
- if(fieldDef.calculator){
414
- $cell.textContent = fieldDef.calculator(record, this);
415
- } else if(fieldDef.formatter){
416
- $cell.innerHTML = fieldDef.formatter(value);
417
- } else {
418
- $cell.textContent = value;
379
+ if(recordEl){
380
+ recordEl.classList.remove('editing');
381
+ recordEl.removeAttribute('editing');
382
+ recordEl.querySelectorAll('.cell[data-field]').forEach($cell => {
383
+ const field = $cell.dataset.field;
384
+ const fieldDef = this.fields.find(f => f.name === field);
385
+ if(fieldDef){
386
+ const value = record[field] || '';
387
+ if(fieldDef.calculator){
388
+ $cell.textContent = fieldDef.calculator(record, this);
389
+ } else if(fieldDef.formatter){
390
+ $cell.innerHTML = fieldDef.formatter(value);
391
+ } else {
392
+ $cell.textContent = value;
393
+ }
419
394
  }
420
- }
421
- });
395
+ });
396
+ }
397
+ this.dispatchEvent(new CustomEvent('editingChange', {
398
+ detail: { record, editing: false },
399
+ bubbles: true
400
+ }));
401
+ };
402
+ const hasChanges = Object.keys(newData).some(key => String(record[key] ?? '') !== newData[key]);
403
+ if(!hasChanges){
404
+ this.cancelEditedRecord(record);
405
+ return;
406
+ }
407
+ if(this.requestEdit){
408
+ recordEl?.classList.add('pending');
409
+ const wrappedCommit = () => { recordEl?.classList.remove('pending'); commit(); };
410
+ const reject = () => { recordEl?.classList.remove('pending'); };
411
+ this.dispatchEvent(new CustomEvent('requestSave', {
412
+ detail: { record, newData, approve: wrappedCommit, reject },
413
+ bubbles: true
414
+ }));
415
+ } else {
416
+ commit();
422
417
  }
423
- this.dispatchEvent(new CustomEvent('editingChange', {
424
- detail: { record, editing: false },
425
- bubbles: true
426
- }));
427
418
  }
428
419
 
429
420
  cancelEditedRecord(record) {
@@ -682,69 +673,79 @@ export default class Table extends ShadowComponent {
682
673
 
683
674
  deleteRecord(record) {
684
675
  let originalRecord = this.records.find(r => r === record);
685
- let totalPagesBefore = this.getTotalPages();
686
-
687
- if (!originalRecord && record[index] !== undefined) {
688
- originalRecord = this.records[record[index]];
689
- }
690
-
691
- if (originalRecord) {
676
+ const totalPagesBefore = this.getTotalPages();
677
+ if(!originalRecord && record[index] !== undefined) originalRecord = this.records[record[index]];
678
+ if(!originalRecord) return;
679
+ const commit = () => {
692
680
  const recordIndex = this.records.indexOf(originalRecord);
693
681
  this.records.splice(recordIndex, 1);
694
- this.records.forEach((rec, idx) => {
695
- rec[index] = idx;
696
- });
682
+ this.records.forEach((rec, idx) => { rec[index] = idx; });
697
683
  this.requestUpdate();
698
684
  this.dispatchEvent(new CustomEvent('selectionChange', { bubbles: true }));
699
- this.dispatchEvent(new CustomEvent('recordDeleted', {
685
+ this.dispatchEvent(new CustomEvent('recordDeleted', {
700
686
  detail: { index: recordIndex },
701
- bubbles: true
687
+ bubbles: true
702
688
  }));
703
-
704
689
  const totalPages = this.getTotalPages();
705
- if (this.currentPage > totalPages) {
706
- this.setPage(totalPages);
707
- }
708
- if (totalPages !== totalPagesBefore) {
709
- this.dispatchEvent(new CustomEvent('pageCountChanged', {
690
+ if(this.currentPage > totalPages) this.setPage(totalPages);
691
+ if(totalPages !== totalPagesBefore){
692
+ this.dispatchEvent(new CustomEvent('pageCountChanged', {
710
693
  detail: { totalPages },
711
- bubbles: true
694
+ bubbles: true
712
695
  }));
713
696
  }
697
+ };
698
+ if(this.requestDelete){
699
+ const recordEl = this.shadowRoot.querySelector(`.record[data-index="${originalRecord[index]}"]`);
700
+ recordEl?.classList.add('pending');
701
+ const wrappedCommit = () => { recordEl?.classList.remove('pending'); commit(); };
702
+ const reject = () => { recordEl?.classList.remove('pending'); };
703
+ this.dispatchEvent(new CustomEvent('requestDelete', {
704
+ detail: { records: [originalRecord], approve: wrappedCommit, reject },
705
+ bubbles: true
706
+ }));
707
+ } else {
708
+ commit();
714
709
  }
715
710
  }
716
711
 
717
712
  deleteSelected() {
718
- let totalPagesBefore = this.getTotalPages();
719
- const selectedRecords = this.getSelectedRecords();
720
-
721
- selectedRecords.forEach(record => {
722
- let originalRecord = this.records.find(r => r === record);
723
- if (!originalRecord && record[index] !== undefined) {
724
- originalRecord = this.records[record[index]];
725
- }
726
- if (originalRecord) {
727
- const recordIndex = this.records.indexOf(originalRecord);
728
- this.records.splice(recordIndex, 1);
713
+ const totalPagesBefore = this.getTotalPages();
714
+ const originalRecords = this.getSelectedRecords()
715
+ .map(record => this.records.find(r => r === record) ?? (record[index] !== undefined ? this.records[record[index]] : null))
716
+ .filter(Boolean);
717
+ if(!originalRecords.length) return;
718
+ const commit = () => {
719
+ originalRecords.forEach(record => {
720
+ const i = this.records.indexOf(record);
721
+ if(i !== -1) this.records.splice(i, 1);
722
+ });
723
+ this.records.forEach((rec, idx) => { rec[index] = idx; });
724
+ this.requestUpdate();
725
+ const totalPages = this.getTotalPages();
726
+ if(this.currentPage > totalPages) this.setPage(totalPages);
727
+ if(totalPages !== totalPagesBefore){
728
+ this.dispatchEvent(new CustomEvent('pageCountChanged', {
729
+ detail: { totalPages },
730
+ bubbles: true
731
+ }));
729
732
  }
730
- });
731
-
732
- this.records.forEach((rec, idx) => {
733
- rec[index] = idx;
734
- });
735
- this.requestUpdate();
736
-
737
- const totalPages = this.getTotalPages();
738
- if (this.currentPage > totalPages) {
739
- this.setPage(totalPages);
740
- }
741
- if (totalPages !== totalPagesBefore) {
742
- this.dispatchEvent(new CustomEvent('pageCountChanged', {
743
- detail: { totalPages },
744
- bubbles: true
733
+ this.dispatchEvent(new CustomEvent('selectionChange', { bubbles: true }));
734
+ };
735
+ if(this.requestDelete){
736
+ const rowEls = originalRecords
737
+ .map(r => this.shadowRoot.querySelector(`.record[data-index="${r[index]}"]`))
738
+ .filter(Boolean);
739
+ rowEls.forEach(el => el.classList.add('pending'));
740
+ const wrappedCommit = () => { rowEls.forEach(el => el.classList.remove('pending')); commit(); };
741
+ const reject = () => { rowEls.forEach(el => el.classList.remove('pending')); };
742
+ this.dispatchEvent(new CustomEvent('requestDelete', {
743
+ detail: { records: originalRecords, approve: wrappedCommit, reject },
744
+ bubbles: true
745
745
  }));
746
+ } else {
747
+ commit();
746
748
  }
747
- this.dispatchEvent(new CustomEvent('selectionChange', { bubbles: true }));
748
749
  }
749
750
 
750
751
  getSelectedRecords() {
@@ -949,54 +950,19 @@ export default class Table extends ShadowComponent {
949
950
  }
950
951
 
951
952
  calculateColumnSizes() {
952
- const newSizes = {};
953
- newSizes.total = 0;
954
-
955
- if (this.enableSelection) newSizes.total += 40;
956
-
957
953
  const beforeEls = Array.from(this.querySelectorAll('[slot="before"]'));
958
954
  const afterEls = Array.from(this.querySelectorAll('[slot="after"]'));
959
-
960
- newSizes.beforeControls = beforeEls.reduce((total, el) => total + (el.maxWidth || 40), 0);
961
- newSizes.afterControls = afterEls.reduce((total, el) => total + (el.maxWidth || 40), 0);
962
-
963
- if (this.hasBeforeControls()) newSizes.total += newSizes.beforeControls;
964
- if (this.hasAfterControls()) newSizes.total += newSizes.afterControls;
965
-
966
- this.fields.forEach(field => {
967
- if (field.size) {
968
- newSizes[field.name] = field.size;
969
- newSizes.total += field.size;
970
- } else {
971
- let maxLength = 0;
972
- this.records.slice(0, 100).forEach(record => {
973
- if(record === null) return;
974
- let value = record[field.name];
975
- if (field.calculator) {
976
- value = field.calculator(record, this);
977
- }
978
- if (field.formatter) {
979
- value = field.formatter(value);
980
- }
981
- if (value && value.toString().length > maxLength) {
982
- maxLength = value.toString().length;
983
- }
984
- });
985
- newSizes[field.name] = Math.max((maxLength * 10 + 32), 128);
986
- if (!field.hidden) newSizes.total += newSizes[field.name];
987
- }
988
- });
989
-
955
+ const newSizes = {
956
+ beforeControls: beforeEls.reduce((total, el) => total + (el.maxWidth || 40), 0),
957
+ afterControls: afterEls.reduce((total, el) => total + (el.maxWidth || 40), 0)
958
+ };
990
959
  const hasUndefinedMaxWidth = [...beforeEls, ...afterEls].some(el => el.maxWidth === undefined);
991
-
992
- if (JSON.stringify(this.columnSizes) !== JSON.stringify(newSizes)) {
960
+ if(JSON.stringify(this.columnSizes) !== JSON.stringify(newSizes)) {
993
961
  this.columnSizes = newSizes;
994
962
  }
995
-
996
- if (hasUndefinedMaxWidth) {
963
+ if(hasUndefinedMaxWidth) {
997
964
  setTimeout(() => this.calculateColumnSizes(), 0);
998
965
  }
999
-
1000
966
  return this.columnSizes;
1001
967
  }
1002
968
 
@@ -1040,13 +1006,12 @@ export default class Table extends ShadowComponent {
1040
1006
  /* Rendering */
1041
1007
 
1042
1008
  render() {
1043
- if (!this.records || !this.fields) {
1009
+ if(!this.records || !this.fields) {
1044
1010
  return html`
1045
1011
  <div id="wrapper">
1046
1012
  <div id="top"><slot name="top"></slot></div>
1047
- <div id="table">
1048
- <div id="fields"></div>
1049
- <div id="records"></div>
1013
+ <div id="table-container">
1014
+ <table><thead><tr></tr></thead><tbody></tbody></table>
1050
1015
  </div>
1051
1016
  <div id="bottom"><slot></slot></div>
1052
1017
  </div>
@@ -1063,16 +1028,23 @@ export default class Table extends ShadowComponent {
1063
1028
 
1064
1029
  return html`
1065
1030
  <div id="wrapper">
1066
- <div id="top" style="width: ${this.columnSizes.total}px"><slot name="top"></slot></div>
1067
- <div id="table">
1068
- <div id="fields" style="width: ${this.columnSizes.total}px">
1069
- ${this.renderFieldsTemplate()}
1070
- </div>
1071
- <div id="records" style="width: ${this.columnSizes.total}px">
1072
- ${this.renderRecordsTemplate()}
1073
- </div>
1031
+ <div id="top"><slot name="top"></slot></div>
1032
+ <div id="table-container">
1033
+ <table>
1034
+ <colgroup>
1035
+ ${this.renderColgroupTemplate()}
1036
+ </colgroup>
1037
+ <thead>
1038
+ <tr>
1039
+ ${this.renderFieldsTemplate()}
1040
+ </tr>
1041
+ </thead>
1042
+ <tbody>
1043
+ ${this.renderRecordsTemplate()}
1044
+ </tbody>
1045
+ </table>
1074
1046
  </div>
1075
- <div id="bottom" style="width: ${this.columnSizes.total}px"><slot></slot></div>
1047
+ <div id="bottom"><slot></slot></div>
1076
1048
  </div>
1077
1049
  <div style="display: none">
1078
1050
  <slot name="before"></slot>
@@ -1084,39 +1056,99 @@ export default class Table extends ShadowComponent {
1084
1056
  static styles = css`
1085
1057
  :host {
1086
1058
  display: block;
1087
- width: 100%;
1088
- overflow: auto;
1089
1059
  margin-bottom: var(--spacer);
1090
1060
  }
1091
1061
  #wrapper {
1092
- width: min-content;
1093
1062
  border: 1px solid var(--c_border);
1094
1063
  border-radius: var(--radius);
1064
+ overflow: hidden;
1095
1065
  }
1096
- #table {
1097
- width: min-content;
1066
+ #table-container {
1067
+ overflow-x: auto;
1098
1068
  }
1099
- #fields,
1100
- .record {
1101
- display: flex;
1069
+ table {
1070
+ width: 100%;
1071
+ border-collapse: collapse;
1102
1072
  }
1103
- #fields {
1073
+ thead tr {
1104
1074
  background-color: var(--c_bg__alt);
1105
- border-bottom: 1px solid var(--c_border);
1106
1075
  }
1107
- .record:not([editing="true"]) .cell:not(.controls),
1108
- #fields .cell:not(.controls) {
1076
+ th, td {
1109
1077
  padding: calc(0.5 * var(--spacer)) var(--spacer);
1078
+ vertical-align: middle;
1079
+ }
1080
+ th:not(:last-child),
1081
+ td:not(:last-child) {
1082
+ border-right: 1px solid var(--c_border);
1083
+ }
1084
+ th:first-child,
1085
+ td:first-child {
1086
+ border-left: none;
1087
+ }
1088
+ th:last-child,
1089
+ td:last-child {
1090
+ border-right: none;
1091
+ }
1092
+ thead tr th {
1093
+ border-top: none;
1094
+ border-bottom: 1px solid var(--c_border);
1110
1095
  }
1111
- .cell {
1096
+ tbody tr:not(:last-child) td {
1097
+ border-bottom: 1px solid var(--c_border);
1098
+ }
1099
+ tbody tr:last-child td {
1100
+ border-bottom: none;
1101
+ }
1102
+ tr.editing td.cell[data-field] {
1103
+ padding: 0;
1104
+ }
1105
+ tr.editing td.cell[data-field] input,
1106
+ tr.editing td.cell[data-field] select {
1107
+ width: 100%;
1108
+ height: 100%;
1109
+ box-sizing: border-box;
1110
+ }
1111
+ tr.pending {
1112
+ pointer-events: none;
1113
+ }
1114
+ tr.pending td {
1115
+ position: relative;
1116
+ overflow: hidden;
1117
+ }
1118
+ tr.pending td::before {
1119
+ content: '';
1120
+ position: absolute;
1121
+ inset: 0;
1122
+ background: linear-gradient(90deg, transparent, rgba(128, 128, 128, 0.15), transparent);
1123
+ transform: translateX(-100%);
1124
+ animation: row-pending 1.2s ease-in-out infinite;
1125
+ }
1126
+ @keyframes row-pending {
1127
+ 0% { transform: translateX(-100%); }
1128
+ 100% { transform: translateX(100%); }
1129
+ }
1130
+ th.controls,
1131
+ td.controls {
1132
+ padding: 0;
1133
+ }
1134
+ td.controls-after,
1135
+ td.controls-before {
1112
1136
  display: flex;
1113
1137
  align-items: center;
1114
1138
  }
1115
- .cell:not(:first-child) {
1116
- border-left: 1px solid var(--c_border);
1139
+ .field-select,
1140
+ .selection {
1141
+ width: 40px;
1142
+ text-align: center;
1117
1143
  }
1118
- .record:not(:last-child) .cell {
1119
- border-bottom: 1px solid var(--c_border);
1144
+ .field-select input,
1145
+ .selection input {
1146
+ width: 1.25rem;
1147
+ height: 1.25rem;
1148
+ }
1149
+ .icon-sort {
1150
+ float: right;
1151
+ opacity: 0.5;
1120
1152
  }
1121
1153
  #top, #bottom {
1122
1154
  display: flex;
@@ -1136,21 +1168,6 @@ export default class Table extends ShadowComponent {
1136
1168
  :host(:not([bottom-controls])) #bottom {
1137
1169
  display: none;
1138
1170
  }
1139
- .field-select,
1140
- .selection {
1141
- display: flex;
1142
- justify-content: center;
1143
- align-items: center;
1144
- }
1145
- .field-select input,
1146
- .selection input {
1147
- width: 1.25rem;
1148
- height: 1.25rem;
1149
- }
1150
- .icon-sort {
1151
- float: right;
1152
- opacity: 0.5;
1153
- }
1154
1171
  `;
1155
1172
 
1156
1173
  /*