kempo 2.7.4 → 2.9.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.
@@ -1 +1 @@
1
- import Component from"./Component.js";import Icon from"./Icon.js";import{toTitleCase}from"../utils/string.js";export default class Table extends Component{constructor(e=!1,t=!1,o={before:[],after:[],top:[],bottom:[]}){super(),this.registerProps({fields:[],records:[],controls:{before:[],after:[],top:[],bottom:[]}}),e&&this.setData(e,t,o)}async render(e){return!!await super.render(e)&&(this.renderControls(this.controls.top,"top"),this.renderFields(),this.renderRecords(),this.renderControls(this.controls.bottom,"bottom"),!0)}renderFields(){const e=this.controls?.before?.length?"<th></th>":"",t=this.controls?.after?.length?"<th></th>":"";this.shadowRoot.getElementById("fields").innerHTML=`${e}${this.fields.map((({label:e})=>`<th>${e}</th>`)).join("")}${t}`}renderRecords(){const e=this.shadowRoot.getElementById("records");e.innerHTML="",this.records.forEach((t=>{const o=document.createElement("tr");this.controls?.before?.length&&o.appendChild(this.renderRowControls(this.controls.before,t)),this.fields.forEach((({name:e})=>{const n=document.createElement("td");let r=t[e]||"";Array.isArray(r)&&(r=r.join(", ")),n.innerHTML=r,o.appendChild(n)})),this.controls?.after?.length&&o.appendChild(this.renderRowControls(this.controls.after,t)),e.appendChild(o)}))}renderRowControls(e=[],t){const o=document.createElement("td");return e.forEach((({html:e,icon:n,action:r,render:s})=>{if(e)o.appendChild(document.createRange().createContextualFragment(e));else if(s&&"function"==typeof s){const e=s(this,t);e instanceof HTMLElement?o.appendChild(e):"string"==typeof e&&o.appendChild(document.createRange().createContextualFragment(e))}else if(n){const e=document.createElement("button");e.appendChild(new Icon(n)),r&&e.addEventListener("click",(()=>r(this,t))),o.appendChild(e)}})),o}renderControls(e=[],t){const o=document.createElement("div");e.forEach((({html:e,icon:t,action:n,render:r})=>{if(e)o.appendChild(document.createRange().createContextualFragment(e));else if(r&&"function"==typeof r){const e=r(this);e instanceof HTMLElement?o.appendChild(e):"string"==typeof e&&o.appendChild(document.createRange().createContextualFragment(e))}else if(t){const e=document.createElement("button");e.appendChild(new Icon(t)),n&&e.addEventListener("click",(()=>n(this))),o.appendChild(e)}})),this.shadowRoot.getElementById(t).innerHTML="",this.shadowRoot.getElementById(t).appendChild(o)}setData(e,t=!1,o={before:[],after:[],top:[],bottom:[]}){this.records=e,this.fields=t||Table.extractFieldsFromRecords(this.records),this.controls=o,this.renderControls(this.controls.top,"top"),this.renderFields(),this.renderRecords(),this.renderControls(this.controls.bottom,"bottom")}get shadowTemplate(){return`\n ${super.shadowTemplate}\n <div class="responsive-table">\n <div id="top"></div>\n <table>\n <thead id="fields"></thead>\n <tbody id="records"></tbody>\n </table>\n <div id="bottom"></div>\n </div>\n `}get shadowStyles(){return`\n ${super.shadowStyles}\n :host {\n display: block;\n margin-bottom: var(--spacer);\n }\n `}static extractFieldsFromRecords(e,t=100){const o=new Set;return e.slice(0,t).forEach((e=>{Object.keys(e).forEach((e=>o.add(e)))})),[...o].map((e=>({name:e,label:toTitleCase(e)})))}}window.customElements.define("k-table",Table);
1
+ import Component from"./Component.js";import Icon from"./Icon.js";import{toTitleCase}from"../utils/string.js";import{onEvent,dispatchEvent}from"../utils/element.js";export default class Table extends Component{static controls={prevPage:{render:e=>{const t=document.createElement("button");return t.classList.add("pq"),t.appendChild(new Icon("chevron-left")),onEvent(t,"click",(()=>e.prevPage())),onEvent(e,"pageChange",(()=>{t.disabled=1===e.getCurrentPage()})),t}},nextPage:{render:e=>{const t=document.createElement("button");return t.classList.add("pq"),t.appendChild(new Icon("chevron-right")),onEvent(t,"click",(()=>e.nextPage())),onEvent(e,"pageChange",(()=>{t.disabled=e.getCurrentPage()===e.getTotalPages()})),t}},pageSelect:{render:e=>new PageSelect(e)},pageSize:{render:e=>new PageSize(e)},spacer:{html:'<div class="flex"></div>'}};constructor({records:e=[],fields:t=[],controls:n={before:[],after:[],top:[],bottom:[]},pageSize:s=100,pageSizeOptions:r=[10,25,50,100,500],currentPage:a=1}={}){super(),this.registerProps({fields:t,records:e,controls:n,pageSize:s,pageSizeOptions:r,currentPage:a})}async render(e){return!!await super.render(e)&&(this.setData({records:this.records,fields:this.fields,controls:this.controls,pageSize:this.pageSize,pageSizeOptions:this.pageSizeOptions,currentPage:this.currentPage}),!0)}renderFields(){if(!this.rendered)return;const e=this.controls?.before?.length?"<th></th>":"",t=this.controls?.after?.length?"<th></th>":"";this.shadowRoot.getElementById("fields").innerHTML=`${e}${this.fields.map((({label:e})=>`<th>${e}</th>`)).join("")}${t}`}renderRecords(){if(!this.rendered)return;const e=this.shadowRoot.getElementById("records");e.innerHTML="";const t=(this.currentPage-1)*this.pageSize,n=t+this.pageSize;this.records.slice(t,n).forEach(((s,r)=>{const a=document.createElement("tr");this.controls?.before?.length&&a.appendChild(this.renderRowControls(this.controls.before,s,t+r)),this.fields.forEach((({name:e,formatter:r,calculator:o})=>{const i=document.createElement("td");if(null===s)i.innerHTML="Loading...",dispatchEvent(this,"fetchRecords",{start:t,end:n});else{let t;o?t=o(s,this):(t=s[e]||"",Array.isArray(t)?t=r?t.map(r).join(", "):t.join(", "):r&&(t=r(t))),i.innerHTML=t}a.appendChild(i)})),this.controls?.after?.length&&a.appendChild(this.renderRowControls(this.controls.after,s,t+r)),e.appendChild(a)}))}renderRowControls(e=[],t,n){if(!this.rendered)return;const s=document.createElement("td");return e.forEach((({html:e,icon:r,action:a,render:o})=>{if(e)s.appendChild(document.createRange().createContextualFragment(e));else if(o&&"function"==typeof o){const e=o(this,t,n);e instanceof HTMLElement?s.appendChild(e):"string"==typeof e&&s.appendChild(document.createRange().createContextualFragment(e))}else if(r){const e=document.createElement("button");e.classList.add("pq"),e.appendChild(new Icon(r)),a&&onEvent(e,"click",(()=>a(this,t,n))),s.appendChild(e)}})),s}renderControls(e=[],t){if(!this.rendered)return;const n=this.shadowRoot.getElementById(t);n.innerHTML="",e.forEach((({html:e,icon:t,action:s,render:r})=>{if(e)n.appendChild(document.createRange().createContextualFragment(e));else if(r&&"function"==typeof r){const e=r(this);e instanceof HTMLElement?n.appendChild(e):"string"==typeof e&&n.appendChild(document.createRange().createContextualFragment(e))}else if(t){const e=document.createElement("button");e.classList.add("pq"),e.appendChild(new Icon(t)),s&&onEvent(e,"click",(()=>s(this))),n.appendChild(e)}}))}getCurrentPage(){return this.currentPage}getTotalPages(){return Math.ceil(this.records.length/this.pageSize)}setPage(e){e<1||e>this.getTotalPages()||(this.currentPage=e,this.renderRecords(),dispatchEvent(this,"pageChange"))}nextPage(){this.currentPage<this.getTotalPages()&&this.setPage(this.currentPage+1)}prevPage(){this.currentPage>1&&this.setPage(this.currentPage-1)}setPageSize(e){this.pageSize=e,this.currentPage=1,this.renderRecords(),dispatchEvent(this,"pageSizeChange")}setData({records:e=!1,fields:t=!1,controls:n=!1,pageSize:s=!1,pageSizeOptions:r=!1,currentPage:a=!1}={}){let o=!1,i=!1;e&&(this.records=e,this.fields=t||Table.extractFieldsFromRecords(this.records),o=!0),s&&(this.pageSize=s,o=!0),r&&(this.pageSizeOptions=r),n&&(this.controls=n,i=!0),a&&(this.currentPage=a,o=!0),o&&(this.renderFields(),this.renderRecords()),i&&(this.renderControls(this.controls.top,"top"),this.renderControls(this.controls.bottom,"bottom"))}setRecords(e){this.records=e,this.currentPage=1,this.renderRecords(),dispatchEvent(this,"recordsSet",{records:e})}setupFetchRecords(e,t){const n=this.records.length;n<e&&(this.records.length=e,this.records.fill(null,n)),onEvent(this,"fetchRecords",(async e=>{const{start:n,end:s}=e.detail,r=await t(n,s-n);this.records.splice(n,r.length,...r),this.renderRecords()}))}addRecord(e){this.records.push(e),this.renderRecords(),dispatchEvent(this,"recordAdded",{record:e})}updateRecord(e,t){this.records[e]=t;const n=(this.currentPage-1)*this.pageSize,s=n+this.pageSize;e>=n&&e<s&&this.renderRecords()}deleteRecord(e){e>=0&&e<this.records.length&&(this.records.splice(e,1),this.renderRecords(),dispatchEvent(this,"recordDeleted",{index:e}))}get shadowTemplate(){return`\n ${super.shadowTemplate}\n <div class="responsive-table">\n <div id="top"></div>\n <table class="b0">\n <thead id="fields"></thead>\n <tbody id="records"></tbody>\n </table>\n <div id="bottom"></div>\n </div>\n `}get shadowStyles(){return`\n ${super.shadowStyles}\n :host {\n display: block;\n margin-bottom: var(--spacer);\n border: 1px solid var(--c_border);\n border-radius: var(--radius);\n }\n #top, #bottom {\n display: flex;\n }\n th {\n border-top: none;\n }\n tr:last-child td:first-child {\n border-bottom-left-radius: 0;\n }\n tr:last-child td:last-child {\n border-bottom-right-radius: 0;\n }\n tr:last-child td {\n border-bottom: none;\n }\n th:first-child, td:first-child {\n border-left: none;\n }\n th:last-child, td:last-child {\n border-right: none;\n }\n #top:not(:empty) {\n border-bottom: 1px solid var(--c_border);\n }\n #bottom:not(:empty) {\n border-top: 1px solid var(--c_border);\n }\n `}static extractFieldsFromRecords(e,t=100){const n=new Set;return e.slice(0,t).forEach((e=>{Object.keys(e).forEach((e=>n.add(e)))})),[...n].map((e=>({name:e,label:toTitleCase(e)})))}}window.customElements.define("k-table",Table);class PageSelect extends Component{constructor(e){super(),this.table=e,this.classList.add("mxq")}connectedCallback(){this.render()}async render(){return!!await super.render()&&(this.updateOptions(),!0)}updateOptions(){const e=this.shadowRoot.getElementById("pageSelect");e.innerHTML="";for(let t=1;t<=this.table.getTotalPages();t++){const n=document.createElement("option");n.value=t,n.textContent=t,e.appendChild(n)}e.value=this.table.getCurrentPage();this.shadowRoot.getElementById("totalPages").textContent=this.table.getTotalPages(),onEvent(e,"change",(()=>{this.table.setPage(parseInt(e.value))})),onEvent(this.table,"pageSizeChange",(()=>{this.updateOptions()})),onEvent(this.table,"pageChange",(()=>{e.value=this.table.getCurrentPage()}))}get shadowTemplate(){return'\n <select id="pageSelect" class="mxq"></select>\n <label> out of <span id="totalPages"></span></label>\n '}get shadowStyles(){return"\n :host {\n display: inline-flex;\n width: max-content;\n align-items: baseline;\n }\n #pageSelect, label {\n display: inline;\n }\n label {\n white-space: nowrap;\n }\n "}}window.customElements.define("k-table-page-select",PageSelect);class PageSize extends Component{constructor(e){super(),this.table=e,this.classList.add("mxq")}connectedCallback(){this.render()}async render(){if(await super.render()){const e=this.shadowRoot.getElementById("pageSizeSelect");return this.table.pageSizeOptions.forEach((t=>{const n=document.createElement("option");n.value=t,n.textContent=t,e.appendChild(n)})),e.value=this.table.pageSize,onEvent(e,"change",(()=>{this.table.setPageSize(parseInt(e.value))})),onEvent(this.table,"pageSizeChange",(()=>{e.value=this.table.pageSize})),!0}return!1}get shadowTemplate(){return'\n <label for="pageSizeSelect">Page Size: </label>\n <select id="pageSizeSelect" class="mxq"></select>\n '}get shadowStyles(){return"\n :host {\n display: inline-flex;\n width: max-content;\n align-items: baseline;\n }\n label {\n white-space: nowrap;\n }\n "}}window.customElements.define("k-table-page-size",PageSize);
File without changes
package/icons/add.svg ADDED
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M440-440H200v-80h240v-240h80v240h240v80H520v240h-80v-240Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M480-160q-134 0-227-93t-93-227q0-134 93-227t227-93q69 0 132 28.5T720-690v-110h80v280H520v-80h168q-32-56-87.5-88T480-720q-100 0-170 70t-70 170q0 100 70 170t170 70q77 0 139-44t87-116h84q-28 106-114 173t-196 67Z"/></svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kempo",
3
- "version": "2.7.4",
3
+ "version": "2.9.0",
4
4
  "type": "module",
5
5
  "description": "A web component library",
6
6
  "main": "dist/index.js",
@@ -1,68 +1,196 @@
1
1
  import Component from './Component.js';
2
- import Icon from './Icon.js'; // Import Icon component
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';
3
7
  import { toTitleCase } from '../utils/string.js';
8
+ import { onEvent, dispatchEvent } from '../utils/element.js';
4
9
 
5
10
  export default class Table extends Component {
6
- constructor(data = false, fields = false, controls = { before: [], after: [], top: [], bottom: [] }){
11
+ static controls = {
12
+ prevPage: {
13
+ render: (table) => {
14
+ const $button = document.createElement('button');
15
+ $button.classList.add('pq');
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');
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
+ };
58
+
59
+ constructor({
60
+ records = [],
61
+ fields = [],
62
+ controls = { before: [], after: [], top: [], bottom: [] },
63
+ pageSize = 100,
64
+ pageSizeOptions = [10, 25, 50, 100, 500],
65
+ currentPage = 1,
66
+ enableSelection = false,
67
+ } = {}) {
7
68
  super();
8
69
 
9
70
  this.registerProps({
10
- fields: [],
11
- records: [],
12
- controls: { before: [], after: [], top: [], bottom: [] }
71
+ fields: fields,
72
+ records: records,
73
+ controls: controls,
74
+ pageSize,
75
+ pageSizeOptions,
76
+ currentPage,
77
+ enableSelection,
78
+ selectedIndexes: [], // Renamed property
13
79
  });
14
80
 
15
81
  /* Init */
16
- if(data){
17
- this.setData(data, fields, controls);
18
- }
82
+ //
19
83
  }
20
84
  async render(force){
21
85
  if(await super.render(force)){
22
- this.renderControls(this.controls.top, 'top');
23
- this.renderFields();
24
- this.renderRecords();
25
- this.renderControls(this.controls.bottom, 'bottom');
86
+ this.setData({
87
+ records: this.records,
88
+ fields: this.fields,
89
+ controls: this.controls,
90
+ pageSize: this.pageSize,
91
+ pageSizeOptions: this.pageSizeOptions,
92
+ currentPage: this.currentPage
93
+ });
26
94
  return true;
27
95
  }
28
96
  return false;
29
97
  }
30
98
  renderFields(){
99
+ if(!this.rendered) return;
31
100
  const beforeControls = this.controls?.before?.length ? '<th></th>' : '';
32
101
  const afterControls = this.controls?.after?.length ? '<th></th>' : '';
33
- this.shadowRoot.getElementById('fields').innerHTML = `${beforeControls}${this.fields.map(({label})=>`<th>${label}</th>`).join('')}${afterControls}`;
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}`;
104
+
105
+ if (this.enableSelection) {
106
+ const selectAllCheckbox = this.shadowRoot.getElementById('select-all');
107
+ selectAllCheckbox.addEventListener('change', () => {
108
+ if (selectAllCheckbox.checked) {
109
+ this.selectAllOnPage();
110
+ } else {
111
+ this.deselectAllOnPage();
112
+ }
113
+ });
114
+ onEvent(this, 'selectionChange pageChange', () => {
115
+ if(this.allOnPageSelected()){
116
+ selectAllCheckbox.checked = true;
117
+ } else {
118
+ selectAllCheckbox.checked = false;
119
+ }
120
+ });
121
+ }
34
122
  }
35
123
  renderRecords(){
124
+ if(!this.rendered) return;
36
125
  const $records = this.shadowRoot.getElementById('records');
37
126
  $records.innerHTML = '';
38
- this.records.forEach((record) => {
127
+ const start = (this.currentPage - 1) * this.pageSize;
128
+ const end = start + this.pageSize;
129
+ const paginatedRecords = this.records.slice(start, end);
130
+ paginatedRecords.forEach((record, index) => {
39
131
  const $tr = document.createElement('tr');
132
+ if (this.enableSelection) {
133
+ const $td = document.createElement('td');
134
+ $td.classList.add('selection');
135
+ const $checkbox = document.createElement('input');
136
+ $checkbox.type = 'checkbox';
137
+ $checkbox.checked = this.selectedIndexes.includes(start + index);
138
+ $checkbox.addEventListener('change', () => {
139
+ if ($checkbox.checked) {
140
+ this.selectedIndexes.push(start + index);
141
+ } else {
142
+ this.selectedIndexes = this.selectedIndexes.filter(i => i !== start + index);
143
+ }
144
+ dispatchEvent(this, 'selectionChange', { selectedIndexes: this.selectedIndexes });
145
+ });
146
+ $td.appendChild($checkbox);
147
+ $tr.appendChild($td);
148
+ }
40
149
  if(this.controls?.before?.length){
41
- $tr.appendChild(this.renderRowControls(this.controls.before, record));
150
+ $tr.appendChild(this.renderRowControls(this.controls.before, record, start + index));
42
151
  }
43
- this.fields.forEach(({name}) => {
152
+ this.fields.forEach(({name, formatter, calculator}) => {
44
153
  const $td = document.createElement('td');
45
- let value = record[name] || '';
46
- if (Array.isArray(value)) {
47
- value = value.join(', ');
154
+ if (record === null) {
155
+ $td.innerHTML = 'Loading...';
156
+ dispatchEvent(this, 'fetchRecords', { start, end });
157
+ } else {
158
+ let value;
159
+ if(calculator){
160
+ value = calculator(record, this);
161
+ } else {
162
+ value = record[name] || '';
163
+ if (Array.isArray(value)) {
164
+ if(formatter){
165
+ value = value.map(formatter).join(', ');
166
+ } else {
167
+ value = value.join(', ');
168
+ }
169
+ } else {
170
+ if(formatter){
171
+ value = formatter(value);
172
+ }
173
+ }
174
+ }
175
+ $td.innerHTML = value;
48
176
  }
49
- $td.innerHTML = value;
50
177
  $tr.appendChild($td);
51
178
  });
52
179
  if(this.controls?.after?.length){
53
- $tr.appendChild(this.renderRowControls(this.controls.after, record));
180
+ $tr.appendChild(this.renderRowControls(this.controls.after, record, start + index));
54
181
  }
55
182
  $records.appendChild($tr);
56
183
  });
57
184
  }
58
185
 
59
- renderRowControls(controls = [], record) {
186
+ renderRowControls(controls = [], record, index) {
187
+ if(!this.rendered) return;
60
188
  const $td = document.createElement('td');
61
189
  controls.forEach(({ html, icon, action, render }) => {
62
190
  if(html){
63
191
  $td.appendChild(document.createRange().createContextualFragment(html));
64
192
  } else if(render && typeof(render) === 'function'){
65
- const rendered = render(this, record);
193
+ const rendered = render(this, record, index);
66
194
  if(rendered instanceof HTMLElement){
67
195
  $td.appendChild(rendered);
68
196
  } else if(typeof(rendered) === 'string'){
@@ -70,9 +198,10 @@ export default class Table extends Component {
70
198
  }
71
199
  } else if(icon){
72
200
  const $button = document.createElement('button');
201
+ $button.classList.add('pq');
73
202
  $button.appendChild(new Icon(icon));
74
203
  if (action) {
75
- $button.addEventListener('click', () => action(this, record));
204
+ onEvent($button, 'click', () => action(this, record, index));
76
205
  }
77
206
  $td.appendChild($button);
78
207
  }
@@ -81,7 +210,9 @@ export default class Table extends Component {
81
210
  }
82
211
 
83
212
  renderControls(controls = [], position) {
84
- const $container = document.createElement('div');
213
+ if(!this.rendered) return;
214
+ const $container = this.shadowRoot.getElementById(position);
215
+ $container.innerHTML = '';
85
216
  controls.forEach(({ html, icon, action, render }) => {
86
217
  if(html){
87
218
  $container.appendChild(document.createRange().createContextualFragment(html));
@@ -94,25 +225,178 @@ export default class Table extends Component {
94
225
  }
95
226
  } else if(icon){
96
227
  const $button = document.createElement('button');
228
+ $button.classList.add('pq');
97
229
  $button.appendChild(new Icon(icon));
98
230
  if (action) {
99
- $button.addEventListener('click', () => action(this));
231
+ onEvent($button, 'click', () => action(this));
100
232
  }
101
233
  $container.appendChild($button);
102
234
  }
103
235
  });
104
- this.shadowRoot.getElementById(position).innerHTML = '';
105
- this.shadowRoot.getElementById(position).appendChild($container);
106
236
  }
107
237
 
108
- setData(records, fields = false, controls = { before: [], after: [], top: [], bottom: [] }){
238
+ getCurrentPage() {
239
+ return this.currentPage;
240
+ }
241
+
242
+ getTotalPages() {
243
+ return Math.ceil(this.records.length / this.pageSize);
244
+ }
245
+
246
+ setPage(page) {
247
+ if (page < 1 || page > this.getTotalPages()) return;
248
+ this.currentPage = page;
249
+ this.renderRecords();
250
+ // Trigger page change event
251
+ dispatchEvent(this, 'pageChange');
252
+ }
253
+
254
+ nextPage() {
255
+ if (this.currentPage < this.getTotalPages()) {
256
+ this.setPage(this.currentPage + 1);
257
+ }
258
+ }
259
+
260
+ prevPage() {
261
+ if (this.currentPage > 1) {
262
+ this.setPage(this.currentPage - 1);
263
+ }
264
+ }
265
+
266
+ setPageSize(pageSize) {
267
+ this.pageSize = pageSize;
268
+ this.currentPage = 1;
269
+ this.renderRecords();
270
+ // Trigger page size change event
271
+ dispatchEvent(this, 'pageSizeChange');
272
+ }
273
+
274
+ setData({
275
+ records = false,
276
+ fields = false,
277
+ controls = false,
278
+ pageSize = false,
279
+ pageSizeOptions = false,
280
+ currentPage = false,
281
+ enableSelection = false // Add enableSelection property
282
+ } = {}) {
283
+ let rerender = false;
284
+ let rerenderControls = false;
285
+ if(records){
286
+ this.records = records;
287
+ this.fields = fields || Table.extractFieldsFromRecords(this.records);
288
+ rerender = true;
289
+ }
290
+ if (pageSize) {
291
+ this.pageSize = pageSize;
292
+ rerender = true;
293
+ }
294
+ if (pageSizeOptions) {
295
+ this.pageSizeOptions = pageSizeOptions;
296
+ }
297
+ if (controls) {
298
+ this.controls = controls;
299
+ rerenderControls = true;
300
+ }
301
+ if(currentPage){
302
+ this.currentPage = currentPage;
303
+ rerender = true;
304
+ }
305
+ if(enableSelection !== undefined){
306
+ this.enableSelection = enableSelection;
307
+ rerender = true;
308
+ }
309
+ if(rerender){
310
+ this.renderFields();
311
+ this.renderRecords();
312
+ }
313
+ if(rerenderControls){
314
+ this.renderControls(this.controls.top, 'top');
315
+ this.renderControls(this.controls.bottom, 'bottom');
316
+ }
317
+ }
318
+
319
+ setRecords(records) {
109
320
  this.records = records;
110
- this.fields = fields || Table.extractFieldsFromRecords(this.records);
111
- this.controls = controls;
112
- this.renderControls(this.controls.top, 'top');
113
- this.renderFields();
321
+ this.currentPage = 1;
322
+ this.renderRecords();
323
+ dispatchEvent(this, 'recordsSet', { records });
324
+ }
325
+
326
+ setupFetchRecords(totalRecords, callback) {
327
+ const previousLength = this.records.length;
328
+ if (previousLength < totalRecords) {
329
+ this.records.length = totalRecords;
330
+ this.records.fill(null, previousLength);
331
+ }
332
+
333
+ onEvent(this, 'fetchRecords', async (event) => {
334
+ const { start, end } = event.detail;
335
+ const records = await callback(start, end - start);
336
+ this.records.splice(start, records.length, ...records);
337
+ this.renderRecords();
338
+ });
339
+ }
340
+
341
+ addRecord(record) {
342
+ this.records.push(record);
343
+ this.renderRecords();
344
+ dispatchEvent(this, 'recordAdded', { record });
345
+ }
346
+
347
+ updateRecord(index, newRecord) {
348
+ this.records[index] = newRecord;
349
+ const start = (this.currentPage - 1) * this.pageSize;
350
+ const end = start + this.pageSize;
351
+ if (index >= start && index < end) {
352
+ this.renderRecords();
353
+ }
354
+ }
355
+
356
+ deleteRecord(index) {
357
+ if (index >= 0 && index < this.records.length) {
358
+ // Deselect the record before deleting
359
+ this.selectedIndexes = this.selectedIndexes.filter(i => i !== index);
360
+ dispatchEvent(this, 'selectionChange', { selectedIndexes: this.selectedIndexes });
361
+ this.records.splice(index, 1);
362
+ this.renderRecords();
363
+ dispatchEvent(this, 'recordDeleted', { index });
364
+ }
365
+ }
366
+
367
+ getSelectedRecords() {
368
+ return this.selectedIndexes.map(index => this.records[index]);
369
+ }
370
+
371
+ selectAllOnPage() {
372
+ const start = (this.currentPage - 1) * this.pageSize;
373
+ const end = Math.min(start + this.pageSize, this.records.length);
374
+ for (let i = start; i < end; i++) {
375
+ if (!this.selectedIndexes.includes(i)) {
376
+ this.selectedIndexes.push(i);
377
+ }
378
+ }
114
379
  this.renderRecords();
115
- this.renderControls(this.controls.bottom, 'bottom');
380
+ dispatchEvent(this, 'selectionChange', { selectedIndexes: this.selectedIndexes });
381
+ }
382
+
383
+ deselectAllOnPage() {
384
+ const start = (this.currentPage - 1) * this.pageSize;
385
+ const end = Math.min(start + this.pageSize, this.records.length);
386
+ this.selectedIndexes = this.selectedIndexes.filter(index => index < start || index >= end);
387
+ this.renderRecords();
388
+ dispatchEvent(this, 'selectionChange', { selectedIndexes: this.selectedIndexes });
389
+ }
390
+
391
+ allOnPageSelected() {
392
+ const start = (this.currentPage - 1) * this.pageSize;
393
+ const end = Math.min(start + this.pageSize, this.records.length);
394
+ for (let i = start; i < end; i++) {
395
+ if (!this.selectedIndexes.includes(i)) {
396
+ return false;
397
+ }
398
+ }
399
+ return true;
116
400
  }
117
401
 
118
402
  get shadowTemplate(){
@@ -120,7 +404,7 @@ export default class Table extends Component {
120
404
  ${super.shadowTemplate}
121
405
  <div class="responsive-table">
122
406
  <div id="top"></div>
123
- <table>
407
+ <table class="b0">
124
408
  <thead id="fields"></thead>
125
409
  <tbody id="records"></tbody>
126
410
  </table>
@@ -134,6 +418,44 @@ export default class Table extends Component {
134
418
  :host {
135
419
  display: block;
136
420
  margin-bottom: var(--spacer);
421
+ border: 1px solid var(--c_border);
422
+ border-radius: var(--radius);
423
+ }
424
+ #top, #bottom {
425
+ display: flex;
426
+ }
427
+ th {
428
+ border-top: none;
429
+ }
430
+ tr:last-child td:first-child {
431
+ border-bottom-left-radius: 0;
432
+ }
433
+ tr:last-child td:last-child {
434
+ border-bottom-right-radius: 0;
435
+ }
436
+ tr:last-child td {
437
+ border-bottom: none;
438
+ }
439
+ th:first-child, td:first-child {
440
+ border-left: none;
441
+ }
442
+ th:last-child, td:last-child {
443
+ border-right: none;
444
+ }
445
+ #top:not(:empty) {
446
+ border-bottom: 1px solid var(--c_border);
447
+ }
448
+ #bottom:not(:empty) {
449
+ border-top: 1px solid var(--c_border);
450
+ }
451
+ th.selection, td.selection {
452
+ width: 50px;
453
+ }
454
+ th.selection input,
455
+ td.selection input {
456
+ margin: 0;
457
+ width: 1.35rem;
458
+ height: 1.35rem;
137
459
  }
138
460
  `;
139
461
  }
@@ -0,0 +1,58 @@
1
+ import Component from '../Component.js';
2
+ import { onEvent } from '../../utils/element.js';
3
+
4
+ export default class DeleteSelected extends Component {
5
+ constructor(table) {
6
+ super();
7
+ this.table = table;
8
+ this.classList.add('mxq');
9
+ }
10
+
11
+ async render() {
12
+ if (await super.render()) {
13
+ this.updateButtonState();
14
+ const $button = this.shadowRoot.getElementById('deleteSelectedButton');
15
+ onEvent($button, 'click', () => this.deleteSelected());
16
+ onEvent(this.table, 'selectionChange', () => this.updateButtonState());
17
+ return true;
18
+ }
19
+ return false;
20
+ }
21
+
22
+ deleteSelected() {
23
+ const selectedIndexes = this.table.selectedIndexes;
24
+ selectedIndexes.sort((a, b) => b - a); // Sort in descending order
25
+ selectedIndexes.forEach(index => this.table.deleteRecord(index));
26
+ this.table.selectedIndexes = [];
27
+ this.table.renderRecords();
28
+ this.updateButtonState();
29
+ }
30
+
31
+ updateButtonState() {
32
+ const $button = this.shadowRoot.getElementById('deleteSelectedButton');
33
+ $button.disabled = this.table.selectedIndexes.length === 0;
34
+ }
35
+
36
+ get shadowTemplate() {
37
+ return /*html*/`
38
+ <button id="deleteSelectedButton" class="pq">
39
+ <k-icon name="delete"></k-icon>
40
+ </button>
41
+ `;
42
+ }
43
+
44
+ get shadowStyles() {
45
+ return /*css*/`
46
+ :host {
47
+ display: inline-flex;
48
+ width: max-content;
49
+ align-items: baseline;
50
+ }
51
+ button {
52
+ display: flex;
53
+ align-items: center;
54
+ }
55
+ `;
56
+ }
57
+ }
58
+ window.customElements.define('k-table-delete-selected', DeleteSelected);
@@ -0,0 +1,68 @@
1
+ import Component from '../Component.js';
2
+ import { onEvent } from '../../utils/element.js';
3
+
4
+ export default class PageSelect extends Component {
5
+ constructor(table) {
6
+ super();
7
+ this.table = table;
8
+ this.classList.add('mxq');
9
+ }
10
+
11
+ async render() {
12
+ if(await super.render()){
13
+ this.updateOptions();
14
+ return true;
15
+ }
16
+ return false;
17
+ }
18
+
19
+ updateOptions() {
20
+ const $select = this.shadowRoot.getElementById('pageSelect');
21
+ $select.innerHTML = '';
22
+ for (let i = 1; i <= this.table.getTotalPages(); i++) {
23
+ const $option = document.createElement('option');
24
+ $option.value = i;
25
+ $option.textContent = i;
26
+ $select.appendChild($option);
27
+ }
28
+ $select.value = this.table.getCurrentPage();
29
+ const $totalPages = this.shadowRoot.getElementById('totalPages');
30
+ $totalPages.textContent = this.table.getTotalPages();
31
+
32
+ onEvent($select, 'change', () => {
33
+ this.table.setPage(parseInt($select.value));
34
+ });
35
+
36
+ onEvent(this.table, 'pageSizeChange', () => {
37
+ this.updateOptions();
38
+ });
39
+
40
+ onEvent(this.table, 'pageChange', () => {
41
+ $select.value = this.table.getCurrentPage();
42
+ });
43
+ }
44
+
45
+ get shadowTemplate() {
46
+ return /*html*/`
47
+ <select id="pageSelect" class="mxq"></select>
48
+ <label> out of <span id="totalPages"></span></label>
49
+ `;
50
+ }
51
+
52
+ get shadowStyles() {
53
+ return /*css*/`
54
+ :host {
55
+ display: inline-flex;
56
+ width: max-content;
57
+ align-items: baseline;
58
+ }
59
+ #pageSelect, label {
60
+ display: inline;
61
+ }
62
+ label {
63
+ white-space: nowrap;
64
+ }
65
+ `;
66
+ }
67
+ }
68
+ window.customElements.define('k-table-page-select', PageSelect);