feat: allow use of any tag in listItems helper (#3147)

* feat: allow use of any tag in `listItems` helper

* fix: fix missing optional chaining

* chore: use more optional chaining

* fix: various typings errors

* chore: replace `Vnode[]` with `Children`
This commit is contained in:
David Wheatley 2021-11-08 23:52:47 +00:00 committed by GitHub
parent 029e34bfd7
commit 0db7f3be74

View File

@ -1,14 +1,15 @@
import type Mithril from 'mithril'; import type Mithril from 'mithril';
import Separator from '../components/Separator'; import Separator from '../components/Separator';
import classList from '../utils/classList'; import classList from '../utils/classList';
import type * as Component from '../Component';
function isSeparator(item): boolean { function isSeparator(item: Mithril.Children): boolean {
return item.tag === Separator; return item.tag === Separator;
} }
function withoutUnnecessarySeparators(items: Array<Mithril.Vnode>): Array<Mithril.Vnode> { function withoutUnnecessarySeparators(items: Mithril.Children): Mithril.Children {
const newItems = []; const newItems: Mithril.Children = [];
let prevItem; let prevItem: Mithril.Child;
items.filter(Boolean).forEach((item: Mithril.Vnode, i: number) => { items.filter(Boolean).forEach((item: Mithril.Vnode, i: number) => {
if (!isSeparator(item) || (prevItem && !isSeparator(prevItem) && i !== items.length - 1)) { if (!isSeparator(item) || (prevItem && !isSeparator(prevItem) && i !== items.length - 1)) {
@ -20,17 +21,36 @@ function withoutUnnecessarySeparators(items: Array<Mithril.Vnode>): Array<Mithri
return newItems; return newItems;
} }
export interface ModdedVnodeAttrs {
itemClassName?: string;
key?: string;
}
export type ModdedVnode<Attrs> = Mithril.Vnode<ModdedVnodeAttrs, Component.default<Attrs> | {}> & {
itemName?: string;
itemClassName?: string;
};
/** /**
* The `listItems` helper wraps a collection of components in <li> tags, * The `listItems` helper wraps an array of components in the provided tag,
* stripping out any unnecessary `Separator` components. * stripping out any unnecessary `Separator` components.
*
* By default, this tag is an `<li>` tag, but this is customisable through the
* second function parameter, `customTag`.
*/ */
export default function listItems(items: Mithril.Vnode | Array<Mithril.Vnode>): Array<Mithril.Vnode> { export default function listItems<Attrs extends Record<string, unknown>>(
items: ModdedVnode<Attrs> | ModdedVnode<Attrs>[],
customTag: string | Component.default<Attrs> = 'li',
attributes: Attrs = {}
): Mithril.Vnode[] {
if (!(items instanceof Array)) items = [items]; if (!(items instanceof Array)) items = [items];
return withoutUnnecessarySeparators(items).map((item: Mithril.Vnode) => { const Tag = customTag;
const isListItem = item.tag && item.tag.isListItem;
const active = item.tag && item.tag.isActive && item.tag.isActive(item.attrs); return withoutUnnecessarySeparators(items).map((item: ModdedVnode<Attrs>) => {
const className = (item.attrs && item.attrs.itemClassName) || item.itemClassName; const isListItem = item.tag?.isListItem;
const active = item.tag?.isActive?.(item.attrs);
const className = item.attrs?.itemClassName || item.itemClassName;
if (isListItem) { if (isListItem) {
item.attrs = item.attrs || {}; item.attrs = item.attrs || {};
@ -41,12 +61,14 @@ export default function listItems(items: Mithril.Vnode | Array<Mithril.Vnode>):
const node: Mithril.Vnode = isListItem ? ( const node: Mithril.Vnode = isListItem ? (
item item
) : ( ) : (
<li // @ts-expect-error `Component` does not have any construct or call signatures
<Tag
className={classList([className, item.itemName && `item-${item.itemName}`, active && 'active'])} className={classList([className, item.itemName && `item-${item.itemName}`, active && 'active'])}
key={(item.attrs && item.attrs.key) || item.itemName} key={item?.attrs?.key || item.itemName}
{...attributes}
> >
{item} {item}
</li> </Tag>
); );
return node; return node;