Skip to content

Commit

Permalink
fix(select): render select menu in document.body
Browse files Browse the repository at this point in the history
  • Loading branch information
jgroth authored and adrianschmidt committed Sep 10, 2019
1 parent a6e817c commit 3b09b9b
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 87 deletions.
41 changes: 30 additions & 11 deletions src/components/list/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,20 +68,16 @@ export class List {
this.handleAction = this.handleAction.bind(this);
}

public componentDidLoad() {
this.mdcList = new MDCList(
this.element.shadowRoot.querySelector('.mdc-list')
);

this.handleType();
public connectedCallback() {
this.setup();
}

public componentDidUnload() {
if (this.selectable) {
this.mdcList.unlisten(ACTION_EVENT, this.handleAction);
}
public disconnectedCallback() {
this.teardown();
}

this.mdcList.destroy();
public componentDidLoad() {
this.setup();
}

public render() {
Expand All @@ -95,6 +91,21 @@ export class List {

@Watch('type')
protected handleType() {
this.setupListeners();
}

private setup() {
const element = this.element.shadowRoot.querySelector('.mdc-list');
if (!element) {
return;
}

this.mdcList = new MDCList(element);

this.setupListeners();
}

private setupListeners() {
this.mdcList.unlisten(ACTION_EVENT, this.handleAction);

this.selectable = ['selectable', 'radio', 'checkbox'].includes(
Expand All @@ -110,6 +121,14 @@ export class List {
this.mdcList.singleSelection = !this.multiple;
}

private teardown() {
if (this.selectable) {
this.mdcList.unlisten(ACTION_EVENT, this.handleAction);
}

this.mdcList.destroy();
}

private handleAction(event: MDCListActionEvent) {
if (!this.multiple) {
this.handleSingleSelect(event.detail.index);
Expand Down
4 changes: 4 additions & 0 deletions src/components/select/select.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ When importing Option, see [Import Statements](/#import-statements).
### Changing Available Options

<limel-example name="limel-example-select-change-options" path="select" />

### Select field inside a dialog

<limel-example name="limel-example-select-dialog" path="select" />
17 changes: 4 additions & 13 deletions src/components/select/select.scss
Original file line number Diff line number Diff line change
Expand Up @@ -126,18 +126,9 @@ $mdc-select-bottom-line-hover-color: $lime-text-field-bottom-line-color;
}
}

.mdc-menu-surface {
left: 0;
top: pxToRem(56);
width: 100%;
}

.mdc-menu-surface--scrim {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 5;
&.limel-select--empty {
limel-portal {
margin-top: pxToRem(-8);
}
}
}
48 changes: 13 additions & 35 deletions src/components/select/select.template.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import { FunctionalComponent, h } from '@stencil/core';
import {
ENTER,
ENTER_KEY_CODE,
SPACE,
SPACE_KEY_CODE,
} from '../../util/keycodes';
import { isMultiple } from '../../util/multiple';
import { ListItem } from '../list/list-item.types';
import { Option } from './option.types';
Expand Down Expand Up @@ -78,7 +72,9 @@ export const NativeSelectTemplate: FunctionalComponent<
};

interface MenuSelectTemplateProps extends SelectTemplateProps {
id: string;
onChange?: (event: CustomEvent<ListItem | ListItem[]>) => void;
onTriggerPress: (event: KeyboardEvent) => void;
isOpen: boolean;
open: () => void;
close: () => void;
Expand Down Expand Up @@ -109,6 +105,7 @@ export const MenuSelectTemplate: FunctionalComponent<
${props.disabled ? 'mdc-select--disabled' : ''}
${props.required ? 'limel-select--required' : ''}
${!isValid ? 'limel-select--invalid' : ''}
${!hasValue ? 'limel-select--empty' : ''}
`}
>
<div
Expand All @@ -118,7 +115,7 @@ export const MenuSelectTemplate: FunctionalComponent<
limel-select-trigger
${props.isOpen ? 'mdc-select--focused' : ''}
`}
slot="trigger"
onKeyPress={props.onTriggerPress}
>
<i class="mdc-select__dropdown-icon" />
<div class="limel-select__selected-text">
Expand All @@ -144,38 +141,19 @@ export const MenuSelectTemplate: FunctionalComponent<
`}
/>
</div>
{props.isOpen ? (
<div class="mdc-menu-surface--scrim" onClick={props.close} />
) : null}
<div
class={`
mdc-menu-surface
${props.isOpen ? 'mdc-menu-surface--open' : ''}
`}
tabindex="-1"
>
<limel-list
items={items}
type={props.multiple ? 'checkbox' : 'selectable'}
onKeyDown={handleListKeys}
onKeyUp={handleListKeys}
onChange={props.onChange}
/>
</div>
<limel-portal containerId={props.id} visible={props.isOpen}>
<limel-menu-surface open={props.isOpen} onDismiss={props.close}>
<limel-list
items={items}
type={props.multiple ? 'checkbox' : 'selectable'}
onChange={props.onChange}
/>
</limel-menu-surface>
</limel-portal>
</div>
);
};

function handleListKeys(event: KeyboardEvent) {
const isEnter = event.key === ENTER || event.keyCode === ENTER_KEY_CODE;
const isSpace = event.key === SPACE || event.keyCode === SPACE_KEY_CODE;

if (isSpace || isEnter) {
event.stopPropagation();
event.preventDefault();
}
}

function isSelected(option: Option, value: Option | Option[]): boolean {
if (!value) {
return false;
Expand Down
68 changes: 40 additions & 28 deletions src/components/select/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@ import { isMobileDevice } from '../../util/device';
import {
ENTER,
ENTER_KEY_CODE,
ESCAPE,
ESCAPE_KEY_CODE,
SPACE,
SPACE_KEY_CODE,
} from '../../util/keycodes';
import { isMultiple } from '../../util/multiple';
import { createRandomString } from '../../util/random-string';
import { ListItem } from '../list/list-item.types';
import { MenuSelectTemplate, NativeSelectTemplate } from './select.template';

Expand Down Expand Up @@ -89,13 +88,18 @@ export class Select {

private isMobileDevice: boolean;

private portalId: string;

constructor() {
this.handleMenuChange = this.handleMenuChange.bind(this);
this.handleNativeChange = this.handleNativeChange.bind(this);
this.handleMenuKeyDown = this.handleMenuKeyDown.bind(this);
this.handleMenuKeyUp = this.handleMenuKeyUp.bind(this);
this.handleMenuTriggerKeyPress = this.handleMenuTriggerKeyPress.bind(
this
);
this.openMenu = this.openMenu.bind(this);
this.closeMenu = this.closeMenu.bind(this);

this.portalId = createRandomString();
}

public componentWillLoad() {
Expand All @@ -117,9 +121,6 @@ export class Select {
element = this.host.shadowRoot.querySelector('.mdc-line-ripple');
this.mdcLineRipple = new MDCLineRipple(element);

this.host.addEventListener('keydown', this.handleMenuKeyDown);
this.host.addEventListener('keyup', this.handleMenuKeyUp);

return;
}

Expand Down Expand Up @@ -149,21 +150,28 @@ export class Select {
if (this.mdcMenuSurface) {
this.mdcMenuSurface.destroy();
}
}

this.host.removeEventListener('keydown', this.handleMenuKeyDown);
this.host.removeEventListener('keyup', this.handleMenuKeyUp);
public componentDidUpdate() {
if (this.menuOpen) {
this.setMenuFocus();
} else {
this.setTriggerFocus();
}
}

public render() {
if (!this.isMobileDevice) {
return (
<MenuSelectTemplate
id={this.portalId}
disabled={this.disabled}
required={this.required}
label={this.label}
value={this.value}
options={this.options}
onChange={this.handleMenuChange}
onTriggerPress={this.handleMenuTriggerKeyPress}
multiple={this.multiple}
isOpen={this.menuOpen}
open={this.openMenu}
Expand Down Expand Up @@ -198,6 +206,26 @@ export class Select {
}
}

private setMenuFocus() {
setTimeout(() => {
const list: HTMLElement = document.querySelector(
`#${this.portalId} limel-menu-surface limel-list`
);
const firstItem: HTMLElement = list.shadowRoot.querySelector(
'[tabindex]'
);

firstItem.focus();
});
}

private setTriggerFocus() {
const trigger: HTMLElement = this.host.shadowRoot.querySelector(
'.limel-select-trigger'
);
trigger.focus();
}

private handleMenuChange(
event: CustomEvent<Array<ListItem<Option>> | ListItem<Option>>
) {
Expand Down Expand Up @@ -225,30 +253,14 @@ export class Select {
this.menuOpen = false;
}

private handleMenuKeyDown(event: KeyboardEvent) {
private handleMenuTriggerKeyPress(event: KeyboardEvent) {
const isEnter = event.key === ENTER || event.keyCode === ENTER_KEY_CODE;
const isSpace = event.key === SPACE || event.keyCode === SPACE_KEY_CODE;

if (isSpace || isEnter) {
if (!this.menuOpen && (isSpace || isEnter)) {
event.stopPropagation();
event.preventDefault();
}
}

private handleMenuKeyUp(event: KeyboardEvent) {
const isEnter = event.key === ENTER || event.keyCode === ENTER_KEY_CODE;
const isSpace = event.key === SPACE || event.keyCode === SPACE_KEY_CODE;
const isEscape =
event.key === ESCAPE || event.keyCode === ESCAPE_KEY_CODE;

if (isSpace || isEnter) {
event.stopPropagation();
this.menuOpen = !this.menuOpen;
}

if (isEscape) {
event.stopPropagation();
this.menuOpen = false;
this.menuOpen = true;
}
}

Expand Down
9 changes: 9 additions & 0 deletions src/examples/select/select-dialog.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
limel-dialog {
--dialog-height: 200px;
}

limel-icon {
height: 300px;
width: 300px;
color: var(--lime-dark-grey);
}
Loading

0 comments on commit 3b09b9b

Please sign in to comment.