kempo-ui 0.3.1 → 0.3.3
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.
- package/dist/components/Table.js +8 -2
- package/dist/components/Toggle.js +6 -1
- package/docs/components/table.html +8 -0
- package/docs/components/tablePlaceholder.html +90 -0
- package/docs/components/toggle.html +20 -0
- package/docs/nav.inc.js +2 -1
- package/docs/src/components/Table.js +8 -2
- package/docs/src/components/Toggle.js +6 -1
- package/llm.txt +1 -1
- package/package.json +1 -1
- package/src/components/Table.js +17 -1
- package/src/components/Toggle.js +10 -1
- package/tests/components/Table.browser-test.js +193 -0
- package/tests/components/Toggle.browser-test.js +62 -0
package/dist/components/Table.js
CHANGED
|
@@ -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"},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`
|
|
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"},placeholder:{type:String,reflect:!0},filteredPlaceholder:{type:String,reflect:!0,attribute:"filtered-placeholder"},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),void 0===this.placeholder&&(this.placeholder="No Records"),void 0===this.filteredPlaceholder&&(this.filteredPlaceholder="")}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.getDisplayedRecords().length>0&&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>
|
|
@@ -11,7 +11,7 @@ import{html,css,unsafeStatic,literal}from"../lit-all.min.js";import ShadowCompon
|
|
|
11
11
|
${s}
|
|
12
12
|
${o?html`<k-icon name="arrow" direction="${r.asc?"down":"up"}" class="icon-sort"></k-icon>`:""}
|
|
13
13
|
</th>
|
|
14
|
-
`)}),this.hasAfterControls()&&e.push(html`<th class="controls field-after-controls"></th>`),e}renderRecordsTemplate(){let e=this.getDisplayedRecords(),t=0,s=this.pageSize;this.enablePages&&(t=(this.currentPage-1)*this.pageSize,s=t+this.pageSize,e=e.slice(t,s));let i=null,r=0;const o=e.map((e,s)=>null!==e?this.renderRecordTemplate(e):(null===i&&(i=t+s),r++,html`<tr class="record fetching"><td class="cell" colspan="${this.getColumnCount()}">Loading...</td></tr>`));return null===i||this.fetchPending||setTimeout(()=>{this.fetchPending||this.dispatchEvent(new CustomEvent("fetchRecords",{detail:{start:i,count:r},bubbles:!0}))},0),o}renderRecordTemplate(e){const t=[];return this.enableSelection&&t.push(html`
|
|
14
|
+
`)}),this.hasAfterControls()&&e.push(html`<th class="controls field-after-controls"></th>`),e}renderRecordsTemplate(){let e=this.getDisplayedRecords(),t=0,s=this.pageSize;if(this.enablePages&&(t=(this.currentPage-1)*this.pageSize,s=t+this.pageSize,e=e.slice(t,s)),0===e.length){const e=this.records.filter(e=>null!==e).length>0?this.filteredPlaceholder:this.placeholder;if(e)return[html`<tr class="placeholder"><td colspan="${this.getColumnCount()}">${e}</td></tr>`]}let i=null,r=0;const o=e.map((e,s)=>null!==e?this.renderRecordTemplate(e):(null===i&&(i=t+s),r++,html`<tr class="record fetching"><td class="cell" colspan="${this.getColumnCount()}">Loading...</td></tr>`));return null===i||this.fetchPending||setTimeout(()=>{this.fetchPending||this.dispatchEvent(new CustomEvent("fetchRecords",{detail:{start:i,count:r},bubbles:!0}))},0),o}renderRecordTemplate(e){const t=[];return this.enableSelection&&t.push(html`
|
|
15
15
|
<td class="cell selection controls">
|
|
16
16
|
<input
|
|
17
17
|
type="checkbox"
|
|
@@ -192,4 +192,10 @@ import{html,css,unsafeStatic,literal}from"../lit-all.min.js";import ShadowCompon
|
|
|
192
192
|
:host(:not([bottom-controls])) #bottom {
|
|
193
193
|
display: none;
|
|
194
194
|
}
|
|
195
|
+
tr.placeholder td {
|
|
196
|
+
text-align: center;
|
|
197
|
+
color: var(--c_text__muted);
|
|
198
|
+
font-style: italic;
|
|
199
|
+
padding: var(--spacer);
|
|
200
|
+
}
|
|
195
201
|
`;static extractFieldsFromRecords(e,t=100){const s=new Set;return e.slice(0,t).forEach(e=>{Object.keys(e).forEach(e=>s.add(e))}),[...s].map(e=>({name:e,label:toTitleCase(e)}))}static format(e){return(Array.isArray(e)?Table.formatters.array:Table.formatters[typeof e])(e)}static formatters={string:e=>e,number:e=>`${e}`,date:e=>e.toLocaleDateString(),boolean:e=>e?"True":"False",array:e=>e.map(e=>Table.format(e)).join(", "),undefined:e=>"",null:e=>"<code>null</code>"};static editors={string:e=>{const t=document.createElement("input");return t.value=e,t},number:e=>{const t=document.createElement("input");return t.type="number",t.value=e,t},date:e=>{const t=document.createElement("input");return t.type="date",t.value=e,t},boolean:e=>{const t=document.createElement("select");return t.innerHTML=`\n <option value="true" ${e?"selected":""}>True</option>\n <option value="false" ${e?"":"selected"}>False</option>\n `,t.value=e,t},calculated:e=>{const t=document.createElement("input");return t.disabled=!0,t.value=e,t}}}window.customElements.define("k-table",Table);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{html,css}from"../lit-all.min.js";import ShadowComponent from"./ShadowComponent.js";export default class Toggle extends ShadowComponent{static properties={value:{type:Boolean,reflect:!0}};constructor(){super(),this.value=!1,this.tabIndex=0}updated(e){super.updated(e),e.has("value")&&this.dispatchEvent(new CustomEvent("change",{detail:{value:this.value},bubbles:!0}))}connectedCallback(){super.connectedCallback(),this.addEventListener("click",this.handleClick),this.addEventListener("keydown",this.handleKeyDown)}disconnectedCallback(){super.disconnectedCallback(),this.removeEventListener("click",this.handleClick),this.removeEventListener("keydown",this.handleKeyDown)}handleClick=()=>{this.toggle()};handleKeyDown=e=>{["Space","Enter"].includes(e.code)&&(e.preventDefault(),this.toggle())};on(){return this.value=!0,this.dispatchEvent(new CustomEvent("on",{detail:{value:!0},bubbles:!0})),this}off(){return this.value=!1,this.dispatchEvent(new CustomEvent("off",{detail:{value:!1},bubbles:!0})),this}toggle(){return this.value?this.off():this.on(),this.dispatchEvent(new CustomEvent("toggle",{detail:{value:this.value},bubbles:!0})),this}render(){return html`
|
|
1
|
+
import{html,css}from"../lit-all.min.js";import ShadowComponent from"./ShadowComponent.js";export default class Toggle extends ShadowComponent{static properties={value:{type:Boolean,reflect:!0},disabled:{type:Boolean,reflect:!0}};constructor(){super(),this.value=!1,this.disabled=!1,this.tabIndex=0}updated(e){super.updated(e),e.has("value")&&this.dispatchEvent(new CustomEvent("change",{detail:{value:this.value},bubbles:!0}))}connectedCallback(){super.connectedCallback(),this.addEventListener("click",this.handleClick),this.addEventListener("keydown",this.handleKeyDown)}disconnectedCallback(){super.disconnectedCallback(),this.removeEventListener("click",this.handleClick),this.removeEventListener("keydown",this.handleKeyDown)}handleClick=()=>{this.disabled||this.toggle()};handleKeyDown=e=>{this.disabled||["Space","Enter"].includes(e.code)&&(e.preventDefault(),this.toggle())};on(){return this.value=!0,this.dispatchEvent(new CustomEvent("on",{detail:{value:!0},bubbles:!0})),this}off(){return this.value=!1,this.dispatchEvent(new CustomEvent("off",{detail:{value:!1},bubbles:!0})),this}toggle(){return this.value?this.off():this.on(),this.dispatchEvent(new CustomEvent("toggle",{detail:{value:this.value},bubbles:!0})),this}render(){return html`
|
|
2
2
|
<div id="switch">
|
|
3
3
|
<div id="handle"></div>
|
|
4
4
|
</div>
|
|
@@ -26,6 +26,11 @@ import{html,css}from"../lit-all.min.js";import ShadowComponent from"./ShadowComp
|
|
|
26
26
|
outline: none;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
:host([disabled]) {
|
|
30
|
+
opacity: 0.5;
|
|
31
|
+
cursor: not-allowed;
|
|
32
|
+
pointer-events: none;
|
|
33
|
+
}
|
|
29
34
|
#switch {
|
|
30
35
|
display: flex;
|
|
31
36
|
align-items: center;
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
<a href="./tableFieldSortHide.html">Field Sorting and Hiding</a><br />
|
|
36
36
|
<a href="./tableFetchRecords.html">Fetching Records</a><br />
|
|
37
37
|
<a href="./tableServerSync.html">Server Sync</a><br />
|
|
38
|
+
<a href="./tablePlaceholder.html">Placeholder</a><br />
|
|
38
39
|
<h6 class="mt"><a href="#jsRef" class="no-link">JavaScript Reference</a></h6>
|
|
39
40
|
<a href="#constructor">Constructor</a><br />
|
|
40
41
|
<a href="#requirements">Requirements</a><br />
|
|
@@ -103,6 +104,7 @@
|
|
|
103
104
|
<li><a href="./tableFieldSortHide.html">Field Sorting and Hiding</a></li>
|
|
104
105
|
<li><a href="./tableFetchRecords.html">Fetching Records</a></li>
|
|
105
106
|
<li><a href="./tableServerSync.html">Server Sync</a></li>
|
|
107
|
+
<li><a href="./tablePlaceholder.html">Placeholder</a></li>
|
|
106
108
|
</ul>
|
|
107
109
|
|
|
108
110
|
<br />
|
|
@@ -159,6 +161,12 @@
|
|
|
159
161
|
<h5><code>pageSizeOptions<i>: <Array>number</i></code></h5>
|
|
160
162
|
<p>An array of numbers representing the available page size options.</p>
|
|
161
163
|
|
|
164
|
+
<h5><code>placeholder<i>: string</i></code></h5>
|
|
165
|
+
<p>Text shown when the table has no records. Defaults to <code>"No Records"</code>. Set to an empty string to suppress. See <a href="./tablePlaceholder.html">Placeholder</a>.</p>
|
|
166
|
+
|
|
167
|
+
<h5><code>filtered-placeholder<i>: string</i></code></h5>
|
|
168
|
+
<p>Text shown when records exist but all are currently filtered or hidden. Defaults to <code>""</code> (no row shown). See <a href="./tablePlaceholder.html">Placeholder</a>.</p>
|
|
169
|
+
|
|
162
170
|
<h3 id="methods"><a href="#methods" class="no-link">Methods</a></h3>
|
|
163
171
|
|
|
164
172
|
<h5><code>setData(<i>object</i> options)<i>: undefined</i></code></h5>
|
|
@@ -0,0 +1,90 @@
|
|
|
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>Placeholder - Table - Kempo Docs</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@2/dist/kempo.min.css" />
|
|
11
|
+
<link rel="stylesheet" href="../kempo-hljs.css" />
|
|
12
|
+
<link rel="stylesheet" href="../styles.css" />
|
|
13
|
+
<script type="module" src="../src/components/Table.js"></script>
|
|
14
|
+
<script>window.litDisableBundleWarning = true;</script>
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<k-import src="../nav-1.inc.html"></k-import>
|
|
18
|
+
<h1 class="ta-center">Table - Placeholder</h1>
|
|
19
|
+
<k-main>
|
|
20
|
+
|
|
21
|
+
<a href="./table.html"><h5>Back to Table Component Documentation</h5></a>
|
|
22
|
+
|
|
23
|
+
<p>The <code>placeholder</code> attribute sets the message shown when the table has no records at all. It defaults to <code>"No Records"</code>. The separate <code>filtered-placeholder</code> attribute sets the message shown when records exist but all have been filtered or hidden — if not set, no row is shown in that case.</p>
|
|
24
|
+
|
|
25
|
+
<h3>Default Placeholder</h3>
|
|
26
|
+
<p>An empty table shows <code>"No Records"</code> by default. No attribute needed.</p>
|
|
27
|
+
<pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">k-table</span> <span class="hljs-attr">id</span>=<span class="hljs-string">emptyTableExample</span>></span><span class="hljs-tag"></<span class="hljs-name">k-table</span>></span><br><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">module</span>></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">'k-table'</span>);<br> <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'emptyTableExample'</span>).<span class="hljs-title function_">setData</span>({<br> <span class="hljs-attr">fields</span>: [{<br> <span class="hljs-attr">name</span>: <span class="hljs-string">'name'</span>,<br> <span class="hljs-attr">label</span>: <span class="hljs-string">'Name'</span><br> }, {<br> <span class="hljs-attr">name</span>: <span class="hljs-string">'role'</span>,<br> <span class="hljs-attr">label</span>: <span class="hljs-string">'Role'</span><br> }],<br> <span class="hljs-attr">records</span>: []<br> })<br></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span></code></pre>
|
|
28
|
+
<k-card label="Result">
|
|
29
|
+
<k-table id="emptyTableExample"></k-table>
|
|
30
|
+
<script type="module">
|
|
31
|
+
await window.customElements.whenDefined('k-table');
|
|
32
|
+
document.getElementById('emptyTableExample').setData({
|
|
33
|
+
fields: [
|
|
34
|
+
{ name: 'name', label: 'Name' },
|
|
35
|
+
{ name: 'role', label: 'Role' }
|
|
36
|
+
],
|
|
37
|
+
records: []
|
|
38
|
+
});
|
|
39
|
+
</script>
|
|
40
|
+
</k-card>
|
|
41
|
+
|
|
42
|
+
<h3>Custom Placeholder</h3>
|
|
43
|
+
<p>Set the <code>placeholder</code> attribute to override the default text.</p>
|
|
44
|
+
<pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">k-table</span> <span class="hljs-attr">id</span>=<span class="hljs-string">customPlaceholderExample</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">'No contacts found.'</span>></span><span class="hljs-tag"></<span class="hljs-name">k-table</span>></span><br><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">module</span>></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">'k-table'</span>);<br> <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'customPlaceholderExample'</span>).<span class="hljs-title function_">setData</span>({<br> <span class="hljs-attr">fields</span>: [{<br> <span class="hljs-attr">name</span>: <span class="hljs-string">'name'</span>,<br> <span class="hljs-attr">label</span>: <span class="hljs-string">'Name'</span><br> }, {<br> <span class="hljs-attr">name</span>: <span class="hljs-string">'email'</span>,<br> <span class="hljs-attr">label</span>: <span class="hljs-string">'Email'</span><br> }],<br> <span class="hljs-attr">records</span>: []<br> })<br></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span></code></pre>
|
|
45
|
+
<k-card label="Result">
|
|
46
|
+
<k-table id="customPlaceholderExample" placeholder="No contacts found."></k-table>
|
|
47
|
+
<script type="module">
|
|
48
|
+
await window.customElements.whenDefined('k-table');
|
|
49
|
+
document.getElementById('customPlaceholderExample').setData({
|
|
50
|
+
fields: [
|
|
51
|
+
{ name: 'name', label: 'Name' },
|
|
52
|
+
{ name: 'email', label: 'Email' }
|
|
53
|
+
],
|
|
54
|
+
records: []
|
|
55
|
+
});
|
|
56
|
+
</script>
|
|
57
|
+
</k-card>
|
|
58
|
+
|
|
59
|
+
<h3>Filtered Placeholder</h3>
|
|
60
|
+
<p>Use <code>filtered-placeholder</code> to show a distinct message when records exist but all are filtered out. Try searching for something that does not match any record below.</p>
|
|
61
|
+
<pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">k-table</span> <span class="hljs-attr">id</span>=<span class="hljs-string">filteredTableExample</span> <span class="hljs-attr">filtered-placeholder</span>=<span class="hljs-string">'No records match your search.'</span>></span><span class="hljs-tag"><<span class="hljs-name">k-tc-search</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">top</span> <span class="hljs-attr">style</span>=<span class="hljs-string">'width: 20rem'</span>></span><span class="hljs-tag"></<span class="hljs-name">k-tc-search</span>></span><span class="hljs-tag"></<span class="hljs-name">k-table</span>></span><br><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">module</span> <span class="hljs-attr">src</span>=<span class="hljs-string">/src/components/tableControls/Search.js</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span><br><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">module</span>></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">'k-table'</span>);<br> <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'filteredTableExample'</span>).<span class="hljs-title function_">setData</span>({<br> <span class="hljs-attr">fields</span>: [{<br> <span class="hljs-attr">name</span>: <span class="hljs-string">'name'</span>,<br> <span class="hljs-attr">label</span>: <span class="hljs-string">'Name'</span><br> }, {<br> <span class="hljs-attr">name</span>: <span class="hljs-string">'city'</span>,<br> <span class="hljs-attr">label</span>: <span class="hljs-string">'City'</span><br> }],<br> <span class="hljs-attr">records</span>: [{<br> <span class="hljs-attr">name</span>: <span class="hljs-string">'Alice'</span>,<br> <span class="hljs-attr">city</span>: <span class="hljs-string">'New York'</span><br> }, {<br> <span class="hljs-attr">name</span>: <span class="hljs-string">'Bob'</span>,<br> <span class="hljs-attr">city</span>: <span class="hljs-string">'Chicago'</span><br> }, {<br> <span class="hljs-attr">name</span>: <span class="hljs-string">'Charlie'</span>,<br> <span class="hljs-attr">city</span>: <span class="hljs-string">'Houston'</span><br> }]<br> })<br></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span></code></pre>
|
|
62
|
+
<k-card label="Result">
|
|
63
|
+
<k-table id="filteredTableExample" filtered-placeholder="No records match your search.">
|
|
64
|
+
<k-tc-search slot="top" style="width: 20rem"></k-tc-search>
|
|
65
|
+
</k-table>
|
|
66
|
+
<script type="module" src="../src/components/tableControls/Search.js"></script>
|
|
67
|
+
<script type="module">
|
|
68
|
+
await window.customElements.whenDefined('k-table');
|
|
69
|
+
document.getElementById('filteredTableExample').setData({
|
|
70
|
+
fields: [
|
|
71
|
+
{ name: 'name', label: 'Name' },
|
|
72
|
+
{ name: 'city', label: 'City' }
|
|
73
|
+
],
|
|
74
|
+
records: [
|
|
75
|
+
{ name: 'Alice', city: 'New York' },
|
|
76
|
+
{ name: 'Bob', city: 'Chicago' },
|
|
77
|
+
{ name: 'Charlie', city: 'Houston' }
|
|
78
|
+
]
|
|
79
|
+
});
|
|
80
|
+
</script>
|
|
81
|
+
</k-card>
|
|
82
|
+
|
|
83
|
+
</k-main>
|
|
84
|
+
<div style="height: 33.333vh"></div>
|
|
85
|
+
<script type="module" src="../src/components/Import.js"></script>
|
|
86
|
+
<script type="module" src="../src/components/Card.js"></script>
|
|
87
|
+
<div style="height: 33vh"></div>
|
|
88
|
+
<script type="module" src="../src/components/Main.js"></script>
|
|
89
|
+
</body>
|
|
90
|
+
</html>
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
<a href="#responsiveBehavior">Responsive Behavior</a><br />
|
|
27
27
|
<a href="#customStyles">Custom Styles</a><br />
|
|
28
28
|
<a href="#javascriptUsage">JavaScript Usage</a><br />
|
|
29
|
+
<a href="#disabled">Disabled</a><br />
|
|
29
30
|
|
|
30
31
|
<h6 class="mt"><a href="jsRef" class="no-link">JavaScript Reference</a></h6>
|
|
31
32
|
<a href="#constructor">Constructor</a><br />
|
|
@@ -137,6 +138,22 @@
|
|
|
137
138
|
</div>
|
|
138
139
|
</div>
|
|
139
140
|
|
|
141
|
+
<h3 id="disabled"><a href="#disabled" class="no-link">Disabled</a></h3>
|
|
142
|
+
<p>The <code>disabled</code> attribute prevents the user from changing the toggle's state while still displaying its current value.</p>
|
|
143
|
+
<div class="row -mx">
|
|
144
|
+
<div class="col m-span-12 px">
|
|
145
|
+
<k-card label="HTML">
|
|
146
|
+
<pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">k-toggle</span> <span class="hljs-attr">disabled</span>></span>Disabled Off<span class="hljs-tag"></<span class="hljs-name">k-toggle</span>></span><br><span class="hljs-tag"><<span class="hljs-name">k-toggle</span> <span class="hljs-attr">disabled</span> <span class="hljs-attr">value</span>></span>Disabled On<span class="hljs-tag"></<span class="hljs-name">k-toggle</span>></span></code></pre>
|
|
147
|
+
</k-card>
|
|
148
|
+
</div>
|
|
149
|
+
<div class="col m-span-12 px">
|
|
150
|
+
<k-card label="Output">
|
|
151
|
+
<k-toggle disabled>Disabled Off</k-toggle>
|
|
152
|
+
<k-toggle disabled value>Disabled On</k-toggle>
|
|
153
|
+
</k-card>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
140
157
|
<h2 id="jsRef"><a href="jsRef" class="no-link">JavaScript Reference</a></h2>
|
|
141
158
|
|
|
142
159
|
<h3 id="constructor"><a href="#constructor" class="no-link">Constructor</a></h3>
|
|
@@ -155,6 +172,9 @@
|
|
|
155
172
|
<p>A boolean that indicates if the switch is on or off, the default value is <code>false</code>. Syncs to <code>value</code> attribute.</p>
|
|
156
173
|
<p>This can be set directly, but it is not recommended as it only triggers the a <code>change</code> event and not a <code>on</code>, <code>off</code> or <code>toggle</code> event as the methods do, so the methods are preferrable.</p>
|
|
157
174
|
|
|
175
|
+
<h5><code>disabled<i>: boolean</i></code></h5>
|
|
176
|
+
<p>When <code>true</code>, the toggle cannot be changed by user interaction. The current state is still displayed. The element will appear at 50% opacity. Syncs to <code>disabled</code> attribute.</p>
|
|
177
|
+
|
|
158
178
|
<h3 id="methods"><a href="#methods" class="no-link">Methods</a></h3>
|
|
159
179
|
<h5><code>on()<i>: this</i></code></h5>
|
|
160
180
|
<p>Set's the <code>value</code> to <code>true</code>, dispatches a <code><i>change</i></code> and an <code><i>on</i></code> event. The switch visual changes to the <b>on</b> state.</p>
|
package/docs/nav.inc.js
CHANGED
|
@@ -58,7 +58,8 @@ searchInput.addEventListener('blur', () => {
|
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
document.addEventListener('keydown', e => {
|
|
61
|
-
|
|
61
|
+
let active = document.activeElement;
|
|
62
|
+
while(active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
|
|
62
63
|
const tag = active?.tagName;
|
|
63
64
|
if(tag === 'INPUT' || tag === 'TEXTAREA' || active?.isContentEditable) return;
|
|
64
65
|
if(e.metaKey || e.ctrlKey || e.altKey) return;
|
|
@@ -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"},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`
|
|
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"},placeholder:{type:String,reflect:!0},filteredPlaceholder:{type:String,reflect:!0,attribute:"filtered-placeholder"},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),void 0===this.placeholder&&(this.placeholder="No Records"),void 0===this.filteredPlaceholder&&(this.filteredPlaceholder="")}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.getDisplayedRecords().length>0&&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>
|
|
@@ -11,7 +11,7 @@ import{html,css,unsafeStatic,literal}from"../lit-all.min.js";import ShadowCompon
|
|
|
11
11
|
${s}
|
|
12
12
|
${o?html`<k-icon name="arrow" direction="${r.asc?"down":"up"}" class="icon-sort"></k-icon>`:""}
|
|
13
13
|
</th>
|
|
14
|
-
`)}),this.hasAfterControls()&&e.push(html`<th class="controls field-after-controls"></th>`),e}renderRecordsTemplate(){let e=this.getDisplayedRecords(),t=0,s=this.pageSize;this.enablePages&&(t=(this.currentPage-1)*this.pageSize,s=t+this.pageSize,e=e.slice(t,s));let i=null,r=0;const o=e.map((e,s)=>null!==e?this.renderRecordTemplate(e):(null===i&&(i=t+s),r++,html`<tr class="record fetching"><td class="cell" colspan="${this.getColumnCount()}">Loading...</td></tr>`));return null===i||this.fetchPending||setTimeout(()=>{this.fetchPending||this.dispatchEvent(new CustomEvent("fetchRecords",{detail:{start:i,count:r},bubbles:!0}))},0),o}renderRecordTemplate(e){const t=[];return this.enableSelection&&t.push(html`
|
|
14
|
+
`)}),this.hasAfterControls()&&e.push(html`<th class="controls field-after-controls"></th>`),e}renderRecordsTemplate(){let e=this.getDisplayedRecords(),t=0,s=this.pageSize;if(this.enablePages&&(t=(this.currentPage-1)*this.pageSize,s=t+this.pageSize,e=e.slice(t,s)),0===e.length){const e=this.records.filter(e=>null!==e).length>0?this.filteredPlaceholder:this.placeholder;if(e)return[html`<tr class="placeholder"><td colspan="${this.getColumnCount()}">${e}</td></tr>`]}let i=null,r=0;const o=e.map((e,s)=>null!==e?this.renderRecordTemplate(e):(null===i&&(i=t+s),r++,html`<tr class="record fetching"><td class="cell" colspan="${this.getColumnCount()}">Loading...</td></tr>`));return null===i||this.fetchPending||setTimeout(()=>{this.fetchPending||this.dispatchEvent(new CustomEvent("fetchRecords",{detail:{start:i,count:r},bubbles:!0}))},0),o}renderRecordTemplate(e){const t=[];return this.enableSelection&&t.push(html`
|
|
15
15
|
<td class="cell selection controls">
|
|
16
16
|
<input
|
|
17
17
|
type="checkbox"
|
|
@@ -192,4 +192,10 @@ import{html,css,unsafeStatic,literal}from"../lit-all.min.js";import ShadowCompon
|
|
|
192
192
|
:host(:not([bottom-controls])) #bottom {
|
|
193
193
|
display: none;
|
|
194
194
|
}
|
|
195
|
+
tr.placeholder td {
|
|
196
|
+
text-align: center;
|
|
197
|
+
color: var(--c_text__muted);
|
|
198
|
+
font-style: italic;
|
|
199
|
+
padding: var(--spacer);
|
|
200
|
+
}
|
|
195
201
|
`;static extractFieldsFromRecords(e,t=100){const s=new Set;return e.slice(0,t).forEach(e=>{Object.keys(e).forEach(e=>s.add(e))}),[...s].map(e=>({name:e,label:toTitleCase(e)}))}static format(e){return(Array.isArray(e)?Table.formatters.array:Table.formatters[typeof e])(e)}static formatters={string:e=>e,number:e=>`${e}`,date:e=>e.toLocaleDateString(),boolean:e=>e?"True":"False",array:e=>e.map(e=>Table.format(e)).join(", "),undefined:e=>"",null:e=>"<code>null</code>"};static editors={string:e=>{const t=document.createElement("input");return t.value=e,t},number:e=>{const t=document.createElement("input");return t.type="number",t.value=e,t},date:e=>{const t=document.createElement("input");return t.type="date",t.value=e,t},boolean:e=>{const t=document.createElement("select");return t.innerHTML=`\n <option value="true" ${e?"selected":""}>True</option>\n <option value="false" ${e?"":"selected"}>False</option>\n `,t.value=e,t},calculated:e=>{const t=document.createElement("input");return t.disabled=!0,t.value=e,t}}}window.customElements.define("k-table",Table);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{html,css}from"../lit-all.min.js";import ShadowComponent from"./ShadowComponent.js";export default class Toggle extends ShadowComponent{static properties={value:{type:Boolean,reflect:!0}};constructor(){super(),this.value=!1,this.tabIndex=0}updated(e){super.updated(e),e.has("value")&&this.dispatchEvent(new CustomEvent("change",{detail:{value:this.value},bubbles:!0}))}connectedCallback(){super.connectedCallback(),this.addEventListener("click",this.handleClick),this.addEventListener("keydown",this.handleKeyDown)}disconnectedCallback(){super.disconnectedCallback(),this.removeEventListener("click",this.handleClick),this.removeEventListener("keydown",this.handleKeyDown)}handleClick=()=>{this.toggle()};handleKeyDown=e=>{["Space","Enter"].includes(e.code)&&(e.preventDefault(),this.toggle())};on(){return this.value=!0,this.dispatchEvent(new CustomEvent("on",{detail:{value:!0},bubbles:!0})),this}off(){return this.value=!1,this.dispatchEvent(new CustomEvent("off",{detail:{value:!1},bubbles:!0})),this}toggle(){return this.value?this.off():this.on(),this.dispatchEvent(new CustomEvent("toggle",{detail:{value:this.value},bubbles:!0})),this}render(){return html`
|
|
1
|
+
import{html,css}from"../lit-all.min.js";import ShadowComponent from"./ShadowComponent.js";export default class Toggle extends ShadowComponent{static properties={value:{type:Boolean,reflect:!0},disabled:{type:Boolean,reflect:!0}};constructor(){super(),this.value=!1,this.disabled=!1,this.tabIndex=0}updated(e){super.updated(e),e.has("value")&&this.dispatchEvent(new CustomEvent("change",{detail:{value:this.value},bubbles:!0}))}connectedCallback(){super.connectedCallback(),this.addEventListener("click",this.handleClick),this.addEventListener("keydown",this.handleKeyDown)}disconnectedCallback(){super.disconnectedCallback(),this.removeEventListener("click",this.handleClick),this.removeEventListener("keydown",this.handleKeyDown)}handleClick=()=>{this.disabled||this.toggle()};handleKeyDown=e=>{this.disabled||["Space","Enter"].includes(e.code)&&(e.preventDefault(),this.toggle())};on(){return this.value=!0,this.dispatchEvent(new CustomEvent("on",{detail:{value:!0},bubbles:!0})),this}off(){return this.value=!1,this.dispatchEvent(new CustomEvent("off",{detail:{value:!1},bubbles:!0})),this}toggle(){return this.value?this.off():this.on(),this.dispatchEvent(new CustomEvent("toggle",{detail:{value:this.value},bubbles:!0})),this}render(){return html`
|
|
2
2
|
<div id="switch">
|
|
3
3
|
<div id="handle"></div>
|
|
4
4
|
</div>
|
|
@@ -26,6 +26,11 @@ import{html,css}from"../lit-all.min.js";import ShadowComponent from"./ShadowComp
|
|
|
26
26
|
outline: none;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
:host([disabled]) {
|
|
30
|
+
opacity: 0.5;
|
|
31
|
+
cursor: not-allowed;
|
|
32
|
+
pointer-events: none;
|
|
33
|
+
}
|
|
29
34
|
#switch {
|
|
30
35
|
display: flex;
|
|
31
36
|
align-items: center;
|
package/llm.txt
CHANGED
|
@@ -73,7 +73,7 @@ import { html, css, LitElement } from 'kempo-ui/src/lit-all.min.js';
|
|
|
73
73
|
| `<k-sortable>` `<k-sortable-item>` | `Sortable.js` | Drag-to-reorder list | [sortable.html](https://dustinpoissant.github.io/kempo-ui/components/sortable.html) |
|
|
74
74
|
| `<k-spinner>` | `Spinner.js` | Loading indicator; `size` (xs/sm/md/lg/xl), `variant` (spinner/dots/bar) | [spinner.html](https://dustinpoissant.github.io/kempo-ui/components/spinner.html) |
|
|
75
75
|
| `<k-split>` | `Split.js` | Resizable split panes | [split.html](https://dustinpoissant.github.io/kempo-ui/components/split.html) |
|
|
76
|
-
| `<k-table>` | `Table.js` | Data table with sorting, filtering, pagination, row selection, server sync | [table.html](https://dustinpoissant.github.io/kempo-ui/components/table.html) |
|
|
76
|
+
| `<k-table>` | `Table.js` | Data table with sorting, filtering, pagination, row selection, server sync; `placeholder` (default "No Records") shown when empty; `filtered-placeholder` shown when all records are filtered/hidden | [table.html](https://dustinpoissant.github.io/kempo-ui/components/table.html) |
|
|
77
77
|
| `<k-tabs>` `<k-tab>` `<k-tab-content>` `<k-tab-spacer>` | `Tabs.js` | Tabbed interface; `<k-tab for-content="id">` links to `<k-tab-content name="id">` | [tabs.html](https://dustinpoissant.github.io/kempo-ui/components/tabs.html) |
|
|
78
78
|
| `<k-tags>` `<k-tag>` | `Tags.js` | Tag input; add/remove tags; fires `change` | [tags.html](https://dustinpoissant.github.io/kempo-ui/components/tags.html) |
|
|
79
79
|
| `<k-theme-select>` | `ThemeSelect.js` | Dropdown to pick a theme | [theme-select.html](https://dustinpoissant.github.io/kempo-ui/components/theme-select.html) |
|
package/package.json
CHANGED
package/src/components/Table.js
CHANGED
|
@@ -19,6 +19,8 @@ export default class Table extends ShadowComponent {
|
|
|
19
19
|
caseSensitiveFilters: { type: Boolean, reflect: true, converter: boolExists, attribute: 'case-sensitive-filters' },
|
|
20
20
|
requestEdit: { type: Boolean, reflect: true, converter: boolExists, attribute: 'request-edit' },
|
|
21
21
|
requestDelete: { type: Boolean, reflect: true, converter: boolExists, attribute: 'request-delete' },
|
|
22
|
+
placeholder: { type: String, reflect: true },
|
|
23
|
+
filteredPlaceholder: { type: String, reflect: true, attribute: 'filtered-placeholder' },
|
|
22
24
|
fields: { type: Array },
|
|
23
25
|
records: { type: Array },
|
|
24
26
|
filters: { type: Array },
|
|
@@ -42,6 +44,8 @@ export default class Table extends ShadowComponent {
|
|
|
42
44
|
if(this.fetchPending === undefined) this.fetchPending = false;
|
|
43
45
|
if(this.requestEdit === undefined) this.requestEdit = false;
|
|
44
46
|
if(this.requestDelete === undefined) this.requestDelete = false;
|
|
47
|
+
if(this.placeholder === undefined) this.placeholder = 'No Records';
|
|
48
|
+
if(this.filteredPlaceholder === undefined) this.filteredPlaceholder = '';
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
/*
|
|
@@ -88,7 +92,7 @@ export default class Table extends ShadowComponent {
|
|
|
88
92
|
if(this.enableSelection) {
|
|
89
93
|
const selectAllCheckbox = this.shadowRoot.getElementById('select-all');
|
|
90
94
|
if(selectAllCheckbox) {
|
|
91
|
-
selectAllCheckbox.checked = this.allOnPageSelected();
|
|
95
|
+
selectAllCheckbox.checked = this.getDisplayedRecords().length > 0 && this.allOnPageSelected();
|
|
92
96
|
}
|
|
93
97
|
}
|
|
94
98
|
}
|
|
@@ -163,6 +167,12 @@ export default class Table extends ShadowComponent {
|
|
|
163
167
|
displayedRecords = displayedRecords.slice(start, end);
|
|
164
168
|
}
|
|
165
169
|
|
|
170
|
+
if(displayedRecords.length === 0) {
|
|
171
|
+
const hasRecords = this.records.filter(r => r !== null).length > 0;
|
|
172
|
+
const message = hasRecords ? this.filteredPlaceholder : this.placeholder;
|
|
173
|
+
if(message) return [html`<tr class="placeholder"><td colspan="${this.getColumnCount()}">${message}</td></tr>`];
|
|
174
|
+
}
|
|
175
|
+
|
|
166
176
|
let fetchStart = null;
|
|
167
177
|
let fetchCount = 0;
|
|
168
178
|
|
|
@@ -1171,6 +1181,12 @@ export default class Table extends ShadowComponent {
|
|
|
1171
1181
|
:host(:not([bottom-controls])) #bottom {
|
|
1172
1182
|
display: none;
|
|
1173
1183
|
}
|
|
1184
|
+
tr.placeholder td {
|
|
1185
|
+
text-align: center;
|
|
1186
|
+
color: var(--c_text__muted);
|
|
1187
|
+
font-style: italic;
|
|
1188
|
+
padding: var(--spacer);
|
|
1189
|
+
}
|
|
1174
1190
|
`;
|
|
1175
1191
|
|
|
1176
1192
|
/*
|
package/src/components/Toggle.js
CHANGED
|
@@ -3,12 +3,14 @@ import ShadowComponent from './ShadowComponent.js';
|
|
|
3
3
|
|
|
4
4
|
export default class Toggle extends ShadowComponent {
|
|
5
5
|
static properties = {
|
|
6
|
-
value: { type: Boolean, reflect: true }
|
|
6
|
+
value: { type: Boolean, reflect: true },
|
|
7
|
+
disabled: { type: Boolean, reflect: true }
|
|
7
8
|
};
|
|
8
9
|
|
|
9
10
|
constructor() {
|
|
10
11
|
super();
|
|
11
12
|
this.value = false;
|
|
13
|
+
this.disabled = false;
|
|
12
14
|
this.tabIndex = 0;
|
|
13
15
|
}
|
|
14
16
|
|
|
@@ -42,10 +44,12 @@ export default class Toggle extends ShadowComponent {
|
|
|
42
44
|
Event Handlers
|
|
43
45
|
*/
|
|
44
46
|
handleClick = () => {
|
|
47
|
+
if(this.disabled) return;
|
|
45
48
|
this.toggle();
|
|
46
49
|
};
|
|
47
50
|
|
|
48
51
|
handleKeyDown = (event) => {
|
|
52
|
+
if(this.disabled) return;
|
|
49
53
|
if (['Space', 'Enter'].includes(event.code)) {
|
|
50
54
|
event.preventDefault();
|
|
51
55
|
this.toggle();
|
|
@@ -120,6 +124,11 @@ export default class Toggle extends ShadowComponent {
|
|
|
120
124
|
outline: none;
|
|
121
125
|
}
|
|
122
126
|
|
|
127
|
+
:host([disabled]) {
|
|
128
|
+
opacity: 0.5;
|
|
129
|
+
cursor: not-allowed;
|
|
130
|
+
pointer-events: none;
|
|
131
|
+
}
|
|
123
132
|
#switch {
|
|
124
133
|
display: flex;
|
|
125
134
|
align-items: center;
|
|
@@ -1247,5 +1247,198 @@ export default {
|
|
|
1247
1247
|
|
|
1248
1248
|
cleanup(table);
|
|
1249
1249
|
pass('Field without editable property defaults to editable');
|
|
1250
|
+
},
|
|
1251
|
+
|
|
1252
|
+
/*
|
|
1253
|
+
Placeholder Tests
|
|
1254
|
+
*/
|
|
1255
|
+
'should default placeholder to "No Records"': async ({pass, fail}) => {
|
|
1256
|
+
const table = await createTable();
|
|
1257
|
+
if(table.placeholder !== 'No Records'){
|
|
1258
|
+
cleanup(table);
|
|
1259
|
+
fail(`placeholder should default to 'No Records', got '${table.placeholder}'`);
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
cleanup(table);
|
|
1263
|
+
pass('placeholder defaults to "No Records"');
|
|
1264
|
+
},
|
|
1265
|
+
|
|
1266
|
+
'should default filteredPlaceholder to empty string': async ({pass, fail}) => {
|
|
1267
|
+
const table = await createTable();
|
|
1268
|
+
if(table.filteredPlaceholder !== ''){
|
|
1269
|
+
cleanup(table);
|
|
1270
|
+
fail(`filteredPlaceholder should default to empty string, got '${table.filteredPlaceholder}'`);
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
cleanup(table);
|
|
1274
|
+
pass('filteredPlaceholder defaults to empty string');
|
|
1275
|
+
},
|
|
1276
|
+
|
|
1277
|
+
'should show placeholder row when records are empty': async ({pass, fail}) => {
|
|
1278
|
+
const table = await createTable();
|
|
1279
|
+
table.setData({
|
|
1280
|
+
fields: [...sampleFields],
|
|
1281
|
+
records: []
|
|
1282
|
+
});
|
|
1283
|
+
await table.updateComplete;
|
|
1284
|
+
|
|
1285
|
+
const placeholderRow = table.shadowRoot.querySelector('tr.placeholder');
|
|
1286
|
+
if(!placeholderRow){
|
|
1287
|
+
cleanup(table);
|
|
1288
|
+
fail('Placeholder row should exist in shadow DOM when records are empty');
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
if(!placeholderRow.textContent.includes('No Records')){
|
|
1293
|
+
cleanup(table);
|
|
1294
|
+
fail(`Placeholder row should contain default placeholder text, got '${placeholderRow.textContent}'`);
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
cleanup(table);
|
|
1299
|
+
pass('Placeholder row shown with default text when records are empty');
|
|
1300
|
+
},
|
|
1301
|
+
|
|
1302
|
+
'should show custom placeholder text when set': async ({pass, fail}) => {
|
|
1303
|
+
const table = await createTable();
|
|
1304
|
+
table.placeholder = 'Nothing here.';
|
|
1305
|
+
table.setData({
|
|
1306
|
+
fields: [...sampleFields],
|
|
1307
|
+
records: []
|
|
1308
|
+
});
|
|
1309
|
+
await table.updateComplete;
|
|
1310
|
+
|
|
1311
|
+
const placeholderRow = table.shadowRoot.querySelector('tr.placeholder');
|
|
1312
|
+
if(!placeholderRow){
|
|
1313
|
+
cleanup(table);
|
|
1314
|
+
fail('Placeholder row should exist when records are empty');
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
if(!placeholderRow.textContent.includes('Nothing here.')){
|
|
1319
|
+
cleanup(table);
|
|
1320
|
+
fail(`Placeholder row should contain custom text, got '${placeholderRow.textContent}'`);
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
cleanup(table);
|
|
1325
|
+
pass('Placeholder row shows custom placeholder text');
|
|
1326
|
+
},
|
|
1327
|
+
|
|
1328
|
+
'should not show placeholder row when records exist': async ({pass, fail}) => {
|
|
1329
|
+
const table = await createTable();
|
|
1330
|
+
table.setData({
|
|
1331
|
+
fields: [...sampleFields],
|
|
1332
|
+
records: [...sampleRecords]
|
|
1333
|
+
});
|
|
1334
|
+
await table.updateComplete;
|
|
1335
|
+
|
|
1336
|
+
const placeholderRow = table.shadowRoot.querySelector('tr.placeholder');
|
|
1337
|
+
if(placeholderRow){
|
|
1338
|
+
cleanup(table);
|
|
1339
|
+
fail('Placeholder row should not exist when records are present');
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
cleanup(table);
|
|
1344
|
+
pass('Placeholder row not shown when records exist');
|
|
1345
|
+
},
|
|
1346
|
+
|
|
1347
|
+
'should not show placeholder row when placeholder is empty string': async ({pass, fail}) => {
|
|
1348
|
+
const table = await createTable();
|
|
1349
|
+
table.placeholder = '';
|
|
1350
|
+
table.setData({
|
|
1351
|
+
fields: [...sampleFields],
|
|
1352
|
+
records: []
|
|
1353
|
+
});
|
|
1354
|
+
await table.updateComplete;
|
|
1355
|
+
|
|
1356
|
+
const placeholderRow = table.shadowRoot.querySelector('tr.placeholder');
|
|
1357
|
+
if(placeholderRow){
|
|
1358
|
+
cleanup(table);
|
|
1359
|
+
fail('Placeholder row should not exist when placeholder is empty string');
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
cleanup(table);
|
|
1364
|
+
pass('Placeholder row not shown when placeholder is empty string');
|
|
1365
|
+
},
|
|
1366
|
+
|
|
1367
|
+
'should show filteredPlaceholder after all records are filtered out': async ({pass, fail}) => {
|
|
1368
|
+
const table = await createTable();
|
|
1369
|
+
table.filteredPlaceholder = 'No results match your filter.';
|
|
1370
|
+
table.setData({
|
|
1371
|
+
fields: [...sampleFields],
|
|
1372
|
+
records: [...sampleRecords]
|
|
1373
|
+
});
|
|
1374
|
+
await table.updateComplete;
|
|
1375
|
+
|
|
1376
|
+
table.addFilter('city', 'equals', 'Nonexistent City');
|
|
1377
|
+
await table.updateComplete;
|
|
1378
|
+
|
|
1379
|
+
const placeholderRow = table.shadowRoot.querySelector('tr.placeholder');
|
|
1380
|
+
if(!placeholderRow){
|
|
1381
|
+
cleanup(table);
|
|
1382
|
+
fail('Placeholder row should appear after all records are filtered out');
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
if(!placeholderRow.textContent.includes('No results match your filter.')){
|
|
1387
|
+
cleanup(table);
|
|
1388
|
+
fail(`Placeholder should show filteredPlaceholder text, got '${placeholderRow.textContent}'`);
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
cleanup(table);
|
|
1393
|
+
pass('filteredPlaceholder shown after all records filtered out');
|
|
1394
|
+
},
|
|
1395
|
+
|
|
1396
|
+
'should not show placeholder when filtered and filteredPlaceholder is not set': async ({pass, fail}) => {
|
|
1397
|
+
const table = await createTable();
|
|
1398
|
+
table.setData({
|
|
1399
|
+
fields: [...sampleFields],
|
|
1400
|
+
records: [...sampleRecords]
|
|
1401
|
+
});
|
|
1402
|
+
await table.updateComplete;
|
|
1403
|
+
|
|
1404
|
+
table.addFilter('city', 'equals', 'Nonexistent City');
|
|
1405
|
+
await table.updateComplete;
|
|
1406
|
+
|
|
1407
|
+
const placeholderRow = table.shadowRoot.querySelector('tr.placeholder');
|
|
1408
|
+
if(placeholderRow){
|
|
1409
|
+
cleanup(table);
|
|
1410
|
+
fail('Placeholder row should not appear when filteredPlaceholder is empty and records are filtered');
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
cleanup(table);
|
|
1415
|
+
pass('No placeholder shown when filteredPlaceholder is not set and records are filtered');
|
|
1416
|
+
},
|
|
1417
|
+
|
|
1418
|
+
'should show placeholder (not filteredPlaceholder) when records array is empty': async ({pass, fail}) => {
|
|
1419
|
+
const table = await createTable();
|
|
1420
|
+
table.placeholder = 'No Records';
|
|
1421
|
+
table.filteredPlaceholder = 'No matches.';
|
|
1422
|
+
table.setData({
|
|
1423
|
+
fields: [...sampleFields],
|
|
1424
|
+
records: []
|
|
1425
|
+
});
|
|
1426
|
+
await table.updateComplete;
|
|
1427
|
+
|
|
1428
|
+
const placeholderRow = table.shadowRoot.querySelector('tr.placeholder');
|
|
1429
|
+
if(!placeholderRow){
|
|
1430
|
+
cleanup(table);
|
|
1431
|
+
fail('Placeholder row should exist when records are empty');
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
if(!placeholderRow.textContent.includes('No Records')){
|
|
1436
|
+
cleanup(table);
|
|
1437
|
+
fail(`Should use placeholder text, not filteredPlaceholder, got '${placeholderRow.textContent}'`);
|
|
1438
|
+
return;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
cleanup(table);
|
|
1442
|
+
pass('placeholder used (not filteredPlaceholder) when records array is empty');
|
|
1250
1443
|
}
|
|
1251
1444
|
};
|
|
@@ -584,5 +584,67 @@ export default {
|
|
|
584
584
|
toggle.remove();
|
|
585
585
|
cleanup(container);
|
|
586
586
|
pass('Handles disconnection and reconnection');
|
|
587
|
+
},
|
|
588
|
+
|
|
589
|
+
/*
|
|
590
|
+
Disabled
|
|
591
|
+
*/
|
|
592
|
+
'disabled should default to false': async ({pass, fail}) => {
|
|
593
|
+
const { container, toggle } = await createToggle();
|
|
594
|
+
if(toggle.disabled !== false){
|
|
595
|
+
cleanup(container);
|
|
596
|
+
return fail(`Expected disabled to be false, got ${toggle.disabled}`);
|
|
597
|
+
}
|
|
598
|
+
cleanup(container);
|
|
599
|
+
pass('disabled defaults to false');
|
|
600
|
+
},
|
|
601
|
+
|
|
602
|
+
'should reflect disabled attribute': async ({pass, fail}) => {
|
|
603
|
+
const container = document.createElement('div');
|
|
604
|
+
container.innerHTML = '<k-toggle disabled></k-toggle>';
|
|
605
|
+
document.body.appendChild(container);
|
|
606
|
+
const toggle = container.querySelector('k-toggle');
|
|
607
|
+
await toggle.updateComplete;
|
|
608
|
+
if(!toggle.hasAttribute('disabled')){
|
|
609
|
+
cleanup(container);
|
|
610
|
+
return fail('Toggle should have disabled attribute');
|
|
611
|
+
}
|
|
612
|
+
if(toggle.disabled !== true){
|
|
613
|
+
cleanup(container);
|
|
614
|
+
return fail(`Expected disabled property to be true, got ${toggle.disabled}`);
|
|
615
|
+
}
|
|
616
|
+
cleanup(container);
|
|
617
|
+
pass('disabled attribute reflects correctly');
|
|
618
|
+
},
|
|
619
|
+
|
|
620
|
+
'click should not change value when disabled': async ({pass, fail}) => {
|
|
621
|
+
const container = document.createElement('div');
|
|
622
|
+
container.innerHTML = '<k-toggle disabled></k-toggle>';
|
|
623
|
+
document.body.appendChild(container);
|
|
624
|
+
const toggle = container.querySelector('k-toggle');
|
|
625
|
+
await toggle.updateComplete;
|
|
626
|
+
toggle.click();
|
|
627
|
+
if(toggle.value !== false){
|
|
628
|
+
cleanup(container);
|
|
629
|
+
return fail(`Expected value to remain false after click when disabled, got ${toggle.value}`);
|
|
630
|
+
}
|
|
631
|
+
cleanup(container);
|
|
632
|
+
pass('Click does not change value when disabled');
|
|
633
|
+
},
|
|
634
|
+
|
|
635
|
+
'keydown should not change value when disabled': async ({pass, fail}) => {
|
|
636
|
+
const container = document.createElement('div');
|
|
637
|
+
container.innerHTML = '<k-toggle disabled></k-toggle>';
|
|
638
|
+
document.body.appendChild(container);
|
|
639
|
+
const toggle = container.querySelector('k-toggle');
|
|
640
|
+
await toggle.updateComplete;
|
|
641
|
+
const event = new KeyboardEvent('keydown', { code: 'Space', bubbles: true });
|
|
642
|
+
toggle.dispatchEvent(event);
|
|
643
|
+
if(toggle.value !== false){
|
|
644
|
+
cleanup(container);
|
|
645
|
+
return fail(`Expected value to remain false after Space key when disabled, got ${toggle.value}`);
|
|
646
|
+
}
|
|
647
|
+
cleanup(container);
|
|
648
|
+
pass('Keydown does not change value when disabled');
|
|
587
649
|
}
|
|
588
650
|
};
|