1 | // eslint-disable-next-line node/no-extraneous-import
|
2 | import { Reference } from '@glimmer/reference';
|
3 | import { SimpleElement } from '@simple-dom/interface';
|
4 | import { PreparedArguments, ComponentInstanceState } from '../../components';
|
5 | import { Option, Destroyable } from '../../core';
|
6 | import { Bounds } from '../../dom/bounds';
|
7 | import { CapturedArguments, VMArguments } from '../../runtime/arguments';
|
8 | import { ElementOperations } from '../../runtime/element';
|
9 | import { Environment } from '../../runtime/environment';
|
10 | import { RuntimeResolver } from '../../serialize';
|
11 | import { CompilableProgram } from '../../template';
|
12 | import { ProgramSymbolTable } from '../../tier1/symbol-table';
|
13 | import { DynamicScope } from '../../runtime/scope';
|
14 | import { RenderNode } from '../../runtime/debug-render-tree';
|
15 | import { Owner } from '../../runtime';
|
16 |
|
17 | /**
|
18 | * Describes the capabilities of a particular component. The capabilities are
|
19 | * provided to the Glimmer compiler and VM via the ComponentDefinition, which
|
20 | * includes a ComponentCapabilities record.
|
21 | *
|
22 | * Certain features in the VM come with some overhead, so the compiler and
|
23 | * runtime use this information to skip unnecessary work for component types
|
24 | * that don't need it.
|
25 | *
|
26 | * For example, a component that is template-only (i.e., it does not have an
|
27 | * associated JavaScript class to instantiate) can skip invoking component
|
28 | * manager hooks related to lifecycle events by setting the `elementHook`
|
29 | * capability to `false`.
|
30 | */
|
31 | export interface InternalComponentCapabilities {
|
32 | /**
|
33 | * Whether a component's template is static across all instances of that
|
34 | * component, or can vary per instance. This should usually be `false` except
|
35 | * for cases of backwards-compatibility.
|
36 | */
|
37 | dynamicLayout: boolean;
|
38 |
|
39 | /**
|
40 | * Whether a "wrapped" component's root element can change after being
|
41 | * rendered. This flag is only used by the WrappedBuilder and should be
|
42 | * `false` except for cases of backwards-compatibility.
|
43 | */
|
44 | dynamicTag: boolean;
|
45 |
|
46 | wrapped: boolean;
|
47 |
|
48 | /**
|
49 | * Setting the `prepareArgs` flag to true enables the `prepareArgs` hook on
|
50 | * the component manager, which would otherwise not be called.
|
51 | *
|
52 | * The component manager's `prepareArgs` hook allows it to programmatically
|
53 | * add or remove positional and named arguments for a component before the
|
54 | * component is invoked. This flag should usually be `false` except for cases
|
55 | * of backwards-compatibility.
|
56 | */
|
57 | prepareArgs: boolean;
|
58 |
|
59 | /**
|
60 | * Whether a reified `Arguments` object will get passed to the component
|
61 | * manager's `create` hook. If a particular component does not access passed
|
62 | * arguments from JavaScript (via the `this.args` property in Glimmer.js, for
|
63 | * example), this flag can be set to `false` to avoid the work of
|
64 | * instantiating extra data structures to expose the arguments to JavaScript.
|
65 | */
|
66 | createArgs: boolean;
|
67 |
|
68 | /**
|
69 | * Whether the component needs the caller component
|
70 | */
|
71 | createCaller: boolean;
|
72 |
|
73 | /**
|
74 | * Whether to call the `didSplatAttributes` hook on the component manager.
|
75 | */
|
76 | attributeHook: boolean;
|
77 |
|
78 | /**
|
79 | * Whether to call the `didCreateElement` hook on the component manager.
|
80 | */
|
81 | elementHook: boolean;
|
82 |
|
83 | /**
|
84 | * Whether the component manager has an update hook.
|
85 | */
|
86 | updateHook: boolean;
|
87 |
|
88 | /**
|
89 | * Whether the component needs an additional dynamic scope frame.
|
90 | */
|
91 | dynamicScope: boolean;
|
92 |
|
93 | /**
|
94 | * Whether there is a component instance to create. If this is false,
|
95 | * the component is a "template only component"
|
96 | */
|
97 | createInstance: boolean;
|
98 |
|
99 | /**
|
100 | * Whether or not the component has a `willDestroy` hook that should fire
|
101 | * prior to the component being removed from the DOM.
|
102 | */
|
103 | willDestroy: boolean;
|
104 |
|
105 | /**
|
106 | * Whether or not the component pushes an owner onto the owner stack. This is
|
107 | * used for engines.
|
108 | */
|
109 | hasSubOwner: boolean;
|
110 | }
|
111 |
|
112 | /**
|
113 | * Enum used for bit flags version of the capabilities, used once the component
|
114 | * has been loaded for the first time
|
115 | */
|
116 | export const enum InternalComponentCapability {
|
117 | DynamicLayout = 0b0000000000001,
|
118 | DynamicTag = 0b0000000000010,
|
119 | PrepareArgs = 0b0000000000100,
|
120 | CreateArgs = 0b0000000001000,
|
121 | AttributeHook = 0b0000000010000,
|
122 | ElementHook = 0b0000000100000,
|
123 | DynamicScope = 0b0000001000000,
|
124 | CreateCaller = 0b0000010000000,
|
125 | UpdateHook = 0b0000100000000,
|
126 | CreateInstance = 0b0001000000000,
|
127 | Wrapped = 0b0010000000000,
|
128 | WillDestroy = 0b0100000000000,
|
129 | HasSubOwner = 0b1000000000000,
|
130 | }
|
131 |
|
132 | ////////////
|
133 |
|
134 | export interface InternalComponentManager<
|
135 | TComponentStateBucket = unknown,
|
136 | TComponentDefinition = object
|
137 | > {
|
138 | getCapabilities(state: TComponentDefinition): InternalComponentCapabilities;
|
139 | getSelf(state: TComponentStateBucket): Reference;
|
140 | getDestroyable(state: TComponentStateBucket): Option<Destroyable>;
|
141 | getDebugName(state: TComponentDefinition): string;
|
142 | }
|
143 |
|
144 | interface CustomRenderNode extends RenderNode {
|
145 | bucket: object;
|
146 | }
|
147 |
|
148 | export interface WithCustomDebugRenderTree<
|
149 | ComponentInstanceState = unknown,
|
150 | ComponentDefinitionState = unknown
|
151 | > extends InternalComponentManager<ComponentInstanceState, ComponentDefinitionState> {
|
152 | // APIs for hooking into the debug render tree, used by components that
|
153 | // represent multiple logical components. Specifically, {{mount}} and {{outlet}}
|
154 | getDebugCustomRenderTree(
|
155 | definition: ComponentDefinitionState,
|
156 | state: ComponentInstanceState,
|
157 | args: CapturedArguments,
|
158 | template?: string
|
159 | ): CustomRenderNode[];
|
160 | }
|
161 |
|
162 | export interface WithPrepareArgs<
|
163 | ComponentInstanceState = unknown,
|
164 | ComponentDefinitionState = unknown
|
165 | > extends InternalComponentManager<ComponentInstanceState, ComponentDefinitionState> {
|
166 | // The component manager is asked to prepare the arguments needed
|
167 | // for `create`. This allows for things like closure> components where the
|
168 | // args need to be curried before constructing the instance of the state
|
169 | // bucket.
|
170 | prepareArgs(state: ComponentDefinitionState, args: VMArguments): Option<PreparedArguments>;
|
171 | }
|
172 |
|
173 | export interface WithSubOwner<ComponentInstanceState = unknown, ComponentDefinitionState = unknown>
|
174 | extends InternalComponentManager<ComponentInstanceState, ComponentDefinitionState> {
|
175 | getOwner(state: ComponentInstanceState): Owner;
|
176 | }
|
177 |
|
178 | export interface WithCreateInstance<
|
179 | ComponentInstanceState = unknown,
|
180 | ComponentDefinitionState = unknown,
|
181 | O extends Owner = Owner
|
182 | > extends InternalComponentManager<ComponentInstanceState, ComponentDefinitionState> {
|
183 | // The component manager is asked to create a bucket of state for
|
184 | // the supplied arguments. From the perspective of Glimmer, this is
|
185 | // an opaque token, but in practice it is probably a component object.
|
186 | create(
|
187 | owner: O,
|
188 | state: ComponentDefinitionState,
|
189 | args: Option<VMArguments>,
|
190 | env: Environment,
|
191 | dynamicScope: Option<DynamicScope>,
|
192 | caller: Option<Reference>,
|
193 | hasDefaultBlock: boolean
|
194 | ): ComponentInstanceState;
|
195 |
|
196 | // This hook is run after the entire layout has been rendered.
|
197 | //
|
198 | // Hosts should use `didCreate`, which runs asynchronously after the rendering
|
199 | // process, to provide hooks for user code.
|
200 | didRenderLayout(state: ComponentInstanceState, bounds: Bounds): void;
|
201 |
|
202 | // This hook is run after the entire layout has been updated.
|
203 | //
|
204 | // Hosts should use `didUpdate`, which runs asynchronously after the rendering
|
205 | // process, to provide hooks for user code.
|
206 | didUpdateLayout(state: ComponentInstanceState, bounds: Bounds): void;
|
207 |
|
208 | // Once the whole top-down rendering process is complete, Glimmer invokes
|
209 | // the `didCreate` callbacks.
|
210 | didCreate(state: ComponentInstanceState): void;
|
211 |
|
212 | // Finally, once top-down revalidation has completed, Glimmer invokes
|
213 | // the `didUpdate` callbacks on components that changed.
|
214 | didUpdate(state: ComponentInstanceState): void;
|
215 | }
|
216 |
|
217 | export interface WithUpdateHook<ComponentInstanceState = unknown>
|
218 | extends InternalComponentManager<ComponentInstanceState> {
|
219 | // When the component's tag has invalidated, the manager's `update` hook is
|
220 | // called.
|
221 | update(state: ComponentInstanceState, dynamicScope: Option<DynamicScope>): void;
|
222 | }
|
223 |
|
224 | export interface WithDynamicLayout<
|
225 | I = ComponentInstanceState,
|
226 | R extends RuntimeResolver = RuntimeResolver
|
227 | > extends InternalComponentManager<I> {
|
228 | // Return the compiled layout to use for this component. This is called
|
229 | // *after* the component instance has been created, because you might
|
230 | // want to return a different layout per-instance for optimization reasons
|
231 | // or to implement features like Ember's "late-bound" layouts.
|
232 | getDynamicLayout(component: I, resolver: R): CompilableProgram | null;
|
233 | }
|
234 |
|
235 | export interface WithDynamicTagName<ComponentInstanceState>
|
236 | extends InternalComponentManager<ComponentInstanceState> {
|
237 | // If the component asks for the dynamic tag name capability, ask for
|
238 | // the tag name to use. (Only used in the "WrappedBuilder".)
|
239 | getTagName(component: ComponentInstanceState): Option<string>;
|
240 | }
|
241 |
|
242 | export interface WithAttributeHook<ComponentInstanceState>
|
243 | extends InternalComponentManager<ComponentInstanceState> {
|
244 | didSplatAttributes(
|
245 | component: ComponentInstanceState,
|
246 | element: ComponentInstanceState,
|
247 | operations: ElementOperations
|
248 | ): void;
|
249 | }
|
250 |
|
251 | export interface WithElementHook<ComponentInstanceState>
|
252 | extends InternalComponentManager<ComponentInstanceState> {
|
253 | // The `didCreateElement` hook is run for non-tagless components after the
|
254 | // element as been created, but before it has been appended ("flushed") to
|
255 | // the DOM. This hook allows the manager to save off the element, as well as
|
256 | // install other dynamic attributes via the ElementOperations object.
|
257 | //
|
258 | // Hosts should use `didCreate`, which runs asynchronously after the rendering
|
259 | // process, to provide hooks for user code.
|
260 | didCreateElement(
|
261 | component: ComponentInstanceState,
|
262 | element: SimpleElement,
|
263 | operations: ElementOperations
|
264 | ): void;
|
265 | }
|
266 |
|
267 | export interface Invocation {
|
268 | handle: number;
|
269 | symbolTable: ProgramSymbolTable;
|
270 | }
|