Skip to content

packages/core/src/lib/define-primitive.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   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 built with @pibbl/core 0.0.2, revision 2dccb19. ALPHA — NOT FOR PRODUCTION USE.