kempo-ui 0.2.3 → 0.2.4
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/Context.js +1 -0
- package/docs/components/aside-items.html +1 -0
- package/docs/components/aside-menu.html +1 -0
- package/docs/components/aside-push.html +1 -0
- package/docs/components/aside.html +6 -2
- package/docs/components/context.html +231 -0
- package/docs/components/nav.html +7 -2
- package/docs/components/table.html +1 -0
- package/docs/components/tableControls.html +1 -0
- package/docs/components/tableCustomFields.html +1 -0
- package/docs/components/tableFetchRecords.html +1 -0
- package/docs/components/tableFieldSortHide.html +1 -0
- package/docs/components/tablePagination.html +1 -0
- package/docs/components/tableRecordEditing.html +1 -0
- package/docs/components/tableRecordFiltering.html +1 -0
- package/docs/components/tableRecordHiding.html +1 -0
- package/docs/components/tableRecordSearching.html +1 -0
- package/docs/components/tableRecordSelection.html +1 -0
- package/docs/components/tableRowControls.html +1 -0
- package/docs/components/tableServerSync.html +1 -0
- package/docs/components/tableSorting.html +1 -0
- package/docs/icons/npm.svg +1 -0
- package/docs/index.html +6 -0
- package/docs/nav-1.inc.html +5 -2
- package/docs/nav.inc.html +4 -1
- package/docs/src/components/Context.js +1 -0
- package/docs/utils/elevation.html +6 -2
- package/icons/npm.svg +1 -0
- package/llm.txt +1 -0
- package/package.json +1 -1
- package/src/components/Context.js +84 -0
- package/tests/components/Context.browser-test.js +238 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{html}from"../lit-all.min.js";import LightComponent from"./LightComponent.js";export default class Context extends LightComponent{static properties={data:{type:String,reflect:!0}};constructor(){super(),this.data="{}"}connectedCallback(){super.connectedCallback(),this.style.display="contents"}set(t,e){const s=JSON.parse(this.data||"{}"),a=!(t in s),n=s[t];s[t]=e,this.data=JSON.stringify(s),this.dispatchEvent(new CustomEvent(a?"context:create":"context:set",{detail:a?{key:t,value:e}:{key:t,value:e,oldValue:n},bubbles:!0,composed:!0}))}get(t){return JSON.parse(this.data||"{}")[t]}has(t){return t in JSON.parse(this.data||"{}")}delete(t){const e=JSON.parse(this.data||"{}");if(!(t in e))return;const s=e[t];delete e[t],this.data=JSON.stringify(e),this.dispatchEvent(new CustomEvent("context:delete",{detail:{key:t,value:s},bubbles:!0,composed:!0}))}clear(){for(const t of Object.keys(JSON.parse(this.data||"{}")))this.delete(t)}getData(){return JSON.parse(this.data||"{}")}renderLightDom(){return html``}}customElements.define("k-context",Context);
|
|
@@ -521,7 +521,8 @@
|
|
|
521
521
|
|
|
522
522
|
<h3 id="asideItem"><a href="#asideItem" class="no-link">k-aside-item</a></h3>
|
|
523
523
|
<p>A navigation link that reacts to the parent aside's collapsed/expanded state. Shows icon + text when expanded, icon-only when collapsed.</p>
|
|
524
|
-
<
|
|
524
|
+
<div class="table-wrapper mb">
|
|
525
|
+
<table>
|
|
525
526
|
<thead><tr><th>Attribute</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
526
527
|
<tbody>
|
|
527
528
|
<tr><td><code>icon</code></td><td>string</td><td><code>""</code></td><td>Icon name (renders <code>k-icon</code>). Shows a dot when collapsed if no icon set.</td></tr>
|
|
@@ -531,6 +532,7 @@
|
|
|
531
532
|
<tr><td><code>hide-when-collapsed</code></td><td>boolean</td><td><code>false</code></td><td>Hides the item entirely when collapsed.</td></tr>
|
|
532
533
|
</tbody>
|
|
533
534
|
</table>
|
|
535
|
+
</div>
|
|
534
536
|
|
|
535
537
|
<h3 id="asideLabel"><a href="#asideLabel" class="no-link">k-aside-label</a></h3>
|
|
536
538
|
<p>A section label. Displays text when expanded, an <code><hr></code> divider when collapsed.</p>
|
|
@@ -538,7 +540,8 @@
|
|
|
538
540
|
|
|
539
541
|
<h3 id="asideMenu"><a href="#asideMenu" class="no-link">k-aside-menu</a></h3>
|
|
540
542
|
<p>An expandable sub-menu that toggles open/closed. When the aside is collapsed, clicking the menu icon expands the aside.</p>
|
|
541
|
-
<
|
|
543
|
+
<div class="table-wrapper mb">
|
|
544
|
+
<table>
|
|
542
545
|
<thead><tr><th>Attribute</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
543
546
|
<tbody>
|
|
544
547
|
<tr><td><code>icon</code></td><td>string</td><td><code>""</code></td><td>Icon name for the menu header.</td></tr>
|
|
@@ -548,6 +551,7 @@
|
|
|
548
551
|
<tr><td><code>hide-when-collapsed</code></td><td>boolean</td><td><code>false</code></td><td>Hides the menu entirely when collapsed.</td></tr>
|
|
549
552
|
</tbody>
|
|
550
553
|
</table>
|
|
554
|
+
</div>
|
|
551
555
|
<p>Slot: Place <code>k-aside-item</code> elements inside for sub-menu links.</p>
|
|
552
556
|
|
|
553
557
|
<h3 id="asideSpacer"><a href="#asideSpacer" class="no-link">k-aside-spacer</a></h3>
|
|
@@ -0,0 +1,231 @@
|
|
|
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>Context - 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@2/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
|
+
<h1 class="ta-center">Context</h1>
|
|
18
|
+
<k-main>
|
|
19
|
+
<k-accordion persistent-id="toc" class="b r mb">
|
|
20
|
+
<k-accordion-header for-panel="toc-panel">Table of Contents</k-accordion-header>
|
|
21
|
+
<k-accordion-panel name="toc-panel">
|
|
22
|
+
<div class="m">
|
|
23
|
+
<h6>Examples</h6>
|
|
24
|
+
<a href="#basicUsage">Basic Usage</a><br />
|
|
25
|
+
<a href="#events">Listening to Events</a><br />
|
|
26
|
+
<a href="#usingInComponents">Using in Components</a><br />
|
|
27
|
+
<a href="#interactiveDemo">Interactive Demo</a><br />
|
|
28
|
+
|
|
29
|
+
<h6 class="mt"><a href="#jsRef" class="no-link">JavaScript Reference</a></h6>
|
|
30
|
+
<a href="#constructor">Constructor</a><br />
|
|
31
|
+
<a href="#attributes">Attributes</a><br />
|
|
32
|
+
<a href="#methods">Methods</a><br />
|
|
33
|
+
<a href="#eventsRef">Events</a><br />
|
|
34
|
+
</div>
|
|
35
|
+
</k-accordion-panel>
|
|
36
|
+
</k-accordion>
|
|
37
|
+
|
|
38
|
+
<p><code>k-context</code> is a non-rendering state container inspired by React's <code>useContext</code>. Place it in the DOM as an ancestor of components that need shared state. Descendant components locate the nearest <code>k-context</code> via <code>closest('k-context')</code>, read and write data with its methods, and react to changes by listening for its events.</p>
|
|
39
|
+
|
|
40
|
+
<h3 id="basicUsage"><a href="#basicUsage" class="no-link">Basic Usage</a></h3>
|
|
41
|
+
<p>Declare a <code><k-context></code> element in your markup. It is invisible and renders nothing; its <code>display: contents</code> style ensures its children remain in the normal document flow.</p>
|
|
42
|
+
<div class="row -mx">
|
|
43
|
+
<div class="col m-span-12 px">
|
|
44
|
+
<k-card label="HTML">
|
|
45
|
+
<pre><code class="hljs html"><span class="hljs-tag"><<span class="hljs-name">k-context</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"myContext"</span>></span><span class="hljs-tag"></<span class="hljs-name">k-context</span>></span></code></pre>
|
|
46
|
+
</k-card>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
<p>You can pre-populate the store declaratively by setting the <code>data</code> attribute to a valid JSON string:</p>
|
|
50
|
+
<div class="row -mx">
|
|
51
|
+
<div class="col m-span-12 px">
|
|
52
|
+
<k-card label="HTML">
|
|
53
|
+
<pre><code class="hljs html"><span class="hljs-tag"><<span class="hljs-name">k-context</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"myContext"</span> <span class="hljs-attr">data</span>=<span class="hljs-string">'{"count":0,"theme":"dark"}'</span>></span><span class="hljs-tag"></<span class="hljs-name">k-context</span>></span></code></pre>
|
|
54
|
+
</k-card>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
<p>Use <code>set()</code> and <code>get()</code> to write and read data by key:</p>
|
|
58
|
+
<div class="row -mx">
|
|
59
|
+
<div class="col m-span-12 px">
|
|
60
|
+
<k-card label="JavaScript">
|
|
61
|
+
<pre><code class="hljs javascript"><span class="hljs-keyword">const</span> ctx = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-string">'k-context'</span>);<br>ctx.<span class="hljs-title function_">set</span>(<span class="hljs-string">'count'</span>, <span class="hljs-number">0</span>);<br>ctx.<span class="hljs-title function_">set</span>(<span class="hljs-string">'theme'</span>, <span class="hljs-string">'dark'</span>);</code></pre>
|
|
62
|
+
</k-card>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<h3 id="events"><a href="#events" class="no-link">Listening to Events</a></h3>
|
|
67
|
+
<p><code>context:create</code> fires when a new key is set for the first time. <code>context:set</code> fires when an existing key's value changes. <code>context:delete</code> fires when a key is removed. All events bubble and are composed.</p>
|
|
68
|
+
<div class="row -mx">
|
|
69
|
+
<div class="col m-span-12 px">
|
|
70
|
+
<k-card label="JavaScript">
|
|
71
|
+
<pre><code class="hljs javascript"><span class="hljs-keyword">const</span> ctx = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-string">'k-context'</span>);<br>ctx.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'context:create'</span>, <span class="hljs-function"><span class="hljs-params">e</span> =></span> {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'Created:'</span>, e.<span class="hljs-property">detail</span>.<span class="hljs-property">key</span>, e.<span class="hljs-property">detail</span>.<span class="hljs-property">value</span>);<br>});<br>ctx.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'context:set'</span>, <span class="hljs-function"><span class="hljs-params">e</span> =></span> {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'Updated:'</span>, e.<span class="hljs-property">detail</span>.<span class="hljs-property">key</span>, <span class="hljs-string">'from'</span>, e.<span class="hljs-property">detail</span>.<span class="hljs-property">oldValue</span>, <span class="hljs-string">'to'</span>, e.<span class="hljs-property">detail</span>.<span class="hljs-property">value</span>);<br>});<br>ctx.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'context:delete'</span>, <span class="hljs-function"><span class="hljs-params">e</span> =></span> {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'Deleted:'</span>, e.<span class="hljs-property">detail</span>.<span class="hljs-property">key</span>, <span class="hljs-string">'was'</span>, e.<span class="hljs-property">detail</span>.<span class="hljs-property">value</span>);<br>});</code></pre>
|
|
72
|
+
</k-card>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<h3 id="usingInComponents"><a href="#usingInComponents" class="no-link">Using in Components</a></h3>
|
|
77
|
+
<p>Child components locate the nearest ancestor context using <code>closest('k-context')</code>. This follows the same scoping pattern used by all Kempo UI parent–child relationships.</p>
|
|
78
|
+
<div class="row -mx">
|
|
79
|
+
<div class="col m-span-12 px">
|
|
80
|
+
<k-card label="JavaScript">
|
|
81
|
+
<pre><code class="hljs javascript"><span class="hljs-comment">// Inside a child component, find the nearest ancestor context:</span><br><span class="hljs-keyword">const</span> ctx = <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">closest</span>(<span class="hljs-string">'k-context'</span>);<br><span class="hljs-keyword">const</span> count = ctx?.<span class="hljs-title function_">get</span>(<span class="hljs-string">'count'</span>);</code></pre>
|
|
82
|
+
</k-card>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
<p>To react to context changes from inside a shadow-DOM component, listen on the context element directly — events bubble and are <code>composed: true</code> so they also cross shadow boundaries.</p>
|
|
86
|
+
|
|
87
|
+
<h3 id="interactiveDemo"><a href="#interactiveDemo" class="no-link">Interactive Demo</a></h3>
|
|
88
|
+
<div class="row -mx">
|
|
89
|
+
<div class="col m-span-12 t-span-6 px">
|
|
90
|
+
<k-card label="HTML">
|
|
91
|
+
<pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">k-context</span> <span class="hljs-attr">data</span>=<span class="hljs-string">'{"count":0}'</span>></span><br /> <span class="hljs-tag"><<span class="hljs-name">button</span><br /> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"const ctx=this.closest('k-context'); ctx.set('count', (ctx.get('count') || 0) - 1)"</span><br /> ></span>-<span class="hljs-tag"></<span class="hljs-name">button</span>></span><br /> <span class="hljs-tag"><<span class="hljs-name">count-view</span>></span><span class="hljs-tag"></<span class="hljs-name">count-view</span>></span><br /> <span class="hljs-tag"><<span class="hljs-name">button</span><br /> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"const ctx=this.closest('k-context'); ctx.set('count', (ctx.get('count') || 0) + 1)"</span><br /> ></span>+<span class="hljs-tag"></<span class="hljs-name">button</span>></span><br /> <span class="hljs-tag"><<span class="hljs-name">button</span><br /> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"this.closest('k-context').clear()"</span><br /> ></span>Clear<span class="hljs-tag"></<span class="hljs-name">button</span>></span><br /><span class="hljs-tag"></<span class="hljs-name">k-context</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> <span class="hljs-string">'../src/components/Context.js'</span>;<br /> <span class="hljs-keyword">import</span> ShadowComponent <span class="hljs-keyword">from</span> <span class="hljs-string">'../src/components/ShadowComponent.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-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CountView</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ShadowComponent</span> </span>{<br /> connectedCallback() {<br /> <span class="hljs-keyword">super</span>.connectedCallback();<br /> <span class="hljs-keyword">const</span> ctx = <span class="hljs-keyword">this</span>.closest(<span class="hljs-string">'k-context'</span>);<br /> ctx?.addEventListener(<span class="hljs-string">'context:create'</span>, () => <span class="hljs-keyword">this</span>.requestUpdate());<br /> ctx?.addEventListener(<span class="hljs-string">'context:set'</span>, () => <span class="hljs-keyword">this</span>.requestUpdate());<br /> ctx?.addEventListener(<span class="hljs-string">'context:delete'</span>, () => <span class="hljs-keyword">this</span>.requestUpdate());<br /> }<br /> render() {<br /> <span class="hljs-keyword">const</span> count = <span class="hljs-keyword">this</span>.closest(<span class="hljs-string">'k-context'</span>)?.get(<span class="hljs-string">'count'</span>);<br /> <span class="hljs-keyword">return</span> count !== <span class="hljs-literal">undefined</span> ? html`<span class="xml"></span><span class="hljs-subst">${count}</span><span class="xml">`</span> : html`<span class="xml"><span class="hljs-tag"><<span class="hljs-name">code</span>></span>undefined<span class="hljs-tag"></<span class="hljs-name">code</span>></span>`</span>;<br /> }<br /> }<br /> customElements.define(<span class="hljs-string">'count-view'</span>, CountView);<br /></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span></code></pre>
|
|
92
|
+
</k-card>
|
|
93
|
+
</div>
|
|
94
|
+
<div class="col m-span-12 t-span-6 px">
|
|
95
|
+
<k-card label="Output">
|
|
96
|
+
<k-context data='{"count":0}'>
|
|
97
|
+
<button
|
|
98
|
+
onclick="const ctx=this.closest('k-context'); ctx.set('count', (ctx.get('count') || 0) - 1)"
|
|
99
|
+
>-</button>
|
|
100
|
+
<count-view></count-view>
|
|
101
|
+
<button
|
|
102
|
+
onclick="const ctx=this.closest('k-context'); ctx.set('count', (ctx.get('count') || 0) + 1)"
|
|
103
|
+
>+</button>
|
|
104
|
+
<button
|
|
105
|
+
onclick="this.closest('k-context').clear()"
|
|
106
|
+
>Clear</button>
|
|
107
|
+
</k-context>
|
|
108
|
+
<script type="module">
|
|
109
|
+
import '../src/components/Context.js';
|
|
110
|
+
import ShadowComponent from '../src/components/ShadowComponent.js';
|
|
111
|
+
import { html } from '../src/lit-all.min.js';
|
|
112
|
+
class CountView extends ShadowComponent {
|
|
113
|
+
connectedCallback() {
|
|
114
|
+
super.connectedCallback();
|
|
115
|
+
const ctx = this.closest('k-context');
|
|
116
|
+
ctx?.addEventListener('context:create', () => this.requestUpdate());
|
|
117
|
+
ctx?.addEventListener('context:set', () => this.requestUpdate());
|
|
118
|
+
ctx?.addEventListener('context:delete', () => this.requestUpdate());
|
|
119
|
+
}
|
|
120
|
+
render() {
|
|
121
|
+
const count = this.closest('k-context')?.get('count');
|
|
122
|
+
return count !== undefined ? html`${count}` : html`<code>undefined</code>`;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
customElements.define('count-view', CountView);
|
|
126
|
+
</script>
|
|
127
|
+
</k-card>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<h2 id="jsRef"><a href="#jsRef" class="no-link">JavaScript Reference</a></h2>
|
|
132
|
+
|
|
133
|
+
<h3 id="constructor"><a href="#constructor" class="no-link">Constructor</a></h3>
|
|
134
|
+
<p><code>k-context</code> extends <code>LightComponent</code>. It renders nothing and sets <code>display: contents</code> on itself so its children remain in the normal document flow. It accepts an optional <code>data</code> attribute containing a JSON string to pre-populate the context state.</p>
|
|
135
|
+
|
|
136
|
+
<h3 id="attributes"><a href="#attributes" class="no-link">Attributes</a></h3>
|
|
137
|
+
<div class="table-wrapper mb">
|
|
138
|
+
<table>
|
|
139
|
+
<thead>
|
|
140
|
+
<tr>
|
|
141
|
+
<th>Attribute</th>
|
|
142
|
+
<th>Type</th>
|
|
143
|
+
<th>Description</th>
|
|
144
|
+
</tr>
|
|
145
|
+
</thead>
|
|
146
|
+
<tbody>
|
|
147
|
+
<tr>
|
|
148
|
+
<td><code>data</code></td>
|
|
149
|
+
<td><code>String</code> (JSON)</td>
|
|
150
|
+
<td>A JSON string representing the initial store state. Reflects back to the attribute as state changes via the component's methods. Defaults to <code>'{}'</code>.</td>
|
|
151
|
+
</tr>
|
|
152
|
+
</tbody>
|
|
153
|
+
</table>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<h3 id="methods"><a href="#methods" class="no-link">Methods</a></h3>
|
|
157
|
+
<div class="table-wrapper mb">
|
|
158
|
+
<table>
|
|
159
|
+
<thead>
|
|
160
|
+
<tr>
|
|
161
|
+
<th>Method</th>
|
|
162
|
+
<th>Description</th>
|
|
163
|
+
</tr>
|
|
164
|
+
</thead>
|
|
165
|
+
<tbody>
|
|
166
|
+
<tr>
|
|
167
|
+
<td><code>set(key, value)</code></td>
|
|
168
|
+
<td>Creates or updates a key. Fires <code>context:create</code> if the key is new, <code>context:set</code> if it already existed.</td>
|
|
169
|
+
</tr>
|
|
170
|
+
<tr>
|
|
171
|
+
<td><code>get(key)</code></td>
|
|
172
|
+
<td>Returns the stored value for <code>key</code>, or <code>undefined</code> if not set.</td>
|
|
173
|
+
</tr>
|
|
174
|
+
<tr>
|
|
175
|
+
<td><code>has(key)</code></td>
|
|
176
|
+
<td>Returns <code>true</code> if <code>key</code> exists in the store.</td>
|
|
177
|
+
</tr>
|
|
178
|
+
<tr>
|
|
179
|
+
<td><code>delete(key)</code></td>
|
|
180
|
+
<td>Removes a key and fires <code>context:delete</code>. No-op if the key does not exist.</td>
|
|
181
|
+
</tr>
|
|
182
|
+
<tr>
|
|
183
|
+
<td><code>clear()</code></td>
|
|
184
|
+
<td>Calls <code>delete()</code> on every key, firing <code>context:delete</code> for each.</td>
|
|
185
|
+
</tr>
|
|
186
|
+
<tr>
|
|
187
|
+
<td><code>getData()</code></td>
|
|
188
|
+
<td>Returns a shallow copy of the entire data store as a plain object.</td>
|
|
189
|
+
</tr>
|
|
190
|
+
</tbody>
|
|
191
|
+
</table>
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<h3 id="eventsRef"><a href="#eventsRef" class="no-link">Events</a></h3>
|
|
195
|
+
<p>All events bubble and are <code>composed: true</code>, so they cross shadow DOM boundaries.</p>
|
|
196
|
+
<div class="table-wrapper mb">
|
|
197
|
+
<table>
|
|
198
|
+
<thead>
|
|
199
|
+
<tr>
|
|
200
|
+
<th>Event</th>
|
|
201
|
+
<th>Fired when</th>
|
|
202
|
+
<th><code>detail</code> shape</th>
|
|
203
|
+
</tr>
|
|
204
|
+
</thead>
|
|
205
|
+
<tbody>
|
|
206
|
+
<tr>
|
|
207
|
+
<td><code>context:create</code></td>
|
|
208
|
+
<td>A new key is set for the first time</td>
|
|
209
|
+
<td><code>{ key, value }</code></td>
|
|
210
|
+
</tr>
|
|
211
|
+
<tr>
|
|
212
|
+
<td><code>context:set</code></td>
|
|
213
|
+
<td>An existing key's value is changed</td>
|
|
214
|
+
<td><code>{ key, value, oldValue }</code></td>
|
|
215
|
+
</tr>
|
|
216
|
+
<tr>
|
|
217
|
+
<td><code>context:delete</code></td>
|
|
218
|
+
<td>A key is removed via <code>delete()</code> or <code>clear()</code></td>
|
|
219
|
+
<td><code>{ key, value }</code></td>
|
|
220
|
+
</tr>
|
|
221
|
+
</tbody>
|
|
222
|
+
</table>
|
|
223
|
+
</div>
|
|
224
|
+
</k-main>
|
|
225
|
+
<div style="height:33vh"></div>
|
|
226
|
+
<script type="module" src="../src/components/Import.js"></script>
|
|
227
|
+
<script type="module" src="../src/components/Accordion.js"></script>
|
|
228
|
+
<script type="module" src="../src/components/Card.js"></script>
|
|
229
|
+
<script type="module" src="../src/components/Main.js"></script>
|
|
230
|
+
</body>
|
|
231
|
+
</html>
|
package/docs/components/nav.html
CHANGED
|
@@ -68,7 +68,8 @@
|
|
|
68
68
|
<pre><code class="hljs html"><span class="hljs-tag"><<span class="hljs-name">k-nav</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"bg-primary"</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">button</span>></span>Menu<span class="hljs-tag"></<span class="hljs-name">button</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span>></span>My App<span class="hljs-tag"></<span class="hljs-name">a</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"flex: 1"</span>></span><span class="hljs-tag"></<span class="hljs-name">div</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/account"</span>></span>Account<span class="hljs-tag"></<span class="hljs-name">a</span>></span><br><span class="hljs-tag"></<span class="hljs-name">k-nav</span>></span></code></pre>
|
|
69
69
|
|
|
70
70
|
<h2 id="attributes"><a href="#attributes" class="no-link">Attributes & Properties</a></h2>
|
|
71
|
-
<
|
|
71
|
+
<div class="table-wrapper mb">
|
|
72
|
+
<table>
|
|
72
73
|
<thead>
|
|
73
74
|
<tr>
|
|
74
75
|
<th>Attribute / Property</th>
|
|
@@ -89,9 +90,11 @@
|
|
|
89
90
|
</tr>
|
|
90
91
|
</tbody>
|
|
91
92
|
</table>
|
|
93
|
+
</div>
|
|
92
94
|
|
|
93
95
|
<h2 id="slots"><a href="#slots" class="no-link">Slots</a></h2>
|
|
94
|
-
<
|
|
96
|
+
<div class="table-wrapper mb">
|
|
97
|
+
<table>
|
|
95
98
|
<thead>
|
|
96
99
|
<tr>
|
|
97
100
|
<th>Slot</th>
|
|
@@ -105,6 +108,7 @@
|
|
|
105
108
|
</tr>
|
|
106
109
|
</tbody>
|
|
107
110
|
</table>
|
|
111
|
+
</div>
|
|
108
112
|
</k-main>
|
|
109
113
|
|
|
110
114
|
<script src="../src/components/Nav.js" type="module"></script>
|
|
@@ -112,6 +116,7 @@
|
|
|
112
116
|
<script src="../src/components/Main.js" type="module"></script>
|
|
113
117
|
<script src="../src/components/Accordion.js" type="module"></script>
|
|
114
118
|
<script src="../src/components/Card.js" type="module"></script>
|
|
119
|
+
<div style="height:33vh"></div>
|
|
115
120
|
<script src="../src/components/Icon.js" type="module"></script>
|
|
116
121
|
</body>
|
|
117
122
|
</html>
|
|
@@ -278,6 +278,7 @@
|
|
|
278
278
|
<script type="module" src="../src/components/Table.js"></script>
|
|
279
279
|
<script type="module" src="../src/components/Accordion.js"></script>
|
|
280
280
|
<script type="module" src="../src/components/Card.js"></script>
|
|
281
|
+
<div style="height:33vh"></div>
|
|
281
282
|
<script type="module" src="../src/components/Resize.js"></script>
|
|
282
283
|
|
|
283
284
|
</body>
|
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
<div style="height: 33.333vh"></div>
|
|
76
76
|
<script type="module" src="../src/components/Import.js"></script>
|
|
77
77
|
<script type="module" src="../src/components/Main.js"></script>
|
|
78
|
+
<div style="height:33vh"></div>
|
|
78
79
|
<script type="module" src="../src/components/Table.js"></script>
|
|
79
80
|
</body>
|
|
80
81
|
</html>
|
|
@@ -110,6 +110,7 @@
|
|
|
110
110
|
<div style="height: 33.333vh"></div>
|
|
111
111
|
<script type="module" src="../src/components/Import.js"></script>
|
|
112
112
|
<script type="module" src="../src/components/Main.js"></script>
|
|
113
|
+
<div style="height:33vh"></div>
|
|
113
114
|
<script type="module" src="../src/components/Table.js"></script>
|
|
114
115
|
</body>
|
|
115
116
|
</html>
|
|
@@ -96,6 +96,7 @@
|
|
|
96
96
|
<script type="module" src="../src/components/tableControls/NextPage.js"></script>
|
|
97
97
|
<script type="module" src="../src/components/tableControls/LastPage.js"></script>
|
|
98
98
|
<script type="module" src="../src/components/tableControls/PageSelect.js"></script>
|
|
99
|
+
<div style="height:33vh"></div>
|
|
99
100
|
<script type="module" src="../src/components/tableControls/PageSize.js"></script>
|
|
100
101
|
</body>
|
|
101
102
|
</html>
|
|
@@ -141,6 +141,7 @@
|
|
|
141
141
|
<script type="module" src="../src/components/Import.js"></script>
|
|
142
142
|
<script type="module" src="../src/components/Main.js"></script>
|
|
143
143
|
<script type="module" src="../src/components/Table.js"></script>
|
|
144
|
+
<div style="height:33vh"></div>
|
|
144
145
|
<script type="module" src="../src/components/tableControls/Edit.js"></script>
|
|
145
146
|
</body>
|
|
146
147
|
</html>
|
|
@@ -77,6 +77,7 @@
|
|
|
77
77
|
<script type="module" src="../src/components/Table.js"></script>
|
|
78
78
|
<script type="module" src="../src/components/tableControls/Hide.js"></script>
|
|
79
79
|
<script type="module" src="../src/components/tableControls/HiddenCount.js"></script>
|
|
80
|
+
<div style="height:33vh"></div>
|
|
80
81
|
<script type="module" src="../src/components/tableControls/ShowAll.js"></script>
|
|
81
82
|
</body>
|
|
82
83
|
</html>
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
<div style="height: 33.333vh"></div>
|
|
72
72
|
<script type="module" src="../src/components/Import.js"></script>
|
|
73
73
|
<script type="module" src="../src/components/Main.js"></script>
|
|
74
|
+
<div style="height:33vh"></div>
|
|
74
75
|
<script type="module" src="../src/components/Table.js"></script>
|
|
75
76
|
</body>
|
|
76
77
|
</html>
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
<script type="module" src="../src/components/tableControls/ExportJson.js"></script>
|
|
74
74
|
<script type="module" src="../src/components/tableControls/DeleteRecord.js"></script>
|
|
75
75
|
<script type="module" src="../src/components/Import.js"></script>
|
|
76
|
+
<div style="height:33vh"></div>
|
|
76
77
|
<script type="module" src="../src/components/Main.js"></script>
|
|
77
78
|
</body>
|
|
78
79
|
</html>
|
|
@@ -139,6 +139,7 @@
|
|
|
139
139
|
<script type="module" src="../src/components/tableControls/Edit.js"></script>
|
|
140
140
|
<script type="module" src="../src/components/tableControls/DeleteRecord.js"></script>
|
|
141
141
|
<script type="module" src="../src/components/tableControls/DeleteSelected.js"></script>
|
|
142
|
+
<div style="height:33vh"></div>
|
|
142
143
|
<script type="module" src="../src/components/Toast.js"></script>
|
|
143
144
|
</body>
|
|
144
145
|
</html>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z"/></svg>
|
package/docs/index.html
CHANGED
|
@@ -77,6 +77,12 @@
|
|
|
77
77
|
<p class="tc-muted">Swipeable carousel for images and content with navigation controls.</p>
|
|
78
78
|
</a>
|
|
79
79
|
</div>
|
|
80
|
+
<div class="span-12 t-span-6 d-span-4 px">
|
|
81
|
+
<a href="./components/context.html" class="card mb no-link d-b">
|
|
82
|
+
<h3 class="tc-primary">Context</h3>
|
|
83
|
+
<p class="tc-muted">Non-rendering state container for sharing data and notifying descendant components of changes.</p>
|
|
84
|
+
</a>
|
|
85
|
+
</div>
|
|
80
86
|
<div class="span-12 t-span-6 d-span-4 px">
|
|
81
87
|
<a href="./components/dialog.html" class="card mb no-link d-b">
|
|
82
88
|
<h3 class="tc-primary">Dialog</h3>
|
package/docs/nav-1.inc.html
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
<k-filter-item filter-keywords="card components"><a href="../components/card.html">Card<br><small>Component</small></a></k-filter-item>
|
|
26
26
|
<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>
|
|
27
27
|
<k-filter-item filter-keywords="content slider components"><a href="../components/content-slider.html">Content Slider<br><small>Component</small></a></k-filter-item>
|
|
28
|
+
<k-filter-item filter-keywords="context state global components"><a href="../components/context.html">Context<br><small>Component</small></a></k-filter-item>
|
|
28
29
|
<k-filter-item filter-keywords="dialog modal popup components"><a href="../components/dialog.html">Dialog<br><small>Component</small></a></k-filter-item>
|
|
29
30
|
<k-filter-item filter-keywords="dropdown select components"><a href="../components/dropdown.html">Dropdown<br><small>Component</small></a></k-filter-item>
|
|
30
31
|
<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>
|
|
@@ -80,8 +81,9 @@
|
|
|
80
81
|
</div>
|
|
81
82
|
</div>
|
|
82
83
|
<div class="flex"></div>
|
|
83
|
-
<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>
|
|
84
|
-
<a href="https://
|
|
84
|
+
<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>
|
|
85
|
+
<a href="https://www.npmjs.com/package/kempo-ui" target="_blank"><k-icon name="npm"></k-icont></a>
|
|
86
|
+
<a href="https://github.com/dustinpoissant/kempo-ui" target="_blank"><k-icon name="github-mark"></k-icont></a>
|
|
85
87
|
<k-theme-switcher></k-theme-switcher>
|
|
86
88
|
</k-nav>
|
|
87
89
|
<div style="width: 100%; height: 4rem;"></div>
|
|
@@ -102,6 +104,7 @@
|
|
|
102
104
|
<a href="../components/card.html">Card</a>
|
|
103
105
|
<a href="../components/color-picker.html">ColorPicker</a>
|
|
104
106
|
<a href="../components/content-slider.html">Content Slider</a>
|
|
107
|
+
<a href="../components/context.html">Context</a>
|
|
105
108
|
<a href="../components/dialog.html">Dialog</a>
|
|
106
109
|
<a href="../components/dropdown.html">Dropdown</a>
|
|
107
110
|
<a href="../components/filter-list.html">Filter List</a>
|
package/docs/nav.inc.html
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
<k-filter-item filter-keywords="card components"><a href="./components/card.html">Card<br><small>Component</small></a></k-filter-item>
|
|
26
26
|
<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>
|
|
27
27
|
<k-filter-item filter-keywords="content slider components"><a href="./components/content-slider.html">Content Slider<br><small>Component</small></a></k-filter-item>
|
|
28
|
+
<k-filter-item filter-keywords="context state global components"><a href="./components/context.html">Context<br><small>Component</small></a></k-filter-item>
|
|
28
29
|
<k-filter-item filter-keywords="dialog modal popup components"><a href="./components/dialog.html">Dialog<br><small>Component</small></a></k-filter-item>
|
|
29
30
|
<k-filter-item filter-keywords="dropdown select components"><a href="./components/dropdown.html">Dropdown<br><small>Component</small></a></k-filter-item>
|
|
30
31
|
<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>
|
|
@@ -80,7 +81,8 @@
|
|
|
80
81
|
</div>
|
|
81
82
|
</div>
|
|
82
83
|
<div class="flex"></div>
|
|
83
|
-
<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>
|
|
84
|
+
<a href="https://github.com/dustinpoissant/kempo-ui?tab=License-1-ov-file#creative-commons-attribution-noncommercial-sharealike-20" target="_blank" target="_blank"><k-icon name="license"></k-icont></a>
|
|
85
|
+
<a href="https://www.npmjs.com/package/kempo-ui" target="_blank"><k-icon name="npm"></k-icont></a>
|
|
84
86
|
<a href="https://github.com/dustinpoissant/kempo-ui" target="_blank"><k-icon name="github-mark"></k-icont></a>
|
|
85
87
|
<k-theme-switcher></k-theme-switcher>
|
|
86
88
|
</k-nav>
|
|
@@ -103,6 +105,7 @@
|
|
|
103
105
|
<a href="./components/card.html">Card</a>
|
|
104
106
|
<a href="./components/color-picker.html">ColorPicker</a>
|
|
105
107
|
<a href="./components/content-slider.html">Content Slider</a>
|
|
108
|
+
<a href="./components/context.html">Context</a>
|
|
106
109
|
<a href="./components/dialog.html">Dialog</a>
|
|
107
110
|
<a href="./components/dropdown.html">Dropdown</a>
|
|
108
111
|
<a href="./components/filter-list.html">Filter List</a>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{html}from"../lit-all.min.js";import LightComponent from"./LightComponent.js";export default class Context extends LightComponent{static properties={data:{type:String,reflect:!0}};constructor(){super(),this.data="{}"}connectedCallback(){super.connectedCallback(),this.style.display="contents"}set(t,e){const s=JSON.parse(this.data||"{}"),a=!(t in s),n=s[t];s[t]=e,this.data=JSON.stringify(s),this.dispatchEvent(new CustomEvent(a?"context:create":"context:set",{detail:a?{key:t,value:e}:{key:t,value:e,oldValue:n},bubbles:!0,composed:!0}))}get(t){return JSON.parse(this.data||"{}")[t]}has(t){return t in JSON.parse(this.data||"{}")}delete(t){const e=JSON.parse(this.data||"{}");if(!(t in e))return;const s=e[t];delete e[t],this.data=JSON.stringify(e),this.dispatchEvent(new CustomEvent("context:delete",{detail:{key:t,value:s},bubbles:!0,composed:!0}))}clear(){for(const t of Object.keys(JSON.parse(this.data||"{}")))this.delete(t)}getData(){return JSON.parse(this.data||"{}")}renderLightDom(){return html``}}customElements.define("k-context",Context);
|
|
@@ -50,7 +50,8 @@
|
|
|
50
50
|
</ul>
|
|
51
51
|
|
|
52
52
|
<h3 id="levels"><a href="#levels" class="no-link">Elevation Levels</a></h3>
|
|
53
|
-
<
|
|
53
|
+
<div class="table-wrapper mb">
|
|
54
|
+
<table>
|
|
54
55
|
<thead>
|
|
55
56
|
<tr>
|
|
56
57
|
<th>Level</th>
|
|
@@ -72,10 +73,12 @@
|
|
|
72
73
|
<tr><td>10</td><td>100</td><td><em>Reserved — use only when absolutely necessary</em></td></tr>
|
|
73
74
|
</tbody>
|
|
74
75
|
</table>
|
|
76
|
+
</div>
|
|
75
77
|
|
|
76
78
|
<h3 id="components"><a href="#components" class="no-link">Component Assignments</a></h3>
|
|
77
79
|
<p>Every Kempo UI component that uses <code>position: fixed</code> is assigned an elevation level:</p>
|
|
78
|
-
<
|
|
80
|
+
<div class="table-wrapper mb">
|
|
81
|
+
<table>
|
|
79
82
|
<thead>
|
|
80
83
|
<tr>
|
|
81
84
|
<th>Level</th>
|
|
@@ -123,6 +126,7 @@
|
|
|
123
126
|
</tr>
|
|
124
127
|
</tbody>
|
|
125
128
|
</table>
|
|
129
|
+
</div>
|
|
126
130
|
<p>
|
|
127
131
|
The <strong>Aside</strong> component uses two different levels depending on its <code>main</code> attribute.
|
|
128
132
|
When <code>main="push"</code>, it shifts the page layout and should sit <em>below</em> your site navbar (level 3).
|
package/icons/npm.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z"/></svg>
|
package/llm.txt
CHANGED
|
@@ -57,6 +57,7 @@ import { html, css, LitElement } from 'kempo-ui/src/lit-all.min.js';
|
|
|
57
57
|
| `<k-card>` | `Card.js` | Bordered container; optional `label` attribute | [card.html](https://dustinpoissant.github.io/kempo-ui/components/card.html) |
|
|
58
58
|
| `<k-color-picker>` | `ColorPicker.js` | Full color picker (hex, rgb, hsl, hsv, alpha); `value` property | [color-picker.html](https://dustinpoissant.github.io/kempo-ui/components/color-picker.html) |
|
|
59
59
|
| `<k-content-slider>` | `ContentSlider.js` | Swipeable carousel | [content-slider.html](https://dustinpoissant.github.io/kempo-ui/components/content-slider.html) |
|
|
60
|
+
| `<k-context>` | `Context.js` | Non-rendering state container; `set(key,value)`, `get(key)`, `delete(key)`, `clear()`, `getData()`; fires `context:create`, `context:set`, `context:delete` | [context.html](https://dustinpoissant.github.io/kempo-ui/components/context.html) |
|
|
60
61
|
| `<k-dialog>` | `Dialog.js` | Modal dialog with focus trapping; call `.open()` / `.close()` | [dialog.html](https://dustinpoissant.github.io/kempo-ui/components/dialog.html) |
|
|
61
62
|
| `<k-dropdown>` | `Dropdown.js` | Action menu with keyboard navigation | [dropdown.html](https://dustinpoissant.github.io/kempo-ui/components/dropdown.html) |
|
|
62
63
|
| `<k-filter-list>` `<k-filter-item>` | `FilterList.js` `FilterItem.js` | Filter/search list; items have `filter` attribute with keywords | [filter-list.html](https://dustinpoissant.github.io/kempo-ui/components/filter-list.html) |
|
package/package.json
CHANGED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Context Component
|
|
3
|
+
|
|
4
|
+
A non-rendering state container for sharing data between sibling and descendant
|
|
5
|
+
components, similar in purpose to React's useContext. Place it as an ancestor
|
|
6
|
+
of components that need shared state; children locate it via closest('k-context').
|
|
7
|
+
*/
|
|
8
|
+
import { html } from '../lit-all.min.js';
|
|
9
|
+
import LightComponent from './LightComponent.js';
|
|
10
|
+
|
|
11
|
+
export default class Context extends LightComponent {
|
|
12
|
+
static properties = {
|
|
13
|
+
data: { type: String, reflect: true }
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
constructor() {
|
|
17
|
+
super();
|
|
18
|
+
this.data = '{}';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
Lifecycle Callbacks
|
|
23
|
+
*/
|
|
24
|
+
connectedCallback() {
|
|
25
|
+
super.connectedCallback();
|
|
26
|
+
this.style.display = 'contents';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/*
|
|
30
|
+
Public Methods
|
|
31
|
+
*/
|
|
32
|
+
set(key, value) {
|
|
33
|
+
const obj = JSON.parse(this.data || '{}');
|
|
34
|
+
const isNew = !(key in obj);
|
|
35
|
+
const oldValue = obj[key];
|
|
36
|
+
obj[key] = value;
|
|
37
|
+
this.data = JSON.stringify(obj);
|
|
38
|
+
this.dispatchEvent(new CustomEvent(isNew ? 'context:create' : 'context:set', {
|
|
39
|
+
detail: isNew ? { key, value } : { key, value, oldValue },
|
|
40
|
+
bubbles: true,
|
|
41
|
+
composed: true
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get(key) {
|
|
46
|
+
return JSON.parse(this.data || '{}')[key];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
has(key) {
|
|
50
|
+
return key in JSON.parse(this.data || '{}');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
delete(key) {
|
|
54
|
+
const obj = JSON.parse(this.data || '{}');
|
|
55
|
+
if(!(key in obj)) return;
|
|
56
|
+
const value = obj[key];
|
|
57
|
+
delete obj[key];
|
|
58
|
+
this.data = JSON.stringify(obj);
|
|
59
|
+
this.dispatchEvent(new CustomEvent('context:delete', {
|
|
60
|
+
detail: { key, value },
|
|
61
|
+
bubbles: true,
|
|
62
|
+
composed: true
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
clear() {
|
|
67
|
+
for(const key of Object.keys(JSON.parse(this.data || '{}'))){
|
|
68
|
+
this.delete(key);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
getData() {
|
|
73
|
+
return JSON.parse(this.data || '{}');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/*
|
|
77
|
+
Rendering
|
|
78
|
+
*/
|
|
79
|
+
renderLightDom() {
|
|
80
|
+
return html``;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
customElements.define('k-context', Context);
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import Context from '../../src/components/Context.js';
|
|
2
|
+
import LightComponent from '../../src/components/LightComponent.js';
|
|
3
|
+
|
|
4
|
+
const createContext = async () => {
|
|
5
|
+
const container = document.createElement('div');
|
|
6
|
+
container.innerHTML = `<k-context></k-context>`;
|
|
7
|
+
document.body.appendChild(container);
|
|
8
|
+
const el = container.querySelector('k-context');
|
|
9
|
+
await el.updateComplete;
|
|
10
|
+
return { container, el };
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const cleanup = (container) => {
|
|
14
|
+
if(container && container.parentNode){
|
|
15
|
+
container.parentNode.removeChild(container);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default {
|
|
20
|
+
/*
|
|
21
|
+
Element Creation
|
|
22
|
+
*/
|
|
23
|
+
'should create context element': async ({pass, fail}) => {
|
|
24
|
+
const { container, el } = await createContext();
|
|
25
|
+
if(!(el instanceof Context)){
|
|
26
|
+
cleanup(container);
|
|
27
|
+
return fail('Element should be instance of Context');
|
|
28
|
+
}
|
|
29
|
+
cleanup(container);
|
|
30
|
+
pass('Context element created correctly');
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
'should extend LightComponent': async ({pass, fail}) => {
|
|
34
|
+
const { container, el } = await createContext();
|
|
35
|
+
if(!(el instanceof LightComponent)){
|
|
36
|
+
cleanup(container);
|
|
37
|
+
return fail('Context should extend LightComponent');
|
|
38
|
+
}
|
|
39
|
+
cleanup(container);
|
|
40
|
+
pass('Context extends LightComponent');
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
'should have display contents': async ({pass, fail}) => {
|
|
44
|
+
const { container, el } = await createContext();
|
|
45
|
+
const display = getComputedStyle(el).display;
|
|
46
|
+
if(display !== 'contents'){
|
|
47
|
+
cleanup(container);
|
|
48
|
+
return fail(`Expected display to be contents, got "${display}"`);
|
|
49
|
+
}
|
|
50
|
+
cleanup(container);
|
|
51
|
+
pass('Context element has display contents');
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
'should have default data of \'{}\'': async ({pass, fail}) => {
|
|
55
|
+
const { container, el } = await createContext();
|
|
56
|
+
if(el.data !== '{}'){
|
|
57
|
+
cleanup(container);
|
|
58
|
+
return fail(`Expected data to be '{}', got '${el.data}'`);
|
|
59
|
+
}
|
|
60
|
+
cleanup(container);
|
|
61
|
+
pass('Default data is \'{}\'');
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
'data attribute reflects JSON state': async ({pass, fail}) => {
|
|
65
|
+
const { container, el } = await createContext();
|
|
66
|
+
el.set('count', 5);
|
|
67
|
+
await el.updateComplete;
|
|
68
|
+
let parsed;
|
|
69
|
+
try { parsed = JSON.parse(el.getAttribute('data')); } catch(e) {
|
|
70
|
+
cleanup(container);
|
|
71
|
+
return fail('data attribute is not valid JSON');
|
|
72
|
+
}
|
|
73
|
+
if(parsed.count !== 5){
|
|
74
|
+
cleanup(container);
|
|
75
|
+
return fail(`Expected data attribute to contain count:5, got ${el.getAttribute('data')}`);
|
|
76
|
+
}
|
|
77
|
+
cleanup(container);
|
|
78
|
+
pass('data attribute reflects JSON state');
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
/*
|
|
82
|
+
set() and get()
|
|
83
|
+
*/
|
|
84
|
+
'should set and get a value': async ({pass, fail}) => {
|
|
85
|
+
const { container, el } = await createContext();
|
|
86
|
+
el.set('user', { name: 'Alice' });
|
|
87
|
+
const value = el.get('user');
|
|
88
|
+
if(!value || value.name !== 'Alice'){
|
|
89
|
+
cleanup(container);
|
|
90
|
+
return fail(`Expected {name:'Alice'}, got ${JSON.stringify(value)}`);
|
|
91
|
+
}
|
|
92
|
+
cleanup(container);
|
|
93
|
+
pass('set and get work correctly');
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
/*
|
|
97
|
+
has()
|
|
98
|
+
*/
|
|
99
|
+
'has() returns true for existing key': async ({pass, fail}) => {
|
|
100
|
+
const { container, el } = await createContext();
|
|
101
|
+
el.set('theme', 'dark');
|
|
102
|
+
if(!el.has('theme')){
|
|
103
|
+
cleanup(container);
|
|
104
|
+
return fail('has() should return true for existing key');
|
|
105
|
+
}
|
|
106
|
+
cleanup(container);
|
|
107
|
+
pass('has() returns true for existing key');
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
'has() returns false for missing key': async ({pass, fail}) => {
|
|
111
|
+
const { container, el } = await createContext();
|
|
112
|
+
if(el.has('missing')){
|
|
113
|
+
cleanup(container);
|
|
114
|
+
return fail('has() should return false for missing key');
|
|
115
|
+
}
|
|
116
|
+
cleanup(container);
|
|
117
|
+
pass('has() returns false for missing key');
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
/*
|
|
121
|
+
Events
|
|
122
|
+
*/
|
|
123
|
+
'set() fires context:create for new key': async ({pass, fail}) => {
|
|
124
|
+
const { container, el } = await createContext();
|
|
125
|
+
let received = null;
|
|
126
|
+
el.addEventListener('context:create', e => { received = e.detail; });
|
|
127
|
+
el.set('count', 0);
|
|
128
|
+
if(!received || received.key !== 'count' || received.value !== 0){
|
|
129
|
+
cleanup(container);
|
|
130
|
+
return fail(`Expected context:create with {key:'count',value:0}, got ${JSON.stringify(received)}`);
|
|
131
|
+
}
|
|
132
|
+
cleanup(container);
|
|
133
|
+
pass('context:create fired for new key');
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
'set() fires context:set for existing key': async ({pass, fail}) => {
|
|
137
|
+
const { container, el } = await createContext();
|
|
138
|
+
el.set('count', 0);
|
|
139
|
+
let received = null;
|
|
140
|
+
el.addEventListener('context:set', e => { received = e.detail; });
|
|
141
|
+
el.set('count', 1);
|
|
142
|
+
if(!received || received.key !== 'count' || received.value !== 1 || received.oldValue !== 0){
|
|
143
|
+
cleanup(container);
|
|
144
|
+
return fail(`Expected context:set with {key:'count',value:1,oldValue:0}, got ${JSON.stringify(received)}`);
|
|
145
|
+
}
|
|
146
|
+
cleanup(container);
|
|
147
|
+
pass('context:set fired for existing key update');
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
'set() does not fire context:create for second set': async ({pass, fail}) => {
|
|
151
|
+
const { container, el } = await createContext();
|
|
152
|
+
el.set('count', 0);
|
|
153
|
+
let createFired = false;
|
|
154
|
+
el.addEventListener('context:create', () => { createFired = true; });
|
|
155
|
+
el.set('count', 1);
|
|
156
|
+
if(createFired){
|
|
157
|
+
cleanup(container);
|
|
158
|
+
return fail('context:create should not fire when updating existing key');
|
|
159
|
+
}
|
|
160
|
+
cleanup(container);
|
|
161
|
+
pass('context:create not fired on update');
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
'delete() removes a key and fires context:delete': async ({pass, fail}) => {
|
|
165
|
+
const { container, el } = await createContext();
|
|
166
|
+
el.set('temp', 42);
|
|
167
|
+
let received = null;
|
|
168
|
+
el.addEventListener('context:delete', e => { received = e.detail; });
|
|
169
|
+
el.delete('temp');
|
|
170
|
+
if(el.has('temp')){
|
|
171
|
+
cleanup(container);
|
|
172
|
+
return fail('Key should be removed after delete()');
|
|
173
|
+
}
|
|
174
|
+
if(!received || received.key !== 'temp' || received.value !== 42){
|
|
175
|
+
cleanup(container);
|
|
176
|
+
return fail(`Expected context:delete with {key:'temp',value:42}, got ${JSON.stringify(received)}`);
|
|
177
|
+
}
|
|
178
|
+
cleanup(container);
|
|
179
|
+
pass('delete() removes key and fires context:delete');
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
'delete() on missing key does nothing': async ({pass, fail}) => {
|
|
183
|
+
const { container, el } = await createContext();
|
|
184
|
+
let fired = false;
|
|
185
|
+
el.addEventListener('context:delete', () => { fired = true; });
|
|
186
|
+
el.delete('nonexistent');
|
|
187
|
+
if(fired){
|
|
188
|
+
cleanup(container);
|
|
189
|
+
return fail('context:delete should not fire for missing key');
|
|
190
|
+
}
|
|
191
|
+
cleanup(container);
|
|
192
|
+
pass('delete() on missing key is a no-op');
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
/*
|
|
196
|
+
clear()
|
|
197
|
+
*/
|
|
198
|
+
'clear() removes all keys and fires context:delete for each': async ({pass, fail}) => {
|
|
199
|
+
const { container, el } = await createContext();
|
|
200
|
+
el.set('a', 1);
|
|
201
|
+
el.set('b', 2);
|
|
202
|
+
const deleted = [];
|
|
203
|
+
el.addEventListener('context:delete', e => deleted.push(e.detail.key));
|
|
204
|
+
el.clear();
|
|
205
|
+
if(Object.keys(el.getData()).length !== 0){
|
|
206
|
+
cleanup(container);
|
|
207
|
+
return fail('getData() should return empty object after clear()');
|
|
208
|
+
}
|
|
209
|
+
if(deleted.length !== 2 || !deleted.includes('a') || !deleted.includes('b')){
|
|
210
|
+
cleanup(container);
|
|
211
|
+
return fail(`Expected context:delete for both keys, got: ${JSON.stringify(deleted)}`);
|
|
212
|
+
}
|
|
213
|
+
cleanup(container);
|
|
214
|
+
pass('clear() removes all keys');
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
/*
|
|
218
|
+
getData()
|
|
219
|
+
*/
|
|
220
|
+
'getData() returns a copy of all data': async ({pass, fail}) => {
|
|
221
|
+
const { container, el } = await createContext();
|
|
222
|
+
el.set('x', 10);
|
|
223
|
+
el.set('y', 20);
|
|
224
|
+
const data = el.getData();
|
|
225
|
+
if(data.x !== 10 || data.y !== 20){
|
|
226
|
+
cleanup(container);
|
|
227
|
+
return fail(`Expected {x:10,y:20}, got ${JSON.stringify(data)}`);
|
|
228
|
+
}
|
|
229
|
+
// ensure it's a copy
|
|
230
|
+
data.x = 999;
|
|
231
|
+
if(el.get('x') !== 10){
|
|
232
|
+
cleanup(container);
|
|
233
|
+
return fail('getData() should return a copy, not a reference');
|
|
234
|
+
}
|
|
235
|
+
cleanup(container);
|
|
236
|
+
pass('getData() returns a shallow copy of all data');
|
|
237
|
+
}
|
|
238
|
+
};
|