kempo-ui 0.3.11 → 0.3.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/dist/components/Aside.js +41 -19
- package/dist/components/Dropdown.js +2 -2
- package/docs/components/dropdown.html +24 -0
- package/docs/src/components/Aside.js +41 -19
- package/docs/src/components/Dropdown.js +2 -2
- package/docs-src/.config.js +10 -0
- package/docs-src/components/dropdown.page.html +24 -0
- package/package.json +2 -2
- package/src/components/Aside.js +53 -20
- package/src/components/Dropdown.js +19 -1
- package/tests/components/Aside.browser-test.js +141 -0
- package/tests/components/Dropdown.browser-test.js +61 -0
- /package/{llm.txt → llms.txt} +0 -0
package/dist/components/Aside.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import e from"./ShadowComponent.js";import{html as t,css as s,nothing as a}from"../lit-all.min.js";import{boolTrueFalse as i}from"../utils/propConverters.js";import"./FocusCapture.js";import"./Icon.js";export default class
|
|
1
|
+
import e from"./ShadowComponent.js";import{html as t,css as s,nothing as a}from"../lit-all.min.js";import{boolTrueFalse as i}from"../utils/propConverters.js";import"./Dropdown.js";import"./FocusCapture.js";import"./Icon.js";export default class n extends e{static properties={state:{type:String,reflect:!0},side:{type:String,reflect:!0},main:{type:String,reflect:!0},overlayClose:{type:Boolean,reflect:!0,attribute:"overlay-close",converter:i},escClose:{type:Boolean,reflect:!0,attribute:"esc-close",converter:i},persistentId:{type:String,reflect:!0,attribute:"persistent-id"}};constructor(){super(),this.state="offscreen",this.side="left",this.main="overlay",this.overlayClose=!0,this.escClose=!0,this.persistentId=null}overlayClick=()=>{this.overlayClose&&this.hide()};handleKeyDown=e=>{this.escClose&&"Escape"===e.key&&this.hide()};updated(e){if(super.updated(e),e.has("persistentId")&&this.persistentId&&window?.localStorage){const e=window.localStorage.getItem(`aside-persistent-id-${this.persistentId}`);e&&(this.state=e)}if(e.has("state")){const t=e.get("state");"offscreen"===this.state||"offscreen"!==t&&void 0!==t?"offscreen"===this.state&&void 0!==t&&"offscreen"!==t&&document.removeEventListener("keydown",this.handleKeyDown):document.addEventListener("keydown",this.handleKeyDown),"overlay"===this.main&&("expanded"===this.state?document.body.classList.add("no-scroll"):document.body.classList.remove("no-scroll"));const s="offscreen"===this.state?0:this.getTargetWidth(this.state),a={aside:this,state:this.state,main:this.main,width:s};this.dispatchEvent(new CustomEvent("aside_state_change",{detail:a})),window.dispatchEvent(new CustomEvent("aside_state_change",{detail:a})),this.inert="offscreen"===this.state,this.persistentId&&window?.localStorage&&window.localStorage.setItem(`aside-persistent-id-${this.persistentId}`,this.state)}}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener("keydown",this.handleKeyDown),document.body.classList.remove("no-scroll");const e={aside:this,state:"offscreen",main:this.main,width:0};this.dispatchEvent(new CustomEvent("aside_state_change",{detail:e})),window.dispatchEvent(new CustomEvent("aside_state_change",{detail:e}))}expand=()=>this.state="expanded";collapse=()=>this.state="collapsed";hide=()=>this.state="offscreen";toggle=()=>{this.state="expanded"===this.state?"offscreen":"expanded"};getTargetWidth(e){const t=parseFloat(getComputedStyle(document.documentElement).fontSize),s="collapsed"===e?"--collapsed-width":"--width",a=getComputedStyle(this).getPropertyValue(s).trim();return(a?parseFloat(a):"collapsed"===e?3.5:20)*t}render(){const e=t`<aside><slot></slot></aside>`;return"overlay"===this.main?t`
|
|
2
2
|
<k-focus-capture>
|
|
3
3
|
<div id="container">
|
|
4
4
|
<button id="overlay-btn" @click=${this.overlayClick}>
|
|
@@ -121,10 +121,10 @@ import e from"./ShadowComponent.js";import{html as t,css as s,nothing as a}from"
|
|
|
121
121
|
}
|
|
122
122
|
:host([main="push"][state="collapsed"]) aside {
|
|
123
123
|
width: var(--collapsed-width);
|
|
124
|
-
transform:
|
|
124
|
+
transform: none;
|
|
125
125
|
}
|
|
126
126
|
:host([main="push"][state="expanded"]) aside {
|
|
127
|
-
transform:
|
|
127
|
+
transform: none;
|
|
128
128
|
}
|
|
129
129
|
:host([main="overlay"]) aside {
|
|
130
130
|
width: var(--width);
|
|
@@ -133,15 +133,15 @@ import e from"./ShadowComponent.js";import{html as t,css as s,nothing as a}from"
|
|
|
133
133
|
transform: translateX(-100%);
|
|
134
134
|
}
|
|
135
135
|
:host([main="overlay"][state="expanded"]) aside {
|
|
136
|
-
transform:
|
|
136
|
+
transform: none;
|
|
137
137
|
}
|
|
138
138
|
:host([main="overlay"][side="right"]) aside {
|
|
139
139
|
transform: translateX(100%);
|
|
140
140
|
}
|
|
141
141
|
:host([main="overlay"][side="right"][state="expanded"]) aside {
|
|
142
|
-
transform:
|
|
142
|
+
transform: none;
|
|
143
143
|
}
|
|
144
|
-
`}customElements.define("k-aside",
|
|
144
|
+
`}customElements.define("k-aside",n);class o extends e{static properties={icon:{type:String},href:{type:String},active:{type:Boolean,reflect:!0},collapsed:{type:Boolean,reflect:!0},"no-expand":{type:Boolean,attribute:"no-expand"},"hide-when-collapsed":{type:Boolean,attribute:"hide-when-collapsed"}};constructor(){super(),this.icon="",this.href="#",this.active=!1,this.collapsed=!1,this["no-expand"]=!1,this["hide-when-collapsed"]=!1}connectedCallback(){super.connectedCallback(),this.aside=this.closest("k-aside"),this.inMenu=!!this.closest("k-aside-menu"),this.aside&&!this.inMenu&&(this.collapsed="collapsed"===this.aside.state,this.aside.addEventListener("aside_state_change",this.handleStateChange))}disconnectedCallback(){super.disconnectedCallback(),this.aside&&this.aside.removeEventListener("aside_state_change",this.handleStateChange)}handleStateChange=e=>{const{state:t}=e.detail;"collapsed"===t?this.collapsed=!0:"expanded"===t&&(this.collapsed=!1)};handleClick=e=>{this.collapsed&&!this["no-expand"]&&this.aside&&(e.preventDefault(),this.aside.expand())};render(){return t`
|
|
145
145
|
<a href="${this.href}" class="item ${this.active?"active bg-primary":""}" @click=${this.handleClick}>
|
|
146
146
|
${this.icon?t`<k-icon name="${this.icon}"></k-icon>`:this.collapsed?t`<k-icon name="dot"></k-icon>`:a}
|
|
147
147
|
${this.collapsed?a:t`<span class="label"><slot></slot></span>`}
|
|
@@ -183,7 +183,7 @@ import e from"./ShadowComponent.js";import{html as t,css as s,nothing as a}from"
|
|
|
183
183
|
overflow: hidden;
|
|
184
184
|
text-overflow: ellipsis;
|
|
185
185
|
}
|
|
186
|
-
`}class
|
|
186
|
+
`}class r extends e{static properties={collapsed:{type:Boolean,reflect:!0}};constructor(){super(),this.collapsed=!1}connectedCallback(){super.connectedCallback(),this.aside=this.closest("k-aside"),this.aside&&(this.collapsed="collapsed"===this.aside.state,this.aside.addEventListener("aside_state_change",this.handleStateChange))}disconnectedCallback(){super.disconnectedCallback(),this.aside&&this.aside.removeEventListener("aside_state_change",this.handleStateChange)}handleStateChange=e=>{const{state:t}=e.detail;"collapsed"===t?this.collapsed=!0:"expanded"===t&&(this.collapsed=!1)};render(){return this.collapsed?t`<hr>`:t`<div class="label"><slot></slot></div>`}static styles=s`
|
|
187
187
|
:host {
|
|
188
188
|
display: block;
|
|
189
189
|
margin: var(--spacer_h) 0;
|
|
@@ -208,14 +208,21 @@ import e from"./ShadowComponent.js";import{html as t,css as s,nothing as a}from"
|
|
|
208
208
|
:host([collapsed]) hr {
|
|
209
209
|
margin: var(--spacer_h) 0;
|
|
210
210
|
}
|
|
211
|
-
`}class
|
|
211
|
+
`}class d extends e{static properties={icon:{type:String},label:{type:String},open:{type:Boolean,reflect:!0},collapsed:{type:Boolean,reflect:!0},side:{type:String},"no-expand":{type:Boolean,attribute:"no-expand"},"hide-when-collapsed":{type:Boolean,attribute:"hide-when-collapsed"}};constructor(){super(),this.icon="",this.label="",this.open=!1,this.collapsed=!1,this.side="left",this["no-expand"]=!1,this["hide-when-collapsed"]=!1}connectedCallback(){super.connectedCallback(),this.aside=this.closest("k-aside"),this.aside&&(this.collapsed="collapsed"===this.aside.state,this.side=this.aside.side||"left",this.aside.addEventListener("aside_state_change",this.handleStateChange))}disconnectedCallback(){super.disconnectedCallback(),this.aside&&this.aside.removeEventListener("aside_state_change",this.handleStateChange)}handleStateChange=e=>{const{state:t}=e.detail;"collapsed"===t?(this.collapsed=!0,this.open=!1):"expanded"===t&&(this.collapsed=!1),this.side=this.aside?.side||"left"};toggleMenu=()=>{this.collapsed||(this.open=!this.open)};handleCollapsedClick=()=>{this.open=!0,this.aside?.expand()};render(){if(this.collapsed){const e="right"===this.side?"left down":"right down";return t`
|
|
212
|
+
<k-dropdown open-direction=${e} hover>
|
|
213
|
+
<button slot="trigger" class="no-btn collapsed-trigger" @click=${this.handleCollapsedClick}>
|
|
214
|
+
${this.icon?t`<k-icon name="${this.icon}"></k-icon>`:t`<k-icon name="dot"></k-icon>`}
|
|
215
|
+
</button>
|
|
216
|
+
<slot></slot>
|
|
217
|
+
</k-dropdown>
|
|
218
|
+
`}return t`
|
|
212
219
|
<div class="menu-container">
|
|
213
220
|
<button class="no-btn menu-header ${this.open?"open":""}" @click=${this.toggleMenu}>
|
|
214
|
-
${this.icon?t`<k-icon name="${this.icon}"></k-icon>`:
|
|
215
|
-
|
|
216
|
-
|
|
221
|
+
${this.icon?t`<k-icon name="${this.icon}"></k-icon>`:a}
|
|
222
|
+
<span class="label">${this.label}</span>
|
|
223
|
+
<k-icon class="chevron" name="chevron" direction="${this.open?"down":"right"}"></k-icon>
|
|
217
224
|
</button>
|
|
218
|
-
<div class="menu-content ${this.open
|
|
225
|
+
<div class="menu-content ${this.open?"open":""}">
|
|
219
226
|
<slot></slot>
|
|
220
227
|
</div>
|
|
221
228
|
</div>
|
|
@@ -242,12 +249,29 @@ import e from"./ShadowComponent.js";import{html as t,css as s,nothing as a}from"
|
|
|
242
249
|
white-space: nowrap;
|
|
243
250
|
transition: background var(--animation_ms);
|
|
244
251
|
}
|
|
245
|
-
:host([collapsed]) .menu-header {
|
|
246
|
-
width: auto;
|
|
247
|
-
}
|
|
248
252
|
.menu-header:hover {
|
|
249
253
|
background: var(--c_bg_hover);
|
|
250
254
|
}
|
|
255
|
+
.collapsed-trigger {
|
|
256
|
+
display: flex;
|
|
257
|
+
align-items: center;
|
|
258
|
+
justify-content: center;
|
|
259
|
+
padding: var(--spacer_h);
|
|
260
|
+
color: var(--tc);
|
|
261
|
+
background: transparent;
|
|
262
|
+
border: none;
|
|
263
|
+
appearance: none;
|
|
264
|
+
border-radius: var(--radius);
|
|
265
|
+
margin: 0 var(--spacer_h);
|
|
266
|
+
cursor: pointer;
|
|
267
|
+
transition: background var(--animation_ms);
|
|
268
|
+
}
|
|
269
|
+
.collapsed-trigger:hover {
|
|
270
|
+
background: var(--c_bg_hover);
|
|
271
|
+
}
|
|
272
|
+
k-dropdown {
|
|
273
|
+
display: block;
|
|
274
|
+
}
|
|
251
275
|
k-icon {
|
|
252
276
|
flex-shrink: 0;
|
|
253
277
|
}
|
|
@@ -266,9 +290,6 @@ import e from"./ShadowComponent.js";import{html as t,css as s,nothing as a}from"
|
|
|
266
290
|
transition: max-height var(--animation_ms);
|
|
267
291
|
padding-left: calc(var(--spacer_h) * 2);
|
|
268
292
|
}
|
|
269
|
-
:host([collapsed]) .menu-content {
|
|
270
|
-
padding-left: 0;
|
|
271
|
-
}
|
|
272
293
|
.menu-content.open {
|
|
273
294
|
max-height: 500px;
|
|
274
295
|
}
|
|
@@ -313,6 +334,7 @@ import e from"./ShadowComponent.js";import{html as t,css as s,nothing as a}from"
|
|
|
313
334
|
width: 2rem;
|
|
314
335
|
height: 2rem;
|
|
315
336
|
border: none;
|
|
337
|
+
appearance: none;
|
|
316
338
|
background: transparent;
|
|
317
339
|
color: var(--c_text);
|
|
318
340
|
cursor: pointer;
|
|
@@ -325,4 +347,4 @@ import e from"./ShadowComponent.js";import{html as t,css as s,nothing as a}from"
|
|
|
325
347
|
#toggle:hover {
|
|
326
348
|
background: var(--c_bg_hover);
|
|
327
349
|
}
|
|
328
|
-
`}customElements.define("k-aside-item",
|
|
350
|
+
`}customElements.define("k-aside-item",o),customElements.define("k-aside-label",r),customElements.define("k-aside-menu",d),customElements.define("k-aside-spacer",l),customElements.define("k-aside-toggle",c);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{html as e,css as t}from"../lit-all.min.js";import o from"./ShadowComponent.js";import{boolTrueFalse as n}from"../utils/propConverters.js";const i=new Set;export default class s extends o{static properties={opened:{type:Boolean,reflect:!0},openDirection:{type:String,reflect:!0,attribute:"open-direction"},closeOnSelect:{type:Boolean,reflect:!0,attribute:"close-on-select",converter:n},closeOnClickOutside:{type:Boolean,reflect:!0,attribute:"close-on-click-outside",converter:n},submenu:{type:Boolean,reflect:!0}};constructor(){super(),this.opened=!1,this.openDirection="down left",this.closeOnSelect=!0,this.closeOnClickOutside=!0,this.submenu=!1}connectedCallback(){super.connectedCallback(),"K-DROPDOWN"!==this.parentElement?.tagName||this.hasAttribute("slot")||(this.submenu=!0,this.hasAttribute("open-direction")||(this.openDirection="right down")),this.submenu?(this.addEventListener("mouseenter",this.handleSubmenuEnter),this.addEventListener("mouseleave",this.handleSubmenuLeave)):document.addEventListener("click",this.handleDocumentClick),document.addEventListener("keydown",this.handleKeydown)}disconnectedCallback(){super.disconnectedCallback(),this.submenu?(this.removeEventListener("mouseenter",this.handleSubmenuEnter),this.removeEventListener("mouseleave",this.handleSubmenuLeave)):document.removeEventListener("click",this.handleDocumentClick),document.removeEventListener("keydown",this.handleKeydown),i.delete(this)}updated(e){super.updated(e),e.has("opened")&&(this.submenu||(this.opened?i.add(this):i.delete(this)),this.dispatchEvent(new CustomEvent(this.opened?"opened":"closed",{bubbles:!0})))}handleDocumentClick=e=>{const t=e.composedPath(),o=t.find(e=>e.matches?.('[slot="trigger"]'));if(o){const e=o.closest("k-dropdown");if(e===this)return;if(this.contains(e))return;return void(this.opened&&this.close())}if(!this.opened)return;if(!t.includes(this))return void(this.closeOnClickOutside&&this.close());const n=t.find(e=>e.matches?.("a, button"));n&&!n.closest('[slot="trigger"]')&&this.closeOnSelect&&this.close()};handleTriggerClick=e=>{e.stopPropagation(),this.opened||i.forEach(e=>{e!==this&&e.close()}),this.toggle()};handleMenuClick=e=>{const t=e.target.closest("a, button");if(!t||t.hasAttribute("disabled"))return;if(t.closest("k-dropdown")!==this)return;const o=t.dataset?.value||t.textContent.trim();this.dispatchEvent(new CustomEvent("select",{detail:{value:o,item:t},bubbles:!0}))};handleKeydown=e=>{if(!this.opened)return;const t=document.activeElement;if(!this.contains(t))return;const o=this.querySelector(":scope > k-dropdown[opened]");if(!o?.contains(t))if("Escape"===e.key||this.submenu&&"ArrowLeft"===e.key)e.preventDefault(),this.close(),this.focusTrigger();else if("ArrowDown"===e.key)e.preventDefault(),this.focusNextItem();else if("ArrowUp"===e.key)e.preventDefault(),this.focusPreviousItem();else if("ArrowRight"===e.key){const o=t?.closest("k-dropdown[submenu]");o?.parentElement===this&&(e.preventDefault(),o.open(),o.focusFirstItem())}else if("Enter"===e.key||" "===e.key){const o=t?.closest("k-dropdown[submenu]");o?.parentElement===this?(e.preventDefault(),o.open(),o.focusFirstItem()):t&&!t.closest('[slot="trigger"]')&&(e.preventDefault(),t.click())}};open(){return this.submenu?[...this.parentElement.querySelectorAll(":scope > k-dropdown[opened]")].forEach(e=>{e!==this&&e.close()}):i.forEach(e=>{e!==this&&e.close()}),this.opened=!0,this.submenu||requestAnimationFrame(()=>this.focusFirstItem()),this}close(){return this.querySelectorAll(":scope > k-dropdown[opened]").forEach(e=>e.close()),this.opened=!1,this}toggle(){return this.opened?this.close():this.open()}focusTrigger(){const e=this.querySelector('[slot="trigger"]');e&&e.focus()}getMenuItems(){return[...this.children].reduce((e,t)=>{if(t.matches('[slot="trigger"]')||t.hasAttribute("disabled"))return e;if("K-DROPDOWN"===t.tagName){const o=t.querySelector('[slot="trigger"]');o&&e.push(o)}else t.matches("a, button")&&e.push(t);return e},[])}focusFirstItem(){const e=this.getMenuItems();e.length>0&&e[0].focus()}focusNextItem(){const e=this.getMenuItems(),t=document.activeElement,o=e.indexOf(t),n=e[o+1]||e[0];n&&n.focus()}focusPreviousItem(){const e=this.getMenuItems(),t=document.activeElement,o=e.indexOf(t),n=e[o-1]||e[e.length-1];n&&n.focus()}static styles=t`
|
|
1
|
+
import{html as e,css as t}from"../lit-all.min.js";import o from"./ShadowComponent.js";import{boolTrueFalse as n}from"../utils/propConverters.js";const i=new Set;export default class s extends o{static properties={opened:{type:Boolean,reflect:!0},openDirection:{type:String,reflect:!0,attribute:"open-direction"},closeOnSelect:{type:Boolean,reflect:!0,attribute:"close-on-select",converter:n},closeOnClickOutside:{type:Boolean,reflect:!0,attribute:"close-on-click-outside",converter:n},submenu:{type:Boolean,reflect:!0},hover:{type:Boolean,reflect:!0}};constructor(){super(),this.opened=!1,this.openDirection="down left",this.closeOnSelect=!0,this.closeOnClickOutside=!0,this.submenu=!1,this.hover=!1}connectedCallback(){super.connectedCallback(),"K-DROPDOWN"!==this.parentElement?.tagName||this.hasAttribute("slot")||(this.submenu=!0,this.hasAttribute("open-direction")||(this.openDirection="right down")),this.submenu?(this.addEventListener("mouseenter",this.handleSubmenuEnter),this.addEventListener("mouseleave",this.handleSubmenuLeave)):(document.addEventListener("click",this.handleDocumentClick),this.hover&&(this.addEventListener("mouseenter",this.handleHoverEnter),this.addEventListener("mouseleave",this.handleHoverLeave))),document.addEventListener("keydown",this.handleKeydown)}disconnectedCallback(){super.disconnectedCallback(),this.submenu?(this.removeEventListener("mouseenter",this.handleSubmenuEnter),this.removeEventListener("mouseleave",this.handleSubmenuLeave)):(document.removeEventListener("click",this.handleDocumentClick),this.removeEventListener("mouseenter",this.handleHoverEnter),this.removeEventListener("mouseleave",this.handleHoverLeave)),document.removeEventListener("keydown",this.handleKeydown),i.delete(this)}updated(e){super.updated(e),e.has("opened")&&(this.submenu||(this.opened?i.add(this):i.delete(this)),this.dispatchEvent(new CustomEvent(this.opened?"opened":"closed",{bubbles:!0})))}handleDocumentClick=e=>{const t=e.composedPath(),o=t.find(e=>e.matches?.('[slot="trigger"]'));if(o){const e=o.closest("k-dropdown");if(e===this)return;if(this.contains(e))return;return void(this.opened&&this.close())}if(!this.opened)return;if(!t.includes(this))return void(this.closeOnClickOutside&&this.close());const n=t.find(e=>e.matches?.("a, button"));n&&!n.closest('[slot="trigger"]')&&this.closeOnSelect&&this.close()};handleTriggerClick=e=>{this.hover||(e.stopPropagation(),this.opened||i.forEach(e=>{e!==this&&e.close()}),this.toggle())};handleMenuClick=e=>{const t=e.target.closest("a, button");if(!t||t.hasAttribute("disabled"))return;if(t.closest("k-dropdown")!==this)return;const o=t.dataset?.value||t.textContent.trim();this.dispatchEvent(new CustomEvent("select",{detail:{value:o,item:t},bubbles:!0}))};handleKeydown=e=>{if(!this.opened)return;const t=document.activeElement;if(!this.contains(t))return;const o=this.querySelector(":scope > k-dropdown[opened]");if(!o?.contains(t))if("Escape"===e.key||this.submenu&&"ArrowLeft"===e.key)e.preventDefault(),this.close(),this.focusTrigger();else if("ArrowDown"===e.key)e.preventDefault(),this.focusNextItem();else if("ArrowUp"===e.key)e.preventDefault(),this.focusPreviousItem();else if("ArrowRight"===e.key){const o=t?.closest("k-dropdown[submenu]");o?.parentElement===this&&(e.preventDefault(),o.open(),o.focusFirstItem())}else if("Enter"===e.key||" "===e.key){const o=t?.closest("k-dropdown[submenu]");o?.parentElement===this?(e.preventDefault(),o.open(),o.focusFirstItem()):t&&!t.closest('[slot="trigger"]')&&(e.preventDefault(),t.click())}};open(){return this.submenu?[...this.parentElement.querySelectorAll(":scope > k-dropdown[opened]")].forEach(e=>{e!==this&&e.close()}):i.forEach(e=>{e!==this&&e.close()}),this.opened=!0,this.submenu||requestAnimationFrame(()=>this.focusFirstItem()),this}close(){return this.querySelectorAll(":scope > k-dropdown[opened]").forEach(e=>e.close()),this.opened=!1,this}toggle(){return this.opened?this.close():this.open()}focusTrigger(){const e=this.querySelector('[slot="trigger"]');e&&e.focus()}getMenuItems(){return[...this.children].reduce((e,t)=>{if(t.matches('[slot="trigger"]')||t.hasAttribute("disabled"))return e;if("K-DROPDOWN"===t.tagName){const o=t.querySelector('[slot="trigger"]');o&&e.push(o)}else t.matches("a, button")&&e.push(t);return e},[])}focusFirstItem(){const e=this.getMenuItems();e.length>0&&e[0].focus()}focusNextItem(){const e=this.getMenuItems(),t=document.activeElement,o=e.indexOf(t),n=e[o+1]||e[0];n&&n.focus()}focusPreviousItem(){const e=this.getMenuItems(),t=document.activeElement,o=e.indexOf(t),n=e[o-1]||e[e.length-1];n&&n.focus()}static styles=t`
|
|
2
2
|
:host {
|
|
3
3
|
display: inline-block;
|
|
4
4
|
position: relative;
|
|
@@ -160,7 +160,7 @@ import{html as e,css as t}from"../lit-all.min.js";import o from"./ShadowComponen
|
|
|
160
160
|
font-size: 0.75em;
|
|
161
161
|
opacity: 0.6;
|
|
162
162
|
}
|
|
163
|
-
`;handleSubmenuEnter=()=>{clearTimeout(this.closeTimer),this.opened||this.open()};handleSubmenuLeave=()=>{this.closeTimer=setTimeout(()=>this.close(),150)};handleSlotChange=e=>{e.target.assignedElements().filter(e=>e.matches("a, button, k-dropdown")).forEach((e,t)=>e.classList.toggle("k-dropdown-first",0===t))};render(){return e`
|
|
163
|
+
`;handleHoverEnter=()=>{clearTimeout(this.closeTimer),this.opened||this.open()};handleHoverLeave=()=>{this.closeTimer=setTimeout(()=>this.close(),150)};handleSubmenuEnter=()=>{clearTimeout(this.closeTimer),this.opened||this.open()};handleSubmenuLeave=()=>{this.closeTimer=setTimeout(()=>this.close(),150)};handleSlotChange=e=>{e.target.assignedElements().filter(e=>e.matches("a, button, k-dropdown")).forEach((e,t)=>e.classList.toggle("k-dropdown-first",0===t))};render(){return e`
|
|
164
164
|
<div id="trigger" @click=${this.handleTriggerClick}>
|
|
165
165
|
<slot name="trigger"></slot>
|
|
166
166
|
${this.submenu?e`<k-icon name="chevron"></k-icon>`:""}
|
|
@@ -263,6 +263,7 @@
|
|
|
263
263
|
<a href="#disabledItems">Disabled Items</a><br />
|
|
264
264
|
<a href="#links">Using Links</a><br />
|
|
265
265
|
<a href="#events">Events</a><br />
|
|
266
|
+
<a href="#hover">Hover</a><br />
|
|
266
267
|
|
|
267
268
|
<h6 class="mt"><a href="#jsRef" class="no-link">JavaScript Reference</a></h6>
|
|
268
269
|
<a href="#constructor">Constructor</a><br />
|
|
@@ -473,6 +474,26 @@
|
|
|
473
474
|
</div>
|
|
474
475
|
</div>
|
|
475
476
|
|
|
477
|
+
<h3 id="hover"><a href="#hover" class="no-link">Hover</a></h3>
|
|
478
|
+
<p>Add the <code>hover</code> attribute to open the menu when the trigger is hovered instead of clicked. The menu closes automatically 150ms after the cursor leaves the component.</p>
|
|
479
|
+
<div class="row -mx">
|
|
480
|
+
<div class="col m-span-12 px">
|
|
481
|
+
<k-card label="HTML">
|
|
482
|
+
<pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">k-dropdown</span> <span class="hljs-attr">hover</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">'trigger'</span>></span>Hover Me<span class="hljs-tag"></<span class="hljs-name">button</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">data-value</span>=<span class="hljs-string">'edit'</span>></span>Edit<span class="hljs-tag"></<span class="hljs-name">button</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">data-value</span>=<span class="hljs-string">'copy'</span>></span>Copy<span class="hljs-tag"></<span class="hljs-name">button</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">data-value</span>=<span class="hljs-string">'delete'</span>></span>Delete<span class="hljs-tag"></<span class="hljs-name">button</span>></span><br><span class="hljs-tag"></<span class="hljs-name">k-dropdown</span>></span></code></pre>
|
|
483
|
+
</k-card>
|
|
484
|
+
</div>
|
|
485
|
+
<div class="col m-span-12 px">
|
|
486
|
+
<k-card label="Output">
|
|
487
|
+
<k-dropdown hover>
|
|
488
|
+
<button slot="trigger">Hover Me</button>
|
|
489
|
+
<button data-value="edit">Edit</button>
|
|
490
|
+
<button data-value="copy">Copy</button>
|
|
491
|
+
<button data-value="delete">Delete</button>
|
|
492
|
+
</k-dropdown>
|
|
493
|
+
</k-card>
|
|
494
|
+
</div>
|
|
495
|
+
</div>
|
|
496
|
+
|
|
476
497
|
<h2 id="jsRef"><a href="#jsRef" class="no-link">JavaScript Reference</a></h2>
|
|
477
498
|
|
|
478
499
|
<h3 id="constructor"><a href="#constructor" class="no-link">Constructor</a></h3>
|
|
@@ -501,6 +522,9 @@
|
|
|
501
522
|
<h5><code>submenu<i>: boolean</i></code></h5>
|
|
502
523
|
<p>Automatically set to <code>true</code> when a <code>k-dropdown</code> is nested inside another <code>k-dropdown</code>. Sub menus open on hover/focus, display a chevron icon, and default to <code>open-direction="right down"</code>. Supports <code>ArrowRight</code> to open and <code>ArrowLeft</code> to close via keyboard.</p>
|
|
503
524
|
|
|
525
|
+
<h5><code>hover<i>: boolean</i></code></h5>
|
|
526
|
+
<p>When present, the menu opens on <code>mouseenter</code> instead of trigger click. The menu closes 150ms after the cursor leaves the component. Default is <code>false</code>.</p>
|
|
527
|
+
|
|
504
528
|
<h3 id="methods"><a href="#methods" class="no-link">Methods</a></h3>
|
|
505
529
|
<h5><code>open()<i>: this</i></code></h5>
|
|
506
530
|
<p>Opens the dropdown menu and focuses the first item.</p>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import e from"./ShadowComponent.js";import{html as t,css as s,nothing as a}from"../lit-all.min.js";import{boolTrueFalse as i}from"../utils/propConverters.js";import"./FocusCapture.js";import"./Icon.js";export default class
|
|
1
|
+
import e from"./ShadowComponent.js";import{html as t,css as s,nothing as a}from"../lit-all.min.js";import{boolTrueFalse as i}from"../utils/propConverters.js";import"./Dropdown.js";import"./FocusCapture.js";import"./Icon.js";export default class n extends e{static properties={state:{type:String,reflect:!0},side:{type:String,reflect:!0},main:{type:String,reflect:!0},overlayClose:{type:Boolean,reflect:!0,attribute:"overlay-close",converter:i},escClose:{type:Boolean,reflect:!0,attribute:"esc-close",converter:i},persistentId:{type:String,reflect:!0,attribute:"persistent-id"}};constructor(){super(),this.state="offscreen",this.side="left",this.main="overlay",this.overlayClose=!0,this.escClose=!0,this.persistentId=null}overlayClick=()=>{this.overlayClose&&this.hide()};handleKeyDown=e=>{this.escClose&&"Escape"===e.key&&this.hide()};updated(e){if(super.updated(e),e.has("persistentId")&&this.persistentId&&window?.localStorage){const e=window.localStorage.getItem(`aside-persistent-id-${this.persistentId}`);e&&(this.state=e)}if(e.has("state")){const t=e.get("state");"offscreen"===this.state||"offscreen"!==t&&void 0!==t?"offscreen"===this.state&&void 0!==t&&"offscreen"!==t&&document.removeEventListener("keydown",this.handleKeyDown):document.addEventListener("keydown",this.handleKeyDown),"overlay"===this.main&&("expanded"===this.state?document.body.classList.add("no-scroll"):document.body.classList.remove("no-scroll"));const s="offscreen"===this.state?0:this.getTargetWidth(this.state),a={aside:this,state:this.state,main:this.main,width:s};this.dispatchEvent(new CustomEvent("aside_state_change",{detail:a})),window.dispatchEvent(new CustomEvent("aside_state_change",{detail:a})),this.inert="offscreen"===this.state,this.persistentId&&window?.localStorage&&window.localStorage.setItem(`aside-persistent-id-${this.persistentId}`,this.state)}}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener("keydown",this.handleKeyDown),document.body.classList.remove("no-scroll");const e={aside:this,state:"offscreen",main:this.main,width:0};this.dispatchEvent(new CustomEvent("aside_state_change",{detail:e})),window.dispatchEvent(new CustomEvent("aside_state_change",{detail:e}))}expand=()=>this.state="expanded";collapse=()=>this.state="collapsed";hide=()=>this.state="offscreen";toggle=()=>{this.state="expanded"===this.state?"offscreen":"expanded"};getTargetWidth(e){const t=parseFloat(getComputedStyle(document.documentElement).fontSize),s="collapsed"===e?"--collapsed-width":"--width",a=getComputedStyle(this).getPropertyValue(s).trim();return(a?parseFloat(a):"collapsed"===e?3.5:20)*t}render(){const e=t`<aside><slot></slot></aside>`;return"overlay"===this.main?t`
|
|
2
2
|
<k-focus-capture>
|
|
3
3
|
<div id="container">
|
|
4
4
|
<button id="overlay-btn" @click=${this.overlayClick}>
|
|
@@ -121,10 +121,10 @@ import e from"./ShadowComponent.js";import{html as t,css as s,nothing as a}from"
|
|
|
121
121
|
}
|
|
122
122
|
:host([main="push"][state="collapsed"]) aside {
|
|
123
123
|
width: var(--collapsed-width);
|
|
124
|
-
transform:
|
|
124
|
+
transform: none;
|
|
125
125
|
}
|
|
126
126
|
:host([main="push"][state="expanded"]) aside {
|
|
127
|
-
transform:
|
|
127
|
+
transform: none;
|
|
128
128
|
}
|
|
129
129
|
:host([main="overlay"]) aside {
|
|
130
130
|
width: var(--width);
|
|
@@ -133,15 +133,15 @@ import e from"./ShadowComponent.js";import{html as t,css as s,nothing as a}from"
|
|
|
133
133
|
transform: translateX(-100%);
|
|
134
134
|
}
|
|
135
135
|
:host([main="overlay"][state="expanded"]) aside {
|
|
136
|
-
transform:
|
|
136
|
+
transform: none;
|
|
137
137
|
}
|
|
138
138
|
:host([main="overlay"][side="right"]) aside {
|
|
139
139
|
transform: translateX(100%);
|
|
140
140
|
}
|
|
141
141
|
:host([main="overlay"][side="right"][state="expanded"]) aside {
|
|
142
|
-
transform:
|
|
142
|
+
transform: none;
|
|
143
143
|
}
|
|
144
|
-
`}customElements.define("k-aside",
|
|
144
|
+
`}customElements.define("k-aside",n);class o extends e{static properties={icon:{type:String},href:{type:String},active:{type:Boolean,reflect:!0},collapsed:{type:Boolean,reflect:!0},"no-expand":{type:Boolean,attribute:"no-expand"},"hide-when-collapsed":{type:Boolean,attribute:"hide-when-collapsed"}};constructor(){super(),this.icon="",this.href="#",this.active=!1,this.collapsed=!1,this["no-expand"]=!1,this["hide-when-collapsed"]=!1}connectedCallback(){super.connectedCallback(),this.aside=this.closest("k-aside"),this.inMenu=!!this.closest("k-aside-menu"),this.aside&&!this.inMenu&&(this.collapsed="collapsed"===this.aside.state,this.aside.addEventListener("aside_state_change",this.handleStateChange))}disconnectedCallback(){super.disconnectedCallback(),this.aside&&this.aside.removeEventListener("aside_state_change",this.handleStateChange)}handleStateChange=e=>{const{state:t}=e.detail;"collapsed"===t?this.collapsed=!0:"expanded"===t&&(this.collapsed=!1)};handleClick=e=>{this.collapsed&&!this["no-expand"]&&this.aside&&(e.preventDefault(),this.aside.expand())};render(){return t`
|
|
145
145
|
<a href="${this.href}" class="item ${this.active?"active bg-primary":""}" @click=${this.handleClick}>
|
|
146
146
|
${this.icon?t`<k-icon name="${this.icon}"></k-icon>`:this.collapsed?t`<k-icon name="dot"></k-icon>`:a}
|
|
147
147
|
${this.collapsed?a:t`<span class="label"><slot></slot></span>`}
|
|
@@ -183,7 +183,7 @@ import e from"./ShadowComponent.js";import{html as t,css as s,nothing as a}from"
|
|
|
183
183
|
overflow: hidden;
|
|
184
184
|
text-overflow: ellipsis;
|
|
185
185
|
}
|
|
186
|
-
`}class
|
|
186
|
+
`}class r extends e{static properties={collapsed:{type:Boolean,reflect:!0}};constructor(){super(),this.collapsed=!1}connectedCallback(){super.connectedCallback(),this.aside=this.closest("k-aside"),this.aside&&(this.collapsed="collapsed"===this.aside.state,this.aside.addEventListener("aside_state_change",this.handleStateChange))}disconnectedCallback(){super.disconnectedCallback(),this.aside&&this.aside.removeEventListener("aside_state_change",this.handleStateChange)}handleStateChange=e=>{const{state:t}=e.detail;"collapsed"===t?this.collapsed=!0:"expanded"===t&&(this.collapsed=!1)};render(){return this.collapsed?t`<hr>`:t`<div class="label"><slot></slot></div>`}static styles=s`
|
|
187
187
|
:host {
|
|
188
188
|
display: block;
|
|
189
189
|
margin: var(--spacer_h) 0;
|
|
@@ -208,14 +208,21 @@ import e from"./ShadowComponent.js";import{html as t,css as s,nothing as a}from"
|
|
|
208
208
|
:host([collapsed]) hr {
|
|
209
209
|
margin: var(--spacer_h) 0;
|
|
210
210
|
}
|
|
211
|
-
`}class
|
|
211
|
+
`}class d extends e{static properties={icon:{type:String},label:{type:String},open:{type:Boolean,reflect:!0},collapsed:{type:Boolean,reflect:!0},side:{type:String},"no-expand":{type:Boolean,attribute:"no-expand"},"hide-when-collapsed":{type:Boolean,attribute:"hide-when-collapsed"}};constructor(){super(),this.icon="",this.label="",this.open=!1,this.collapsed=!1,this.side="left",this["no-expand"]=!1,this["hide-when-collapsed"]=!1}connectedCallback(){super.connectedCallback(),this.aside=this.closest("k-aside"),this.aside&&(this.collapsed="collapsed"===this.aside.state,this.side=this.aside.side||"left",this.aside.addEventListener("aside_state_change",this.handleStateChange))}disconnectedCallback(){super.disconnectedCallback(),this.aside&&this.aside.removeEventListener("aside_state_change",this.handleStateChange)}handleStateChange=e=>{const{state:t}=e.detail;"collapsed"===t?(this.collapsed=!0,this.open=!1):"expanded"===t&&(this.collapsed=!1),this.side=this.aside?.side||"left"};toggleMenu=()=>{this.collapsed||(this.open=!this.open)};handleCollapsedClick=()=>{this.open=!0,this.aside?.expand()};render(){if(this.collapsed){const e="right"===this.side?"left down":"right down";return t`
|
|
212
|
+
<k-dropdown open-direction=${e} hover>
|
|
213
|
+
<button slot="trigger" class="no-btn collapsed-trigger" @click=${this.handleCollapsedClick}>
|
|
214
|
+
${this.icon?t`<k-icon name="${this.icon}"></k-icon>`:t`<k-icon name="dot"></k-icon>`}
|
|
215
|
+
</button>
|
|
216
|
+
<slot></slot>
|
|
217
|
+
</k-dropdown>
|
|
218
|
+
`}return t`
|
|
212
219
|
<div class="menu-container">
|
|
213
220
|
<button class="no-btn menu-header ${this.open?"open":""}" @click=${this.toggleMenu}>
|
|
214
|
-
${this.icon?t`<k-icon name="${this.icon}"></k-icon>`:
|
|
215
|
-
|
|
216
|
-
|
|
221
|
+
${this.icon?t`<k-icon name="${this.icon}"></k-icon>`:a}
|
|
222
|
+
<span class="label">${this.label}</span>
|
|
223
|
+
<k-icon class="chevron" name="chevron" direction="${this.open?"down":"right"}"></k-icon>
|
|
217
224
|
</button>
|
|
218
|
-
<div class="menu-content ${this.open
|
|
225
|
+
<div class="menu-content ${this.open?"open":""}">
|
|
219
226
|
<slot></slot>
|
|
220
227
|
</div>
|
|
221
228
|
</div>
|
|
@@ -242,12 +249,29 @@ import e from"./ShadowComponent.js";import{html as t,css as s,nothing as a}from"
|
|
|
242
249
|
white-space: nowrap;
|
|
243
250
|
transition: background var(--animation_ms);
|
|
244
251
|
}
|
|
245
|
-
:host([collapsed]) .menu-header {
|
|
246
|
-
width: auto;
|
|
247
|
-
}
|
|
248
252
|
.menu-header:hover {
|
|
249
253
|
background: var(--c_bg_hover);
|
|
250
254
|
}
|
|
255
|
+
.collapsed-trigger {
|
|
256
|
+
display: flex;
|
|
257
|
+
align-items: center;
|
|
258
|
+
justify-content: center;
|
|
259
|
+
padding: var(--spacer_h);
|
|
260
|
+
color: var(--tc);
|
|
261
|
+
background: transparent;
|
|
262
|
+
border: none;
|
|
263
|
+
appearance: none;
|
|
264
|
+
border-radius: var(--radius);
|
|
265
|
+
margin: 0 var(--spacer_h);
|
|
266
|
+
cursor: pointer;
|
|
267
|
+
transition: background var(--animation_ms);
|
|
268
|
+
}
|
|
269
|
+
.collapsed-trigger:hover {
|
|
270
|
+
background: var(--c_bg_hover);
|
|
271
|
+
}
|
|
272
|
+
k-dropdown {
|
|
273
|
+
display: block;
|
|
274
|
+
}
|
|
251
275
|
k-icon {
|
|
252
276
|
flex-shrink: 0;
|
|
253
277
|
}
|
|
@@ -266,9 +290,6 @@ import e from"./ShadowComponent.js";import{html as t,css as s,nothing as a}from"
|
|
|
266
290
|
transition: max-height var(--animation_ms);
|
|
267
291
|
padding-left: calc(var(--spacer_h) * 2);
|
|
268
292
|
}
|
|
269
|
-
:host([collapsed]) .menu-content {
|
|
270
|
-
padding-left: 0;
|
|
271
|
-
}
|
|
272
293
|
.menu-content.open {
|
|
273
294
|
max-height: 500px;
|
|
274
295
|
}
|
|
@@ -313,6 +334,7 @@ import e from"./ShadowComponent.js";import{html as t,css as s,nothing as a}from"
|
|
|
313
334
|
width: 2rem;
|
|
314
335
|
height: 2rem;
|
|
315
336
|
border: none;
|
|
337
|
+
appearance: none;
|
|
316
338
|
background: transparent;
|
|
317
339
|
color: var(--c_text);
|
|
318
340
|
cursor: pointer;
|
|
@@ -325,4 +347,4 @@ import e from"./ShadowComponent.js";import{html as t,css as s,nothing as a}from"
|
|
|
325
347
|
#toggle:hover {
|
|
326
348
|
background: var(--c_bg_hover);
|
|
327
349
|
}
|
|
328
|
-
`}customElements.define("k-aside-item",
|
|
350
|
+
`}customElements.define("k-aside-item",o),customElements.define("k-aside-label",r),customElements.define("k-aside-menu",d),customElements.define("k-aside-spacer",l),customElements.define("k-aside-toggle",c);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{html as e,css as t}from"../lit-all.min.js";import o from"./ShadowComponent.js";import{boolTrueFalse as n}from"../utils/propConverters.js";const i=new Set;export default class s extends o{static properties={opened:{type:Boolean,reflect:!0},openDirection:{type:String,reflect:!0,attribute:"open-direction"},closeOnSelect:{type:Boolean,reflect:!0,attribute:"close-on-select",converter:n},closeOnClickOutside:{type:Boolean,reflect:!0,attribute:"close-on-click-outside",converter:n},submenu:{type:Boolean,reflect:!0}};constructor(){super(),this.opened=!1,this.openDirection="down left",this.closeOnSelect=!0,this.closeOnClickOutside=!0,this.submenu=!1}connectedCallback(){super.connectedCallback(),"K-DROPDOWN"!==this.parentElement?.tagName||this.hasAttribute("slot")||(this.submenu=!0,this.hasAttribute("open-direction")||(this.openDirection="right down")),this.submenu?(this.addEventListener("mouseenter",this.handleSubmenuEnter),this.addEventListener("mouseleave",this.handleSubmenuLeave)):document.addEventListener("click",this.handleDocumentClick),document.addEventListener("keydown",this.handleKeydown)}disconnectedCallback(){super.disconnectedCallback(),this.submenu?(this.removeEventListener("mouseenter",this.handleSubmenuEnter),this.removeEventListener("mouseleave",this.handleSubmenuLeave)):document.removeEventListener("click",this.handleDocumentClick),document.removeEventListener("keydown",this.handleKeydown),i.delete(this)}updated(e){super.updated(e),e.has("opened")&&(this.submenu||(this.opened?i.add(this):i.delete(this)),this.dispatchEvent(new CustomEvent(this.opened?"opened":"closed",{bubbles:!0})))}handleDocumentClick=e=>{const t=e.composedPath(),o=t.find(e=>e.matches?.('[slot="trigger"]'));if(o){const e=o.closest("k-dropdown");if(e===this)return;if(this.contains(e))return;return void(this.opened&&this.close())}if(!this.opened)return;if(!t.includes(this))return void(this.closeOnClickOutside&&this.close());const n=t.find(e=>e.matches?.("a, button"));n&&!n.closest('[slot="trigger"]')&&this.closeOnSelect&&this.close()};handleTriggerClick=e=>{e.stopPropagation(),this.opened||i.forEach(e=>{e!==this&&e.close()}),this.toggle()};handleMenuClick=e=>{const t=e.target.closest("a, button");if(!t||t.hasAttribute("disabled"))return;if(t.closest("k-dropdown")!==this)return;const o=t.dataset?.value||t.textContent.trim();this.dispatchEvent(new CustomEvent("select",{detail:{value:o,item:t},bubbles:!0}))};handleKeydown=e=>{if(!this.opened)return;const t=document.activeElement;if(!this.contains(t))return;const o=this.querySelector(":scope > k-dropdown[opened]");if(!o?.contains(t))if("Escape"===e.key||this.submenu&&"ArrowLeft"===e.key)e.preventDefault(),this.close(),this.focusTrigger();else if("ArrowDown"===e.key)e.preventDefault(),this.focusNextItem();else if("ArrowUp"===e.key)e.preventDefault(),this.focusPreviousItem();else if("ArrowRight"===e.key){const o=t?.closest("k-dropdown[submenu]");o?.parentElement===this&&(e.preventDefault(),o.open(),o.focusFirstItem())}else if("Enter"===e.key||" "===e.key){const o=t?.closest("k-dropdown[submenu]");o?.parentElement===this?(e.preventDefault(),o.open(),o.focusFirstItem()):t&&!t.closest('[slot="trigger"]')&&(e.preventDefault(),t.click())}};open(){return this.submenu?[...this.parentElement.querySelectorAll(":scope > k-dropdown[opened]")].forEach(e=>{e!==this&&e.close()}):i.forEach(e=>{e!==this&&e.close()}),this.opened=!0,this.submenu||requestAnimationFrame(()=>this.focusFirstItem()),this}close(){return this.querySelectorAll(":scope > k-dropdown[opened]").forEach(e=>e.close()),this.opened=!1,this}toggle(){return this.opened?this.close():this.open()}focusTrigger(){const e=this.querySelector('[slot="trigger"]');e&&e.focus()}getMenuItems(){return[...this.children].reduce((e,t)=>{if(t.matches('[slot="trigger"]')||t.hasAttribute("disabled"))return e;if("K-DROPDOWN"===t.tagName){const o=t.querySelector('[slot="trigger"]');o&&e.push(o)}else t.matches("a, button")&&e.push(t);return e},[])}focusFirstItem(){const e=this.getMenuItems();e.length>0&&e[0].focus()}focusNextItem(){const e=this.getMenuItems(),t=document.activeElement,o=e.indexOf(t),n=e[o+1]||e[0];n&&n.focus()}focusPreviousItem(){const e=this.getMenuItems(),t=document.activeElement,o=e.indexOf(t),n=e[o-1]||e[e.length-1];n&&n.focus()}static styles=t`
|
|
1
|
+
import{html as e,css as t}from"../lit-all.min.js";import o from"./ShadowComponent.js";import{boolTrueFalse as n}from"../utils/propConverters.js";const i=new Set;export default class s extends o{static properties={opened:{type:Boolean,reflect:!0},openDirection:{type:String,reflect:!0,attribute:"open-direction"},closeOnSelect:{type:Boolean,reflect:!0,attribute:"close-on-select",converter:n},closeOnClickOutside:{type:Boolean,reflect:!0,attribute:"close-on-click-outside",converter:n},submenu:{type:Boolean,reflect:!0},hover:{type:Boolean,reflect:!0}};constructor(){super(),this.opened=!1,this.openDirection="down left",this.closeOnSelect=!0,this.closeOnClickOutside=!0,this.submenu=!1,this.hover=!1}connectedCallback(){super.connectedCallback(),"K-DROPDOWN"!==this.parentElement?.tagName||this.hasAttribute("slot")||(this.submenu=!0,this.hasAttribute("open-direction")||(this.openDirection="right down")),this.submenu?(this.addEventListener("mouseenter",this.handleSubmenuEnter),this.addEventListener("mouseleave",this.handleSubmenuLeave)):(document.addEventListener("click",this.handleDocumentClick),this.hover&&(this.addEventListener("mouseenter",this.handleHoverEnter),this.addEventListener("mouseleave",this.handleHoverLeave))),document.addEventListener("keydown",this.handleKeydown)}disconnectedCallback(){super.disconnectedCallback(),this.submenu?(this.removeEventListener("mouseenter",this.handleSubmenuEnter),this.removeEventListener("mouseleave",this.handleSubmenuLeave)):(document.removeEventListener("click",this.handleDocumentClick),this.removeEventListener("mouseenter",this.handleHoverEnter),this.removeEventListener("mouseleave",this.handleHoverLeave)),document.removeEventListener("keydown",this.handleKeydown),i.delete(this)}updated(e){super.updated(e),e.has("opened")&&(this.submenu||(this.opened?i.add(this):i.delete(this)),this.dispatchEvent(new CustomEvent(this.opened?"opened":"closed",{bubbles:!0})))}handleDocumentClick=e=>{const t=e.composedPath(),o=t.find(e=>e.matches?.('[slot="trigger"]'));if(o){const e=o.closest("k-dropdown");if(e===this)return;if(this.contains(e))return;return void(this.opened&&this.close())}if(!this.opened)return;if(!t.includes(this))return void(this.closeOnClickOutside&&this.close());const n=t.find(e=>e.matches?.("a, button"));n&&!n.closest('[slot="trigger"]')&&this.closeOnSelect&&this.close()};handleTriggerClick=e=>{this.hover||(e.stopPropagation(),this.opened||i.forEach(e=>{e!==this&&e.close()}),this.toggle())};handleMenuClick=e=>{const t=e.target.closest("a, button");if(!t||t.hasAttribute("disabled"))return;if(t.closest("k-dropdown")!==this)return;const o=t.dataset?.value||t.textContent.trim();this.dispatchEvent(new CustomEvent("select",{detail:{value:o,item:t},bubbles:!0}))};handleKeydown=e=>{if(!this.opened)return;const t=document.activeElement;if(!this.contains(t))return;const o=this.querySelector(":scope > k-dropdown[opened]");if(!o?.contains(t))if("Escape"===e.key||this.submenu&&"ArrowLeft"===e.key)e.preventDefault(),this.close(),this.focusTrigger();else if("ArrowDown"===e.key)e.preventDefault(),this.focusNextItem();else if("ArrowUp"===e.key)e.preventDefault(),this.focusPreviousItem();else if("ArrowRight"===e.key){const o=t?.closest("k-dropdown[submenu]");o?.parentElement===this&&(e.preventDefault(),o.open(),o.focusFirstItem())}else if("Enter"===e.key||" "===e.key){const o=t?.closest("k-dropdown[submenu]");o?.parentElement===this?(e.preventDefault(),o.open(),o.focusFirstItem()):t&&!t.closest('[slot="trigger"]')&&(e.preventDefault(),t.click())}};open(){return this.submenu?[...this.parentElement.querySelectorAll(":scope > k-dropdown[opened]")].forEach(e=>{e!==this&&e.close()}):i.forEach(e=>{e!==this&&e.close()}),this.opened=!0,this.submenu||requestAnimationFrame(()=>this.focusFirstItem()),this}close(){return this.querySelectorAll(":scope > k-dropdown[opened]").forEach(e=>e.close()),this.opened=!1,this}toggle(){return this.opened?this.close():this.open()}focusTrigger(){const e=this.querySelector('[slot="trigger"]');e&&e.focus()}getMenuItems(){return[...this.children].reduce((e,t)=>{if(t.matches('[slot="trigger"]')||t.hasAttribute("disabled"))return e;if("K-DROPDOWN"===t.tagName){const o=t.querySelector('[slot="trigger"]');o&&e.push(o)}else t.matches("a, button")&&e.push(t);return e},[])}focusFirstItem(){const e=this.getMenuItems();e.length>0&&e[0].focus()}focusNextItem(){const e=this.getMenuItems(),t=document.activeElement,o=e.indexOf(t),n=e[o+1]||e[0];n&&n.focus()}focusPreviousItem(){const e=this.getMenuItems(),t=document.activeElement,o=e.indexOf(t),n=e[o-1]||e[e.length-1];n&&n.focus()}static styles=t`
|
|
2
2
|
:host {
|
|
3
3
|
display: inline-block;
|
|
4
4
|
position: relative;
|
|
@@ -160,7 +160,7 @@ import{html as e,css as t}from"../lit-all.min.js";import o from"./ShadowComponen
|
|
|
160
160
|
font-size: 0.75em;
|
|
161
161
|
opacity: 0.6;
|
|
162
162
|
}
|
|
163
|
-
`;handleSubmenuEnter=()=>{clearTimeout(this.closeTimer),this.opened||this.open()};handleSubmenuLeave=()=>{this.closeTimer=setTimeout(()=>this.close(),150)};handleSlotChange=e=>{e.target.assignedElements().filter(e=>e.matches("a, button, k-dropdown")).forEach((e,t)=>e.classList.toggle("k-dropdown-first",0===t))};render(){return e`
|
|
163
|
+
`;handleHoverEnter=()=>{clearTimeout(this.closeTimer),this.opened||this.open()};handleHoverLeave=()=>{this.closeTimer=setTimeout(()=>this.close(),150)};handleSubmenuEnter=()=>{clearTimeout(this.closeTimer),this.opened||this.open()};handleSubmenuLeave=()=>{this.closeTimer=setTimeout(()=>this.close(),150)};handleSlotChange=e=>{e.target.assignedElements().filter(e=>e.matches("a, button, k-dropdown")).forEach((e,t)=>e.classList.toggle("k-dropdown-first",0===t))};render(){return e`
|
|
164
164
|
<div id="trigger" @click=${this.handleTriggerClick}>
|
|
165
165
|
<slot name="trigger"></slot>
|
|
166
166
|
${this.submenu?e`<k-icon name="chevron"></k-icon>`:""}
|
package/docs-src/.config.js
CHANGED
|
@@ -12,5 +12,15 @@ export default {
|
|
|
12
12
|
templating: {
|
|
13
13
|
ssr: true,
|
|
14
14
|
ssrPriority: true
|
|
15
|
+
},
|
|
16
|
+
"middleware": {
|
|
17
|
+
"security": {
|
|
18
|
+
"enabled": true,
|
|
19
|
+
"headers": {
|
|
20
|
+
"X-Content-Type-Options": "nosniff",
|
|
21
|
+
"X-Frame-Options": "SAMEORIGIN",
|
|
22
|
+
"X-XSS-Protection": "1; mode=block"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
15
25
|
}
|
|
16
26
|
};
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
<a href="#disabledItems">Disabled Items</a><br />
|
|
16
16
|
<a href="#links">Using Links</a><br />
|
|
17
17
|
<a href="#events">Events</a><br />
|
|
18
|
+
<a href="#hover">Hover</a><br />
|
|
18
19
|
|
|
19
20
|
<h6 class="mt"><a href="#jsRef" class="no-link">JavaScript Reference</a></h6>
|
|
20
21
|
<a href="#constructor">Constructor</a><br />
|
|
@@ -225,6 +226,26 @@
|
|
|
225
226
|
</div>
|
|
226
227
|
</div>
|
|
227
228
|
|
|
229
|
+
<h3 id="hover"><a href="#hover" class="no-link">Hover</a></h3>
|
|
230
|
+
<p>Add the <code>hover</code> attribute to open the menu when the trigger is hovered instead of clicked. The menu closes automatically 150ms after the cursor leaves the component.</p>
|
|
231
|
+
<div class="row -mx">
|
|
232
|
+
<div class="col m-span-12 px">
|
|
233
|
+
<k-card label="HTML">
|
|
234
|
+
<pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">k-dropdown</span> <span class="hljs-attr">hover</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">'trigger'</span>></span>Hover Me<span class="hljs-tag"></<span class="hljs-name">button</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">data-value</span>=<span class="hljs-string">'edit'</span>></span>Edit<span class="hljs-tag"></<span class="hljs-name">button</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">data-value</span>=<span class="hljs-string">'copy'</span>></span>Copy<span class="hljs-tag"></<span class="hljs-name">button</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">data-value</span>=<span class="hljs-string">'delete'</span>></span>Delete<span class="hljs-tag"></<span class="hljs-name">button</span>></span><br><span class="hljs-tag"></<span class="hljs-name">k-dropdown</span>></span></code></pre>
|
|
235
|
+
</k-card>
|
|
236
|
+
</div>
|
|
237
|
+
<div class="col m-span-12 px">
|
|
238
|
+
<k-card label="Output">
|
|
239
|
+
<k-dropdown hover>
|
|
240
|
+
<button slot="trigger">Hover Me</button>
|
|
241
|
+
<button data-value="edit">Edit</button>
|
|
242
|
+
<button data-value="copy">Copy</button>
|
|
243
|
+
<button data-value="delete">Delete</button>
|
|
244
|
+
</k-dropdown>
|
|
245
|
+
</k-card>
|
|
246
|
+
</div>
|
|
247
|
+
</div>
|
|
248
|
+
|
|
228
249
|
<h2 id="jsRef"><a href="#jsRef" class="no-link">JavaScript Reference</a></h2>
|
|
229
250
|
|
|
230
251
|
<h3 id="constructor"><a href="#constructor" class="no-link">Constructor</a></h3>
|
|
@@ -253,6 +274,9 @@
|
|
|
253
274
|
<h5><code>submenu<i>: boolean</i></code></h5>
|
|
254
275
|
<p>Automatically set to <code>true</code> when a <code>k-dropdown</code> is nested inside another <code>k-dropdown</code>. Sub menus open on hover/focus, display a chevron icon, and default to <code>open-direction="right down"</code>. Supports <code>ArrowRight</code> to open and <code>ArrowLeft</code> to close via keyboard.</p>
|
|
255
276
|
|
|
277
|
+
<h5><code>hover<i>: boolean</i></code></h5>
|
|
278
|
+
<p>When present, the menu opens on <code>mouseenter</code> instead of trigger click. The menu closes 150ms after the cursor leaves the component. Default is <code>false</code>.</p>
|
|
279
|
+
|
|
256
280
|
<h3 id="methods"><a href="#methods" class="no-link">Methods</a></h3>
|
|
257
281
|
<h5><code>open()<i>: this</i></code></h5>
|
|
258
282
|
<p>Opens the dropdown menu and focuses the first item.</p>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kempo-ui",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A Lit based web-component library",
|
|
6
6
|
"main": "index.js",
|
|
@@ -42,6 +42,6 @@
|
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@inquirer/prompts": "^8.3.0",
|
|
45
|
-
"kempo-css": "^2.
|
|
45
|
+
"kempo-css": "^2.2.0"
|
|
46
46
|
}
|
|
47
47
|
}
|
package/src/components/Aside.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import ShadowComponent from './ShadowComponent.js';
|
|
2
2
|
import { html, css, nothing } from '../lit-all.min.js';
|
|
3
3
|
import { boolTrueFalse } from '../utils/propConverters.js';
|
|
4
|
+
import './Dropdown.js';
|
|
4
5
|
import './FocusCapture.js';
|
|
5
6
|
import './Icon.js';
|
|
6
7
|
|
|
@@ -240,10 +241,10 @@ export default class Aside extends ShadowComponent {
|
|
|
240
241
|
}
|
|
241
242
|
:host([main="push"][state="collapsed"]) aside {
|
|
242
243
|
width: var(--collapsed-width);
|
|
243
|
-
transform:
|
|
244
|
+
transform: none;
|
|
244
245
|
}
|
|
245
246
|
:host([main="push"][state="expanded"]) aside {
|
|
246
|
-
transform:
|
|
247
|
+
transform: none;
|
|
247
248
|
}
|
|
248
249
|
:host([main="overlay"]) aside {
|
|
249
250
|
width: var(--width);
|
|
@@ -252,13 +253,13 @@ export default class Aside extends ShadowComponent {
|
|
|
252
253
|
transform: translateX(-100%);
|
|
253
254
|
}
|
|
254
255
|
:host([main="overlay"][state="expanded"]) aside {
|
|
255
|
-
transform:
|
|
256
|
+
transform: none;
|
|
256
257
|
}
|
|
257
258
|
:host([main="overlay"][side="right"]) aside {
|
|
258
259
|
transform: translateX(100%);
|
|
259
260
|
}
|
|
260
261
|
:host([main="overlay"][side="right"][state="expanded"]) aside {
|
|
261
|
-
transform:
|
|
262
|
+
transform: none;
|
|
262
263
|
}
|
|
263
264
|
`;
|
|
264
265
|
}
|
|
@@ -294,7 +295,8 @@ class AsideItem extends ShadowComponent {
|
|
|
294
295
|
connectedCallback() {
|
|
295
296
|
super.connectedCallback();
|
|
296
297
|
this.aside = this.closest('k-aside');
|
|
297
|
-
|
|
298
|
+
this.inMenu = !!this.closest('k-aside-menu');
|
|
299
|
+
if(this.aside && !this.inMenu) {
|
|
298
300
|
this.collapsed = this.aside.state === 'collapsed';
|
|
299
301
|
this.aside.addEventListener('aside_state_change', this.handleStateChange);
|
|
300
302
|
}
|
|
@@ -466,6 +468,7 @@ class AsideMenu extends ShadowComponent {
|
|
|
466
468
|
label: { type: String },
|
|
467
469
|
open: { type: Boolean, reflect: true },
|
|
468
470
|
collapsed: { type: Boolean, reflect: true },
|
|
471
|
+
side: { type: String },
|
|
469
472
|
'no-expand': { type: Boolean, attribute: 'no-expand' },
|
|
470
473
|
'hide-when-collapsed': { type: Boolean, attribute: 'hide-when-collapsed' }
|
|
471
474
|
};
|
|
@@ -476,6 +479,7 @@ class AsideMenu extends ShadowComponent {
|
|
|
476
479
|
this.label = '';
|
|
477
480
|
this.open = false;
|
|
478
481
|
this.collapsed = false;
|
|
482
|
+
this.side = 'left';
|
|
479
483
|
this['no-expand'] = false;
|
|
480
484
|
this['hide-when-collapsed'] = false;
|
|
481
485
|
}
|
|
@@ -488,6 +492,7 @@ class AsideMenu extends ShadowComponent {
|
|
|
488
492
|
this.aside = this.closest('k-aside');
|
|
489
493
|
if(this.aside) {
|
|
490
494
|
this.collapsed = this.aside.state === 'collapsed';
|
|
495
|
+
this.side = this.aside.side || 'left';
|
|
491
496
|
this.aside.addEventListener('aside_state_change', this.handleStateChange);
|
|
492
497
|
}
|
|
493
498
|
}
|
|
@@ -510,28 +515,41 @@ class AsideMenu extends ShadowComponent {
|
|
|
510
515
|
} else if(state === 'expanded') {
|
|
511
516
|
this.collapsed = false;
|
|
512
517
|
}
|
|
518
|
+
this.side = this.aside?.side || 'left';
|
|
513
519
|
};
|
|
514
520
|
|
|
515
521
|
toggleMenu = () => {
|
|
516
|
-
if(this.collapsed
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
522
|
+
if(!this.collapsed) this.open = !this.open;
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
handleCollapsedClick = () => {
|
|
526
|
+
this.open = true;
|
|
527
|
+
this.aside?.expand();
|
|
521
528
|
};
|
|
522
529
|
|
|
523
530
|
/*
|
|
524
531
|
Rendering
|
|
525
532
|
*/
|
|
526
533
|
render() {
|
|
534
|
+
if(this.collapsed) {
|
|
535
|
+
const openDirection = this.side === 'right' ? 'left down' : 'right down';
|
|
536
|
+
return html`
|
|
537
|
+
<k-dropdown open-direction=${openDirection} hover>
|
|
538
|
+
<button slot="trigger" class="no-btn collapsed-trigger" @click=${this.handleCollapsedClick}>
|
|
539
|
+
${this.icon ? html`<k-icon name="${this.icon}"></k-icon>` : html`<k-icon name="dot"></k-icon>`}
|
|
540
|
+
</button>
|
|
541
|
+
<slot></slot>
|
|
542
|
+
</k-dropdown>
|
|
543
|
+
`;
|
|
544
|
+
}
|
|
527
545
|
return html`
|
|
528
546
|
<div class="menu-container">
|
|
529
547
|
<button class="no-btn menu-header ${this.open ? 'open' : ''}" @click=${this.toggleMenu}>
|
|
530
|
-
${this.icon ? html`<k-icon name="${this.icon}"></k-icon>` :
|
|
531
|
-
|
|
532
|
-
|
|
548
|
+
${this.icon ? html`<k-icon name="${this.icon}"></k-icon>` : nothing}
|
|
549
|
+
<span class="label">${this.label}</span>
|
|
550
|
+
<k-icon class="chevron" name="chevron" direction="${this.open ? 'down' : 'right'}"></k-icon>
|
|
533
551
|
</button>
|
|
534
|
-
<div class="menu-content ${this.open
|
|
552
|
+
<div class="menu-content ${this.open ? 'open' : ''}">
|
|
535
553
|
<slot></slot>
|
|
536
554
|
</div>
|
|
537
555
|
</div>
|
|
@@ -564,12 +582,29 @@ class AsideMenu extends ShadowComponent {
|
|
|
564
582
|
white-space: nowrap;
|
|
565
583
|
transition: background var(--animation_ms);
|
|
566
584
|
}
|
|
567
|
-
:host([collapsed]) .menu-header {
|
|
568
|
-
width: auto;
|
|
569
|
-
}
|
|
570
585
|
.menu-header:hover {
|
|
571
586
|
background: var(--c_bg_hover);
|
|
572
587
|
}
|
|
588
|
+
.collapsed-trigger {
|
|
589
|
+
display: flex;
|
|
590
|
+
align-items: center;
|
|
591
|
+
justify-content: center;
|
|
592
|
+
padding: var(--spacer_h);
|
|
593
|
+
color: var(--tc);
|
|
594
|
+
background: transparent;
|
|
595
|
+
border: none;
|
|
596
|
+
appearance: none;
|
|
597
|
+
border-radius: var(--radius);
|
|
598
|
+
margin: 0 var(--spacer_h);
|
|
599
|
+
cursor: pointer;
|
|
600
|
+
transition: background var(--animation_ms);
|
|
601
|
+
}
|
|
602
|
+
.collapsed-trigger:hover {
|
|
603
|
+
background: var(--c_bg_hover);
|
|
604
|
+
}
|
|
605
|
+
k-dropdown {
|
|
606
|
+
display: block;
|
|
607
|
+
}
|
|
573
608
|
k-icon {
|
|
574
609
|
flex-shrink: 0;
|
|
575
610
|
}
|
|
@@ -588,9 +623,6 @@ class AsideMenu extends ShadowComponent {
|
|
|
588
623
|
transition: max-height var(--animation_ms);
|
|
589
624
|
padding-left: calc(var(--spacer_h) * 2);
|
|
590
625
|
}
|
|
591
|
-
:host([collapsed]) .menu-content {
|
|
592
|
-
padding-left: 0;
|
|
593
|
-
}
|
|
594
626
|
.menu-content.open {
|
|
595
627
|
max-height: 500px;
|
|
596
628
|
}
|
|
@@ -717,6 +749,7 @@ class AsideToggle extends ShadowComponent {
|
|
|
717
749
|
width: 2rem;
|
|
718
750
|
height: 2rem;
|
|
719
751
|
border: none;
|
|
752
|
+
appearance: none;
|
|
720
753
|
background: transparent;
|
|
721
754
|
color: var(--c_text);
|
|
722
755
|
cursor: pointer;
|
|
@@ -11,7 +11,8 @@ export default class Dropdown extends ShadowComponent {
|
|
|
11
11
|
openDirection: { type: String, reflect: true, attribute: 'open-direction' },
|
|
12
12
|
closeOnSelect: { type: Boolean, reflect: true, attribute: 'close-on-select', converter: boolTrueFalse },
|
|
13
13
|
closeOnClickOutside: { type: Boolean, reflect: true, attribute: 'close-on-click-outside', converter: boolTrueFalse },
|
|
14
|
-
submenu: { type: Boolean, reflect: true }
|
|
14
|
+
submenu: { type: Boolean, reflect: true },
|
|
15
|
+
hover: { type: Boolean, reflect: true }
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
constructor() {
|
|
@@ -21,6 +22,7 @@ export default class Dropdown extends ShadowComponent {
|
|
|
21
22
|
this.closeOnSelect = true;
|
|
22
23
|
this.closeOnClickOutside = true;
|
|
23
24
|
this.submenu = false;
|
|
25
|
+
this.hover = false;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
/*
|
|
@@ -37,6 +39,10 @@ export default class Dropdown extends ShadowComponent {
|
|
|
37
39
|
this.addEventListener('mouseleave', this.handleSubmenuLeave);
|
|
38
40
|
} else {
|
|
39
41
|
document.addEventListener('click', this.handleDocumentClick);
|
|
42
|
+
if(this.hover) {
|
|
43
|
+
this.addEventListener('mouseenter', this.handleHoverEnter);
|
|
44
|
+
this.addEventListener('mouseleave', this.handleHoverLeave);
|
|
45
|
+
}
|
|
40
46
|
}
|
|
41
47
|
document.addEventListener('keydown', this.handleKeydown);
|
|
42
48
|
}
|
|
@@ -48,6 +54,8 @@ export default class Dropdown extends ShadowComponent {
|
|
|
48
54
|
this.removeEventListener('mouseleave', this.handleSubmenuLeave);
|
|
49
55
|
} else {
|
|
50
56
|
document.removeEventListener('click', this.handleDocumentClick);
|
|
57
|
+
this.removeEventListener('mouseenter', this.handleHoverEnter);
|
|
58
|
+
this.removeEventListener('mouseleave', this.handleHoverLeave);
|
|
51
59
|
}
|
|
52
60
|
document.removeEventListener('keydown', this.handleKeydown);
|
|
53
61
|
openDropdowns.delete(this);
|
|
@@ -99,6 +107,7 @@ export default class Dropdown extends ShadowComponent {
|
|
|
99
107
|
};
|
|
100
108
|
|
|
101
109
|
handleTriggerClick = e => {
|
|
110
|
+
if(this.hover) return;
|
|
102
111
|
e.stopPropagation();
|
|
103
112
|
// Close all other dropdowns before opening this one
|
|
104
113
|
if(!this.opened) {
|
|
@@ -396,6 +405,15 @@ export default class Dropdown extends ShadowComponent {
|
|
|
396
405
|
}
|
|
397
406
|
`;
|
|
398
407
|
|
|
408
|
+
handleHoverEnter = () => {
|
|
409
|
+
clearTimeout(this.closeTimer);
|
|
410
|
+
if(!this.opened) this.open();
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
handleHoverLeave = () => {
|
|
414
|
+
this.closeTimer = setTimeout(() => this.close(), 150);
|
|
415
|
+
};
|
|
416
|
+
|
|
399
417
|
/*
|
|
400
418
|
Slot Change Handler
|
|
401
419
|
*/
|
|
@@ -814,6 +814,147 @@ export default {
|
|
|
814
814
|
pass('Clicking collapsed menu expands aside');
|
|
815
815
|
},
|
|
816
816
|
|
|
817
|
+
'AsideMenu: clicking collapsed menu should also set open to true': async ({pass, fail}) => {
|
|
818
|
+
const aside = await createAside({ main: 'push', state: 'collapsed' });
|
|
819
|
+
const menu = document.createElement('k-aside-menu');
|
|
820
|
+
menu.icon = 'folder';
|
|
821
|
+
menu.label = 'Projects';
|
|
822
|
+
aside.appendChild(menu);
|
|
823
|
+
await menu.updateComplete;
|
|
824
|
+
const btn = menu.shadowRoot.querySelector('button');
|
|
825
|
+
btn.click();
|
|
826
|
+
await menu.updateComplete;
|
|
827
|
+
if(!menu.open) {
|
|
828
|
+
cleanup(aside);
|
|
829
|
+
return fail('Menu open should be true after clicking collapsed trigger');
|
|
830
|
+
}
|
|
831
|
+
cleanup(aside);
|
|
832
|
+
pass('Clicking collapsed trigger sets menu open to true');
|
|
833
|
+
},
|
|
834
|
+
|
|
835
|
+
'AsideMenu: collapsed state should render k-dropdown': async ({pass, fail}) => {
|
|
836
|
+
const aside = await createAside({ main: 'push', state: 'collapsed' });
|
|
837
|
+
const menu = document.createElement('k-aside-menu');
|
|
838
|
+
menu.icon = 'folder';
|
|
839
|
+
menu.label = 'Projects';
|
|
840
|
+
aside.appendChild(menu);
|
|
841
|
+
await menu.updateComplete;
|
|
842
|
+
const dropdown = menu.shadowRoot.querySelector('k-dropdown');
|
|
843
|
+
if(!dropdown) {
|
|
844
|
+
cleanup(aside);
|
|
845
|
+
return fail('Collapsed menu should render k-dropdown');
|
|
846
|
+
}
|
|
847
|
+
cleanup(aside);
|
|
848
|
+
pass('Collapsed menu renders k-dropdown');
|
|
849
|
+
},
|
|
850
|
+
|
|
851
|
+
'AsideMenu: collapsed k-dropdown should have hover attribute': async ({pass, fail}) => {
|
|
852
|
+
const aside = await createAside({ main: 'push', state: 'collapsed' });
|
|
853
|
+
const menu = document.createElement('k-aside-menu');
|
|
854
|
+
menu.icon = 'folder';
|
|
855
|
+
menu.label = 'Projects';
|
|
856
|
+
aside.appendChild(menu);
|
|
857
|
+
await menu.updateComplete;
|
|
858
|
+
const dropdown = menu.shadowRoot.querySelector('k-dropdown');
|
|
859
|
+
if(!dropdown || !dropdown.hasAttribute('hover')) {
|
|
860
|
+
cleanup(aside);
|
|
861
|
+
return fail('Collapsed k-dropdown should have hover attribute');
|
|
862
|
+
}
|
|
863
|
+
cleanup(aside);
|
|
864
|
+
pass('Collapsed k-dropdown has hover attribute');
|
|
865
|
+
},
|
|
866
|
+
|
|
867
|
+
'AsideMenu: collapsed k-dropdown open-direction should be right down for left aside': async ({pass, fail}) => {
|
|
868
|
+
const aside = await createAside({ main: 'push', state: 'collapsed', side: 'left' });
|
|
869
|
+
const menu = document.createElement('k-aside-menu');
|
|
870
|
+
menu.icon = 'folder';
|
|
871
|
+
aside.appendChild(menu);
|
|
872
|
+
await menu.updateComplete;
|
|
873
|
+
const dropdown = menu.shadowRoot.querySelector('k-dropdown');
|
|
874
|
+
if(!dropdown || dropdown.getAttribute('open-direction') !== 'right down') {
|
|
875
|
+
cleanup(aside);
|
|
876
|
+
return fail(`Expected open-direction "right down", got "${dropdown?.getAttribute('open-direction')}"`);
|
|
877
|
+
}
|
|
878
|
+
cleanup(aside);
|
|
879
|
+
pass('Left aside collapsed menu opens right down');
|
|
880
|
+
},
|
|
881
|
+
|
|
882
|
+
'AsideMenu: collapsed k-dropdown open-direction should be left down for right aside': async ({pass, fail}) => {
|
|
883
|
+
const aside = await createAside({ main: 'push', state: 'collapsed', side: 'right' });
|
|
884
|
+
const menu = document.createElement('k-aside-menu');
|
|
885
|
+
menu.icon = 'folder';
|
|
886
|
+
aside.appendChild(menu);
|
|
887
|
+
await menu.updateComplete;
|
|
888
|
+
const dropdown = menu.shadowRoot.querySelector('k-dropdown');
|
|
889
|
+
if(!dropdown || dropdown.getAttribute('open-direction') !== 'left down') {
|
|
890
|
+
cleanup(aside);
|
|
891
|
+
return fail(`Expected open-direction "left down", got "${dropdown?.getAttribute('open-direction')}"`);
|
|
892
|
+
}
|
|
893
|
+
cleanup(aside);
|
|
894
|
+
pass('Right aside collapsed menu opens left down');
|
|
895
|
+
},
|
|
896
|
+
|
|
897
|
+
'AsideMenu: should not render k-dropdown when expanded': async ({pass, fail}) => {
|
|
898
|
+
const aside = await createAside({ main: 'push', state: 'expanded' });
|
|
899
|
+
const menu = document.createElement('k-aside-menu');
|
|
900
|
+
menu.icon = 'folder';
|
|
901
|
+
menu.label = 'Projects';
|
|
902
|
+
aside.appendChild(menu);
|
|
903
|
+
await menu.updateComplete;
|
|
904
|
+
const dropdown = menu.shadowRoot.querySelector('k-dropdown');
|
|
905
|
+
if(dropdown) {
|
|
906
|
+
cleanup(aside);
|
|
907
|
+
return fail('Expanded menu should not render k-dropdown');
|
|
908
|
+
}
|
|
909
|
+
cleanup(aside);
|
|
910
|
+
pass('Expanded menu does not render k-dropdown');
|
|
911
|
+
},
|
|
912
|
+
|
|
913
|
+
'AsideItem: inside AsideMenu should not sync collapsed state from aside': async ({pass, fail}) => {
|
|
914
|
+
const aside = await createAside({ main: 'push', state: 'expanded' });
|
|
915
|
+
const menu = document.createElement('k-aside-menu');
|
|
916
|
+
menu.icon = 'folder';
|
|
917
|
+
menu.label = 'Projects';
|
|
918
|
+
const item = document.createElement('k-aside-item');
|
|
919
|
+
item.icon = 'cards';
|
|
920
|
+
item.textContent = 'Dashboard';
|
|
921
|
+
menu.appendChild(item);
|
|
922
|
+
aside.appendChild(menu);
|
|
923
|
+
await item.updateComplete;
|
|
924
|
+
aside.collapse();
|
|
925
|
+
await aside.updateComplete;
|
|
926
|
+
await item.updateComplete;
|
|
927
|
+
if(item.collapsed) {
|
|
928
|
+
cleanup(aside);
|
|
929
|
+
return fail('AsideItem inside AsideMenu should not become collapsed');
|
|
930
|
+
}
|
|
931
|
+
cleanup(aside);
|
|
932
|
+
pass('AsideItem inside AsideMenu ignores aside collapsed state');
|
|
933
|
+
},
|
|
934
|
+
|
|
935
|
+
'AsideItem: inside AsideMenu should render label (not dot) when aside is collapsed': async ({pass, fail}) => {
|
|
936
|
+
const aside = await createAside({ main: 'push', state: 'expanded' });
|
|
937
|
+
const menu = document.createElement('k-aside-menu');
|
|
938
|
+
menu.icon = 'folder';
|
|
939
|
+
menu.label = 'Projects';
|
|
940
|
+
const item = document.createElement('k-aside-item');
|
|
941
|
+
item.icon = 'cards';
|
|
942
|
+
item.textContent = 'Dashboard';
|
|
943
|
+
menu.appendChild(item);
|
|
944
|
+
aside.appendChild(menu);
|
|
945
|
+
await item.updateComplete;
|
|
946
|
+
aside.collapse();
|
|
947
|
+
await aside.updateComplete;
|
|
948
|
+
await item.updateComplete;
|
|
949
|
+
const label = item.shadowRoot.querySelector('.label');
|
|
950
|
+
if(!label) {
|
|
951
|
+
cleanup(aside);
|
|
952
|
+
return fail('AsideItem inside AsideMenu should still render label when aside is collapsed');
|
|
953
|
+
}
|
|
954
|
+
cleanup(aside);
|
|
955
|
+
pass('AsideItem inside AsideMenu renders label regardless of aside state');
|
|
956
|
+
},
|
|
957
|
+
|
|
817
958
|
/*
|
|
818
959
|
AsideSpacer
|
|
819
960
|
*/
|
|
@@ -8,6 +8,7 @@ const createDropdown = async (options = {}) => {
|
|
|
8
8
|
${options.opened ? 'opened' : ''}
|
|
9
9
|
${options.closeOnSelect === false ? 'close-on-select="false"' : ''}
|
|
10
10
|
${options.closeOnClickOutside === false ? 'close-on-click-outside="false"' : ''}
|
|
11
|
+
${options.hover ? 'hover' : ''}
|
|
11
12
|
>
|
|
12
13
|
<button slot="trigger">Trigger</button>
|
|
13
14
|
<button data-value="item1">Item 1</button>
|
|
@@ -661,5 +662,65 @@ export default {
|
|
|
661
662
|
}
|
|
662
663
|
cleanup(container);
|
|
663
664
|
pass('Dropdown does not inflate in pre-wrap context');
|
|
665
|
+
},
|
|
666
|
+
|
|
667
|
+
/*
|
|
668
|
+
Hover Mode
|
|
669
|
+
*/
|
|
670
|
+
'hover should be false by default': async ({pass, fail}) => {
|
|
671
|
+
const { container, dropdown } = await createDropdown();
|
|
672
|
+
if(dropdown.hover !== false) {
|
|
673
|
+
cleanup(container);
|
|
674
|
+
return fail(`Expected hover to be false, got ${dropdown.hover}`);
|
|
675
|
+
}
|
|
676
|
+
cleanup(container);
|
|
677
|
+
pass('hover is false by default');
|
|
678
|
+
},
|
|
679
|
+
|
|
680
|
+
'hover attribute should reflect': async ({pass, fail}) => {
|
|
681
|
+
const { container, dropdown } = await createDropdown({ hover: true });
|
|
682
|
+
if(!dropdown.hasAttribute('hover')) {
|
|
683
|
+
cleanup(container);
|
|
684
|
+
return fail('hover attribute should be reflected');
|
|
685
|
+
}
|
|
686
|
+
cleanup(container);
|
|
687
|
+
pass('hover attribute reflects correctly');
|
|
688
|
+
},
|
|
689
|
+
|
|
690
|
+
'should open on mouseenter when hover is true': async ({pass, fail}) => {
|
|
691
|
+
const { container, dropdown } = await createDropdown({ hover: true });
|
|
692
|
+
dropdown.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
|
|
693
|
+
await dropdown.updateComplete;
|
|
694
|
+
if(!dropdown.opened) {
|
|
695
|
+
cleanup(container);
|
|
696
|
+
return fail('Dropdown should open on mouseenter in hover mode');
|
|
697
|
+
}
|
|
698
|
+
cleanup(container);
|
|
699
|
+
pass('Dropdown opens on mouseenter in hover mode');
|
|
700
|
+
},
|
|
701
|
+
|
|
702
|
+
'should close on mouseleave when hover is true': async ({pass, fail}) => {
|
|
703
|
+
const { container, dropdown } = await createDropdown({ hover: true, opened: true });
|
|
704
|
+
dropdown.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }));
|
|
705
|
+
await new Promise(r => setTimeout(r, 200));
|
|
706
|
+
if(dropdown.opened) {
|
|
707
|
+
cleanup(container);
|
|
708
|
+
return fail('Dropdown should close after mouseleave in hover mode');
|
|
709
|
+
}
|
|
710
|
+
cleanup(container);
|
|
711
|
+
pass('Dropdown closes on mouseleave in hover mode');
|
|
712
|
+
},
|
|
713
|
+
|
|
714
|
+
'trigger click should not toggle in hover mode': async ({pass, fail}) => {
|
|
715
|
+
const { container, dropdown } = await createDropdown({ hover: true });
|
|
716
|
+
const trigger = dropdown.querySelector('[slot="trigger"]');
|
|
717
|
+
trigger.click();
|
|
718
|
+
await dropdown.updateComplete;
|
|
719
|
+
if(dropdown.opened) {
|
|
720
|
+
cleanup(container);
|
|
721
|
+
return fail('Trigger click should not open dropdown in hover mode');
|
|
722
|
+
}
|
|
723
|
+
cleanup(container);
|
|
724
|
+
pass('Trigger click does not toggle in hover mode');
|
|
664
725
|
}
|
|
665
726
|
};
|
/package/{llm.txt → llms.txt}
RENAMED
|
File without changes
|