Skip to content

Commit

Permalink
PoC working of ArrowRight to focus on submenu.
Browse files Browse the repository at this point in the history
  • Loading branch information
ecyrb committed Jun 30, 2023
1 parent 3544044 commit be04e9a
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 1 deletion.
27 changes: 27 additions & 0 deletions src/components/menu-item/menu-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export default class SlMenuItem extends ShoelaceElement {
constructor() {
super();
this.addEventListener('click', this.handleHostClick);
this.addEventListener('keydown', this.handleKeyDown);
this.submenuController = new SubmenuController(this, this.hasSlotController, this.localize);
}

Expand All @@ -86,6 +87,28 @@ export default class SlMenuItem extends ShoelaceElement {
event.stopImmediatePropagation();
}
};

private handleKeyDown(event: KeyboardEvent) {
console.log(`<menu-item ${this.id}> handleKeyDown: ${event.key}`);
}

/*
// Make a selection when pressing enter
if (event.key === 'Enter') {
event.preventDefault();
// Simulate a click to support @click handlers on menu items that also work with the keyboard
// item?.click();
}
// Prevent scrolling when space is pressed
if (event.key === ' ') {
event.preventDefault();
}
}
*/


@watch('checked')
handleCheckedChange() {
Expand All @@ -111,9 +134,13 @@ export default class SlMenuItem extends ShoelaceElement {

@watch('type')
handleTypeChange() {
console.log(`handleTypeChange() ${this.getTextLabel()} : submenu? ${this.hasSlotController.test("submenu")}`);
if (this.type === 'checkbox') {
this.setAttribute('role', 'menuitemcheckbox');
this.setAttribute('aria-checked', this.checked ? 'true' : 'false');
} else if (this.hasSlotController.test('submenu')) {
this.setAttribute('role', 'menu');
this.removeAttribute('aria-checked');
} else {
this.setAttribute('role', 'menuitem');
this.removeAttribute('aria-checked');
Expand Down
63 changes: 63 additions & 0 deletions src/components/menu-item/submenu-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type { ReactiveController, ReactiveControllerHost } from 'lit';
export class SubmenuController implements ReactiveController {
private host: ReactiveControllerHost & SlMenuItem;
private popupRef: Ref<SlPopup> = createRef();

private mouseOutTimer: number = -1;
private isActive: boolean = false;

Expand Down Expand Up @@ -50,6 +51,7 @@ export class SubmenuController implements ReactiveController {
if (!this.isActive) {
this.host.addEventListener('mouseover', this.handleMouseOver);
this.host.addEventListener('mouseout', this.handleMouseOut);
this.host.addEventListener('keydown', (event) => { this.handleKeyDown(event) });
this.isActive = true;
}
}
Expand All @@ -58,6 +60,7 @@ export class SubmenuController implements ReactiveController {
if (this.isActive) {
this.host.removeEventListener('mouseover', this.handleMouseOver);
this.host.removeEventListener('mouseout', this.handleMouseOut);
this.host.removeEventListener('keydown', this.handleKeyDown);
this.isActive = false;
}
}
Expand All @@ -79,6 +82,65 @@ export class SubmenuController implements ReactiveController {
}, 100);
}
};

private isMenuItem(item: HTMLElement) {
return (
item.tagName.toLowerCase() === 'sl-menu-item' ||
['menuitem', 'menuitemcheckbox', 'menuitemradio'].includes(item.getAttribute('role') ?? '')
);
}

/** @internal Gets all slotted menu items, ignoring dividers, headers, and other elements. */
getAllItems() {
const rr = this.host.renderRoot;
const slotQS : HTMLSlotElement = rr.querySelector("slot[name='submenu']") as HTMLSlotElement;
console.log(slotQS);
const aEs = slotQS.assignedElements({ flatten: true });
console.log(aEs[0]);
const menuItems = aEs.filter((el: HTMLElement) => {
if (el.inert || !this.isMenuItem(el)) {
return false;
}
return true;
}) as SlMenuItem[];
return [...menuItems];
}

/*
return [...this.host.renderRoot.querySelector("slot[name='submenu']").assignedElements({ flatten: true }).filter((el: HTMLElement) => {
if (el.inert || !this.isMenuItem(el)) {
return false;
}
return true;
}) as SlMenuItem[];
}
*/

private handleKeyDown(event: KeyboardEvent) {
console.log(`submenuController.handleKeyDown: ${event.key}`);
switch(event.key) {
case "ArrowRight":
console.log("ArrowRight detected.");
//let items = this.getAllItems();
// find *a* menu-item
// let item = this.host.renderRoot.querySelector("sl-menu-item, [role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio']")
//let item = this.host.renderRoot.querySelector("sl-menu-item");
let item : HTMLSlotElement = this.host.renderRoot.querySelector("slot[name='submenu']") as HTMLSlotElement;
console.log(item);
console.log(item!.assignedElements()[0]);
console.log(item!.assignedElements()[0].querySelector("sl-menu-item"));
item!.assignedElements()[0].querySelector("sl-menu-item")!.focus();
break;
}
}

show() {

}

hide() {

}

renderSubmenu() {
// Always render the slot. Conditionally render the outer sl-popup.
Expand All @@ -99,6 +161,7 @@ export class SubmenuController implements ReactiveController {
placement=${isLtr ? 'right-start' : 'left-start'}
anchor="anchor"
flip
flip-fallback-strategy="best-fit"
strategy="fixed"
>
<slot name="submenu"></slot>
Expand Down
13 changes: 12 additions & 1 deletion src/components/popup/popup.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { arrow, autoUpdate, computePosition, flip, offset, platform, shift, size } from '@floating-ui/dom';
import { arrow, autoPlacement, autoUpdate, computePosition, flip, offset, platform, shift, size } from '@floating-ui/dom';
import { classMap } from 'lit/directives/class-map.js';
import { customElement, property, query } from 'lit/decorators.js';
import { html } from 'lit';
Expand Down Expand Up @@ -114,6 +114,11 @@ export default class SlPopup extends ShoelaceElement {
*/
@property({ type: Boolean }) flip = false;

/**
* When set, placement of the popup will choose the placement that has the most space avaiable automatically.
*/
@property({ attribute: 'auto-placement', type: Boolean }) autoPlacement = false;

/**
* If the preferred placement doesn't fit, popup will be tested in these fallback placements until one fits. Must be a
* string of any number of placements separated by a space, e.g. "top bottom left". If no placement fits, the flip
Expand Down Expand Up @@ -305,8 +310,14 @@ export default class SlPopup extends ShoelaceElement {
this.popup.style.width = '';
this.popup.style.height = '';
}

// Then we check for autoPlacement
if (this.autoPlacement) {
middleware.push(autoPlacement());
}

// Then we flip
// TODO Don't do both
if (this.flip) {
middleware.push(
flip({
Expand Down

0 comments on commit be04e9a

Please sign in to comment.