packages/core/src/lib/style/positioning.ts
This is the source snapshot used to build these API details. View this revision on GitHub.
1 import type { StyleResolutionContext, SystemStyle } from '../types.js';
2 import {
3 getPlacementSignalHandoffStyle,
4 setPlacementSignalHandoff,
5 } from '../element/metadata.js';
6 import type { PibblElement } from '../element/types.js';
7 import { isSignal } from '../signals/graph.js';
8 import { resolveLocalOffset } from './resolve-built-in.js';
9 import type { Length } from './types.js';
10
11 const PLACEMENT_HANDOFF = Symbol('pibbl.placement-handoff');
12
13 type PropsWithStyle = Record<PropertyKey, unknown> & {
14 readonly style?: unknown;
15 };
16
17 type PlacementHandoff = Readonly<{
18 source: Readonly<SystemStyle>;
19 }>;
20
21 let activeSignalHandoff: {
22 readonly signal: object;
23 readonly consumedStyle: Readonly<SystemStyle>;
24 } | undefined;
25
26 function hasPositioning(style: Readonly<Record<PropertyKey, unknown>>): boolean {
27 if (Object.hasOwn(style, 'left') || Object.hasOwn(style, 'top')) return true;
28 const when = style.when;
29 return Array.isArray(when) && when.some((rule) => {
30 if (!rule || typeof rule !== 'object') return false;
31 const patch = (rule as { style?: unknown }).style;
32 return !!patch && typeof patch === 'object' &&
33 (Object.hasOwn(patch, 'left') || Object.hasOwn(patch, 'top'));
34 });
35 }
36
37 /** @internal Marks a placed function component's object style for forwarding. */
38 export function withPlacementHandoff<P>(
39 props: P,
40 consumedStyle: Readonly<SystemStyle> | undefined,
41 ): P {
42 if (!props || typeof props !== 'object') return props;
43 const candidate = props as PropsWithStyle;
44 const style = candidate.style;
45 if (
46 !style ||
47 typeof style !== 'object' ||
48 isSignal(style) ||
49 (style as Record<PropertyKey, unknown>)[PLACEMENT_HANDOFF] !== undefined ||
50 !hasPositioning(style as Readonly<Record<PropertyKey, unknown>>)
51 ) {
52 return props;
53 }
54 return {
55 ...candidate,
56 style: Object.freeze({
57 ...(style as Record<PropertyKey, unknown>),
58 [PLACEMENT_HANDOFF]: Object.freeze({
59 source: consumedStyle ?? style as Readonly<SystemStyle>,
60 }),
61 }),
62 } as P;
63 }
64
65 /** @internal Activates signal identity capture while one plain component runs. */
66 export function enterPlacementHandoffCapture(
67 props: unknown,
68 consumedStyle: Readonly<SystemStyle> | undefined,
69 ): () => void {
70 const style = props && typeof props === 'object' ?
71 (props as PropsWithStyle).style : undefined;
72 const previous = activeSignalHandoff;
73 activeSignalHandoff = style && isSignal(style) && consumedStyle !== undefined ? {
74 signal: style,
75 consumedStyle,
76 } : undefined;
77 return () => {
78 activeSignalHandoff = previous;
79 };
80 }
81
82 /** @internal Annotates an element created with the active forwarded style signal. */
83 export function capturePlacementHandoff(
84 element: PibblElement<any>,
85 style: unknown,
86 ): void {
87 if (style && style === activeSignalHandoff?.signal) {
88 setPlacementSignalHandoff(element, activeSignalHandoff.consumedStyle);
89 }
90 }
91
92 /** @internal Reads the original style behind an enumerable forwarding marker. */
93 export function getPlacementHandoffSource(
94 style: unknown,
95 ): Readonly<SystemStyle> | undefined {
96 if (!style || typeof style !== 'object') return undefined;
97 const marker = (style as Record<PropertyKey, unknown>)[PLACEMENT_HANDOFF];
98 if (!marker || typeof marker !== 'object') return undefined;
99 return (marker as PlacementHandoff).source;
100 }
101
102 /** @internal Removes a private handoff marker before style callbacks run. */
103 export function consumePlacementHandoff(
104 style: Readonly<SystemStyle>,
105 ): Readonly<SystemStyle> {
106 if (getPlacementHandoffSource(style) === undefined) return style;
107 const withoutHandoff = { ...(style as Record<PropertyKey, unknown>) };
108 delete withoutHandoff[PLACEMENT_HANDOFF];
109 return Object.freeze(withoutHandoff) as Readonly<SystemStyle>;
110 }
111
112 /** @internal Determines whether a primitive consumes one forwarded signal style. */
113 export function getPlacementSignalHandoff(
114 element: PibblElement<any>,
115 ): Readonly<SystemStyle> | undefined {
116 return getPlacementSignalHandoffStyle(element);
117 }
118
119 /**
120 * Offsets in the parent layout coordinate system; percentages resolve against the containing box.
121 *
122 * @see {@link Length}
123 * @see {@link SystemStyle}
124 */
125 export interface PositioningStyle extends SystemStyle {
126 /** Left edge coordinate or inset. See {@link Length}. */
127 left?: Exclude<Length, 'auto'>;
128 /** Top edge value or top envelope guide. See {@link Length}. */
129 top?: Exclude<Length, 'auto'>;
130 }
131
132 export interface ResolvedPosition {
133 left: number;
134 top: number;
135 }
136
137 export function resolveStandalonePosition(
138 style: Readonly<PositioningStyle>,
139 context: Readonly<StyleResolutionContext>,
140 ): ResolvedPosition {
141 return {
142 left: resolveLocalOffset(style.left, context.percentageBasis.width, 'left', context),
143 top: resolveLocalOffset(style.top, context.percentageBasis.height, 'top', context),
144 };
145 }
146
Documentation version
Section titled “Documentation version”Documentation built with @pibbl/core 0.0.2, revision 2dccb19. ALPHA — NOT FOR PRODUCTION USE.