kempo 3.1.0 → 3.2.1

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.
@@ -1 +1 @@
1
- import Component from"./Component.js";import{isInView}from"../utils/element.js";import raf from"../utils/raf.js";export default class LazyComponent extends Component{constructor(e){super(e),new IntersectionObserver((([e])=>{e.intersectionRatio>0&&!this.rendered?this.inViewCallback():this.outOfViewCallback()})).observe(this),this.registerAttribute("unrender",!1)}async connectedCallback(){await this.renderSkelton(),raf((async()=>{await isInView(this)&&await this.inViewCallback()}),2)}async inViewCallback(){return await this.render()}async outOfViewCallback(){return!!this.unrender&&(await this.renderSkelton(),!0)}async renderSkelton(){return this.shadowRoot.innerHTML=`<link rel="stylesheet" href="${Component.pathToKempo}/kempo-styles.css" />${this.skeletonTemplate}<style>${this.shadowStyles}</style>`,this.rendered=!1,!0}get skeletonTemplate(){return'<div style="height: var(--skeleton_height, 1rem)"></div>'}}
1
+ import Component from"./Component.js";import{isInView}from"../utils/element.js";import raf from"../utils/raf.js";const inViewIntervalId=Symbol();export default class LazyComponent extends Component{constructor(e){super(e),this[inViewIntervalId]=null,new IntersectionObserver((([e])=>{e.intersectionRatio>0&&!this.rendered?this.inViewCallback():this.outOfViewCallback()})).observe(this),this.registerAttributes({unrender:!1,inViewInterval:0})}connectedCallback(){this.renderSkelton(),raf((()=>{isInView(this)?this.inViewCallback():this.outOfViewCallback()}),2)}async inViewCallback(){return clearInterval(this[inViewIntervalId]),await this.render()}async outOfViewCallback(){return clearInterval(this[inViewIntervalId]),this[inViewIntervalId]=setInterval((async()=>{await isInView(this)&&this.inViewCallback()}),this.inViewInterval||this.constructor.inViewIntervalTimeout),!!this.unrender&&(await this.renderSkelton(),!0)}async renderSkelton(){return this.shadowRoot.innerHTML=`<link rel="stylesheet" href="${Component.pathToKempo}/kempo-styles.css" />${this.skeletonTemplate}<style>${this.shadowStyles}</style>`,this.rendered=!1,!0}get skeletonTemplate(){return'<div style="height: var(--skeleton_height, 1rem)"></div>'}static inViewIntervalTimeout=1e3}
@@ -1 +1 @@
1
- import Component from"./Component.js";import{dispatchEvent}from"../utils/element.js";const clickHandler=Symbol();export class Tab extends Component{constructor(){super(),this[clickHandler]=()=>{this.active||(this.tabs.active=this.for)},this.slot="tabs",this.registerAttribute("active",!1)}connectedCallback(){super.connectedCallback(),this.shadowRoot.getElementById("button").addEventListener("click",this[clickHandler])}get for(){const t=this.getAttribute("for");return t||this?.tabs.tabs.indexOf(this)}set for(t){t?this.setAttribute("for",t):this.removeAttribute("for")}get tabs(){return"K-TABS"===this.parentElement.tagName?this.parentElement:null}get shadowTemplate(){return`\n <button id="button">\n ${super.shadowTemplate}\n </button>\n `}get shadowStyles(){return"\n :host {\n margin-bottom: -1px;\n flex: 0 0 auto; /* Important: prevent shrinking and growing */\n }\n #button {\n padding: var(--spacer_h);\n background-color: transparent;\n border: none;\n cursor: inherit;\n box-shadow: none;\n color: inherit;\n white-space: nowrap; /* Prevent text wrapping */\n }\n :host(:not([active])) #button {\n cursor: pointer;\n }\n :host([active]){\n border-bottom: 2px solid var(--c_primary);\n margin-bottom: -1px;\n }\n :host([active]) #button {\n color: var(--tc_primary);\n }\n "}}window.customElements.define("k-tab",Tab);export class TabContent extends Component{constructor(){super(),this.registerAttribute("active",!1)}get tabs(){return"K-TABS"===this.parentElement.tagName?this.parentElement:null}get name(){const t=this.getAttribute("name");return t||this?.tabs.contents.indexOf(this)}set name(t){t?this.setAttribute("name",t):this.removeAttribute("name")}get shadowStyles(){return`\n ${super.shadowStyles}\n :host {\n display: block;\n height: 100%;\n max-height: 100%;\n flex: 1 1 auto;\n overflow: hidden;\n }\n :host([active]) {\n display: block;\n }\n :host(:not([active])) {\n display: none;\n }\n `}}window.customElements.define("k-tab-content",TabContent);export class Tabs extends Component{constructor(){super(),this.registerAttribute("active","")}async connectedCallback(){await super.connectedCallback(),this.active||(this.active=`${this.querySelectorAll("k-tab-content")[0].name}`);const t=this.shadowRoot.getElementById("tabs");t.addEventListener("scroll",this.updateScrollIndicators.bind(this)),this.updateScrollIndicators(),new ResizeObserver((()=>this.updateScrollIndicators())).observe(t);const e=this.shadowRoot.getElementById("scroll-left"),n=this.shadowRoot.getElementById("scroll-right");e.addEventListener("click",(()=>{t.scrollBy({left:-200,behavior:"smooth"})})),n.addEventListener("click",(()=>{t.scrollBy({left:200,behavior:"smooth"})}))}updateScrollIndicators(){const t=this.shadowRoot.getElementById("tabs"),e=this.shadowRoot.getElementById("scroll-left"),n=this.shadowRoot.getElementById("scroll-right"),o=t.scrollLeft>0,s=t.scrollLeft<t.scrollWidth-t.clientWidth;e.classList.toggle("visible",o),n.classList.toggle("visible",s)}attributeChangedCallback(t,e,n){if("active"===t){const t=this.getActiveTab();t&&(t.active=!1);const e=this.getActiveContent();e&&(e.active=!1);const o=this.getTab(n);o&&(o.active=!0);const s=this.getContent(n);s&&(s.active=!0),dispatchEvent(this,"tab",{tab:n})}}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 n=parseInt(t);n||(n=0),e=this.querySelectorAll("k-tab")[n]}return e}getActiveTab(){return this.querySelector(':scope > k-tab[active="true"]')}getContent(t){let e;if("string"==typeof t&&(e=this.querySelector(`k-tab-content[name="${t}"]`)),!e){let n=parseInt(t);n||(n=0),e=this.querySelectorAll("k-tab-content")[n]}return e}getActiveContent(){return this.querySelector(':scope > k-tab-content[active="true"]')}get shadowTemplate(){return`\n <div id="wrapper">\n <div id="tabs-container">\n <div id="scroll-left" class="scroll-indicator">\n <svg class="arrow" width="20" height="20" viewBox="0 0 20 20" fill="none">\n <path d="M12 15L7 10L12 5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>\n </svg>\n </div>\n <div id="tabs">\n <slot name="tabs"></slot>\n </div>\n <div id="scroll-right" class="scroll-indicator">\n <svg class="arrow" width="20" height="20" viewBox="0 0 20 20" fill="none">\n <path d="M8 15L13 10L8 5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>\n </svg>\n </div>\n </div>\n <div id="contents">\n ${super.shadowTemplate}\n </div>\n </div>\n `}get shadowStyles(){return`\n ${super.shadowStyles}\n :host {\n display: block;\n width: 100%;\n }\n #wrapper {\n display: flex;\n flex-direction: column;\n width: 100%;\n min-width: 0;\n }\n #tabs-container {\n position: relative;\n border-bottom: 1px solid var(--c_border);\n }\n #tabs {\n display: flex;\n overflow-x: auto;\n overflow-y: hidden;\n }\n #tabs ::slotted(*) {\n flex: 0 0 auto;\n }\n .scroll-indicator {\n position: absolute;\n top: 0;\n bottom: 0;\n width: 72px;\n pointer-events: none;\n opacity: 0;\n transition: opacity 0.2s;\n display: flex;\n align-items: center;\n justify-content: flex-start;\n padding-bottom: 2px;\n }\n .scroll-indicator .arrow {\n color: var(--tc_base);\n z-index: 1;\n }\n .scroll-indicator.visible {\n opacity: 1;\n }\n #scroll-left {\n left: 0;\n background: linear-gradient(90deg, \n var(--c_bg) 0%,\n var(--c_bg) 30%,\n transparent 100%\n );\n }\n #scroll-right {\n right: 0;\n justify-content: flex-end;\n background: linear-gradient(-90deg, \n var(--c_bg) 0%,\n var(--c_bg) 30%,\n transparent 100%\n );\n }\n `}static observedAttributes=[...super.observedAttributes,"active"]}window.customElements.define("k-tabs",Tabs);export class TabSpacer extends Component{constructor(){super(),this.slot="tabs"}get shadowStyles(){return`\n ${super.shadowStyles}\n :host {\n flex: 1 1 auto;\n height: 1px;\n }\n `}}window.customElements.define("k-tab-spacer",TabSpacer);export default{Tab:Tab,TabContent:TabContent,Tabs:Tabs,TabSpacer:TabSpacer};
1
+ import Component from"./Component.js";import{dispatchEvent}from"../utils/element.js";export class Tabs extends Component{constructor(){super(),this.registerAttributes({active:"",fixedHeight:!1})}async connectedCallback(){await super.connectedCallback(),this.active||(this.active=`${this.querySelectorAll("k-tab-content")[0].name}`);const t=this.shadowRoot.getElementById("tabs");t.addEventListener("scroll",this.updateScrollIndicators.bind(this)),this.updateScrollIndicators(),new ResizeObserver((()=>this.updateScrollIndicators())).observe(t);const e=this.shadowRoot.getElementById("scroll-left"),n=this.shadowRoot.getElementById("scroll-right");e.addEventListener("click",(()=>{t.scrollBy({left:-200,behavior:"smooth"})})),n.addEventListener("click",(()=>{t.scrollBy({left:200,behavior:"smooth"})}))}updateScrollIndicators(){const t=this.shadowRoot.getElementById("tabs"),e=this.shadowRoot.getElementById("scroll-left"),n=this.shadowRoot.getElementById("scroll-right"),o=t.scrollLeft>0,s=t.scrollLeft<t.scrollWidth-t.clientWidth;e.classList.toggle("visible",o),n.classList.toggle("visible",s)}attributeChangedCallback(t,e,n){if("active"===t){const t=this.getActiveTab();t&&(t.active=!1);const e=this.getActiveContent();e&&(e.active=!1);const o=this.getTab(n);o&&(o.active=!0);const s=this.getContent(n);s&&(s.active=!0),dispatchEvent(this,"tab",{tab:n})}}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 n=parseInt(t);n||(n=0),e=this.querySelectorAll("k-tab")[n]}return e}getActiveTab(){return this.querySelector(':scope > k-tab[active="true"]')}getContent(t){let e;if("string"==typeof t&&(e=this.querySelector(`k-tab-content[name="${t}"]`)),!e){let n=parseInt(t);n||(n=0),e=this.querySelectorAll("k-tab-content")[n]}return e}getActiveContent(){return this.querySelector(':scope > k-tab-content[active="true"]')}get shadowTemplate(){return`\n <div id="wrapper">\n <div id="tabs-container">\n <div id="scroll-left" class="scroll-indicator">\n <svg class="arrow" width="20" height="20" viewBox="0 0 20 20" fill="none">\n <path d="M12 15L7 10L12 5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>\n </svg>\n </div>\n <div id="tabs">\n <slot name="tabs"></slot>\n </div>\n <div id="scroll-right" class="scroll-indicator">\n <svg class="arrow" width="20" height="20" viewBox="0 0 20 20" fill="none">\n <path d="M8 15L13 10L8 5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>\n </svg>\n </div>\n </div>\n <div id="contents">\n ${super.shadowTemplate}\n </div>\n </div>\n `}get shadowStyles(){return`\n ${super.shadowStyles}\n :host {\n display: block;\n width: 100%;\n }\n #wrapper {\n display: flex;\n flex-direction: column;\n width: 100%;\n min-width: 0;\n }\n #tabs-container {\n position: relative;\n border-bottom: 1px solid var(--c_border);\n }\n #tabs {\n display: flex;\n overflow-x: auto;\n overflow-y: hidden;\n }\n #tabs ::slotted(*) {\n flex: 0 0 auto;\n }\n .scroll-indicator {\n position: absolute;\n top: 0;\n bottom: 0;\n width: 72px;\n pointer-events: none;\n opacity: 0;\n transition: opacity 0.2s;\n display: flex;\n align-items: center;\n justify-content: flex-start;\n padding-bottom: 2px;\n }\n .scroll-indicator .arrow {\n color: var(--tc_base);\n z-index: 1;\n }\n .scroll-indicator.visible {\n opacity: 1;\n }\n #scroll-left {\n left: 0;\n background: linear-gradient(90deg, \n var(--c_bg) 0%,\n var(--c_bg) 30%,\n transparent 100%\n );\n }\n #scroll-right {\n right: 0;\n justify-content: flex-end;\n background: linear-gradient(-90deg, \n var(--c_bg) 0%,\n var(--c_bg) 30%,\n transparent 100%\n );\n }\n :host([fixed-height="true"]) #wrapper {\n height: 100%;\n }\n :host([fixed-height="true"]) #contents {\n height: 100%;\n flex: 1;\n min-height: 1.35rem;\n overflow: auto;\n }\n `}static observedAttributes=[...super.observedAttributes,"active"]}window.customElements.define("k-tabs",Tabs);const clickHandler=Symbol();export class Tab extends Component{constructor(){super(),this[clickHandler]=()=>{this.active||(this.tabs.active=this.for)},this.slot="tabs",this.registerAttribute("active",!1)}connectedCallback(){super.connectedCallback(),this.shadowRoot.getElementById("button").addEventListener("click",this[clickHandler])}get for(){const t=this.getAttribute("for");return t||this?.tabs.tabs.indexOf(this)}set for(t){t?this.setAttribute("for",t):this.removeAttribute("for")}get tabs(){return"K-TABS"===this.parentElement.tagName?this.parentElement:null}get shadowTemplate(){return`\n <button id="button">\n ${super.shadowTemplate}\n </button>\n `}get shadowStyles(){return"\n :host {\n margin-bottom: -1px;\n flex: 0 0 auto; /* Important: prevent shrinking and growing */\n }\n #button {\n padding: var(--spacer_h);\n background-color: transparent;\n border: none;\n cursor: inherit;\n box-shadow: none;\n color: inherit;\n white-space: nowrap; /* Prevent text wrapping */\n }\n :host(:not([active])) #button {\n cursor: pointer;\n }\n :host([active]){\n border-bottom: 2px solid var(--c_primary);\n margin-bottom: -1px;\n }\n :host([active]) #button {\n color: var(--tc_primary);\n }\n "}}window.customElements.define("k-tab",Tab);export class TabContent extends Component{constructor(){super(),this.registerAttribute("active",!1)}get tabs(){return"K-TABS"===this.parentElement.tagName?this.parentElement:null}get name(){const t=this.getAttribute("name");return t||this?.tabs.contents.indexOf(this)}set name(t){t?this.setAttribute("name",t):this.removeAttribute("name")}get shadowStyles(){return`\n ${super.shadowStyles}\n :host {\n display: block;\n height: 100%;\n max-height: 100%;\n flex: 1 1 auto;\n overflow: auto;\n }\n :host([active]) {\n display: block;\n }\n :host(:not([active])) {\n display: none;\n }\n `}}window.customElements.define("k-tab-content",TabContent);export class TabSpacer extends Component{constructor(){super(),this.slot="tabs"}get shadowStyles(){return`\n ${super.shadowStyles}\n :host {\n flex: 1 1 auto !important;\n height: 1px;\n }\n `}}window.customElements.define("k-tab-spacer",TabSpacer);export default{Tab:Tab,TabContent:TabContent,Tabs:Tabs,TabSpacer:TabSpacer};
@@ -41,6 +41,14 @@
41
41
  <p>Extend the <code>LazyComponent</code> class to create a lazy-loaded component. The component will render its content only when it becomes visible in the viewport.</p>
42
42
  <pre><code class="hljs javascript"><span class="hljs-keyword">import</span> LazyComponent <span class="hljs-keyword">from</span> <span class="hljs-string">'kempo'</span>;<br /><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyLazyComponent</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">LazyComponent</span> </span>{<br /> <span class="hljs-keyword">constructor</span>(){<br /> <span class="hljs-keyword">super</span>();<br /> <span class="hljs-comment">// Your Constructor</span><br /> }<br /> <span class="hljs-keyword">async</span> render(force){<br /> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">await</span> <span class="hljs-keyword">super</span>.render(force)){<br /> <span class="hljs-comment">// Your render logic</span><br /> <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br /> }<br /> <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br /> }<br />}<br /><span class="hljs-built_in">window</span>.customElements.define(<span class="hljs-string">'my-lazy-component'</span>, MyLazyComponent);</code></pre>
43
43
 
44
+ <h3 id="basicUsage"><a href="#interval" class="no-link">In-View Interval Timeout</a></h3>
45
+ <p>In addition to using intersection observes to watch for components being scrolled into view, an interval is used to check if the component suddenly become visible via a display style change to itself or a parent. The timeout of the interval can be set for all instances of your LazyComponent by chaning the static member <code>inViewIntervalTimeout</code>.</p>
46
+ <pre><code class="hljs javascript"><span class="hljs-keyword">import</span> LazyComponent <span class="hljs-keyword">from</span> <span class="hljs-string">'kempo'</span>;<br /><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyLazyComponent</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">LazyComponent</span> </span>{<br /> <span class="hljs-keyword">constructor</span>(){<br /> <span class="hljs-keyword">super</span>();<br /> <span class="hljs-comment">// Your Constructor</span><br /> }<br /> <span class="hljs-keyword">async</span> render(force){<br /> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">await</span> <span class="hljs-keyword">super</span>.render(force)){<br /> <span class="hljs-comment">// Your render logic</span><br /> <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br /> }<br /> <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br /> }<br /><br /> <span class="hljs-keyword">static</span> inViewIntervalTimeout = <span class="hljs-number">200</span>; <span class="hljs-comment">// Change the inView checking interval to 200ms instead of the default 1000</span><br />}<br /><span class="hljs-built_in">window</span>.customElements.define(<span class="hljs-string">'my-lazy-component'</span>, MyLazyComponent);</code></pre>
47
+
48
+ <h5>Individal Component interval timer overwrites</h5>
49
+ <p>You can overwrite this interval timeout for individual elements by setting the <code>in-view-interval</code> attribute.</p>
50
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">my-lazy-component</span> <span class="hljs-attr">in-view-interval</span>=<span class="hljs-string">"5000"</span>&gt;</span><br /> <span class="hljs-comment">&lt;!-- The interval to check if it is in-view (due to element/parent visibility change) is now set to check every 5 seconds --&gt;</span><br /><span class="hljs-tag">&lt;/<span class="hljs-name">my-lazy-component</span>&gt;</span></code></pre>
51
+
44
52
  <h2 id="jsRef">JavaScript Reference</h2>
45
53
 
46
54
  <h3 id="constructor"><a href="#constructor" class="no-link">Constructor</a></h3>
@@ -26,6 +26,7 @@
26
26
  <a href="#basicUsage">Basic Usage</a><br />
27
27
  <a href="#tabSpacers">Tab Spacers</a><br />
28
28
  <a href="#scrollingTabs">Scrolling Tabs</a><br />
29
+ <a href="#fixedHeight">Fixed Height</a><br />
29
30
 
30
31
  <h6 class="mt">JavaScript Reference</h6>
31
32
  <a href="#constructor">Constructor</a><br />
@@ -113,6 +114,27 @@
113
114
  <k-tab-content name="tab10"><p>Content 10</p></k-tab-content>
114
115
  </k-tabs>
115
116
 
117
+ <h3 id="fixedHeight"><a href="#fixedHeight" class="no-link">Fixed Height</a></h3>
118
+ <p>Use the <code>fixed-height</code> attribute to make the tabs component fill its container height and enable content scrolling.</p>
119
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">k-tabs</span> <span class="hljs-attr">fixed-height</span>=<span class="hljs-string">"true"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"height: 200px"</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">k-tab</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"fixed1"</span>&gt;</span>Tab 1<span class="hljs-tag">&lt;/<span class="hljs-name">k-tab</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">k-tab</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"fixed2"</span>&gt;</span>Tab 2<span class="hljs-tag">&lt;/<span class="hljs-name">k-tab</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">k-tab-content</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"fixed1"</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>This content will scroll if it's too long.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span><br /> <span class="hljs-tag">&lt;/<span class="hljs-name">k-tab-content</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">k-tab-content</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"fixed2"</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Another tab that is not tall enough.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span><br /> <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span><br /> <span class="hljs-tag">&lt;/<span class="hljs-name">k-tab-content</span>&gt;</span><br /><span class="hljs-tag">&lt;/<span class="hljs-name">k-tabs</span>&gt;</span></code></pre>
120
+ <k-tabs fixed-height="true" style="height: 200px">
121
+ <k-tab for="fixed1">Tab 1</k-tab>
122
+ <k-tab for="fixed2">Tab 2</k-tab>
123
+ <k-tab-content name="fixed1">
124
+ <p>This content will scroll if it's too long.</p>
125
+ <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
126
+ <p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
127
+ <p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.</p>
128
+ <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
129
+ <p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
130
+ <p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.</p>
131
+ </k-tab-content>
132
+ <k-tab-content name="fixed2">
133
+ <p>Another tab that is not tall enough.</p>
134
+ <p>Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.</p>
135
+ </k-tab-content>
136
+ </k-tabs>
137
+
116
138
  <h2 id="jsRef">JavaScript Reference</h2>
117
139
  <h3 id="constructor"><a href="#constructor" class="no-link">Constructor</a></h3>
118
140
  <h6>Extends <a href="./component.html">Component</a></h6>
@@ -124,6 +146,8 @@
124
146
  <h3 id="attributes"><a href="#attributes" class="no-link">Attributes</a></h3>
125
147
  <h5><code>active<i>: string</i></code></h5>
126
148
  <p>The name of the active tab.</p>
149
+ <h5><code>fixed-height<i>: boolean</i></code></h5>
150
+ <p>When true, the tabs component will fill its container height and enable content scrolling.</p>
127
151
  <h3 id="methods"><a href="#methods" class="no-link">Methods</a></h3>
128
152
  <h5><code>getActiveTab()<i>: Tab</i></code></h5>
129
153
  <p>Returns the currently active tab.</p>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H600v-80h160v-480H200v480h160v80H200Zm240 0v-246l-64 64-56-58 160-160 160 160-56 58-64-64v246h-80Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M200-440v-80h560v80H200Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M840-680v480q0 33-23.5 56.5T760-120H200q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h480l160 160Zm-80 34L646-760H200v560h560v-446ZM480-240q50 0 85-35t35-85q0-50-35-85t-85-35q-50 0-85 35t-35 85q0 50 35 85t85 35ZM240-560h360v-160H240v160Zm-40-86v446-560 114Z"/></svg>
@@ -1 +1 @@
1
- import Component from"./Component.js";import{isInView}from"../utils/element.js";import raf from"../utils/raf.js";export default class LazyComponent extends Component{constructor(e){super(e),new IntersectionObserver((([e])=>{e.intersectionRatio>0&&!this.rendered?this.inViewCallback():this.outOfViewCallback()})).observe(this),this.registerAttribute("unrender",!1)}async connectedCallback(){await this.renderSkelton(),raf((async()=>{await isInView(this)&&await this.inViewCallback()}),2)}async inViewCallback(){return await this.render()}async outOfViewCallback(){return!!this.unrender&&(await this.renderSkelton(),!0)}async renderSkelton(){return this.shadowRoot.innerHTML=`<link rel="stylesheet" href="${Component.pathToKempo}/kempo-styles.css" />${this.skeletonTemplate}<style>${this.shadowStyles}</style>`,this.rendered=!1,!0}get skeletonTemplate(){return'<div style="height: var(--skeleton_height, 1rem)"></div>'}}
1
+ import Component from"./Component.js";import{isInView}from"../utils/element.js";import raf from"../utils/raf.js";const inViewIntervalId=Symbol();export default class LazyComponent extends Component{constructor(e){super(e),this[inViewIntervalId]=null,new IntersectionObserver((([e])=>{e.intersectionRatio>0&&!this.rendered?this.inViewCallback():this.outOfViewCallback()})).observe(this),this.registerAttributes({unrender:!1,inViewInterval:0})}connectedCallback(){this.renderSkelton(),raf((()=>{isInView(this)?this.inViewCallback():this.outOfViewCallback()}),2)}async inViewCallback(){return clearInterval(this[inViewIntervalId]),await this.render()}async outOfViewCallback(){return clearInterval(this[inViewIntervalId]),this[inViewIntervalId]=setInterval((async()=>{await isInView(this)&&this.inViewCallback()}),this.inViewInterval||this.constructor.inViewIntervalTimeout),!!this.unrender&&(await this.renderSkelton(),!0)}async renderSkelton(){return this.shadowRoot.innerHTML=`<link rel="stylesheet" href="${Component.pathToKempo}/kempo-styles.css" />${this.skeletonTemplate}<style>${this.shadowStyles}</style>`,this.rendered=!1,!0}get skeletonTemplate(){return'<div style="height: var(--skeleton_height, 1rem)"></div>'}static inViewIntervalTimeout=1e3}
@@ -1 +1 @@
1
- import Component from"./Component.js";import{dispatchEvent}from"../utils/element.js";const clickHandler=Symbol();export class Tab extends Component{constructor(){super(),this[clickHandler]=()=>{this.active||(this.tabs.active=this.for)},this.slot="tabs",this.registerAttribute("active",!1)}connectedCallback(){super.connectedCallback(),this.shadowRoot.getElementById("button").addEventListener("click",this[clickHandler])}get for(){const t=this.getAttribute("for");return t||this?.tabs.tabs.indexOf(this)}set for(t){t?this.setAttribute("for",t):this.removeAttribute("for")}get tabs(){return"K-TABS"===this.parentElement.tagName?this.parentElement:null}get shadowTemplate(){return`\n <button id="button">\n ${super.shadowTemplate}\n </button>\n `}get shadowStyles(){return"\n :host {\n margin-bottom: -1px;\n flex: 0 0 auto; /* Important: prevent shrinking and growing */\n }\n #button {\n padding: var(--spacer_h);\n background-color: transparent;\n border: none;\n cursor: inherit;\n box-shadow: none;\n color: inherit;\n white-space: nowrap; /* Prevent text wrapping */\n }\n :host(:not([active])) #button {\n cursor: pointer;\n }\n :host([active]){\n border-bottom: 2px solid var(--c_primary);\n margin-bottom: -1px;\n }\n :host([active]) #button {\n color: var(--tc_primary);\n }\n "}}window.customElements.define("k-tab",Tab);export class TabContent extends Component{constructor(){super(),this.registerAttribute("active",!1)}get tabs(){return"K-TABS"===this.parentElement.tagName?this.parentElement:null}get name(){const t=this.getAttribute("name");return t||this?.tabs.contents.indexOf(this)}set name(t){t?this.setAttribute("name",t):this.removeAttribute("name")}get shadowStyles(){return`\n ${super.shadowStyles}\n :host {\n display: block;\n height: 100%;\n max-height: 100%;\n flex: 1 1 auto;\n overflow: hidden;\n }\n :host([active]) {\n display: block;\n }\n :host(:not([active])) {\n display: none;\n }\n `}}window.customElements.define("k-tab-content",TabContent);export class Tabs extends Component{constructor(){super(),this.registerAttribute("active","")}async connectedCallback(){await super.connectedCallback(),this.active||(this.active=`${this.querySelectorAll("k-tab-content")[0].name}`);const t=this.shadowRoot.getElementById("tabs");t.addEventListener("scroll",this.updateScrollIndicators.bind(this)),this.updateScrollIndicators(),new ResizeObserver((()=>this.updateScrollIndicators())).observe(t);const e=this.shadowRoot.getElementById("scroll-left"),n=this.shadowRoot.getElementById("scroll-right");e.addEventListener("click",(()=>{t.scrollBy({left:-200,behavior:"smooth"})})),n.addEventListener("click",(()=>{t.scrollBy({left:200,behavior:"smooth"})}))}updateScrollIndicators(){const t=this.shadowRoot.getElementById("tabs"),e=this.shadowRoot.getElementById("scroll-left"),n=this.shadowRoot.getElementById("scroll-right"),o=t.scrollLeft>0,s=t.scrollLeft<t.scrollWidth-t.clientWidth;e.classList.toggle("visible",o),n.classList.toggle("visible",s)}attributeChangedCallback(t,e,n){if("active"===t){const t=this.getActiveTab();t&&(t.active=!1);const e=this.getActiveContent();e&&(e.active=!1);const o=this.getTab(n);o&&(o.active=!0);const s=this.getContent(n);s&&(s.active=!0),dispatchEvent(this,"tab",{tab:n})}}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 n=parseInt(t);n||(n=0),e=this.querySelectorAll("k-tab")[n]}return e}getActiveTab(){return this.querySelector(':scope > k-tab[active="true"]')}getContent(t){let e;if("string"==typeof t&&(e=this.querySelector(`k-tab-content[name="${t}"]`)),!e){let n=parseInt(t);n||(n=0),e=this.querySelectorAll("k-tab-content")[n]}return e}getActiveContent(){return this.querySelector(':scope > k-tab-content[active="true"]')}get shadowTemplate(){return`\n <div id="wrapper">\n <div id="tabs-container">\n <div id="scroll-left" class="scroll-indicator">\n <svg class="arrow" width="20" height="20" viewBox="0 0 20 20" fill="none">\n <path d="M12 15L7 10L12 5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>\n </svg>\n </div>\n <div id="tabs">\n <slot name="tabs"></slot>\n </div>\n <div id="scroll-right" class="scroll-indicator">\n <svg class="arrow" width="20" height="20" viewBox="0 0 20 20" fill="none">\n <path d="M8 15L13 10L8 5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>\n </svg>\n </div>\n </div>\n <div id="contents">\n ${super.shadowTemplate}\n </div>\n </div>\n `}get shadowStyles(){return`\n ${super.shadowStyles}\n :host {\n display: block;\n width: 100%;\n }\n #wrapper {\n display: flex;\n flex-direction: column;\n width: 100%;\n min-width: 0;\n }\n #tabs-container {\n position: relative;\n border-bottom: 1px solid var(--c_border);\n }\n #tabs {\n display: flex;\n overflow-x: auto;\n overflow-y: hidden;\n }\n #tabs ::slotted(*) {\n flex: 0 0 auto;\n }\n .scroll-indicator {\n position: absolute;\n top: 0;\n bottom: 0;\n width: 72px;\n pointer-events: none;\n opacity: 0;\n transition: opacity 0.2s;\n display: flex;\n align-items: center;\n justify-content: flex-start;\n padding-bottom: 2px;\n }\n .scroll-indicator .arrow {\n color: var(--tc_base);\n z-index: 1;\n }\n .scroll-indicator.visible {\n opacity: 1;\n }\n #scroll-left {\n left: 0;\n background: linear-gradient(90deg, \n var(--c_bg) 0%,\n var(--c_bg) 30%,\n transparent 100%\n );\n }\n #scroll-right {\n right: 0;\n justify-content: flex-end;\n background: linear-gradient(-90deg, \n var(--c_bg) 0%,\n var(--c_bg) 30%,\n transparent 100%\n );\n }\n `}static observedAttributes=[...super.observedAttributes,"active"]}window.customElements.define("k-tabs",Tabs);export class TabSpacer extends Component{constructor(){super(),this.slot="tabs"}get shadowStyles(){return`\n ${super.shadowStyles}\n :host {\n flex: 1 1 auto;\n height: 1px;\n }\n `}}window.customElements.define("k-tab-spacer",TabSpacer);export default{Tab:Tab,TabContent:TabContent,Tabs:Tabs,TabSpacer:TabSpacer};
1
+ import Component from"./Component.js";import{dispatchEvent}from"../utils/element.js";export class Tabs extends Component{constructor(){super(),this.registerAttributes({active:"",fixedHeight:!1})}async connectedCallback(){await super.connectedCallback(),this.active||(this.active=`${this.querySelectorAll("k-tab-content")[0].name}`);const t=this.shadowRoot.getElementById("tabs");t.addEventListener("scroll",this.updateScrollIndicators.bind(this)),this.updateScrollIndicators(),new ResizeObserver((()=>this.updateScrollIndicators())).observe(t);const e=this.shadowRoot.getElementById("scroll-left"),n=this.shadowRoot.getElementById("scroll-right");e.addEventListener("click",(()=>{t.scrollBy({left:-200,behavior:"smooth"})})),n.addEventListener("click",(()=>{t.scrollBy({left:200,behavior:"smooth"})}))}updateScrollIndicators(){const t=this.shadowRoot.getElementById("tabs"),e=this.shadowRoot.getElementById("scroll-left"),n=this.shadowRoot.getElementById("scroll-right"),o=t.scrollLeft>0,s=t.scrollLeft<t.scrollWidth-t.clientWidth;e.classList.toggle("visible",o),n.classList.toggle("visible",s)}attributeChangedCallback(t,e,n){if("active"===t){const t=this.getActiveTab();t&&(t.active=!1);const e=this.getActiveContent();e&&(e.active=!1);const o=this.getTab(n);o&&(o.active=!0);const s=this.getContent(n);s&&(s.active=!0),dispatchEvent(this,"tab",{tab:n})}}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 n=parseInt(t);n||(n=0),e=this.querySelectorAll("k-tab")[n]}return e}getActiveTab(){return this.querySelector(':scope > k-tab[active="true"]')}getContent(t){let e;if("string"==typeof t&&(e=this.querySelector(`k-tab-content[name="${t}"]`)),!e){let n=parseInt(t);n||(n=0),e=this.querySelectorAll("k-tab-content")[n]}return e}getActiveContent(){return this.querySelector(':scope > k-tab-content[active="true"]')}get shadowTemplate(){return`\n <div id="wrapper">\n <div id="tabs-container">\n <div id="scroll-left" class="scroll-indicator">\n <svg class="arrow" width="20" height="20" viewBox="0 0 20 20" fill="none">\n <path d="M12 15L7 10L12 5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>\n </svg>\n </div>\n <div id="tabs">\n <slot name="tabs"></slot>\n </div>\n <div id="scroll-right" class="scroll-indicator">\n <svg class="arrow" width="20" height="20" viewBox="0 0 20 20" fill="none">\n <path d="M8 15L13 10L8 5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>\n </svg>\n </div>\n </div>\n <div id="contents">\n ${super.shadowTemplate}\n </div>\n </div>\n `}get shadowStyles(){return`\n ${super.shadowStyles}\n :host {\n display: block;\n width: 100%;\n }\n #wrapper {\n display: flex;\n flex-direction: column;\n width: 100%;\n min-width: 0;\n }\n #tabs-container {\n position: relative;\n border-bottom: 1px solid var(--c_border);\n }\n #tabs {\n display: flex;\n overflow-x: auto;\n overflow-y: hidden;\n }\n #tabs ::slotted(*) {\n flex: 0 0 auto;\n }\n .scroll-indicator {\n position: absolute;\n top: 0;\n bottom: 0;\n width: 72px;\n pointer-events: none;\n opacity: 0;\n transition: opacity 0.2s;\n display: flex;\n align-items: center;\n justify-content: flex-start;\n padding-bottom: 2px;\n }\n .scroll-indicator .arrow {\n color: var(--tc_base);\n z-index: 1;\n }\n .scroll-indicator.visible {\n opacity: 1;\n }\n #scroll-left {\n left: 0;\n background: linear-gradient(90deg, \n var(--c_bg) 0%,\n var(--c_bg) 30%,\n transparent 100%\n );\n }\n #scroll-right {\n right: 0;\n justify-content: flex-end;\n background: linear-gradient(-90deg, \n var(--c_bg) 0%,\n var(--c_bg) 30%,\n transparent 100%\n );\n }\n :host([fixed-height="true"]) #wrapper {\n height: 100%;\n }\n :host([fixed-height="true"]) #contents {\n height: 100%;\n flex: 1;\n min-height: 1.35rem;\n overflow: auto;\n }\n `}static observedAttributes=[...super.observedAttributes,"active"]}window.customElements.define("k-tabs",Tabs);const clickHandler=Symbol();export class Tab extends Component{constructor(){super(),this[clickHandler]=()=>{this.active||(this.tabs.active=this.for)},this.slot="tabs",this.registerAttribute("active",!1)}connectedCallback(){super.connectedCallback(),this.shadowRoot.getElementById("button").addEventListener("click",this[clickHandler])}get for(){const t=this.getAttribute("for");return t||this?.tabs.tabs.indexOf(this)}set for(t){t?this.setAttribute("for",t):this.removeAttribute("for")}get tabs(){return"K-TABS"===this.parentElement.tagName?this.parentElement:null}get shadowTemplate(){return`\n <button id="button">\n ${super.shadowTemplate}\n </button>\n `}get shadowStyles(){return"\n :host {\n margin-bottom: -1px;\n flex: 0 0 auto; /* Important: prevent shrinking and growing */\n }\n #button {\n padding: var(--spacer_h);\n background-color: transparent;\n border: none;\n cursor: inherit;\n box-shadow: none;\n color: inherit;\n white-space: nowrap; /* Prevent text wrapping */\n }\n :host(:not([active])) #button {\n cursor: pointer;\n }\n :host([active]){\n border-bottom: 2px solid var(--c_primary);\n margin-bottom: -1px;\n }\n :host([active]) #button {\n color: var(--tc_primary);\n }\n "}}window.customElements.define("k-tab",Tab);export class TabContent extends Component{constructor(){super(),this.registerAttribute("active",!1)}get tabs(){return"K-TABS"===this.parentElement.tagName?this.parentElement:null}get name(){const t=this.getAttribute("name");return t||this?.tabs.contents.indexOf(this)}set name(t){t?this.setAttribute("name",t):this.removeAttribute("name")}get shadowStyles(){return`\n ${super.shadowStyles}\n :host {\n display: block;\n height: 100%;\n max-height: 100%;\n flex: 1 1 auto;\n overflow: auto;\n }\n :host([active]) {\n display: block;\n }\n :host(:not([active])) {\n display: none;\n }\n `}}window.customElements.define("k-tab-content",TabContent);export class TabSpacer extends Component{constructor(){super(),this.slot="tabs"}get shadowStyles(){return`\n ${super.shadowStyles}\n :host {\n flex: 1 1 auto !important;\n height: 1px;\n }\n `}}window.customElements.define("k-tab-spacer",TabSpacer);export default{Tab:Tab,TabContent:TabContent,Tabs:Tabs,TabSpacer:TabSpacer};
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H600v-80h160v-480H200v480h160v80H200Zm240 0v-246l-64 64-56-58 160-160 160 160-56 58-64-64v246h-80Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M200-440v-80h560v80H200Z"/></svg>
package/icons/save.svg ADDED
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M840-680v480q0 33-23.5 56.5T760-120H200q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h480l160 160Zm-80 34L646-760H200v560h560v-446ZM480-240q50 0 85-35t35-85q0-50-35-85t-85-35q-50 0-85 35t-35 85q0 50 35 85t85 35ZM240-560h360v-160H240v160Zm-40-86v446-560 114Z"/></svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kempo",
3
- "version": "3.1.0",
3
+ "version": "3.2.1",
4
4
  "type": "module",
5
5
  "description": "A web component library",
6
6
  "exports": {
@@ -11,7 +11,7 @@
11
11
  "./components/*": "./dist/components/*.js"
12
12
  },
13
13
  "scripts": {
14
- "test": "node ./scrips/test.js",
14
+ "test": "node ./scripts/test.js",
15
15
  "build": "node ./scripts/build.js",
16
16
  "docs": "node ./scripts/docs.js",
17
17
  "docs-src": "node ./scripts/docs.js -s"
@@ -0,0 +1,31 @@
1
+ import express from 'express';
2
+ import { dirname, join } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import {
5
+ getArgs,
6
+ runChildNodeProcess
7
+ } from '../src/utils/cli.js';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+
12
+ const options = getArgs({
13
+ p: 'port',
14
+ b: 'build',
15
+ s: 'src'
16
+ });
17
+
18
+ const {
19
+ port = 8084,
20
+ build = true
21
+ } = options;
22
+
23
+ const app = express();
24
+
25
+ app.use('/kempo', express.static('src'));
26
+ app.use('/icons', express.static('icons'));
27
+ app.use(express.static('test'));
28
+
29
+ app.listen(port, () => {
30
+ console.log(`Docs Running on: http://localhost:${port}`);
31
+ });
@@ -107,3 +107,4 @@ export default class Component extends HTMLElement {
107
107
  static observedAttributes = ['rendered'];
108
108
  static pathToKempo = import.meta.url.substring(0, import.meta.url.indexOf('/components/Component.js') );
109
109
  }
110
+ window.customElements.define('k-component', Component);
@@ -2,9 +2,14 @@ import Component from './Component.js';
2
2
  import { isInView } from '../utils/element.js';
3
3
  import raf from '../utils/raf.js';
4
4
 
5
+ const inViewIntervalId = Symbol();
5
6
  export default class LazyComponent extends Component {
6
7
  constructor(shadowDetails){
7
8
  super(shadowDetails);
9
+
10
+ /* Private Members */
11
+ this[inViewIntervalId] = null;
12
+
8
13
  (new IntersectionObserver(([comp]) => {
9
14
  if(comp.intersectionRatio > 0 && !this.rendered){
10
15
  this.inViewCallback();
@@ -13,19 +18,33 @@ export default class LazyComponent extends Component {
13
18
  }
14
19
  })).observe(this);
15
20
 
16
- this.registerAttribute('unrender', false);
21
+ this.registerAttributes({
22
+ unrender: false,
23
+ inViewInterval: 0
24
+ });
17
25
  }
18
- async connectedCallback(){
26
+ connectedCallback(){
19
27
  // Do not do super.connectedCallback() because it will render the component prematurely
20
- await this.renderSkelton(); // so that the skeleton height is rendered, which will give it the propert height so potentional LazyComponents below it will get out of the viewport as they normally would, before we then check if it is in View (which is delayed a few frames.
21
- raf(async ()=>{
22
- if(await isInView(this)) await this.inViewCallback();
28
+ this.renderSkelton(); // so that the skeleton height is rendered, which will give it the propert height so potentional LazyComponents below it will get out of the viewport as they normally would, before we then check if it is in View (which is delayed a few frames.
29
+ raf(()=>{
30
+ if(isInView(this)){
31
+ this.inViewCallback();
32
+ } else {
33
+ this.outOfViewCallback();
34
+ }
23
35
  }, 2);
24
36
  }
25
37
  async inViewCallback(){
38
+ clearInterval(this[inViewIntervalId]);
26
39
  return await this.render();
27
40
  }
28
41
  async outOfViewCallback(){
42
+ clearInterval(this[inViewIntervalId]);
43
+ this[inViewIntervalId] = setInterval(async ()=>{
44
+ if(await isInView(this)){
45
+ this.inViewCallback();
46
+ }
47
+ }, this.inViewInterval || LazyComponent.inViewIntervalTimeout);
29
48
  if(this.unrender){
30
49
  await this.renderSkelton();
31
50
  return true;
@@ -40,4 +59,7 @@ export default class LazyComponent extends Component {
40
59
  get skeletonTemplate(){
41
60
  return /*html*/`<div style="height: var(--skeleton_height, 1rem)"></div>`;
42
61
  }
62
+
63
+ /* Static Members */
64
+ static inViewIntervalTimeout = 1000;
43
65
  }
@@ -3,131 +3,14 @@ import {
3
3
  dispatchEvent
4
4
  } from '../utils/element.js';
5
5
 
6
- const clickHandler = Symbol();
7
- export class Tab extends Component {
8
- constructor(){
9
- super();
10
-
11
- /* Private Methods */
12
- this[clickHandler] = () => {
13
- if(!this.active){
14
- this.tabs.active = this.for;
15
- }
16
- }
17
-
18
- /* Init */
19
- this.slot = 'tabs';
20
- this.registerAttribute('active', false);
21
- }
22
-
23
- connectedCallback(){
24
- super.connectedCallback();
25
- this.shadowRoot.getElementById('button').addEventListener('click', this[clickHandler]);
26
- }
27
-
28
- get for(){
29
- const forValue = this.getAttribute('for');
30
- if(forValue) return forValue;
31
- return this?.tabs.tabs.indexOf(this);
32
- }
33
- set for(v){
34
- if(v){
35
- this.setAttribute('for', v);
36
- } else {
37
- this.removeAttribute('for');
38
- }
39
- }
40
-
41
- get tabs(){
42
- return this.parentElement.tagName === 'K-TABS'?this.parentElement:null;
43
- }
44
-
45
- get shadowTemplate(){
46
- return /*html*/`
47
- <button id="button">
48
- ${super.shadowTemplate}
49
- </button>
50
- `;
51
- }
52
- get shadowStyles(){
53
- return /*css*/`
54
- :host {
55
- margin-bottom: -1px;
56
- flex: 0 0 auto; /* Important: prevent shrinking and growing */
57
- }
58
- #button {
59
- padding: var(--spacer_h);
60
- background-color: transparent;
61
- border: none;
62
- cursor: inherit;
63
- box-shadow: none;
64
- color: inherit;
65
- white-space: nowrap; /* Prevent text wrapping */
66
- }
67
- :host(:not([active])) #button {
68
- cursor: pointer;
69
- }
70
- :host([active]){
71
- border-bottom: 2px solid var(--c_primary);
72
- margin-bottom: -1px;
73
- }
74
- :host([active]) #button {
75
- color: var(--tc_primary);
76
- }
77
- `;
78
- }
79
-
80
- }
81
- window.customElements.define('k-tab', Tab);
82
-
83
- export class TabContent extends Component {
84
- constructor(){
85
- super();
86
-
87
- this.registerAttribute('active', false);
88
- }
89
-
90
- get tabs(){
91
- return this.parentElement.tagName === 'K-TABS'?this.parentElement:null;
92
- }
93
- get name(){
94
- const name = this.getAttribute('name');
95
- if(name) return name;
96
- return this?.tabs.contents.indexOf(this);
97
- }
98
- set name(v){
99
- if(v){
100
- this.setAttribute('name', v);
101
- } else {
102
- this.removeAttribute('name');
103
- }
104
- }
105
-
106
- get shadowStyles(){
107
- return /*css*/`
108
- ${super.shadowStyles}
109
- :host {
110
- display: block;
111
- height: 100%;
112
- max-height: 100%;
113
- flex: 1 1 auto;
114
- overflow: hidden;
115
- }
116
- :host([active]) {
117
- display: block;
118
- }
119
- :host(:not([active])) {
120
- display: none;
121
- }
122
- `;
123
- }
124
- }
125
- window.customElements.define('k-tab-content', TabContent);
126
6
 
127
7
  export class Tabs extends Component {
128
8
  constructor(){
129
9
  super();
130
- this.registerAttribute('active', '');
10
+ this.registerAttributes({
11
+ active: '',
12
+ fixedHeight: false
13
+ });
131
14
  }
132
15
 
133
16
  async connectedCallback(){
@@ -136,12 +19,10 @@ export class Tabs extends Component {
136
19
  this.active = `${this.querySelectorAll('k-tab-content')[0].name}`;
137
20
  }
138
21
 
139
- // Add scroll listeners for indicators
140
22
  const tabsContainer = this.shadowRoot.getElementById('tabs');
141
23
  tabsContainer.addEventListener('scroll', this.updateScrollIndicators.bind(this));
142
24
  this.updateScrollIndicators();
143
25
 
144
- // Update indicators on resize
145
26
  new ResizeObserver(() => this.updateScrollIndicators()).observe(tabsContainer);
146
27
 
147
28
  const leftButton = this.shadowRoot.getElementById('scroll-left');
@@ -217,8 +98,7 @@ export class Tabs extends Component {
217
98
  }
218
99
  getActiveContent(){
219
100
  return this.querySelector(':scope > k-tab-content[active="true"]');
220
- }
221
-
101
+ }
222
102
 
223
103
  get shadowTemplate(){
224
104
  return /*html*/`
@@ -306,6 +186,15 @@ export class Tabs extends Component {
306
186
  transparent 100%
307
187
  );
308
188
  }
189
+ :host([fixed-height="true"]) #wrapper {
190
+ height: 100%;
191
+ }
192
+ :host([fixed-height="true"]) #contents {
193
+ height: 100%;
194
+ flex: 1;
195
+ min-height: 1.35rem;
196
+ overflow: auto;
197
+ }
309
198
  `;
310
199
  }
311
200
 
@@ -313,17 +202,138 @@ export class Tabs extends Component {
313
202
  }
314
203
  window.customElements.define('k-tabs', Tabs);
315
204
 
316
- export class TabSpacer extends Component {
205
+ const clickHandler = Symbol();
206
+ export class Tab extends Component {
317
207
  constructor(){
318
208
  super();
209
+
210
+ /* Private Methods */
211
+ this[clickHandler] = () => {
212
+ if(!this.active){
213
+ this.tabs.active = this.for;
214
+ }
215
+ }
216
+
217
+ /* Init */
319
218
  this.slot = 'tabs';
219
+ this.registerAttribute('active', false);
220
+ }
221
+
222
+ connectedCallback(){
223
+ super.connectedCallback();
224
+ this.shadowRoot.getElementById('button').addEventListener('click', this[clickHandler]);
225
+ }
226
+
227
+ get for(){
228
+ const forValue = this.getAttribute('for');
229
+ if(forValue) return forValue;
230
+ return this?.tabs.tabs.indexOf(this);
231
+ }
232
+ set for(v){
233
+ if(v){
234
+ this.setAttribute('for', v);
235
+ } else {
236
+ this.removeAttribute('for');
237
+ }
238
+ }
239
+
240
+ get tabs(){
241
+ return this.parentElement.tagName === 'K-TABS'?this.parentElement:null;
242
+ }
243
+
244
+ get shadowTemplate(){
245
+ return /*html*/`
246
+ <button id="button">
247
+ ${super.shadowTemplate}
248
+ </button>
249
+ `;
250
+ }
251
+ get shadowStyles(){
252
+ return /*css*/`
253
+ :host {
254
+ margin-bottom: -1px;
255
+ flex: 0 0 auto; /* Important: prevent shrinking and growing */
256
+ }
257
+ #button {
258
+ padding: var(--spacer_h);
259
+ background-color: transparent;
260
+ border: none;
261
+ cursor: inherit;
262
+ box-shadow: none;
263
+ color: inherit;
264
+ white-space: nowrap; /* Prevent text wrapping */
265
+ }
266
+ :host(:not([active])) #button {
267
+ cursor: pointer;
268
+ }
269
+ :host([active]){
270
+ border-bottom: 2px solid var(--c_primary);
271
+ margin-bottom: -1px;
272
+ }
273
+ :host([active]) #button {
274
+ color: var(--tc_primary);
275
+ }
276
+ `;
277
+ }
278
+
279
+ }
280
+ window.customElements.define('k-tab', Tab);
281
+
282
+ export class TabContent extends Component {
283
+ constructor(){
284
+ super();
285
+
286
+ this.registerAttribute('active', false);
287
+ }
288
+
289
+ get tabs(){
290
+ return this.parentElement.tagName === 'K-TABS'?this.parentElement:null;
291
+ }
292
+ get name(){
293
+ const name = this.getAttribute('name');
294
+ if(name) return name;
295
+ return this?.tabs.contents.indexOf(this);
296
+ }
297
+ set name(v){
298
+ if(v){
299
+ this.setAttribute('name', v);
300
+ } else {
301
+ this.removeAttribute('name');
302
+ }
320
303
  }
321
304
 
322
305
  get shadowStyles(){
323
306
  return /*css*/`
324
307
  ${super.shadowStyles}
325
308
  :host {
309
+ display: block;
310
+ height: 100%;
311
+ max-height: 100%;
326
312
  flex: 1 1 auto;
313
+ overflow: auto;
314
+ }
315
+ :host([active]) {
316
+ display: block;
317
+ }
318
+ :host(:not([active])) {
319
+ display: none;
320
+ }
321
+ `;
322
+ }
323
+ }
324
+ window.customElements.define('k-tab-content', TabContent);
325
+
326
+ export class TabSpacer extends Component {
327
+ constructor(){
328
+ super();
329
+ this.slot = 'tabs';
330
+ }
331
+
332
+ get shadowStyles(){
333
+ return /*css*/`
334
+ ${super.shadowStyles}
335
+ :host {
336
+ flex: 1 1 auto !important;
327
337
  height: 1px;
328
338
  }
329
339
  `;