kempo-ui 0.0.79 → 0.0.81

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.
@@ -88,79 +88,8 @@ Do not prefix identifiers with underscores.
88
88
  - Use clear, descriptive names without prefixes.
89
89
  - When true privacy is needed inside classes, prefer native JavaScript private fields (e.g., `#myField`) instead of simulated privacy via underscores.
90
90
 
91
- ## Components
92
-
93
- ### Base Component Architecture
94
-
95
- The project provides three base components for different rendering strategies. Choose the appropriate base component and extend it:
96
-
97
- #### ShadowComponent
98
- For components that need shadow DOM encapsulation and automatic `/kempo.css` stylesheet injection.
99
-
100
- ```javascript
101
- import ShadowComponent from './ShadowComponent.js';
102
-
103
- export default class MyComponent extends ShadowComponent {
104
- render() {
105
- return html`<p>Shadow DOM content with scoped styles</p>`;
106
- }
107
- }
108
- ```
109
-
110
- #### LightComponent
111
- For components that render directly to light DOM without shadow DOM encapsulation.
112
-
113
- ```javascript
114
- import LightComponent from './LightComponent.js';
115
-
116
- export default class MyComponent extends LightComponent {
117
- renderLightDom() {
118
- return html`<p>Light DOM content</p>`;
119
- }
120
- }
121
- ```
122
-
123
- #### HybridComponent
124
- For components that need both shadow DOM (with automatic `/kempo.css`) and light DOM rendering.
125
-
126
- ```javascript
127
- import HybridComponent from './HybridComponent.js';
128
-
129
- export default class MyComponent extends HybridComponent {
130
- render() {
131
- return html`<p>Shadow DOM content</p>`;
132
- }
133
-
134
- renderLightDom() {
135
- return html`<p>Light DOM content alongside natural children</p>`;
136
- }
137
- }
138
- ```
139
-
140
- **Important:** Always call `super.updated()` when overriding the `updated()` method in LightComponent or HybridComponent to ensure proper rendering.
141
-
142
- ### Component Architecture and Communication
143
-
144
- - Use methods to cause actions; do not emit events to trigger logic. Events are for notifying that something already happened.
145
- - Prefer `el.closest('ktf-test-framework')?.enqueueSuite({...})` over firing an `enqueue` event.
146
-
147
- - Wrap dependent components inside a parent `ktf-test-framework` element. Children find it via `closest('ktf-test-framework')` and call its methods. The framework can query its subtree to orchestrate children.
148
-
149
- - Avoid `window` globals and global custom events for coordination. If broadcast is needed, scope events to the framework element; reserve window events for global, non-visual concerns (e.g., settings changes).
150
-
151
91
  ## Development Workflow
152
92
 
153
- ### Icons
154
- - All icons are stored in the `icons/` directory as SVG files
155
- - Icons should have no `width` or `height` attributes, and use `fill="currentColor"` for theming
156
- - To download new Material Icons from Google's CDN:
157
- ```bash
158
- npm run geticon icon_name
159
- ```
160
- Example: `npm run geticon format_bold`
161
- - The script automatically formats the icon with proper attributes
162
- - Browse available icons at [Google Material Symbols](https://fonts.google.com/icons)
163
-
164
93
  ### Local Development Server
165
94
  - **DO NOT** start a development server - one is already running
166
95
  - Default port: **8083**
@@ -0,0 +1,201 @@
1
+ ---
2
+ name: new-component
3
+ description: Creates a new Kempo UI web component — choosing the right base class, writing the source file, registering the custom element, and adding documentation.
4
+ ---
5
+
6
+ # New Component
7
+
8
+ ## When to Use
9
+
10
+ Use this skill any time you are asked to create a new component or add a new custom element to the project.
11
+
12
+ ---
13
+
14
+ ## Overview
15
+
16
+ Creating a component involves four steps:
17
+
18
+ 1. **Choose the base component** — pick the right rendering strategy
19
+ 2. **Write the source file** in `src/components/`
20
+ 3. **Register the custom element** at the bottom of the source file
21
+ 4. **Add documentation** in `docs/components/`
22
+
23
+ ---
24
+
25
+ ## Step 1: Choose the Base Component
26
+
27
+ Three base classes are available. Pick based on rendering needs:
28
+
29
+ ### `ShadowComponent`
30
+ Use when the component needs shadow DOM encapsulation. The base class automatically injects the `/kempo.css` stylesheet into the shadow root.
31
+
32
+ ```javascript
33
+ import { html } from '../lit-all.min.js';
34
+ import ShadowComponent from './ShadowComponent.js';
35
+
36
+ export default class MyComponent extends ShadowComponent {
37
+ render() {
38
+ return html`<p>Shadow DOM content with scoped styles</p>`;
39
+ }
40
+ }
41
+ ```
42
+
43
+ ### `LightComponent`
44
+ Use when the component renders to the light DOM (no encapsulation, inherits page styles).
45
+
46
+ ```javascript
47
+ import { html } from '../lit-all.min.js';
48
+ import LightComponent from './LightComponent.js';
49
+
50
+ export default class MyComponent extends LightComponent {
51
+ renderLightDom() {
52
+ return html`<p>Light DOM content</p>`;
53
+ }
54
+ }
55
+ ```
56
+
57
+ ### `HybridComponent`
58
+ Use when the component needs both a shadow DOM portion and a light DOM portion (e.g. slotted children that also need managed light DOM output).
59
+
60
+ ```javascript
61
+ import { html } from '../lit-all.min.js';
62
+ import HybridComponent from './HybridComponent.js';
63
+
64
+ export default class MyComponent extends HybridComponent {
65
+ render() {
66
+ return html`<p>Shadow DOM content</p>`;
67
+ }
68
+
69
+ renderLightDom() {
70
+ return html`<p>Light DOM content alongside natural children</p>`;
71
+ }
72
+ }
73
+ ```
74
+
75
+ **Important:** Always call `super.updated()` when overriding the `updated()` method in `LightComponent` or `HybridComponent`.
76
+
77
+ ---
78
+
79
+ ## Step 2: Write the Source File
80
+
81
+ Create `src/components/MyComponent.js`. Follow these conventions:
82
+
83
+ - Use multi-line comments to separate logical sections: `Lifecycle Callbacks`, `Event Handlers`, `Public Methods`, `Utility Functions`, `Rendering`, `Styles`.
84
+ - Declare reactive properties with `static properties = { ... }` and initialize defaults in the constructor.
85
+ - Use `static styles = css\`...\`` for component-scoped CSS (shadow DOM only).
86
+ - Use arrow functions for class methods to avoid `.bind(this)`.
87
+ - Do not prefix private fields with underscores — use native JS private fields (`#field`) when true privacy is needed.
88
+
89
+ Example skeleton:
90
+
91
+ ```javascript
92
+ import { html, css } from '../lit-all.min.js';
93
+ import ShadowComponent from './ShadowComponent.js';
94
+
95
+ export default class MyComponent extends ShadowComponent {
96
+ static properties = {
97
+ value: { type: String, reflect: true }
98
+ };
99
+
100
+ /*
101
+ Lifecycle Calblacks
102
+ */
103
+ constructor(){
104
+ super();
105
+ this.value = 'default value';
106
+ }
107
+ connectedCallback(){
108
+ super.connectedCallback();
109
+ // Do things
110
+ }
111
+
112
+ /*
113
+ Event Handlers
114
+ */
115
+ handleClick = () => {
116
+ this.dispatchEvent(new CustomEvent('my-event', { bubbles: true, composed: true }));
117
+ };
118
+
119
+ /*
120
+ Rendering
121
+ */
122
+ render() {
123
+ return html`
124
+ <button @click=${this.handleClick}>${this.value}</button>
125
+ `;
126
+ }
127
+ /*
128
+ Styles
129
+ */
130
+ static styles = css`
131
+ :host {
132
+ display: block;
133
+ }
134
+ `;
135
+
136
+ /*
137
+ Static Methods
138
+ */
139
+ static staticMethod(){
140
+ // Do things
141
+ }
142
+ }
143
+
144
+ customElements.define('k-my-component', MyComponent);
145
+ ```
146
+
147
+ ### Custom element naming
148
+ - All elements use the `k-` prefix (e.g. `k-spinner`, `k-color-picker`).
149
+ - Use kebab-case for multi-word names.
150
+ - The `customElements.define(...)` call goes at the **bottom** of the file, after the class.
151
+
152
+ ---
153
+
154
+ ## Step 3: Register in the Nav and Index (if it should appear in docs)
155
+
156
+ Add a link to the component in `docs/nav.inc.html` inside the `<menu>` under the appropriate section, in alphabetical order:
157
+
158
+ ```html
159
+ <a href="./components/my-component.html">My Component</a>
160
+ ```
161
+
162
+ Also add a card in `docs/index.html` inside the `<div class="row -mx">` under the `<h2>Components</h2>` section, in alphabetical order:
163
+
164
+ ```html
165
+ <div class="span-12 t-span-6 d-span-4 px">
166
+ <a href="./components/my-component.html" class="card mb no-link d-b">
167
+ <h3 class="tc-primary">My Component</h3>
168
+ <p class="tc-muted">One-sentence description of what the component does.</p>
169
+ </a>
170
+ </div>
171
+ ```
172
+
173
+ ---
174
+
175
+ ## Step 4: Add Documentation
176
+
177
+ Create `docs/components/my-component.html`. Use an existing docs page (e.g. `docs/components/spinner.html`) as a reference for structure and conventions.
178
+
179
+ Key requirements:
180
+ - Consistent `<head>` with title, stylesheets, and `window.litDisableBundleWarning = true`.
181
+ - `<k-import src="../nav-1.inc.html"></k-import>` at the top of `<body>`.
182
+ - A Table of Contents accordion.
183
+ - Sections for: examples/usage, and a JavaScript Reference covering constructor, attributes/properties, CSS variables, events, and public methods (as applicable).
184
+ - Use the `highlight-code` skill for all code examples that need syntax highlighting.
185
+ - At the bottom of the file, load the component and its dependencies as `<script type="module">` tags:
186
+
187
+ ```html
188
+ <script type="module" src="../src/components/MyComponent.js"></script>
189
+ <script type="module" src="../src/components/Import.js"></script>
190
+ <script type="module" src="../src/components/Accordion.js"></script>
191
+ <script type="module" src="../src/components/Card.js"></script>
192
+ ```
193
+
194
+ ---
195
+
196
+ ## Component Architecture and Communication
197
+
198
+ - Use **methods** to trigger actions. Events notify that something already happened; they should not be used to trigger logic.
199
+ - Prefer `el.closest('k-parent')?.doSomething()` over dispatching an event and listening for it.
200
+ - Child components should locate their parent via `closest('k-parent-element')` and call its methods directly.
201
+ - Avoid `window` globals and global custom events for coordination. Scope events to the relevant element; reserve `window` events for global, non-visual concerns (e.g. settings changes).
@@ -0,0 +1,8 @@
1
+ import ShadowComponent from"./ShadowComponent.js";import{html,css}from"../lit-all.min.js";export default class FilterItem extends ShadowComponent{render(){return html`<slot></slot>`}static styles=css`
2
+ :host {
3
+ display: contents;
4
+ }
5
+ :host([hidden]) {
6
+ display: none !important;
7
+ }
8
+ `}customElements.define("k-filter-item",FilterItem);
@@ -0,0 +1,5 @@
1
+ import ShadowComponent from"./ShadowComponent.js";import{html,css}from"../lit-all.min.js";export default class FilterList extends ShadowComponent{render(){return html`<slot></slot>`}filter(t){const e=t.toLowerCase().split(/\s+/).filter(t=>t.length>0);this.querySelectorAll("k-filter-item").forEach(t=>{const s=(t.getAttribute("filter-keywords")||"").toLowerCase();t.hidden=e.length>0&&!e.every(t=>s.includes(t))})}static styles=css`
2
+ :host {
3
+ display: block;
4
+ }
5
+ `}customElements.define("k-filter-list",FilterList);
@@ -34,11 +34,11 @@ import{html,css,unsafeStatic,literal}from"../lit-all.min.js";import ShadowCompon
34
34
  </select>
35
35
  `;default:return html`<input type="text" .value=${s} />`}}renderDisplayCell(e,t,s,i,r){return i?i(e,this):r?r(s):s}renderBeforeControlsTemplate(){const e=[];return this.querySelectorAll('[slot="before"]').forEach(t=>{const s=t.tagName.toLowerCase(),i=document.createElement(s);Array.from(t.attributes).forEach(e=>{"slot"!==e.name&&i.setAttribute(e.name,e.value)}),t.innerHTML&&(i.innerHTML=t.innerHTML),e.push(i)}),html`
36
36
  <td class="cell controls controls-before">
37
- ${e}
37
+ <div>${e}</div>
38
38
  </td>
39
39
  `}renderAfterControlsTemplate(){const e=[];return this.querySelectorAll('[slot="after"]').forEach(t=>{const s=t.tagName.toLowerCase(),i=document.createElement(s);Array.from(t.attributes).forEach(e=>{"slot"!==e.name&&i.setAttribute(e.name,e.value)}),t.innerHTML&&(i.innerHTML=t.innerHTML),e.push(i)}),html`
40
40
  <td class="cell controls controls-after">
41
- ${e}
41
+ <div>${e}</div>
42
42
  </td>
43
43
  `}hasBeforeControls(){return!!this.querySelector('[slot="before"]')}hasAfterControls(){return!!this.querySelector('[slot="after"]')}hasTopControls(){return!!this.querySelector('[slot="top"]')}hasBottomControls(){return!!this.querySelector(":scope > :not([slot])")}editRecord(e){e[editing]=!0;const t=this.shadowRoot.querySelector(`.record[data-index="${e[index]}"]`);t&&(t.classList.add("editing"),t.setAttribute("editing","true"),t.querySelectorAll(".cell[data-field]").forEach(t=>{const s=t.dataset.field,i=this.fields.find(e=>e.name===s);if(i){const r=e[s]||"";if(t.innerHTML="",i.calculator){const s=document.createElement("input");s.disabled=!0,s.value=i.calculator(e,this),t.appendChild(s)}else if(i.editor)t.appendChild(i.editor(r));else{const e=i.type||typeof r,s=Table.editors[e]||Table.editors.string;t.appendChild(s(r))}}})),this.dispatchEvent(new CustomEvent("editingChange",{detail:{record:e,editing:!0},bubbles:!0}))}saveEditedRecord(e){const t=this.shadowRoot.querySelector(`.record[data-index="${e[index]}"]`),s={};t&&t.querySelectorAll(".cell[data-field]").forEach(e=>{const t=e.dataset.field,i=this.fields.find(e=>e.name===t);if(i&&!i.calculator){const i=e.querySelector("input, select");i&&(s[t]=i.value)}});const i=()=>{Object.assign(e,s),e[editing]=!1,t&&(t.classList.remove("editing"),t.removeAttribute("editing"),t.querySelectorAll(".cell[data-field]").forEach(t=>{const s=t.dataset.field,i=this.fields.find(e=>e.name===s);if(i){const r=e[s]||"";i.calculator?t.textContent=i.calculator(e,this):i.formatter?t.innerHTML=i.formatter(r):t.textContent=r}})),this.dispatchEvent(new CustomEvent("editingChange",{detail:{record:e,editing:!1},bubbles:!0}))};if(Object.keys(s).some(t=>String(e[t]??"")!==s[t]))if(this.requestEdit){t?.classList.add("pending");const r=()=>{t?.classList.remove("pending"),i()},o=()=>{t?.classList.remove("pending")};this.dispatchEvent(new CustomEvent("requestSave",{detail:{record:e,newData:s,approve:r,reject:o},bubbles:!0}))}else i();else this.cancelEditedRecord(e)}cancelEditedRecord(e){e[editing]=!1;const t=this.shadowRoot.querySelector(`.record[data-index="${e[index]}"]`);t&&(t.classList.remove("editing"),t.removeAttribute("editing"),t.querySelectorAll(".cell[data-field]").forEach(t=>{const s=t.dataset.field,i=this.fields.find(e=>e.name===s);if(i){const r=e[s]||"";i.calculator?t.textContent=i.calculator(e,this):i.formatter?t.innerHTML=i.formatter(r):t.textContent=r}})),this.dispatchEvent(new CustomEvent("editingChange",{detail:{record:e,editing:!1},bubbles:!0}))}recordIsEditing(e){return e[editing]}getCurrentPage(){return this.currentPage}getTotalPages(){return Math.ceil(this.getDisplayedRecords().length/this.pageSize)}setPage(e){e<1||e>this.getTotalPages()||(this.currentPage=e,this.requestUpdate(),this.dispatchEvent(new CustomEvent("pageChange",{bubbles:!0})))}firstPage(){1!==this.currentPage&&this.setPage(1)}nextPage(){this.currentPage<this.getTotalPages()&&this.setPage(this.currentPage+1)}prevPage(){this.currentPage>1&&this.setPage(this.currentPage-1)}lastPage(){this.currentPage!==this.getTotalPages()&&this.setPage(this.getTotalPages())}setPageSize(e){this.pageSize=e,this.currentPage=1,this.requestUpdate(),this.dispatchEvent(new CustomEvent("pageSizeChange",{bubbles:!0}))}getPageSize(){return this.pageSize}getPageSizeOptions(){return this.pageSizeOptions}getFieldLabel(e){const t=this.fields.find(t=>t.name===e);return t?t.label:toTitleCase(e)}setPageSizeOptions(e){this.pageSizeOptions=e,this.requestUpdate()}setData({records:e=!1,fields:t=!1,pageSize:s=!1,pageSizeOptions:i=!1,currentPage:r=!1,enableSelection:o}={}){let n=!1,l=this.getTotalPages(),a=this.currentPage;e&&(this.records=e.map(e=>({...e})),this.records.forEach((e,t)=>{e[index]=t,e[selected]=!1,e[hidden]=!1,e[editing]=!1}),this.fields=t||Table.extractFieldsFromRecords(this.records),n=!0),s&&(this.pageSize=s,n=!0),i&&(this.pageSizeOptions=i),r&&(this.currentPage=r,n=!0),void 0!==o&&(this.enableSelection=o,n=!0),n&&this.requestUpdate();const d=this.getTotalPages();d!==l&&this.dispatchEvent(new CustomEvent("pageCountChanged",{detail:{totalPages:this.getTotalPages()},bubbles:!0})),a>d&&this.setPage(d)}setRecords(e,t){let s=this.getTotalPages(),i=this.currentPage;this.records=e.map(e=>({...e})),this.records.forEach((e,t)=>{e[index]=t,e[selected]=!1,e[hidden]=!1,e[editing]=!1}),this.fields=t||Table.extractFieldsFromRecords(this.records),this.requestUpdate(),this.dispatchEvent(new CustomEvent("recordsSet",{detail:{records:e},bubbles:!0}));const r=this.getTotalPages();r!==s&&this.dispatchEvent(new CustomEvent("pageCountChanged",{detail:{totalPages:this.getTotalPages()},bubbles:!0})),i>r&&this.setPage(r)}setupFetchRecords(e,t){const s=this.records.length,i=this.getTotalPages();if(s<e){this.records.length=e,this.records.fill(null,s),this.requestUpdate();const t=this.getTotalPages();t!==i&&setTimeout(()=>{this.dispatchEvent(new CustomEvent("pageCountChanged",{detail:{totalPages:t},bubbles:!0}))},0)}this.addEventListener("fetchRecords",async e=>{if(this.fetchPending)return;this.fetchPending=!0;const{start:s,count:i}=e.detail,r=await t(s,i);r.forEach((e,t)=>{e[index]=s+t,void 0===e[selected]&&(e[selected]=!1),void 0===e[hidden]&&(e[hidden]=!1),void 0===e[editing]&&(e[editing]=!1)}),this.records.splice(s,r.length,...r),this.fetchPending=!1,this.requestUpdate()})}addRecord(e){e[selected]=!1,e[hidden]=!1,e[index]=this.records.length,this.records.push(e),this.requestUpdate(),this.dispatchEvent(new CustomEvent("recordAdded",{detail:{record:e},bubbles:!0}))}updateRecord(e,t){let s=!1,i=this.records.find(t=>t===e);if(i||void 0===e[index]||(i=this.records[e[index]]),Object.keys(t).forEach(e=>{i.hasOwnProperty(e)&&(i[e]=t[e],s=!0)}),s){const e=(this.currentPage-1)*this.pageSize,t=e+this.pageSize;(!this.enablePages||i[index]>=e&&i[index]<t)&&this.requestUpdate()}}deleteRecord(e){let t=this.records.find(t=>t===e);const s=this.getTotalPages();if(t||void 0===e[index]||(t=this.records[e[index]]),!t)return;const i=()=>{const e=this.records.indexOf(t);this.records.splice(e,1),this.records.forEach((e,t)=>{e[index]=t}),this.requestUpdate(),this.dispatchEvent(new CustomEvent("selectionChange",{bubbles:!0})),this.dispatchEvent(new CustomEvent("recordDeleted",{detail:{index:e},bubbles:!0}));const i=this.getTotalPages();this.currentPage>i&&this.setPage(i),i!==s&&this.dispatchEvent(new CustomEvent("pageCountChanged",{detail:{totalPages:i},bubbles:!0}))};if(this.requestDelete){const e=this.shadowRoot.querySelector(`.record[data-index="${t[index]}"]`);e?.classList.add("pending");const s=()=>{e?.classList.remove("pending"),i()},r=()=>{e?.classList.remove("pending")};this.dispatchEvent(new CustomEvent("requestDelete",{detail:{records:[t],approve:s,reject:r},bubbles:!0}))}else i()}deleteSelected(){const e=this.getTotalPages(),t=this.getSelectedRecords().map(e=>this.records.find(t=>t===e)??(void 0!==e[index]?this.records[e[index]]:null)).filter(Boolean);if(!t.length)return;const s=()=>{t.forEach(e=>{const t=this.records.indexOf(e);-1!==t&&this.records.splice(t,1)}),this.records.forEach((e,t)=>{e[index]=t}),this.requestUpdate();const s=this.getTotalPages();this.currentPage>s&&this.setPage(s),s!==e&&this.dispatchEvent(new CustomEvent("pageCountChanged",{detail:{totalPages:s},bubbles:!0})),this.dispatchEvent(new CustomEvent("selectionChange",{bubbles:!0}))};if(this.requestDelete){const e=t.map(e=>this.shadowRoot.querySelector(`.record[data-index="${e[index]}"]`)).filter(Boolean);e.forEach(e=>e.classList.add("pending"));const i=()=>{e.forEach(e=>e.classList.remove("pending")),s()},r=()=>{e.forEach(e=>e.classList.remove("pending"))};this.dispatchEvent(new CustomEvent("requestDelete",{detail:{records:t,approve:i,reject:r},bubbles:!0}))}else s()}getSelectedRecords(){return this.records.filter(e=>e[selected])}selectAllOnPage(){const e=(this.currentPage-1)*this.pageSize,t=Math.min(e+this.pageSize,this.records.length);for(let s=e;s<t;s++)this.records[s][selected]=!0;this.requestUpdate(),setTimeout(()=>{this.dispatchEvent(new CustomEvent("selectionChange",{bubbles:!0}))},0)}deselectAllOnPage(){const e=(this.currentPage-1)*this.pageSize,t=Math.min(e+this.pageSize,this.records.length);for(let s=e;s<t;s++)this.records[s][selected]=!1;this.requestUpdate(),setTimeout(()=>{this.dispatchEvent(new CustomEvent("selectionChange",{bubbles:!0}))},0)}allOnPageSelected(){const e=(this.currentPage-1)*this.pageSize,t=Math.min(e+this.pageSize,this.records.length);for(let s=e;s<t;s++)if(!this.records[s][selected])return!1;return!0}sortBy(e,t=!0){this.sort=this.sort.filter(t=>t.name!==e),this.sort.push({name:e,asc:t}),this.requestUpdate()}hideRecord(e){let t=this.records.find(t=>t===e);t||void 0===e[index]||(t=this.records[e[index]]),t&&(t[hidden]=!0,this.requestUpdate(),this.dispatchEvent(new CustomEvent("recordHidden",{bubbles:!0})))}showRecord(e){let t=this.records.find(t=>t===e);t||void 0===e[index]||(t=this.records[e[index]]),t&&(t[hidden]=!1,this.requestUpdate(),this.dispatchEvent(new CustomEvent("recordShown",{bubbles:!0})))}showAllRecords(){this.records.forEach(e=>{e[hidden]=!1}),this.filters.length&&(this.filters=[],this.dispatchEvent(new CustomEvent("filterRemoved",{bubbles:!0})),this.dispatchEvent(new CustomEvent("filterChange",{bubbles:!0}))),this.requestUpdate(),this.dispatchEvent(new CustomEvent("recordShown",{bubbles:!0})),this.dispatchEvent(new CustomEvent("allRecordsShown",{bubbles:!0}))}addFilter(e,t,s){this.filters.push({field:e,condition:t,value:s}),this.dispatchEvent(new CustomEvent("filterAdded",{bubbles:!0})),this.dispatchEvent(new CustomEvent("filterChange",{bubbles:!0})),this.requestUpdate()}removeFilter(e,t,s,i=!0){const r=this.filters.findIndex(i=>i.field===e&&i.condition===t&&i.value===s);-1!==r&&(this.records.forEach(i=>{this.testFilter(i,e,t,s)||(i[hidden]=!1)}),this.filters.splice(r,1),this.dispatchEvent(new CustomEvent("filterRemoved",{bubbles:!0})),this.dispatchEvent(new CustomEvent("filterChange",{bubbles:!0})),i&&this.requestUpdate())}testFilter(e,t,s,i){let r=e[t],o=i;switch(this.caseSensitiveFilters||"string"!=typeof r||"string"!=typeof i||(r=r.toLowerCase(),o=i.toLowerCase()),s){case"equals":return r===o;case"not-equals":return r!==o;case"contains":return r.includes(o);case"not-contains":return!r.includes(o);case"greater-than":return r>o;case"less-than":return r<o;case"greater-than-or-equal":return r>=o;case"less-than-or-equal":return r<=o;default:return!0}}removeAllFilters(){this.filters.length&&([...this.filters].forEach(({field:e,condition:t,value:s})=>{this.removeFilter(e,t,s,!1)}),this.requestUpdate())}search(e){const t=e.trim().toLowerCase();let s=!1;this.records.forEach(e=>{if(e[hidden])return;let i=!1;this.fields.forEach(({name:s})=>{(e[s]?.toString().toLowerCase()||"").includes(t)&&(i=!0)}),e[hidden]!==!i&&(e[hidden]=!i,s=!0)}),s&&(this.dispatchEvent(new CustomEvent("recordHidden",{bubbles:!0})),this.requestUpdate()),this.dispatchEvent(new CustomEvent("search",{detail:{term:e},bubbles:!0}))}getDisplayedRecords(){this.filters.forEach(({field:e,condition:t,value:s})=>{this.records.forEach(i=>{null!==i&&(this.testFilter(i,e,t,s)||(i[hidden]=!0))})});let e=this.records.filter(e=>null===e||!e[hidden]);return this.sort.forEach(({name:t,asc:s})=>{e.sort((e,i)=>null===e||null===i?0:e[t]<i[t]?s?-1:1:e[t]>i[t]?s?1:-1:0)}),e}getHiddenRecords(){return this.records.filter(e=>e[hidden])}calculateColumnSizes(){const e=Array.from(this.querySelectorAll('[slot="before"]')),t=Array.from(this.querySelectorAll('[slot="after"]')),s={beforeControls:e.reduce((e,t)=>e+(t.maxWidth||40),0),afterControls:t.reduce((e,t)=>e+(t.maxWidth||40),0)},i=[...e,...t].some(e=>void 0===e.maxWidth);return JSON.stringify(this.columnSizes)!==JSON.stringify(s)&&(this.columnSizes=s),i&&setTimeout(()=>this.calculateColumnSizes(),0),this.columnSizes}setFieldHiddenState(e,t){const s=this.fields.find(t=>t.name===e);s&&(s.hidden=t,this.calculateColumnSizes(),this.requestUpdate(),this.dispatchEvent(new CustomEvent("fieldVisibilityChanged",{detail:{field:s},bubbles:!0})),this.dispatchEvent(new CustomEvent(t?"fieldHidden":"fieldShown",{detail:{field:s},bubbles:!0})))}hideField(e){this.setFieldHiddenState(e,!0)}showField(e){this.setFieldHiddenState(e,!1)}reorderFields(e){const t=[];e.forEach(e=>{const s=this.fields.find(t=>t.name===e);s&&t.push(s)}),this.fields=t,this.requestUpdate()}render(){return this.records&&this.fields?(this.calculateColumnSizes(),this.hasTopControls()?this.setAttribute("top-controls","true"):this.removeAttribute("top-controls"),this.hasBottomControls()?this.setAttribute("bottom-controls","true"):this.removeAttribute("bottom-controls"),html`
44
44
  <div id="wrapper">
@@ -154,8 +154,8 @@ import{html,css,unsafeStatic,literal}from"../lit-all.min.js";import ShadowCompon
154
154
  td.controls {
155
155
  padding: 0;
156
156
  }
157
- td.controls-after,
158
- td.controls-before {
157
+ td.controls-after > div,
158
+ td.controls-before > div {
159
159
  display: flex;
160
160
  align-items: center;
161
161
  }
@@ -0,0 +1,135 @@
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>Filter List - Components - Kempo Docs - A Web Components Solution</title>
7
+ <link rel="icon" type="image/png" sizes="48x48" href="../media/icon48.png">
8
+ <link rel="manifest" href="../manifest.json" />
9
+ <link rel="stylesheet" href="../kempo-vars.css" />
10
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/kempo-css@1.3.11/dist/kempo.min.css" />
11
+ <link rel="stylesheet" href="../kempo-hljs.css" />
12
+ <link rel="stylesheet" href="../styles.css" />
13
+ <script>window.litDisableBundleWarning = true;</script>
14
+ </head>
15
+ <body>
16
+ <k-import src="../nav-1.inc.html"></k-import>
17
+ <main>
18
+ <h1 class="ta-center">Filter List</h1>
19
+
20
+ <k-accordion persistent-id="toc" class="b r mb">
21
+ <k-accordion-header for-panel="toc-panel">Table of Contents</k-accordion-header>
22
+ <k-accordion-panel name="toc-panel">
23
+ <div class="m">
24
+ <h6>Examples</h6>
25
+ <a href="#basicUsage">Basic Usage</a><br />
26
+ <a href="#tagButtons">Tag Buttons</a><br />
27
+
28
+ <h6 class="mt"><a href="#jsRef" class="no-link">JavaScript Reference</a></h6>
29
+ <a href="#constructor">Constructor</a><br />
30
+ <a href="#requirements">Requirements</a><br />
31
+ <a href="#filterItem">FilterItem Attributes</a><br />
32
+ <a href="#methods">Methods</a><br />
33
+ </div>
34
+ </k-accordion-panel>
35
+ </k-accordion>
36
+
37
+ <h3 id="basicUsage"><a href="#basicUsage" class="no-link">Basic Usage</a></h3>
38
+ <p>Wrap items in <code>k-filter-item</code> elements with a <code>filter-keywords</code> attribute inside a <code>k-filter-list</code>. Connect an input to <code>filter()</code> using the <a href="../utils/debounce.html">debounce</a> utility to avoid filtering on every keystroke.</p>
39
+ <div class="row -mx">
40
+ <div class="col m-span-12 px">
41
+ <k-card label="HTML">
42
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">id</span>=<span class="hljs-string">filterInput</span> <span class="hljs-attr">type</span>=<span class="hljs-string">search</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">&#x27;Filter...&#x27;</span> /&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">k-filter-list</span> <span class="hljs-attr">id</span>=<span class="hljs-string">myFilterList</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-filter-item</span> <span class="hljs-attr">class</span>=<span class="hljs-string">d-b</span> <span class="hljs-attr">filter-keywords</span>=<span class="hljs-string">&#x27;apple fruit red&#x27;</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">#</span>&gt;</span>Apple<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-filter-item</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-filter-item</span> <span class="hljs-attr">class</span>=<span class="hljs-string">d-b</span> <span class="hljs-attr">filter-keywords</span>=<span class="hljs-string">&#x27;banana fruit yellow&#x27;</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">#</span>&gt;</span>Banana<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-filter-item</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-filter-item</span> <span class="hljs-attr">class</span>=<span class="hljs-string">d-b</span> <span class="hljs-attr">filter-keywords</span>=<span class="hljs-string">&#x27;cherry fruit red&#x27;</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">#</span>&gt;</span>Cherry<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-filter-item</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-filter-item</span> <span class="hljs-attr">class</span>=<span class="hljs-string">d-b</span> <span class="hljs-attr">filter-keywords</span>=<span class="hljs-string">&#x27;grape fruit purple&#x27;</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">#</span>&gt;</span>Grape<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-filter-item</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-filter-item</span> <span class="hljs-attr">class</span>=<span class="hljs-string">d-b</span> <span class="hljs-attr">filter-keywords</span>=<span class="hljs-string">&#x27;lemon citrus yellow fruit&#x27;</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">#</span>&gt;</span>Lemon<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-filter-item</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-filter-item</span> <span class="hljs-attr">class</span>=<span class="hljs-string">d-b</span> <span class="hljs-attr">filter-keywords</span>=<span class="hljs-string">&#x27;orange citrus fruit&#x27;</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">#</span>&gt;</span>Orange<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-filter-item</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-filter-item</span> <span class="hljs-attr">class</span>=<span class="hljs-string">d-b</span> <span class="hljs-attr">filter-keywords</span>=<span class="hljs-string">&#x27;strawberry fruit red&#x27;</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">#</span>&gt;</span>Strawberry<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-filter-item</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">k-filter-list</span>&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">module</span>&gt;</span><span class="language-javascript"><br> <span class="hljs-keyword">import</span> debounce <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;../src/utils/debounce.js&#x27;</span>;<br> <span class="hljs-keyword">const</span> list = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;myFilterList&#x27;</span>);<br> <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;filterInput&#x27;</span>).<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">&#x27;input&#x27;</span>, <span class="hljs-title function_">debounce</span>(<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> list.<span class="hljs-title function_">filter</span>(e.<span class="hljs-property">target</span>.<span class="hljs-property">value</span>)));<br></span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></code></pre>
43
+ </k-card>
44
+ </div>
45
+ <div class="col m-span-12 px">
46
+ <k-card label="Output">
47
+ <input id="filterInput" type="search" placeholder="Filter..." class="mb" />
48
+ <k-filter-list id="myFilterList">
49
+ <k-filter-item class="d-b" filter-keywords="apple fruit red"><a href="#">Apple</a></k-filter-item>
50
+ <k-filter-item class="d-b" filter-keywords="banana fruit yellow"><a href="#">Banana</a></k-filter-item>
51
+ <k-filter-item class="d-b" filter-keywords="cherry fruit red"><a href="#">Cherry</a></k-filter-item>
52
+ <k-filter-item class="d-b" filter-keywords="grape fruit purple"><a href="#">Grape</a></k-filter-item>
53
+ <k-filter-item class="d-b" filter-keywords="lemon citrus yellow fruit"><a href="#">Lemon</a></k-filter-item>
54
+ <k-filter-item class="d-b" filter-keywords="orange citrus fruit"><a href="#">Orange</a></k-filter-item>
55
+ <k-filter-item class="d-b" filter-keywords="strawberry fruit red"><a href="#">Strawberry</a></k-filter-item>
56
+ </k-filter-list>
57
+ <script type="module">
58
+ import debounce from '../src/utils/debounce.js';
59
+ const list = document.getElementById('myFilterList');
60
+ document.getElementById('filterInput').addEventListener('input', debounce(e => list.filter(e.target.value)));
61
+ </script>
62
+ </k-card>
63
+ </div>
64
+ </div>
65
+
66
+ <h3 id="tagButtons"><a href="#tagButtons" class="no-link">Tag Buttons</a></h3>
67
+ <p>Use category buttons to filter the list. Each button stores its filter term in <code>data-filter</code>; clicking it activates that button and calls <code>filter()</code>. An empty string clears the filter and shows all items.</p>
68
+ <div class="row -mx">
69
+ <div class="col m-span-12 px">
70
+ <k-card label="HTML">
71
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&#x27;d-f gap mb&#x27;</span> <span class="hljs-attr">id</span>=<span class="hljs-string">tagButtons</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&#x27;btn primary&#x27;</span> <span class="hljs-attr">data-filter</span>=<span class="hljs-string">&#x27;&#x27;</span>&gt;</span>All<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">btn</span> <span class="hljs-attr">data-filter</span>=<span class="hljs-string">red</span>&gt;</span>Red<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">btn</span> <span class="hljs-attr">data-filter</span>=<span class="hljs-string">yellow</span>&gt;</span>Yellow<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">btn</span> <span class="hljs-attr">data-filter</span>=<span class="hljs-string">citrus</span>&gt;</span>Citrus<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">k-filter-list</span> <span class="hljs-attr">id</span>=<span class="hljs-string">tagFilterList</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-filter-item</span> <span class="hljs-attr">class</span>=<span class="hljs-string">d-b</span> <span class="hljs-attr">filter-keywords</span>=<span class="hljs-string">&#x27;apple fruit red&#x27;</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">#</span>&gt;</span>Apple<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-filter-item</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-filter-item</span> <span class="hljs-attr">class</span>=<span class="hljs-string">d-b</span> <span class="hljs-attr">filter-keywords</span>=<span class="hljs-string">&#x27;banana fruit yellow&#x27;</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">#</span>&gt;</span>Banana<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-filter-item</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-filter-item</span> <span class="hljs-attr">class</span>=<span class="hljs-string">d-b</span> <span class="hljs-attr">filter-keywords</span>=<span class="hljs-string">&#x27;cherry fruit red&#x27;</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">#</span>&gt;</span>Cherry<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-filter-item</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-filter-item</span> <span class="hljs-attr">class</span>=<span class="hljs-string">d-b</span> <span class="hljs-attr">filter-keywords</span>=<span class="hljs-string">&#x27;grape fruit purple&#x27;</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">#</span>&gt;</span>Grape<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-filter-item</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-filter-item</span> <span class="hljs-attr">class</span>=<span class="hljs-string">d-b</span> <span class="hljs-attr">filter-keywords</span>=<span class="hljs-string">&#x27;lemon citrus yellow fruit&#x27;</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">#</span>&gt;</span>Lemon<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-filter-item</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-filter-item</span> <span class="hljs-attr">class</span>=<span class="hljs-string">d-b</span> <span class="hljs-attr">filter-keywords</span>=<span class="hljs-string">&#x27;orange citrus fruit&#x27;</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">#</span>&gt;</span>Orange<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-filter-item</span>&gt;</span><br> <span class="hljs-tag">&lt;<span class="hljs-name">k-filter-item</span> <span class="hljs-attr">class</span>=<span class="hljs-string">d-b</span> <span class="hljs-attr">filter-keywords</span>=<span class="hljs-string">&#x27;strawberry fruit red&#x27;</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">#</span>&gt;</span>Strawberry<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-filter-item</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">k-filter-list</span>&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">module</span>&gt;</span><span class="language-javascript"><br> <span class="hljs-keyword">const</span> list = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;tagFilterList&#x27;</span>);<br> <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelectorAll</span>(<span class="hljs-string">&#x27;#tagButtons [data-filter]&#x27;</span>).<span class="hljs-title function_">forEach</span>(<span class="hljs-function"><span class="hljs-params">btn</span> =&gt;</span> {<br> btn.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">&#x27;click&#x27;</span>, <span class="hljs-function">() =&gt;</span> {<br> <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelectorAll</span>(<span class="hljs-string">&#x27;#tagButtons [data-filter]&#x27;</span>).<span class="hljs-title function_">forEach</span>(<span class="hljs-function"><span class="hljs-params">b</span> =&gt;</span> b.<span class="hljs-property">classList</span>.<span class="hljs-title function_">remove</span>(<span class="hljs-string">&#x27;primary&#x27;</span>));<br> btn.<span class="hljs-property">classList</span>.<span class="hljs-title function_">add</span>(<span class="hljs-string">&#x27;primary&#x27;</span>);<br> list.<span class="hljs-title function_">filter</span>(btn.<span class="hljs-property">dataset</span>.<span class="hljs-property">filter</span>);<br> });<br> });<br></span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></code></pre>
72
+ </k-card>
73
+ </div>
74
+ <div class="col m-span-12 px">
75
+ <k-card label="Output">
76
+ <div class="d-f gap mb" id="tagButtons">
77
+ <button class="btn primary" data-filter="">All</button>
78
+ <button class="btn" data-filter="red">Red</button>
79
+ <button class="btn" data-filter="yellow">Yellow</button>
80
+ <button class="btn" data-filter="citrus">Citrus</button>
81
+ </div>
82
+ <k-filter-list id="tagFilterList">
83
+ <k-filter-item class="d-b" filter-keywords="apple fruit red"><a href="#">Apple</a></k-filter-item>
84
+ <k-filter-item class="d-b" filter-keywords="banana fruit yellow"><a href="#">Banana</a></k-filter-item>
85
+ <k-filter-item class="d-b" filter-keywords="cherry fruit red"><a href="#">Cherry</a></k-filter-item>
86
+ <k-filter-item class="d-b" filter-keywords="grape fruit purple"><a href="#">Grape</a></k-filter-item>
87
+ <k-filter-item class="d-b" filter-keywords="lemon citrus yellow fruit"><a href="#">Lemon</a></k-filter-item>
88
+ <k-filter-item class="d-b" filter-keywords="orange citrus fruit"><a href="#">Orange</a></k-filter-item>
89
+ <k-filter-item class="d-b" filter-keywords="strawberry fruit red"><a href="#">Strawberry</a></k-filter-item>
90
+ </k-filter-list>
91
+ <script type="module">
92
+ const list = document.getElementById('tagFilterList');
93
+ document.querySelectorAll('#tagButtons [data-filter]').forEach(btn => {
94
+ btn.addEventListener('click', () => {
95
+ document.querySelectorAll('#tagButtons [data-filter]').forEach(b => b.classList.remove('primary'));
96
+ btn.classList.add('primary');
97
+ list.filter(btn.dataset.filter);
98
+ });
99
+ });
100
+ </script>
101
+ </k-card>
102
+ </div>
103
+ </div>
104
+
105
+ <h2 id="jsRef"><a href="#jsRef" class="no-link">JavaScript Reference</a></h2>
106
+
107
+ <h3 id="constructor"><a href="#constructor" class="no-link">Constructor</a></h3>
108
+ <h6>Extends <a href="./shadow-component.html">ShadowComponent</a></h6>
109
+ <h5><code>new FilterList()</code></h5>
110
+ <h5><code>new FilterItem()</code></h5>
111
+
112
+ <h3 id="requirements"><a href="#requirements" class="no-link">Requirements</a></h3>
113
+ <ul>
114
+ <li><a href="./shadow-component.html">ShadowComponent</a></li>
115
+ </ul>
116
+
117
+ <h3 id="filterItem"><a href="#filterItem" class="no-link">FilterItem Attributes</a></h3>
118
+ <h5><code>filter-keywords<i>: string</i></code></h5>
119
+ <p>A string of keywords used to match against the search term. The <code>filter()</code> method checks if this value includes the lowercased search term as a substring.</p>
120
+
121
+ <h3 id="methods"><a href="#methods" class="no-link">Methods</a></h3>
122
+ <h5><code>filter(term<i>: string</i>)<i>: void</i></code></h5>
123
+ <p>Shows or hides each child <code>k-filter-item</code> based on whether its <code>filter-keywords</code> attribute includes <code>term</code> (case-insensitive). Passing an empty string reveals all items.</p>
124
+
125
+ </main>
126
+ <div style="height:33vh"></div>
127
+
128
+ <script type="module" src="../src/components/Import.js"></script>
129
+ <script type="module" src="../src/components/FilterList.js"></script>
130
+ <script type="module" src="../src/components/FilterItem.js"></script>
131
+ <script type="module" src="../src/components/Accordion.js"></script>
132
+ <script type="module" src="../src/components/Card.js"></script>
133
+
134
+ </body>
135
+ </html>
@@ -102,6 +102,7 @@
102
102
  <li><a href="./tableSorting.html">Sorting</a></li>
103
103
  <li><a href="./tableFieldSortHide.html">Field Sorting and Hiding</a></li>
104
104
  <li><a href="./tableFetchRecords.html">Fetching Records</a></li>
105
+ <li><a href="./tableServerSync.html">Server Sync</a></li>
105
106
  </ul>
106
107
 
107
108
  <br />
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" ><path fill="currentColor" d="M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z"/></svg>
package/docs/index.html CHANGED
@@ -83,6 +83,12 @@
83
83
  <p class="tc-muted">Action menus with keyboard navigation and customizable positioning.</p>
84
84
  </a>
85
85
  </div>
86
+ <div class="span-12 t-span-6 d-span-4 px">
87
+ <a href="./components/filter-list.html" class="card mb no-link d-b">
88
+ <h3 class="tc-primary">Filter List</h3>
89
+ <p class="tc-muted">Show or hide items based on a search term matching their filter keywords.</p>
90
+ </a>
91
+ </div>
86
92
  <div class="span-12 t-span-6 d-span-4 px">
87
93
  <a href="./components/focus-capture.html" class="card mb no-link d-b">
88
94
  <h3 class="tc-primary">FocusCapture</h3>
@@ -15,6 +15,67 @@
15
15
  <img src="../media/icon32.png" alt="Kempo UI Icon" class="pr" />
16
16
  Kempo UI
17
17
  </a>
18
+ <div style="position:relative;margin:0 .25rem;align-self:center;" theme="light">
19
+ <input id="navSearchInput" type="search" placeholder="Search docs..." autocomplete="off" style="width:11rem;" />
20
+ <div id="navSearchDropdown" hidden theme="light" style="position:absolute;top:calc(100% + 4px);left:0;width:min(480px,90vw);max-height:70vh;background:var(--c_bg);border:1px solid var(--c_border);border-radius:var(--radius);overflow:auto;z-index:9999;box-shadow:0 4px 12px rgba(0,0,0,.3);">
21
+ <k-filter-list id="navSearchList" style="display:block;padding:.25rem 0;">
22
+ <k-filter-item filter-keywords="accordion components"><a href="../components/accordion.html">Accordion<br><small>Component</small></a></k-filter-item>
23
+ <k-filter-item filter-keywords="card components"><a href="../components/card.html">Card<br><small>Component</small></a></k-filter-item>
24
+ <k-filter-item filter-keywords="color picker colorpicker components"><a href="../components/color-picker.html">Color Picker<br><small>Component</small></a></k-filter-item>
25
+ <k-filter-item filter-keywords="content slider components"><a href="../components/content-slider.html">Content Slider<br><small>Component</small></a></k-filter-item>
26
+ <k-filter-item filter-keywords="dialog modal popup components"><a href="../components/dialog.html">Dialog<br><small>Component</small></a></k-filter-item>
27
+ <k-filter-item filter-keywords="dropdown select components"><a href="../components/dropdown.html">Dropdown<br><small>Component</small></a></k-filter-item>
28
+ <k-filter-item filter-keywords="filter list filterlist components"><a href="../components/filter-list.html">Filter List<br><small>Component</small></a></k-filter-item>
29
+ <k-filter-item filter-keywords="focus capture focuscapture components"><a href="../components/focus-capture.html">Focus Capture<br><small>Component</small></a></k-filter-item>
30
+ <k-filter-item filter-keywords="html editor components"><a href="../components/html-editor.html">HTML Editor<br><small>Component</small></a></k-filter-item>
31
+ <k-filter-item filter-keywords="icon components"><a href="../components/icon.html">Icon<br><small>Component</small></a></k-filter-item>
32
+ <k-filter-item filter-keywords="import components"><a href="../components/import.html">Import<br><small>Component</small></a></k-filter-item>
33
+ <k-filter-item filter-keywords="photo viewer photoviewer components"><a href="../components/photo-viewer.html">Photo Viewer<br><small>Component</small></a></k-filter-item>
34
+ <k-filter-item filter-keywords="resize components"><a href="../components/resize.html">Resize<br><small>Component</small></a></k-filter-item>
35
+ <k-filter-item filter-keywords="show more showmore components"><a href="../components/show-more.html">Show More<br><small>Component</small></a></k-filter-item>
36
+ <k-filter-item filter-keywords="side menu sidemenu components"><a href="../components/side-menu.html">Side Menu<br><small>Component</small></a></k-filter-item>
37
+ <k-filter-item filter-keywords="side panel sidepanel components"><a href="../components/side-panel.html">Side Panel<br><small>Component</small></a></k-filter-item>
38
+ <k-filter-item filter-keywords="sortable drag drop sort components"><a href="../components/sortable.html">Sortable<br><small>Component</small></a></k-filter-item>
39
+ <k-filter-item filter-keywords="spinner loading components"><a href="../components/spinner.html">Spinner<br><small>Component</small></a></k-filter-item>
40
+ <k-filter-item filter-keywords="split pane components"><a href="../components/split.html">Split<br><small>Component</small></a></k-filter-item>
41
+ <k-filter-item filter-keywords="table data grid components"><a href="../components/table.html">Table<br><small>Component</small></a></k-filter-item>
42
+ <k-filter-item filter-keywords="table controls advanced example"><a href="../components/tableControls.html">Table — Controls<br><small>Advanced Example</small></a></k-filter-item>
43
+ <k-filter-item filter-keywords="table custom fields advanced example"><a href="../components/tableCustomFields.html">Table — Custom Fields<br><small>Advanced Example</small></a></k-filter-item>
44
+ <k-filter-item filter-keywords="table fetch records advanced example"><a href="../components/tableFetchRecords.html">Table — Fetch Records<br><small>Advanced Example</small></a></k-filter-item>
45
+ <k-filter-item filter-keywords="table field sort hide advanced example"><a href="../components/tableFieldSortHide.html">Table — Field Sort &amp; Hide<br><small>Advanced Example</small></a></k-filter-item>
46
+ <k-filter-item filter-keywords="table pagination advanced example"><a href="../components/tablePagination.html">Table — Pagination<br><small>Advanced Example</small></a></k-filter-item>
47
+ <k-filter-item filter-keywords="table record editing advanced example"><a href="../components/tableRecordEditing.html">Table — Record Editing<br><small>Advanced Example</small></a></k-filter-item>
48
+ <k-filter-item filter-keywords="table record filtering advanced example"><a href="../components/tableRecordFiltering.html">Table — Record Filtering<br><small>Advanced Example</small></a></k-filter-item>
49
+ <k-filter-item filter-keywords="table record hiding advanced example"><a href="../components/tableRecordHiding.html">Table — Record Hiding<br><small>Advanced Example</small></a></k-filter-item>
50
+ <k-filter-item filter-keywords="table record searching advanced example"><a href="../components/tableRecordSearching.html">Table — Record Searching<br><small>Advanced Example</small></a></k-filter-item>
51
+ <k-filter-item filter-keywords="table record selection advanced example"><a href="../components/tableRecordSelection.html">Table — Record Selection<br><small>Advanced Example</small></a></k-filter-item>
52
+ <k-filter-item filter-keywords="table row controls advanced example"><a href="../components/tableRowControls.html">Table — Row Controls<br><small>Advanced Example</small></a></k-filter-item>
53
+ <k-filter-item filter-keywords="table server sync advanced example"><a href="../components/tableServerSync.html">Table — Server Sync<br><small>Advanced Example</small></a></k-filter-item>
54
+ <k-filter-item filter-keywords="table sorting advanced example"><a href="../components/tableSorting.html">Table — Sorting<br><small>Advanced Example</small></a></k-filter-item>
55
+ <k-filter-item filter-keywords="tabs tab panel components"><a href="../components/tabs.html">Tabs<br><small>Component</small></a></k-filter-item>
56
+ <k-filter-item filter-keywords="tags tag input components"><a href="../components/tags.html">Tags<br><small>Component</small></a></k-filter-item>
57
+ <k-filter-item filter-keywords="toast notification alert components"><a href="../components/toast.html">Toast<br><small>Component</small></a></k-filter-item>
58
+ <k-filter-item filter-keywords="theme switcher dark light components"><a href="../components/theme-switcher.html">Theme Switcher<br><small>Component</small></a></k-filter-item>
59
+ <k-filter-item filter-keywords="timestamp date time components"><a href="../components/timestamp.html">Timestamp<br><small>Component</small></a></k-filter-item>
60
+ <k-filter-item filter-keywords="toggle switch checkbox components"><a href="../components/toggle.html">Toggle<br><small>Component</small></a></k-filter-item>
61
+ <k-filter-item filter-keywords="tree treeview components"><a href="../components/tree.html">Tree<br><small>Component</small></a></k-filter-item>
62
+ <k-filter-item filter-keywords="shadow component base"><a href="../components/shadow-component.html">Shadow Component<br><small>Base Component</small></a></k-filter-item>
63
+ <k-filter-item filter-keywords="light component base"><a href="../components/light-component.html">Light Component<br><small>Base Component</small></a></k-filter-item>
64
+ <k-filter-item filter-keywords="hybrid component base"><a href="../components/hybrid-component.html">Hybrid Component<br><small>Base Component</small></a></k-filter-item>
65
+ <k-filter-item filter-keywords="cookie utils utility"><a href="../utils/cookie.html">cookie<br><small>Utility</small></a></k-filter-item>
66
+ <k-filter-item filter-keywords="context utils utility"><a href="../utils/context.html">context<br><small>Utility</small></a></k-filter-item>
67
+ <k-filter-item filter-keywords="debounce utils utility"><a href="../utils/debounce.html">debounce<br><small>Utility</small></a></k-filter-item>
68
+ <k-filter-item filter-keywords="drag utils utility"><a href="../utils/drag.html">drag<br><small>Utility</small></a></k-filter-item>
69
+ <k-filter-item filter-keywords="formattimestamp timestamp format date time utils utility"><a href="../utils/formatTimestamp.html">formatTimestamp<br><small>Utility</small></a></k-filter-item>
70
+ <k-filter-item filter-keywords="object utils utility"><a href="../utils/object.html">object<br><small>Utility</small></a></k-filter-item>
71
+ <k-filter-item filter-keywords="propconverters prop converters utils utility"><a href="../utils/propConverters.html">propConverters<br><small>Utility</small></a></k-filter-item>
72
+ <k-filter-item filter-keywords="string utils utility"><a href="../utils/string.html">string<br><small>Utility</small></a></k-filter-item>
73
+ <k-filter-item filter-keywords="theme utils utility"><a href="../utils/theme.html">theme<br><small>Utility</small></a></k-filter-item>
74
+ <k-filter-item filter-keywords="type utils utility"><a href="../utils/type.html">type<br><small>Utility</small></a></k-filter-item>
75
+ <k-filter-item filter-keywords="wait async utils utility"><a href="../utils/wait.html">wait<br><small>Utility</small></a></k-filter-item>
76
+ </k-filter-list>
77
+ </div>
78
+ </div>
18
79
  <div class="flex"></div>
19
80
  <a href="https://github.com/dustinpoissant/kempo-ui?tab=License-1-ov-file#creative-commons-attribution-noncommercial-sharealike-20"><k-icon name="license"></k-icont></a>
20
81
  <a href="https://github.com/dustinpoissant/kempo-ui"><k-icon name="github-mark"></k-icont></a>
@@ -37,6 +98,7 @@
37
98
  <a href="../components/content-slider.html">Content Slider</a>
38
99
  <a href="../components/dialog.html">Dialog</a>
39
100
  <a href="../components/dropdown.html">Dropdown</a>
101
+ <a href="../components/filter-list.html">Filter List</a>
40
102
  <a href="../components/focus-capture.html">FocusCapture</a>
41
103
  <a href="../components/html-editor.html">HTML Editor</a>
42
104
  <a href="../components/icon.html">Icon</a>
@@ -82,6 +144,13 @@
82
144
  </div>
83
145
  </menu>
84
146
  </k-side-menu>
147
+ <style>
148
+ #navSearchList a{display:block;padding:.25rem .5rem;border-radius:var(--radius);text-decoration:none;color:var(--tc);}
149
+ #navSearchList a:hover{background:rgba(128,128,128,.15);}
150
+ #navSearchList a small{display:block;color:var(--tc_muted);}
151
+ </style>
152
+ <script src="../src/components/FilterList.js" type="module"></script>
153
+ <script src="../src/components/FilterItem.js" type="module"></script>
85
154
  <script src="../src/components/SideMenu.js" type="module"></script>
86
155
  <script src="../src/components/Icon.js" type="module"></script>
87
156
  <script src="../src/components/ThemeSwitcher.js" type="module"></script>
package/docs/nav.inc.html CHANGED
@@ -15,6 +15,67 @@
15
15
  <img src="./media/icon32.png" alt="Kempo UI Icon" class="pr" />
16
16
  Kempo UI
17
17
  </a>
18
+ <div style="position:relative;margin:0 .25rem;align-self:center;" theme="light">
19
+ <input id="navSearchInput" type="search" placeholder="Search docs..." autocomplete="off" style="width:11rem;" />
20
+ <div id="navSearchDropdown" hidden theme="light" style="position:absolute;top:calc(100% + 4px);left:0;width:min(480px,90vw);max-height:70vh;background:var(--c_bg);border:1px solid var(--c_border);border-radius:var(--radius);overflow:auto;z-index:9999;box-shadow:0 4px 12px rgba(0,0,0,.3);">
21
+ <k-filter-list id="navSearchList" style="display:block;padding:.25rem 0;">
22
+ <k-filter-item filter-keywords="accordion components"><a href="./components/accordion.html">Accordion<br><small>Component</small></a></k-filter-item>
23
+ <k-filter-item filter-keywords="card components"><a href="./components/card.html">Card<br><small>Component</small></a></k-filter-item>
24
+ <k-filter-item filter-keywords="color picker colorpicker components"><a href="./components/color-picker.html">Color Picker<br><small>Component</small></a></k-filter-item>
25
+ <k-filter-item filter-keywords="content slider components"><a href="./components/content-slider.html">Content Slider<br><small>Component</small></a></k-filter-item>
26
+ <k-filter-item filter-keywords="dialog modal popup components"><a href="./components/dialog.html">Dialog<br><small>Component</small></a></k-filter-item>
27
+ <k-filter-item filter-keywords="dropdown select components"><a href="./components/dropdown.html">Dropdown<br><small>Component</small></a></k-filter-item>
28
+ <k-filter-item filter-keywords="filter list filterlist components"><a href="./components/filter-list.html">Filter List<br><small>Component</small></a></k-filter-item>
29
+ <k-filter-item filter-keywords="focus capture focuscapture components"><a href="./components/focus-capture.html">Focus Capture<br><small>Component</small></a></k-filter-item>
30
+ <k-filter-item filter-keywords="html editor components"><a href="./components/html-editor.html">HTML Editor<br><small>Component</small></a></k-filter-item>
31
+ <k-filter-item filter-keywords="icon components"><a href="./components/icon.html">Icon<br><small>Component</small></a></k-filter-item>
32
+ <k-filter-item filter-keywords="import components"><a href="./components/import.html">Import<br><small>Component</small></a></k-filter-item>
33
+ <k-filter-item filter-keywords="photo viewer photoviewer components"><a href="./components/photo-viewer.html">Photo Viewer<br><small>Component</small></a></k-filter-item>
34
+ <k-filter-item filter-keywords="resize components"><a href="./components/resize.html">Resize<br><small>Component</small></a></k-filter-item>
35
+ <k-filter-item filter-keywords="show more showmore components"><a href="./components/show-more.html">Show More<br><small>Component</small></a></k-filter-item>
36
+ <k-filter-item filter-keywords="side menu sidemenu components"><a href="./components/side-menu.html">Side Menu<br><small>Component</small></a></k-filter-item>
37
+ <k-filter-item filter-keywords="side panel sidepanel components"><a href="./components/side-panel.html">Side Panel<br><small>Component</small></a></k-filter-item>
38
+ <k-filter-item filter-keywords="sortable drag drop sort components"><a href="./components/sortable.html">Sortable<br><small>Component</small></a></k-filter-item>
39
+ <k-filter-item filter-keywords="spinner loading components"><a href="./components/spinner.html">Spinner<br><small>Component</small></a></k-filter-item>
40
+ <k-filter-item filter-keywords="split pane components"><a href="./components/split.html">Split<br><small>Component</small></a></k-filter-item>
41
+ <k-filter-item filter-keywords="table data grid components"><a href="./components/table.html">Table<br><small>Component</small></a></k-filter-item>
42
+ <k-filter-item filter-keywords="table controls advanced example"><a href="./components/tableControls.html">Table — Controls<br><small>Advanced Example</small></a></k-filter-item>
43
+ <k-filter-item filter-keywords="table custom fields advanced example"><a href="./components/tableCustomFields.html">Table — Custom Fields<br><small>Advanced Example</small></a></k-filter-item>
44
+ <k-filter-item filter-keywords="table fetch records advanced example"><a href="./components/tableFetchRecords.html">Table — Fetch Records<br><small>Advanced Example</small></a></k-filter-item>
45
+ <k-filter-item filter-keywords="table field sort hide advanced example"><a href="./components/tableFieldSortHide.html">Table — Field Sort &amp; Hide<br><small>Advanced Example</small></a></k-filter-item>
46
+ <k-filter-item filter-keywords="table pagination advanced example"><a href="./components/tablePagination.html">Table — Pagination<br><small>Advanced Example</small></a></k-filter-item>
47
+ <k-filter-item filter-keywords="table record editing advanced example"><a href="./components/tableRecordEditing.html">Table — Record Editing<br><small>Advanced Example</small></a></k-filter-item>
48
+ <k-filter-item filter-keywords="table record filtering advanced example"><a href="./components/tableRecordFiltering.html">Table — Record Filtering<br><small>Advanced Example</small></a></k-filter-item>
49
+ <k-filter-item filter-keywords="table record hiding advanced example"><a href="./components/tableRecordHiding.html">Table — Record Hiding<br><small>Advanced Example</small></a></k-filter-item>
50
+ <k-filter-item filter-keywords="table record searching advanced example"><a href="./components/tableRecordSearching.html">Table — Record Searching<br><small>Advanced Example</small></a></k-filter-item>
51
+ <k-filter-item filter-keywords="table record selection advanced example"><a href="./components/tableRecordSelection.html">Table — Record Selection<br><small>Advanced Example</small></a></k-filter-item>
52
+ <k-filter-item filter-keywords="table row controls advanced example"><a href="./components/tableRowControls.html">Table — Row Controls<br><small>Advanced Example</small></a></k-filter-item>
53
+ <k-filter-item filter-keywords="table server sync advanced example"><a href="./components/tableServerSync.html">Table — Server Sync<br><small>Advanced Example</small></a></k-filter-item>
54
+ <k-filter-item filter-keywords="table sorting advanced example"><a href="./components/tableSorting.html">Table — Sorting<br><small>Advanced Example</small></a></k-filter-item>
55
+ <k-filter-item filter-keywords="tabs tab panel components"><a href="./components/tabs.html">Tabs<br><small>Component</small></a></k-filter-item>
56
+ <k-filter-item filter-keywords="tags tag input components"><a href="./components/tags.html">Tags<br><small>Component</small></a></k-filter-item>
57
+ <k-filter-item filter-keywords="toast notification alert components"><a href="./components/toast.html">Toast<br><small>Component</small></a></k-filter-item>
58
+ <k-filter-item filter-keywords="theme switcher dark light components"><a href="./components/theme-switcher.html">Theme Switcher<br><small>Component</small></a></k-filter-item>
59
+ <k-filter-item filter-keywords="timestamp date time components"><a href="./components/timestamp.html">Timestamp<br><small>Component</small></a></k-filter-item>
60
+ <k-filter-item filter-keywords="toggle switch checkbox components"><a href="./components/toggle.html">Toggle<br><small>Component</small></a></k-filter-item>
61
+ <k-filter-item filter-keywords="tree treeview components"><a href="./components/tree.html">Tree<br><small>Component</small></a></k-filter-item>
62
+ <k-filter-item filter-keywords="shadow component base"><a href="./components/shadow-component.html">Shadow Component<br><small>Base Component</small></a></k-filter-item>
63
+ <k-filter-item filter-keywords="light component base"><a href="./components/light-component.html">Light Component<br><small>Base Component</small></a></k-filter-item>
64
+ <k-filter-item filter-keywords="hybrid component base"><a href="./components/hybrid-component.html">Hybrid Component<br><small>Base Component</small></a></k-filter-item>
65
+ <k-filter-item filter-keywords="cookie utils utility"><a href="./utils/cookie.html">cookie<br><small>Utility</small></a></k-filter-item>
66
+ <k-filter-item filter-keywords="context utils utility"><a href="./utils/context.html">context<br><small>Utility</small></a></k-filter-item>
67
+ <k-filter-item filter-keywords="debounce utils utility"><a href="./utils/debounce.html">debounce<br><small>Utility</small></a></k-filter-item>
68
+ <k-filter-item filter-keywords="drag utils utility"><a href="./utils/drag.html">drag<br><small>Utility</small></a></k-filter-item>
69
+ <k-filter-item filter-keywords="formattimestamp timestamp format date time utils utility"><a href="./utils/formatTimestamp.html">formatTimestamp<br><small>Utility</small></a></k-filter-item>
70
+ <k-filter-item filter-keywords="object utils utility"><a href="./utils/object.html">object<br><small>Utility</small></a></k-filter-item>
71
+ <k-filter-item filter-keywords="propconverters prop converters utils utility"><a href="./utils/propConverters.html">propConverters<br><small>Utility</small></a></k-filter-item>
72
+ <k-filter-item filter-keywords="string utils utility"><a href="./utils/string.html">string<br><small>Utility</small></a></k-filter-item>
73
+ <k-filter-item filter-keywords="theme utils utility"><a href="./utils/theme.html">theme<br><small>Utility</small></a></k-filter-item>
74
+ <k-filter-item filter-keywords="type utils utility"><a href="./utils/type.html">type<br><small>Utility</small></a></k-filter-item>
75
+ <k-filter-item filter-keywords="wait async utils utility"><a href="./utils/wait.html">wait<br><small>Utility</small></a></k-filter-item>
76
+ </k-filter-list>
77
+ </div>
78
+ </div>
18
79
  <div class="flex"></div>
19
80
  <a href="https://github.com/dustinpoissant/kempo-ui?tab=License-1-ov-file#creative-commons-attribution-noncommercial-sharealike-20" target="_blank"><k-icon name="license"></k-icont></a>
20
81
  <a href="https://github.com/dustinpoissant/kempo-ui" target="_blank"><k-icon name="github-mark"></k-icont></a>
@@ -37,6 +98,7 @@
37
98
  <a href="./components/color-picker.html">ColorPicker</a>
38
99
  <a href="./components/content-slider.html">Content Slider</a>
39
100
  <a href="./components/dialog.html">Dialog</a>
101
+ <a href="./components/filter-list.html">Filter List</a>
40
102
  <a href="./components/focus-capture.html">FocusCapture</a>
41
103
  <a href="./components/html-editor.html">HTML Editor</a>
42
104
  <a href="./components/icon.html">Icon</a>
@@ -81,6 +143,13 @@
81
143
  </div>
82
144
  </menu>
83
145
  </k-side-menu>
146
+ <style>
147
+ #navSearchList a{display:block;padding:.25rem .5rem;border-radius:var(--radius);text-decoration:none;color:var(--tc);}
148
+ #navSearchList a:hover{background:rgba(128,128,128,.15);}
149
+ #navSearchList a small{display:block;color:var(--tc_muted);}
150
+ </style>
151
+ <script src="./src/components/FilterList.js" type="module"></script>
152
+ <script src="./src/components/FilterItem.js" type="module"></script>
84
153
  <script src="./src/components/SideMenu.js" type="module"></script>
85
154
  <script src="./src/components/Icon.js" type="module"></script>
86
155
  <script src="./src/components/ThemeSwitcher.js" type="module"></script>
package/docs/nav.inc.js CHANGED
@@ -1,16 +1,66 @@
1
+ import debounce from './src/utils/debounce.js';
2
+
1
3
  document.getElementById('toggleNavSideMenu').addEventListener('click', async () => {
2
4
  await window.customElements.whenDefined('k-side-menu');
3
5
  document.getElementById('navSideMenu').toggle();
4
6
  });
7
+
5
8
  document.addEventListener('click', function(e) {
6
- if (e.target.matches('a[href^="#"]')) {
9
+ if(e.target.matches('a[href^="#"]')) {
7
10
  e.preventDefault();
8
11
  const targetId = e.target.getAttribute('href').replace('#', '');
9
12
  const target = document.getElementById(targetId);
10
- if (target) {
13
+ if(target) {
11
14
  target.scrollIntoView({ behavior: 'smooth' });
12
15
  const url = window.location.pathname + window.location.search + '#' + targetId;
13
16
  history.replaceState(null, '', url);
14
17
  }
15
18
  }
19
+ });
20
+
21
+ /*
22
+ Nav Search
23
+ */
24
+ const searchInput = document.getElementById('navSearchInput');
25
+ const searchDropdown = document.getElementById('navSearchDropdown');
26
+ let navSearchList = null;
27
+
28
+ const openSearch = async () => {
29
+ await customElements.whenDefined('k-filter-list');
30
+ if(!navSearchList) navSearchList = document.getElementById('navSearchList');
31
+ navSearchList.filter(searchInput.value);
32
+ searchDropdown.hidden = false;
33
+ };
34
+
35
+ const closeSearch = () => {
36
+ searchDropdown.hidden = true;
37
+ };
38
+
39
+ searchInput.addEventListener('focus', openSearch);
40
+
41
+ searchInput.addEventListener('input', debounce(e => {
42
+ navSearchList?.filter(e.target.value);
43
+ }, 150));
44
+
45
+ searchInput.addEventListener('keydown', e => {
46
+ if(e.key === 'Escape') {
47
+ searchInput.value = '';
48
+ searchInput.blur();
49
+ closeSearch();
50
+ }
51
+ });
52
+
53
+ searchInput.addEventListener('blur', () => {
54
+ setTimeout(closeSearch, 150);
55
+ });
56
+
57
+ document.addEventListener('keydown', e => {
58
+ const active = document.activeElement;
59
+ const tag = active?.tagName;
60
+ if(tag === 'INPUT' || tag === 'TEXTAREA' || active?.isContentEditable) return;
61
+ if(e.metaKey || e.ctrlKey || e.altKey) return;
62
+ if(e.key.length === 1) {
63
+ searchInput.focus();
64
+ // Don't preventDefault — let the keystroke land in the input naturally
65
+ }
16
66
  });
@@ -0,0 +1,8 @@
1
+ import ShadowComponent from"./ShadowComponent.js";import{html,css}from"../lit-all.min.js";export default class FilterItem extends ShadowComponent{render(){return html`<slot></slot>`}static styles=css`
2
+ :host {
3
+ display: contents;
4
+ }
5
+ :host([hidden]) {
6
+ display: none !important;
7
+ }
8
+ `}customElements.define("k-filter-item",FilterItem);
@@ -0,0 +1,5 @@
1
+ import ShadowComponent from"./ShadowComponent.js";import{html,css}from"../lit-all.min.js";export default class FilterList extends ShadowComponent{render(){return html`<slot></slot>`}filter(t){const e=t.toLowerCase().split(/\s+/).filter(t=>t.length>0);this.querySelectorAll("k-filter-item").forEach(t=>{const s=(t.getAttribute("filter-keywords")||"").toLowerCase();t.hidden=e.length>0&&!e.every(t=>s.includes(t))})}static styles=css`
2
+ :host {
3
+ display: block;
4
+ }
5
+ `}customElements.define("k-filter-list",FilterList);
@@ -34,11 +34,11 @@ import{html,css,unsafeStatic,literal}from"../lit-all.min.js";import ShadowCompon
34
34
  </select>
35
35
  `;default:return html`<input type="text" .value=${s} />`}}renderDisplayCell(e,t,s,i,r){return i?i(e,this):r?r(s):s}renderBeforeControlsTemplate(){const e=[];return this.querySelectorAll('[slot="before"]').forEach(t=>{const s=t.tagName.toLowerCase(),i=document.createElement(s);Array.from(t.attributes).forEach(e=>{"slot"!==e.name&&i.setAttribute(e.name,e.value)}),t.innerHTML&&(i.innerHTML=t.innerHTML),e.push(i)}),html`
36
36
  <td class="cell controls controls-before">
37
- ${e}
37
+ <div>${e}</div>
38
38
  </td>
39
39
  `}renderAfterControlsTemplate(){const e=[];return this.querySelectorAll('[slot="after"]').forEach(t=>{const s=t.tagName.toLowerCase(),i=document.createElement(s);Array.from(t.attributes).forEach(e=>{"slot"!==e.name&&i.setAttribute(e.name,e.value)}),t.innerHTML&&(i.innerHTML=t.innerHTML),e.push(i)}),html`
40
40
  <td class="cell controls controls-after">
41
- ${e}
41
+ <div>${e}</div>
42
42
  </td>
43
43
  `}hasBeforeControls(){return!!this.querySelector('[slot="before"]')}hasAfterControls(){return!!this.querySelector('[slot="after"]')}hasTopControls(){return!!this.querySelector('[slot="top"]')}hasBottomControls(){return!!this.querySelector(":scope > :not([slot])")}editRecord(e){e[editing]=!0;const t=this.shadowRoot.querySelector(`.record[data-index="${e[index]}"]`);t&&(t.classList.add("editing"),t.setAttribute("editing","true"),t.querySelectorAll(".cell[data-field]").forEach(t=>{const s=t.dataset.field,i=this.fields.find(e=>e.name===s);if(i){const r=e[s]||"";if(t.innerHTML="",i.calculator){const s=document.createElement("input");s.disabled=!0,s.value=i.calculator(e,this),t.appendChild(s)}else if(i.editor)t.appendChild(i.editor(r));else{const e=i.type||typeof r,s=Table.editors[e]||Table.editors.string;t.appendChild(s(r))}}})),this.dispatchEvent(new CustomEvent("editingChange",{detail:{record:e,editing:!0},bubbles:!0}))}saveEditedRecord(e){const t=this.shadowRoot.querySelector(`.record[data-index="${e[index]}"]`),s={};t&&t.querySelectorAll(".cell[data-field]").forEach(e=>{const t=e.dataset.field,i=this.fields.find(e=>e.name===t);if(i&&!i.calculator){const i=e.querySelector("input, select");i&&(s[t]=i.value)}});const i=()=>{Object.assign(e,s),e[editing]=!1,t&&(t.classList.remove("editing"),t.removeAttribute("editing"),t.querySelectorAll(".cell[data-field]").forEach(t=>{const s=t.dataset.field,i=this.fields.find(e=>e.name===s);if(i){const r=e[s]||"";i.calculator?t.textContent=i.calculator(e,this):i.formatter?t.innerHTML=i.formatter(r):t.textContent=r}})),this.dispatchEvent(new CustomEvent("editingChange",{detail:{record:e,editing:!1},bubbles:!0}))};if(Object.keys(s).some(t=>String(e[t]??"")!==s[t]))if(this.requestEdit){t?.classList.add("pending");const r=()=>{t?.classList.remove("pending"),i()},o=()=>{t?.classList.remove("pending")};this.dispatchEvent(new CustomEvent("requestSave",{detail:{record:e,newData:s,approve:r,reject:o},bubbles:!0}))}else i();else this.cancelEditedRecord(e)}cancelEditedRecord(e){e[editing]=!1;const t=this.shadowRoot.querySelector(`.record[data-index="${e[index]}"]`);t&&(t.classList.remove("editing"),t.removeAttribute("editing"),t.querySelectorAll(".cell[data-field]").forEach(t=>{const s=t.dataset.field,i=this.fields.find(e=>e.name===s);if(i){const r=e[s]||"";i.calculator?t.textContent=i.calculator(e,this):i.formatter?t.innerHTML=i.formatter(r):t.textContent=r}})),this.dispatchEvent(new CustomEvent("editingChange",{detail:{record:e,editing:!1},bubbles:!0}))}recordIsEditing(e){return e[editing]}getCurrentPage(){return this.currentPage}getTotalPages(){return Math.ceil(this.getDisplayedRecords().length/this.pageSize)}setPage(e){e<1||e>this.getTotalPages()||(this.currentPage=e,this.requestUpdate(),this.dispatchEvent(new CustomEvent("pageChange",{bubbles:!0})))}firstPage(){1!==this.currentPage&&this.setPage(1)}nextPage(){this.currentPage<this.getTotalPages()&&this.setPage(this.currentPage+1)}prevPage(){this.currentPage>1&&this.setPage(this.currentPage-1)}lastPage(){this.currentPage!==this.getTotalPages()&&this.setPage(this.getTotalPages())}setPageSize(e){this.pageSize=e,this.currentPage=1,this.requestUpdate(),this.dispatchEvent(new CustomEvent("pageSizeChange",{bubbles:!0}))}getPageSize(){return this.pageSize}getPageSizeOptions(){return this.pageSizeOptions}getFieldLabel(e){const t=this.fields.find(t=>t.name===e);return t?t.label:toTitleCase(e)}setPageSizeOptions(e){this.pageSizeOptions=e,this.requestUpdate()}setData({records:e=!1,fields:t=!1,pageSize:s=!1,pageSizeOptions:i=!1,currentPage:r=!1,enableSelection:o}={}){let n=!1,l=this.getTotalPages(),a=this.currentPage;e&&(this.records=e.map(e=>({...e})),this.records.forEach((e,t)=>{e[index]=t,e[selected]=!1,e[hidden]=!1,e[editing]=!1}),this.fields=t||Table.extractFieldsFromRecords(this.records),n=!0),s&&(this.pageSize=s,n=!0),i&&(this.pageSizeOptions=i),r&&(this.currentPage=r,n=!0),void 0!==o&&(this.enableSelection=o,n=!0),n&&this.requestUpdate();const d=this.getTotalPages();d!==l&&this.dispatchEvent(new CustomEvent("pageCountChanged",{detail:{totalPages:this.getTotalPages()},bubbles:!0})),a>d&&this.setPage(d)}setRecords(e,t){let s=this.getTotalPages(),i=this.currentPage;this.records=e.map(e=>({...e})),this.records.forEach((e,t)=>{e[index]=t,e[selected]=!1,e[hidden]=!1,e[editing]=!1}),this.fields=t||Table.extractFieldsFromRecords(this.records),this.requestUpdate(),this.dispatchEvent(new CustomEvent("recordsSet",{detail:{records:e},bubbles:!0}));const r=this.getTotalPages();r!==s&&this.dispatchEvent(new CustomEvent("pageCountChanged",{detail:{totalPages:this.getTotalPages()},bubbles:!0})),i>r&&this.setPage(r)}setupFetchRecords(e,t){const s=this.records.length,i=this.getTotalPages();if(s<e){this.records.length=e,this.records.fill(null,s),this.requestUpdate();const t=this.getTotalPages();t!==i&&setTimeout(()=>{this.dispatchEvent(new CustomEvent("pageCountChanged",{detail:{totalPages:t},bubbles:!0}))},0)}this.addEventListener("fetchRecords",async e=>{if(this.fetchPending)return;this.fetchPending=!0;const{start:s,count:i}=e.detail,r=await t(s,i);r.forEach((e,t)=>{e[index]=s+t,void 0===e[selected]&&(e[selected]=!1),void 0===e[hidden]&&(e[hidden]=!1),void 0===e[editing]&&(e[editing]=!1)}),this.records.splice(s,r.length,...r),this.fetchPending=!1,this.requestUpdate()})}addRecord(e){e[selected]=!1,e[hidden]=!1,e[index]=this.records.length,this.records.push(e),this.requestUpdate(),this.dispatchEvent(new CustomEvent("recordAdded",{detail:{record:e},bubbles:!0}))}updateRecord(e,t){let s=!1,i=this.records.find(t=>t===e);if(i||void 0===e[index]||(i=this.records[e[index]]),Object.keys(t).forEach(e=>{i.hasOwnProperty(e)&&(i[e]=t[e],s=!0)}),s){const e=(this.currentPage-1)*this.pageSize,t=e+this.pageSize;(!this.enablePages||i[index]>=e&&i[index]<t)&&this.requestUpdate()}}deleteRecord(e){let t=this.records.find(t=>t===e);const s=this.getTotalPages();if(t||void 0===e[index]||(t=this.records[e[index]]),!t)return;const i=()=>{const e=this.records.indexOf(t);this.records.splice(e,1),this.records.forEach((e,t)=>{e[index]=t}),this.requestUpdate(),this.dispatchEvent(new CustomEvent("selectionChange",{bubbles:!0})),this.dispatchEvent(new CustomEvent("recordDeleted",{detail:{index:e},bubbles:!0}));const i=this.getTotalPages();this.currentPage>i&&this.setPage(i),i!==s&&this.dispatchEvent(new CustomEvent("pageCountChanged",{detail:{totalPages:i},bubbles:!0}))};if(this.requestDelete){const e=this.shadowRoot.querySelector(`.record[data-index="${t[index]}"]`);e?.classList.add("pending");const s=()=>{e?.classList.remove("pending"),i()},r=()=>{e?.classList.remove("pending")};this.dispatchEvent(new CustomEvent("requestDelete",{detail:{records:[t],approve:s,reject:r},bubbles:!0}))}else i()}deleteSelected(){const e=this.getTotalPages(),t=this.getSelectedRecords().map(e=>this.records.find(t=>t===e)??(void 0!==e[index]?this.records[e[index]]:null)).filter(Boolean);if(!t.length)return;const s=()=>{t.forEach(e=>{const t=this.records.indexOf(e);-1!==t&&this.records.splice(t,1)}),this.records.forEach((e,t)=>{e[index]=t}),this.requestUpdate();const s=this.getTotalPages();this.currentPage>s&&this.setPage(s),s!==e&&this.dispatchEvent(new CustomEvent("pageCountChanged",{detail:{totalPages:s},bubbles:!0})),this.dispatchEvent(new CustomEvent("selectionChange",{bubbles:!0}))};if(this.requestDelete){const e=t.map(e=>this.shadowRoot.querySelector(`.record[data-index="${e[index]}"]`)).filter(Boolean);e.forEach(e=>e.classList.add("pending"));const i=()=>{e.forEach(e=>e.classList.remove("pending")),s()},r=()=>{e.forEach(e=>e.classList.remove("pending"))};this.dispatchEvent(new CustomEvent("requestDelete",{detail:{records:t,approve:i,reject:r},bubbles:!0}))}else s()}getSelectedRecords(){return this.records.filter(e=>e[selected])}selectAllOnPage(){const e=(this.currentPage-1)*this.pageSize,t=Math.min(e+this.pageSize,this.records.length);for(let s=e;s<t;s++)this.records[s][selected]=!0;this.requestUpdate(),setTimeout(()=>{this.dispatchEvent(new CustomEvent("selectionChange",{bubbles:!0}))},0)}deselectAllOnPage(){const e=(this.currentPage-1)*this.pageSize,t=Math.min(e+this.pageSize,this.records.length);for(let s=e;s<t;s++)this.records[s][selected]=!1;this.requestUpdate(),setTimeout(()=>{this.dispatchEvent(new CustomEvent("selectionChange",{bubbles:!0}))},0)}allOnPageSelected(){const e=(this.currentPage-1)*this.pageSize,t=Math.min(e+this.pageSize,this.records.length);for(let s=e;s<t;s++)if(!this.records[s][selected])return!1;return!0}sortBy(e,t=!0){this.sort=this.sort.filter(t=>t.name!==e),this.sort.push({name:e,asc:t}),this.requestUpdate()}hideRecord(e){let t=this.records.find(t=>t===e);t||void 0===e[index]||(t=this.records[e[index]]),t&&(t[hidden]=!0,this.requestUpdate(),this.dispatchEvent(new CustomEvent("recordHidden",{bubbles:!0})))}showRecord(e){let t=this.records.find(t=>t===e);t||void 0===e[index]||(t=this.records[e[index]]),t&&(t[hidden]=!1,this.requestUpdate(),this.dispatchEvent(new CustomEvent("recordShown",{bubbles:!0})))}showAllRecords(){this.records.forEach(e=>{e[hidden]=!1}),this.filters.length&&(this.filters=[],this.dispatchEvent(new CustomEvent("filterRemoved",{bubbles:!0})),this.dispatchEvent(new CustomEvent("filterChange",{bubbles:!0}))),this.requestUpdate(),this.dispatchEvent(new CustomEvent("recordShown",{bubbles:!0})),this.dispatchEvent(new CustomEvent("allRecordsShown",{bubbles:!0}))}addFilter(e,t,s){this.filters.push({field:e,condition:t,value:s}),this.dispatchEvent(new CustomEvent("filterAdded",{bubbles:!0})),this.dispatchEvent(new CustomEvent("filterChange",{bubbles:!0})),this.requestUpdate()}removeFilter(e,t,s,i=!0){const r=this.filters.findIndex(i=>i.field===e&&i.condition===t&&i.value===s);-1!==r&&(this.records.forEach(i=>{this.testFilter(i,e,t,s)||(i[hidden]=!1)}),this.filters.splice(r,1),this.dispatchEvent(new CustomEvent("filterRemoved",{bubbles:!0})),this.dispatchEvent(new CustomEvent("filterChange",{bubbles:!0})),i&&this.requestUpdate())}testFilter(e,t,s,i){let r=e[t],o=i;switch(this.caseSensitiveFilters||"string"!=typeof r||"string"!=typeof i||(r=r.toLowerCase(),o=i.toLowerCase()),s){case"equals":return r===o;case"not-equals":return r!==o;case"contains":return r.includes(o);case"not-contains":return!r.includes(o);case"greater-than":return r>o;case"less-than":return r<o;case"greater-than-or-equal":return r>=o;case"less-than-or-equal":return r<=o;default:return!0}}removeAllFilters(){this.filters.length&&([...this.filters].forEach(({field:e,condition:t,value:s})=>{this.removeFilter(e,t,s,!1)}),this.requestUpdate())}search(e){const t=e.trim().toLowerCase();let s=!1;this.records.forEach(e=>{if(e[hidden])return;let i=!1;this.fields.forEach(({name:s})=>{(e[s]?.toString().toLowerCase()||"").includes(t)&&(i=!0)}),e[hidden]!==!i&&(e[hidden]=!i,s=!0)}),s&&(this.dispatchEvent(new CustomEvent("recordHidden",{bubbles:!0})),this.requestUpdate()),this.dispatchEvent(new CustomEvent("search",{detail:{term:e},bubbles:!0}))}getDisplayedRecords(){this.filters.forEach(({field:e,condition:t,value:s})=>{this.records.forEach(i=>{null!==i&&(this.testFilter(i,e,t,s)||(i[hidden]=!0))})});let e=this.records.filter(e=>null===e||!e[hidden]);return this.sort.forEach(({name:t,asc:s})=>{e.sort((e,i)=>null===e||null===i?0:e[t]<i[t]?s?-1:1:e[t]>i[t]?s?1:-1:0)}),e}getHiddenRecords(){return this.records.filter(e=>e[hidden])}calculateColumnSizes(){const e=Array.from(this.querySelectorAll('[slot="before"]')),t=Array.from(this.querySelectorAll('[slot="after"]')),s={beforeControls:e.reduce((e,t)=>e+(t.maxWidth||40),0),afterControls:t.reduce((e,t)=>e+(t.maxWidth||40),0)},i=[...e,...t].some(e=>void 0===e.maxWidth);return JSON.stringify(this.columnSizes)!==JSON.stringify(s)&&(this.columnSizes=s),i&&setTimeout(()=>this.calculateColumnSizes(),0),this.columnSizes}setFieldHiddenState(e,t){const s=this.fields.find(t=>t.name===e);s&&(s.hidden=t,this.calculateColumnSizes(),this.requestUpdate(),this.dispatchEvent(new CustomEvent("fieldVisibilityChanged",{detail:{field:s},bubbles:!0})),this.dispatchEvent(new CustomEvent(t?"fieldHidden":"fieldShown",{detail:{field:s},bubbles:!0})))}hideField(e){this.setFieldHiddenState(e,!0)}showField(e){this.setFieldHiddenState(e,!1)}reorderFields(e){const t=[];e.forEach(e=>{const s=this.fields.find(t=>t.name===e);s&&t.push(s)}),this.fields=t,this.requestUpdate()}render(){return this.records&&this.fields?(this.calculateColumnSizes(),this.hasTopControls()?this.setAttribute("top-controls","true"):this.removeAttribute("top-controls"),this.hasBottomControls()?this.setAttribute("bottom-controls","true"):this.removeAttribute("bottom-controls"),html`
44
44
  <div id="wrapper">
@@ -154,8 +154,8 @@ import{html,css,unsafeStatic,literal}from"../lit-all.min.js";import ShadowCompon
154
154
  td.controls {
155
155
  padding: 0;
156
156
  }
157
- td.controls-after,
158
- td.controls-before {
157
+ td.controls-after > div,
158
+ td.controls-before > div {
159
159
  display: flex;
160
160
  align-items: center;
161
161
  }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z"/></svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kempo-ui",
3
- "version": "0.0.79",
3
+ "version": "0.0.81",
4
4
  "type": "module",
5
5
  "description": "A Lit based web-component library",
6
6
  "main": "index.js",
@@ -0,0 +1,25 @@
1
+ import ShadowComponent from './ShadowComponent.js';
2
+ import { html, css } from '../lit-all.min.js';
3
+
4
+ export default class FilterItem extends ShadowComponent {
5
+ /*
6
+ Rendering
7
+ */
8
+ render() {
9
+ return html`<slot></slot>`;
10
+ }
11
+
12
+ /*
13
+ Styles
14
+ */
15
+ static styles = css`
16
+ :host {
17
+ display: contents;
18
+ }
19
+ :host([hidden]) {
20
+ display: none !important;
21
+ }
22
+ `;
23
+ }
24
+
25
+ customElements.define('k-filter-item', FilterItem);
@@ -0,0 +1,33 @@
1
+ import ShadowComponent from './ShadowComponent.js';
2
+ import { html, css } from '../lit-all.min.js';
3
+
4
+ export default class FilterList extends ShadowComponent {
5
+ /*
6
+ Rendering
7
+ */
8
+ render() {
9
+ return html`<slot></slot>`;
10
+ }
11
+
12
+ /*
13
+ Public Methods
14
+ */
15
+ filter(term) {
16
+ const words = term.toLowerCase().split(/\s+/).filter(w => w.length > 0);
17
+ this.querySelectorAll('k-filter-item').forEach(item => {
18
+ const keywords = (item.getAttribute('filter-keywords') || '').toLowerCase();
19
+ item.hidden = words.length > 0 && !words.every(w => keywords.includes(w));
20
+ });
21
+ }
22
+
23
+ /*
24
+ Styles
25
+ */
26
+ static styles = css`
27
+ :host {
28
+ display: block;
29
+ }
30
+ `;
31
+ }
32
+
33
+ customElements.define('k-filter-list', FilterList);
@@ -280,7 +280,7 @@ export default class Table extends ShadowComponent {
280
280
  });
281
281
  return html`
282
282
  <td class="cell controls controls-before">
283
- ${controls}
283
+ <div>${controls}</div>
284
284
  </td>
285
285
  `;
286
286
  }
@@ -298,7 +298,7 @@ export default class Table extends ShadowComponent {
298
298
  });
299
299
  return html`
300
300
  <td class="cell controls controls-after">
301
- ${controls}
301
+ <div>${controls}</div>
302
302
  </td>
303
303
  `;
304
304
  }
@@ -1131,8 +1131,8 @@ export default class Table extends ShadowComponent {
1131
1131
  td.controls {
1132
1132
  padding: 0;
1133
1133
  }
1134
- td.controls-after,
1135
- td.controls-before {
1134
+ td.controls-after > div,
1135
+ td.controls-before > div {
1136
1136
  display: flex;
1137
1137
  align-items: center;
1138
1138
  }