jiro-ui
Version:
A Mithril.js UI library based from construct-ui
141 lines (117 loc) • 3.51 kB
text/typescript
import m from 'mithril';
import classnames from 'classnames';
import { Classes, safeCall } from '../../_shared';
import { Popover, IPopoverAttrs } from '../popover';
import { AbstractComponent } from '../abstract-component';
import { Spinner } from '../spinner';
import { QueryList, IQueryListAttrs } from '../query-list';
export interface ISelectListAttrs<T> extends IQueryListAttrs<T> {
/**
* Closes popover on item select
* @default true
*/
closeOnSelect?: boolean;
/**
* Attrs passed through to Popover component
* @default {}
*/
popoverAttrs?: Partial<IPopoverAttrs>;
/** Trigger element */
trigger: m.Vnode<any, any>;
/** Header content */
header?: m.Children;
/** Footer content */
footer?: m.Children;
/** Toggles loading state of inner list */
loading?: boolean;
}
export class SelectList<T> extends AbstractComponent<ISelectListAttrs<T>> {
public queryList = QueryList.ofType<T>();
private isOpen: boolean;
private activeIndex: number = 0;
public static ofType<T>() {
return SelectList as new () => SelectList<T>;
}
public getDefaultAttrs() {
return {
closeOnSelect: true,
popoverAttrs: {},
inputAttrs: {}
} as ISelectListAttrs<T>;
}
public oninit(vnode: m.Vnode<ISelectListAttrs<T>>) {
super.oninit(vnode);
const { isOpen, defaultIsOpen } = vnode.attrs.popoverAttrs!;
this.isOpen = isOpen != null ? isOpen : defaultIsOpen != null ? defaultIsOpen : false;
}
public onbeforeupdate(vnode: m.Vnode<ISelectListAttrs<T>>, old: m.VnodeDOM<ISelectListAttrs<T>>) {
super.onbeforeupdate(vnode, old);
const isOpen = vnode.attrs.popoverAttrs!.isOpen;
const wasOpen = old.attrs.popoverAttrs!.isOpen;
if (isOpen && !wasOpen) {
this.isOpen = true;
} else if (!isOpen && wasOpen) {
this.isOpen = false;
}
}
public view() {
const {
class: className,
popoverAttrs,
header,
footer,
trigger,
closeOnSelect,
loading,
...queryListAttrs
} = this.attrs;
const queryList = m(this.queryList, {
activeIndex: this.activeIndex,
onActiveItemChange: this.handleActiveItemChange,
...queryListAttrs,
inputAttrs: {
...queryListAttrs.inputAttrs,
autofocus: true
},
onSelect: this.handleSelect
});
const content = [
header,
m(Spinner, {
active: loading,
background: true,
fill: true
}),
queryList,
footer
];
return m(Popover, {
autofocus: true,
position: 'bottom-start',
closeOnEscapeKey: false,
...popoverAttrs,
class: classnames(Classes.SELECT_LIST, className, popoverAttrs!.class),
isOpen: this.isOpen,
content,
onInteraction: this.handlePopoverInteraction,
trigger
});
}
private handleActiveItemChange = (activeItem: T, index: number) => {
this.activeIndex = index;
safeCall(this.attrs.onActiveItemChange, activeItem, index);
}
private handleSelect = (item: T, e: Event, index: number) => {
const { onSelect, closeOnSelect } = this.attrs;
if (closeOnSelect) {
this.isOpen = false;
}
safeCall(onSelect, item, e, index);
}
private handlePopoverInteraction = (nextOpenState: boolean, e: Event) => {
const { isOpen, onInteraction } = this.attrs.popoverAttrs!;
if (isOpen != null) {
safeCall(onInteraction, nextOpenState, e);
} else this.isOpen = nextOpenState;
}
}