Skip to content

packages/core/src/lib/components/html-box.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 type { HtmlMountCallback } from '../html/types.js';
2 import type { ResolvedDrawingStyle } from '../style/types.js';
3 import { definePrimitive } from '../define-primitive.js';
4 import { GLOBAL_STATE } from '../global-state.js';
5 import { resolveBox } from '../layout/box.js';
6 import type { LayoutBox } from '../layout/types.js';
7 import { createGroupTransform, setPrimitivePresentation } from '../primitive-presentation.js';
8 import { getCurrentWorldTransform } from '../render-target.js';
9 import { registerOpaquePrimitiveProps } from '../element/metadata.js';
10 import { requireHtmlService, snapshotClips } from '../html/html-service.js';
11 import type { HtmlBoxProps, HtmlBoxSlot, HtmlBoxStyle } from '../html/types.js';
12 import type { PibblRenderResult, PrimitiveInput, RenderingContext2D } from '../types.js';
13 import { stripLayoutParticipation } from '../style/resolve-built-in.js';
14 
15 type Resolved = ResolvedDrawingStyle<HtmlBoxStyle> & {
16   borderBox: Readonly<LayoutBox>; contentBox: Readonly<LayoutBox>; overflow: 'visible' | 'clip';
17 };
18 
19 function renderHtmlBox(props: HtmlBoxProps<unknown>, style: Readonly<Resolved>, ctx: RenderingContext2D): void {
20   const refs = GLOBAL_STATE.componentRefs!;
21   const owner = GLOBAL_STATE.currentPibblInstance!;
22   const service = requireHtmlService(owner);
23   let slot = refs.refs[0] as HtmlBoxSlot | undefined;
24   if (!slot || slot.disposed) {
25     if (typeof props.mount !== 'function') throw new TypeError('HtmlBox mount must be a function.');
26     slot = { owner, mount: props.mount, disposed: false, region: { identity: {}, owner, content: undefined, rootBounds: undefined, active: true } };
27     const stable = slot;
28     stable.region = { ...stable.region, onFocusSnapshot(snapshot) {
29       queueMicrotask(() => { if (!stable.disposed) stable.callback?.(snapshot); });
30     } };
31     refs.refs[0] = slot;
32     refs.teardowns.add(() => service.release(stable));
33   }
34   const opacity = style.opacity ?? 1;
35   if (!Number.isFinite(opacity) || opacity < 0 || opacity > 1) throw new RangeError('HtmlBox opacity must be between 0 and 1.');
36   service.register({ slot, data: props.data, matrix: getCurrentWorldTransform(),
37     clips: snapshotClips(GLOBAL_STATE.clippingStack), border: style.borderBox, content: style.contentBox,
38     opacity: ctx.globalAlpha * opacity, interactive: props.pointerEvents !== 'none', overflow: style.overflow ?? 'clip', onFocusChange: props.onFocusChange });
39 }
40 
41 const primitive = definePrimitive<HtmlBoxProps<unknown>, HtmlBoxStyle, HtmlBoxStyle, Resolved>(renderHtmlBox, {
42   resolveStyle(style, context) {
43     const box = resolveBox({ ...style, width: style.width === undefined || style.width === 'auto' ? context.allocation.width : style.width,
44       height: style.height === undefined || style.height === 'auto' ? context.allocation.height : style.height }, context.constraints, { component: 'HtmlBox' });
45     return { ...stripLayoutParticipation(style), ...box, overflow: style.overflow ?? 'clip' } as Resolved;
46   },
47 });
48 
49 registerOpaquePrimitiveProps(primitive, ['data']);
50 setPrimitivePresentation<Resolved>(primitive, style => ({ transform: createGroupTransform(style) }));
51 
52 type HtmlBoxInput<Props> = Omit<PrimitiveInput<HtmlBoxProps<Props>, HtmlBoxStyle>, 'data' | 'mount'> & Pick<HtmlBoxProps<Props>, 'data' | 'mount'>;
53 type HtmlBoxComponent = {
54   <Props>(props: HtmlBoxInput<Props>): PibblRenderResult;
55 };
56 /**
57  * A persistent native HTML box projected into Pibbl layout, transforms and clips.
58  *
59  * @see {@link HtmlBoxProps}
60  * @see {@link HtmlBoxStyle}
61  * @see {@link HtmlMountCallback}
62  */
63 export const HtmlBox = primitive as HtmlBoxComponent;
64 

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