Skip to content

packages/core/src/lib/transitions/binding.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 { PibblVisibilityTransition } from '../hooks/use-visibility-transition.js';
2 import { GLOBAL_STATE } from '../global-state.js';
3 import { requireHookContext } from '../hooks/hook-slot.js';
4 import type { PibblComponentRefs, PibblEventRoot, PibblInstance } from '../types.js';
5 import type { PibblFilter } from '../style/filter-types.js';
6 
7 declare const bindingBrand: unique symbol;
8 /**
9  * An opaque connection between one transition controller and its painted subject.
10  *
11  * @see {@link PibblVisibilityTransition}
12  */
13 export interface PibblTransitionBinding { readonly [bindingBrand]: true }
14 interface BindingRecord {
15   readonly resolve: () => readonly PibblFilter[];
16   readonly root: PibblEventRoot;
17   readonly owner: PibblComponentRefs<any>;
18   receiver?: PibblComponentRefs<any>;
19   receiverInstance?: PibblInstance;
20 }
21 const bindings = new WeakMap<PibblTransitionBinding, BindingRecord>();
22 export function createTransitionBinding(resolve: () => readonly PibblFilter[]): PibblTransitionBinding {
23   const binding = Object.freeze({}) as PibblTransitionBinding;
24   const { componentRefs, pibblInstance } = requireHookContext();
25   bindings.set(binding, { resolve, owner: componentRefs, root: pibblInstance.eventRoot });
26   return binding;
27 }
28 export function resolveTransitionBinding(binding: PibblTransitionBinding): readonly PibblFilter[] {
29   const record = bindings.get(binding);
30   if (!record) throw new TypeError('style.transition requires a binding from useVisibilityTransition in the same Pibbl runtime.');
31   const receiver = GLOBAL_STATE.componentRefs;
32   if (GLOBAL_STATE.currentPibblInstance?.eventRoot !== record.root) throw new Error('A transition binding must stay within its owning Pibbl root.');
33   if (record.owner.teardowns.closed) throw new Error('Cannot use a disposed transition binding.');
34   if (!receiver) throw new Error('Transition bindings require a mounted receiver.');
35   const instance = GLOBAL_STATE.currentPibblInstance!;
36   // Keyed replacements are visited before the old receiver is torn down. Only
37   // retire an unseen receiver in this same render instance; cached Layers own
38   // independent generations and must not silently lose their binding.
39   if (record.receiver && record.receiver !== receiver && record.receiverInstance === instance && record.receiver.renderGeneration !== instance.renderGeneration) {
40     record.receiver = undefined;
41   }
42   if (record.receiver && record.receiver !== receiver) throw new Error('A transition binding supports one mounted receiver. Wrap shared content in a Group.');
43   if (!record.receiver) {
44     record.receiver = receiver;
45     record.receiverInstance = instance;
46     receiver.teardowns.add(() => { if (record.receiver === receiver) record.receiver = undefined; });
47   }
48   return record.resolve();
49 }
50 

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