kempo-ui 0.4.12 → 0.4.13
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/CHANGELOG.md +10 -0
- package/dist/components/Tabs.js +4 -4
- package/docs/components/tabs.html +29 -0
- package/docs/src/components/Tabs.js +4 -4
- package/docs-src/components/tabs.page.html +29 -0
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/src/components/Tabs.js +21 -1
- package/tasks/released/0005-add-persistent-id-to-tabs.md +98 -0
- package/tests/components/Tabs.browser-test.js +262 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
|
+
### Added
|
|
9
|
+
- **Tabs component**: `persistent-id` attribute for storing/restoring active tab state
|
|
10
|
+
- New `persistentId` property (String type, reflects to `persistent-id` attribute)
|
|
11
|
+
- Active tab state is automatically saved to localStorage when it changes
|
|
12
|
+
- Previously active tab is automatically restored on page load
|
|
13
|
+
- Fires `restored` event when state is restored from localStorage
|
|
14
|
+
- Fires `tab` event on both manual change and state restoration
|
|
15
|
+
- Supports multiple Tabs components with different `persistent-id` values for independent state management
|
|
16
|
+
- Safe fallback when localStorage is unavailable
|
|
17
|
+
- New unit test suite with 6 test cases covering all persistence scenarios
|
|
8
18
|
|
|
9
19
|
## [0.4.12] - 2026-05-03
|
|
10
20
|
### Added
|
package/dist/components/Tabs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{html as t,css as e}from"../lit-all.min.js";import
|
|
1
|
+
import{html as t,css as e}from"../lit-all.min.js";import s from"./ShadowComponent.js";import{boolExists as o}from"../utils/propConverters.js";export class Tabs extends s{static properties={active:{type:String,reflect:!0},fixedHeight:{type:Boolean,reflect:!0,attribute:"fixed-height",converter:o},persistentId:{type:String,reflect:!0,attribute:"persistent-id"}};constructor(){super(),this.active="",this.fixedHeight=!1,this.persistentId=null}firstUpdated(){if(super.firstUpdated(),!this.active){const t=this.querySelector("k-tab-content");t&&(this.active=t.name)}this.setupScrollListeners(),this.updateScrollIndicators(),new ResizeObserver(()=>this.updateScrollIndicators()).observe(this.shadowRoot.getElementById("tabs"))}updated(t){if(super.updated(t),t.has("persistentId")&&this.persistentId&&window?.localStorage){const t=`tabs-persistent-id-${this.persistentId}`,e=window.localStorage.getItem(t);null!==e&&(this.active=e,this.dispatchEvent(new CustomEvent("restored",{detail:{tab:e},bubbles:!0})))}t.has("active")&&this.updateActiveElements()}setupScrollListeners(){const t=this.shadowRoot.getElementById("tabs"),e=this.shadowRoot.getElementById("scroll-left"),s=this.shadowRoot.getElementById("scroll-right");t.addEventListener("scroll",()=>this.updateScrollIndicators()),e.addEventListener("click",()=>{t.scrollBy({left:-200,behavior:"smooth"})}),s.addEventListener("click",()=>{t.scrollBy({left:200,behavior:"smooth"})})}updateScrollIndicators(){const t=this.shadowRoot.getElementById("tabs"),e=this.shadowRoot.getElementById("scroll-left"),s=this.shadowRoot.getElementById("scroll-right"),o=t.scrollLeft>0,i=t.scrollLeft<t.scrollWidth-t.clientWidth;e.classList.toggle("visible",o),s.classList.toggle("visible",i)}updateActiveElements(){const t=this.getActiveTab();t&&(t.active=!1);const e=this.getActiveContent();e&&(e.active=!1);const s=this.getTab(this.active);s&&(s.active=!0);const o=this.getContent(this.active);if(o&&(o.active=!0),this.dispatchEvent(new CustomEvent("tab",{detail:{tab:this.active},bubbles:!0})),this.persistentId&&window?.localStorage){const t=`tabs-persistent-id-${this.persistentId}`;window.localStorage.setItem(t,this.active)}}get contents(){return[...this.querySelectorAll(":scope > k-tab-content")]}get tabs(){return[...this.querySelectorAll(":scope > k-tab")]}getTab(t){let e;if("string"==typeof t&&(e=this.querySelector(`k-tab[for="${t}"]`)),!e){let s=parseInt(t);s||(s=0),e=this.querySelectorAll("k-tab")[s]}return e}getActiveTab(){return this.querySelector(":scope > k-tab[active]")}getContent(t){let e;if("string"==typeof t&&(e=this.querySelector(`k-tab-content[name="${t}"]`)),!e){let s=parseInt(t);s||(s=0),e=this.querySelectorAll("k-tab-content")[s]}return e}getActiveContent(){return this.querySelector(":scope > k-tab-content[active]")}static styles=e`
|
|
2
2
|
:host {
|
|
3
3
|
display: block;
|
|
4
4
|
width: 100%;
|
|
@@ -99,7 +99,7 @@ import{html as t,css as e}from"../lit-all.min.js";import o from"./ShadowComponen
|
|
|
99
99
|
<slot></slot>
|
|
100
100
|
</div>
|
|
101
101
|
</div>
|
|
102
|
-
`}}export class Tab extends
|
|
102
|
+
`}}export class Tab extends s{static properties={active:{type:Boolean,reflect:!0,converter:o},for:{type:String,reflect:!0}};constructor(){super(),this.active=!1,this.for="",this.slot="tabs"}handleClick=()=>{if(!this.active){const t=this.parentElement;t&&"K-TABS"===t.tagName&&(t.active=this.for||t.tabs.indexOf(this).toString())}};get tabs(){return"K-TABS"===this.parentElement?.tagName?this.parentElement:null}render(){return t`
|
|
103
103
|
<button id="button" @click=${this.handleClick}>
|
|
104
104
|
<slot></slot>
|
|
105
105
|
</button>
|
|
@@ -131,7 +131,7 @@ import{html as t,css as e}from"../lit-all.min.js";import o from"./ShadowComponen
|
|
|
131
131
|
:host([active]) #button {
|
|
132
132
|
color: var(--tc_primary);
|
|
133
133
|
}
|
|
134
|
-
`}export class TabContent extends
|
|
134
|
+
`}export class TabContent extends s{static properties={active:{type:Boolean,reflect:!0,converter:o},name:{type:String,reflect:!0}};constructor(){super(),this.active=!1,this.name=""}get tabs(){return"K-TABS"===this.parentElement?.tagName?this.parentElement:null}render(){return t`<slot></slot>`}static styles=e`
|
|
135
135
|
:host {
|
|
136
136
|
display: block;
|
|
137
137
|
height: 100%;
|
|
@@ -148,7 +148,7 @@ import{html as t,css as e}from"../lit-all.min.js";import o from"./ShadowComponen
|
|
|
148
148
|
:host(:not([active])) {
|
|
149
149
|
display: none;
|
|
150
150
|
}
|
|
151
|
-
`}export class TabSpacer extends
|
|
151
|
+
`}export class TabSpacer extends s{constructor(){super(),this.slot="tabs"}static styles=e`
|
|
152
152
|
:host {
|
|
153
153
|
flex: 1 1 auto !important;
|
|
154
154
|
height: 1px;
|
|
@@ -497,6 +497,7 @@
|
|
|
497
497
|
<a href="#tabSpacers">Tab Spacers</a><br />
|
|
498
498
|
<a href="#scrollingTabs">Scrolling Tabs</a><br />
|
|
499
499
|
<a href="#fixedHeight">Fixed Height</a><br />
|
|
500
|
+
<a href="#persistentState">Persistent State</a><br />
|
|
500
501
|
<h6 class="mt"><a href="#jsRef" class="no-link">JavaScript Reference</a></h6>
|
|
501
502
|
<a href="#constructor">Constructor</a><br />
|
|
502
503
|
<a href="#requirements">Requirements</a><br />
|
|
@@ -643,6 +644,30 @@
|
|
|
643
644
|
</div>
|
|
644
645
|
</div>
|
|
645
646
|
|
|
647
|
+
<h3 id="persistentState"><a href="#persistentState" class="no-link">Persistent State</a></h3>
|
|
648
|
+
<p>Use the <code>persistent-id</code> attribute to automatically save and restore the active tab to browser localStorage. When the component is reloaded, it will restore the previously active tab.</p>
|
|
649
|
+
<div class="row -mx mb">
|
|
650
|
+
<div class="col d-span-6 m-span-12 px">
|
|
651
|
+
<k-card label="HTML">
|
|
652
|
+
<pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">k-tabs</span> <span class="hljs-attr">persistent-id</span>=<span class="hljs-string">"userPreferences"</span>></span><br /> <span class="hljs-tag"><<span class="hljs-name">k-tab</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"account"</span>></span>Account<span class="hljs-tag"></<span class="hljs-name">k-tab</span>></span><br /> <span class="hljs-tag"><<span class="hljs-name">k-tab</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"settings"</span>></span>Settings<span class="hljs-tag"></<span class="hljs-name">k-tab</span>></span><br /> <span class="hljs-tag"><<span class="hljs-name">k-tab-content</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"account"</span>></span><br /> <span class="hljs-tag"><<span class="hljs-name">p</span>></span>Account settings content<span class="hljs-tag"></<span class="hljs-name">p</span>></span><br /> <span class="hljs-tag"></<span class="hljs-name">k-tab-content</span>></span><br /> <span class="hljs-tag"><<span class="hljs-name">k-tab-content</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"settings"</span>></span><br /> <span class="hljs-tag"><<span class="hljs-name">p</span>></span>General settings content<span class="hljs-tag"></<span class="hljs-name">p</span>></span><br /> <span class="hljs-tag"></<span class="hljs-name">k-tab-content</span>></span><br /><span class="hljs-tag"></<span class="hljs-name">k-tabs</span>></span></code></pre>
|
|
653
|
+
</k-card>
|
|
654
|
+
</div>
|
|
655
|
+
<div class="col d-span-6 m-span-12 px">
|
|
656
|
+
<k-card label="Results">
|
|
657
|
+
<k-tabs persistent-id="tabsPersistentExample">
|
|
658
|
+
<k-tab for="account">Account</k-tab>
|
|
659
|
+
<k-tab for="settings">Settings</k-tab>
|
|
660
|
+
<k-tab-content name="account">
|
|
661
|
+
<p>Account settings content</p>
|
|
662
|
+
</k-tab-content>
|
|
663
|
+
<k-tab-content name="settings">
|
|
664
|
+
<p>General settings content</p>
|
|
665
|
+
</k-tab-content>
|
|
666
|
+
</k-tabs>
|
|
667
|
+
</k-card>
|
|
668
|
+
</div>
|
|
669
|
+
</div>
|
|
670
|
+
|
|
646
671
|
<h2 id="jsRef"><a href="#jsRef" class="no-link">JavaScript Reference</a></h2>
|
|
647
672
|
<h3 id="constructor"><a href="#constructor" class="no-link">Constructor</a></h3>
|
|
648
673
|
<h6>Extends <a href="./shadow-component.html">ShadowComponent</a></h6>
|
|
@@ -658,6 +683,8 @@
|
|
|
658
683
|
<p>The name of the active tab. Syncs to <code>active</code> attribute.</p>
|
|
659
684
|
<h5><code>fixedHeight<i>: boolean</i></code></h5>
|
|
660
685
|
<p>Whether the tabs component uses fixed height mode. Syncs to <code>fixed-height</code> attribute.</p>
|
|
686
|
+
<h5><code>persistentId<i>: string</i></code></h5>
|
|
687
|
+
<p>A unique identifier for persisting the active tab state to localStorage. When set, the component will save the active tab name and restore it on reload. Syncs to <code>persistent-id</code> attribute.</p>
|
|
661
688
|
<h5><code>contents<i>: TabContent[]</i></code></h5>
|
|
662
689
|
<p>Returns an array of all tab content elements.</p>
|
|
663
690
|
<h5><code>tabs<i>: Tab[]</i></code></h5>
|
|
@@ -680,6 +707,8 @@
|
|
|
680
707
|
<h3 id="events"><a href="#events" class="no-link">Events</a></h3>
|
|
681
708
|
<h5><code>tab</code></h5>
|
|
682
709
|
<p>Fired when the active tab changes. Detail contains <code>{ tab }</code> with the name of the new active tab.</p>
|
|
710
|
+
<h5><code>restored</code></h5>
|
|
711
|
+
<p>Fired when the active tab is restored from persistent localStorage. Detail contains <code>{ tab }</code> with the name of the restored active tab. Only fires when <code>persistent-id</code> is set and a stored value is found.</p>
|
|
683
712
|
|
|
684
713
|
</k-main>
|
|
685
714
|
<div style="height:33vh"></div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{html as t,css as e}from"../lit-all.min.js";import
|
|
1
|
+
import{html as t,css as e}from"../lit-all.min.js";import s from"./ShadowComponent.js";import{boolExists as o}from"../utils/propConverters.js";export class Tabs extends s{static properties={active:{type:String,reflect:!0},fixedHeight:{type:Boolean,reflect:!0,attribute:"fixed-height",converter:o},persistentId:{type:String,reflect:!0,attribute:"persistent-id"}};constructor(){super(),this.active="",this.fixedHeight=!1,this.persistentId=null}firstUpdated(){if(super.firstUpdated(),!this.active){const t=this.querySelector("k-tab-content");t&&(this.active=t.name)}this.setupScrollListeners(),this.updateScrollIndicators(),new ResizeObserver(()=>this.updateScrollIndicators()).observe(this.shadowRoot.getElementById("tabs"))}updated(t){if(super.updated(t),t.has("persistentId")&&this.persistentId&&window?.localStorage){const t=`tabs-persistent-id-${this.persistentId}`,e=window.localStorage.getItem(t);null!==e&&(this.active=e,this.dispatchEvent(new CustomEvent("restored",{detail:{tab:e},bubbles:!0})))}t.has("active")&&this.updateActiveElements()}setupScrollListeners(){const t=this.shadowRoot.getElementById("tabs"),e=this.shadowRoot.getElementById("scroll-left"),s=this.shadowRoot.getElementById("scroll-right");t.addEventListener("scroll",()=>this.updateScrollIndicators()),e.addEventListener("click",()=>{t.scrollBy({left:-200,behavior:"smooth"})}),s.addEventListener("click",()=>{t.scrollBy({left:200,behavior:"smooth"})})}updateScrollIndicators(){const t=this.shadowRoot.getElementById("tabs"),e=this.shadowRoot.getElementById("scroll-left"),s=this.shadowRoot.getElementById("scroll-right"),o=t.scrollLeft>0,i=t.scrollLeft<t.scrollWidth-t.clientWidth;e.classList.toggle("visible",o),s.classList.toggle("visible",i)}updateActiveElements(){const t=this.getActiveTab();t&&(t.active=!1);const e=this.getActiveContent();e&&(e.active=!1);const s=this.getTab(this.active);s&&(s.active=!0);const o=this.getContent(this.active);if(o&&(o.active=!0),this.dispatchEvent(new CustomEvent("tab",{detail:{tab:this.active},bubbles:!0})),this.persistentId&&window?.localStorage){const t=`tabs-persistent-id-${this.persistentId}`;window.localStorage.setItem(t,this.active)}}get contents(){return[...this.querySelectorAll(":scope > k-tab-content")]}get tabs(){return[...this.querySelectorAll(":scope > k-tab")]}getTab(t){let e;if("string"==typeof t&&(e=this.querySelector(`k-tab[for="${t}"]`)),!e){let s=parseInt(t);s||(s=0),e=this.querySelectorAll("k-tab")[s]}return e}getActiveTab(){return this.querySelector(":scope > k-tab[active]")}getContent(t){let e;if("string"==typeof t&&(e=this.querySelector(`k-tab-content[name="${t}"]`)),!e){let s=parseInt(t);s||(s=0),e=this.querySelectorAll("k-tab-content")[s]}return e}getActiveContent(){return this.querySelector(":scope > k-tab-content[active]")}static styles=e`
|
|
2
2
|
:host {
|
|
3
3
|
display: block;
|
|
4
4
|
width: 100%;
|
|
@@ -99,7 +99,7 @@ import{html as t,css as e}from"../lit-all.min.js";import o from"./ShadowComponen
|
|
|
99
99
|
<slot></slot>
|
|
100
100
|
</div>
|
|
101
101
|
</div>
|
|
102
|
-
`}}export class Tab extends
|
|
102
|
+
`}}export class Tab extends s{static properties={active:{type:Boolean,reflect:!0,converter:o},for:{type:String,reflect:!0}};constructor(){super(),this.active=!1,this.for="",this.slot="tabs"}handleClick=()=>{if(!this.active){const t=this.parentElement;t&&"K-TABS"===t.tagName&&(t.active=this.for||t.tabs.indexOf(this).toString())}};get tabs(){return"K-TABS"===this.parentElement?.tagName?this.parentElement:null}render(){return t`
|
|
103
103
|
<button id="button" @click=${this.handleClick}>
|
|
104
104
|
<slot></slot>
|
|
105
105
|
</button>
|
|
@@ -131,7 +131,7 @@ import{html as t,css as e}from"../lit-all.min.js";import o from"./ShadowComponen
|
|
|
131
131
|
:host([active]) #button {
|
|
132
132
|
color: var(--tc_primary);
|
|
133
133
|
}
|
|
134
|
-
`}export class TabContent extends
|
|
134
|
+
`}export class TabContent extends s{static properties={active:{type:Boolean,reflect:!0,converter:o},name:{type:String,reflect:!0}};constructor(){super(),this.active=!1,this.name=""}get tabs(){return"K-TABS"===this.parentElement?.tagName?this.parentElement:null}render(){return t`<slot></slot>`}static styles=e`
|
|
135
135
|
:host {
|
|
136
136
|
display: block;
|
|
137
137
|
height: 100%;
|
|
@@ -148,7 +148,7 @@ import{html as t,css as e}from"../lit-all.min.js";import o from"./ShadowComponen
|
|
|
148
148
|
:host(:not([active])) {
|
|
149
149
|
display: none;
|
|
150
150
|
}
|
|
151
|
-
`}export class TabSpacer extends
|
|
151
|
+
`}export class TabSpacer extends s{constructor(){super(),this.slot="tabs"}static styles=e`
|
|
152
152
|
:host {
|
|
153
153
|
flex: 1 1 auto !important;
|
|
154
154
|
height: 1px;
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
<a href="#tabSpacers">Tab Spacers</a><br />
|
|
10
10
|
<a href="#scrollingTabs">Scrolling Tabs</a><br />
|
|
11
11
|
<a href="#fixedHeight">Fixed Height</a><br />
|
|
12
|
+
<a href="#persistentState">Persistent State</a><br />
|
|
12
13
|
<h6 class="mt"><a href="#jsRef" class="no-link">JavaScript Reference</a></h6>
|
|
13
14
|
<a href="#constructor">Constructor</a><br />
|
|
14
15
|
<a href="#requirements">Requirements</a><br />
|
|
@@ -155,6 +156,30 @@
|
|
|
155
156
|
</div>
|
|
156
157
|
</div>
|
|
157
158
|
|
|
159
|
+
<h3 id="persistentState"><a href="#persistentState" class="no-link">Persistent State</a></h3>
|
|
160
|
+
<p>Use the <code>persistent-id</code> attribute to automatically save and restore the active tab to browser localStorage. When the component is reloaded, it will restore the previously active tab.</p>
|
|
161
|
+
<div class="row -mx mb">
|
|
162
|
+
<div class="col d-span-6 m-span-12 px">
|
|
163
|
+
<k-card label="HTML">
|
|
164
|
+
<pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">k-tabs</span> <span class="hljs-attr">persistent-id</span>=<span class="hljs-string">"userPreferences"</span>></span><br /> <span class="hljs-tag"><<span class="hljs-name">k-tab</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"account"</span>></span>Account<span class="hljs-tag"></<span class="hljs-name">k-tab</span>></span><br /> <span class="hljs-tag"><<span class="hljs-name">k-tab</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"settings"</span>></span>Settings<span class="hljs-tag"></<span class="hljs-name">k-tab</span>></span><br /> <span class="hljs-tag"><<span class="hljs-name">k-tab-content</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"account"</span>></span><br /> <span class="hljs-tag"><<span class="hljs-name">p</span>></span>Account settings content<span class="hljs-tag"></<span class="hljs-name">p</span>></span><br /> <span class="hljs-tag"></<span class="hljs-name">k-tab-content</span>></span><br /> <span class="hljs-tag"><<span class="hljs-name">k-tab-content</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"settings"</span>></span><br /> <span class="hljs-tag"><<span class="hljs-name">p</span>></span>General settings content<span class="hljs-tag"></<span class="hljs-name">p</span>></span><br /> <span class="hljs-tag"></<span class="hljs-name">k-tab-content</span>></span><br /><span class="hljs-tag"></<span class="hljs-name">k-tabs</span>></span></code></pre>
|
|
165
|
+
</k-card>
|
|
166
|
+
</div>
|
|
167
|
+
<div class="col d-span-6 m-span-12 px">
|
|
168
|
+
<k-card label="Results">
|
|
169
|
+
<k-tabs persistent-id="tabsPersistentExample">
|
|
170
|
+
<k-tab for="account">Account</k-tab>
|
|
171
|
+
<k-tab for="settings">Settings</k-tab>
|
|
172
|
+
<k-tab-content name="account">
|
|
173
|
+
<p>Account settings content</p>
|
|
174
|
+
</k-tab-content>
|
|
175
|
+
<k-tab-content name="settings">
|
|
176
|
+
<p>General settings content</p>
|
|
177
|
+
</k-tab-content>
|
|
178
|
+
</k-tabs>
|
|
179
|
+
</k-card>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
|
|
158
183
|
<h2 id="jsRef"><a href="#jsRef" class="no-link">JavaScript Reference</a></h2>
|
|
159
184
|
<h3 id="constructor"><a href="#constructor" class="no-link">Constructor</a></h3>
|
|
160
185
|
<h6>Extends <a href="./shadow-component.html">ShadowComponent</a></h6>
|
|
@@ -170,6 +195,8 @@
|
|
|
170
195
|
<p>The name of the active tab. Syncs to <code>active</code> attribute.</p>
|
|
171
196
|
<h5><code>fixedHeight<i>: boolean</i></code></h5>
|
|
172
197
|
<p>Whether the tabs component uses fixed height mode. Syncs to <code>fixed-height</code> attribute.</p>
|
|
198
|
+
<h5><code>persistentId<i>: string</i></code></h5>
|
|
199
|
+
<p>A unique identifier for persisting the active tab state to localStorage. When set, the component will save the active tab name and restore it on reload. Syncs to <code>persistent-id</code> attribute.</p>
|
|
173
200
|
<h5><code>contents<i>: TabContent[]</i></code></h5>
|
|
174
201
|
<p>Returns an array of all tab content elements.</p>
|
|
175
202
|
<h5><code>tabs<i>: Tab[]</i></code></h5>
|
|
@@ -192,6 +219,8 @@
|
|
|
192
219
|
<h3 id="events"><a href="#events" class="no-link">Events</a></h3>
|
|
193
220
|
<h5><code>tab</code></h5>
|
|
194
221
|
<p>Fired when the active tab changes. Detail contains <code>{ tab }</code> with the name of the new active tab.</p>
|
|
222
|
+
<h5><code>restored</code></h5>
|
|
223
|
+
<p>Fired when the active tab is restored from persistent localStorage. Detail contains <code>{ tab }</code> with the name of the restored active tab. Only fires when <code>persistent-id</code> is set and a stored value is found.</p>
|
|
195
224
|
</content>
|
|
196
225
|
<content location="scripts">
|
|
197
226
|
<script type="module" src="{{pathToRoot}}src/components/Tabs.js"></script>
|
package/llms.txt
CHANGED
|
@@ -84,7 +84,7 @@ import { html, css, LitElement } from 'kempo-ui/src/lit-all.min.js';
|
|
|
84
84
|
| `<k-spinner>` | `Spinner.js` | Loading indicator; `size` (xs/sm/md/lg/xl), `variant` (spinner/dots/bar) | [spinner.html](https://dustinpoissant.github.io/kempo-ui/components/spinner.html) |
|
|
85
85
|
| `<k-split>` | `Split.js` | Resizable split panes | [split.html](https://dustinpoissant.github.io/kempo-ui/components/split.html) |
|
|
86
86
|
| `<k-table>` | `Table.js` | Data table with sorting, filtering, pagination, row selection, server sync; `placeholder` (default "No Records") shown when empty; `filtered-placeholder` shown when all records are filtered/hidden | [table.html](https://dustinpoissant.github.io/kempo-ui/components/table.html) |
|
|
87
|
-
| `<k-tabs>` `<k-tab>` `<k-tab-content>` `<k-tab-spacer>` | `Tabs.js` | Tabbed interface; `<k-tab for-content="id">` links to `<k-tab-content name="id"
|
|
87
|
+
| `<k-tabs>` `<k-tab>` `<k-tab-content>` `<k-tab-spacer>` | `Tabs.js` | Tabbed interface; `<k-tab for-content="id">` links to `<k-tab-content name="id">`; `persistent-id` attribute persists active tab to localStorage | [tabs.html](https://dustinpoissant.github.io/kempo-ui/components/tabs.html) |
|
|
88
88
|
| `<k-tags>` `<k-tag>` | `Tags.js` | Tag input; add/remove tags; fires `change` | [tags.html](https://dustinpoissant.github.io/kempo-ui/components/tags.html) |
|
|
89
89
|
| `<k-text-to-speech>` | `TextToSpeech.js` | Button that wraps `window.speechSynthesis`; speaks `text` attr or slotted content; `voice`, `language`, `rate`, `pitch`, `volume` attrs; methods `speak(text?)` / `stop()` / `toggle()`; fires `start`, `end`, `error` | [text-to-speech.html](https://dustinpoissant.github.io/kempo-ui/components/text-to-speech.html) |
|
|
90
90
|
| `<k-theme-select>` | `ThemeSelect.js` | Dropdown to pick a theme | [theme-select.html](https://dustinpoissant.github.io/kempo-ui/components/theme-select.html) |
|
package/package.json
CHANGED
package/src/components/Tabs.js
CHANGED
|
@@ -8,13 +8,15 @@ import { boolExists } from '../utils/propConverters.js';
|
|
|
8
8
|
export class Tabs extends ShadowComponent {
|
|
9
9
|
static properties = {
|
|
10
10
|
active: { type: String, reflect: true },
|
|
11
|
-
fixedHeight: { type: Boolean, reflect: true, attribute: 'fixed-height', converter: boolExists }
|
|
11
|
+
fixedHeight: { type: Boolean, reflect: true, attribute: 'fixed-height', converter: boolExists },
|
|
12
|
+
persistentId: { type: String, reflect: true, attribute: 'persistent-id' }
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
constructor() {
|
|
15
16
|
super();
|
|
16
17
|
this.active = '';
|
|
17
18
|
this.fixedHeight = false;
|
|
19
|
+
this.persistentId = null;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
/*
|
|
@@ -39,6 +41,18 @@ export class Tabs extends ShadowComponent {
|
|
|
39
41
|
updated(changedProperties) {
|
|
40
42
|
super.updated(changedProperties);
|
|
41
43
|
|
|
44
|
+
if(changedProperties.has('persistentId') && this.persistentId && window?.localStorage) {
|
|
45
|
+
const key = `tabs-persistent-id-${this.persistentId}`;
|
|
46
|
+
const value = window.localStorage.getItem(key);
|
|
47
|
+
if(value !== null) {
|
|
48
|
+
this.active = value;
|
|
49
|
+
this.dispatchEvent(new CustomEvent('restored', {
|
|
50
|
+
detail: { tab: value },
|
|
51
|
+
bubbles: true
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
42
56
|
if(changedProperties.has('active')) {
|
|
43
57
|
this.updateActiveElements();
|
|
44
58
|
}
|
|
@@ -96,6 +110,12 @@ export class Tabs extends ShadowComponent {
|
|
|
96
110
|
detail: { tab: this.active },
|
|
97
111
|
bubbles: true
|
|
98
112
|
}));
|
|
113
|
+
|
|
114
|
+
// Save state if persistentId is set
|
|
115
|
+
if(this.persistentId && window?.localStorage) {
|
|
116
|
+
const key = `tabs-persistent-id-${this.persistentId}`;
|
|
117
|
+
window.localStorage.setItem(key, this.active);
|
|
118
|
+
}
|
|
99
119
|
}
|
|
100
120
|
|
|
101
121
|
get contents() {
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# 0005 - Add persistentId Feature to Tabs Component
|
|
2
|
+
|
|
3
|
+
## Status: Released
|
|
4
|
+
|
|
5
|
+
## Dependency
|
|
6
|
+
|
|
7
|
+
## References
|
|
8
|
+
- [Accordion persistentId implementation](../../src/components/Accordion.js)
|
|
9
|
+
|
|
10
|
+
## Current State
|
|
11
|
+
The Tabs component does not have a `persistentId` feature. The Accordion component has this feature which allows the component to persist the state of open/closed panels to localStorage using a unique identifier key. The Tabs component should have similar functionality to persist the currently active tab to localStorage.
|
|
12
|
+
|
|
13
|
+
## Aceptance Criteria
|
|
14
|
+
- The Tabs component should accept a `persistentId` property (string type, reflects to DOM as `persistent-id` attribute)
|
|
15
|
+
- When `persistentId` is set and the component updates, it should restore the active tab from localStorage using the key format `tabs-persistent-id-{persistentId}`
|
|
16
|
+
- When the active tab changes (via the `active` property), the new active tab state should be saved to localStorage
|
|
17
|
+
- The feature should only work when localStorage is available
|
|
18
|
+
- The restored state should properly update both the Tabs and TabContent elements' `active` properties
|
|
19
|
+
- When the active tab is restored from localStorage, the `tab` event should be fired with the restored tab name in the detail
|
|
20
|
+
- A `restored` event should be fired when the persistent state is successfully restored from localStorage
|
|
21
|
+
|
|
22
|
+
### In-Scope
|
|
23
|
+
- [src/components/Tabs.js](../../src/components/Tabs.js)
|
|
24
|
+
- [docs-src/components/tabs.page.html](../../docs-src/components/tabs.page.html) - documentation updates
|
|
25
|
+
- [llms.txt](../../llms.txt) - update Tabs entry with persistentId property
|
|
26
|
+
- [tests/components/tabs.test.js](../../tests/components/tabs.test.js) - add tests for persistentId feature
|
|
27
|
+
|
|
28
|
+
### Out of Scope
|
|
29
|
+
- Other components should not be modified
|
|
30
|
+
- Accordion component implementation should not be changed
|
|
31
|
+
|
|
32
|
+
## Task Details
|
|
33
|
+
1. Add `persistentId` property to Tabs.js static properties (String type, reflect true, attribute 'persistent-id')
|
|
34
|
+
2. Initialize `persistentId` to null in the constructor
|
|
35
|
+
3. Modify the `updated()` lifecycle method to restore active tab state from localStorage when `persistentId` is set
|
|
36
|
+
4. Modify the `updateActiveElements()` method to save the current active tab state to localStorage when `persistentId` is set
|
|
37
|
+
5. Update the component documentation with examples of using the persistentId feature
|
|
38
|
+
6. Update llms.txt with the new persistentId property for the Tabs component
|
|
39
|
+
7. Add unit tests to verify persistentId functionality (restore on init, save on tab change)
|
|
40
|
+
|
|
41
|
+
## Testing / Validation Plan
|
|
42
|
+
1. Verify that setting `persistent-id` attribute persists the active tab state to localStorage
|
|
43
|
+
2. Verify that reloading the page with a set `persistent-id` restores the previously active tab
|
|
44
|
+
3. Verify that changing the active tab updates localStorage with the new active tab name
|
|
45
|
+
4. Verify that the feature works correctly when localStorage is not available (no errors thrown)
|
|
46
|
+
5. Verify that multiple Tabs components with different `persistent-id` values maintain separate states
|
|
47
|
+
6. Test in the documentation page at http://localhost:8083/components/tabs.html
|
|
48
|
+
|
|
49
|
+
### Testing / Validation Results
|
|
50
|
+
|
|
51
|
+
#### LLM Validation Results
|
|
52
|
+
|
|
53
|
+
**persistentId Property Added**
|
|
54
|
+
✅ PASS - The `persistentId` property (String type, reflects to `persistent-id` attribute) has been successfully added to Tabs.js static properties.
|
|
55
|
+
|
|
56
|
+
**localStorage Key Format**
|
|
57
|
+
✅ PASS - The component uses the correct key format `tabs-persistent-id-{persistentId}` for storing state in localStorage.
|
|
58
|
+
|
|
59
|
+
**State Restoration on Init**
|
|
60
|
+
✅ PASS - When a component with a `persistent-id` is loaded, it correctly restores the previously active tab from localStorage. Verified by:
|
|
61
|
+
- Setting active tab to "settings"
|
|
62
|
+
- Confirming it's saved to localStorage as `tabs-persistent-id-tabsPersistentExample = "settings"`
|
|
63
|
+
- Reloading the page
|
|
64
|
+
- Verifying the active tab is restored to "settings"
|
|
65
|
+
|
|
66
|
+
**State Persistence on Change**
|
|
67
|
+
✅ PASS - When the active tab changes, the new state is immediately saved to localStorage. Verified by changing tabs and checking localStorage updates.
|
|
68
|
+
|
|
69
|
+
**tab Event Fires on Restoration**
|
|
70
|
+
✅ PASS - The `tab` event is dispatched when state is restored from localStorage, allowing consumers to react to restored state. (Note: Verified through code inspection; event listener testing would require interactive browser testing)
|
|
71
|
+
|
|
72
|
+
**restored Event Fires on Restoration**
|
|
73
|
+
✅ PASS - A new `restored` event is dispatched when persistent state is successfully restored from localStorage. (Note: Verified through code inspection)
|
|
74
|
+
|
|
75
|
+
**localStorage Unavailability Handling**
|
|
76
|
+
✅ PASS - Code includes null-safe checks for localStorage (`window?.localStorage`) to prevent errors when localStorage is unavailable.
|
|
77
|
+
|
|
78
|
+
**Multiple Components with Different IDs**
|
|
79
|
+
✅ PASS - The implementation supports multiple Tabs components with different `persistent-id` values maintaining separate states, as they use unique localStorage keys.
|
|
80
|
+
|
|
81
|
+
**Documentation Updates**
|
|
82
|
+
✅ PASS - Updated documentation includes:
|
|
83
|
+
- New "Persistent State" example section with code sample and live example
|
|
84
|
+
- persistentId property documented in Properties section
|
|
85
|
+
- Both `tab` and `restored` events documented in Events section
|
|
86
|
+
- Updated llms.txt with persistentId feature description
|
|
87
|
+
|
|
88
|
+
**Unit Tests**
|
|
89
|
+
✅ PASS - Added comprehensive test suite with 6 new test cases covering:
|
|
90
|
+
1. Saving active tab to localStorage
|
|
91
|
+
2. Restoring active tab from localStorage
|
|
92
|
+
3. Firing restored event when state is restored
|
|
93
|
+
4. Firing tab event when state is restored
|
|
94
|
+
5. Handling missing localStorage gracefully
|
|
95
|
+
6. Maintaining separate states for different persistent-id values
|
|
96
|
+
|
|
97
|
+
#### User Validation Results
|
|
98
|
+
I (Dustin Poissant) have validated that this works as described above.
|
|
@@ -843,5 +843,267 @@ export default {
|
|
|
843
843
|
|
|
844
844
|
cleanup(container);
|
|
845
845
|
pass('TabSpacer has flex grow');
|
|
846
|
+
},
|
|
847
|
+
|
|
848
|
+
'persistent-id: should save active tab to localStorage': async ({pass, fail}) => {
|
|
849
|
+
const id = 'test-tabs-save-' + Date.now();
|
|
850
|
+
const key = `tabs-persistent-id-${id}`;
|
|
851
|
+
window.localStorage.removeItem(key);
|
|
852
|
+
|
|
853
|
+
const container = document.createElement('div');
|
|
854
|
+
container.innerHTML = `
|
|
855
|
+
<k-tabs persistent-id="${id}">
|
|
856
|
+
<k-tab for="tab1" slot="tabs">Tab 1</k-tab>
|
|
857
|
+
<k-tab for="tab2" slot="tabs">Tab 2</k-tab>
|
|
858
|
+
<k-tab for="tab3" slot="tabs">Tab 3</k-tab>
|
|
859
|
+
<k-tab-content name="tab1">Content 1</k-tab-content>
|
|
860
|
+
<k-tab-content name="tab2">Content 2</k-tab-content>
|
|
861
|
+
<k-tab-content name="tab3">Content 3</k-tab-content>
|
|
862
|
+
</k-tabs>
|
|
863
|
+
`;
|
|
864
|
+
document.body.appendChild(container);
|
|
865
|
+
|
|
866
|
+
const tabs = container.querySelector('k-tabs');
|
|
867
|
+
await tabs.updateComplete;
|
|
868
|
+
|
|
869
|
+
tabs.active = 'tab2';
|
|
870
|
+
await tabs.updateComplete;
|
|
871
|
+
|
|
872
|
+
const stored = window.localStorage.getItem(key);
|
|
873
|
+
window.localStorage.removeItem(key);
|
|
874
|
+
cleanup(container);
|
|
875
|
+
|
|
876
|
+
if(stored !== 'tab2'){
|
|
877
|
+
fail(`Expected localStorage to contain "tab2", got "${stored}"`);
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
pass('Active tab saved to localStorage');
|
|
882
|
+
},
|
|
883
|
+
|
|
884
|
+
'persistent-id: should restore active tab from localStorage': async ({pass, fail}) => {
|
|
885
|
+
const id = 'test-tabs-restore-' + Date.now();
|
|
886
|
+
const key = `tabs-persistent-id-${id}`;
|
|
887
|
+
window.localStorage.setItem(key, 'tab3');
|
|
888
|
+
|
|
889
|
+
const container = document.createElement('div');
|
|
890
|
+
container.innerHTML = `
|
|
891
|
+
<k-tabs persistent-id="${id}">
|
|
892
|
+
<k-tab for="tab1" slot="tabs">Tab 1</k-tab>
|
|
893
|
+
<k-tab for="tab2" slot="tabs">Tab 2</k-tab>
|
|
894
|
+
<k-tab for="tab3" slot="tabs">Tab 3</k-tab>
|
|
895
|
+
<k-tab-content name="tab1">Content 1</k-tab-content>
|
|
896
|
+
<k-tab-content name="tab2">Content 2</k-tab-content>
|
|
897
|
+
<k-tab-content name="tab3">Content 3</k-tab-content>
|
|
898
|
+
</k-tabs>
|
|
899
|
+
`;
|
|
900
|
+
document.body.appendChild(container);
|
|
901
|
+
|
|
902
|
+
const tabs = container.querySelector('k-tabs');
|
|
903
|
+
await tabs.updateComplete;
|
|
904
|
+
await new Promise(r => setTimeout(r, 50));
|
|
905
|
+
|
|
906
|
+
const tab1 = tabs.querySelector('k-tab[for="tab1"]');
|
|
907
|
+
const tab2 = tabs.querySelector('k-tab[for="tab2"]');
|
|
908
|
+
const tab3 = tabs.querySelector('k-tab[for="tab3"]');
|
|
909
|
+
const content1 = tabs.querySelector('k-tab-content[name="tab1"]');
|
|
910
|
+
const content2 = tabs.querySelector('k-tab-content[name="tab2"]');
|
|
911
|
+
const content3 = tabs.querySelector('k-tab-content[name="tab3"]');
|
|
912
|
+
|
|
913
|
+
window.localStorage.removeItem(key);
|
|
914
|
+
cleanup(container);
|
|
915
|
+
|
|
916
|
+
if(tabs.active !== 'tab3'){
|
|
917
|
+
fail(`Expected active to be "tab3", got "${tabs.active}"`);
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
if(!tab3.active || content3.active){
|
|
921
|
+
fail('tab3 should be active (restored from localStorage)');
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
if(tab1.active || tab2.active || content1.active || content2.active){
|
|
925
|
+
fail('tab1 and tab2 should not be active');
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
pass('Active tab restored from localStorage');
|
|
930
|
+
},
|
|
931
|
+
|
|
932
|
+
'persistent-id: should fire restored event when state is restored': async ({pass, fail}) => {
|
|
933
|
+
const id = 'test-tabs-event-' + Date.now();
|
|
934
|
+
const key = `tabs-persistent-id-${id}`;
|
|
935
|
+
window.localStorage.setItem(key, 'tab2');
|
|
936
|
+
|
|
937
|
+
const container = document.createElement('div');
|
|
938
|
+
container.innerHTML = `
|
|
939
|
+
<k-tabs persistent-id="${id}">
|
|
940
|
+
<k-tab for="tab1" slot="tabs">Tab 1</k-tab>
|
|
941
|
+
<k-tab for="tab2" slot="tabs">Tab 2</k-tab>
|
|
942
|
+
<k-tab-content name="tab1">Content 1</k-tab-content>
|
|
943
|
+
<k-tab-content name="tab2">Content 2</k-tab-content>
|
|
944
|
+
</k-tabs>
|
|
945
|
+
`;
|
|
946
|
+
document.body.appendChild(container);
|
|
947
|
+
|
|
948
|
+
const tabs = container.querySelector('k-tabs');
|
|
949
|
+
let restoredFired = false;
|
|
950
|
+
let restoredDetail = null;
|
|
951
|
+
|
|
952
|
+
tabs.addEventListener('restored', (e) => {
|
|
953
|
+
restoredFired = true;
|
|
954
|
+
restoredDetail = e.detail;
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
await tabs.updateComplete;
|
|
958
|
+
await new Promise(r => setTimeout(r, 50));
|
|
959
|
+
|
|
960
|
+
window.localStorage.removeItem(key);
|
|
961
|
+
cleanup(container);
|
|
962
|
+
|
|
963
|
+
if(!restoredFired){
|
|
964
|
+
fail('restored event should be fired');
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
if(!restoredDetail || restoredDetail.tab !== 'tab2'){
|
|
968
|
+
fail(`Expected restored event detail to contain tab: "tab2", got ${JSON.stringify(restoredDetail)}`);
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
pass('restored event fired with correct detail');
|
|
973
|
+
},
|
|
974
|
+
|
|
975
|
+
'persistent-id: should fire tab event when restored': async ({pass, fail}) => {
|
|
976
|
+
const id = 'test-tabs-tab-event-' + Date.now();
|
|
977
|
+
const key = `tabs-persistent-id-${id}`;
|
|
978
|
+
window.localStorage.setItem(key, 'tab2');
|
|
979
|
+
|
|
980
|
+
const container = document.createElement('div');
|
|
981
|
+
container.innerHTML = `
|
|
982
|
+
<k-tabs persistent-id="${id}">
|
|
983
|
+
<k-tab for="tab1" slot="tabs">Tab 1</k-tab>
|
|
984
|
+
<k-tab for="tab2" slot="tabs">Tab 2</k-tab>
|
|
985
|
+
<k-tab-content name="tab1">Content 1</k-tab-content>
|
|
986
|
+
<k-tab-content name="tab2">Content 2</k-tab-content>
|
|
987
|
+
</k-tabs>
|
|
988
|
+
`;
|
|
989
|
+
document.body.appendChild(container);
|
|
990
|
+
|
|
991
|
+
const tabs = container.querySelector('k-tabs');
|
|
992
|
+
let tabEventFired = false;
|
|
993
|
+
let tabDetail = null;
|
|
994
|
+
|
|
995
|
+
tabs.addEventListener('tab', (e) => {
|
|
996
|
+
tabEventFired = true;
|
|
997
|
+
tabDetail = e.detail;
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
await tabs.updateComplete;
|
|
1001
|
+
await new Promise(r => setTimeout(r, 50));
|
|
1002
|
+
|
|
1003
|
+
window.localStorage.removeItem(key);
|
|
1004
|
+
cleanup(container);
|
|
1005
|
+
|
|
1006
|
+
if(!tabEventFired){
|
|
1007
|
+
fail('tab event should be fired on restoration');
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
if(!tabDetail || tabDetail.tab !== 'tab2'){
|
|
1011
|
+
fail(`Expected tab event detail to contain tab: "tab2", got ${JSON.stringify(tabDetail)}`);
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
pass('tab event fired on restoration with correct detail');
|
|
1016
|
+
},
|
|
1017
|
+
|
|
1018
|
+
'persistent-id: should handle missing localStorage gracefully': async ({pass, fail}) => {
|
|
1019
|
+
const container = document.createElement('div');
|
|
1020
|
+
container.innerHTML = `
|
|
1021
|
+
<k-tabs persistent-id="test">
|
|
1022
|
+
<k-tab for="tab1" slot="tabs">Tab 1</k-tab>
|
|
1023
|
+
<k-tab for="tab2" slot="tabs">Tab 2</k-tab>
|
|
1024
|
+
<k-tab-content name="tab1">Content 1</k-tab-content>
|
|
1025
|
+
<k-tab-content name="tab2">Content 2</k-tab-content>
|
|
1026
|
+
</k-tabs>
|
|
1027
|
+
`;
|
|
1028
|
+
document.body.appendChild(container);
|
|
1029
|
+
|
|
1030
|
+
const tabs = container.querySelector('k-tabs');
|
|
1031
|
+
const originalLocalStorage = window.localStorage;
|
|
1032
|
+
|
|
1033
|
+
try {
|
|
1034
|
+
Object.defineProperty(window, 'localStorage', {
|
|
1035
|
+
value: null,
|
|
1036
|
+
configurable: true
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
tabs.active = 'tab2';
|
|
1040
|
+
await tabs.updateComplete;
|
|
1041
|
+
|
|
1042
|
+
window.Object.defineProperty(window, 'localStorage', {
|
|
1043
|
+
value: originalLocalStorage,
|
|
1044
|
+
configurable: true
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
cleanup(container);
|
|
1048
|
+
pass('Handles missing localStorage gracefully');
|
|
1049
|
+
} catch(e){
|
|
1050
|
+
window.Object.defineProperty(window, 'localStorage', {
|
|
1051
|
+
value: originalLocalStorage,
|
|
1052
|
+
configurable: true
|
|
1053
|
+
});
|
|
1054
|
+
cleanup(container);
|
|
1055
|
+
fail(`Should not throw error when localStorage is unavailable: ${e.message}`);
|
|
1056
|
+
}
|
|
1057
|
+
},
|
|
1058
|
+
|
|
1059
|
+
'persistent-id: should maintain separate states for different ids': async ({pass, fail}) => {
|
|
1060
|
+
const id1 = 'test-tabs-id1-' + Date.now();
|
|
1061
|
+
const id2 = 'test-tabs-id2-' + Date.now();
|
|
1062
|
+
const key1 = `tabs-persistent-id-${id1}`;
|
|
1063
|
+
const key2 = `tabs-persistent-id-${id2}`;
|
|
1064
|
+
|
|
1065
|
+
window.localStorage.setItem(key1, 'tab1');
|
|
1066
|
+
window.localStorage.setItem(key2, 'tab2');
|
|
1067
|
+
|
|
1068
|
+
const container = document.createElement('div');
|
|
1069
|
+
container.innerHTML = `
|
|
1070
|
+
<div>
|
|
1071
|
+
<k-tabs persistent-id="${id1}">
|
|
1072
|
+
<k-tab for="tab1" slot="tabs">Tab 1</k-tab>
|
|
1073
|
+
<k-tab for="tab2" slot="tabs">Tab 2</k-tab>
|
|
1074
|
+
<k-tab-content name="tab1">Content 1</k-tab-content>
|
|
1075
|
+
<k-tab-content name="tab2">Content 2</k-tab-content>
|
|
1076
|
+
</k-tabs>
|
|
1077
|
+
<k-tabs persistent-id="${id2}">
|
|
1078
|
+
<k-tab for="tab1" slot="tabs">Tab 1</k-tab>
|
|
1079
|
+
<k-tab for="tab2" slot="tabs">Tab 2</k-tab>
|
|
1080
|
+
<k-tab-content name="tab1">Content 1</k-tab-content>
|
|
1081
|
+
<k-tab-content name="tab2">Content 2</k-tab-content>
|
|
1082
|
+
</k-tabs>
|
|
1083
|
+
</div>
|
|
1084
|
+
`;
|
|
1085
|
+
document.body.appendChild(container);
|
|
1086
|
+
|
|
1087
|
+
const tabs1 = container.querySelectorAll('k-tabs')[0];
|
|
1088
|
+
const tabs2 = container.querySelectorAll('k-tabs')[1];
|
|
1089
|
+
|
|
1090
|
+
await tabs1.updateComplete;
|
|
1091
|
+
await tabs2.updateComplete;
|
|
1092
|
+
await new Promise(r => setTimeout(r, 50));
|
|
1093
|
+
|
|
1094
|
+
window.localStorage.removeItem(key1);
|
|
1095
|
+
window.localStorage.removeItem(key2);
|
|
1096
|
+
cleanup(container);
|
|
1097
|
+
|
|
1098
|
+
if(tabs1.active !== 'tab1'){
|
|
1099
|
+
fail(`tabs1 should have active "tab1", got "${tabs1.active}"`);
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
if(tabs2.active !== 'tab2'){
|
|
1103
|
+
fail(`tabs2 should have active "tab2", got "${tabs2.active}"`);
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
pass('Separate persistent-id instances maintain separate states');
|
|
846
1108
|
}
|
|
847
1109
|
};
|