|
| 1 | +/* eslint-disable prefer-rest-params */ |
| 2 | +import { |
| 3 | + createTextNode, |
| 4 | + type Block, |
| 5 | + type Component, |
| 6 | + type ComponentOptions, |
| 7 | + type ConcreteComponent, |
| 8 | + type DefineComponent, |
| 9 | + type EmitsOptions, |
| 10 | + type Fragment, |
| 11 | + type FunctionalComponent, |
| 12 | + type RawSlots, |
| 13 | + type Suspense, |
| 14 | + type SuspenseProps, |
| 15 | + type Teleport, |
| 16 | + type TeleportProps, |
| 17 | + type VNodeRef, |
| 18 | +} from 'vue' |
| 19 | +import { createComponentWithFallback, isBlock } from './runtime' |
| 20 | + |
| 21 | +type NodeChildAtom = Block | string | number | boolean | null | undefined | void |
| 22 | + |
| 23 | +type NodeArrayChildren = Array<NodeArrayChildren | NodeChildAtom> |
| 24 | + |
| 25 | +type RawChildren = NodeChildAtom | NodeArrayChildren |
| 26 | + |
| 27 | +function normalizeNode(node: RawChildren): Block { |
| 28 | + if (node == null || typeof node === 'boolean') { |
| 29 | + return [] |
| 30 | + } else if (Array.isArray(node) && node.length) { |
| 31 | + return node.map(normalizeNode) |
| 32 | + } else if (isBlock(node)) { |
| 33 | + return node |
| 34 | + } else { |
| 35 | + // strings and numbers |
| 36 | + return createTextNode(String(node)) |
| 37 | + } |
| 38 | +} |
| 39 | + |
| 40 | +// fake constructor type returned from `defineComponent` |
| 41 | +interface Constructor<P = any> { |
| 42 | + __isFragment?: never |
| 43 | + __isTeleport?: never |
| 44 | + __isSuspense?: never |
| 45 | + new (...args: any[]): { $props: P } |
| 46 | +} |
| 47 | + |
| 48 | +type HTMLElementEventHandler = { |
| 49 | + [K in keyof HTMLElementEventMap as `on${Capitalize<K>}`]?: ( |
| 50 | + ev: HTMLElementEventMap[K], |
| 51 | + ) => any |
| 52 | +} |
| 53 | + |
| 54 | +type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N |
| 55 | + |
| 56 | +type RawProps = Record<string, any> |
| 57 | + |
| 58 | +// The following is a series of overloads for providing props validation of |
| 59 | +// manually written render functions. |
| 60 | + |
| 61 | +// element |
| 62 | +export function h<K extends keyof HTMLElementTagNameMap>( |
| 63 | + type: K, |
| 64 | + children?: RawChildren, |
| 65 | +): Block |
| 66 | +export function h<K extends keyof HTMLElementTagNameMap>( |
| 67 | + type: K, |
| 68 | + props?: (RawProps & HTMLElementEventHandler) | null, |
| 69 | + children?: RawChildren | RawSlots, |
| 70 | +): Block |
| 71 | + |
| 72 | +// custom element |
| 73 | +export function h(type: string, children?: RawChildren): Block |
| 74 | +export function h( |
| 75 | + type: string, |
| 76 | + props?: RawProps | null, |
| 77 | + children?: RawChildren | RawSlots, |
| 78 | +): Block |
| 79 | + |
| 80 | +// text/comment |
| 81 | +export function h( |
| 82 | + type: typeof Text | typeof Comment, |
| 83 | + children?: string | number | boolean, |
| 84 | +): Block |
| 85 | +export function h( |
| 86 | + type: typeof Text | typeof Comment, |
| 87 | + props?: null, |
| 88 | + children?: string | number | boolean, |
| 89 | +): Block |
| 90 | + |
| 91 | +// fragment |
| 92 | +export function h(type: typeof Fragment, children?: NodeArrayChildren): Block |
| 93 | +export function h( |
| 94 | + type: typeof Fragment, |
| 95 | + props?: { key?: PropertyKey; ref?: VNodeRef } | null, |
| 96 | + children?: NodeArrayChildren, |
| 97 | +): Block |
| 98 | + |
| 99 | +// teleport (target prop is required) |
| 100 | +export function h( |
| 101 | + type: typeof Teleport, |
| 102 | + props: RawProps & TeleportProps, |
| 103 | + children: RawChildren | RawSlots, |
| 104 | +): Block |
| 105 | + |
| 106 | +// suspense |
| 107 | +export function h(type: typeof Suspense, children?: RawChildren): Block |
| 108 | +export function h( |
| 109 | + type: typeof Suspense, |
| 110 | + props?: (RawProps & SuspenseProps) | null, |
| 111 | + children?: RawChildren | RawSlots, |
| 112 | +): Block |
| 113 | + |
| 114 | +// functional component |
| 115 | +export function h(type: FunctionalComponent, children?: RawChildren): Block |
| 116 | +export function h< |
| 117 | + P, |
| 118 | + E extends EmitsOptions = {}, |
| 119 | + S extends Record<string, any> = any, |
| 120 | +>( |
| 121 | + type: FunctionalComponent<P, E, S>, |
| 122 | + props?: (RawProps & P) | ({} extends P ? null : never), |
| 123 | + children?: RawChildren | IfAny<S, RawSlots, S>, |
| 124 | +): Block |
| 125 | + |
| 126 | +// catch all types |
| 127 | +export function h( |
| 128 | + type: |
| 129 | + | string |
| 130 | + | ConcreteComponent |
| 131 | + | Component |
| 132 | + | ComponentOptions |
| 133 | + | Constructor |
| 134 | + | DefineComponent, |
| 135 | + children?: RawChildren, |
| 136 | +): Block |
| 137 | +export function h<P>( |
| 138 | + type: |
| 139 | + | string |
| 140 | + | ConcreteComponent<P> |
| 141 | + | Component<P> |
| 142 | + | ComponentOptions<P> |
| 143 | + | Constructor<P> |
| 144 | + | DefineComponent<P>, |
| 145 | + props?: (RawProps & P) | ({} extends P ? null : never), |
| 146 | + children?: RawChildren | RawSlots, |
| 147 | +): Block |
| 148 | + |
| 149 | +export function h(type: any, propsOrChildren?: any, children?: any) { |
| 150 | + const l = arguments.length |
| 151 | + if (l === 2) { |
| 152 | + if ( |
| 153 | + typeof propsOrChildren === 'object' && |
| 154 | + !Array.isArray(propsOrChildren) |
| 155 | + ) { |
| 156 | + // single block without props |
| 157 | + if (isBlock(propsOrChildren)) { |
| 158 | + return createComponentWithFallback(type, null, { |
| 159 | + default: () => normalizeNode(propsOrChildren), |
| 160 | + }) |
| 161 | + } |
| 162 | + |
| 163 | + // props without children |
| 164 | + return createComponentWithFallback( |
| 165 | + type, |
| 166 | + propsOrChildren ? { $: [() => propsOrChildren] } : null, |
| 167 | + ) |
| 168 | + } else { |
| 169 | + // omit props |
| 170 | + return createComponentWithFallback(type, null, { |
| 171 | + default: () => normalizeNode(propsOrChildren), |
| 172 | + }) |
| 173 | + } |
| 174 | + } else { |
| 175 | + if (l > 3) { |
| 176 | + children = Array.prototype.slice.call(arguments, 2) |
| 177 | + } |
| 178 | + return createComponentWithFallback( |
| 179 | + type, |
| 180 | + propsOrChildren ? { $: [() => propsOrChildren] } : null, |
| 181 | + children |
| 182 | + ? typeof children === 'object' && !Array.isArray(children) |
| 183 | + ? children |
| 184 | + : { |
| 185 | + default: () => normalizeNode(children), |
| 186 | + } |
| 187 | + : undefined, |
| 188 | + ) |
| 189 | + } |
| 190 | +} |
0 commit comments