diff options
Diffstat (limited to 'src/vs/base/browser/dom.ts')
-rw-r--r-- | src/vs/base/browser/dom.ts | 145 |
1 files changed, 86 insertions, 59 deletions
diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 4d08566a4f6..4019ea90b61 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -1734,50 +1734,87 @@ export function computeClippingRect(elementOrRect: HTMLElement | DOMRectReadOnly return { top, right, bottom, left }; } -interface DomNodeAttributes { - role?: string; - ariaHidden?: boolean; - style?: StyleAttributes; -} +type HTMLElementAttributeKeys<T> = Partial<{ [K in keyof T]: T[K] extends Function ? never : T[K] extends object ? HTMLElementAttributeKeys<T[K]> : T[K] }>; +type ElementAttributes<T> = HTMLElementAttributeKeys<T> & Record<string, any>; +type RemoveHTMLElement<T> = T extends HTMLElement ? never : T; +type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; +type ArrayToObj<T extends readonly any[]> = UnionToIntersection<RemoveHTMLElement<T[number]>>; +type HHTMLElementTagNameMap = HTMLElementTagNameMap & { '': HTMLDivElement }; -interface StyleAttributes { - height?: number | string; - width?: number | string; -} +type TagToElement<T> = T extends `${infer TStart}#${string}` + ? TStart extends keyof HHTMLElementTagNameMap + ? HHTMLElementTagNameMap[TStart] + : HTMLElement + : T extends `${infer TStart}.${string}` + ? TStart extends keyof HHTMLElementTagNameMap + ? HHTMLElementTagNameMap[TStart] + : HTMLElement + : T extends keyof HTMLElementTagNameMap + ? HTMLElementTagNameMap[T] + : HTMLElement; -//<div role="presentation" aria-hidden="true" class="scroll-decoration"></div> +type TagToElementAndId<TTag> = TTag extends `${infer TTag}@${infer TId}` + ? { element: TagToElement<TTag>; id: TId } + : { element: TagToElement<TTag>; id: 'root' }; + +type TagToRecord<TTag> = TagToElementAndId<TTag> extends { element: infer TElement; id: infer TId } + ? Record<(TId extends string ? TId : never) | 'root', TElement> + : never; + +type Child = HTMLElement | string | Record<string, HTMLElement>; +type Children = [] + | [Child] + | [Child, Child] + | [Child, Child, Child] + | [Child, Child, Child, Child] + | [Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child]; + +const H_REGEX = /(?<tag>[\w\-]+)?(?:#(?<id>[\w\-]+))?(?<class>(?:\.(?:[\w\-]+))*)(?:@(?<name>(?:[\w\_])+))?/; /** * A helper function to create nested dom nodes. * * * ```ts - * private readonly htmlElements = h('div.code-view', [ - * h('div.title', { $: 'title' }), + * const elements = h('div.code-view', [ + * h('div.title@title'), * h('div.container', [ - * h('div.gutter', { $: 'gutterDiv' }), - * h('div', { $: 'editor' }), + * h('div.gutter@gutterDiv'), + * h('div@editor'), * ]), * ]); - * private readonly editor = createEditor(this.htmlElements.editor); + * const editor = createEditor(elements.editor); * ``` */ -export function h<TTag extends string, TId extends string>( - tag: TTag, - attributes: { $: TId } & DomNodeAttributes -): Record<TId | 'root', TagToElement<TTag>>; -export function h<TTag extends string>(tag: TTag, attributes: DomNodeAttributes): Record<'root', TagToElement<TTag>>; -export function h<TTag extends string, T extends (HTMLElement | string | Record<string, HTMLElement>)[]>( - tag: TTag, - children: T -): (ArrayToObj<T> & Record<'root', TagToElement<TTag>>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; -export function h<TTag extends string, TId extends string, T extends (HTMLElement | string | Record<string, HTMLElement>)[]>( - tag: TTag, - attributes: { $: TId } & DomNodeAttributes, - children: T -): (ArrayToObj<T> & Record<TId, TagToElement<TTag>>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; -export function h(tag: string, ...args: [] | [attributes: { $: string } & DomNodeAttributes | Record<string, any>, children?: any[]] | [children: any[]]): Record<string, HTMLElement> { - let attributes: { $?: string } & DomNodeAttributes; +export function h<TTag extends string> + (tag: TTag): + TagToRecord<TTag> extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; + +export function h<TTag extends string, T extends Children> + (tag: TTag, children: T): + (ArrayToObj<T> & TagToRecord<TTag>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; + +export function h<TTag extends string> + (tag: TTag, attributes: Partial<ElementAttributes<TagToElement<TTag>>>): + TagToRecord<TTag> extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; + +export function h<TTag extends string, T extends Children> + (tag: TTag, attributes: Partial<ElementAttributes<TagToElement<TTag>>>, children: T): + (ArrayToObj<T> & TagToRecord<TTag>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; + +export function h(tag: string, ...args: [] | [attributes: { $: string } & Partial<ElementAttributes<HTMLElement>> | Record<string, any>, children?: any[]] | [children: any[]]): Record<string, HTMLElement> { + let attributes: { $?: string } & Partial<ElementAttributes<HTMLElement>>; let children: (Record<string, HTMLElement> | HTMLElement)[] | undefined; if (Array.isArray(args[0])) { @@ -1788,14 +1825,29 @@ export function h(tag: string, ...args: [] | [attributes: { $: string } & DomNod children = args[1]; } - const [tagName, className] = tag.split('.'); + const match = H_REGEX.exec(tag); + + if (!match || !match.groups) { + throw new Error('Bad use of h'); + } + + const tagName = match.groups['tag'] || 'div'; const el = document.createElement(tagName); - if (className) { - el.className = className; + + if (match.groups['id']) { + el.id = match.groups['id']; + } + + if (match.groups['class']) { + el.className = match.groups['class'].replace(/\./g, ' ').trim(); } const result: Record<string, HTMLElement> = {}; + if (match.groups['name']) { + result[match.groups['name']] = el; + } + if (children) { for (const c of children) { if (c instanceof HTMLElement) { @@ -1810,10 +1862,6 @@ export function h(tag: string, ...args: [] | [attributes: { $: string } & DomNod } for (const [key, value] of Object.entries(attributes)) { - if (key === '$') { - result[value] = el; - continue; - } if (key === 'style') { for (const [cssKey, cssValue] of Object.entries(value)) { el.style.setProperty( @@ -1834,24 +1882,3 @@ export function h(tag: string, ...args: [] | [attributes: { $: string } & DomNod function camelCaseToHyphenCase(str: string) { return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); } - -type RemoveHTMLElement<T> = T extends HTMLElement ? never : T; - -type ArrayToObj<T extends any[]> = UnionToIntersection<RemoveHTMLElement<T[number]>>; - - -type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; - -type HTMLElementsByTagName = { - div: HTMLDivElement; - span: HTMLSpanElement; - a: HTMLAnchorElement; -}; - -type TagToElement<T> = T extends `${infer TStart}.${string}` - ? TStart extends keyof HTMLElementsByTagName - ? HTMLElementsByTagName[TStart] - : HTMLElement - : T extends keyof HTMLElementsByTagName - ? HTMLElementsByTagName[T] - : HTMLElement; |