packages/core/src/lib/define-primitive.ts
This is the source snapshot used to build these API details. View this revision on GitHub.
1 import {
2 derivePrimitiveMetadata,
3 getPrimitiveDefinition,
4 setPrimitiveDefinition,
5 } from './element/metadata.js';
6 import {
7 CANVAS_2D_BACKEND,
8 } from './backend/canvas-2d-backend.js';
9 import type {
10 PibblBackendPrimitiveProgram,
11 PibblBackendToken,
12 } from './backend/types.js';
13 import type {
14 PibblPrimitiveComponent,
15 PreservedSignalPropKey,
16 PrimitiveCapabilities,
17 PrimitiveInput,
18 PrimitiveProgramProps,
19 PrimitiveRender,
20 RenderingContext2D,
21 SystemStyle,
22 } from './types.js';
23 import { GLOBAL_STATE } from './global-state.js';
24 import { withSignalWriteForbidden } from './signals/graph.js';
25
26 type DeclaresWhen<S> = S extends unknown ? 'when' extends keyof S ? true : false : never;
27 type PrimitiveStyleGuard<S, N, R> = true extends DeclaresWhen<S> | DeclaresWhen<N> | DeclaresWhen<R> ? {
28 readonly 'Pibbl: style.when is framework-owned; remove when from the primitive style type': never;
29 } : unknown;
30
31 type AllocationValue = `${number}%` | 'auto';
32 type DeclaredStyleValue<S> = S extends unknown
33 ? S[Exclude<keyof S, 'custom'>]
34 : never;
35 type RequiresResolver<S> = [
36 Extract<DeclaredStyleValue<S>, AllocationValue>,
37 ] extends [never] ? false : true;
38 type PrimitiveCapabilitiesInput<
39 P,
40 S extends SystemStyle,
41 N extends SystemStyle,
42 R extends SystemStyle,
43 > = Readonly<PrimitiveCapabilities<P, S, N, R>> & PrimitiveStyleGuard<S, N, R> & (
44 [PreservedSignalPropKey<P>] extends [never] ? object : {
45 readonly preserveSignalProps: readonly [
46 PreservedSignalPropKey<P>,
47 ...PreservedSignalPropKey<P>[],
48 ];
49 }
50 );
51 type CapabilityArguments<
52 P,
53 S extends SystemStyle,
54 N extends SystemStyle,
55 R extends SystemStyle,
56 > = RequiresResolver<R> extends true ? [capabilities: never] :
57 RequiresResolver<S> extends true ? [
58 capabilities: PrimitiveCapabilitiesInput<P, S, N, R> & {
59 resolveStyle: NonNullable<
60 PrimitiveCapabilities<P, S, N, R>['resolveStyle']
61 >;
62 },
63 ] : [PreservedSignalPropKey<P>] extends [never] ? [
64 capabilities?: PrimitiveCapabilitiesInput<P, S, N, R>,
65 ] : [capabilities: PrimitiveCapabilitiesInput<P, S, N, R>];
66
67 /**
68 * Defines a platform component with private style and measurement behavior.
69 *
70 * @param render - Synchronous primitive renderer receiving props, resolved style, and Canvas
71 * context. See {@link PrimitiveRender} , {@link PrimitiveStyleGuard} , {@link PrimitiveInput} .
72 * @param capabilityArguments - Style normalization, resolution, and measurement capabilities
73 * required by the primitive. See {@link CapabilityArguments} .
74 * @returns A primitive component usable with JSX or createElement. See
75 * {@link PibblPrimitiveComponent} , {@link PrimitiveProgramProps} , {@link PrimitiveInput} .
76 *
77 * @see {@link PrimitiveRender}
78 * @see {@link PrimitiveInput}
79 * @see {@link PibblPrimitiveComponent}
80 * @see {@link PrimitiveProgramProps}
81 * @see {@link SystemStyle}
82 */
83 export function definePrimitive<
84 P = Record<string, never>,
85 S extends SystemStyle = SystemStyle,
86 N extends SystemStyle = S,
87 R extends SystemStyle = N,
88 >(
89 render: PrimitiveRender<P, R> & PrimitiveStyleGuard<S, N, R> & (
90 PrimitiveInput<P, S> extends never ? never : unknown
91 ),
92 ...capabilityArguments: CapabilityArguments<P, S, N, R>
93 ): PibblPrimitiveComponent<PrimitiveProgramProps<P>, PrimitiveInput<P, S>> {
94 return defineBackendPrimitive<P, S, N, R, RenderingContext2D>(
95 CANVAS_2D_BACKEND.token,
96 render,
97 ...capabilityArguments,
98 );
99 }
100
101 /**
102 * Defines a primitive owned by one official Pibbl rendering backend.
103 *
104 * @param backend - Identity of the backend that can execute this primitive. See
105 * {@link PibblBackendToken} .
106 * @param program - Synchronous program using the active backend environment. See
107 * {@link PibblBackendPrimitiveProgram} , {@link PrimitiveStyleGuard} , {@link PrimitiveInput} .
108 * @param capabilityArguments - Style and measurement capabilities for the primitive. See
109 * {@link CapabilityArguments} .
110 * @returns A primitive component bound to the specified backend. See {@link PibblPrimitiveComponent}
111 * , {@link PrimitiveProgramProps} , {@link PrimitiveInput} .
112 *
113 * @see {@link PibblBackendToken}
114 * @see {@link PibblBackendPrimitiveProgram}
115 * @see {@link PrimitiveInput}
116 * @see {@link PibblPrimitiveComponent}
117 * @see {@link PrimitiveProgramProps}
118 * @see {@link SystemStyle}
119 */
120 export function defineBackendPrimitive<
121 P = Record<string, never>,
122 S extends SystemStyle = SystemStyle,
123 N extends SystemStyle = S,
124 R extends SystemStyle = N,
125 E = unknown,
126 >(
127 backend: PibblBackendToken,
128 program: PibblBackendPrimitiveProgram<P, R, E> & PrimitiveStyleGuard<S, N, R> & (
129 PrimitiveInput<P, S> extends never ? never : unknown
130 ),
131 ...capabilityArguments: CapabilityArguments<P, S, N, R>
132 ): PibblPrimitiveComponent<PrimitiveProgramProps<P>, PrimitiveInput<P, S>> {
133 const componentName = primitiveComponentName(program.name);
134 const component = function PibblPrimitiveComponent(): never {
135 throw new Error(
136 `${componentName} is a Pibbl component and was invoked outside the Pibbl ` +
137 'renderer.\n' +
138 `Use <${componentName} ... /> in a Pibbl JSX file or ` +
139 `createElement(${componentName}, props).`,
140 );
141 } as unknown as PibblPrimitiveComponent<
142 PrimitiveProgramProps<P>,
143 PrimitiveInput<P, S>
144 >;
145 Object.defineProperty(component, 'name', {
146 configurable: true,
147 value: componentName,
148 });
149
150 const capabilities = (capabilityArguments[0] ?? {}) as Readonly<
151 PrimitiveCapabilities<P, S, N, R>
152 >;
153 setPrimitiveDefinition(component, {
154 backend,
155 program,
156 preserveSignalProps: capabilities.preserveSignalProps,
157 childInput: capabilities.childInput,
158 normalizeStyle: capabilities.normalizeStyle,
159 resolveStyle: capabilities.resolveStyle,
160 measure: capabilities.measure,
161 });
162 return component;
163 }
164
165 /**
166 * Derives a new official-package primitive identity with the source behavior.
167 *
168 * @param source - Primitive whose rendering and capabilities are reused.
169 * @returns A derived primitive with its own component identity.
170 *
171 * @see {@link PibblPrimitiveComponent}
172 */
173 export function derivePrimitive<T extends PibblPrimitiveComponent<any, any>>(
174 source: T,
175 ): T {
176 if (!getPrimitiveDefinition(source)) {
177 throw new TypeError('Can only derive a Pibbl primitive component.');
178 }
179 const componentName = source.name || 'Anonymous';
180 const derived = function PibblPrimitiveComponent(): never {
181 throw new Error(
182 `${componentName} is a Pibbl component and was invoked outside the Pibbl ` +
183 'renderer.\n' +
184 `Use <${componentName} ... /> in a Pibbl JSX file or ` +
185 `createElement(${componentName}, props).`,
186 );
187 } as unknown as T;
188 Object.defineProperty(derived, 'name', {
189 configurable: true,
190 value: componentName,
191 });
192 derivePrimitiveMetadata(source, derived);
193 return derived;
194 }
195
196 /**
197 * Runs one official-package primitive resolver without render side-effect authority.
198 *
199 * @param callback - Synchronous resolver to evaluate with hooks and signal writes forbidden.
200 * @returns The resolver's return value.
201 *
202 * @see {@link PrimitiveCapabilities}
203 */
204 export function pibblInternalRunPurePrimitiveResolver<T>(callback: () => T): T {
205 const previousComponentRefs = GLOBAL_STATE.componentRefs;
206 const previousComponentHookIndex = GLOBAL_STATE.componentHookIndex;
207 const previousComponentIsMounting = GLOBAL_STATE.componentIsMounting;
208 GLOBAL_STATE.componentRefs = undefined;
209 GLOBAL_STATE.componentHookIndex = 0;
210 GLOBAL_STATE.componentIsMounting = false;
211 try {
212 return withSignalWriteForbidden('Pibbl pure primitive resolver', callback);
213 } finally {
214 GLOBAL_STATE.componentRefs = previousComponentRefs;
215 GLOBAL_STATE.componentHookIndex = previousComponentHookIndex;
216 GLOBAL_STATE.componentIsMounting = previousComponentIsMounting;
217 }
218 }
219
220 function primitiveComponentName(renderName: string): string {
221 if (/^render[A-Z]/.test(renderName)) return renderName.slice('render'.length);
222 return renderName || 'Anonymous';
223 }
224
Documentation version
Section titled “Documentation version”Documentation built with @pibbl/core 0.0.2, revision 272a94a. ALPHA — NOT FOR PRODUCTION USE.