From 580be37eb45f57ba1b5446824c3a53f56e32eac7 Mon Sep 17 00:00:00 2001 From: Alexander Skvortsov <38059171+askvortsov1@users.noreply.github.com> Date: Sun, 12 Dec 2021 14:51:05 -0500 Subject: [PATCH] `listItems` typing fix (#3176) Co-authored-by: David Wheatley --- js/src/common/helpers/listItems.tsx | 87 +++++++++++++++++------------ 1 file changed, 52 insertions(+), 35 deletions(-) diff --git a/js/src/common/helpers/listItems.tsx b/js/src/common/helpers/listItems.tsx index 20a2b985b..e4b39d225 100644 --- a/js/src/common/helpers/listItems.tsx +++ b/js/src/common/helpers/listItems.tsx @@ -3,29 +3,41 @@ import Component, { ComponentAttrs } from '../Component'; import Separator from '../components/Separator'; import classList from '../utils/classList'; -export interface ModdedVnodeAttrs { +type ModdedVnodeAttrs = { itemClassName?: string; key?: string; -} - -export type ModdedVnode = Mithril.Vnode | {}> & { - itemName?: string; - itemClassName?: string; - tag: Mithril.Vnode['tag'] & { - isListItem?: boolean; - isActive?: (attrs: ComponentAttrs) => boolean; - }; }; -function isSeparator(item: ModdedVnode): boolean { - return item.tag === Separator; +type ModdedTag = Mithril.Vnode['tag'] & { + isListItem?: boolean; + isActive?: (attrs: ComponentAttrs) => boolean; +}; + +type ModdedVnode = Mithril.Vnode & { 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(items: ModdedVnode[]): ModdedVnode[] { - const newItems: ModdedVnode[] = []; - let prevItem: ModdedVnode; +function isSeparator(item: ModdedChildren): boolean { + return isVnode(item) && item.tag === Separator; +} - 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)) { prevItem = item; newItems.push(item); @@ -42,38 +54,43 @@ function withoutUnnecessarySeparators(items: ModdedVnode[]): Modde * By default, this tag is an `
  • ` tag, but this is customisable through the * second function parameter, `customTag`. */ -export default function listItems>( - rawItems: ModdedVnode | ModdedVnode[], - customTag: string | Component = 'li', +export default function listItems( + rawItems: ModdedChildrenWithItemName[], + customTag: VnodeElementTag = 'li', attributes: Attrs = {} as Attrs ): Mithril.Vnode[] { const items = rawItems instanceof Array ? rawItems : [rawItems]; const Tag = customTag; - return withoutUnnecessarySeparators(items).map((item: ModdedVnode) => { - const isListItem = item.tag?.isListItem; - const active = item.tag?.isActive?.(item.attrs); - const className = item.attrs?.itemClassName || item.itemClassName; + return withoutUnnecessarySeparators(items).map((item) => { + const classes = [item.itemName && `item-${item.itemName}`]; - if (isListItem) { + if (!isVnode(item)) { + return ( + + {item} + + ); + } + + if (item.tag.isListItem) { item.attrs = item.attrs || {}; item.attrs.key = item.attrs.key || item.itemName; item.key = item.attrs.key; + + return item; } - const node: Mithril.Vnode = isListItem ? ( - item - ) : ( - // @ts-expect-error `Component` does not have any construct or call signatures - + classes.push(item.attrs?.itemClassName || item.itemClassName); + + if (item.tag.isActive?.(item.attrs)) { + classes.push('active'); + } + + return ( + {item} ); - - return node; }); }