listItems typing fix (#3176)

Co-authored-by: David Wheatley <hi@davwheat.dev>
This commit is contained in:
Alexander Skvortsov 2021-12-12 14:51:05 -05:00 committed by GitHub
parent 0e00196d8e
commit 580be37eb4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -3,29 +3,41 @@ import Component, { ComponentAttrs } from '../Component';
import Separator from '../components/Separator'; import Separator from '../components/Separator';
import classList from '../utils/classList'; import classList from '../utils/classList';
export interface ModdedVnodeAttrs { type ModdedVnodeAttrs = {
itemClassName?: string; itemClassName?: string;
key?: string; key?: string;
}
export type ModdedVnode<Attrs> = Mithril.Vnode<ModdedVnodeAttrs, Component<Attrs> | {}> & {
itemName?: string;
itemClassName?: string;
tag: Mithril.Vnode['tag'] & {
isListItem?: boolean;
isActive?: (attrs: ComponentAttrs) => boolean;
};
}; };
function isSeparator<Attrs>(item: ModdedVnode<Attrs>): boolean { type ModdedTag = Mithril.Vnode['tag'] & {
return item.tag === Separator; isListItem?: boolean;
isActive?: (attrs: ComponentAttrs) => boolean;
};
type ModdedVnode = Mithril.Vnode<ModdedVnodeAttrs> & { itemName?: string; itemClassName?: string; tag: ModdedTag };
type ModdedChild = ModdedVnode | string | number | boolean | null | undefined;
type ModdedChildArray = ModdedChildren[];
type ModdedChildren = ModdedChild | ModdedChildArray;
/**
* This type represents an element of a list returned by `ItemList.toArray()`,
* coupled with some static properties used on various components.
*/
export type ModdedChildrenWithItemName = ModdedChildren & { itemName?: string };
function isVnode(item: ModdedChildren): item is Mithril.Vnode {
return typeof item === 'object' && item !== null && 'tag' in item;
} }
function withoutUnnecessarySeparators<Attrs>(items: ModdedVnode<Attrs>[]): ModdedVnode<Attrs>[] { function isSeparator(item: ModdedChildren): boolean {
const newItems: ModdedVnode<Attrs>[] = []; return isVnode(item) && item.tag === Separator;
let prevItem: ModdedVnode<Attrs>; }
items.filter(Boolean).forEach((item: Mithril.Vnode, i: number) => { function withoutUnnecessarySeparators(items: ModdedChildrenWithItemName[]): ModdedChildrenWithItemName[] {
const newItems: ModdedChildrenWithItemName[] = [];
let prevItem: ModdedChildren;
items.filter(Boolean).forEach((item, i: number) => {
if (!isSeparator(item) || (prevItem && !isSeparator(prevItem) && i !== items.length - 1)) { if (!isSeparator(item) || (prevItem && !isSeparator(prevItem) && i !== items.length - 1)) {
prevItem = item; prevItem = item;
newItems.push(item); newItems.push(item);
@ -42,38 +54,43 @@ function withoutUnnecessarySeparators<Attrs>(items: ModdedVnode<Attrs>[]): Modde
* By default, this tag is an `<li>` tag, but this is customisable through the * By default, this tag is an `<li>` tag, but this is customisable through the
* second function parameter, `customTag`. * second function parameter, `customTag`.
*/ */
export default function listItems<Attrs extends Record<string, unknown>>( export default function listItems<Attrs extends ComponentAttrs>(
rawItems: ModdedVnode<Attrs> | ModdedVnode<Attrs>[], rawItems: ModdedChildrenWithItemName[],
customTag: string | Component<Attrs> = 'li', customTag: VnodeElementTag<Attrs> = 'li',
attributes: Attrs = {} as Attrs attributes: Attrs = {} as Attrs
): Mithril.Vnode[] { ): Mithril.Vnode[] {
const items = rawItems instanceof Array ? rawItems : [rawItems]; const items = rawItems instanceof Array ? rawItems : [rawItems];
const Tag = customTag; const Tag = customTag;
return withoutUnnecessarySeparators(items).map((item: ModdedVnode<Attrs>) => { return withoutUnnecessarySeparators(items).map((item) => {
const isListItem = item.tag?.isListItem; const classes = [item.itemName && `item-${item.itemName}`];
const active = item.tag?.isActive?.(item.attrs);
const className = item.attrs?.itemClassName || item.itemClassName;
if (isListItem) { if (!isVnode(item)) {
item.attrs = item.attrs || {}; return (
item.attrs.key = item.attrs.key || item.itemName; <Tag className={classList(classes)} {...attributes}>
item.key = item.attrs.key;
}
const node: Mithril.Vnode = isListItem ? (
item
) : (
// @ts-expect-error `Component` does not have any construct or call signatures
<Tag
className={classList([className, item.itemName && `item-${item.itemName}`, active && 'active'])}
key={item?.attrs?.key || item.itemName}
{...attributes}
>
{item} {item}
</Tag> </Tag>
); );
}
return node; if (item.tag.isListItem) {
item.attrs = item.attrs || {};
item.attrs.key = item.attrs.key || item.itemName;
item.key = item.attrs.key;
return item;
}
classes.push(item.attrs?.itemClassName || item.itemClassName);
if (item.tag.isActive?.(item.attrs)) {
classes.push('active');
}
return (
<Tag className={classList(classes)} key={item?.attrs?.key || item.itemName} {...attributes}>
{item}
</Tag>
);
}); });
} }