kempo-ui 0.4.11 → 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.
Files changed (104) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/components/SegmentedControl.js +16 -0
  3. package/dist/components/Tabs.js +4 -4
  4. package/docs/components/SegmentedControl.html +649 -0
  5. package/docs/components/accordion.html +3 -0
  6. package/docs/components/aside.html +3 -0
  7. package/docs/components/calendar.html +3 -0
  8. package/docs/components/card.html +3 -0
  9. package/docs/components/chat.html +3 -0
  10. package/docs/components/code-editor.html +3 -0
  11. package/docs/components/color-picker.html +3 -0
  12. package/docs/components/combobox.html +3 -0
  13. package/docs/components/content-slider.html +3 -0
  14. package/docs/components/context.html +3 -0
  15. package/docs/components/dialog.html +3 -0
  16. package/docs/components/dropdown.html +3 -0
  17. package/docs/components/filter-list.html +3 -0
  18. package/docs/components/focus-capture.html +3 -0
  19. package/docs/components/html-editor.html +3 -0
  20. package/docs/components/hybrid-component.html +3 -0
  21. package/docs/components/icon.html +3 -0
  22. package/docs/components/import.html +3 -0
  23. package/docs/components/light-component.html +3 -0
  24. package/docs/components/markdown-editor.html +3 -0
  25. package/docs/components/markdown.html +3 -0
  26. package/docs/components/nav-spacer.html +3 -0
  27. package/docs/components/nav.html +3 -0
  28. package/docs/components/photo-viewer.html +3 -0
  29. package/docs/components/progress.html +3 -0
  30. package/docs/components/resize.html +3 -0
  31. package/docs/components/segmented-control.html +728 -0
  32. package/docs/components/shadow-component.html +3 -0
  33. package/docs/components/show-more.html +3 -0
  34. package/docs/components/slider.html +3 -0
  35. package/docs/components/sortable.html +3 -0
  36. package/docs/components/speech-to-text.html +3 -0
  37. package/docs/components/spinner.html +3 -0
  38. package/docs/components/split.html +3 -0
  39. package/docs/components/table.html +3 -0
  40. package/docs/components/tableControls.html +3 -0
  41. package/docs/components/tableCustomFields.html +3 -0
  42. package/docs/components/tableFetchRecords.html +3 -0
  43. package/docs/components/tableFieldSortHide.html +3 -0
  44. package/docs/components/tablePagination.html +3 -0
  45. package/docs/components/tablePlaceholder.html +3 -0
  46. package/docs/components/tableRecordEditing.html +3 -0
  47. package/docs/components/tableRecordFiltering.html +3 -0
  48. package/docs/components/tableRecordHiding.html +3 -0
  49. package/docs/components/tableRecordSearching.html +3 -0
  50. package/docs/components/tableRecordSelection.html +3 -0
  51. package/docs/components/tableRowControls.html +3 -0
  52. package/docs/components/tableServerSync.html +3 -0
  53. package/docs/components/tableSorting.html +3 -0
  54. package/docs/components/tabs.html +32 -0
  55. package/docs/components/tags.html +3 -0
  56. package/docs/components/text-to-speech.html +3 -0
  57. package/docs/components/theme-select.html +3 -0
  58. package/docs/components/theme-switcher.html +3 -0
  59. package/docs/components/time.html +3 -0
  60. package/docs/components/timestamp.html +3 -0
  61. package/docs/components/toast.html +3 -0
  62. package/docs/components/toggle.html +3 -0
  63. package/docs/components/tree.html +3 -0
  64. package/docs/components/voice-selector.html +3 -0
  65. package/docs/icons/account-circle.svg +1 -1
  66. package/docs/index.html +9 -0
  67. package/docs/src/components/SegmentedControl.js +16 -0
  68. package/docs/src/components/Tabs.js +4 -4
  69. package/docs/utils/context.html +3 -0
  70. package/docs/utils/cookie.html +3 -0
  71. package/docs/utils/debounce.html +3 -0
  72. package/docs/utils/drag.html +3 -0
  73. package/docs/utils/elevation.html +3 -0
  74. package/docs/utils/formatTimestamp.html +3 -0
  75. package/docs/utils/object.html +3 -0
  76. package/docs/utils/propConverters.html +3 -0
  77. package/docs/utils/sanitizeHtml.html +3 -0
  78. package/docs/utils/string.html +3 -0
  79. package/docs/utils/theme.html +3 -0
  80. package/docs/utils/toTitleCase.html +3 -0
  81. package/docs/utils/type.html +3 -0
  82. package/docs/utils/voice.html +3 -0
  83. package/docs/utils/wait.html +3 -0
  84. package/docs-src/components/segmented-control.page.html +236 -0
  85. package/docs-src/components/tabs.page.html +29 -0
  86. package/docs-src/index.page.html +6 -0
  87. package/docs-src/nav.fragment.html +3 -0
  88. package/icons/account-circle.svg +1 -1
  89. package/llms.txt +2 -1
  90. package/package.json +1 -1
  91. package/src/components/SegmentedControl.js +118 -0
  92. package/src/components/Tabs.js +21 -1
  93. package/tasks/released/0004-create-segmented-control-component/component-rendering.png +0 -0
  94. package/tasks/released/0004-create-segmented-control-component/final-component-page.png +0 -0
  95. package/tasks/released/0004-create-segmented-control-component/form-integration-section.png +0 -0
  96. package/tasks/released/0004-create-segmented-control-component/option-element-section.png +0 -0
  97. package/tasks/released/0004-create-segmented-control-component/page-screenshot.png +0 -0
  98. package/tasks/released/0004-create-segmented-control-component/validation-form-integration.png +0 -0
  99. package/tasks/released/0004-create-segmented-control-component/validation-screenshot-1-full-page.png +0 -0
  100. package/tasks/released/0004-create-segmented-control-component/validation-screenshot-2-icon-options.png +0 -0
  101. package/tasks/released/0004-create-segmented-control-component.md +106 -0
  102. package/tasks/released/0005-add-persistent-id-to-tabs.md +98 -0
  103. package/tests/components/SegmentedControl.browser-test.js +223 -0
  104. package/tests/components/Tabs.browser-test.js +262 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,30 @@ 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
18
+
19
+ ## [0.4.12] - 2026-05-03
20
+ ### Added
21
+ - **SegmentedControl component**: New form-integrated segmented control component
22
+ - Renders child `<k-sc-option>` elements as a button group using Kempo-CSS utility classes
23
+ - Supports rich content in options including text and icon elements (via `<k-icon>`)
24
+ - Full form integration using ElementInternals API (`formAssociated`) for proper form submission and reset
25
+ - `name` attribute for form field identification
26
+ - `value` property reflects and controls the selected option
27
+ - Dispatches `change` events when selection changes
28
+ - Comprehensive unit test suite with 13 test cases covering element creation, properties, option recognition, selection, styling, and events
29
+ - Complete documentation with examples: Basic Usage, Selected By Default, JavaScript Usage, Icon Options, and Form Integration
30
+ - Test file: `tests/components/SegmentedControl.browser-test.js`
31
+ - Documentation: `docs-src/components/segmented-control.page.html`
8
32
 
9
33
  ## [0.4.11] - 2026-05-01
10
34
  ### Added
@@ -0,0 +1,16 @@
1
+ import{html as t,css as e,unsafeHTML as s}from"../lit-all.min.js";import n from"./ShadowComponent.js";class i extends n{static formAssociated=!0;static properties={value:{type:String,reflect:!0},name:{type:String}};constructor(){if(super(),this.value="",this.name="",this.initialValue="","function"==typeof this.attachInternals)try{this.internals=this.attachInternals()}catch(t){}}connectedCallback(){super.connectedCallback(),this.initialValue=this.getAttribute("value")||"",this.addEventListener("slotchange",this.handleSlotChange,!0);const t=this.closest("form");t&&t.addEventListener("reset",this.handleReset,!0)}disconnectedCallback(){super.disconnectedCallback(),this.removeEventListener("slotchange",this.handleSlotChange,!0);const t=this.closest("form");t&&t.removeEventListener("reset",this.handleReset,!0)}updated(t){if(super.updated(t),t.has("value")){if(this.internals&&"function"==typeof this.internals.setFormValue)try{this.internals.setFormValue(this.value||null)}catch(t){}this.dispatchEvent(new CustomEvent("change",{detail:{value:this.value},bubbles:!0}))}}handleSlotChange=()=>{this.requestUpdate()};handleReset=()=>{this.value=this.initialValue};handleButtonClick=t=>{this.value=t};getOptions=()=>Array.from(this.children).filter(t=>"K-SC-OPTION"===t.tagName);render(){const e=this.getOptions();return t`
2
+ <div class="btn-grp">
3
+ ${e.map(e=>{const n=e.getAttribute("value")||"",i=n===this.value;return t`
4
+ <button
5
+ class="${i?"primary":""}"
6
+ @click=${()=>this.handleButtonClick(n)}
7
+ ?aria-pressed=${i}
8
+ >${s(e.innerHTML)}</button>
9
+ `})}
10
+ </div>
11
+ <slot style="display:none;"></slot>
12
+ `}static styles=e`
13
+ :host {
14
+ display: block;
15
+ }
16
+ `}class a extends HTMLElement{connectedCallback(){this.hasAttribute("value")||this.setAttribute("value","")}}export default i;customElements.define("k-segmented-control",i),customElements.define("k-sc-option",a);
@@ -1,4 +1,4 @@
1
- import{html as t,css as e}from"../lit-all.min.js";import o from"./ShadowComponent.js";import{boolExists as s}from"../utils/propConverters.js";export class Tabs extends o{static properties={active:{type:String,reflect:!0},fixedHeight:{type:Boolean,reflect:!0,attribute:"fixed-height",converter:s}};constructor(){super(),this.active="",this.fixedHeight=!1}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){super.updated(t),t.has("active")&&this.updateActiveElements()}setupScrollListeners(){const t=this.shadowRoot.getElementById("tabs"),e=this.shadowRoot.getElementById("scroll-left"),o=this.shadowRoot.getElementById("scroll-right");t.addEventListener("scroll",()=>this.updateScrollIndicators()),e.addEventListener("click",()=>{t.scrollBy({left:-200,behavior:"smooth"})}),o.addEventListener("click",()=>{t.scrollBy({left:200,behavior:"smooth"})})}updateScrollIndicators(){const t=this.shadowRoot.getElementById("tabs"),e=this.shadowRoot.getElementById("scroll-left"),o=this.shadowRoot.getElementById("scroll-right"),s=t.scrollLeft>0,r=t.scrollLeft<t.scrollWidth-t.clientWidth;e.classList.toggle("visible",s),o.classList.toggle("visible",r)}updateActiveElements(){const t=this.getActiveTab();t&&(t.active=!1);const e=this.getActiveContent();e&&(e.active=!1);const o=this.getTab(this.active);o&&(o.active=!0);const s=this.getContent(this.active);s&&(s.active=!0),this.dispatchEvent(new CustomEvent("tab",{detail:{tab:this.active},bubbles:!0}))}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 o=parseInt(t);o||(o=0),e=this.querySelectorAll("k-tab")[o]}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 o=parseInt(t);o||(o=0),e=this.querySelectorAll("k-tab-content")[o]}return e}getActiveContent(){return this.querySelector(":scope > k-tab-content[active]")}static styles=e`
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 o{static properties={active:{type:Boolean,reflect:!0,converter:s},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`
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 o{static properties={active:{type:Boolean,reflect:!0,converter:s},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`
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 o{constructor(){super(),this.slot="tabs"}static styles=e`
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;