kempo-ui 0.0.78 → 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.
@@ -11,47 +11,34 @@ Use this skill whenever you need syntax-highlighted HTML to embed in a documenta
11
11
 
12
12
  ## Usage
13
13
 
14
- ```bash
15
- npm run highlightcode -- <lang> "<code>"
16
- ```
17
-
18
- Or via npx:
19
- ```bash
20
- npx kempo-highlightcode <lang> "<code>"
14
+ ```powershell
15
+ node bin/highlight_code.js <lang> <code>
21
16
  ```
22
17
 
23
18
  **Supported languages:** `html`, `css`, `js` / `javascript`, `ts` / `typescript`, `json`, `md` / `markdown`, `sh` / `bash`, `xml`
24
19
 
25
20
  ## Output
26
21
 
27
- The script returns the **inner highlighted content only** — the hljs span markup without any `<pre><code>` wrapper. Paste it directly inside an existing `<pre><code class="hljs <lang>">...</code></pre>` block.
28
-
29
- ## Example
30
-
31
- ```bash
32
- npm run highlightcode -- html "<p>Hello World</p>"
33
- ```
22
+ Prints the inner highlighted content (hljs spans, no `<pre><code>` wrapper) to stdout, with `<br>` tags instead of newlines a single line suitable for embedding in HTML.
34
23
 
35
- Returns:
36
- ```
37
- <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Hello World<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
38
- ```
24
+ Input is automatically beautified before highlighting, so you can pass minified code.
39
25
 
40
- ## Multi-line Code
26
+ ## Workflow for Updating Doc Code Samples
41
27
 
42
- For multi-line code, use a here-string in PowerShell:
28
+ Run the script with minified code as a single argument, capture to `$hl`, then use PowerShell's `.Replace()` to patch the HTML file:
43
29
 
44
30
  ```powershell
45
- $code = @"
46
- class Foo extends Bar {
47
- render(){ return html`<p>hello</p>`; }
48
- }
49
- "@
50
- node bin/highlight_code.js js $code
31
+ $hl = node bin/highlight_code.js xml "<k-table id=myExample></k-table><script type=module>doSomething('foo');</script>"
32
+ $html = [System.IO.File]::ReadAllText((Resolve-Path 'docs/components/my-component.html'))
33
+ $old = [regex]::Match($html, '(?s)<pre><code class="hljs xml">.*?</code></pre>').Value
34
+ [System.IO.File]::WriteAllText((Resolve-Path 'docs/components/my-component.html'), $html.Replace($old, "<pre><code class=`"hljs xml`">$hl</code></pre>"))
51
35
  ```
52
36
 
53
- ## Workflow for Updating Doc Code Samples
37
+ ## Important Notes
54
38
 
55
- 1. Run `npm run highlightcode -- <lang> "<new code>"` to get the highlighted HTML.
56
- 2. Replace the inner content of the matching `<pre><code class="hljs ...">` block in the doc file with the output.
57
- 3. Also update the live demo `<script type="module">` block with the raw (unhighlighted) code.
39
+ - Use **outer double-quotes** for the PowerShell argument string, and **single-quotes** for JS string literals inside. This preserves all quoting correctly through to node.
40
+ - HTML attribute values should be **unquoted** in the argument (`id=foo` not `id="foo"`) the beautifier adds consistent formatting anyway.
41
+ - Use `[System.IO.File]::ReadAllText` / `WriteAllText` + `.Replace()` (not `-replace`) to avoid regex interpretation of the replacement string.
42
+ - If a page has multiple `<pre><code>` blocks, adjust the regex to match the specific one (e.g. match by a nearby unique string, or loop with an index).
43
+ - The beautifier handles indentation and formatting automatically.
44
+ - Output uses `<br>` instead of `\n`, keeping the block on one line in the HTML source.
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { highlight } from '../tools/highlight.js';
3
+ import beautify from 'js-beautify';
3
4
 
4
5
  const LANG_ALIASES = {
5
6
  js: 'javascript',
@@ -16,11 +17,17 @@ const LANG_ALIASES = {
16
17
  markdown: 'markdown'
17
18
  };
18
19
 
20
+ const BEAUTIFY_OPTIONS = {
21
+ indent_size: 2,
22
+ wrap_line_length: 0,
23
+ preserve_newlines: false,
24
+ max_preserve_newlines: 0
25
+ };
26
+
19
27
  const args = process.argv.slice(2);
20
28
 
21
- if(args.length < 2){
22
- console.error('Usage: kempo-highlightcode <lang> <code>');
23
- console.error('Example: kempo-highlightcode html "<p>Hello World</p>"');
29
+ if(args.length < 1){
30
+ console.error('Usage: node bin/highlight_code.js <lang> <code>');
24
31
  process.exit(1);
25
32
  }
26
33
 
@@ -33,7 +40,27 @@ if(!lang){
33
40
  process.exit(1);
34
41
  }
35
42
 
36
- const code = rest.join(' ');
37
- const full = highlight(code, lang);
38
- // Strip the <pre><code> wrapper — return just the inner highlighted content
39
- process.stdout.write(full.replace(/^<pre><code class="hljs [^"]*">/, '').replace(/<\/code><\/pre>\n?$/, '') + '\n');
43
+ const beautifyCode = (code) => {
44
+ const fns = { javascript: beautify.js, css: beautify.css, html: beautify.html, xml: beautify.html };
45
+ const fn = fns[lang] || beautify.js;
46
+ return fn(code, BEAUTIFY_OPTIONS);
47
+ };
48
+
49
+ const run = (code) => {
50
+ const formatted = beautifyCode(code);
51
+ const full = highlight(formatted, lang);
52
+ process.stdout.write(
53
+ full
54
+ .replace(/^<pre><code class="hljs [^"]*">/, '')
55
+ .replace(/<\/code><\/pre>\n?$/, '')
56
+ .replace(/\n/g, '<br>') + '\n'
57
+ );
58
+ };
59
+
60
+ if(rest.length > 0){
61
+ run(rest.join(' '));
62
+ } else {
63
+ const chunks = [];
64
+ process.stdin.on('data', chunk => chunks.push(chunk));
65
+ process.stdin.on('end', () => run(chunks.join('')));
66
+ }
@@ -1,4 +1,4 @@
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())}}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`
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
2
  <th class="controls field-select">
3
3
  <input type="checkbox" id="select-all" @change=${this.handleSelectAllChange} />
4
4
  </th>
@@ -40,7 +40,7 @@ import{html,css,unsafeStatic,literal}from"../lit-all.min.js";import ShadowCompon
40
40
  <td class="cell controls controls-after">
41
41
  ${e}
42
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]}"]`);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 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),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=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`
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`
44
44
  <div id="wrapper">
45
45
  <div id="top"><slot name="top"></slot></div>
46
46
  <div id="table-container">
@@ -122,10 +122,43 @@ import{html,css,unsafeStatic,literal}from"../lit-all.min.js";import ShadowCompon
122
122
  tbody tr:last-child td {
123
123
  border-bottom: none;
124
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
+ }
125
153
  th.controls,
126
154
  td.controls {
127
155
  padding: 0;
128
156
  }
157
+ td.controls-after,
158
+ td.controls-before {
159
+ display: flex;
160
+ align-items: center;
161
+ }
129
162
  .field-select,
130
163
  .selection {
131
164
  width: 40px;
@@ -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);
@@ -34,6 +34,7 @@
34
34
  <a href="./tableSorting.html">Sorting</a><br />
35
35
  <a href="./tableFieldSortHide.html">Field Sorting and Hiding</a><br />
36
36
  <a href="./tableFetchRecords.html">Fetching Records</a><br />
37
+ <a href="./tableServerSync.html">Server Sync</a><br />
37
38
  <h6 class="mt"><a href="#jsRef" class="no-link">JavaScript Reference</a></h6>
38
39
  <a href="#constructor">Constructor</a><br />
39
40
  <a href="#requirements">Requirements</a><br />
@@ -50,7 +51,7 @@
50
51
  <p>In HTML create a <code>&lt;k-table></code>, and then in JavaScript call the <code>setData</code> method passing in an options object containing your data.</p>
51
52
 
52
53
  <k-card label="HTML">
53
- <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">"basicUsageExample"</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="javascript"><br /> <span class="hljs-keyword">await</span> <span class="hljs-built_in">window</span>.customElements.whenDefined(<span class="hljs-string">'k-table'</span>);<br /> <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'basicUsageExample'</span>).setData({<br /> <span class="hljs-attr">records</span>: [<br /> {<br /> <span class="hljs-attr">name</span>: <span class="hljs-string">"Dustin"</span>,<br /> <span class="hljs-attr">phoneNumber</span>: <span class="hljs-string">"(111) 111-1111"</span>,<br /> <span class="hljs-attr">emailAddress</span>: <span class="hljs-string">"dustin@mailserver.com"</span>,<br /> <span class="hljs-attr">gender</span>: <span class="hljs-string">"Male"</span><br /> },<br /> {<br /> <span class="hljs-attr">name</span>: <span class="hljs-string">"Kayla"</span>,<br /> <span class="hljs-attr">phoneNumber</span>: <span class="hljs-string">"(222) 222-2222"</span>,<br /> <span class="hljs-attr">emailAddress</span>: <span class="hljs-string">"kayla@mailserver.com"</span>,<br /> <span class="hljs-attr">gender</span>: <span class="hljs-string">"Female"</span><br /> },<br /> {<br /> <span class="hljs-attr">name</span>: <span class="hljs-string">"Alexander"</span>,<br /> <span class="hljs-attr">phoneNumber</span>: <span class="hljs-string">"(333) 333-33333"</span>,<br /> <span class="hljs-attr">emailAddress</span>: <span class="hljs-string">"alex@mailserver.com"</span>,<br /> <span class="hljs-attr">gender</span>: <span class="hljs-string">"Male"</span><br /> },<br /> {<br /> <span class="hljs-attr">name</span>: <span class="hljs-string">"Amelia"</span>,<br /> <span class="hljs-attr">phoneNumber</span>: <span class="hljs-string">"(444) 444-44444"</span>,<br /> <span class="hljs-attr">emailAddress</span>: <span class="hljs-string">"amelia@mailserver.com"</span>,<br /> <span class="hljs-attr">gender</span>: <span class="hljs-string">"Female"</span><br /> },<br /> ]<br /> });<br /></span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></code></pre>
54
+ <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">basicUsageExample</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">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-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;basicUsageExample&#x27;</span>).<span class="hljs-title function_">setData</span>({<br> <span class="hljs-attr">records</span>: [{<br> <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;Dustin&#x27;</span>,<br> <span class="hljs-attr">phoneNumber</span>: <span class="hljs-string">&#x27;(111) 111-1111&#x27;</span>,<br> <span class="hljs-attr">emailAddress</span>: <span class="hljs-string">&#x27;dustin@mailserver.com&#x27;</span>,<br> <span class="hljs-attr">gender</span>: <span class="hljs-string">&#x27;Male&#x27;</span><br> }, {<br> <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;Kayla&#x27;</span>,<br> <span class="hljs-attr">phoneNumber</span>: <span class="hljs-string">&#x27;(222) 222-2222&#x27;</span>,<br> <span class="hljs-attr">emailAddress</span>: <span class="hljs-string">&#x27;kayla@mailserver.com&#x27;</span>,<br> <span class="hljs-attr">gender</span>: <span class="hljs-string">&#x27;Female&#x27;</span><br> }, {<br> <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;Alexander&#x27;</span>,<br> <span class="hljs-attr">phoneNumber</span>: <span class="hljs-string">&#x27;(333) 333-33333&#x27;</span>,<br> <span class="hljs-attr">emailAddress</span>: <span class="hljs-string">&#x27;alex@mailserver.com&#x27;</span>,<br> <span class="hljs-attr">gender</span>: <span class="hljs-string">&#x27;Male&#x27;</span><br> }, {<br> <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;Amelia&#x27;</span>,<br> <span class="hljs-attr">phoneNumber</span>: <span class="hljs-string">&#x27;(444) 444-44444&#x27;</span>,<br> <span class="hljs-attr">emailAddress</span>: <span class="hljs-string">&#x27;amelia@mailserver.com&#x27;</span>,<br> <span class="hljs-attr">gender</span>: <span class="hljs-string">&#x27;Female&#x27;</span><br> }]<br> });<br></span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></code></pre>
54
55
  </k-card>
55
56
  <k-card label="Results">
56
57
  <k-table id="basicUsageExample"></k-table>
@@ -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,4 +1,4 @@
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())}}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`
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
2
  <th class="controls field-select">
3
3
  <input type="checkbox" id="select-all" @change=${this.handleSelectAllChange} />
4
4
  </th>
@@ -40,7 +40,7 @@ import{html,css,unsafeStatic,literal}from"../lit-all.min.js";import ShadowCompon
40
40
  <td class="cell controls controls-after">
41
41
  ${e}
42
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]}"]`);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 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),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=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`
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`
44
44
  <div id="wrapper">
45
45
  <div id="top"><slot name="top"></slot></div>
46
46
  <div id="table-container">
@@ -122,10 +122,43 @@ import{html,css,unsafeStatic,literal}from"../lit-all.min.js";import ShadowCompon
122
122
  tbody tr:last-child td {
123
123
  border-bottom: none;
124
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
+ }
125
153
  th.controls,
126
154
  td.controls {
127
155
  padding: 0;
128
156
  }
157
+ td.controls-after,
158
+ td.controls-before {
159
+ display: flex;
160
+ align-items: center;
161
+ }
129
162
  .field-select,
130
163
  .selection {
131
164
  width: 40px;
@@ -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.78",
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"
@@ -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
  /*
@@ -358,39 +362,59 @@ export default class Table extends ShadowComponent {
358
362
 
359
363
  saveEditedRecord(record) {
360
364
  const recordEl = this.shadowRoot.querySelector(`.record[data-index="${record[index]}"]`);
365
+ const newData = {};
361
366
  if(recordEl){
362
367
  recordEl.querySelectorAll('.cell[data-field]').forEach($cell => {
363
368
  const field = $cell.dataset.field;
364
369
  const fieldDef = this.fields.find(f => f.name === field);
365
370
  if(fieldDef && !fieldDef.calculator){
366
371
  const $input = $cell.querySelector('input, select');
367
- if($input){
368
- record[field] = $input.value;
369
- }
372
+ if($input) newData[field] = $input.value;
370
373
  }
371
374
  });
375
+ }
376
+ const commit = () => {
377
+ Object.assign(record, newData);
372
378
  record[editing] = false;
373
- recordEl.classList.remove('editing');
374
- recordEl.removeAttribute('editing');
375
- recordEl.querySelectorAll('.cell[data-field]').forEach($cell => {
376
- const field = $cell.dataset.field;
377
- const fieldDef = this.fields.find(f => f.name === field);
378
- if(fieldDef){
379
- const value = record[field] || '';
380
- if(fieldDef.calculator){
381
- $cell.textContent = fieldDef.calculator(record, this);
382
- } else if(fieldDef.formatter){
383
- $cell.innerHTML = fieldDef.formatter(value);
384
- } else {
385
- $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
+ }
386
394
  }
387
- }
388
- });
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();
389
417
  }
390
- this.dispatchEvent(new CustomEvent('editingChange', {
391
- detail: { record, editing: false },
392
- bubbles: true
393
- }));
394
418
  }
395
419
 
396
420
  cancelEditedRecord(record) {
@@ -649,69 +673,79 @@ export default class Table extends ShadowComponent {
649
673
 
650
674
  deleteRecord(record) {
651
675
  let originalRecord = this.records.find(r => r === record);
652
- let totalPagesBefore = this.getTotalPages();
653
-
654
- if (!originalRecord && record[index] !== undefined) {
655
- originalRecord = this.records[record[index]];
656
- }
657
-
658
- 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 = () => {
659
680
  const recordIndex = this.records.indexOf(originalRecord);
660
681
  this.records.splice(recordIndex, 1);
661
- this.records.forEach((rec, idx) => {
662
- rec[index] = idx;
663
- });
682
+ this.records.forEach((rec, idx) => { rec[index] = idx; });
664
683
  this.requestUpdate();
665
684
  this.dispatchEvent(new CustomEvent('selectionChange', { bubbles: true }));
666
- this.dispatchEvent(new CustomEvent('recordDeleted', {
685
+ this.dispatchEvent(new CustomEvent('recordDeleted', {
667
686
  detail: { index: recordIndex },
668
- bubbles: true
687
+ bubbles: true
669
688
  }));
670
-
671
689
  const totalPages = this.getTotalPages();
672
- if (this.currentPage > totalPages) {
673
- this.setPage(totalPages);
674
- }
675
- if (totalPages !== totalPagesBefore) {
676
- this.dispatchEvent(new CustomEvent('pageCountChanged', {
690
+ if(this.currentPage > totalPages) this.setPage(totalPages);
691
+ if(totalPages !== totalPagesBefore){
692
+ this.dispatchEvent(new CustomEvent('pageCountChanged', {
677
693
  detail: { totalPages },
678
- bubbles: true
694
+ bubbles: true
679
695
  }));
680
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();
681
709
  }
682
710
  }
683
711
 
684
712
  deleteSelected() {
685
- let totalPagesBefore = this.getTotalPages();
686
- const selectedRecords = this.getSelectedRecords();
687
-
688
- selectedRecords.forEach(record => {
689
- let originalRecord = this.records.find(r => r === record);
690
- if (!originalRecord && record[index] !== undefined) {
691
- originalRecord = this.records[record[index]];
692
- }
693
- if (originalRecord) {
694
- const recordIndex = this.records.indexOf(originalRecord);
695
- 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
+ }));
696
732
  }
697
- });
698
-
699
- this.records.forEach((rec, idx) => {
700
- rec[index] = idx;
701
- });
702
- this.requestUpdate();
703
-
704
- const totalPages = this.getTotalPages();
705
- if (this.currentPage > totalPages) {
706
- this.setPage(totalPages);
707
- }
708
- if (totalPages !== totalPagesBefore) {
709
- this.dispatchEvent(new CustomEvent('pageCountChanged', {
710
- detail: { totalPages },
711
- 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
712
745
  }));
746
+ } else {
747
+ commit();
713
748
  }
714
- this.dispatchEvent(new CustomEvent('selectionChange', { bubbles: true }));
715
749
  }
716
750
 
717
751
  getSelectedRecords() {
@@ -1065,10 +1099,43 @@ export default class Table extends ShadowComponent {
1065
1099
  tbody tr:last-child td {
1066
1100
  border-bottom: none;
1067
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
+ }
1068
1130
  th.controls,
1069
1131
  td.controls {
1070
1132
  padding: 0;
1071
1133
  }
1134
+ td.controls-after,
1135
+ td.controls-before {
1136
+ display: flex;
1137
+ align-items: center;
1138
+ }
1072
1139
  .field-select,
1073
1140
  .selection {
1074
1141
  width: 40px;
@@ -0,0 +1,27 @@
1
+ import TableControl from './TableControl.js';
2
+ import { html } from '../../lit-all.min.js';
3
+ import '../Icon.js';
4
+
5
+ export default class DeleteSelected extends TableControl {
6
+ /*
7
+ Public Methods
8
+ */
9
+ deleteSelected = () => {
10
+ this.table.deleteSelected();
11
+ };
12
+
13
+ /*
14
+ Rendering
15
+ */
16
+ render() {
17
+ return html`
18
+ <button class="no-btn icon-btn" title="Delete Selected" @click="${this.deleteSelected}">
19
+ <slot>
20
+ <k-icon name="delete_sweep"></k-icon>
21
+ </slot>
22
+ </button>
23
+ `;
24
+ }
25
+ }
26
+
27
+ customElements.define('k-tc-delete-selected', DeleteSelected);