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.
@@ -0,0 +1,143 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Server Sync - Table - Kempo Docs - A Web Components Solution</title>
7
+ <link rel="icon" type="image/png" sizes="48x48" href="../media/icon48.png">
8
+ <link rel="manifest" href="../manifest.json" />
9
+ <link rel="stylesheet" href="../kempo-vars.css" />
10
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/kempo-css@1.3.11/dist/kempo.min.css" />
11
+ <link rel="stylesheet" href="../kempo-hljs.css" />
12
+ <link rel="stylesheet" href="../styles.css" />
13
+ <script>window.litDisableBundleWarning = true;</script>
14
+ <script>
15
+ window.contacts = [];
16
+ function generateContact(){
17
+ const maleNames = [
18
+ "Liam", "Noah", "Oliver", "Elijah", "William", "James", "Benjamin",
19
+ "Lucas", "Henry", "Alexander", "Mason", "Michael", "Ethan", "Daniel",
20
+ "Jacob", "Logan", "Jackson", "Levi", "Sebastian", "Mateo", "Jack",
21
+ "Owen", "Theodore", "Aiden", "Samuel", "Joseph", "John", "David",
22
+ "Wyatt", "Matthew"
23
+ ];
24
+ const femaleNames = [
25
+ "Olivia", "Emma", "Ava", "Sophia", "Isabella", "Mia", "Amelia",
26
+ "Harper", "Evelyn", "Abigail", "Emily", "Ella", "Elizabeth",
27
+ "Camila", "Luna", "Sofia", "Avery", "Mila", "Aria", "Scarlett",
28
+ "Penelope", "Layla", "Chloe", "Victoria", "Madison", "Eleanor",
29
+ "Grace", "Nora", "Riley", "Zoey"
30
+ ];
31
+ const lastNames = [
32
+ "Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller",
33
+ "Davis", "Rodriguez", "Martinez", "Hernandez", "Lopez", "Gonzalez",
34
+ "Wilson", "Anderson", "Thomas", "Taylor", "Moore", "Jackson", "Martin",
35
+ "Lee", "Perez", "Thompson", "White", "Harris", "Sanchez", "Clark",
36
+ "Ramirez", "Lewis", "Robinson"
37
+ ];
38
+ const gender = Math.random() > 0.5 ? 'm' : 'f';
39
+ const firstName = gender === 'm' ? maleNames[Math.floor(Math.random() * maleNames.length)] : femaleNames[Math.floor(Math.random() * femaleNames.length)];
40
+ const lastName = lastNames[Math.floor(Math.random() * lastNames.length)];
41
+ const name = `${firstName} ${lastName}`;
42
+ const email = `${firstName.toLowerCase()}.${lastName.toLowerCase()}@mailserver.com`;
43
+ const birthday = `${Math.floor(Math.random() * 100) + 1920}-${String(Math.floor(Math.random() * 12) + 1).padStart(2, '0')}-${String(Math.floor(Math.random() * 28) + 1).padStart(2, '0')}`;
44
+ const phoneNumber = `(${Math.floor(Math.random() * 900) + 100}) ${Math.floor(Math.random() * 900) + 100}-${Math.floor(Math.random() * 9000) + 1000}`;
45
+ return { name, email, birthday, phoneNumber, gender };
46
+ }
47
+ for(let i = 0; i < 10; i++){
48
+ contacts.push(generateContact());
49
+ }
50
+ </script>
51
+ </head>
52
+ <body>
53
+ <k-import src="../nav-1.inc.html"></k-import>
54
+ <h1 class="ta-center">Table - Server Sync</h1>
55
+ <main>
56
+ <a href="./table.html"><h5>Back to Table Component Documentation</h5></a>
57
+
58
+ <p>
59
+ The table supports an optional server-sync workflow for saving edits and deleting records. When the
60
+ <code>request-edit</code> attribute is present, calling <code>saveEditedRecord()</code> fires a
61
+ <code>requestSave</code> event instead of applying the changes immediately. When the <code>request-delete</code>
62
+ attribute is present, calling <code>deleteRecord()</code> or <code>deleteSelected()</code> fires a
63
+ <code>requestDelete</code> event instead of deleting immediately.
64
+ </p>
65
+ <p>
66
+ Both events expose an <code>approve()</code> function on <code>event.detail</code>. Call it once the server
67
+ operation succeeds to commit the change. If the operation fails, simply do not call <code>approve()</code> —
68
+ the record stays in its current state so the user can retry. <code>requestSave</code> also provides
69
+ <code>event.detail.record</code> (the original record) and <code>event.detail.newData</code> (the values
70
+ read from the editor inputs). <code>requestDelete</code> provides <code>event.detail.records</code>, an
71
+ array of the records targeted for deletion.
72
+ </p>
73
+ <p>
74
+ The example below mocks a server call that takes 1–3 seconds and randomly fails 50% of the time.
75
+ Toasts are used to notify the user of success or failure.
76
+ </p>
77
+
78
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">k-table</span> <span class="hljs-attr">id</span>=<span class="hljs-string">serverSyncExample</span> <span class="hljs-attr">enable-selection</span> <span class="hljs-attr">request-edit</span> <span class="hljs-attr">request-delete</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">k-tc-edit</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">after</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-tc-edit</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">k-tc-delete-record</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">after</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-tc-delete-record</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">k-tc-delete-selected</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">top</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-tc-delete-selected</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-table</span>&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">module</span>&gt;</span><span class="language-javascript"><br> <span class="hljs-keyword">import</span> <span class="hljs-title class_">Toast</span> <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;/src/components/Toast.js&#x27;</span>;<br> <span class="hljs-keyword">await</span> <span class="hljs-variable language_">window</span>.<span class="hljs-property">customElements</span>.<span class="hljs-title function_">whenDefined</span>(<span class="hljs-string">&#x27;k-table&#x27;</span>);<br> <span class="hljs-keyword">const</span> table = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;serverSyncExample&#x27;</span>);<br> <span class="hljs-keyword">const</span> <span class="hljs-title function_">mockServer</span> = (<span class="hljs-params"></span>) =&gt; <span class="hljs-keyword">new</span> <span class="hljs-title class_">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {<br> <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {<br> <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">random</span>() &lt; <span class="hljs-number">0.8</span> ? <span class="hljs-title function_">resolve</span>() : <span class="hljs-title function_">reject</span>();<br> }, <span class="hljs-number">1000</span> + <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">random</span>() * <span class="hljs-number">2000</span>);<br> });<br> table.<span class="hljs-title function_">setData</span>({<br> <span class="hljs-attr">records</span>: contacts,<br> <span class="hljs-attr">fields</span>: [{<br> <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;name&#x27;</span>,<br> <span class="hljs-attr">label</span>: <span class="hljs-string">&#x27;Name&#x27;</span><br> }, {<br> <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;email&#x27;</span>,<br> <span class="hljs-attr">label</span>: <span class="hljs-string">&#x27;Email&#x27;</span><br> }, {<br> <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;birthday&#x27;</span>,<br> <span class="hljs-attr">label</span>: <span class="hljs-string">&#x27;Birthday&#x27;</span>,<br> <span class="hljs-attr">type</span>: <span class="hljs-string">&#x27;date&#x27;</span><br> }, {<br> <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;phoneNumber&#x27;</span>,<br> <span class="hljs-attr">label</span>: <span class="hljs-string">&#x27;Phone Number&#x27;</span><br> }, {<br> <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;gender&#x27;</span>,<br> <span class="hljs-attr">label</span>: <span class="hljs-string">&#x27;Gender&#x27;</span>,<br> <span class="hljs-attr">formatter</span>: <span class="hljs-function"><span class="hljs-params">v</span> =&gt;</span> v === <span class="hljs-string">&#x27;m&#x27;</span> ? <span class="hljs-string">&#x27;Male&#x27;</span> : <span class="hljs-string">&#x27;Female&#x27;</span><br> }]<br> });<br> table.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">&#x27;requestSave&#x27;</span>, <span class="hljs-title function_">async</span> ({<br> <span class="hljs-attr">detail</span>: {<br> approve,<br> reject<br> }<br> }) =&gt; {<br> <span class="hljs-keyword">try</span> {<br> <span class="hljs-keyword">await</span> <span class="hljs-title function_">mockServer</span>();<br> <span class="hljs-title function_">approve</span>();<br> <span class="hljs-title class_">Toast</span>.<span class="hljs-title function_">success</span>(<span class="hljs-string">&#x27;Record saved.&#x27;</span>, {<br> <span class="hljs-attr">timeout</span>: <span class="hljs-number">3000</span><br> });<br> } <span class="hljs-keyword">catch</span> {<br> <span class="hljs-title function_">reject</span>();<br> <span class="hljs-title class_">Toast</span>.<span class="hljs-title function_">error</span>(<span class="hljs-string">&#x27;Save failed. The record remains open for editing.&#x27;</span>, {<br> <span class="hljs-attr">timeout</span>: <span class="hljs-number">5000</span><br> });<br> }<br> });<br> table.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">&#x27;requestDelete&#x27;</span>, <span class="hljs-title function_">async</span> ({<br> <span class="hljs-attr">detail</span>: {<br> records,<br> approve,<br> reject<br> }<br> }) =&gt; {<br> <span class="hljs-keyword">const</span> count = records.<span class="hljs-property">length</span>;<br> <span class="hljs-keyword">try</span> {<br> <span class="hljs-keyword">await</span> <span class="hljs-title function_">mockServer</span>();<br> <span class="hljs-title function_">approve</span>();<br> <span class="hljs-title class_">Toast</span>.<span class="hljs-title function_">success</span>(count === <span class="hljs-number">1</span> ? <span class="hljs-string">&#x27;1 record deleted.&#x27;</span> : count + <span class="hljs-string">&#x27; records deleted.&#x27;</span>, {<br> <span class="hljs-attr">timeout</span>: <span class="hljs-number">3000</span><br> });<br> } <span class="hljs-keyword">catch</span> {<br> <span class="hljs-title function_">reject</span>();<br> <span class="hljs-title class_">Toast</span>.<span class="hljs-title function_">error</span>(<span class="hljs-string">&#x27;Delete failed. Please try again.&#x27;</span>, {<br> <span class="hljs-attr">timeout</span>: <span class="hljs-number">5000</span><br> });<br> }<br> });<br></span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></code></pre>
79
+ <k-table
80
+ id="serverSyncExample"
81
+ enable-selection
82
+ request-edit
83
+ request-delete
84
+ >
85
+ <k-tc-edit slot="after"></k-tc-edit>
86
+ <k-tc-delete-record slot="after"></k-tc-delete-record>
87
+ <k-tc-delete-selected slot="top"></k-tc-delete-selected>
88
+ </k-table>
89
+ <script type="module">
90
+ import Toast from '../src/components/Toast.js';
91
+ await window.customElements.whenDefined('k-table');
92
+ const table = document.getElementById('serverSyncExample');
93
+
94
+ const mockServer = () => new Promise((resolve, reject) => {
95
+ setTimeout(() => {
96
+ Math.random() < 0.8 ? resolve() : reject();
97
+ }, 1000 + Math.random() * 2000);
98
+ });
99
+
100
+ table.setData({
101
+ records: contacts,
102
+ fields: [
103
+ { name: 'name', label: 'Name' },
104
+ { name: 'email', label: 'Email' },
105
+ { name: 'birthday', label: 'Birthday', type: 'date' },
106
+ { name: 'phoneNumber', label: 'Phone Number' },
107
+ { name: 'gender', label: 'Gender', formatter: v => v === 'm' ? 'Male' : 'Female' }
108
+ ]
109
+ });
110
+
111
+ table.addEventListener('requestSave', async ({ detail: { approve, reject } }) => {
112
+ try {
113
+ await mockServer();
114
+ approve();
115
+ Toast.success('Record saved.', { timeout: 3000 });
116
+ } catch {
117
+ reject();
118
+ Toast.error('Save failed. The record remains open for editing.', { timeout: 5000 });
119
+ }
120
+ });
121
+
122
+ table.addEventListener('requestDelete', async ({ detail: { records, approve, reject } }) => {
123
+ const count = records.length;
124
+ try {
125
+ await mockServer();
126
+ approve();
127
+ Toast.success(count === 1 ? '1 record deleted.' : count + ' records deleted.', { timeout: 3000 });
128
+ } catch {
129
+ reject();
130
+ Toast.error('Delete failed. Please try again.', { timeout: 5000 });
131
+ }
132
+ });
133
+ </script>
134
+ </main>
135
+ <div style="height: 33.333vh"></div>
136
+ <script type="module" src="../src/components/Import.js"></script>
137
+ <script type="module" src="../src/components/Table.js"></script>
138
+ <script type="module" src="../src/components/tableControls/Edit.js"></script>
139
+ <script type="module" src="../src/components/tableControls/DeleteRecord.js"></script>
140
+ <script type="module" src="../src/components/tableControls/DeleteSelected.js"></script>
141
+ <script type="module" src="../src/components/Toast.js"></script>
142
+ </body>
143
+ </html>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M600-240v-80h160v80H600Zm0-320v-80h280v80H600Zm0 160v-80h240v80H600ZM120-640H80v-80h160v-60h160v60h160v80h-40v360q0 33-23.5 56.5T440-200H200q-33 0-56.5-23.5T120-280v-360Zm80 0v360h240v-360H200Zm0 0v360-360Z"/></svg>
@@ -1,65 +1,64 @@
1
- import{html,css,unsafeStatic,literal}from"../lit-all.min.js";import ShadowComponent from"./ShadowComponent.js";import{toTitleCase}from"../utils/string.js";import{boolExists}from"../utils/propConverters.js";const selected=Symbol("selected"),hidden=Symbol("hidden"),index=Symbol("index"),editing=Symbol("editing");export default class Table extends ShadowComponent{static properties={enablePages:{type:Boolean,reflect:!0,converter:boolExists,attribute:"enable-pages"},pageSize:{type:Number,reflect:!0,attribute:"page-size"},currentPage:{type:Number,reflect:!0,attribute:"current-page"},pageSizeOptions:{type:Array,attribute:"page-size-options"},enableSelection:{type:Boolean,reflect:!0,converter:boolExists,attribute:"enable-selection"},enableSorting:{type:Boolean,reflect:!0,converter:boolExists,attribute:"enable-sorting"},caseSensitiveFilters:{type:Boolean,reflect:!0,converter:boolExists,attribute:"case-sensitive-filters"},fields:{type:Array},records:{type:Array},filters:{type:Array},sort:{type:Array},columnSizes:{type:Object},fetchPending:{type:Boolean}};constructor(e={}){super(),void 0===this.pageSize&&(this.pageSize=50),void 0===this.currentPage&&(this.currentPage=1),void 0===this.pageSizeOptions&&(this.pageSizeOptions=[10,25,50,100,500]),void 0===this.records&&(this.records=e.records||[]),void 0===this.fields&&(this.fields=e.fields||[]),void 0===this.filters&&(this.filters=e.filters||[]),void 0===this.sort&&(this.sort=[]),void 0===this.columnSizes&&(this.columnSizes={}),void 0===this.fetchPending&&(this.fetchPending=!1)}handleSelectAllChange=e=>{e.target.checked?this.selectAllOnPage():this.deselectAllOnPage()};handleFieldClick=e=>{const t=this.sort.find(t=>t.name===e),s=!t||!t.asc;this.sortBy(e,s)};handleRecordSelectionChange=(e,t)=>{e[selected]=!!t.target.checked,this.requestUpdate(),this.dispatchEvent(new CustomEvent("selectionChange",{bubbles:!0}))};firstUpdated(){this.setData({records:this.records,fields:this.fields,filters:this.filters})}childrenUpdated(){this.requestUpdate()}updated(e){if(super.updated(e),this.enableSelection){const e=this.shadowRoot.getElementById("select-all");e&&(e.checked=this.allOnPageSelected())}this.updateContainerWidths()}updateContainerWidths(){const e=this.columnSizes.total+"px",t=this.shadowRoot.getElementById("fields"),s=this.shadowRoot.getElementById("top"),i=this.shadowRoot.getElementById("bottom"),r=this.shadowRoot.getElementById("records");t&&(t.style.width=e),s&&(s.style.width=e),i&&(i.style.width=e),r&&(r.style.width=e)}renderFieldsTemplate(){this.calculateColumnSizes(),this.hasTopControls()?this.setAttribute("top-controls","true"):this.removeAttribute("top-controls"),this.hasBottomControls()?this.setAttribute("bottom-controls","true"):this.removeAttribute("bottom-controls");const e=[];return this.enableSelection&&e.push(html`
2
- <div class="field controls cell field-select" style="width: 40px">
3
- <input
4
- type="checkbox"
5
- id="select-all"
6
- @change=${this.handleSelectAllChange}
7
- />
8
- </div>
9
- `),this.hasBeforeControls()&&e.push(html`
10
- <div class="field cell field-before-controls" style="width: ${this.columnSizes.beforeControls}px"></div>
11
- `),this.fields.forEach(({name:t,label:s,hidden:i})=>{if(i)return;const r=this.sort.find(e=>e.name===t),o=this.sort.length>0&&this.sort[this.sort.length-1].name===t,l=r?r.asc?"sort-asc":"sort-desc":"";e.push(html`
12
- <div
13
- class="field cell ${l}"
14
- style="width: ${this.columnSizes[t]}px; ${this.enableSorting?"cursor: pointer;":""}"
1
+ import{html,css,unsafeStatic,literal}from"../lit-all.min.js";import ShadowComponent from"./ShadowComponent.js";import{toTitleCase}from"../utils/string.js";import{boolExists}from"../utils/propConverters.js";const selected=Symbol("selected"),hidden=Symbol("hidden"),index=Symbol("index"),editing=Symbol("editing");export default class Table extends ShadowComponent{static properties={enablePages:{type:Boolean,reflect:!0,converter:boolExists,attribute:"enable-pages"},pageSize:{type:Number,reflect:!0,attribute:"page-size"},currentPage:{type:Number,reflect:!0,attribute:"current-page"},pageSizeOptions:{type:Array,attribute:"page-size-options"},enableSelection:{type:Boolean,reflect:!0,converter:boolExists,attribute:"enable-selection"},enableSorting:{type:Boolean,reflect:!0,converter:boolExists,attribute:"enable-sorting"},caseSensitiveFilters:{type:Boolean,reflect:!0,converter:boolExists,attribute:"case-sensitive-filters"},requestEdit:{type:Boolean,reflect:!0,converter:boolExists,attribute:"request-edit"},requestDelete:{type:Boolean,reflect:!0,converter:boolExists,attribute:"request-delete"},fields:{type:Array},records:{type:Array},filters:{type:Array},sort:{type:Array},columnSizes:{type:Object},fetchPending:{type:Boolean}};constructor(e={}){super(),void 0===this.pageSize&&(this.pageSize=50),void 0===this.currentPage&&(this.currentPage=1),void 0===this.pageSizeOptions&&(this.pageSizeOptions=[10,25,50,100,500]),void 0===this.records&&(this.records=e.records||[]),void 0===this.fields&&(this.fields=e.fields||[]),void 0===this.filters&&(this.filters=e.filters||[]),void 0===this.sort&&(this.sort=[]),void 0===this.columnSizes&&(this.columnSizes={}),void 0===this.fetchPending&&(this.fetchPending=!1),void 0===this.requestEdit&&(this.requestEdit=!1),void 0===this.requestDelete&&(this.requestDelete=!1)}handleSelectAllChange=e=>{e.target.checked?this.selectAllOnPage():this.deselectAllOnPage()};handleFieldClick=e=>{const t=this.sort.find(t=>t.name===e),s=!t||!t.asc;this.sortBy(e,s)};handleRecordSelectionChange=(e,t)=>{e[selected]=!!t.target.checked,this.requestUpdate(),this.dispatchEvent(new CustomEvent("selectionChange",{bubbles:!0}))};firstUpdated(){this.setData({records:this.records,fields:this.fields,filters:this.filters})}childrenUpdated(){this.requestUpdate()}updated(e){if(super.updated(e),this.enableSelection){const e=this.shadowRoot.getElementById("select-all");e&&(e.checked=this.allOnPageSelected())}}renderColgroupTemplate(){const e=[];return this.enableSelection&&e.push(html`<col style="width: 40px" />`),this.hasBeforeControls()&&e.push(html`<col style="width: ${this.columnSizes.beforeControls}px" />`),this.fields.forEach(({size:t,hidden:s})=>{s||e.push(t?html`<col style="width: ${t}px" />`:html`<col />`)}),this.hasAfterControls()&&e.push(html`<col style="width: ${this.columnSizes.afterControls}px" />`),e}getColumnCount(){let e=0;return this.enableSelection&&e++,this.hasBeforeControls()&&e++,this.fields.forEach(({hidden:t})=>{t||e++}),this.hasAfterControls()&&e++,e}renderFieldsTemplate(){const e=[];return this.enableSelection&&e.push(html`
2
+ <th class="controls field-select">
3
+ <input type="checkbox" id="select-all" @change=${this.handleSelectAllChange} />
4
+ </th>
5
+ `),this.hasBeforeControls()&&e.push(html`<th class="controls field-before-controls"></th>`),this.fields.forEach(({name:t,label:s,hidden:i})=>{if(i)return;const r=this.sort.find(e=>e.name===t),o=this.sort.length>0&&this.sort[this.sort.length-1].name===t,n=r?r.asc?"sort-asc":"sort-desc":"";e.push(html`
6
+ <th
7
+ class="${n}"
8
+ style="${this.enableSorting?"cursor: pointer;":""}"
15
9
  @click=${this.enableSorting?()=>this.handleFieldClick(t):null}
16
10
  >
17
11
  ${s}
18
12
  ${o?html`<k-icon name="arrow" direction="${r.asc?"down":"up"}" class="icon-sort"></k-icon>`:""}
19
- </div>
20
- `)}),this.hasAfterControls()&&e.push(html`
21
- <div class="field cell field-after-controls" style="width: ${this.columnSizes.afterControls}px"></div>
22
- `),e}renderRecordsTemplate(){let e=this.getDisplayedRecords(),t=0,s=this.pageSize;this.enablePages&&(t=(this.currentPage-1)*this.pageSize,s=t+this.pageSize,e=e.slice(t,s));let i=null,r=0;const o=e.map((e,s)=>null!==e?this.renderRecordTemplate(e):(null===i&&(i=t+s),r++,html`<div class="record fetching"><div class="cell">Loading...</div></div>`));return null===i||this.fetchPending||setTimeout(()=>{this.fetchPending||this.dispatchEvent(new CustomEvent("fetchRecords",{detail:{start:i,count:r},bubbles:!0}))},0),o}renderRecordTemplate(e){const t=[];return this.enableSelection&&t.push(html`
23
- <div class="cell selection controls" style="width: 40px">
24
- <input
25
- type="checkbox"
13
+ </th>
14
+ `)}),this.hasAfterControls()&&e.push(html`<th class="controls field-after-controls"></th>`),e}renderRecordsTemplate(){let e=this.getDisplayedRecords(),t=0,s=this.pageSize;this.enablePages&&(t=(this.currentPage-1)*this.pageSize,s=t+this.pageSize,e=e.slice(t,s));let i=null,r=0;const o=e.map((e,s)=>null!==e?this.renderRecordTemplate(e):(null===i&&(i=t+s),r++,html`<tr class="record fetching"><td class="cell" colspan="${this.getColumnCount()}">Loading...</td></tr>`));return null===i||this.fetchPending||setTimeout(()=>{this.fetchPending||this.dispatchEvent(new CustomEvent("fetchRecords",{detail:{start:i,count:r},bubbles:!0}))},0),o}renderRecordTemplate(e){const t=[];return this.enableSelection&&t.push(html`
15
+ <td class="cell selection controls">
16
+ <input
17
+ type="checkbox"
26
18
  .checked=${e[selected]}
27
19
  @change=${t=>this.handleRecordSelectionChange(e,t)}
28
20
  />
29
- </div>
30
- `),this.hasBeforeControls()&&t.push(this.renderBeforeControlsTemplate()),this.fields.forEach(({name:s,formatter:i,calculator:r,type:o,editor:l,hidden:n})=>{if(n)return;let a=e[s]||"";t.push(html`
31
- <div class="cell" data-field=${s} style="width: ${this.columnSizes[s]}px">
32
- ${e[editing]?this.renderEditingCell(e,s,a,r,l,o):this.renderDisplayCell(e,s,a,r,i)}
33
- </div>
21
+ </td>
22
+ `),this.hasBeforeControls()&&t.push(this.renderBeforeControlsTemplate()),this.fields.forEach(({name:s,formatter:i,calculator:r,type:o,editor:n,hidden:l})=>{if(l)return;let a=e[s]||"";t.push(html`
23
+ <td class="cell" data-field=${s}>
24
+ ${e[editing]?this.renderEditingCell(e,s,a,r,n,o):this.renderDisplayCell(e,s,a,r,i)}
25
+ </td>
34
26
  `)}),this.hasAfterControls()&&t.push(this.renderAfterControlsTemplate()),html`
35
- <div class="record ${e[editing]?"editing":""}" data-index=${e[index]}>
27
+ <tr class="record ${e[editing]?"editing":""}" data-index=${e[index]}>
36
28
  ${t}
37
- </div>
29
+ </tr>
38
30
  `}renderEditingCell(e,t,s,i,r,o){if(i)return html`<input disabled .value=${i(e,this)} />`;if(r){const e=r(s);return html`${e}`}switch(o||typeof s){case"number":return html`<input type="number" .value=${s} />`;case"date":return html`<input type="date" .value=${s} />`;case"boolean":return html`
39
31
  <select .value=${s}>
40
32
  <option value="true" ?selected=${s}>True</option>
41
33
  <option value="false" ?selected=${!s}>False</option>
42
34
  </select>
43
35
  `;default:return html`<input type="text" .value=${s} />`}}renderDisplayCell(e,t,s,i,r){return i?i(e,this):r?r(s):s}renderBeforeControlsTemplate(){const e=[];return this.querySelectorAll('[slot="before"]').forEach(t=>{const s=t.tagName.toLowerCase(),i=document.createElement(s);Array.from(t.attributes).forEach(e=>{"slot"!==e.name&&i.setAttribute(e.name,e.value)}),t.innerHTML&&(i.innerHTML=t.innerHTML),e.push(i)}),html`
44
- <div class="cell controls controls-before" style="width: ${this.columnSizes.beforeControls}px">
36
+ <td class="cell controls controls-before">
45
37
  ${e}
46
- </div>
38
+ </td>
47
39
  `}renderAfterControlsTemplate(){const e=[];return this.querySelectorAll('[slot="after"]').forEach(t=>{const s=t.tagName.toLowerCase(),i=document.createElement(s);Array.from(t.attributes).forEach(e=>{"slot"!==e.name&&i.setAttribute(e.name,e.value)}),t.innerHTML&&(i.innerHTML=t.innerHTML),e.push(i)}),html`
48
- <div class="cell controls controls-after" style="width: ${this.columnSizes.afterControls}px">
40
+ <td class="cell controls controls-after">
49
41
  ${e}
50
- </div>
51
- `}hasBeforeControls(){return!!this.querySelector('[slot="before"]')}hasAfterControls(){return!!this.querySelector('[slot="after"]')}hasTopControls(){return!!this.querySelector('[slot="top"]')}hasBottomControls(){return!!this.querySelector(":scope > :not([slot])")}editRecord(e){e[editing]=!0;const t=this.shadowRoot.querySelector(`.record[data-index="${e[index]}"]`);t&&(t.classList.add("editing"),t.setAttribute("editing","true"),t.querySelectorAll(".cell[data-field]").forEach(t=>{const s=t.dataset.field,i=this.fields.find(e=>e.name===s);if(i){const r=e[s]||"";if(t.innerHTML="",i.calculator){const s=document.createElement("input");s.disabled=!0,s.value=i.calculator(e,this),t.appendChild(s)}else if(i.editor)t.appendChild(i.editor(r));else{const e=i.type||typeof r,s=Table.editors[e]||Table.editors.string;t.appendChild(s(r))}}})),this.dispatchEvent(new CustomEvent("editingChange",{detail:{record:e,editing:!0},bubbles:!0}))}saveEditedRecord(e){const t=this.shadowRoot.querySelector(`.record[data-index="${e[index]}"]`);t&&(t.querySelectorAll(".cell[data-field]").forEach(t=>{const s=t.dataset.field,i=this.fields.find(e=>e.name===s);if(i&&!i.calculator){const i=t.querySelector("input, select");i&&(e[s]=i.value)}}),e[editing]=!1,t.classList.remove("editing"),t.removeAttribute("editing"),t.querySelectorAll(".cell[data-field]").forEach(t=>{const s=t.dataset.field,i=this.fields.find(e=>e.name===s);if(i){const r=e[s]||"";i.calculator?t.textContent=i.calculator(e,this):i.formatter?t.innerHTML=i.formatter(r):t.textContent=r}})),this.dispatchEvent(new CustomEvent("editingChange",{detail:{record:e,editing:!1},bubbles:!0}))}cancelEditedRecord(e){e[editing]=!1;const t=this.shadowRoot.querySelector(`.record[data-index="${e[index]}"]`);t&&(t.classList.remove("editing"),t.removeAttribute("editing"),t.querySelectorAll(".cell[data-field]").forEach(t=>{const s=t.dataset.field,i=this.fields.find(e=>e.name===s);if(i){const r=e[s]||"";i.calculator?t.textContent=i.calculator(e,this):i.formatter?t.innerHTML=i.formatter(r):t.textContent=r}})),this.dispatchEvent(new CustomEvent("editingChange",{detail:{record:e,editing:!1},bubbles:!0}))}recordIsEditing(e){return e[editing]}getCurrentPage(){return this.currentPage}getTotalPages(){return Math.ceil(this.getDisplayedRecords().length/this.pageSize)}setPage(e){e<1||e>this.getTotalPages()||(this.currentPage=e,this.requestUpdate(),this.dispatchEvent(new CustomEvent("pageChange",{bubbles:!0})))}firstPage(){1!==this.currentPage&&this.setPage(1)}nextPage(){this.currentPage<this.getTotalPages()&&this.setPage(this.currentPage+1)}prevPage(){this.currentPage>1&&this.setPage(this.currentPage-1)}lastPage(){this.currentPage!==this.getTotalPages()&&this.setPage(this.getTotalPages())}setPageSize(e){this.pageSize=e,this.currentPage=1,this.requestUpdate(),this.dispatchEvent(new CustomEvent("pageSizeChange",{bubbles:!0}))}getPageSize(){return this.pageSize}getPageSizeOptions(){return this.pageSizeOptions}getFieldLabel(e){const t=this.fields.find(t=>t.name===e);return t?t.label:toTitleCase(e)}setPageSizeOptions(e){this.pageSizeOptions=e,this.requestUpdate()}setData({records:e=!1,fields:t=!1,pageSize:s=!1,pageSizeOptions:i=!1,currentPage:r=!1,enableSelection:o}={}){let l=!1,n=this.getTotalPages(),a=this.currentPage;e&&(this.records=e.map(e=>({...e})),this.records.forEach((e,t)=>{e[index]=t,e[selected]=!1,e[hidden]=!1,e[editing]=!1}),this.fields=t||Table.extractFieldsFromRecords(this.records),l=!0),s&&(this.pageSize=s,l=!0),i&&(this.pageSizeOptions=i),r&&(this.currentPage=r,l=!0),void 0!==o&&(this.enableSelection=o,l=!0),l&&this.requestUpdate();const d=this.getTotalPages();d!==n&&this.dispatchEvent(new CustomEvent("pageCountChanged",{detail:{totalPages:this.getTotalPages()},bubbles:!0})),a>d&&this.setPage(d)}setRecords(e,t){let s=this.getTotalPages(),i=this.currentPage;this.records=e.map(e=>({...e})),this.records.forEach((e,t)=>{e[index]=t,e[selected]=!1,e[hidden]=!1,e[editing]=!1}),this.fields=t||Table.extractFieldsFromRecords(this.records),this.requestUpdate(),this.dispatchEvent(new CustomEvent("recordsSet",{detail:{records:e},bubbles:!0}));const r=this.getTotalPages();r!==s&&this.dispatchEvent(new CustomEvent("pageCountChanged",{detail:{totalPages:this.getTotalPages()},bubbles:!0})),i>r&&this.setPage(r)}setupFetchRecords(e,t){const s=this.records.length,i=this.getTotalPages();if(s<e){this.records.length=e,this.records.fill(null,s),this.requestUpdate();const t=this.getTotalPages();t!==i&&setTimeout(()=>{this.dispatchEvent(new CustomEvent("pageCountChanged",{detail:{totalPages:t},bubbles:!0}))},0)}this.addEventListener("fetchRecords",async e=>{if(this.fetchPending)return;this.fetchPending=!0;const{start:s,count:i}=e.detail,r=await t(s,i);r.forEach((e,t)=>{e[index]=s+t,void 0===e[selected]&&(e[selected]=!1),void 0===e[hidden]&&(e[hidden]=!1),void 0===e[editing]&&(e[editing]=!1)}),this.records.splice(s,r.length,...r),this.fetchPending=!1,this.requestUpdate()})}addRecord(e){e[selected]=!1,e[hidden]=!1,e[index]=this.records.length,this.records.push(e),this.requestUpdate(),this.dispatchEvent(new CustomEvent("recordAdded",{detail:{record:e},bubbles:!0}))}updateRecord(e,t){let s=!1,i=this.records.find(t=>t===e);if(i||void 0===e[index]||(i=this.records[e[index]]),Object.keys(t).forEach(e=>{i.hasOwnProperty(e)&&(i[e]=t[e],s=!0)}),s){const e=(this.currentPage-1)*this.pageSize,t=e+this.pageSize;(!this.enablePages||i[index]>=e&&i[index]<t)&&this.requestUpdate()}}deleteRecord(e){let t=this.records.find(t=>t===e),s=this.getTotalPages();if(t||void 0===e[index]||(t=this.records[e[index]]),t){const e=this.records.indexOf(t);this.records.splice(e,1),this.records.forEach((e,t)=>{e[index]=t}),this.requestUpdate(),this.dispatchEvent(new CustomEvent("selectionChange",{bubbles:!0})),this.dispatchEvent(new CustomEvent("recordDeleted",{detail:{index:e},bubbles:!0}));const i=this.getTotalPages();this.currentPage>i&&this.setPage(i),i!==s&&this.dispatchEvent(new CustomEvent("pageCountChanged",{detail:{totalPages:i},bubbles:!0}))}}deleteSelected(){let e=this.getTotalPages();this.getSelectedRecords().forEach(e=>{let t=this.records.find(t=>t===e);if(t||void 0===e[index]||(t=this.records[e[index]]),t){const e=this.records.indexOf(t);this.records.splice(e,1)}}),this.records.forEach((e,t)=>{e[index]=t}),this.requestUpdate();const t=this.getTotalPages();this.currentPage>t&&this.setPage(t),t!==e&&this.dispatchEvent(new CustomEvent("pageCountChanged",{detail:{totalPages:t},bubbles:!0})),this.dispatchEvent(new CustomEvent("selectionChange",{bubbles:!0}))}getSelectedRecords(){return this.records.filter(e=>e[selected])}selectAllOnPage(){const e=(this.currentPage-1)*this.pageSize,t=Math.min(e+this.pageSize,this.records.length);for(let s=e;s<t;s++)this.records[s][selected]=!0;this.requestUpdate(),setTimeout(()=>{this.dispatchEvent(new CustomEvent("selectionChange",{bubbles:!0}))},0)}deselectAllOnPage(){const e=(this.currentPage-1)*this.pageSize,t=Math.min(e+this.pageSize,this.records.length);for(let s=e;s<t;s++)this.records[s][selected]=!1;this.requestUpdate(),setTimeout(()=>{this.dispatchEvent(new CustomEvent("selectionChange",{bubbles:!0}))},0)}allOnPageSelected(){const e=(this.currentPage-1)*this.pageSize,t=Math.min(e+this.pageSize,this.records.length);for(let s=e;s<t;s++)if(!this.records[s][selected])return!1;return!0}sortBy(e,t=!0){this.sort=this.sort.filter(t=>t.name!==e),this.sort.push({name:e,asc:t}),this.requestUpdate()}hideRecord(e){let t=this.records.find(t=>t===e);t||void 0===e[index]||(t=this.records[e[index]]),t&&(t[hidden]=!0,this.requestUpdate(),this.dispatchEvent(new CustomEvent("recordHidden",{bubbles:!0})))}showRecord(e){let t=this.records.find(t=>t===e);t||void 0===e[index]||(t=this.records[e[index]]),t&&(t[hidden]=!1,this.requestUpdate(),this.dispatchEvent(new CustomEvent("recordShown",{bubbles:!0})))}showAllRecords(){this.records.forEach(e=>{e[hidden]=!1}),this.filters.length&&(this.filters=[],this.dispatchEvent(new CustomEvent("filterRemoved",{bubbles:!0})),this.dispatchEvent(new CustomEvent("filterChange",{bubbles:!0}))),this.requestUpdate(),this.dispatchEvent(new CustomEvent("recordShown",{bubbles:!0})),this.dispatchEvent(new CustomEvent("allRecordsShown",{bubbles:!0}))}addFilter(e,t,s){this.filters.push({field:e,condition:t,value:s}),this.dispatchEvent(new CustomEvent("filterAdded",{bubbles:!0})),this.dispatchEvent(new CustomEvent("filterChange",{bubbles:!0})),this.requestUpdate()}removeFilter(e,t,s,i=!0){const r=this.filters.findIndex(i=>i.field===e&&i.condition===t&&i.value===s);-1!==r&&(this.records.forEach(i=>{this.testFilter(i,e,t,s)||(i[hidden]=!1)}),this.filters.splice(r,1),this.dispatchEvent(new CustomEvent("filterRemoved",{bubbles:!0})),this.dispatchEvent(new CustomEvent("filterChange",{bubbles:!0})),i&&this.requestUpdate())}testFilter(e,t,s,i){let r=e[t],o=i;switch(this.caseSensitiveFilters||"string"!=typeof r||"string"!=typeof i||(r=r.toLowerCase(),o=i.toLowerCase()),s){case"equals":return r===o;case"not-equals":return r!==o;case"contains":return r.includes(o);case"not-contains":return!r.includes(o);case"greater-than":return r>o;case"less-than":return r<o;case"greater-than-or-equal":return r>=o;case"less-than-or-equal":return r<=o;default:return!0}}removeAllFilters(){this.filters.length&&([...this.filters].forEach(({field:e,condition:t,value:s})=>{this.removeFilter(e,t,s,!1)}),this.requestUpdate())}search(e){const t=e.trim().toLowerCase();let s=!1;this.records.forEach(e=>{if(e[hidden])return;let i=!1;this.fields.forEach(({name:s})=>{(e[s]?.toString().toLowerCase()||"").includes(t)&&(i=!0)}),e[hidden]!==!i&&(e[hidden]=!i,s=!0)}),s&&(this.dispatchEvent(new CustomEvent("recordHidden",{bubbles:!0})),this.requestUpdate()),this.dispatchEvent(new CustomEvent("search",{detail:{term:e},bubbles:!0}))}getDisplayedRecords(){this.filters.forEach(({field:e,condition:t,value:s})=>{this.records.forEach(i=>{null!==i&&(this.testFilter(i,e,t,s)||(i[hidden]=!0))})});let e=this.records.filter(e=>null===e||!e[hidden]);return this.sort.forEach(({name:t,asc:s})=>{e.sort((e,i)=>null===e||null===i?0:e[t]<i[t]?s?-1:1:e[t]>i[t]?s?1:-1:0)}),e}getHiddenRecords(){return this.records.filter(e=>e[hidden])}calculateColumnSizes(){const e={total:0};this.enableSelection&&(e.total+=40);const t=Array.from(this.querySelectorAll('[slot="before"]')),s=Array.from(this.querySelectorAll('[slot="after"]'));e.beforeControls=t.reduce((e,t)=>e+(t.maxWidth||40),0),e.afterControls=s.reduce((e,t)=>e+(t.maxWidth||40),0),this.hasBeforeControls()&&(e.total+=e.beforeControls),this.hasAfterControls()&&(e.total+=e.afterControls),this.fields.forEach(t=>{if(t.size)e[t.name]=t.size,e.total+=t.size;else{let s=0;this.records.slice(0,100).forEach(e=>{if(null===e)return;let i=e[t.name];t.calculator&&(i=t.calculator(e,this)),t.formatter&&(i=t.formatter(i)),i&&i.toString().length>s&&(s=i.toString().length)}),e[t.name]=Math.max(10*s+32,128),t.hidden||(e.total+=e[t.name])}});const i=[...t,...s].some(e=>void 0===e.maxWidth);return JSON.stringify(this.columnSizes)!==JSON.stringify(e)&&(this.columnSizes=e),i&&setTimeout(()=>this.calculateColumnSizes(),0),this.columnSizes}setFieldHiddenState(e,t){const s=this.fields.find(t=>t.name===e);s&&(s.hidden=t,this.calculateColumnSizes(),this.requestUpdate(),this.dispatchEvent(new CustomEvent("fieldVisibilityChanged",{detail:{field:s},bubbles:!0})),this.dispatchEvent(new CustomEvent(t?"fieldHidden":"fieldShown",{detail:{field:s},bubbles:!0})))}hideField(e){this.setFieldHiddenState(e,!0)}showField(e){this.setFieldHiddenState(e,!1)}reorderFields(e){const t=[];e.forEach(e=>{const s=this.fields.find(t=>t.name===e);s&&t.push(s)}),this.fields=t,this.requestUpdate()}render(){return this.records&&this.fields?(this.calculateColumnSizes(),this.hasTopControls()?this.setAttribute("top-controls","true"):this.removeAttribute("top-controls"),this.hasBottomControls()?this.setAttribute("bottom-controls","true"):this.removeAttribute("bottom-controls"),html`
42
+ </td>
43
+ `}hasBeforeControls(){return!!this.querySelector('[slot="before"]')}hasAfterControls(){return!!this.querySelector('[slot="after"]')}hasTopControls(){return!!this.querySelector('[slot="top"]')}hasBottomControls(){return!!this.querySelector(":scope > :not([slot])")}editRecord(e){e[editing]=!0;const t=this.shadowRoot.querySelector(`.record[data-index="${e[index]}"]`);t&&(t.classList.add("editing"),t.setAttribute("editing","true"),t.querySelectorAll(".cell[data-field]").forEach(t=>{const s=t.dataset.field,i=this.fields.find(e=>e.name===s);if(i){const r=e[s]||"";if(t.innerHTML="",i.calculator){const s=document.createElement("input");s.disabled=!0,s.value=i.calculator(e,this),t.appendChild(s)}else if(i.editor)t.appendChild(i.editor(r));else{const e=i.type||typeof r,s=Table.editors[e]||Table.editors.string;t.appendChild(s(r))}}})),this.dispatchEvent(new CustomEvent("editingChange",{detail:{record:e,editing:!0},bubbles:!0}))}saveEditedRecord(e){const t=this.shadowRoot.querySelector(`.record[data-index="${e[index]}"]`),s={};t&&t.querySelectorAll(".cell[data-field]").forEach(e=>{const t=e.dataset.field,i=this.fields.find(e=>e.name===t);if(i&&!i.calculator){const i=e.querySelector("input, select");i&&(s[t]=i.value)}});const i=()=>{Object.assign(e,s),e[editing]=!1,t&&(t.classList.remove("editing"),t.removeAttribute("editing"),t.querySelectorAll(".cell[data-field]").forEach(t=>{const s=t.dataset.field,i=this.fields.find(e=>e.name===s);if(i){const r=e[s]||"";i.calculator?t.textContent=i.calculator(e,this):i.formatter?t.innerHTML=i.formatter(r):t.textContent=r}})),this.dispatchEvent(new CustomEvent("editingChange",{detail:{record:e,editing:!1},bubbles:!0}))};if(Object.keys(s).some(t=>String(e[t]??"")!==s[t]))if(this.requestEdit){t?.classList.add("pending");const r=()=>{t?.classList.remove("pending"),i()},o=()=>{t?.classList.remove("pending")};this.dispatchEvent(new CustomEvent("requestSave",{detail:{record:e,newData:s,approve:r,reject:o},bubbles:!0}))}else i();else this.cancelEditedRecord(e)}cancelEditedRecord(e){e[editing]=!1;const t=this.shadowRoot.querySelector(`.record[data-index="${e[index]}"]`);t&&(t.classList.remove("editing"),t.removeAttribute("editing"),t.querySelectorAll(".cell[data-field]").forEach(t=>{const s=t.dataset.field,i=this.fields.find(e=>e.name===s);if(i){const r=e[s]||"";i.calculator?t.textContent=i.calculator(e,this):i.formatter?t.innerHTML=i.formatter(r):t.textContent=r}})),this.dispatchEvent(new CustomEvent("editingChange",{detail:{record:e,editing:!1},bubbles:!0}))}recordIsEditing(e){return e[editing]}getCurrentPage(){return this.currentPage}getTotalPages(){return Math.ceil(this.getDisplayedRecords().length/this.pageSize)}setPage(e){e<1||e>this.getTotalPages()||(this.currentPage=e,this.requestUpdate(),this.dispatchEvent(new CustomEvent("pageChange",{bubbles:!0})))}firstPage(){1!==this.currentPage&&this.setPage(1)}nextPage(){this.currentPage<this.getTotalPages()&&this.setPage(this.currentPage+1)}prevPage(){this.currentPage>1&&this.setPage(this.currentPage-1)}lastPage(){this.currentPage!==this.getTotalPages()&&this.setPage(this.getTotalPages())}setPageSize(e){this.pageSize=e,this.currentPage=1,this.requestUpdate(),this.dispatchEvent(new CustomEvent("pageSizeChange",{bubbles:!0}))}getPageSize(){return this.pageSize}getPageSizeOptions(){return this.pageSizeOptions}getFieldLabel(e){const t=this.fields.find(t=>t.name===e);return t?t.label:toTitleCase(e)}setPageSizeOptions(e){this.pageSizeOptions=e,this.requestUpdate()}setData({records:e=!1,fields:t=!1,pageSize:s=!1,pageSizeOptions:i=!1,currentPage:r=!1,enableSelection:o}={}){let n=!1,l=this.getTotalPages(),a=this.currentPage;e&&(this.records=e.map(e=>({...e})),this.records.forEach((e,t)=>{e[index]=t,e[selected]=!1,e[hidden]=!1,e[editing]=!1}),this.fields=t||Table.extractFieldsFromRecords(this.records),n=!0),s&&(this.pageSize=s,n=!0),i&&(this.pageSizeOptions=i),r&&(this.currentPage=r,n=!0),void 0!==o&&(this.enableSelection=o,n=!0),n&&this.requestUpdate();const d=this.getTotalPages();d!==l&&this.dispatchEvent(new CustomEvent("pageCountChanged",{detail:{totalPages:this.getTotalPages()},bubbles:!0})),a>d&&this.setPage(d)}setRecords(e,t){let s=this.getTotalPages(),i=this.currentPage;this.records=e.map(e=>({...e})),this.records.forEach((e,t)=>{e[index]=t,e[selected]=!1,e[hidden]=!1,e[editing]=!1}),this.fields=t||Table.extractFieldsFromRecords(this.records),this.requestUpdate(),this.dispatchEvent(new CustomEvent("recordsSet",{detail:{records:e},bubbles:!0}));const r=this.getTotalPages();r!==s&&this.dispatchEvent(new CustomEvent("pageCountChanged",{detail:{totalPages:this.getTotalPages()},bubbles:!0})),i>r&&this.setPage(r)}setupFetchRecords(e,t){const s=this.records.length,i=this.getTotalPages();if(s<e){this.records.length=e,this.records.fill(null,s),this.requestUpdate();const t=this.getTotalPages();t!==i&&setTimeout(()=>{this.dispatchEvent(new CustomEvent("pageCountChanged",{detail:{totalPages:t},bubbles:!0}))},0)}this.addEventListener("fetchRecords",async e=>{if(this.fetchPending)return;this.fetchPending=!0;const{start:s,count:i}=e.detail,r=await t(s,i);r.forEach((e,t)=>{e[index]=s+t,void 0===e[selected]&&(e[selected]=!1),void 0===e[hidden]&&(e[hidden]=!1),void 0===e[editing]&&(e[editing]=!1)}),this.records.splice(s,r.length,...r),this.fetchPending=!1,this.requestUpdate()})}addRecord(e){e[selected]=!1,e[hidden]=!1,e[index]=this.records.length,this.records.push(e),this.requestUpdate(),this.dispatchEvent(new CustomEvent("recordAdded",{detail:{record:e},bubbles:!0}))}updateRecord(e,t){let s=!1,i=this.records.find(t=>t===e);if(i||void 0===e[index]||(i=this.records[e[index]]),Object.keys(t).forEach(e=>{i.hasOwnProperty(e)&&(i[e]=t[e],s=!0)}),s){const e=(this.currentPage-1)*this.pageSize,t=e+this.pageSize;(!this.enablePages||i[index]>=e&&i[index]<t)&&this.requestUpdate()}}deleteRecord(e){let t=this.records.find(t=>t===e);const s=this.getTotalPages();if(t||void 0===e[index]||(t=this.records[e[index]]),!t)return;const i=()=>{const e=this.records.indexOf(t);this.records.splice(e,1),this.records.forEach((e,t)=>{e[index]=t}),this.requestUpdate(),this.dispatchEvent(new CustomEvent("selectionChange",{bubbles:!0})),this.dispatchEvent(new CustomEvent("recordDeleted",{detail:{index:e},bubbles:!0}));const i=this.getTotalPages();this.currentPage>i&&this.setPage(i),i!==s&&this.dispatchEvent(new CustomEvent("pageCountChanged",{detail:{totalPages:i},bubbles:!0}))};if(this.requestDelete){const e=this.shadowRoot.querySelector(`.record[data-index="${t[index]}"]`);e?.classList.add("pending");const s=()=>{e?.classList.remove("pending"),i()},r=()=>{e?.classList.remove("pending")};this.dispatchEvent(new CustomEvent("requestDelete",{detail:{records:[t],approve:s,reject:r},bubbles:!0}))}else i()}deleteSelected(){const e=this.getTotalPages(),t=this.getSelectedRecords().map(e=>this.records.find(t=>t===e)??(void 0!==e[index]?this.records[e[index]]:null)).filter(Boolean);if(!t.length)return;const s=()=>{t.forEach(e=>{const t=this.records.indexOf(e);-1!==t&&this.records.splice(t,1)}),this.records.forEach((e,t)=>{e[index]=t}),this.requestUpdate();const s=this.getTotalPages();this.currentPage>s&&this.setPage(s),s!==e&&this.dispatchEvent(new CustomEvent("pageCountChanged",{detail:{totalPages:s},bubbles:!0})),this.dispatchEvent(new CustomEvent("selectionChange",{bubbles:!0}))};if(this.requestDelete){const e=t.map(e=>this.shadowRoot.querySelector(`.record[data-index="${e[index]}"]`)).filter(Boolean);e.forEach(e=>e.classList.add("pending"));const i=()=>{e.forEach(e=>e.classList.remove("pending")),s()},r=()=>{e.forEach(e=>e.classList.remove("pending"))};this.dispatchEvent(new CustomEvent("requestDelete",{detail:{records:t,approve:i,reject:r},bubbles:!0}))}else s()}getSelectedRecords(){return this.records.filter(e=>e[selected])}selectAllOnPage(){const e=(this.currentPage-1)*this.pageSize,t=Math.min(e+this.pageSize,this.records.length);for(let s=e;s<t;s++)this.records[s][selected]=!0;this.requestUpdate(),setTimeout(()=>{this.dispatchEvent(new CustomEvent("selectionChange",{bubbles:!0}))},0)}deselectAllOnPage(){const e=(this.currentPage-1)*this.pageSize,t=Math.min(e+this.pageSize,this.records.length);for(let s=e;s<t;s++)this.records[s][selected]=!1;this.requestUpdate(),setTimeout(()=>{this.dispatchEvent(new CustomEvent("selectionChange",{bubbles:!0}))},0)}allOnPageSelected(){const e=(this.currentPage-1)*this.pageSize,t=Math.min(e+this.pageSize,this.records.length);for(let s=e;s<t;s++)if(!this.records[s][selected])return!1;return!0}sortBy(e,t=!0){this.sort=this.sort.filter(t=>t.name!==e),this.sort.push({name:e,asc:t}),this.requestUpdate()}hideRecord(e){let t=this.records.find(t=>t===e);t||void 0===e[index]||(t=this.records[e[index]]),t&&(t[hidden]=!0,this.requestUpdate(),this.dispatchEvent(new CustomEvent("recordHidden",{bubbles:!0})))}showRecord(e){let t=this.records.find(t=>t===e);t||void 0===e[index]||(t=this.records[e[index]]),t&&(t[hidden]=!1,this.requestUpdate(),this.dispatchEvent(new CustomEvent("recordShown",{bubbles:!0})))}showAllRecords(){this.records.forEach(e=>{e[hidden]=!1}),this.filters.length&&(this.filters=[],this.dispatchEvent(new CustomEvent("filterRemoved",{bubbles:!0})),this.dispatchEvent(new CustomEvent("filterChange",{bubbles:!0}))),this.requestUpdate(),this.dispatchEvent(new CustomEvent("recordShown",{bubbles:!0})),this.dispatchEvent(new CustomEvent("allRecordsShown",{bubbles:!0}))}addFilter(e,t,s){this.filters.push({field:e,condition:t,value:s}),this.dispatchEvent(new CustomEvent("filterAdded",{bubbles:!0})),this.dispatchEvent(new CustomEvent("filterChange",{bubbles:!0})),this.requestUpdate()}removeFilter(e,t,s,i=!0){const r=this.filters.findIndex(i=>i.field===e&&i.condition===t&&i.value===s);-1!==r&&(this.records.forEach(i=>{this.testFilter(i,e,t,s)||(i[hidden]=!1)}),this.filters.splice(r,1),this.dispatchEvent(new CustomEvent("filterRemoved",{bubbles:!0})),this.dispatchEvent(new CustomEvent("filterChange",{bubbles:!0})),i&&this.requestUpdate())}testFilter(e,t,s,i){let r=e[t],o=i;switch(this.caseSensitiveFilters||"string"!=typeof r||"string"!=typeof i||(r=r.toLowerCase(),o=i.toLowerCase()),s){case"equals":return r===o;case"not-equals":return r!==o;case"contains":return r.includes(o);case"not-contains":return!r.includes(o);case"greater-than":return r>o;case"less-than":return r<o;case"greater-than-or-equal":return r>=o;case"less-than-or-equal":return r<=o;default:return!0}}removeAllFilters(){this.filters.length&&([...this.filters].forEach(({field:e,condition:t,value:s})=>{this.removeFilter(e,t,s,!1)}),this.requestUpdate())}search(e){const t=e.trim().toLowerCase();let s=!1;this.records.forEach(e=>{if(e[hidden])return;let i=!1;this.fields.forEach(({name:s})=>{(e[s]?.toString().toLowerCase()||"").includes(t)&&(i=!0)}),e[hidden]!==!i&&(e[hidden]=!i,s=!0)}),s&&(this.dispatchEvent(new CustomEvent("recordHidden",{bubbles:!0})),this.requestUpdate()),this.dispatchEvent(new CustomEvent("search",{detail:{term:e},bubbles:!0}))}getDisplayedRecords(){this.filters.forEach(({field:e,condition:t,value:s})=>{this.records.forEach(i=>{null!==i&&(this.testFilter(i,e,t,s)||(i[hidden]=!0))})});let e=this.records.filter(e=>null===e||!e[hidden]);return this.sort.forEach(({name:t,asc:s})=>{e.sort((e,i)=>null===e||null===i?0:e[t]<i[t]?s?-1:1:e[t]>i[t]?s?1:-1:0)}),e}getHiddenRecords(){return this.records.filter(e=>e[hidden])}calculateColumnSizes(){const e=Array.from(this.querySelectorAll('[slot="before"]')),t=Array.from(this.querySelectorAll('[slot="after"]')),s={beforeControls:e.reduce((e,t)=>e+(t.maxWidth||40),0),afterControls:t.reduce((e,t)=>e+(t.maxWidth||40),0)},i=[...e,...t].some(e=>void 0===e.maxWidth);return JSON.stringify(this.columnSizes)!==JSON.stringify(s)&&(this.columnSizes=s),i&&setTimeout(()=>this.calculateColumnSizes(),0),this.columnSizes}setFieldHiddenState(e,t){const s=this.fields.find(t=>t.name===e);s&&(s.hidden=t,this.calculateColumnSizes(),this.requestUpdate(),this.dispatchEvent(new CustomEvent("fieldVisibilityChanged",{detail:{field:s},bubbles:!0})),this.dispatchEvent(new CustomEvent(t?"fieldHidden":"fieldShown",{detail:{field:s},bubbles:!0})))}hideField(e){this.setFieldHiddenState(e,!0)}showField(e){this.setFieldHiddenState(e,!1)}reorderFields(e){const t=[];e.forEach(e=>{const s=this.fields.find(t=>t.name===e);s&&t.push(s)}),this.fields=t,this.requestUpdate()}render(){return this.records&&this.fields?(this.calculateColumnSizes(),this.hasTopControls()?this.setAttribute("top-controls","true"):this.removeAttribute("top-controls"),this.hasBottomControls()?this.setAttribute("bottom-controls","true"):this.removeAttribute("bottom-controls"),html`
52
44
  <div id="wrapper">
53
- <div id="top" style="width: ${this.columnSizes.total}px"><slot name="top"></slot></div>
54
- <div id="table">
55
- <div id="fields" style="width: ${this.columnSizes.total}px">
56
- ${this.renderFieldsTemplate()}
57
- </div>
58
- <div id="records" style="width: ${this.columnSizes.total}px">
59
- ${this.renderRecordsTemplate()}
60
- </div>
45
+ <div id="top"><slot name="top"></slot></div>
46
+ <div id="table-container">
47
+ <table>
48
+ <colgroup>
49
+ ${this.renderColgroupTemplate()}
50
+ </colgroup>
51
+ <thead>
52
+ <tr>
53
+ ${this.renderFieldsTemplate()}
54
+ </tr>
55
+ </thead>
56
+ <tbody>
57
+ ${this.renderRecordsTemplate()}
58
+ </tbody>
59
+ </table>
61
60
  </div>
62
- <div id="bottom" style="width: ${this.columnSizes.total}px"><slot></slot></div>
61
+ <div id="bottom"><slot></slot></div>
63
62
  </div>
64
63
  <div style="display: none">
65
64
  <slot name="before"></slot>
@@ -68,9 +67,8 @@ import{html,css,unsafeStatic,literal}from"../lit-all.min.js";import ShadowCompon
68
67
  `):html`
69
68
  <div id="wrapper">
70
69
  <div id="top"><slot name="top"></slot></div>
71
- <div id="table">
72
- <div id="fields"></div>
73
- <div id="records"></div>
70
+ <div id="table-container">
71
+ <table><thead><tr></tr></thead><tbody></tbody></table>
74
72
  </div>
75
73
  <div id="bottom"><slot></slot></div>
76
74
  </div>
@@ -81,39 +79,99 @@ import{html,css,unsafeStatic,literal}from"../lit-all.min.js";import ShadowCompon
81
79
  `}static styles=css`
82
80
  :host {
83
81
  display: block;
84
- width: 100%;
85
- overflow: auto;
86
82
  margin-bottom: var(--spacer);
87
83
  }
88
84
  #wrapper {
89
- width: min-content;
90
85
  border: 1px solid var(--c_border);
91
86
  border-radius: var(--radius);
87
+ overflow: hidden;
92
88
  }
93
- #table {
94
- width: min-content;
89
+ #table-container {
90
+ overflow-x: auto;
95
91
  }
96
- #fields,
97
- .record {
98
- display: flex;
92
+ table {
93
+ width: 100%;
94
+ border-collapse: collapse;
99
95
  }
100
- #fields {
96
+ thead tr {
101
97
  background-color: var(--c_bg__alt);
102
- border-bottom: 1px solid var(--c_border);
103
98
  }
104
- .record:not([editing="true"]) .cell:not(.controls),
105
- #fields .cell:not(.controls) {
99
+ th, td {
106
100
  padding: calc(0.5 * var(--spacer)) var(--spacer);
101
+ vertical-align: middle;
102
+ }
103
+ th:not(:last-child),
104
+ td:not(:last-child) {
105
+ border-right: 1px solid var(--c_border);
106
+ }
107
+ th:first-child,
108
+ td:first-child {
109
+ border-left: none;
110
+ }
111
+ th:last-child,
112
+ td:last-child {
113
+ border-right: none;
114
+ }
115
+ thead tr th {
116
+ border-top: none;
117
+ border-bottom: 1px solid var(--c_border);
107
118
  }
108
- .cell {
119
+ tbody tr:not(:last-child) td {
120
+ border-bottom: 1px solid var(--c_border);
121
+ }
122
+ tbody tr:last-child td {
123
+ border-bottom: none;
124
+ }
125
+ tr.editing td.cell[data-field] {
126
+ padding: 0;
127
+ }
128
+ tr.editing td.cell[data-field] input,
129
+ tr.editing td.cell[data-field] select {
130
+ width: 100%;
131
+ height: 100%;
132
+ box-sizing: border-box;
133
+ }
134
+ tr.pending {
135
+ pointer-events: none;
136
+ }
137
+ tr.pending td {
138
+ position: relative;
139
+ overflow: hidden;
140
+ }
141
+ tr.pending td::before {
142
+ content: '';
143
+ position: absolute;
144
+ inset: 0;
145
+ background: linear-gradient(90deg, transparent, rgba(128, 128, 128, 0.15), transparent);
146
+ transform: translateX(-100%);
147
+ animation: row-pending 1.2s ease-in-out infinite;
148
+ }
149
+ @keyframes row-pending {
150
+ 0% { transform: translateX(-100%); }
151
+ 100% { transform: translateX(100%); }
152
+ }
153
+ th.controls,
154
+ td.controls {
155
+ padding: 0;
156
+ }
157
+ td.controls-after,
158
+ td.controls-before {
109
159
  display: flex;
110
160
  align-items: center;
111
161
  }
112
- .cell:not(:first-child) {
113
- border-left: 1px solid var(--c_border);
162
+ .field-select,
163
+ .selection {
164
+ width: 40px;
165
+ text-align: center;
114
166
  }
115
- .record:not(:last-child) .cell {
116
- border-bottom: 1px solid var(--c_border);
167
+ .field-select input,
168
+ .selection input {
169
+ width: 1.25rem;
170
+ height: 1.25rem;
171
+ }
172
+ .icon-sort {
173
+ float: right;
174
+ opacity: 0.5;
117
175
  }
118
176
  #top, #bottom {
119
177
  display: flex;
@@ -133,19 +191,4 @@ import{html,css,unsafeStatic,literal}from"../lit-all.min.js";import ShadowCompon
133
191
  :host(:not([bottom-controls])) #bottom {
134
192
  display: none;
135
193
  }
136
- .field-select,
137
- .selection {
138
- display: flex;
139
- justify-content: center;
140
- align-items: center;
141
- }
142
- .field-select input,
143
- .selection input {
144
- width: 1.25rem;
145
- height: 1.25rem;
146
- }
147
- .icon-sort {
148
- float: right;
149
- opacity: 0.5;
150
- }
151
194
  `;static extractFieldsFromRecords(e,t=100){const s=new Set;return e.slice(0,t).forEach(e=>{Object.keys(e).forEach(e=>s.add(e))}),[...s].map(e=>({name:e,label:toTitleCase(e)}))}static format(e){return(Array.isArray(e)?Table.formatters.array:Table.formatters[typeof e])(e)}static formatters={string:e=>e,number:e=>`${e}`,date:e=>e.toLocaleDateString(),boolean:e=>e?"True":"False",array:e=>e.map(e=>Table.format(e)).join(", "),undefined:e=>"",null:e=>"<code>null</code>"};static editors={string:e=>{const t=document.createElement("input");return t.value=e,t},number:e=>{const t=document.createElement("input");return t.type="number",t.value=e,t},date:e=>{const t=document.createElement("input");return t.type="date",t.value=e,t},boolean:e=>{const t=document.createElement("select");return t.innerHTML=`\n <option value="true" ${e?"selected":""}>True</option>\n <option value="false" ${e?"":"selected"}>False</option>\n `,t.value=e,t},calculated:e=>{const t=document.createElement("input");return t.disabled=!0,t.value=e,t}}}window.customElements.define("k-table",Table);
@@ -0,0 +1,7 @@
1
+ import TableControl from"./TableControl.js";import{html}from"../../lit-all.min.js";import"../Icon.js";export default class DeleteSelected extends TableControl{deleteSelected=()=>{this.table.deleteSelected()};render(){return html`
2
+ <button class="no-btn icon-btn" title="Delete Selected" @click="${this.deleteSelected}">
3
+ <slot>
4
+ <k-icon name="delete_sweep"></k-icon>
5
+ </slot>
6
+ </button>
7
+ `}}customElements.define("k-tc-delete-selected",DeleteSelected);
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M600-240v-80h160v80H600Zm0-320v-80h280v80H600Zm0 160v-80h240v80H600ZM120-640H80v-80h160v-60h160v60h160v80h-40v360q0 33-23.5 56.5T440-200H200q-33 0-56.5-23.5T120-280v-360Zm80 0v360h240v-360H200Zm0 0v360-360Z"/></svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kempo-ui",
3
- "version": "0.0.77",
3
+ "version": "0.0.79",
4
4
  "type": "module",
5
5
  "description": "A Lit based web-component library",
6
6
  "main": "index.js",
@@ -36,6 +36,7 @@
36
36
  "homepage": "https://github.com/dustinpoissant/kempo-ui#readme",
37
37
  "devDependencies": {
38
38
  "highlight.js": "^11.11.1",
39
+ "js-beautify": "^1.15.4",
39
40
  "kempo-server": "^1.8.2",
40
41
  "kempo-testing-framework": "^1.4.0",
41
42
  "terser": "^5.43.1"