Skip to content

DO NOT MERGE: replace symbols with dumb classes #31

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@
"sourceType": "module",
"ecmaVersion": 2018,
"project": "./tsconfig.json"
},
"rules": {
"max-classes-per-file": "off"
}
}
40 changes: 19 additions & 21 deletions src/keyboard-direction-mixin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,27 @@ import { SlottedItemsInterface } from './slotted-items-mixin';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Constructor<T = object> = new (...args: any[]) => T;

export const $onKeydown = Symbol('onKeydown');
export interface KeyboardDirectionInterface {
items: HTMLElement[];
focused: Element | null;
}

export const $isPrevKey = Symbol('isPrevKey');
export abstract class KeyboardDirectionClass extends LitElement {
protected _onKeydown?(event: KeyboardEvent): void;

export const $isNextKey = Symbol('isNextKey');
protected _focus?(item: HTMLElement): void;

export const $focus = Symbol('focus');
protected _isNextKey?(key: string): boolean;

export interface KeyboardDirectionInterface {
items: HTMLElement[];
focused: Element | null;
[$onKeydown](event: KeyboardEvent): void;
[$isPrevKey](key: string): boolean;
[$isNextKey](key: string): boolean;
[$focus](item: HTMLElement): void;
protected _isPrevKey?(key: string): boolean;
}

export type ItemCondition = (item: Element) => boolean;

export const isFocusable: ItemCondition = (item: Element) =>
!item.hasAttribute('disabled') && !item.hasAttribute('hidden');

export type KeyboardDirectionConstructor = Constructor<KeyboardDirectionInterface>;
export type KeyboardDirectionConstructor = Constructor<KeyboardDirectionClass & KeyboardDirectionInterface>;

/**
* Returns index of the next item that satisfies the given condition,
Expand Down Expand Up @@ -60,7 +58,7 @@ export const getAvailableIndex = (
return -1;
};

export const KeyboardDirectionMixin = <T extends Constructor<SlottedItemsInterface & LitElement>>(
export const KeyboardDirectionMixin = <T extends Constructor<SlottedItemsInterface & KeyboardDirectionClass>>(
base: T
): KeyboardDirectionConstructor & T => {
class KeyboardDirection extends base {
Expand All @@ -81,11 +79,11 @@ export const KeyboardDirectionMixin = <T extends Constructor<SlottedItemsInterfa
super.firstUpdated(props);

this.addEventListener('keydown', (event: KeyboardEvent) => {
this[$onKeydown](event);
this._onKeydown(event);
});
}

[$onKeydown](event: KeyboardEvent) {
protected _onKeydown(event: KeyboardEvent) {
if (event.metaKey || event.ctrlKey) {
return;
}
Expand All @@ -97,10 +95,10 @@ export const KeyboardDirectionMixin = <T extends Constructor<SlottedItemsInterfa
let idx;
let increment;

if (this[$isPrevKey](key)) {
if (this._isPrevKey(key)) {
increment = -1;
idx = currentIdx - 1;
} else if (this[$isNextKey](key)) {
} else if (this._isNextKey(key)) {
increment = 1;
idx = currentIdx + 1;
} else if (key === 'Home') {
Expand All @@ -116,20 +114,20 @@ export const KeyboardDirectionMixin = <T extends Constructor<SlottedItemsInterfa
event.preventDefault();
const item = items[idx] as HTMLElement;
if (item) {
this[$focus](item);
this._focus(item);
}
}
}

[$isPrevKey](key: string) {
protected _isPrevKey(key: string) {
return key === 'ArrowUp' || key === 'ArrowLeft';
}

[$isNextKey](key: string) {
protected _isNextKey(key: string) {
return key === 'ArrowDown' || key === 'ArrowRight';
}

[$focus](item: HTMLElement) {
protected _focus(item: HTMLElement) {
item.focus();
}
}
Expand Down
46 changes: 27 additions & 19 deletions src/roving-tabindex-mixin.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
import { LitElement } from 'lit-element';
import { KeyboardDirectionInterface, getAvailableIndex, isFocusable, $focus } from './keyboard-direction-mixin';
import { $itemsChanged, SlottedItemsInterface } from './slotted-items-mixin';
import {
KeyboardDirectionClass,
KeyboardDirectionInterface,
getAvailableIndex,
isFocusable
} from './keyboard-direction-mixin';
import { SlottedItemsClass, SlottedItemsInterface } from './slotted-items-mixin';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Constructor<T = object> = new (...args: any[]) => T;

const $setFocusable = Symbol('setFocusable');
export abstract class RovingTabIndexClass extends LitElement {
protected _setFocusable?(idx: number): void;

const $setTabIndex = Symbol('setTabIndex');

export interface RovingTabIndexInterface {
[$setFocusable](idx: number): void;
[$setTabIndex](item: HTMLElement): void;
protected _setTabIndex?(item: HTMLElement): void;
}

export type RovingTabIndexConstructor = Constructor<RovingTabIndexInterface>;
export type RovingTabIndexConstructor = Constructor<RovingTabIndexClass>;

export const RovingTabIndexMixin = <
T extends Constructor<KeyboardDirectionInterface & SlottedItemsInterface & LitElement>
T extends Constructor<
KeyboardDirectionClass &
KeyboardDirectionInterface &
SlottedItemsClass &
SlottedItemsInterface &
RovingTabIndexClass
>
>(
base: T
): T & RovingTabIndexConstructor => {
Expand All @@ -29,30 +37,30 @@ export const RovingTabIndexMixin = <
}
}

[$itemsChanged](items: HTMLElement[], oldItems: HTMLElement[]) {
super[$itemsChanged](items, oldItems);
protected _itemsChanged(items: HTMLElement[], oldItems: HTMLElement[]) {
super._itemsChanged && super._itemsChanged(items, oldItems); // eslint-disable-line no-unused-expressions

if (items) {
const { focused } = this;
this[$setFocusable](focused && this.contains(focused) ? items.indexOf(focused as HTMLElement) : 0);
this._setFocusable(focused && this.contains(focused) ? items.indexOf(focused as HTMLElement) : 0);
}
}

[$setFocusable](idx: number) {
protected _setFocusable(idx: number) {
const index = getAvailableIndex(this.items, idx, 1, isFocusable);
this[$setTabIndex](this.items[index]);
this._setTabIndex(this.items[index]);
}

[$setTabIndex](item: HTMLElement) {
protected _setTabIndex(item: HTMLElement) {
this.items.forEach((el: HTMLElement) => {
// eslint-disable-next-line no-param-reassign
el.tabIndex = el === item ? 0 : -1;
});
}

[$focus](item: HTMLElement) {
super[$focus](item);
this[$setTabIndex](item);
protected _focus(item: HTMLElement) {
super._focus && super._focus(item); // eslint-disable-line no-unused-expressions
this._setTabIndex(item);
}
}

Expand Down
25 changes: 13 additions & 12 deletions src/slotted-items-mixin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@ import { LitElement, property, PropertyValues } from 'lit-element';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Constructor<T = object> = new (...args: any[]) => T;

export const $itemsChanged = Symbol('itemsChanged');
export const $filterItems = Symbol('filterItems');

export interface SlottedItemsInterface {
items: HTMLElement[];
[$itemsChanged](items: HTMLElement[], oldItems: HTMLElement[]): void;
[$filterItems](): HTMLElement[];
}

export type SlottedItemsConstructor = Constructor<SlottedItemsInterface>;
export abstract class SlottedItemsClass extends LitElement {
protected _itemsChanged?(items: HTMLElement[], oldItems: HTMLElement[]): void;

protected _filterItems?(): HTMLElement[];
}

export type SlottedItemsConstructor = Constructor<SlottedItemsClass & SlottedItemsInterface>;

export const SlottedItemsMixin = <T extends Constructor<LitElement>>(base: T): SlottedItemsConstructor & T => {
export const SlottedItemsMixin = <T extends Constructor<SlottedItemsClass>>(base: T): SlottedItemsConstructor & T => {
class SlottedItems extends base {
@property({ attribute: false, hasChanged: () => true })
protected _items: HTMLElement[] = [];
Expand All @@ -26,12 +27,12 @@ export const SlottedItemsMixin = <T extends Constructor<LitElement>>(base: T): S
connectedCallback() {
super.connectedCallback();

this._items = this[$filterItems]();
this._items = this._filterItems();
}

protected update(props: PropertyValues) {
if (props.has('_items')) {
this[$itemsChanged](this._items, (props.get('_items') || []) as HTMLElement[]);
this._itemsChanged(this._items, (props.get('_items') || []) as HTMLElement[]);
}

super.update(props);
Expand All @@ -43,13 +44,13 @@ export const SlottedItemsMixin = <T extends Constructor<LitElement>>(base: T): S
const slot = this.renderRoot.querySelector('slot');
if (slot) {
slot.addEventListener('slotchange', () => {
this._items = this[$filterItems]();
this._items = this._filterItems();
});
}
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
[$itemsChanged](items: HTMLElement[], _oldItems: HTMLElement[]) {
protected _itemsChanged(items: HTMLElement[], _oldItems: HTMLElement[]) {
this.dispatchEvent(
new CustomEvent('items-changed', {
detail: {
Expand All @@ -59,7 +60,7 @@ export const SlottedItemsMixin = <T extends Constructor<LitElement>>(base: T): S
);
}

[$filterItems]() {
protected _filterItems() {
return Array.from(this.children) as HTMLElement[];
}
}
Expand Down
24 changes: 12 additions & 12 deletions src/vaadin-accordion.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { html, css, customElement, property, PropertyValues } from 'lit-element';
import { VaadinElement } from '@vaadin/element-base/vaadin-element.js';
import { KeyboardDirectionMixin, $focus, $isPrevKey, $isNextKey, $onKeydown } from './keyboard-direction-mixin';
import { SlottedItemsMixin, $itemsChanged, $filterItems } from './slotted-items-mixin';
import { KeyboardDirectionMixin } from './keyboard-direction-mixin';
import { SlottedItemsMixin } from './slotted-items-mixin';
import { VaadinAccordionPanel } from './vaadin-accordion-panel';

declare global {
Expand Down Expand Up @@ -85,25 +85,25 @@ export class VaadinAccordion extends KeyboardDirectionMixin(SlottedItemsMixin(Va
}
}

[$filterItems]() {
protected _filterItems() {
return Array.from(this.querySelectorAll(VaadinAccordionPanel.is)) as VaadinAccordionPanel[];
}

[$focus](item: VaadinAccordionPanel) {
super[$focus](item);
protected _focus(item: VaadinAccordionPanel) {
super._focus && super._focus(item); // eslint-disable-line no-unused-expressions
item.setAttribute('focus-ring', '');
}

[$isNextKey](key: string) {
protected _isNextKey(key: string) {
return key === 'ArrowDown';
}

[$isPrevKey](key: string) {
protected _isPrevKey(key: string) {
return key === 'ArrowUp';
}

[$itemsChanged](panels: VaadinAccordionPanel[], oldPanels: VaadinAccordionPanel[]) {
super[$itemsChanged](panels, oldPanels);
protected _itemsChanged(panels: VaadinAccordionPanel[], oldPanels: VaadinAccordionPanel[]) {
super._itemsChanged && super._itemsChanged(panels, oldPanels); // eslint-disable-line no-unused-expressions

panels
.filter(panel => !oldPanels.includes(panel))
Expand All @@ -118,11 +118,11 @@ export class VaadinAccordion extends KeyboardDirectionMixin(SlottedItemsMixin(Va
});
}

[$onKeydown](event: KeyboardEvent) {
protected _onKeydown(event: KeyboardEvent) {
// only check keyboard events on summary, not on the content
const summary = event.composedPath()[0] as HTMLElement;
if (summary && summary.getAttribute('part') === 'summary') {
super[$onKeydown](event);
if (summary && summary.getAttribute('part') === 'summary' && super._onKeydown) {
super._onKeydown(event);
}
}

Expand Down