kempo-ui 0.3.0 → 0.3.2
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/Dialog.js +2 -2
- package/dist/components/Table.js +8 -2
- package/docs/components/dialog.html +26 -6
- package/docs/components/table.html +8 -0
- package/docs/components/tablePlaceholder.html +90 -0
- package/docs/nav.inc.js +2 -1
- package/docs/src/components/Dialog.js +2 -2
- package/docs/src/components/Table.js +8 -2
- package/llm.txt +1 -1
- package/package.json +1 -1
- package/src/components/Dialog.js +7 -3
- package/src/components/Table.js +16 -0
- package/tests/components/Dialog.browser-test.js +97 -0
- package/tests/components/Table.browser-test.js +193 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import ShadowComponent from"./ShadowComponent.js";import{html,css}from"../lit-all.min.js";import{boolExists}from"../utils/propConverters.js";import"./Icon.js";import"./FocusCapture.js";const firstFocusable=e=>e.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')[0];export default class Dialog extends ShadowComponent{static properties={opened:{type:Boolean,reflect:!0,converter:boolExists},closeBtn:{type:Boolean,reflect:!0,attribute:"close-btn",converter:boolExists},overlayClose:{type:Boolean,reflect:!0,attribute:"overlay-close",converter:boolExists},disableKeyboardClose:{type:Boolean,reflect:!0,attribute:"disable-keyboard-close",converter:boolExists},confirmText:{type:String,reflect:!0,attribute:"confirm-text"},confirmClasses:{type:String,reflect:!0,attribute:"confirm-classes"},cancelText:{type:String,reflect:!0,attribute:"cancel-text"},cancelClasses:{type:String,reflect:!0,attribute:"cancel-classes"}};constructor(){super(),this.opened=!1,this.closeBtn=!0,this.overlayClose=!0,this.disableKeyboardClose=!1,this.confirmText="",this.confirmClasses="success ml",this.cancelText="",this.cancelClasses="",this.confirmAction=()=>{},this.cancelAction=()=>{},this.closeCallback=()=>{},this.previousFocus=null}handleClick=e=>{const{target:t}=e,o=t.id||t.closest("[id]")?.id;"overlay"===o&&this.overlayClose||"close"===o?this.close():"cancel"===o&&(this.cancelAction(e),e.defaultPrevented||this.close())};handleSubmit=e=>{e.preventDefault(),this.confirmAction(e),e.keepDialogOpen||this.close()};handleSlotKeydown=e=>{if("Enter"===e.key&&this.confirmText){const t=e.target;if("INPUT"===t.tagName||"TEXTAREA"===t.tagName){e.preventDefault();const t=this.shadowRoot.querySelector("form");t&&t.requestSubmit()}}};handleKeydown=e=>{this.disableKeyboardClose||27!==e.keyCode||this.close()};connectedCallback(){super.connectedCallback()}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("keydown",this.handleKeydown)}updated(e){super.updated(e),e.has("opened")&&this.dispatchEvent(new CustomEvent(this.opened?"opened":"close"))}open(){this.opened=!0,window.addEventListener("keydown",this.handleKeydown),document.body.classList.add("no-scroll");const e=this.updateComplete||this.requestUpdate();e&&e.then?e.then(()=>{const e=this.shadowRoot.querySelector("[autofocus]")||firstFocusable(this.shadowRoot);e&&e.focus()}):setTimeout(()=>{const e=this.shadowRoot.querySelector("[autofocus]")||firstFocusable(this.shadowRoot);e&&e.focus()},0)}close(){this.opened=!1,this.blur(),this.closeCallback(),window.removeEventListener("keydown",this.handleKeydown),document.body.classList.remove("no-scroll")}toggle(){this.opened?this.close():this.open()}focus(){const e=firstFocusable(this.shadowRoot);e&&(this.previousFocus=document.activeElement,e.focus())}blur(){this.previousFocus&&this.previousFocus.focus()}hasTitle(){return!!this.querySelector('[slot="title"]')}static styles=css`
|
|
1
|
+
import ShadowComponent from"./ShadowComponent.js";import{html,css,render}from"../lit-all.min.js";import{boolExists}from"../utils/propConverters.js";import"./Icon.js";import"./FocusCapture.js";const firstFocusable=e=>e.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')[0];export default class Dialog extends ShadowComponent{static properties={opened:{type:Boolean,reflect:!0,converter:boolExists},closeBtn:{type:Boolean,reflect:!0,attribute:"close-btn",converter:boolExists},overlayClose:{type:Boolean,reflect:!0,attribute:"overlay-close",converter:boolExists},disableKeyboardClose:{type:Boolean,reflect:!0,attribute:"disable-keyboard-close",converter:boolExists},confirmText:{type:String,reflect:!0,attribute:"confirm-text"},confirmClasses:{type:String,reflect:!0,attribute:"confirm-classes"},cancelText:{type:String,reflect:!0,attribute:"cancel-text"},cancelClasses:{type:String,reflect:!0,attribute:"cancel-classes"}};constructor(){super(),this.opened=!1,this.closeBtn=!0,this.overlayClose=!0,this.disableKeyboardClose=!1,this.confirmText="",this.confirmClasses="success ml",this.cancelText="",this.cancelClasses="",this.confirmAction=()=>{},this.cancelAction=()=>{},this.closeCallback=()=>{},this.previousFocus=null}handleClick=e=>{const{target:t}=e,o=t.id||t.closest("[id]")?.id;"overlay"===o&&this.overlayClose||"close"===o?this.close():"cancel"===o&&(this.cancelAction(e),e.defaultPrevented||this.close())};handleSubmit=e=>{e.preventDefault(),this.confirmAction(e),e.keepDialogOpen||this.close()};handleSlotKeydown=e=>{if("Enter"===e.key&&this.confirmText){const t=e.target;if("INPUT"===t.tagName||"TEXTAREA"===t.tagName){e.preventDefault();const t=this.shadowRoot.querySelector("form");t&&t.requestSubmit()}}};handleKeydown=e=>{this.disableKeyboardClose||27!==e.keyCode||this.close()};connectedCallback(){super.connectedCallback()}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("keydown",this.handleKeydown)}updated(e){super.updated(e),e.has("opened")&&this.dispatchEvent(new CustomEvent(this.opened?"opened":"close"))}open(){this.opened=!0,window.addEventListener("keydown",this.handleKeydown),document.body.classList.add("no-scroll");const e=this.updateComplete||this.requestUpdate();e&&e.then?e.then(()=>{const e=this.shadowRoot.querySelector("[autofocus]")||firstFocusable(this.shadowRoot);e&&e.focus()}):setTimeout(()=>{const e=this.shadowRoot.querySelector("[autofocus]")||firstFocusable(this.shadowRoot);e&&e.focus()},0)}close(){this.opened=!1,this.blur(),this.closeCallback(),window.removeEventListener("keydown",this.handleKeydown),document.body.classList.remove("no-scroll")}toggle(){this.opened?this.close():this.open()}focus(){const e=firstFocusable(this.shadowRoot);e&&(this.previousFocus=document.activeElement,e.focus())}blur(){this.previousFocus&&this.previousFocus.focus()}hasTitle(){return!!this.querySelector('[slot="title"]')}static styles=css`
|
|
2
2
|
:host {
|
|
3
3
|
position: fixed;
|
|
4
4
|
top: 0;
|
|
@@ -118,4 +118,4 @@ import ShadowComponent from"./ShadowComponent.js";import{html,css}from"../lit-al
|
|
|
118
118
|
</form>
|
|
119
119
|
</k-focus-capture>
|
|
120
120
|
</div>
|
|
121
|
-
`}static create(e="",t={}){!1!==t.closeExisting&&document.querySelectorAll("k-dialog").forEach(e=>e.close());const{removeOnClose:o=!0,closeCallback:s=()=>{},title:i="",titleClasses:l="pyh px m0"}=t,
|
|
121
|
+
`}static create(e="",t={}){!1!==t.closeExisting&&document.querySelectorAll("k-dialog").forEach(e=>e.close());const{removeOnClose:o=!0,closeCallback:s=()=>{},title:i="",titleClasses:l="pyh px m0"}=t,n=new Dialog;if(Object.assign(n,{opened:!0,...t,closeCallback:(...e)=>{o&&n.remove(),s(...e)}}),i){const e=document.createElement("h5");e.slot="title",e.className=l,i instanceof HTMLElement?e.appendChild(i):e.innerHTML=i,n.appendChild(e)}if(void 0!==e?._$litType$){const t=document.createElement("div");t.style.display="contents",render(e,t),n.appendChild(t)}else if(e instanceof HTMLElement||e instanceof DocumentFragment)n.appendChild(e);else if(e){if(/<[^>]+>/.test(e))n.innerHTML+=e;else{const t=document.createElement("p");t.className="p",t.textContent=e,n.appendChild(t)}}return t.width&&n.style.setProperty("--width",t.width),t.minWidth&&n.style.setProperty("--min_width",t.minWidth),t.maxWidth&&n.style.setProperty("--max_width",t.maxWidth),t.height&&n.style.setProperty("--height",t.height),t.minHeight&&n.style.setProperty("--min_height",t.minHeight),t.maxHeight&&n.style.setProperty("--max_height",t.maxHeight),document.body.appendChild(n),n.open(),n}static confirm(e,t,o={}){return Dialog.create(e,{title:o.title||"Confirm",closeBtn:!1,overlayClose:!1,disableKeyboardClose:!0,confirmText:"Yes",confirmClasses:"success ml",confirmAction:()=>t(!0),cancelText:"No",cancelClasses:"danger",cancelAction:()=>t(!1),...o})}static alert(e,t=()=>{},o={}){return Dialog.create(e,{title:o.title||"Alert",closeCallback:t,cancelText:"Ok",...o})}static error(e,t=()=>{},o={}){return Dialog.create(e,{title:o.title||"Error",titleClasses:"pyh px m0 tc-danger",closeCallback:t,cancelText:"Ok",...o})}static success(e,t=()=>{},o={}){return Dialog.create(e,{title:o.title||"Success",titleClasses:"pyh px m0 tc-success",closeCallback:t,cancelText:"Ok",...o})}}customElements.define("k-dialog",Dialog);
|
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.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);
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
<a href="#actionButtons">Action Buttons</a><br />
|
|
28
28
|
<a href="#disableKeyboardClose">Disabling Keyboard Close</a><br />
|
|
29
29
|
<a href="#jsUsage">JavaScript Usage</a><br />
|
|
30
|
+
<a href="#litTemplate">Lit Template Content</a><br />
|
|
30
31
|
<a href="#confirmDialog">Confirm Dialog</a><br />
|
|
31
32
|
|
|
32
33
|
<h6 class="mt"><a href="#jsRef" class="no-link">JavaScript Reference</a></h6>
|
|
@@ -135,6 +136,25 @@
|
|
|
135
136
|
});
|
|
136
137
|
</script>
|
|
137
138
|
|
|
139
|
+
<h3 id="litTemplate"><a href="#litTemplate" class="no-link">Lit Template Content</a></h3>
|
|
140
|
+
<p>You can pass a Lit <code>html</code> tagged template literal directly as the content to any of the static methods (<code>Dialog.create</code>, <code>Dialog.confirm</code>, <code>Dialog.alert</code>, <code>Dialog.error</code>, <code>Dialog.success</code>).</p>
|
|
141
|
+
<pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"d11"</span>></span>Open Dialog with Lit Template<span class="hljs-tag"></<span class="hljs-name">button</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="javascript"><br /> <span class="hljs-keyword">import</span> Dialog <span class="hljs-keyword">from</span> <span class="hljs-string">'/src/components/Dialog.js'</span>;<br /> <span class="hljs-keyword">import</span> { html } <span class="hljs-keyword">from</span> <span class="hljs-string">'/src/lit-all.min.js'</span>;<br /> <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'d11'</span>).addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =></span> {<br /> Dialog.create(html<span class="hljs-string">`<br /> <div class="p"><br /> <h6>Custom Lit Content</h6><br /> <p>This was created with a Lit template.</p><br /> </div><br /> `</span>, {<br /> <span class="hljs-attr">title</span>: <span class="hljs-string">'Lit Template Dialog'</span><br /> });<br /> });<br /></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span></code></pre>
|
|
142
|
+
<button id="d11" class="mb">Open Dialog with Lit Template</button>
|
|
143
|
+
<script type="module">
|
|
144
|
+
import Dialog from '../src/components/Dialog.js';
|
|
145
|
+
import { html } from '../src/lit-all.min.js';
|
|
146
|
+
document.getElementById('d11').addEventListener('click', () => {
|
|
147
|
+
Dialog.create(html`
|
|
148
|
+
<div class="p">
|
|
149
|
+
<h6>Custom Lit Content</h6>
|
|
150
|
+
<p>This was created with a Lit template.</p>
|
|
151
|
+
</div>
|
|
152
|
+
`, {
|
|
153
|
+
title: 'Lit Template Dialog'
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
</script>
|
|
157
|
+
|
|
138
158
|
<h3 id="confirmDialog"><a href="#confirmDialog" class="no-link">Confirm Dialog</a></h3>
|
|
139
159
|
<p>Use the <code>Dialog.confirm</code> static method to create a confirmation dialog. This method takes a text message, a response callback, and an options object.</p>
|
|
140
160
|
<pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"d9"</span>></span>Open Confirm Dialog<span class="hljs-tag"></<span class="hljs-name">button</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="javascript"><br /> <span class="hljs-keyword">import</span> Dialog <span class="hljs-keyword">from</span> <span class="hljs-string">'/src/components/Dialog.js'</span>;<br /> <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'d9'</span>).addEventListener(<span class="hljs-string">'click'</span>, () => {<br /> Dialog.confirm(<span class="hljs-string">"Are you sure you want to proceed?"</span>, <span class="hljs-function"><span class="hljs-params">response</span> =></span> {<br /> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"User response:"</span>, response);<br /> });<br /> });<br /></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span></code></pre>
|
|
@@ -240,11 +260,11 @@
|
|
|
240
260
|
<h3 id="staticMethods"><a href="#staticMethods" class="no-link">Static Methods</a></h3>
|
|
241
261
|
<h5><code>Dialog.create(contents, options)<i>: Dialog</i></code></h5>
|
|
242
262
|
<p>Creates and opens a new dialog with the specified contents and options. Returns the created Dialog instance.</p>
|
|
243
|
-
<p>If <code>contents</code> is plain text (no HTML tags), it will automatically be wrapped in a <code><p class="p"></code> tag. If it contains HTML, it will be inserted as-is. If it's an HTMLElement or DocumentFragment, it will be appended directly
|
|
263
|
+
<p>If <code>contents</code> is plain text (no HTML tags), it will automatically be wrapped in a <code><p class="p"></code> tag. If it contains HTML, it will be inserted as-is. If it's an HTMLElement or DocumentFragment, it will be appended directly. You can also pass a Lit <code>html</code> tagged template literal (<code>TemplateResult</code>), which will be rendered using Lit's <code>render()</code>.</p>
|
|
244
264
|
|
|
245
265
|
<h6>Parameters</h6>
|
|
246
266
|
<ul>
|
|
247
|
-
<li><code>contents<i>: string | HTMLElement | DocumentFragment</i></code> - The content to display in the dialog. Plain text will be wrapped in a paragraph tag automatically.</li>
|
|
267
|
+
<li><code>contents<i>: string | HTMLElement | DocumentFragment | TemplateResult</i></code> - The content to display in the dialog. Plain text will be wrapped in a paragraph tag automatically.</li>
|
|
248
268
|
<li><code>options<i>: object</i></code> - Configuration options for the dialog.</li>
|
|
249
269
|
</ul>
|
|
250
270
|
|
|
@@ -278,7 +298,7 @@
|
|
|
278
298
|
|
|
279
299
|
<h6>Parameters</h6>
|
|
280
300
|
<ul>
|
|
281
|
-
<li><code>text<i>: string</i></code> - The message to display in the dialog.</li>
|
|
301
|
+
<li><code>text<i>: string | TemplateResult</i></code> - The message to display in the dialog.</li>
|
|
282
302
|
<li><code>responseCallback<i>: function</i></code> - Function called with the user's response (<code>true</code> for confirm, <code>false</code> for cancel).</li>
|
|
283
303
|
<li><code>options<i>: object</i></code> - Configuration options.</li>
|
|
284
304
|
</ul>
|
|
@@ -300,7 +320,7 @@
|
|
|
300
320
|
|
|
301
321
|
<h6>Parameters</h6>
|
|
302
322
|
<ul>
|
|
303
|
-
<li><code>text<i>: string</i></code> - The message to display in the dialog.</li>
|
|
323
|
+
<li><code>text<i>: string | TemplateResult</i></code> - The message to display in the dialog.</li>
|
|
304
324
|
<li><code>responseCallback<i>: function</i></code> - Function called when the dialog is closed.</li>
|
|
305
325
|
<li><code>options<i>: object</i></code> - Configuration options.</li>
|
|
306
326
|
</ul>
|
|
@@ -316,7 +336,7 @@
|
|
|
316
336
|
|
|
317
337
|
<h6>Parameters</h6>
|
|
318
338
|
<ul>
|
|
319
|
-
<li><code>text<i>: string</i></code> - The error message to display in the dialog.</li>
|
|
339
|
+
<li><code>text<i>: string | TemplateResult</i></code> - The error message to display in the dialog.</li>
|
|
320
340
|
<li><code>responseCallback<i>: function</i></code> - Function called when the dialog is closed.</li>
|
|
321
341
|
<li><code>options<i>: object</i></code> - Configuration options.</li>
|
|
322
342
|
</ul>
|
|
@@ -332,7 +352,7 @@
|
|
|
332
352
|
|
|
333
353
|
<h6>Parameters</h6>
|
|
334
354
|
<ul>
|
|
335
|
-
<li><code>text<i>: string</i></code> - The success message to display in the dialog.</li>
|
|
355
|
+
<li><code>text<i>: string | TemplateResult</i></code> - The success message to display in the dialog.</li>
|
|
336
356
|
<li><code>responseCallback<i>: function</i></code> - Function called when the dialog is closed.</li>
|
|
337
357
|
<li><code>options<i>: object</i></code> - Configuration options.</li>
|
|
338
358
|
</ul>
|
|
@@ -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>
|
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 ShadowComponent from"./ShadowComponent.js";import{html,css}from"../lit-all.min.js";import{boolExists}from"../utils/propConverters.js";import"./Icon.js";import"./FocusCapture.js";const firstFocusable=e=>e.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')[0];export default class Dialog extends ShadowComponent{static properties={opened:{type:Boolean,reflect:!0,converter:boolExists},closeBtn:{type:Boolean,reflect:!0,attribute:"close-btn",converter:boolExists},overlayClose:{type:Boolean,reflect:!0,attribute:"overlay-close",converter:boolExists},disableKeyboardClose:{type:Boolean,reflect:!0,attribute:"disable-keyboard-close",converter:boolExists},confirmText:{type:String,reflect:!0,attribute:"confirm-text"},confirmClasses:{type:String,reflect:!0,attribute:"confirm-classes"},cancelText:{type:String,reflect:!0,attribute:"cancel-text"},cancelClasses:{type:String,reflect:!0,attribute:"cancel-classes"}};constructor(){super(),this.opened=!1,this.closeBtn=!0,this.overlayClose=!0,this.disableKeyboardClose=!1,this.confirmText="",this.confirmClasses="success ml",this.cancelText="",this.cancelClasses="",this.confirmAction=()=>{},this.cancelAction=()=>{},this.closeCallback=()=>{},this.previousFocus=null}handleClick=e=>{const{target:t}=e,o=t.id||t.closest("[id]")?.id;"overlay"===o&&this.overlayClose||"close"===o?this.close():"cancel"===o&&(this.cancelAction(e),e.defaultPrevented||this.close())};handleSubmit=e=>{e.preventDefault(),this.confirmAction(e),e.keepDialogOpen||this.close()};handleSlotKeydown=e=>{if("Enter"===e.key&&this.confirmText){const t=e.target;if("INPUT"===t.tagName||"TEXTAREA"===t.tagName){e.preventDefault();const t=this.shadowRoot.querySelector("form");t&&t.requestSubmit()}}};handleKeydown=e=>{this.disableKeyboardClose||27!==e.keyCode||this.close()};connectedCallback(){super.connectedCallback()}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("keydown",this.handleKeydown)}updated(e){super.updated(e),e.has("opened")&&this.dispatchEvent(new CustomEvent(this.opened?"opened":"close"))}open(){this.opened=!0,window.addEventListener("keydown",this.handleKeydown),document.body.classList.add("no-scroll");const e=this.updateComplete||this.requestUpdate();e&&e.then?e.then(()=>{const e=this.shadowRoot.querySelector("[autofocus]")||firstFocusable(this.shadowRoot);e&&e.focus()}):setTimeout(()=>{const e=this.shadowRoot.querySelector("[autofocus]")||firstFocusable(this.shadowRoot);e&&e.focus()},0)}close(){this.opened=!1,this.blur(),this.closeCallback(),window.removeEventListener("keydown",this.handleKeydown),document.body.classList.remove("no-scroll")}toggle(){this.opened?this.close():this.open()}focus(){const e=firstFocusable(this.shadowRoot);e&&(this.previousFocus=document.activeElement,e.focus())}blur(){this.previousFocus&&this.previousFocus.focus()}hasTitle(){return!!this.querySelector('[slot="title"]')}static styles=css`
|
|
1
|
+
import ShadowComponent from"./ShadowComponent.js";import{html,css,render}from"../lit-all.min.js";import{boolExists}from"../utils/propConverters.js";import"./Icon.js";import"./FocusCapture.js";const firstFocusable=e=>e.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')[0];export default class Dialog extends ShadowComponent{static properties={opened:{type:Boolean,reflect:!0,converter:boolExists},closeBtn:{type:Boolean,reflect:!0,attribute:"close-btn",converter:boolExists},overlayClose:{type:Boolean,reflect:!0,attribute:"overlay-close",converter:boolExists},disableKeyboardClose:{type:Boolean,reflect:!0,attribute:"disable-keyboard-close",converter:boolExists},confirmText:{type:String,reflect:!0,attribute:"confirm-text"},confirmClasses:{type:String,reflect:!0,attribute:"confirm-classes"},cancelText:{type:String,reflect:!0,attribute:"cancel-text"},cancelClasses:{type:String,reflect:!0,attribute:"cancel-classes"}};constructor(){super(),this.opened=!1,this.closeBtn=!0,this.overlayClose=!0,this.disableKeyboardClose=!1,this.confirmText="",this.confirmClasses="success ml",this.cancelText="",this.cancelClasses="",this.confirmAction=()=>{},this.cancelAction=()=>{},this.closeCallback=()=>{},this.previousFocus=null}handleClick=e=>{const{target:t}=e,o=t.id||t.closest("[id]")?.id;"overlay"===o&&this.overlayClose||"close"===o?this.close():"cancel"===o&&(this.cancelAction(e),e.defaultPrevented||this.close())};handleSubmit=e=>{e.preventDefault(),this.confirmAction(e),e.keepDialogOpen||this.close()};handleSlotKeydown=e=>{if("Enter"===e.key&&this.confirmText){const t=e.target;if("INPUT"===t.tagName||"TEXTAREA"===t.tagName){e.preventDefault();const t=this.shadowRoot.querySelector("form");t&&t.requestSubmit()}}};handleKeydown=e=>{this.disableKeyboardClose||27!==e.keyCode||this.close()};connectedCallback(){super.connectedCallback()}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("keydown",this.handleKeydown)}updated(e){super.updated(e),e.has("opened")&&this.dispatchEvent(new CustomEvent(this.opened?"opened":"close"))}open(){this.opened=!0,window.addEventListener("keydown",this.handleKeydown),document.body.classList.add("no-scroll");const e=this.updateComplete||this.requestUpdate();e&&e.then?e.then(()=>{const e=this.shadowRoot.querySelector("[autofocus]")||firstFocusable(this.shadowRoot);e&&e.focus()}):setTimeout(()=>{const e=this.shadowRoot.querySelector("[autofocus]")||firstFocusable(this.shadowRoot);e&&e.focus()},0)}close(){this.opened=!1,this.blur(),this.closeCallback(),window.removeEventListener("keydown",this.handleKeydown),document.body.classList.remove("no-scroll")}toggle(){this.opened?this.close():this.open()}focus(){const e=firstFocusable(this.shadowRoot);e&&(this.previousFocus=document.activeElement,e.focus())}blur(){this.previousFocus&&this.previousFocus.focus()}hasTitle(){return!!this.querySelector('[slot="title"]')}static styles=css`
|
|
2
2
|
:host {
|
|
3
3
|
position: fixed;
|
|
4
4
|
top: 0;
|
|
@@ -118,4 +118,4 @@ import ShadowComponent from"./ShadowComponent.js";import{html,css}from"../lit-al
|
|
|
118
118
|
</form>
|
|
119
119
|
</k-focus-capture>
|
|
120
120
|
</div>
|
|
121
|
-
`}static create(e="",t={}){!1!==t.closeExisting&&document.querySelectorAll("k-dialog").forEach(e=>e.close());const{removeOnClose:o=!0,closeCallback:s=()=>{},title:i="",titleClasses:l="pyh px m0"}=t,
|
|
121
|
+
`}static create(e="",t={}){!1!==t.closeExisting&&document.querySelectorAll("k-dialog").forEach(e=>e.close());const{removeOnClose:o=!0,closeCallback:s=()=>{},title:i="",titleClasses:l="pyh px m0"}=t,n=new Dialog;if(Object.assign(n,{opened:!0,...t,closeCallback:(...e)=>{o&&n.remove(),s(...e)}}),i){const e=document.createElement("h5");e.slot="title",e.className=l,i instanceof HTMLElement?e.appendChild(i):e.innerHTML=i,n.appendChild(e)}if(void 0!==e?._$litType$){const t=document.createElement("div");t.style.display="contents",render(e,t),n.appendChild(t)}else if(e instanceof HTMLElement||e instanceof DocumentFragment)n.appendChild(e);else if(e){if(/<[^>]+>/.test(e))n.innerHTML+=e;else{const t=document.createElement("p");t.className="p",t.textContent=e,n.appendChild(t)}}return t.width&&n.style.setProperty("--width",t.width),t.minWidth&&n.style.setProperty("--min_width",t.minWidth),t.maxWidth&&n.style.setProperty("--max_width",t.maxWidth),t.height&&n.style.setProperty("--height",t.height),t.minHeight&&n.style.setProperty("--min_height",t.minHeight),t.maxHeight&&n.style.setProperty("--max_height",t.maxHeight),document.body.appendChild(n),n.open(),n}static confirm(e,t,o={}){return Dialog.create(e,{title:o.title||"Confirm",closeBtn:!1,overlayClose:!1,disableKeyboardClose:!0,confirmText:"Yes",confirmClasses:"success ml",confirmAction:()=>t(!0),cancelText:"No",cancelClasses:"danger",cancelAction:()=>t(!1),...o})}static alert(e,t=()=>{},o={}){return Dialog.create(e,{title:o.title||"Alert",closeCallback:t,cancelText:"Ok",...o})}static error(e,t=()=>{},o={}){return Dialog.create(e,{title:o.title||"Error",titleClasses:"pyh px m0 tc-danger",closeCallback:t,cancelText:"Ok",...o})}static success(e,t=()=>{},o={}){return Dialog.create(e,{title:o.title||"Success",titleClasses:"pyh px m0 tc-success",closeCallback:t,cancelText:"Ok",...o})}}customElements.define("k-dialog",Dialog);
|
|
@@ -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.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);
|
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/Dialog.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import ShadowComponent from './ShadowComponent.js';
|
|
2
|
-
import { html, css } from '../lit-all.min.js';
|
|
2
|
+
import { html, css, render } from '../lit-all.min.js';
|
|
3
3
|
import { boolExists } from '../utils/propConverters.js';
|
|
4
4
|
import './Icon.js';
|
|
5
5
|
import './FocusCapture.js';
|
|
@@ -330,10 +330,14 @@ export default class Dialog extends ShadowComponent {
|
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
// Set content
|
|
333
|
-
if(contents
|
|
333
|
+
if(contents?._$litType$ !== undefined) {
|
|
334
|
+
const container = document.createElement('div');
|
|
335
|
+
container.style.display = 'contents';
|
|
336
|
+
render(contents, container);
|
|
337
|
+
dialog.appendChild(container);
|
|
338
|
+
} else if(contents instanceof HTMLElement || contents instanceof DocumentFragment) {
|
|
334
339
|
dialog.appendChild(contents);
|
|
335
340
|
} else if(contents) {
|
|
336
|
-
// Check if content is plain text (no HTML tags)
|
|
337
341
|
const hasHtmlTags = /<[^>]+>/.test(contents);
|
|
338
342
|
if(hasHtmlTags) {
|
|
339
343
|
dialog.innerHTML += contents;
|
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
|
/*
|
|
@@ -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
|
/*
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Dialog from '../../src/components/Dialog.js';
|
|
2
|
+
import { html } from '../../src/lit-all.min.js';
|
|
2
3
|
|
|
3
4
|
const createDialog = async (options = {}) => {
|
|
4
5
|
const container = document.createElement('div');
|
|
@@ -673,5 +674,101 @@ export default {
|
|
|
673
674
|
|
|
674
675
|
cleanup(container);
|
|
675
676
|
pass('Default content slot rendered');
|
|
677
|
+
},
|
|
678
|
+
|
|
679
|
+
/*
|
|
680
|
+
Lit TemplateResult Content Tests
|
|
681
|
+
*/
|
|
682
|
+
'Dialog.create should accept Lit TemplateResult as content': async ({pass, fail}) => {
|
|
683
|
+
const dialog = Dialog.create(html`<p class="p">Lit template content</p>`);
|
|
684
|
+
await dialog.updateComplete;
|
|
685
|
+
|
|
686
|
+
const container = dialog.querySelector('div[style="display: contents;"]');
|
|
687
|
+
if(!container){
|
|
688
|
+
cleanupAllDialogs();
|
|
689
|
+
fail('Should render TemplateResult into a display:contents wrapper');
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const p = container.querySelector('p');
|
|
694
|
+
if(!p || p.textContent !== 'Lit template content'){
|
|
695
|
+
cleanupAllDialogs();
|
|
696
|
+
fail(`Expected paragraph with "Lit template content", got "${p?.textContent}"`);
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
cleanupAllDialogs();
|
|
701
|
+
pass('Dialog.create accepts Lit TemplateResult');
|
|
702
|
+
},
|
|
703
|
+
|
|
704
|
+
'Dialog.confirm should accept Lit TemplateResult as content': async ({pass, fail}) => {
|
|
705
|
+
let response = null;
|
|
706
|
+
const dialog = Dialog.confirm(
|
|
707
|
+
html`<p class="p">Confirm this?</p>`,
|
|
708
|
+
r => { response = r; }
|
|
709
|
+
);
|
|
710
|
+
await dialog.updateComplete;
|
|
711
|
+
|
|
712
|
+
const container = dialog.querySelector('div[style="display: contents;"]');
|
|
713
|
+
if(!container){
|
|
714
|
+
cleanupAllDialogs();
|
|
715
|
+
fail('Should render TemplateResult in confirm dialog');
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
const p = container.querySelector('p');
|
|
720
|
+
if(!p || p.textContent !== 'Confirm this?'){
|
|
721
|
+
cleanupAllDialogs();
|
|
722
|
+
fail(`Expected paragraph with "Confirm this?", got "${p?.textContent}"`);
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
cleanupAllDialogs();
|
|
727
|
+
pass('Dialog.confirm accepts Lit TemplateResult');
|
|
728
|
+
},
|
|
729
|
+
|
|
730
|
+
'Dialog.alert should accept Lit TemplateResult as content': async ({pass, fail}) => {
|
|
731
|
+
const dialog = Dialog.alert(html`<p class="p">Alert content</p>`);
|
|
732
|
+
await dialog.updateComplete;
|
|
733
|
+
|
|
734
|
+
const container = dialog.querySelector('div[style="display: contents;"]');
|
|
735
|
+
if(!container){
|
|
736
|
+
cleanupAllDialogs();
|
|
737
|
+
fail('Should render TemplateResult in alert dialog');
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
cleanupAllDialogs();
|
|
742
|
+
pass('Dialog.alert accepts Lit TemplateResult');
|
|
743
|
+
},
|
|
744
|
+
|
|
745
|
+
'Dialog.error should accept Lit TemplateResult as content': async ({pass, fail}) => {
|
|
746
|
+
const dialog = Dialog.error(html`<p class="p">Error content</p>`);
|
|
747
|
+
await dialog.updateComplete;
|
|
748
|
+
|
|
749
|
+
const container = dialog.querySelector('div[style="display: contents;"]');
|
|
750
|
+
if(!container){
|
|
751
|
+
cleanupAllDialogs();
|
|
752
|
+
fail('Should render TemplateResult in error dialog');
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
cleanupAllDialogs();
|
|
757
|
+
pass('Dialog.error accepts Lit TemplateResult');
|
|
758
|
+
},
|
|
759
|
+
|
|
760
|
+
'Dialog.success should accept Lit TemplateResult as content': async ({pass, fail}) => {
|
|
761
|
+
const dialog = Dialog.success(html`<p class="p">Success content</p>`);
|
|
762
|
+
await dialog.updateComplete;
|
|
763
|
+
|
|
764
|
+
const container = dialog.querySelector('div[style="display: contents;"]');
|
|
765
|
+
if(!container){
|
|
766
|
+
cleanupAllDialogs();
|
|
767
|
+
fail('Should render TemplateResult in success dialog');
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
cleanupAllDialogs();
|
|
772
|
+
pass('Dialog.success accepts Lit TemplateResult');
|
|
676
773
|
}
|
|
677
774
|
};
|
|
@@ -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
|
};
|