Skip to content

packages/core/src/lib/style/positioning.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 { 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 built with @pibbl/core 0.0.2, revision 272a94a. ALPHA — NOT FOR PRODUCTION USE.