Skip to content

packages/core/src/lib/element/create-element.ts

Read as Markdown

This is the source snapshot used to build these API details. View this revision on GitHub.

Back to reference

1 import {
2   copyElementCloneMetadata,
3   getElementMetadata,
4   getPrimitiveDefinition,
5   setElementMetadata,
6   type PibblElementSource,
7 } from './metadata.js';
8 import type {
9   PibblElement,
10   PibblElementType,
11   PibblKey,
12 } from './types.js';
13 import { isDevelopment } from '../development.js';
14 import { capturePlacementHandoff } from '../style/positioning.js';
15 
16 const PIBBL_ELEMENT_TYPE = Symbol.for('@pibbl/core.element');
17 
18 type ElementProps = Record<PropertyKey, unknown>;
19 type UnionKeys<T> = T extends unknown ? keyof T : never;
20 type ElementInput<P> = 0 extends (1 & P) ?
21   P & { key?: PibblKey } :
22   'key' extends UnionKeys<P> ? never :
23   P & { key?: PibblKey };
24 type ElementInputOrNull<P> = [ElementInput<P>] extends [never] ?
25   never :
26   ElementInput<P> | null;
27 type ChildrenForProps<P> = P extends unknown ?
28   'children' extends keyof P ? P['children'] : never :
29   never;
30 type MultipleChildren<C> = C extends unknown ?
31   C extends readonly (infer Child)[] ?
32     Child[] extends C ? [Child, Child, ...Child[]] : never :
33   C extends Iterable<infer Child> ?
34     Child[] extends C ? [Child, Child, ...Child[]] : never :
35   never :
36   never;
37 type ChildrenArguments<P> = P extends unknown ?
38   'children' extends keyof P ?
39     | []
40     | [ChildrenForProps<P>]
41     | MultipleChildren<ChildrenForProps<P>> :
42   [] :
43   never;
44 
45 /**
46  * Public constructor used by direct element authoring.
47  *
48  * @param type - Pibbl component or fragment to instantiate. See {@link PibblElementType}.
49  * @param props - Authored props, or null when allowed by the component's input type. See
50  * {@link ElementInputOrNull} .
51  * @param children - Child nodes supplied in source order. See {@link ChildrenArguments}.
52  * @returns A branded Pibbl element descriptor; rendering occurs when it is mounted. See
53  * {@link PibblElement} .
54  *
55  * @see {@link PibblElementType}
56  * @see {@link PibblElement}
57  */
58 export function createElement<P>(
59   type: PibblElementType<P>,
60   props: ElementInputOrNull<P>,
61   ...children: ChildrenArguments<P>
62 ): PibblElement<P> {
63   const withChildren = children.length === 0 ? props : {
64     ...(props as object | null),
65     children: children.length === 1 ? children[0] : children,
66   };
67   return createPibblElement(type, withChildren as P | null, undefined, undefined);
68 }
69 
70 /** @internal Shared construction path for direct and compiler element creation. */
71 export function createPibblElement<P>(
72   type: PibblElementType<P>,
73   incomingProps: P | null,
74   compilerKey: PibblKey | null | undefined,
75   source: PibblElementSource | undefined,
76 ): PibblElement<P> {
77   const rawProps = (incomingProps ?? {}) as ElementProps;
78   const defensiveKey = Object.prototype.hasOwnProperty.call(rawProps, 'key') ?
79     rawProps.key :
80     undefined;
81   const rawKey = compilerKey === undefined ? defensiveKey : compilerKey;
82   const { key: _key, ...props } = rawProps;
83   const primitiveProgramProps = getPrimitiveDefinition(type) === undefined ?
84     undefined :
85     preparePrimitiveProgramProps(props);
86   const normalizedKey = rawKey === undefined || rawKey === null ?
87     undefined :
88     String(rawKey);
89   return finalizePibblElement(type, props as P, normalizedKey, {
90     source,
91     primitiveProgramProps,
92   });
93 }
94 
95 function preparePrimitiveProgramProps(
96   props: Readonly<ElementProps>,
97 ): Readonly<ElementProps> {
98   if (!Object.prototype.hasOwnProperty.call(props, 'style')) return props;
99   const { style: _style, ...programProps } = props;
100   return programProps;
101 }
102 
103 function finalizePibblElement<P>(
104   type: PibblElementType<P>,
105   props: P,
106   key: string | undefined,
107   metadata: Parameters<typeof setElementMetadata>[1],
108 ): PibblElement<P> {
109   const element = (key === undefined ?
110     { type, props } :
111     { type, props, key }) as PibblElement<P>;
112 
113   setElementMetadata(element, metadata);
114   Object.defineProperties(element, {
115     $$typeof: {
116       configurable: false,
117       enumerable: false,
118       value: PIBBL_ELEMENT_TYPE,
119       writable: false,
120     },
121   });
122 
123   capturePlacementHandoff(
124     element,
125     (props as { readonly style?: unknown }).style,
126   );
127   if (isDevelopment) {
128     Object.freeze(props);
129     Object.freeze(element);
130   }
131   return element;
132 }
133 
134 /** @internal */
135 export function isPibblElement(value: unknown): value is PibblElement<any> {
136   return !!value &&
137     typeof value === 'object' &&
138     (value as { $$typeof?: unknown }).$$typeof === PIBBL_ELEMENT_TYPE;
139 }
140 
141 /** @internal Creates another immutable descriptor while retaining private annotations. */
142 export function clonePibblElement<P>(element: PibblElement<P>): PibblElement<P> {
143   const metadata = getElementMetadata(element);
144   const clone = finalizePibblElement(
145     element.type,
146     element.props,
147     element.key,
148     metadata ? { ...metadata } : {},
149   );
150   copyElementCloneMetadata(element, clone);
151   return clone;
152 }
153 

Documentation built with @pibbl/core 0.0.2, revision 2dccb19. ALPHA — NOT FOR PRODUCTION USE.