@b9g/crank 0.5.0-beta.4 → 0.5.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core.cjs +1827 -0
- package/core.cjs.map +1 -0
- package/core.d.ts +481 -0
- package/core.js +1814 -0
- package/core.js.map +1 -0
- package/crank.cjs +15 -1821
- package/crank.cjs.map +1 -1
- package/crank.d.ts +2 -481
- package/crank.js +2 -1812
- package/crank.js.map +1 -1
- package/dom.cjs +4 -4
- package/dom.cjs.map +1 -1
- package/dom.d.ts +1 -1
- package/dom.js +1 -1
- package/dom.js.map +1 -1
- package/html.cjs +3 -3
- package/html.cjs.map +1 -1
- package/html.d.ts +2 -1
- package/html.js +1 -1
- package/html.js.map +1 -1
- package/jsx-runtime.cjs +20 -0
- package/jsx-runtime.cjs.map +1 -0
- package/jsx-runtime.d.ts +5 -0
- package/jsx-runtime.js +15 -0
- package/jsx-runtime.js.map +1 -0
- package/package.json +20 -12
- package/tags.cjs +2 -2
- package/tags.cjs.map +1 -1
- package/tags.d.ts +1 -1
- package/tags.js +1 -1
- package/tags.js.map +1 -1
- package/umd.js +424 -0
- package/umd.js.map +1 -1
- package/mod.cjs +0 -21
- package/mod.cjs.map +0 -1
- package/mod.d.ts +0 -2
- package/mod.js +0 -4
- package/mod.js.map +0 -1
package/crank.js
CHANGED
|
@@ -1,1814 +1,4 @@
|
|
|
1
1
|
/// <reference types="crank.d.ts" />
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
function wrap(value) {
|
|
5
|
-
return value === undefined ? [] : Array.isArray(value) ? value : [value];
|
|
6
|
-
}
|
|
7
|
-
function unwrap(arr) {
|
|
8
|
-
return arr.length === 0 ? undefined : arr.length === 1 ? arr[0] : arr;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Ensures a value is an array.
|
|
12
|
-
*
|
|
13
|
-
* This function does the same thing as wrap() above except it handles nulls
|
|
14
|
-
* and iterables, so it is appropriate for wrapping user-provided element
|
|
15
|
-
* children.
|
|
16
|
-
*/
|
|
17
|
-
function arrayify(value) {
|
|
18
|
-
return value == null
|
|
19
|
-
? []
|
|
20
|
-
: Array.isArray(value)
|
|
21
|
-
? value
|
|
22
|
-
: typeof value === "string" ||
|
|
23
|
-
typeof value[Symbol.iterator] !== "function"
|
|
24
|
-
? [value]
|
|
25
|
-
: [...value];
|
|
26
|
-
}
|
|
27
|
-
function isIteratorLike(value) {
|
|
28
|
-
return value != null && typeof value.next === "function";
|
|
29
|
-
}
|
|
30
|
-
function isPromiseLike(value) {
|
|
31
|
-
return value != null && typeof value.then === "function";
|
|
32
|
-
}
|
|
33
|
-
/***
|
|
34
|
-
* SPECIAL TAGS
|
|
35
|
-
*
|
|
36
|
-
* Crank provides a couple tags which have special meaning for the renderer.
|
|
37
|
-
***/
|
|
38
|
-
/**
|
|
39
|
-
* A special tag for grouping multiple children within the same parent.
|
|
40
|
-
*
|
|
41
|
-
* All non-string iterables which appear in the element tree are implicitly
|
|
42
|
-
* wrapped in a fragment element.
|
|
43
|
-
*
|
|
44
|
-
* This tag is just the empty string, and you can use the empty string in
|
|
45
|
-
* createElement calls or transpiler options directly to avoid having to
|
|
46
|
-
* reference this export.
|
|
47
|
-
*/
|
|
48
|
-
const Fragment = "";
|
|
49
|
-
// TODO: We assert the following symbol tags as any because TypeScript support
|
|
50
|
-
// for symbol tags in JSX doesn’t exist yet.
|
|
51
|
-
// https://github.com/microsoft/TypeScript/issues/38367
|
|
52
|
-
/**
|
|
53
|
-
* A special tag for rendering into a new root node via a root prop.
|
|
54
|
-
*
|
|
55
|
-
* This tag is useful for creating element trees with multiple roots, for
|
|
56
|
-
* things like modals or tooltips.
|
|
57
|
-
*
|
|
58
|
-
* Renderer.prototype.render() will implicitly wrap top-level element trees in
|
|
59
|
-
* a Portal element.
|
|
60
|
-
*/
|
|
61
|
-
const Portal = Symbol.for("crank.Portal");
|
|
62
|
-
/**
|
|
63
|
-
* A special tag which preserves whatever was previously rendered in the
|
|
64
|
-
* element’s position.
|
|
65
|
-
*
|
|
66
|
-
* Copy elements are useful for when you want to prevent a subtree from
|
|
67
|
-
* rerendering as a performance optimization. Copy elements can also be keyed,
|
|
68
|
-
* in which case the previously rendered keyed element will be copied.
|
|
69
|
-
*/
|
|
70
|
-
const Copy = Symbol.for("crank.Copy");
|
|
71
|
-
/**
|
|
72
|
-
* A special tag for injecting raw nodes or strings via a value prop.
|
|
73
|
-
*
|
|
74
|
-
* If the value prop is a string, Renderer.prototype.parse() will be called on
|
|
75
|
-
* the string and the result will be set as the element’s value.
|
|
76
|
-
*/
|
|
77
|
-
const Raw = Symbol.for("crank.Raw");
|
|
78
|
-
const ElementSymbol = Symbol.for("crank.Element");
|
|
79
|
-
/**
|
|
80
|
-
* Elements are the basic building blocks of Crank applications. They are
|
|
81
|
-
* JavaScript objects which are interpreted by special classes called renderers
|
|
82
|
-
* to produce and manage stateful nodes.
|
|
83
|
-
*
|
|
84
|
-
* @template {Tag} [TTag=Tag] - The type of the tag of the element.
|
|
85
|
-
*
|
|
86
|
-
* @example
|
|
87
|
-
* // specific element types
|
|
88
|
-
* let div: Element<"div">;
|
|
89
|
-
* let portal: Element<Portal>;
|
|
90
|
-
* let myEl: Element<MyComponent>;
|
|
91
|
-
*
|
|
92
|
-
* // general element types
|
|
93
|
-
* let host: Element<string | symbol>;
|
|
94
|
-
* let component: Element<Component>;
|
|
95
|
-
*
|
|
96
|
-
* Typically, you use a helper function like createElement to create elements
|
|
97
|
-
* rather than instatiating this class directly.
|
|
98
|
-
*/
|
|
99
|
-
class Element {
|
|
100
|
-
constructor(tag, props, key, ref, static_) {
|
|
101
|
-
this.tag = tag;
|
|
102
|
-
this.props = props;
|
|
103
|
-
this.key = key;
|
|
104
|
-
this.ref = ref;
|
|
105
|
-
this.static_ = static_;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
// See Element interface
|
|
109
|
-
Element.prototype.$$typeof = ElementSymbol;
|
|
110
|
-
function isElement(value) {
|
|
111
|
-
return value != null && value.$$typeof === ElementSymbol;
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Creates an element with the specified tag, props and children.
|
|
115
|
-
*
|
|
116
|
-
* This function is usually used as a transpilation target for JSX transpilers,
|
|
117
|
-
* but it can also be called directly. It additionally extracts special props so
|
|
118
|
-
* they aren’t accessible to renderer methods or components, and assigns the
|
|
119
|
-
* children prop according to any additional arguments passed to the function.
|
|
120
|
-
*/
|
|
121
|
-
function createElement(tag, props, ...children) {
|
|
122
|
-
let key;
|
|
123
|
-
let ref;
|
|
124
|
-
let static_ = false;
|
|
125
|
-
const props1 = {};
|
|
126
|
-
if (props != null) {
|
|
127
|
-
for (const name in props) {
|
|
128
|
-
switch (name) {
|
|
129
|
-
case "crank-key":
|
|
130
|
-
case "c-key":
|
|
131
|
-
case "$key":
|
|
132
|
-
// We have to make sure we don’t assign null to the key because we
|
|
133
|
-
// don’t check for null keys in the diffing functions.
|
|
134
|
-
if (props[name] != null) {
|
|
135
|
-
key = props[name];
|
|
136
|
-
}
|
|
137
|
-
break;
|
|
138
|
-
case "crank-ref":
|
|
139
|
-
case "c-ref":
|
|
140
|
-
case "$ref":
|
|
141
|
-
if (typeof props[name] === "function") {
|
|
142
|
-
ref = props[name];
|
|
143
|
-
}
|
|
144
|
-
break;
|
|
145
|
-
case "crank-static":
|
|
146
|
-
case "c-static":
|
|
147
|
-
case "$static":
|
|
148
|
-
static_ = !!props[name];
|
|
149
|
-
break;
|
|
150
|
-
default:
|
|
151
|
-
props1[name] = props[name];
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
if (children.length > 1) {
|
|
156
|
-
props1.children = children;
|
|
157
|
-
}
|
|
158
|
-
else if (children.length === 1) {
|
|
159
|
-
props1.children = children[0];
|
|
160
|
-
}
|
|
161
|
-
return new Element(tag, props1, key, ref, static_);
|
|
162
|
-
}
|
|
163
|
-
/** Clones a given element, shallowly copying the props object. */
|
|
164
|
-
function cloneElement(el) {
|
|
165
|
-
if (!isElement(el)) {
|
|
166
|
-
throw new TypeError("Cannot clone non-element");
|
|
167
|
-
}
|
|
168
|
-
return new Element(el.tag, { ...el.props }, el.key, el.ref);
|
|
169
|
-
}
|
|
170
|
-
function narrow(value) {
|
|
171
|
-
if (typeof value === "boolean" || value == null) {
|
|
172
|
-
return undefined;
|
|
173
|
-
}
|
|
174
|
-
else if (typeof value === "string" || isElement(value)) {
|
|
175
|
-
return value;
|
|
176
|
-
}
|
|
177
|
-
else if (typeof value[Symbol.iterator] === "function") {
|
|
178
|
-
return createElement(Fragment, null, value);
|
|
179
|
-
}
|
|
180
|
-
return value.toString();
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
* Takes an array of element values and normalizes the output as an array of
|
|
184
|
-
* nodes and strings.
|
|
185
|
-
*
|
|
186
|
-
* @returns Normalized array of nodes and/or strings.
|
|
187
|
-
*
|
|
188
|
-
* Normalize will flatten only one level of nested arrays, because it is
|
|
189
|
-
* designed to be called once at each level of the tree. It will also
|
|
190
|
-
* concatenate adjacent strings and remove all undefined values.
|
|
191
|
-
*/
|
|
192
|
-
function normalize(values) {
|
|
193
|
-
const result = [];
|
|
194
|
-
let buffer;
|
|
195
|
-
for (let i = 0; i < values.length; i++) {
|
|
196
|
-
const value = values[i];
|
|
197
|
-
if (!value) ;
|
|
198
|
-
else if (typeof value === "string") {
|
|
199
|
-
buffer = (buffer || "") + value;
|
|
200
|
-
}
|
|
201
|
-
else if (!Array.isArray(value)) {
|
|
202
|
-
if (buffer) {
|
|
203
|
-
result.push(buffer);
|
|
204
|
-
buffer = undefined;
|
|
205
|
-
}
|
|
206
|
-
result.push(value);
|
|
207
|
-
}
|
|
208
|
-
else {
|
|
209
|
-
// We could use recursion here but it’s just easier to do it inline.
|
|
210
|
-
for (let j = 0; j < value.length; j++) {
|
|
211
|
-
const value1 = value[j];
|
|
212
|
-
if (!value1) ;
|
|
213
|
-
else if (typeof value1 === "string") {
|
|
214
|
-
buffer = (buffer || "") + value1;
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
if (buffer) {
|
|
218
|
-
result.push(buffer);
|
|
219
|
-
buffer = undefined;
|
|
220
|
-
}
|
|
221
|
-
result.push(value1);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
if (buffer) {
|
|
227
|
-
result.push(buffer);
|
|
228
|
-
}
|
|
229
|
-
return result;
|
|
230
|
-
}
|
|
231
|
-
/**
|
|
232
|
-
* @internal
|
|
233
|
-
* The internal nodes which are cached and diffed against new elements when
|
|
234
|
-
* rendering element trees.
|
|
235
|
-
*/
|
|
236
|
-
class Retainer {
|
|
237
|
-
constructor(el) {
|
|
238
|
-
this.el = el;
|
|
239
|
-
this.ctx = undefined;
|
|
240
|
-
this.children = undefined;
|
|
241
|
-
this.value = undefined;
|
|
242
|
-
this.cachedChildValues = undefined;
|
|
243
|
-
this.fallbackValue = undefined;
|
|
244
|
-
this.inflightValue = undefined;
|
|
245
|
-
this.onNextValues = undefined;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
/**
|
|
249
|
-
* Finds the value of the element according to its type.
|
|
250
|
-
*
|
|
251
|
-
* @returns The value of the element.
|
|
252
|
-
*/
|
|
253
|
-
function getValue(ret) {
|
|
254
|
-
if (typeof ret.fallbackValue !== "undefined") {
|
|
255
|
-
return typeof ret.fallbackValue === "object"
|
|
256
|
-
? getValue(ret.fallbackValue)
|
|
257
|
-
: ret.fallbackValue;
|
|
258
|
-
}
|
|
259
|
-
else if (ret.el.tag === Portal) {
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
else if (typeof ret.el.tag !== "function" && ret.el.tag !== Fragment) {
|
|
263
|
-
return ret.value;
|
|
264
|
-
}
|
|
265
|
-
return unwrap(getChildValues(ret));
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Walks an element’s children to find its child values.
|
|
269
|
-
*
|
|
270
|
-
* @returns A normalized array of nodes and strings.
|
|
271
|
-
*/
|
|
272
|
-
function getChildValues(ret) {
|
|
273
|
-
if (ret.cachedChildValues) {
|
|
274
|
-
return wrap(ret.cachedChildValues);
|
|
275
|
-
}
|
|
276
|
-
const values = [];
|
|
277
|
-
const children = wrap(ret.children);
|
|
278
|
-
for (let i = 0; i < children.length; i++) {
|
|
279
|
-
const child = children[i];
|
|
280
|
-
if (child) {
|
|
281
|
-
values.push(typeof child === "string" ? child : getValue(child));
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
const values1 = normalize(values);
|
|
285
|
-
const tag = ret.el.tag;
|
|
286
|
-
if (typeof tag === "function" || (tag !== Fragment && tag !== Raw)) {
|
|
287
|
-
ret.cachedChildValues = unwrap(values1);
|
|
288
|
-
}
|
|
289
|
-
return values1;
|
|
290
|
-
}
|
|
291
|
-
const defaultRendererImpl = {
|
|
292
|
-
create() {
|
|
293
|
-
throw new Error("Not implemented");
|
|
294
|
-
},
|
|
295
|
-
scope: IDENTITY,
|
|
296
|
-
read: IDENTITY,
|
|
297
|
-
escape: IDENTITY,
|
|
298
|
-
parse: IDENTITY,
|
|
299
|
-
patch: NOOP,
|
|
300
|
-
arrange: NOOP,
|
|
301
|
-
dispose: NOOP,
|
|
302
|
-
flush: NOOP,
|
|
303
|
-
};
|
|
304
|
-
const _RendererImpl = Symbol.for("crank.RendererImpl");
|
|
305
|
-
/**
|
|
306
|
-
* An abstract class which is subclassed to render to different target
|
|
307
|
-
* environments. This class is responsible for kicking off the rendering
|
|
308
|
-
* process and caching previous trees by root.
|
|
309
|
-
*
|
|
310
|
-
* @template TNode - The type of the node for a rendering environment.
|
|
311
|
-
* @template TScope - Data which is passed down the tree.
|
|
312
|
-
* @template TRoot - The type of the root for a rendering environment.
|
|
313
|
-
* @template TResult - The type of exposed values.
|
|
314
|
-
*/
|
|
315
|
-
class Renderer {
|
|
316
|
-
constructor(impl) {
|
|
317
|
-
this.cache = new WeakMap();
|
|
318
|
-
this[_RendererImpl] = {
|
|
319
|
-
...defaultRendererImpl,
|
|
320
|
-
...impl,
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
|
-
/**
|
|
324
|
-
* Renders an element tree into a specific root.
|
|
325
|
-
*
|
|
326
|
-
* @param children - An element tree. You can render null with a previously
|
|
327
|
-
* used root to delete the previously rendered element tree from the cache.
|
|
328
|
-
* @param root - The node to be rendered into. The renderer will cache
|
|
329
|
-
* element trees per root.
|
|
330
|
-
* @param bridge - An optional context that will be the ancestor context of all
|
|
331
|
-
* elements in the tree. Useful for connecting different renderers so that
|
|
332
|
-
* events/provisions properly propagate. The context for a given root must be
|
|
333
|
-
* the same or an error will be thrown.
|
|
334
|
-
*
|
|
335
|
-
* @returns The result of rendering the children, or a possible promise of
|
|
336
|
-
* the result if the element tree renders asynchronously.
|
|
337
|
-
*/
|
|
338
|
-
render(children, root, bridge) {
|
|
339
|
-
let ret;
|
|
340
|
-
const ctx = bridge && bridge[_ContextImpl];
|
|
341
|
-
if (typeof root === "object" && root !== null) {
|
|
342
|
-
ret = this.cache.get(root);
|
|
343
|
-
}
|
|
344
|
-
let oldProps;
|
|
345
|
-
if (ret === undefined) {
|
|
346
|
-
ret = new Retainer(createElement(Portal, { children, root }));
|
|
347
|
-
ret.value = root;
|
|
348
|
-
ret.ctx = ctx;
|
|
349
|
-
if (typeof root === "object" && root !== null && children != null) {
|
|
350
|
-
this.cache.set(root, ret);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
else if (ret.ctx !== ctx) {
|
|
354
|
-
throw new Error("Context mismatch");
|
|
355
|
-
}
|
|
356
|
-
else {
|
|
357
|
-
oldProps = ret.el.props;
|
|
358
|
-
ret.el = createElement(Portal, { children, root });
|
|
359
|
-
if (typeof root === "object" && root !== null && children == null) {
|
|
360
|
-
this.cache.delete(root);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
const impl = this[_RendererImpl];
|
|
364
|
-
const childValues = diffChildren(impl, root, ret, ctx, impl.scope(undefined, Portal, ret.el.props), ret, children);
|
|
365
|
-
// We return the child values of the portal because portal elements
|
|
366
|
-
// themselves have no readable value.
|
|
367
|
-
if (isPromiseLike(childValues)) {
|
|
368
|
-
return childValues.then((childValues) => commitRootRender(impl, root, ctx, ret, childValues, oldProps));
|
|
369
|
-
}
|
|
370
|
-
return commitRootRender(impl, root, ctx, ret, childValues, oldProps);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
/*** PRIVATE RENDERER FUNCTIONS ***/
|
|
374
|
-
function commitRootRender(renderer, root, ctx, ret, childValues, oldProps) {
|
|
375
|
-
// element is a host or portal element
|
|
376
|
-
if (root != null) {
|
|
377
|
-
renderer.arrange(Portal, root, ret.el.props, childValues, oldProps, wrap(ret.cachedChildValues));
|
|
378
|
-
flush(renderer, root);
|
|
379
|
-
}
|
|
380
|
-
ret.cachedChildValues = unwrap(childValues);
|
|
381
|
-
if (root == null) {
|
|
382
|
-
unmount(renderer, ret, ctx, ret);
|
|
383
|
-
}
|
|
384
|
-
return renderer.read(ret.cachedChildValues);
|
|
385
|
-
}
|
|
386
|
-
function diffChildren(renderer, root, host, ctx, scope, parent, children) {
|
|
387
|
-
const oldRetained = wrap(parent.children);
|
|
388
|
-
const newRetained = [];
|
|
389
|
-
const newChildren = arrayify(children);
|
|
390
|
-
const values = [];
|
|
391
|
-
let graveyard;
|
|
392
|
-
let childrenByKey;
|
|
393
|
-
let seenKeys;
|
|
394
|
-
let isAsync = false;
|
|
395
|
-
let oi = 0, oldLength = oldRetained.length;
|
|
396
|
-
for (let ni = 0, newLength = newChildren.length; ni < newLength; ni++) {
|
|
397
|
-
// We make sure we don’t access indices out of bounds to prevent
|
|
398
|
-
// deoptimizations.
|
|
399
|
-
let ret = oi >= oldLength ? undefined : oldRetained[oi];
|
|
400
|
-
let child = narrow(newChildren[ni]);
|
|
401
|
-
{
|
|
402
|
-
// Aligning new children with old retainers
|
|
403
|
-
let oldKey = typeof ret === "object" ? ret.el.key : undefined;
|
|
404
|
-
let newKey = typeof child === "object" ? child.key : undefined;
|
|
405
|
-
if (newKey !== undefined && seenKeys && seenKeys.has(newKey)) {
|
|
406
|
-
console.error("Duplicate key", newKey);
|
|
407
|
-
newKey = undefined;
|
|
408
|
-
}
|
|
409
|
-
if (oldKey === newKey) {
|
|
410
|
-
if (childrenByKey !== undefined && newKey !== undefined) {
|
|
411
|
-
childrenByKey.delete(newKey);
|
|
412
|
-
}
|
|
413
|
-
oi++;
|
|
414
|
-
}
|
|
415
|
-
else {
|
|
416
|
-
childrenByKey = childrenByKey || createChildrenByKey(oldRetained, oi);
|
|
417
|
-
if (newKey === undefined) {
|
|
418
|
-
while (ret !== undefined && oldKey !== undefined) {
|
|
419
|
-
oi++;
|
|
420
|
-
ret = oldRetained[oi];
|
|
421
|
-
oldKey = typeof ret === "object" ? ret.el.key : undefined;
|
|
422
|
-
}
|
|
423
|
-
oi++;
|
|
424
|
-
}
|
|
425
|
-
else {
|
|
426
|
-
ret = childrenByKey.get(newKey);
|
|
427
|
-
if (ret !== undefined) {
|
|
428
|
-
childrenByKey.delete(newKey);
|
|
429
|
-
}
|
|
430
|
-
(seenKeys = seenKeys || new Set()).add(newKey);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
// Updating
|
|
435
|
-
let value;
|
|
436
|
-
if (typeof child === "object") {
|
|
437
|
-
if (typeof ret === "object" && child.static_) {
|
|
438
|
-
ret.el = child;
|
|
439
|
-
value = getInflightValue(ret);
|
|
440
|
-
}
|
|
441
|
-
else if (child.tag === Copy) {
|
|
442
|
-
value = getInflightValue(ret);
|
|
443
|
-
}
|
|
444
|
-
else {
|
|
445
|
-
let oldProps;
|
|
446
|
-
if (typeof ret === "object" && ret.el.tag === child.tag) {
|
|
447
|
-
oldProps = ret.el.props;
|
|
448
|
-
ret.el = child;
|
|
449
|
-
}
|
|
450
|
-
else {
|
|
451
|
-
if (typeof ret === "object") {
|
|
452
|
-
(graveyard = graveyard || []).push(ret);
|
|
453
|
-
}
|
|
454
|
-
const fallback = ret;
|
|
455
|
-
ret = new Retainer(child);
|
|
456
|
-
ret.fallbackValue = fallback;
|
|
457
|
-
}
|
|
458
|
-
if (child.tag === Raw) {
|
|
459
|
-
value = updateRaw(renderer, ret, scope, oldProps);
|
|
460
|
-
}
|
|
461
|
-
else if (child.tag === Fragment) {
|
|
462
|
-
value = updateFragment(renderer, root, host, ctx, scope, ret);
|
|
463
|
-
}
|
|
464
|
-
else if (typeof child.tag === "function") {
|
|
465
|
-
value = updateComponent(renderer, root, host, ctx, scope, ret, oldProps);
|
|
466
|
-
}
|
|
467
|
-
else {
|
|
468
|
-
value = updateHost(renderer, root, ctx, scope, ret, oldProps);
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
const ref = child.ref;
|
|
472
|
-
if (isPromiseLike(value)) {
|
|
473
|
-
isAsync = true;
|
|
474
|
-
if (typeof ref === "function") {
|
|
475
|
-
value = value.then((value) => {
|
|
476
|
-
ref(renderer.read(value));
|
|
477
|
-
return value;
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
else if (typeof ref === "function") {
|
|
482
|
-
ref(renderer.read(value));
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
else {
|
|
486
|
-
// child is a string or undefined
|
|
487
|
-
if (typeof ret === "object") {
|
|
488
|
-
(graveyard = graveyard || []).push(ret);
|
|
489
|
-
}
|
|
490
|
-
if (typeof child === "string") {
|
|
491
|
-
value = ret = renderer.escape(child, scope);
|
|
492
|
-
}
|
|
493
|
-
else {
|
|
494
|
-
ret = undefined;
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
values[ni] = value;
|
|
498
|
-
newRetained[ni] = ret;
|
|
499
|
-
}
|
|
500
|
-
// cleanup remaining retainers
|
|
501
|
-
for (; oi < oldLength; oi++) {
|
|
502
|
-
const ret = oldRetained[oi];
|
|
503
|
-
if (typeof ret === "object" && typeof ret.el.key === "undefined") {
|
|
504
|
-
(graveyard = graveyard || []).push(ret);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
if (childrenByKey !== undefined && childrenByKey.size > 0) {
|
|
508
|
-
(graveyard = graveyard || []).push(...childrenByKey.values());
|
|
509
|
-
}
|
|
510
|
-
parent.children = unwrap(newRetained);
|
|
511
|
-
if (isAsync) {
|
|
512
|
-
let childValues1 = Promise.all(values).finally(() => {
|
|
513
|
-
if (graveyard) {
|
|
514
|
-
for (let i = 0; i < graveyard.length; i++) {
|
|
515
|
-
unmount(renderer, host, ctx, graveyard[i]);
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
});
|
|
519
|
-
let onChildValues;
|
|
520
|
-
childValues1 = Promise.race([
|
|
521
|
-
childValues1,
|
|
522
|
-
new Promise((resolve) => (onChildValues = resolve)),
|
|
523
|
-
]);
|
|
524
|
-
if (parent.onNextValues) {
|
|
525
|
-
parent.onNextValues(childValues1);
|
|
526
|
-
}
|
|
527
|
-
parent.onNextValues = onChildValues;
|
|
528
|
-
return childValues1.then((childValues) => {
|
|
529
|
-
parent.inflightValue = parent.fallbackValue = undefined;
|
|
530
|
-
return normalize(childValues);
|
|
531
|
-
});
|
|
532
|
-
}
|
|
533
|
-
else {
|
|
534
|
-
if (graveyard) {
|
|
535
|
-
for (let i = 0; i < graveyard.length; i++) {
|
|
536
|
-
unmount(renderer, host, ctx, graveyard[i]);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
if (parent.onNextValues) {
|
|
540
|
-
parent.onNextValues(values);
|
|
541
|
-
parent.onNextValues = undefined;
|
|
542
|
-
}
|
|
543
|
-
parent.inflightValue = parent.fallbackValue = undefined;
|
|
544
|
-
// We can assert there are no promises in the array because isAsync is false
|
|
545
|
-
return normalize(values);
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
function createChildrenByKey(children, offset) {
|
|
549
|
-
const childrenByKey = new Map();
|
|
550
|
-
for (let i = offset; i < children.length; i++) {
|
|
551
|
-
const child = children[i];
|
|
552
|
-
if (typeof child === "object" && typeof child.el.key !== "undefined") {
|
|
553
|
-
childrenByKey.set(child.el.key, child);
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
return childrenByKey;
|
|
557
|
-
}
|
|
558
|
-
function getInflightValue(child) {
|
|
559
|
-
if (typeof child !== "object") {
|
|
560
|
-
return child;
|
|
561
|
-
}
|
|
562
|
-
const ctx = typeof child.el.tag === "function" ? child.ctx : undefined;
|
|
563
|
-
if (ctx && ctx.f & IsUpdating && ctx.inflightValue) {
|
|
564
|
-
return ctx.inflightValue;
|
|
565
|
-
}
|
|
566
|
-
else if (child.inflightValue) {
|
|
567
|
-
return child.inflightValue;
|
|
568
|
-
}
|
|
569
|
-
return getValue(child);
|
|
570
|
-
}
|
|
571
|
-
function updateRaw(renderer, ret, scope, oldProps) {
|
|
572
|
-
const props = ret.el.props;
|
|
573
|
-
if (typeof props.value === "string") {
|
|
574
|
-
if (!oldProps || oldProps.value !== props.value) {
|
|
575
|
-
ret.value = renderer.parse(props.value, scope);
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
else {
|
|
579
|
-
ret.value = props.value;
|
|
580
|
-
}
|
|
581
|
-
return ret.value;
|
|
582
|
-
}
|
|
583
|
-
function updateFragment(renderer, root, host, ctx, scope, ret) {
|
|
584
|
-
const childValues = diffChildren(renderer, root, host, ctx, scope, ret, ret.el.props.children);
|
|
585
|
-
if (isPromiseLike(childValues)) {
|
|
586
|
-
ret.inflightValue = childValues.then((childValues) => unwrap(childValues));
|
|
587
|
-
return ret.inflightValue;
|
|
588
|
-
}
|
|
589
|
-
return unwrap(childValues);
|
|
590
|
-
}
|
|
591
|
-
function updateHost(renderer, root, ctx, scope, ret, oldProps) {
|
|
592
|
-
const el = ret.el;
|
|
593
|
-
const tag = el.tag;
|
|
594
|
-
if (el.tag === Portal) {
|
|
595
|
-
root = ret.value = el.props.root;
|
|
596
|
-
}
|
|
597
|
-
else if (!oldProps) {
|
|
598
|
-
// We use the truthiness of oldProps to determine if this the first render.
|
|
599
|
-
ret.value = renderer.create(tag, el.props, scope);
|
|
600
|
-
}
|
|
601
|
-
scope = renderer.scope(scope, tag, el.props);
|
|
602
|
-
const childValues = diffChildren(renderer, root, ret, ctx, scope, ret, ret.el.props.children);
|
|
603
|
-
if (isPromiseLike(childValues)) {
|
|
604
|
-
ret.inflightValue = childValues.then((childValues) => commitHost(renderer, scope, ret, childValues, oldProps));
|
|
605
|
-
return ret.inflightValue;
|
|
606
|
-
}
|
|
607
|
-
return commitHost(renderer, scope, ret, childValues, oldProps);
|
|
608
|
-
}
|
|
609
|
-
function commitHost(renderer, scope, ret, childValues, oldProps) {
|
|
610
|
-
const tag = ret.el.tag;
|
|
611
|
-
const value = ret.value;
|
|
612
|
-
let props = ret.el.props;
|
|
613
|
-
let copied;
|
|
614
|
-
if (tag !== Portal) {
|
|
615
|
-
for (const propName in { ...oldProps, ...props }) {
|
|
616
|
-
const propValue = props[propName];
|
|
617
|
-
if (propValue === Copy) {
|
|
618
|
-
(copied = copied || new Set()).add(propName);
|
|
619
|
-
}
|
|
620
|
-
else if (propName !== "children") {
|
|
621
|
-
renderer.patch(tag, value, propName, propValue, oldProps && oldProps[propName], scope);
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
if (copied) {
|
|
626
|
-
props = { ...ret.el.props };
|
|
627
|
-
for (const name of copied) {
|
|
628
|
-
props[name] = oldProps && oldProps[name];
|
|
629
|
-
}
|
|
630
|
-
ret.el = new Element(tag, props, ret.el.key, ret.el.ref);
|
|
631
|
-
}
|
|
632
|
-
renderer.arrange(tag, value, props, childValues, oldProps, wrap(ret.cachedChildValues));
|
|
633
|
-
ret.cachedChildValues = unwrap(childValues);
|
|
634
|
-
if (tag === Portal) {
|
|
635
|
-
flush(renderer, ret.value);
|
|
636
|
-
return;
|
|
637
|
-
}
|
|
638
|
-
return value;
|
|
639
|
-
}
|
|
640
|
-
function flush(renderer, root, initiator) {
|
|
641
|
-
renderer.flush(root);
|
|
642
|
-
if (typeof root !== "object" || root === null) {
|
|
643
|
-
return;
|
|
644
|
-
}
|
|
645
|
-
const flushMap = flushMaps.get(root);
|
|
646
|
-
if (flushMap) {
|
|
647
|
-
if (initiator) {
|
|
648
|
-
const flushMap1 = new Map();
|
|
649
|
-
for (let [ctx, callbacks] of flushMap) {
|
|
650
|
-
if (!ctxContains(initiator, ctx)) {
|
|
651
|
-
flushMap.delete(ctx);
|
|
652
|
-
flushMap1.set(ctx, callbacks);
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
if (flushMap1.size) {
|
|
656
|
-
flushMaps.set(root, flushMap1);
|
|
657
|
-
}
|
|
658
|
-
else {
|
|
659
|
-
flushMaps.delete(root);
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
else {
|
|
663
|
-
flushMaps.delete(root);
|
|
664
|
-
}
|
|
665
|
-
for (const [ctx, callbacks] of flushMap) {
|
|
666
|
-
const value = renderer.read(getValue(ctx.ret));
|
|
667
|
-
for (const callback of callbacks) {
|
|
668
|
-
callback(value);
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
function unmount(renderer, host, ctx, ret) {
|
|
674
|
-
if (typeof ret.el.tag === "function") {
|
|
675
|
-
ctx = ret.ctx;
|
|
676
|
-
unmountComponent(ctx);
|
|
677
|
-
}
|
|
678
|
-
else if (ret.el.tag === Portal) {
|
|
679
|
-
host = ret;
|
|
680
|
-
renderer.arrange(Portal, host.value, host.el.props, [], host.el.props, wrap(host.cachedChildValues));
|
|
681
|
-
flush(renderer, host.value);
|
|
682
|
-
}
|
|
683
|
-
else if (ret.el.tag !== Fragment) {
|
|
684
|
-
if (isEventTarget(ret.value)) {
|
|
685
|
-
const records = getListenerRecords(ctx, host);
|
|
686
|
-
for (let i = 0; i < records.length; i++) {
|
|
687
|
-
const record = records[i];
|
|
688
|
-
ret.value.removeEventListener(record.type, record.callback, record.options);
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
renderer.dispose(ret.el.tag, ret.value, ret.el.props);
|
|
692
|
-
host = ret;
|
|
693
|
-
}
|
|
694
|
-
const children = wrap(ret.children);
|
|
695
|
-
for (let i = 0; i < children.length; i++) {
|
|
696
|
-
const child = children[i];
|
|
697
|
-
if (typeof child === "object") {
|
|
698
|
-
unmount(renderer, host, ctx, child);
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
/*** CONTEXT FLAGS ***/
|
|
703
|
-
/**
|
|
704
|
-
* A flag which is true when the component is initialized or updated by an
|
|
705
|
-
* ancestor component or the root render call.
|
|
706
|
-
*
|
|
707
|
-
* Used to determine things like whether the nearest host ancestor needs to be
|
|
708
|
-
* rearranged.
|
|
709
|
-
*/
|
|
710
|
-
const IsUpdating = 1 << 0;
|
|
711
|
-
/**
|
|
712
|
-
* A flag which is true when the component is synchronously executing.
|
|
713
|
-
*
|
|
714
|
-
* Used to guard against components triggering stack overflow or generator error.
|
|
715
|
-
*/
|
|
716
|
-
const IsSyncExecuting = 1 << 1;
|
|
717
|
-
/**
|
|
718
|
-
* A flag which is true when the component is in the render loop.
|
|
719
|
-
*/
|
|
720
|
-
const IsInRenderLoop = 1 << 2;
|
|
721
|
-
/**
|
|
722
|
-
* A flag which is true when the component starts the render loop but has not
|
|
723
|
-
* yielded yet.
|
|
724
|
-
*
|
|
725
|
-
* Used to make sure that components yield at least once per loop.
|
|
726
|
-
*/
|
|
727
|
-
const NeedsToYield = 1 << 3;
|
|
728
|
-
/**
|
|
729
|
-
* A flag used by async generator components in conjunction with the
|
|
730
|
-
* onAvailable callback to mark whether new props can be pulled via the context
|
|
731
|
-
* async iterator. See the Symbol.asyncIterator method and the
|
|
732
|
-
* resumeCtxIterator function.
|
|
733
|
-
*/
|
|
734
|
-
const PropsAvailable = 1 << 4;
|
|
735
|
-
/**
|
|
736
|
-
* A flag which is set when a component errors.
|
|
737
|
-
*
|
|
738
|
-
* NOTE: This is mainly used to prevent some false positives in component
|
|
739
|
-
* yields or returns undefined warnings. The reason we’re using this versus
|
|
740
|
-
* IsUnmounted is a very troubling test (cascades sync generator parent and
|
|
741
|
-
* sync generator child) where synchronous code causes a stack overflow error
|
|
742
|
-
* in a non-deterministic way. Deeply disturbing stuff.
|
|
743
|
-
*/
|
|
744
|
-
const IsErrored = 1 << 6;
|
|
745
|
-
/**
|
|
746
|
-
* A flag which is set when the component is unmounted. Unmounted components
|
|
747
|
-
* are no longer in the element tree and cannot refresh or rerender.
|
|
748
|
-
*/
|
|
749
|
-
const IsUnmounted = 1 << 7;
|
|
750
|
-
/**
|
|
751
|
-
* A flag which indicates that the component is a sync generator component.
|
|
752
|
-
*/
|
|
753
|
-
const IsSyncGen = 1 << 8;
|
|
754
|
-
/**
|
|
755
|
-
* A flag which indicates that the component is an async generator component.
|
|
756
|
-
*/
|
|
757
|
-
const IsAsyncGen = 1 << 9;
|
|
758
|
-
/**
|
|
759
|
-
* A flag which is set while schedule callbacks are called.
|
|
760
|
-
*/
|
|
761
|
-
const IsScheduling = 1 << 10;
|
|
762
|
-
/**
|
|
763
|
-
* A flag which is set when a schedule callback calls refresh.
|
|
764
|
-
*/
|
|
765
|
-
const IsSchedulingRefresh = 1 << 11;
|
|
766
|
-
const provisionMaps = new WeakMap();
|
|
767
|
-
const scheduleMap = new WeakMap();
|
|
768
|
-
const cleanupMap = new WeakMap();
|
|
769
|
-
// keys are roots
|
|
770
|
-
const flushMaps = new WeakMap();
|
|
771
|
-
/**
|
|
772
|
-
* @internal
|
|
773
|
-
* The internal class which holds context data.
|
|
774
|
-
*/
|
|
775
|
-
class ContextImpl {
|
|
776
|
-
constructor(renderer, root, host, parent, scope, ret) {
|
|
777
|
-
this.f = 0;
|
|
778
|
-
this.owner = new Context(this);
|
|
779
|
-
this.renderer = renderer;
|
|
780
|
-
this.root = root;
|
|
781
|
-
this.host = host;
|
|
782
|
-
this.parent = parent;
|
|
783
|
-
this.scope = scope;
|
|
784
|
-
this.ret = ret;
|
|
785
|
-
this.iterator = undefined;
|
|
786
|
-
this.inflightBlock = undefined;
|
|
787
|
-
this.inflightValue = undefined;
|
|
788
|
-
this.enqueuedBlock = undefined;
|
|
789
|
-
this.enqueuedValue = undefined;
|
|
790
|
-
this.onProps = undefined;
|
|
791
|
-
this.onPropsRequested = undefined;
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
const _ContextImpl = Symbol.for("crank.ContextImpl");
|
|
795
|
-
/**
|
|
796
|
-
* A class which is instantiated and passed to every component as its this
|
|
797
|
-
* value. Contexts form a tree just like elements and all components in the
|
|
798
|
-
* element tree are connected via contexts. Components can use this tree to
|
|
799
|
-
* communicate data upwards via events and downwards via provisions.
|
|
800
|
-
*
|
|
801
|
-
* @template [TProps=*] - The expected shape of the props passed to the
|
|
802
|
-
* component. Used to strongly type the Context iterator methods.
|
|
803
|
-
* @template [TResult=*] - The readable element value type. It is used in
|
|
804
|
-
* places such as the return value of refresh and the argument passed to
|
|
805
|
-
* schedule and cleanup callbacks.
|
|
806
|
-
*/
|
|
807
|
-
class Context {
|
|
808
|
-
// TODO: If we could make the constructor function take a nicer value, it
|
|
809
|
-
// would be useful for testing purposes.
|
|
810
|
-
constructor(impl) {
|
|
811
|
-
this[_ContextImpl] = impl;
|
|
812
|
-
}
|
|
813
|
-
/**
|
|
814
|
-
* The current props of the associated element.
|
|
815
|
-
*
|
|
816
|
-
* Typically, you should read props either via the first parameter of the
|
|
817
|
-
* component or via the context iterator methods. This property is mainly for
|
|
818
|
-
* plugins or utilities which wrap contexts.
|
|
819
|
-
*/
|
|
820
|
-
get props() {
|
|
821
|
-
return this[_ContextImpl].ret.el.props;
|
|
822
|
-
}
|
|
823
|
-
// TODO: Should we rename this???
|
|
824
|
-
/**
|
|
825
|
-
* The current value of the associated element.
|
|
826
|
-
*
|
|
827
|
-
* Typically, you should read values via refs, generator yield expressions,
|
|
828
|
-
* or the refresh, schedule, cleanup, or flush methods. This property is
|
|
829
|
-
* mainly for plugins or utilities which wrap contexts.
|
|
830
|
-
*/
|
|
831
|
-
get value() {
|
|
832
|
-
return this[_ContextImpl].renderer.read(getValue(this[_ContextImpl].ret));
|
|
833
|
-
}
|
|
834
|
-
*[Symbol.iterator]() {
|
|
835
|
-
const ctx = this[_ContextImpl];
|
|
836
|
-
if (ctx.f & IsAsyncGen) {
|
|
837
|
-
throw new Error("Use for await…of in async generator components");
|
|
838
|
-
}
|
|
839
|
-
try {
|
|
840
|
-
ctx.f |= IsInRenderLoop;
|
|
841
|
-
while (!(ctx.f & IsUnmounted)) {
|
|
842
|
-
if (ctx.f & NeedsToYield) {
|
|
843
|
-
throw new Error("Context iterated twice without a yield");
|
|
844
|
-
}
|
|
845
|
-
else {
|
|
846
|
-
ctx.f |= NeedsToYield;
|
|
847
|
-
}
|
|
848
|
-
yield ctx.ret.el.props;
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
finally {
|
|
852
|
-
ctx.f &= ~IsInRenderLoop;
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
async *[Symbol.asyncIterator]() {
|
|
856
|
-
const ctx = this[_ContextImpl];
|
|
857
|
-
if (ctx.f & IsSyncGen) {
|
|
858
|
-
throw new Error("Use for…of in sync generator components");
|
|
859
|
-
}
|
|
860
|
-
try {
|
|
861
|
-
// await an empty promise to prevent the IsInRenderLoop flag from
|
|
862
|
-
// returning false positives in the case of async generator components
|
|
863
|
-
// which immediately enter the loop
|
|
864
|
-
ctx.f |= IsInRenderLoop;
|
|
865
|
-
while (!(ctx.f & IsUnmounted)) {
|
|
866
|
-
if (ctx.f & NeedsToYield) {
|
|
867
|
-
throw new Error("Context iterated twice without a yield");
|
|
868
|
-
}
|
|
869
|
-
else {
|
|
870
|
-
ctx.f |= NeedsToYield;
|
|
871
|
-
}
|
|
872
|
-
if (ctx.f & PropsAvailable) {
|
|
873
|
-
ctx.f &= ~PropsAvailable;
|
|
874
|
-
yield ctx.ret.el.props;
|
|
875
|
-
}
|
|
876
|
-
else {
|
|
877
|
-
const props = await new Promise((resolve) => (ctx.onProps = resolve));
|
|
878
|
-
if (ctx.f & IsUnmounted) {
|
|
879
|
-
break;
|
|
880
|
-
}
|
|
881
|
-
yield props;
|
|
882
|
-
}
|
|
883
|
-
if (ctx.onPropsRequested) {
|
|
884
|
-
ctx.onPropsRequested();
|
|
885
|
-
ctx.onPropsRequested = undefined;
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
finally {
|
|
890
|
-
ctx.f &= ~IsInRenderLoop;
|
|
891
|
-
if (ctx.onPropsRequested) {
|
|
892
|
-
ctx.onPropsRequested();
|
|
893
|
-
ctx.onPropsRequested = undefined;
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
/**
|
|
898
|
-
* Re-executes a component.
|
|
899
|
-
*
|
|
900
|
-
* @returns The rendered value of the component or a promise thereof if the
|
|
901
|
-
* component or its children execute asynchronously.
|
|
902
|
-
*
|
|
903
|
-
* The refresh method works a little differently for async generator
|
|
904
|
-
* components, in that it will resume the Context’s props async iterator
|
|
905
|
-
* rather than resuming execution. This is because async generator components
|
|
906
|
-
* are perpetually resumed independent of updates, and rely on the props
|
|
907
|
-
* async iterator to suspend.
|
|
908
|
-
*/
|
|
909
|
-
refresh() {
|
|
910
|
-
const ctx = this[_ContextImpl];
|
|
911
|
-
if (ctx.f & IsUnmounted) {
|
|
912
|
-
console.error("Component is unmounted");
|
|
913
|
-
return ctx.renderer.read(undefined);
|
|
914
|
-
}
|
|
915
|
-
else if (ctx.f & IsSyncExecuting) {
|
|
916
|
-
console.error("Component is already executing");
|
|
917
|
-
return this.value;
|
|
918
|
-
}
|
|
919
|
-
const value = enqueueComponentRun(ctx);
|
|
920
|
-
if (isPromiseLike(value)) {
|
|
921
|
-
return value.then((value) => ctx.renderer.read(value));
|
|
922
|
-
}
|
|
923
|
-
return ctx.renderer.read(value);
|
|
924
|
-
}
|
|
925
|
-
/**
|
|
926
|
-
* Registers a callback which fires when the component commits. Will only
|
|
927
|
-
* fire once per callback and update.
|
|
928
|
-
*/
|
|
929
|
-
schedule(callback) {
|
|
930
|
-
const ctx = this[_ContextImpl];
|
|
931
|
-
let callbacks = scheduleMap.get(ctx);
|
|
932
|
-
if (!callbacks) {
|
|
933
|
-
callbacks = new Set();
|
|
934
|
-
scheduleMap.set(ctx, callbacks);
|
|
935
|
-
}
|
|
936
|
-
callbacks.add(callback);
|
|
937
|
-
}
|
|
938
|
-
/**
|
|
939
|
-
* Registers a callback which fires when the component’s children are
|
|
940
|
-
* rendered into the root. Will only fire once per callback and render.
|
|
941
|
-
*/
|
|
942
|
-
flush(callback) {
|
|
943
|
-
const ctx = this[_ContextImpl];
|
|
944
|
-
if (typeof ctx.root !== "object" || ctx.root === null) {
|
|
945
|
-
return;
|
|
946
|
-
}
|
|
947
|
-
let flushMap = flushMaps.get(ctx.root);
|
|
948
|
-
if (!flushMap) {
|
|
949
|
-
flushMap = new Map();
|
|
950
|
-
flushMaps.set(ctx.root, flushMap);
|
|
951
|
-
}
|
|
952
|
-
let callbacks = flushMap.get(ctx);
|
|
953
|
-
if (!callbacks) {
|
|
954
|
-
callbacks = new Set();
|
|
955
|
-
flushMap.set(ctx, callbacks);
|
|
956
|
-
}
|
|
957
|
-
callbacks.add(callback);
|
|
958
|
-
}
|
|
959
|
-
/**
|
|
960
|
-
* Registers a callback which fires when the component unmounts. Will only
|
|
961
|
-
* fire once per callback.
|
|
962
|
-
*/
|
|
963
|
-
cleanup(callback) {
|
|
964
|
-
const ctx = this[_ContextImpl];
|
|
965
|
-
let callbacks = cleanupMap.get(ctx);
|
|
966
|
-
if (!callbacks) {
|
|
967
|
-
callbacks = new Set();
|
|
968
|
-
cleanupMap.set(ctx, callbacks);
|
|
969
|
-
}
|
|
970
|
-
callbacks.add(callback);
|
|
971
|
-
}
|
|
972
|
-
consume(key) {
|
|
973
|
-
for (let ctx = this[_ContextImpl].parent; ctx !== undefined; ctx = ctx.parent) {
|
|
974
|
-
const provisions = provisionMaps.get(ctx);
|
|
975
|
-
if (provisions && provisions.has(key)) {
|
|
976
|
-
return provisions.get(key);
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
provide(key, value) {
|
|
981
|
-
const ctx = this[_ContextImpl];
|
|
982
|
-
let provisions = provisionMaps.get(ctx);
|
|
983
|
-
if (!provisions) {
|
|
984
|
-
provisions = new Map();
|
|
985
|
-
provisionMaps.set(ctx, provisions);
|
|
986
|
-
}
|
|
987
|
-
provisions.set(key, value);
|
|
988
|
-
}
|
|
989
|
-
addEventListener(type, listener, options) {
|
|
990
|
-
const ctx = this[_ContextImpl];
|
|
991
|
-
let listeners;
|
|
992
|
-
if (!isListenerOrListenerObject(listener)) {
|
|
993
|
-
return;
|
|
994
|
-
}
|
|
995
|
-
else {
|
|
996
|
-
const listeners1 = listenersMap.get(ctx);
|
|
997
|
-
if (listeners1) {
|
|
998
|
-
listeners = listeners1;
|
|
999
|
-
}
|
|
1000
|
-
else {
|
|
1001
|
-
listeners = [];
|
|
1002
|
-
listenersMap.set(ctx, listeners);
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
options = normalizeListenerOptions(options);
|
|
1006
|
-
let callback;
|
|
1007
|
-
if (typeof listener === "object") {
|
|
1008
|
-
callback = () => listener.handleEvent.apply(listener, arguments);
|
|
1009
|
-
}
|
|
1010
|
-
else {
|
|
1011
|
-
callback = listener;
|
|
1012
|
-
}
|
|
1013
|
-
const record = { type, listener, callback, options };
|
|
1014
|
-
if (options.once) {
|
|
1015
|
-
record.callback = function () {
|
|
1016
|
-
const i = listeners.indexOf(record);
|
|
1017
|
-
if (i !== -1) {
|
|
1018
|
-
listeners.splice(i, 1);
|
|
1019
|
-
}
|
|
1020
|
-
return callback.apply(this, arguments);
|
|
1021
|
-
};
|
|
1022
|
-
}
|
|
1023
|
-
if (listeners.some((record1) => record.type === record1.type &&
|
|
1024
|
-
record.listener === record1.listener &&
|
|
1025
|
-
!record.options.capture === !record1.options.capture)) {
|
|
1026
|
-
return;
|
|
1027
|
-
}
|
|
1028
|
-
listeners.push(record);
|
|
1029
|
-
// TODO: is it possible to separate out the EventTarget delegation logic
|
|
1030
|
-
for (const value of getChildValues(ctx.ret)) {
|
|
1031
|
-
if (isEventTarget(value)) {
|
|
1032
|
-
value.addEventListener(record.type, record.callback, record.options);
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
removeEventListener(type, listener, options) {
|
|
1037
|
-
const ctx = this[_ContextImpl];
|
|
1038
|
-
const listeners = listenersMap.get(ctx);
|
|
1039
|
-
if (listeners == null || !isListenerOrListenerObject(listener)) {
|
|
1040
|
-
return;
|
|
1041
|
-
}
|
|
1042
|
-
const options1 = normalizeListenerOptions(options);
|
|
1043
|
-
const i = listeners.findIndex((record) => record.type === type &&
|
|
1044
|
-
record.listener === listener &&
|
|
1045
|
-
!record.options.capture === !options1.capture);
|
|
1046
|
-
if (i === -1) {
|
|
1047
|
-
return;
|
|
1048
|
-
}
|
|
1049
|
-
const record = listeners[i];
|
|
1050
|
-
listeners.splice(i, 1);
|
|
1051
|
-
// TODO: is it possible to separate out the EventTarget delegation logic
|
|
1052
|
-
for (const value of getChildValues(ctx.ret)) {
|
|
1053
|
-
if (isEventTarget(value)) {
|
|
1054
|
-
value.removeEventListener(record.type, record.callback, record.options);
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
dispatchEvent(ev) {
|
|
1059
|
-
const ctx = this[_ContextImpl];
|
|
1060
|
-
const path = [];
|
|
1061
|
-
for (let parent = ctx.parent; parent !== undefined; parent = parent.parent) {
|
|
1062
|
-
path.push(parent);
|
|
1063
|
-
}
|
|
1064
|
-
// We patch the stopImmediatePropagation method because ev.cancelBubble
|
|
1065
|
-
// only informs us if stopPropagation was called and there are no
|
|
1066
|
-
// properties which inform us if stopImmediatePropagation was called.
|
|
1067
|
-
let immediateCancelBubble = false;
|
|
1068
|
-
const stopImmediatePropagation = ev.stopImmediatePropagation;
|
|
1069
|
-
setEventProperty(ev, "stopImmediatePropagation", () => {
|
|
1070
|
-
immediateCancelBubble = true;
|
|
1071
|
-
return stopImmediatePropagation.call(ev);
|
|
1072
|
-
});
|
|
1073
|
-
setEventProperty(ev, "target", ctx.owner);
|
|
1074
|
-
// The only possible errors in this block are errors thrown by callbacks,
|
|
1075
|
-
// and dispatchEvent will only log these errors rather than throwing
|
|
1076
|
-
// them. Therefore, we place all code in a try block, log errors in the
|
|
1077
|
-
// catch block, and use an unsafe return statement in the finally block.
|
|
1078
|
-
//
|
|
1079
|
-
// Each early return within the try block returns true because while the
|
|
1080
|
-
// return value is overridden in the finally block, TypeScript
|
|
1081
|
-
// (justifiably) does not recognize the unsafe return statement.
|
|
1082
|
-
try {
|
|
1083
|
-
setEventProperty(ev, "eventPhase", CAPTURING_PHASE);
|
|
1084
|
-
for (let i = path.length - 1; i >= 0; i--) {
|
|
1085
|
-
const target = path[i];
|
|
1086
|
-
const listeners = listenersMap.get(target);
|
|
1087
|
-
if (listeners) {
|
|
1088
|
-
setEventProperty(ev, "currentTarget", target.owner);
|
|
1089
|
-
for (const record of listeners) {
|
|
1090
|
-
if (record.type === ev.type && record.options.capture) {
|
|
1091
|
-
try {
|
|
1092
|
-
record.callback.call(target.owner, ev);
|
|
1093
|
-
}
|
|
1094
|
-
catch (err) {
|
|
1095
|
-
console.error(err);
|
|
1096
|
-
}
|
|
1097
|
-
if (immediateCancelBubble) {
|
|
1098
|
-
return true;
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
if (ev.cancelBubble) {
|
|
1104
|
-
return true;
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
{
|
|
1108
|
-
setEventProperty(ev, "eventPhase", AT_TARGET);
|
|
1109
|
-
setEventProperty(ev, "currentTarget", ctx.owner);
|
|
1110
|
-
const propCallback = ctx.ret.el.props["on" + ev.type];
|
|
1111
|
-
if (propCallback != null) {
|
|
1112
|
-
propCallback(ev);
|
|
1113
|
-
if (immediateCancelBubble || ev.cancelBubble) {
|
|
1114
|
-
return true;
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
const listeners = listenersMap.get(ctx);
|
|
1118
|
-
if (listeners) {
|
|
1119
|
-
for (const record of listeners) {
|
|
1120
|
-
if (record.type === ev.type) {
|
|
1121
|
-
try {
|
|
1122
|
-
record.callback.call(ctx.owner, ev);
|
|
1123
|
-
}
|
|
1124
|
-
catch (err) {
|
|
1125
|
-
console.error(err);
|
|
1126
|
-
}
|
|
1127
|
-
if (immediateCancelBubble) {
|
|
1128
|
-
return true;
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
if (ev.cancelBubble) {
|
|
1133
|
-
return true;
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
if (ev.bubbles) {
|
|
1138
|
-
setEventProperty(ev, "eventPhase", BUBBLING_PHASE);
|
|
1139
|
-
for (let i = 0; i < path.length; i++) {
|
|
1140
|
-
const target = path[i];
|
|
1141
|
-
const listeners = listenersMap.get(target);
|
|
1142
|
-
if (listeners) {
|
|
1143
|
-
setEventProperty(ev, "currentTarget", target.owner);
|
|
1144
|
-
for (const record of listeners) {
|
|
1145
|
-
if (record.type === ev.type && !record.options.capture) {
|
|
1146
|
-
try {
|
|
1147
|
-
record.callback.call(target.owner, ev);
|
|
1148
|
-
}
|
|
1149
|
-
catch (err) {
|
|
1150
|
-
console.error(err);
|
|
1151
|
-
}
|
|
1152
|
-
if (immediateCancelBubble) {
|
|
1153
|
-
return true;
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
if (ev.cancelBubble) {
|
|
1159
|
-
return true;
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
finally {
|
|
1165
|
-
setEventProperty(ev, "eventPhase", NONE);
|
|
1166
|
-
setEventProperty(ev, "currentTarget", null);
|
|
1167
|
-
// eslint-disable-next-line no-unsafe-finally
|
|
1168
|
-
return !ev.defaultPrevented;
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
/*** PRIVATE CONTEXT FUNCTIONS ***/
|
|
1173
|
-
function ctxContains(parent, child) {
|
|
1174
|
-
for (let current = child; current !== undefined; current = current.parent) {
|
|
1175
|
-
if (current === parent) {
|
|
1176
|
-
return true;
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
return false;
|
|
1180
|
-
}
|
|
1181
|
-
function updateComponent(renderer, root, host, parent, scope, ret, oldProps) {
|
|
1182
|
-
let ctx;
|
|
1183
|
-
if (oldProps) {
|
|
1184
|
-
ctx = ret.ctx;
|
|
1185
|
-
if (ctx.f & IsSyncExecuting) {
|
|
1186
|
-
console.error("Component is already executing");
|
|
1187
|
-
return ret.cachedChildValues;
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
else {
|
|
1191
|
-
ctx = ret.ctx = new ContextImpl(renderer, root, host, parent, scope, ret);
|
|
1192
|
-
}
|
|
1193
|
-
ctx.f |= IsUpdating;
|
|
1194
|
-
return enqueueComponentRun(ctx);
|
|
1195
|
-
}
|
|
1196
|
-
function updateComponentChildren(ctx, children) {
|
|
1197
|
-
if (ctx.f & IsUnmounted) {
|
|
1198
|
-
return;
|
|
1199
|
-
}
|
|
1200
|
-
else if (ctx.f & IsErrored) {
|
|
1201
|
-
// This branch is necessary for some race conditions where this function is
|
|
1202
|
-
// called after iterator.throw() in async generator components.
|
|
1203
|
-
return;
|
|
1204
|
-
}
|
|
1205
|
-
else if (children === undefined) {
|
|
1206
|
-
console.error("A component has returned or yielded undefined. If this was intentional, return or yield null instead.");
|
|
1207
|
-
}
|
|
1208
|
-
let childValues;
|
|
1209
|
-
try {
|
|
1210
|
-
// TODO: WAT
|
|
1211
|
-
// We set the isExecuting flag in case a child component dispatches an event
|
|
1212
|
-
// which bubbles to this component and causes a synchronous refresh().
|
|
1213
|
-
ctx.f |= IsSyncExecuting;
|
|
1214
|
-
childValues = diffChildren(ctx.renderer, ctx.root, ctx.host, ctx, ctx.scope, ctx.ret, narrow(children));
|
|
1215
|
-
}
|
|
1216
|
-
finally {
|
|
1217
|
-
ctx.f &= ~IsSyncExecuting;
|
|
1218
|
-
}
|
|
1219
|
-
if (isPromiseLike(childValues)) {
|
|
1220
|
-
ctx.ret.inflightValue = childValues.then((childValues) => commitComponent(ctx, childValues));
|
|
1221
|
-
return ctx.ret.inflightValue;
|
|
1222
|
-
}
|
|
1223
|
-
return commitComponent(ctx, childValues);
|
|
1224
|
-
}
|
|
1225
|
-
function commitComponent(ctx, values) {
|
|
1226
|
-
if (ctx.f & IsUnmounted) {
|
|
1227
|
-
return;
|
|
1228
|
-
}
|
|
1229
|
-
const listeners = listenersMap.get(ctx);
|
|
1230
|
-
if (listeners && listeners.length) {
|
|
1231
|
-
for (let i = 0; i < values.length; i++) {
|
|
1232
|
-
const value = values[i];
|
|
1233
|
-
if (isEventTarget(value)) {
|
|
1234
|
-
for (let j = 0; j < listeners.length; j++) {
|
|
1235
|
-
const record = listeners[j];
|
|
1236
|
-
value.addEventListener(record.type, record.callback, record.options);
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
|
-
}
|
|
1241
|
-
const oldValues = wrap(ctx.ret.cachedChildValues);
|
|
1242
|
-
let value = (ctx.ret.cachedChildValues = unwrap(values));
|
|
1243
|
-
if (ctx.f & IsScheduling) {
|
|
1244
|
-
ctx.f |= IsSchedulingRefresh;
|
|
1245
|
-
}
|
|
1246
|
-
else if (!(ctx.f & IsUpdating)) {
|
|
1247
|
-
// If we’re not updating the component, which happens when components are
|
|
1248
|
-
// refreshed, or when async generator components iterate, we have to do a
|
|
1249
|
-
// little bit housekeeping when a component’s child values have changed.
|
|
1250
|
-
if (!arrayEqual(oldValues, values)) {
|
|
1251
|
-
const records = getListenerRecords(ctx.parent, ctx.host);
|
|
1252
|
-
if (records.length) {
|
|
1253
|
-
for (let i = 0; i < values.length; i++) {
|
|
1254
|
-
const value = values[i];
|
|
1255
|
-
if (isEventTarget(value)) {
|
|
1256
|
-
for (let j = 0; j < records.length; j++) {
|
|
1257
|
-
const record = records[j];
|
|
1258
|
-
value.addEventListener(record.type, record.callback, record.options);
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
}
|
|
1263
|
-
// rearranging the nearest ancestor host element
|
|
1264
|
-
const host = ctx.host;
|
|
1265
|
-
const oldHostValues = wrap(host.cachedChildValues);
|
|
1266
|
-
invalidate(ctx, host);
|
|
1267
|
-
const hostValues = getChildValues(host);
|
|
1268
|
-
ctx.renderer.arrange(host.el.tag, host.value, host.el.props, hostValues,
|
|
1269
|
-
// props and oldProps are the same because the host isn’t updated.
|
|
1270
|
-
host.el.props, oldHostValues);
|
|
1271
|
-
}
|
|
1272
|
-
flush(ctx.renderer, ctx.root, ctx);
|
|
1273
|
-
}
|
|
1274
|
-
const callbacks = scheduleMap.get(ctx);
|
|
1275
|
-
if (callbacks) {
|
|
1276
|
-
scheduleMap.delete(ctx);
|
|
1277
|
-
ctx.f |= IsScheduling;
|
|
1278
|
-
const value1 = ctx.renderer.read(value);
|
|
1279
|
-
for (const callback of callbacks) {
|
|
1280
|
-
callback(value1);
|
|
1281
|
-
}
|
|
1282
|
-
ctx.f &= ~IsScheduling;
|
|
1283
|
-
// Handles an edge case where refresh() is called during a schedule().
|
|
1284
|
-
if (ctx.f & IsSchedulingRefresh) {
|
|
1285
|
-
ctx.f &= ~IsSchedulingRefresh;
|
|
1286
|
-
value = getValue(ctx.ret);
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
|
-
ctx.f &= ~IsUpdating;
|
|
1290
|
-
return value;
|
|
1291
|
-
}
|
|
1292
|
-
function invalidate(ctx, host) {
|
|
1293
|
-
for (let parent = ctx.parent; parent !== undefined && parent.host === host; parent = parent.parent) {
|
|
1294
|
-
parent.ret.cachedChildValues = undefined;
|
|
1295
|
-
}
|
|
1296
|
-
host.cachedChildValues = undefined;
|
|
1297
|
-
}
|
|
1298
|
-
function arrayEqual(arr1, arr2) {
|
|
1299
|
-
if (arr1.length !== arr2.length) {
|
|
1300
|
-
return false;
|
|
1301
|
-
}
|
|
1302
|
-
for (let i = 0; i < arr1.length; i++) {
|
|
1303
|
-
const value1 = arr1[i];
|
|
1304
|
-
const value2 = arr2[i];
|
|
1305
|
-
if (value1 !== value2) {
|
|
1306
|
-
return false;
|
|
1307
|
-
}
|
|
1308
|
-
}
|
|
1309
|
-
return true;
|
|
1310
|
-
}
|
|
1311
|
-
/** Enqueues and executes the component associated with the context. */
|
|
1312
|
-
function enqueueComponentRun(ctx) {
|
|
1313
|
-
if (ctx.f & IsAsyncGen) {
|
|
1314
|
-
// This branch will only run for async generator components after the
|
|
1315
|
-
// initial render.
|
|
1316
|
-
//
|
|
1317
|
-
// Async generator components which are in the props loop can be in one of
|
|
1318
|
-
// three states:
|
|
1319
|
-
//
|
|
1320
|
-
// 1. propsAvailable flag is true: "available"
|
|
1321
|
-
//
|
|
1322
|
-
// The component is paused somewhere in the loop. When the component
|
|
1323
|
-
// reaches the bottom of the loop, it will run again with the next props.
|
|
1324
|
-
//
|
|
1325
|
-
// 2. onAvailable callback is defined: "suspended"
|
|
1326
|
-
//
|
|
1327
|
-
// The component has reached the bottom of the loop and is waiting for
|
|
1328
|
-
// new props.
|
|
1329
|
-
//
|
|
1330
|
-
// 3. neither 1 or 2: "Running"
|
|
1331
|
-
//
|
|
1332
|
-
// The component is paused somewhere in the loop. When the component
|
|
1333
|
-
// reaches the bottom of the loop, it will suspend.
|
|
1334
|
-
//
|
|
1335
|
-
// By definition, components will never be both available and suspended at
|
|
1336
|
-
// the same time.
|
|
1337
|
-
//
|
|
1338
|
-
// If the component is at the loop bottom, this means that the next value
|
|
1339
|
-
// produced by the component will have the most up to date props, so we can
|
|
1340
|
-
// simply return the current inflight value. Otherwise, we have to wait for
|
|
1341
|
-
// the bottom of the loop before returning the inflight value.
|
|
1342
|
-
const isAtLoopbottom = ctx.f & IsInRenderLoop && !ctx.onProps;
|
|
1343
|
-
resumePropsIterator(ctx);
|
|
1344
|
-
if (isAtLoopbottom) {
|
|
1345
|
-
if (ctx.inflightBlock == null) {
|
|
1346
|
-
ctx.inflightBlock = new Promise((resolve) => (ctx.onPropsRequested = resolve));
|
|
1347
|
-
}
|
|
1348
|
-
return ctx.inflightBlock.then(() => {
|
|
1349
|
-
ctx.inflightBlock = undefined;
|
|
1350
|
-
return ctx.inflightValue;
|
|
1351
|
-
});
|
|
1352
|
-
}
|
|
1353
|
-
return ctx.inflightValue;
|
|
1354
|
-
}
|
|
1355
|
-
else if (!ctx.inflightBlock) {
|
|
1356
|
-
try {
|
|
1357
|
-
const [block, value] = runComponent(ctx);
|
|
1358
|
-
if (block) {
|
|
1359
|
-
ctx.inflightBlock = block
|
|
1360
|
-
// TODO: there is some fuckery going on here related to async
|
|
1361
|
-
// generator components resuming when they’re meant to be returned.
|
|
1362
|
-
.then((v) => v)
|
|
1363
|
-
.finally(() => advanceComponent(ctx));
|
|
1364
|
-
// stepComponent will only return a block if the value is asynchronous
|
|
1365
|
-
ctx.inflightValue = value;
|
|
1366
|
-
}
|
|
1367
|
-
return value;
|
|
1368
|
-
}
|
|
1369
|
-
catch (err) {
|
|
1370
|
-
if (!(ctx.f & IsUpdating)) {
|
|
1371
|
-
return propagateError(ctx.parent, err);
|
|
1372
|
-
}
|
|
1373
|
-
throw err;
|
|
1374
|
-
}
|
|
1375
|
-
}
|
|
1376
|
-
else if (!ctx.enqueuedBlock) {
|
|
1377
|
-
// We need to assign enqueuedBlock and enqueuedValue synchronously, hence
|
|
1378
|
-
// the Promise constructor call.
|
|
1379
|
-
let resolveEnqueuedBlock;
|
|
1380
|
-
ctx.enqueuedBlock = new Promise((resolve) => (resolveEnqueuedBlock = resolve));
|
|
1381
|
-
ctx.enqueuedValue = ctx.inflightBlock.then(() => {
|
|
1382
|
-
try {
|
|
1383
|
-
const [block, value] = runComponent(ctx);
|
|
1384
|
-
if (block) {
|
|
1385
|
-
resolveEnqueuedBlock(block.finally(() => advanceComponent(ctx)));
|
|
1386
|
-
}
|
|
1387
|
-
return value;
|
|
1388
|
-
}
|
|
1389
|
-
catch (err) {
|
|
1390
|
-
if (!(ctx.f & IsUpdating)) {
|
|
1391
|
-
return propagateError(ctx.parent, err);
|
|
1392
|
-
}
|
|
1393
|
-
throw err;
|
|
1394
|
-
}
|
|
1395
|
-
});
|
|
1396
|
-
}
|
|
1397
|
-
return ctx.enqueuedValue;
|
|
1398
|
-
}
|
|
1399
|
-
/** Called when the inflight block promise settles. */
|
|
1400
|
-
function advanceComponent(ctx) {
|
|
1401
|
-
if (ctx.f & IsAsyncGen) {
|
|
1402
|
-
return;
|
|
1403
|
-
}
|
|
1404
|
-
ctx.inflightBlock = ctx.enqueuedBlock;
|
|
1405
|
-
ctx.inflightValue = ctx.enqueuedValue;
|
|
1406
|
-
ctx.enqueuedBlock = undefined;
|
|
1407
|
-
ctx.enqueuedValue = undefined;
|
|
1408
|
-
}
|
|
1409
|
-
/**
|
|
1410
|
-
* This function is responsible for executing the component and handling all
|
|
1411
|
-
* the different component types. We cannot identify whether a component is a
|
|
1412
|
-
* generator or async without calling it and inspecting the return value.
|
|
1413
|
-
*
|
|
1414
|
-
* @returns {[block, value]} A tuple where
|
|
1415
|
-
* block - A possible promise which represents the duration during which the
|
|
1416
|
-
* component is blocked from updating.
|
|
1417
|
-
* value - A possible promise resolving to the rendered value of children.
|
|
1418
|
-
*
|
|
1419
|
-
* Each component type will block according to the type of the component.
|
|
1420
|
-
* - Sync function components never block and will transparently pass updates
|
|
1421
|
-
* to children.
|
|
1422
|
-
* - Async function components and async generator components block while
|
|
1423
|
-
* executing itself, but will not block for async children.
|
|
1424
|
-
* - Sync generator components block while any children are executing, because
|
|
1425
|
-
* they are expected to only resume when they’ve actually rendered.
|
|
1426
|
-
*/
|
|
1427
|
-
function runComponent(ctx) {
|
|
1428
|
-
const ret = ctx.ret;
|
|
1429
|
-
const initial = !ctx.iterator;
|
|
1430
|
-
if (initial) {
|
|
1431
|
-
resumePropsIterator(ctx);
|
|
1432
|
-
ctx.f |= IsSyncExecuting;
|
|
1433
|
-
clearEventListeners(ctx);
|
|
1434
|
-
let result;
|
|
1435
|
-
try {
|
|
1436
|
-
result = ret.el.tag.call(ctx.owner, ret.el.props);
|
|
1437
|
-
}
|
|
1438
|
-
catch (err) {
|
|
1439
|
-
ctx.f |= IsErrored;
|
|
1440
|
-
throw err;
|
|
1441
|
-
}
|
|
1442
|
-
finally {
|
|
1443
|
-
ctx.f &= ~IsSyncExecuting;
|
|
1444
|
-
}
|
|
1445
|
-
if (isIteratorLike(result)) {
|
|
1446
|
-
ctx.iterator = result;
|
|
1447
|
-
}
|
|
1448
|
-
else if (isPromiseLike(result)) {
|
|
1449
|
-
// async function component
|
|
1450
|
-
const result1 = result instanceof Promise ? result : Promise.resolve(result);
|
|
1451
|
-
const value = result1.then((result) => updateComponentChildren(ctx, result), (err) => {
|
|
1452
|
-
ctx.f |= IsErrored;
|
|
1453
|
-
throw err;
|
|
1454
|
-
});
|
|
1455
|
-
return [result1.catch(NOOP), value];
|
|
1456
|
-
}
|
|
1457
|
-
else {
|
|
1458
|
-
// sync function component
|
|
1459
|
-
return [undefined, updateComponentChildren(ctx, result)];
|
|
1460
|
-
}
|
|
1461
|
-
}
|
|
1462
|
-
let iteration;
|
|
1463
|
-
if (initial) {
|
|
1464
|
-
try {
|
|
1465
|
-
ctx.f |= IsSyncExecuting;
|
|
1466
|
-
iteration = ctx.iterator.next();
|
|
1467
|
-
}
|
|
1468
|
-
catch (err) {
|
|
1469
|
-
ctx.f |= IsErrored;
|
|
1470
|
-
throw err;
|
|
1471
|
-
}
|
|
1472
|
-
finally {
|
|
1473
|
-
ctx.f &= ~IsSyncExecuting;
|
|
1474
|
-
}
|
|
1475
|
-
if (isPromiseLike(iteration)) {
|
|
1476
|
-
ctx.f |= IsAsyncGen;
|
|
1477
|
-
runAsyncGenComponent(ctx, iteration);
|
|
1478
|
-
}
|
|
1479
|
-
else {
|
|
1480
|
-
ctx.f |= IsSyncGen;
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
if (ctx.f & IsSyncGen) {
|
|
1484
|
-
// sync generator component
|
|
1485
|
-
ctx.f &= ~NeedsToYield;
|
|
1486
|
-
if (!initial) {
|
|
1487
|
-
try {
|
|
1488
|
-
ctx.f |= IsSyncExecuting;
|
|
1489
|
-
iteration = ctx.iterator.next(ctx.renderer.read(getValue(ret)));
|
|
1490
|
-
}
|
|
1491
|
-
catch (err) {
|
|
1492
|
-
ctx.f |= IsErrored;
|
|
1493
|
-
throw err;
|
|
1494
|
-
}
|
|
1495
|
-
finally {
|
|
1496
|
-
ctx.f &= ~IsSyncExecuting;
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
if (isPromiseLike(iteration)) {
|
|
1500
|
-
throw new Error("Sync generator component returned an async iteration");
|
|
1501
|
-
}
|
|
1502
|
-
if (iteration.done) {
|
|
1503
|
-
ctx.f &= ~IsSyncGen;
|
|
1504
|
-
ctx.iterator = undefined;
|
|
1505
|
-
}
|
|
1506
|
-
let value;
|
|
1507
|
-
try {
|
|
1508
|
-
value = updateComponentChildren(ctx,
|
|
1509
|
-
// Children can be void so we eliminate that here
|
|
1510
|
-
iteration.value);
|
|
1511
|
-
if (isPromiseLike(value)) {
|
|
1512
|
-
value = value.catch((err) => handleChildError(ctx, err));
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1515
|
-
catch (err) {
|
|
1516
|
-
value = handleChildError(ctx, err);
|
|
1517
|
-
}
|
|
1518
|
-
const block = isPromiseLike(value) ? value.catch(NOOP) : undefined;
|
|
1519
|
-
return [block, value];
|
|
1520
|
-
}
|
|
1521
|
-
else {
|
|
1522
|
-
// async generator component
|
|
1523
|
-
return [undefined, ctx.inflightValue];
|
|
1524
|
-
}
|
|
1525
|
-
}
|
|
1526
|
-
async function runAsyncGenComponent(ctx, iterationP) {
|
|
1527
|
-
let done = false;
|
|
1528
|
-
try {
|
|
1529
|
-
while (!done) {
|
|
1530
|
-
// inflightValue must be set synchronously.
|
|
1531
|
-
let onValue;
|
|
1532
|
-
ctx.inflightValue = new Promise((resolve) => (onValue = resolve));
|
|
1533
|
-
if (ctx.f & IsUpdating) {
|
|
1534
|
-
// We should not swallow unhandled promise rejections if the component is
|
|
1535
|
-
// updating independently.
|
|
1536
|
-
// TODO: Does this handle this.refresh() calls?
|
|
1537
|
-
ctx.inflightValue.catch(NOOP);
|
|
1538
|
-
}
|
|
1539
|
-
let iteration;
|
|
1540
|
-
try {
|
|
1541
|
-
iteration = await iterationP;
|
|
1542
|
-
}
|
|
1543
|
-
catch (err) {
|
|
1544
|
-
done = true;
|
|
1545
|
-
ctx.f |= IsErrored;
|
|
1546
|
-
onValue(Promise.reject(err));
|
|
1547
|
-
break;
|
|
1548
|
-
}
|
|
1549
|
-
finally {
|
|
1550
|
-
ctx.f &= ~NeedsToYield;
|
|
1551
|
-
if (!(ctx.f & IsInRenderLoop)) {
|
|
1552
|
-
ctx.f &= ~PropsAvailable;
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
1555
|
-
done = !!iteration.done;
|
|
1556
|
-
let value;
|
|
1557
|
-
try {
|
|
1558
|
-
value = updateComponentChildren(ctx, iteration.value);
|
|
1559
|
-
if (isPromiseLike(value)) {
|
|
1560
|
-
value = value.catch((err) => handleChildError(ctx, err));
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
catch (err) {
|
|
1564
|
-
done = true;
|
|
1565
|
-
// Do we need to catch potential errors here in the case of unhandled
|
|
1566
|
-
// promise rejections?
|
|
1567
|
-
value = handleChildError(ctx, err);
|
|
1568
|
-
}
|
|
1569
|
-
finally {
|
|
1570
|
-
onValue(value);
|
|
1571
|
-
}
|
|
1572
|
-
// TODO: this can be done more elegantly
|
|
1573
|
-
let oldValue;
|
|
1574
|
-
if (ctx.ret.inflightValue) {
|
|
1575
|
-
// The value passed back into the generator as the argument to the next
|
|
1576
|
-
// method is a promise if an async generator component has async
|
|
1577
|
-
// children. Sync generator components only resume when their children
|
|
1578
|
-
// have fulfilled so the element’s inflight child values will never be
|
|
1579
|
-
// defined.
|
|
1580
|
-
oldValue = ctx.ret.inflightValue.then((value) => ctx.renderer.read(value), () => ctx.renderer.read(undefined));
|
|
1581
|
-
}
|
|
1582
|
-
else {
|
|
1583
|
-
oldValue = ctx.renderer.read(getValue(ctx.ret));
|
|
1584
|
-
}
|
|
1585
|
-
if (ctx.f & IsUnmounted) {
|
|
1586
|
-
if (ctx.f & IsInRenderLoop) {
|
|
1587
|
-
try {
|
|
1588
|
-
ctx.f |= IsSyncExecuting;
|
|
1589
|
-
iterationP = ctx.iterator.next(oldValue);
|
|
1590
|
-
}
|
|
1591
|
-
finally {
|
|
1592
|
-
ctx.f &= ~IsSyncExecuting;
|
|
1593
|
-
}
|
|
1594
|
-
}
|
|
1595
|
-
else {
|
|
1596
|
-
returnComponent(ctx);
|
|
1597
|
-
break;
|
|
1598
|
-
}
|
|
1599
|
-
}
|
|
1600
|
-
else if (!done) {
|
|
1601
|
-
try {
|
|
1602
|
-
ctx.f |= IsSyncExecuting;
|
|
1603
|
-
iterationP = ctx.iterator.next(oldValue);
|
|
1604
|
-
}
|
|
1605
|
-
finally {
|
|
1606
|
-
ctx.f &= ~IsSyncExecuting;
|
|
1607
|
-
}
|
|
1608
|
-
}
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
|
-
finally {
|
|
1612
|
-
ctx.f &= ~IsAsyncGen;
|
|
1613
|
-
ctx.iterator = undefined;
|
|
1614
|
-
}
|
|
1615
|
-
}
|
|
1616
|
-
/**
|
|
1617
|
-
* Called to resume the props async iterator for async generator components.
|
|
1618
|
-
*/
|
|
1619
|
-
function resumePropsIterator(ctx) {
|
|
1620
|
-
if (ctx.onProps) {
|
|
1621
|
-
ctx.onProps(ctx.ret.el.props);
|
|
1622
|
-
ctx.onProps = undefined;
|
|
1623
|
-
ctx.f &= ~PropsAvailable;
|
|
1624
|
-
}
|
|
1625
|
-
else {
|
|
1626
|
-
ctx.f |= PropsAvailable;
|
|
1627
|
-
}
|
|
1628
|
-
}
|
|
1629
|
-
// TODO: async unmounting
|
|
1630
|
-
function unmountComponent(ctx) {
|
|
1631
|
-
clearEventListeners(ctx);
|
|
1632
|
-
const callbacks = cleanupMap.get(ctx);
|
|
1633
|
-
if (callbacks) {
|
|
1634
|
-
cleanupMap.delete(ctx);
|
|
1635
|
-
const value = ctx.renderer.read(getValue(ctx.ret));
|
|
1636
|
-
for (const callback of callbacks) {
|
|
1637
|
-
callback(value);
|
|
1638
|
-
}
|
|
1639
|
-
}
|
|
1640
|
-
ctx.f |= IsUnmounted;
|
|
1641
|
-
if (ctx.iterator) {
|
|
1642
|
-
if (ctx.f & IsSyncGen) {
|
|
1643
|
-
let value;
|
|
1644
|
-
if (ctx.f & IsInRenderLoop) {
|
|
1645
|
-
value = enqueueComponentRun(ctx);
|
|
1646
|
-
}
|
|
1647
|
-
if (isPromiseLike(value)) {
|
|
1648
|
-
value.then(() => {
|
|
1649
|
-
if (ctx.f & IsInRenderLoop) {
|
|
1650
|
-
unmountComponent(ctx);
|
|
1651
|
-
}
|
|
1652
|
-
else {
|
|
1653
|
-
returnComponent(ctx);
|
|
1654
|
-
}
|
|
1655
|
-
}, (err) => {
|
|
1656
|
-
propagateError(ctx.parent, err);
|
|
1657
|
-
});
|
|
1658
|
-
}
|
|
1659
|
-
else {
|
|
1660
|
-
if (ctx.f & IsInRenderLoop) {
|
|
1661
|
-
unmountComponent(ctx);
|
|
1662
|
-
}
|
|
1663
|
-
else {
|
|
1664
|
-
returnComponent(ctx);
|
|
1665
|
-
}
|
|
1666
|
-
}
|
|
1667
|
-
}
|
|
1668
|
-
else if (ctx.f & IsAsyncGen) {
|
|
1669
|
-
// The logic for unmounting async generator components is in the
|
|
1670
|
-
// runAsyncGenComponent function.
|
|
1671
|
-
resumePropsIterator(ctx);
|
|
1672
|
-
}
|
|
1673
|
-
}
|
|
1674
|
-
}
|
|
1675
|
-
function returnComponent(ctx) {
|
|
1676
|
-
resumePropsIterator(ctx);
|
|
1677
|
-
if (ctx.iterator && typeof ctx.iterator.return === "function") {
|
|
1678
|
-
try {
|
|
1679
|
-
ctx.f |= IsSyncExecuting;
|
|
1680
|
-
const iteration = ctx.iterator.return();
|
|
1681
|
-
if (isPromiseLike(iteration)) {
|
|
1682
|
-
iteration.catch((err) => propagateError(ctx.parent, err));
|
|
1683
|
-
}
|
|
1684
|
-
}
|
|
1685
|
-
finally {
|
|
1686
|
-
ctx.f &= ~IsSyncExecuting;
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
|
-
/*** EVENT TARGET UTILITIES ***/
|
|
1691
|
-
// EVENT PHASE CONSTANTS
|
|
1692
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/Event/eventPhase
|
|
1693
|
-
const NONE = 0;
|
|
1694
|
-
const CAPTURING_PHASE = 1;
|
|
1695
|
-
const AT_TARGET = 2;
|
|
1696
|
-
const BUBBLING_PHASE = 3;
|
|
1697
|
-
const listenersMap = new WeakMap();
|
|
1698
|
-
function isListenerOrListenerObject(value) {
|
|
1699
|
-
return (typeof value === "function" ||
|
|
1700
|
-
(value !== null &&
|
|
1701
|
-
typeof value === "object" &&
|
|
1702
|
-
typeof value.handleEvent === "function"));
|
|
1703
|
-
}
|
|
1704
|
-
function normalizeListenerOptions(options) {
|
|
1705
|
-
if (typeof options === "boolean") {
|
|
1706
|
-
return { capture: options };
|
|
1707
|
-
}
|
|
1708
|
-
else if (options == null) {
|
|
1709
|
-
return {};
|
|
1710
|
-
}
|
|
1711
|
-
return options;
|
|
1712
|
-
}
|
|
1713
|
-
function isEventTarget(value) {
|
|
1714
|
-
return (value != null &&
|
|
1715
|
-
typeof value.addEventListener === "function" &&
|
|
1716
|
-
typeof value.removeEventListener === "function" &&
|
|
1717
|
-
typeof value.dispatchEvent === "function");
|
|
1718
|
-
}
|
|
1719
|
-
function setEventProperty(ev, key, value) {
|
|
1720
|
-
Object.defineProperty(ev, key, { value, writable: false, configurable: true });
|
|
1721
|
-
}
|
|
1722
|
-
// TODO: Maybe we can pass in the current context directly, rather than
|
|
1723
|
-
// starting from the parent?
|
|
1724
|
-
/**
|
|
1725
|
-
* A function to reconstruct an array of every listener given a context and a
|
|
1726
|
-
* host element.
|
|
1727
|
-
*
|
|
1728
|
-
* This function exploits the fact that contexts retain their nearest ancestor
|
|
1729
|
-
* host element. We can determine all the contexts which are directly listening
|
|
1730
|
-
* to an element by traversing up the context tree and checking that the host
|
|
1731
|
-
* element passed in matches the parent context’s host element.
|
|
1732
|
-
*/
|
|
1733
|
-
function getListenerRecords(ctx, ret) {
|
|
1734
|
-
let listeners = [];
|
|
1735
|
-
while (ctx !== undefined && ctx.host === ret) {
|
|
1736
|
-
const listeners1 = listenersMap.get(ctx);
|
|
1737
|
-
if (listeners1) {
|
|
1738
|
-
listeners = listeners.concat(listeners1);
|
|
1739
|
-
}
|
|
1740
|
-
ctx = ctx.parent;
|
|
1741
|
-
}
|
|
1742
|
-
return listeners;
|
|
1743
|
-
}
|
|
1744
|
-
function clearEventListeners(ctx) {
|
|
1745
|
-
const listeners = listenersMap.get(ctx);
|
|
1746
|
-
if (listeners && listeners.length) {
|
|
1747
|
-
for (const value of getChildValues(ctx.ret)) {
|
|
1748
|
-
if (isEventTarget(value)) {
|
|
1749
|
-
for (const record of listeners) {
|
|
1750
|
-
value.removeEventListener(record.type, record.callback, record.options);
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1753
|
-
}
|
|
1754
|
-
listeners.length = 0;
|
|
1755
|
-
}
|
|
1756
|
-
}
|
|
1757
|
-
/*** ERROR HANDLING UTILITIES ***/
|
|
1758
|
-
function handleChildError(ctx, err) {
|
|
1759
|
-
if (!ctx.iterator || typeof ctx.iterator.throw !== "function") {
|
|
1760
|
-
throw err;
|
|
1761
|
-
}
|
|
1762
|
-
resumePropsIterator(ctx);
|
|
1763
|
-
let iteration;
|
|
1764
|
-
try {
|
|
1765
|
-
ctx.f |= IsSyncExecuting;
|
|
1766
|
-
iteration = ctx.iterator.throw(err);
|
|
1767
|
-
}
|
|
1768
|
-
catch (err) {
|
|
1769
|
-
ctx.f |= IsErrored;
|
|
1770
|
-
throw err;
|
|
1771
|
-
}
|
|
1772
|
-
finally {
|
|
1773
|
-
ctx.f &= ~IsSyncExecuting;
|
|
1774
|
-
}
|
|
1775
|
-
if (isPromiseLike(iteration)) {
|
|
1776
|
-
return iteration.then((iteration) => {
|
|
1777
|
-
if (iteration.done) {
|
|
1778
|
-
ctx.f &= ~IsAsyncGen;
|
|
1779
|
-
ctx.iterator = undefined;
|
|
1780
|
-
}
|
|
1781
|
-
return updateComponentChildren(ctx, iteration.value);
|
|
1782
|
-
}, (err) => {
|
|
1783
|
-
ctx.f |= IsErrored;
|
|
1784
|
-
throw err;
|
|
1785
|
-
});
|
|
1786
|
-
}
|
|
1787
|
-
if (iteration.done) {
|
|
1788
|
-
ctx.f &= ~IsSyncGen;
|
|
1789
|
-
ctx.iterator = undefined;
|
|
1790
|
-
}
|
|
1791
|
-
return updateComponentChildren(ctx, iteration.value);
|
|
1792
|
-
}
|
|
1793
|
-
function propagateError(ctx, err) {
|
|
1794
|
-
if (ctx === undefined) {
|
|
1795
|
-
throw err;
|
|
1796
|
-
}
|
|
1797
|
-
let result;
|
|
1798
|
-
try {
|
|
1799
|
-
result = handleChildError(ctx, err);
|
|
1800
|
-
}
|
|
1801
|
-
catch (err) {
|
|
1802
|
-
return propagateError(ctx.parent, err);
|
|
1803
|
-
}
|
|
1804
|
-
if (isPromiseLike(result)) {
|
|
1805
|
-
return result.catch((err) => propagateError(ctx.parent, err));
|
|
1806
|
-
}
|
|
1807
|
-
return result;
|
|
1808
|
-
}
|
|
1809
|
-
// Some JSX transpilation tools expect these functions to be defined on the
|
|
1810
|
-
// default export. Prefer named exports when importing directly.
|
|
1811
|
-
var crank = { createElement, Fragment };
|
|
1812
|
-
|
|
1813
|
-
export { Context, Copy, Element, Fragment, Portal, Raw, Renderer, cloneElement, createElement, crank as default, isElement };
|
|
2
|
+
export { Context, Copy, Element, Fragment, Portal, Raw, Renderer, cloneElement, createElement, isElement } from './core.js';
|
|
3
|
+
export { jsx } from './tags.js';
|
|
1814
4
|
//# sourceMappingURL=crank.js.map
|