packages/core/src/lib/layout/overlay.ts
This is the source snapshot used to build these API details. View this revision on GitHub.
1 import { preparedStyleValue, preparedStyleQuery } from '../style/responsive.js';
2 import { definePrimitive } from '../define-primitive.js';
3 import { GLOBAL_STATE } from '../global-state.js';
4 import { layoutDiagnostic } from '../style/diagnostics.js';
5 import {
6 type PibblStructuralEventProps,
7 wireStructuralEvents,
8 } from '../components/structural-events.js';
9 import type { BoxStyle, LayoutItemStyle, ResolvedBoxStyle } from '../style/types.js';
10 import type {
11 PibblNodeInput,
12 RenderingContext2D,
13 SystemStyle,
14 } from '../types.js';
15 import type { PibblElement } from '../element/types.js';
16 import { getChildPrimitiveReceiverToken } from '../element/metadata.js';
17 import {
18 childWithUsedSize,
19 prepareLayoutChildren,
20 prepareDirectChildStyle,
21 placeDirectChildren,
22 resolveContainerBox,
23 resolveDirectChildBox,
24 } from './absolute.js';
25 import { pushLayoutBox } from './context.js';
26 import { recordLayoutEvaluation } from './diagnostics.js';
27 import type { LayoutBox } from './types.js';
28
29 type OverlayAlignment = 'start' | 'center' | 'end' | 'stretch';
30
31 /**
32 * Supported geometry and presentation properties for Overlay.
33 *
34 * @see {@link BoxStyle}
35 * @see {@link LayoutItemStyle}
36 * @see {@link Overlay}
37 */
38 export interface OverlayStyle extends BoxStyle, LayoutItemStyle {
39 /** Default alignment of children on the cross or block axis. See {@link OverlayAlignment}. */
40 alignItems?: OverlayAlignment;
41 /** Default inline-axis alignment of grid or overlay children. See {@link OverlayAlignment}. */
42 justifyItems?: OverlayAlignment;
43 }
44
45 /**
46 * Authored inputs for Overlay, including the declared data and presentation options.
47 *
48 * @see {@link PibblNodeInput}
49 * @see {@link Overlay}
50 */
51 export interface OverlayProps extends PibblStructuralEventProps {
52 /** Descendant content or the callback that supplies it. See {@link PibblNodeInput}. */
53 children: PibblNodeInput;
54 }
55
56 interface ResolvedOverlayStyle extends SystemStyle {
57 box: ResolvedBoxStyle;
58 overflow: 'visible' | 'clip';
59 alignItems: OverlayAlignment;
60 justifyItems: OverlayAlignment;
61 }
62
63 function renderOverlay(
64 props: OverlayProps,
65 style: Readonly<ResolvedOverlayStyle>,
66 ctx: RenderingContext2D,
67 ) {
68 wireStructuralEvents(props);
69 const children = prepareLayoutChildren(props.children, 'Overlay');
70 recordLayoutEvaluation();
71 const content = style.box.contentBox;
72 ctx.translate(content.x, content.y);
73 const localContent = Object.freeze({
74 x: 0,
75 y: 0,
76 width: content.width,
77 height: content.height,
78 });
79 const releaseLayout = pushLayoutBox(localContent);
80 GLOBAL_STATE.componentRefs!.onAfterRender = releaseLayout;
81
82 let occurrence = 0;
83 return placeDirectChildren(
84 children,
85 localContent,
86 child => resolveOverlayChildPlacement(
87 localContent,
88 child,
89 style,
90 getChildPrimitiveReceiverToken(
91 GLOBAL_STATE.renderTransaction,
92 GLOBAL_STATE.primitiveReceiverToken,
93 occurrence++,
94 ),
95 ),
96 style.overflow,
97 );
98 }
99
100 /**
101 * Describes overlay layout for JSX or createElement authoring.
102 *
103 * @param props - Authored component inputs, supplied through JSX or createElement. See the linked
104 * props and style types.
105 * @throws When called directly; Pibbl mounts this component through JSX or createElement.
106 *
107 * @see {@link OverlayProps}
108 * @see {@link OverlayStyle}
109 */
110 export const Overlay = definePrimitive<
111 OverlayProps,
112 OverlayStyle,
113 OverlayStyle,
114 ResolvedOverlayStyle
115 >(renderOverlay, {
116 childInput: 'structural',
117 resolveStyle: (style, context) => ({
118 box: resolveContainerBox(style, context, 'Overlay', 'overlay'),
119 overflow: style.overflow ?? 'visible',
120 alignItems: style.alignItems ?? 'stretch',
121 justifyItems: style.justifyItems ?? 'stretch',
122 }),
123 });
124
125 export function placeOverlayChild(
126 container: Readonly<LayoutBox>,
127 child: PibblElement<any>,
128 alignment: Readonly<Pick<OverlayStyle, 'alignItems' | 'justifyItems'>> = {},
129 ): LayoutBox {
130 return resolveOverlayChildPlacement(container, child, alignment).box;
131 }
132
133 function resolveOverlayChildPlacement(
134 container: Readonly<LayoutBox>,
135 child: PibblElement<any>,
136 alignment: Readonly<Pick<OverlayStyle, 'alignItems' | 'justifyItems'>> = {},
137 receiverToken?: object,
138 ): { box: LayoutBox; element: PibblElement<any> } {
139 const prepared = prepareDirectChildStyle(child, receiverToken, container);
140 const item = preparedStyleValue(prepared) as Readonly<
141 BoxStyle & LayoutItemStyle
142 >;
143 const resolved = resolveDirectChildBox(
144 container,
145 child,
146 'overlay',
147 item,
148 );
149 const declaredBox = resolved.borderBox;
150 const horizontal = resolveItemAlignment(
151 item.justifySelf,
152 alignment.justifyItems ?? 'stretch',
153 child,
154 container,
155 'justifySelf',
156 );
157 const vertical = resolveItemAlignment(
158 item.alignSelf,
159 alignment.alignItems ?? 'stretch',
160 child,
161 container,
162 'alignSelf',
163 );
164 const x = alignAxis(
165 container.x,
166 container.width,
167 declaredBox.width,
168 horizontal,
169 );
170 const y = alignAxis(
171 container.y,
172 container.height,
173 declaredBox.height,
174 vertical,
175 );
176 const box = {
177 x,
178 y,
179 width: horizontal === 'stretch' ?
180 Math.max(0, container.width) :
181 declaredBox.width,
182 height: vertical === 'stretch' ?
183 Math.max(0, container.height) :
184 declaredBox.height,
185 };
186 const horizontalPadding = declaredBox.width - resolved.contentBox.width;
187 const verticalPadding = declaredBox.height - resolved.contentBox.height;
188 return {
189 box,
190 element: childWithUsedSize(
191 child,
192 item,
193 Math.max(0, box.width - horizontalPadding),
194 Math.max(0, box.height - verticalPadding),
195 receiverToken,
196 preparedStyleQuery(prepared),
197 ),
198 };
199 }
200
201 function resolveItemAlignment(
202 item: LayoutItemStyle['alignSelf'] | LayoutItemStyle['justifySelf'],
203 fallback: OverlayAlignment,
204 child: PibblElement<any>,
205 container: Readonly<LayoutBox>,
206 property: 'alignSelf' | 'justifySelf',
207 ): OverlayAlignment {
208 if (item === undefined || item === 'auto') return fallback;
209 if (item === 'start' || item === 'center' || item === 'end' || item === 'stretch') {
210 return item;
211 }
212 throw layoutDiagnostic({
213 component: child.type.name || 'Anonymous',
214 property,
215 value: item,
216 algorithm: 'overlay',
217 constraints: {
218 minWidth: 0,
219 maxWidth: container.width,
220 minHeight: 0,
221 maxHeight: container.height,
222 },
223 reason: 'uses an unsupported overlay alignment value',
224 });
225 }
226
227 function alignAxis(
228 start: number,
229 available: number,
230 size: number,
231 alignment: OverlayAlignment,
232 ): number {
233 switch (alignment) {
234 case 'center': return start + (available - size) / 2;
235 case 'end': return start + available - size;
236 default: return start;
237 }
238 }
239
Documentation version
Section titled “Documentation version”Documentation built with @pibbl/core 0.0.2, revision 272a94a. ALPHA — NOT FOR PRODUCTION USE.