@b9g/crank 0.6.1 → 0.7.1

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/crank.cjs CHANGED
@@ -2,42 +2,19 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
+ var eventTarget = require('./event-target.cjs');
6
+ var _utils = require('./_utils.cjs');
7
+
5
8
  const NOOP = () => { };
6
- const IDENTITY = (value) => value;
7
- function wrap(value) {
8
- return value === undefined ? [] : Array.isArray(value) ? value : [value];
9
- }
10
- function unwrap(arr) {
11
- return arr.length === 0 ? undefined : arr.length === 1 ? arr[0] : arr;
12
- }
13
- /**
14
- * Ensures a value is an array.
15
- *
16
- * This function does the same thing as wrap() above except it handles nulls
17
- * and iterables, so it is appropriate for wrapping user-provided element
18
- * children.
19
- */
20
- function arrayify(value) {
21
- return value == null
22
- ? []
23
- : Array.isArray(value)
24
- ? value
25
- : typeof value === "string" ||
26
- typeof value[Symbol.iterator] !== "function"
27
- ? [value]
28
- : [...value];
29
- }
30
- function isIteratorLike(value) {
31
- return value != null && typeof value.next === "function";
32
- }
33
- function isPromiseLike(value) {
34
- return value != null && typeof value.then === "function";
9
+ function getTagName(tag) {
10
+ return typeof tag === "function"
11
+ ? tag.name || "Anonymous"
12
+ : typeof tag === "string"
13
+ ? tag
14
+ : // tag is symbol, using else branch to avoid typeof tag === "symbol"
15
+ tag.description || "Anonymous";
35
16
  }
36
- /***
37
- * SPECIAL TAGS
38
- *
39
- * Crank provides a couple tags which have special meaning for the renderer.
40
- ***/
17
+ /*** SPECIAL TAGS ***/
41
18
  /**
42
19
  * A special tag for grouping multiple children within the same parent.
43
20
  *
@@ -49,8 +26,8 @@ function isPromiseLike(value) {
49
26
  * reference this export.
50
27
  */
51
28
  const Fragment = "";
52
- // TODO: We assert the following symbol tags as any because TypeScript support
53
- // for symbol tags in JSX doesnt exist yet.
29
+ // TODO: We assert the following symbol tags as Components because TypeScript
30
+ // support for symbol tags in JSX doesn't exist yet.
54
31
  // https://github.com/microsoft/TypeScript/issues/38367
55
32
  /**
56
33
  * A special tag for rendering into a new root node via a root prop.
@@ -58,13 +35,13 @@ const Fragment = "";
58
35
  * This tag is useful for creating element trees with multiple roots, for
59
36
  * things like modals or tooltips.
60
37
  *
61
- * Renderer.prototype.render() will implicitly wrap top-level element trees in
62
- * a Portal element.
38
+ * Renderer.prototype.render() implicitly wraps top-level in a Portal element
39
+ * with the root set to the second argument passed in.
63
40
  */
64
41
  const Portal = Symbol.for("crank.Portal");
65
42
  /**
66
43
  * A special tag which preserves whatever was previously rendered in the
67
- * elements position.
44
+ * element's position.
68
45
  *
69
46
  * Copy elements are useful for when you want to prevent a subtree from
70
47
  * rerendering as a performance optimization. Copy elements can also be keyed,
@@ -72,10 +49,13 @@ const Portal = Symbol.for("crank.Portal");
72
49
  */
73
50
  const Copy = Symbol.for("crank.Copy");
74
51
  /**
75
- * A special tag for injecting raw nodes or strings via a value prop.
52
+ * A special tag for rendering text nodes.
76
53
  *
77
- * Renderer.prototype.raw() is called with the value prop.
54
+ * Strings in the element tree are implicitly wrapped in a Text element with
55
+ * value set to the string.
78
56
  */
57
+ const Text = Symbol.for("crank.Text");
58
+ /** A special tag for injecting raw nodes or strings via a value prop. */
79
59
  const Raw = Symbol.for("crank.Raw");
80
60
  const ElementSymbol = Symbol.for("crank.Element");
81
61
  /**
@@ -103,15 +83,6 @@ class Element {
103
83
  this.tag = tag;
104
84
  this.props = props;
105
85
  }
106
- get key() {
107
- return this.props.key;
108
- }
109
- get ref() {
110
- return this.props.ref;
111
- }
112
- get copy() {
113
- return !!this.props.copy;
114
- }
115
86
  }
116
87
  // See Element interface
117
88
  Element.prototype.$$typeof = ElementSymbol;
@@ -119,34 +90,34 @@ function isElement(value) {
119
90
  return value != null && value.$$typeof === ElementSymbol;
120
91
  }
121
92
  const DEPRECATED_PROP_PREFIXES = ["crank-", "c-", "$"];
122
- const DEPRECATED_SPECIAL_PROP_BASES = ["key", "ref", "static"];
123
- const SPECIAL_PROPS = new Set(["children", "key", "ref", "copy"]);
124
- for (const propPrefix of DEPRECATED_PROP_PREFIXES) {
125
- for (const propBase of DEPRECATED_SPECIAL_PROP_BASES) {
126
- SPECIAL_PROPS.add(propPrefix + propBase);
127
- }
128
- }
93
+ const DEPRECATED_SPECIAL_PROP_BASES = ["key", "ref", "static", "copy"];
129
94
  /**
130
95
  * Creates an element with the specified tag, props and children.
131
96
  *
132
97
  * This function is usually used as a transpilation target for JSX transpilers,
133
98
  * but it can also be called directly. It additionally extracts special props so
134
- * they arent accessible to renderer methods or components, and assigns the
99
+ * they aren't accessible to renderer methods or components, and assigns the
135
100
  * children prop according to any additional arguments passed to the function.
136
101
  */
137
102
  function createElement(tag, props, ...children) {
138
103
  if (props == null) {
139
104
  props = {};
140
105
  }
106
+ if ("static" in props) {
107
+ console.error(`The \`static\` prop is deprecated. Use \`copy\` instead.`);
108
+ props["copy"] = props["static"];
109
+ delete props["static"];
110
+ }
141
111
  for (let i = 0; i < DEPRECATED_PROP_PREFIXES.length; i++) {
142
112
  const propPrefix = DEPRECATED_PROP_PREFIXES[i];
143
113
  for (let j = 0; j < DEPRECATED_SPECIAL_PROP_BASES.length; j++) {
144
114
  const propBase = DEPRECATED_SPECIAL_PROP_BASES[j];
145
115
  const deprecatedPropName = propPrefix + propBase;
146
- const targetPropBase = propBase === "static" ? "copy" : propBase;
147
116
  if (deprecatedPropName in props) {
148
- console.warn(`The \`${deprecatedPropName}\` prop is deprecated. Use \`${targetPropBase}\` instead.`);
117
+ const targetPropBase = propBase === "static" ? "copy" : propBase;
118
+ console.error(`The \`${deprecatedPropName}\` prop is deprecated. Use \`${targetPropBase}\` instead.`);
149
119
  props[targetPropBase] = props[deprecatedPropName];
120
+ delete props[deprecatedPropName];
150
121
  }
151
122
  }
152
123
  }
@@ -161,13 +132,13 @@ function createElement(tag, props, ...children) {
161
132
  /** Clones a given element, shallowly copying the props object. */
162
133
  function cloneElement(el) {
163
134
  if (!isElement(el)) {
164
- throw new TypeError("Cannot clone non-element");
135
+ throw new TypeError(`Cannot clone non-element: ${String(el)}`);
165
136
  }
166
137
  return new Element(el.tag, { ...el.props });
167
138
  }
168
139
  function narrow(value) {
169
140
  if (typeof value === "boolean" || value == null) {
170
- return undefined;
141
+ return;
171
142
  }
172
143
  else if (typeof value === "string" || isElement(value)) {
173
144
  return value;
@@ -177,137 +148,203 @@ function narrow(value) {
177
148
  }
178
149
  return value.toString();
179
150
  }
180
- /**
181
- * Takes an array of element values and normalizes the output as an array of
182
- * nodes and strings.
183
- *
184
- * @returns Normalized array of nodes and/or strings.
185
- *
186
- * Normalize will flatten only one level of nested arrays, because it is
187
- * designed to be called once at each level of the tree. It will also
188
- * concatenate adjacent strings and remove all undefined values.
189
- */
190
- function normalize(values) {
191
- const result = [];
192
- let buffer;
193
- for (let i = 0; i < values.length; i++) {
194
- const value = values[i];
195
- if (!value) ;
196
- else if (typeof value === "string") {
197
- buffer = (buffer || "") + value;
198
- }
199
- else if (!Array.isArray(value)) {
200
- if (buffer) {
201
- result.push(buffer);
202
- buffer = undefined;
203
- }
204
- result.push(value);
205
- }
206
- else {
207
- // We could use recursion here but it’s just easier to do it inline.
208
- for (let j = 0; j < value.length; j++) {
209
- const value1 = value[j];
210
- if (!value1) ;
211
- else if (typeof value1 === "string") {
212
- buffer = (buffer || "") + value1;
213
- }
214
- else {
215
- if (buffer) {
216
- result.push(buffer);
217
- buffer = undefined;
218
- }
219
- result.push(value1);
220
- }
221
- }
222
- }
151
+ /*** RETAINER FLAGS ***/
152
+ const DidDiff = 1 << 0;
153
+ const DidCommit = 1 << 1;
154
+ const IsCopied = 1 << 2;
155
+ const IsUpdating = 1 << 3;
156
+ const IsExecuting = 1 << 4;
157
+ const IsRefreshing = 1 << 5;
158
+ const IsScheduling = 1 << 6;
159
+ const IsSchedulingFallback = 1 << 7;
160
+ const IsUnmounted = 1 << 8;
161
+ // TODO: Is this flag still necessary or can we use IsUnmounted?
162
+ const IsErrored = 1 << 9;
163
+ const IsResurrecting = 1 << 10;
164
+ // TODO: Maybe we can get rid of IsSyncGen and IsAsyncGen
165
+ const IsSyncGen = 1 << 11;
166
+ const IsAsyncGen = 1 << 12;
167
+ const IsInForOfLoop = 1 << 13;
168
+ const IsInForAwaitOfLoop = 1 << 14;
169
+ const NeedsToYield = 1 << 15;
170
+ const PropsAvailable = 1 << 16;
171
+ function getFlag(ret, flag) {
172
+ return !!(ret.f & flag);
173
+ }
174
+ function setFlag(ret, flag, value = true) {
175
+ if (value) {
176
+ ret.f |= flag;
223
177
  }
224
- if (buffer) {
225
- result.push(buffer);
178
+ else {
179
+ ret.f &= ~flag;
226
180
  }
227
- return result;
228
181
  }
229
182
  /**
230
183
  * @internal
231
- * The internal nodes which are cached and diffed against new elements when
232
- * rendering element trees.
184
+ * Retainers are objects which act as the internal representation of elements,
185
+ * mirroring the element tree.
233
186
  */
234
187
  class Retainer {
235
188
  constructor(el) {
189
+ this.f = 0;
236
190
  this.el = el;
237
191
  this.ctx = undefined;
238
192
  this.children = undefined;
193
+ this.fallback = undefined;
239
194
  this.value = undefined;
240
- this.cachedChildValues = undefined;
241
- this.fallbackValue = undefined;
242
- this.inflightValue = undefined;
243
- this.onNextValues = undefined;
195
+ this.oldProps = undefined;
196
+ this.pendingDiff = undefined;
197
+ this.onNextDiff = undefined;
198
+ this.graveyard = undefined;
199
+ this.lingerers = undefined;
244
200
  }
245
201
  }
202
+ function cloneRetainer(ret) {
203
+ const clone = new Retainer(ret.el);
204
+ clone.f = ret.f;
205
+ clone.ctx = ret.ctx;
206
+ clone.children = ret.children;
207
+ clone.fallback = ret.fallback;
208
+ clone.value = ret.value;
209
+ clone.scope = ret.scope;
210
+ clone.oldProps = ret.oldProps;
211
+ clone.pendingDiff = ret.pendingDiff;
212
+ clone.onNextDiff = ret.onNextDiff;
213
+ clone.graveyard = ret.graveyard;
214
+ clone.lingerers = ret.lingerers;
215
+ return clone;
216
+ }
246
217
  /**
247
218
  * Finds the value of the element according to its type.
248
219
  *
249
- * @returns The value of the element.
220
+ * @returns A node, an array of nodes or undefined.
250
221
  */
251
- function getValue(ret) {
252
- if (typeof ret.fallbackValue !== "undefined") {
253
- return typeof ret.fallbackValue === "object"
254
- ? getValue(ret.fallbackValue)
255
- : ret.fallbackValue;
222
+ function getValue(ret, isNested = false, index) {
223
+ if (getFlag(ret, IsScheduling) && isNested) {
224
+ return ret.fallback ? getValue(ret.fallback, isNested, index) : undefined;
225
+ }
226
+ else if (ret.fallback && !getFlag(ret, DidDiff)) {
227
+ return ret.fallback
228
+ ? getValue(ret.fallback, isNested, index)
229
+ : ret.fallback;
256
230
  }
257
231
  else if (ret.el.tag === Portal) {
258
232
  return;
259
233
  }
260
- else if (typeof ret.el.tag !== "function" && ret.el.tag !== Fragment) {
261
- return ret.value;
234
+ else if (ret.el.tag === Fragment || typeof ret.el.tag === "function") {
235
+ if (index != null && ret.ctx) {
236
+ ret.ctx.index = index;
237
+ }
238
+ return _utils.unwrap(getChildValues(ret, index));
262
239
  }
263
- return unwrap(getChildValues(ret));
240
+ return ret.value;
264
241
  }
265
242
  /**
266
- * Walks an elements children to find its child values.
243
+ * Walks an element's children to find its child values.
267
244
  *
268
- * @returns A normalized array of nodes and strings.
245
+ * @param ret - The retainer whose child values we are reading.
246
+ * @param startIndex - Starting index to thread through for context index updates.
247
+ *
248
+ * @returns An array of nodes.
269
249
  */
270
- function getChildValues(ret) {
271
- if (ret.cachedChildValues) {
272
- return wrap(ret.cachedChildValues);
273
- }
250
+ function getChildValues(ret, startIndex) {
274
251
  const values = [];
275
- const children = wrap(ret.children);
252
+ const lingerers = ret.lingerers;
253
+ const children = _utils.wrap(ret.children);
254
+ let currentIndex = startIndex;
276
255
  for (let i = 0; i < children.length; i++) {
256
+ if (lingerers != null && lingerers[i] != null) {
257
+ const rets = lingerers[i];
258
+ for (const ret of rets) {
259
+ const value = getValue(ret, true, currentIndex);
260
+ if (Array.isArray(value)) {
261
+ for (let j = 0; j < value.length; j++) {
262
+ values.push(value[j]);
263
+ }
264
+ if (currentIndex != null) {
265
+ currentIndex += value.length;
266
+ }
267
+ }
268
+ else if (value) {
269
+ values.push(value);
270
+ if (currentIndex != null) {
271
+ currentIndex++;
272
+ }
273
+ }
274
+ }
275
+ }
277
276
  const child = children[i];
278
277
  if (child) {
279
- values.push(typeof child === "string" ? child : getValue(child));
278
+ const value = getValue(child, true, currentIndex);
279
+ if (Array.isArray(value)) {
280
+ for (let j = 0; j < value.length; j++) {
281
+ values.push(value[j]);
282
+ }
283
+ if (currentIndex != null) {
284
+ currentIndex += value.length;
285
+ }
286
+ }
287
+ else if (value) {
288
+ values.push(value);
289
+ if (currentIndex != null) {
290
+ currentIndex++;
291
+ }
292
+ }
280
293
  }
281
294
  }
282
- const values1 = normalize(values);
283
- const tag = ret.el.tag;
284
- if (typeof tag === "function" || (tag !== Fragment && tag !== Raw)) {
285
- ret.cachedChildValues = unwrap(values1);
295
+ if (lingerers != null && lingerers.length > children.length) {
296
+ for (let i = children.length; i < lingerers.length; i++) {
297
+ const rets = lingerers[i];
298
+ if (rets != null) {
299
+ for (const ret of rets) {
300
+ const value = getValue(ret, true, currentIndex);
301
+ if (Array.isArray(value)) {
302
+ for (let j = 0; j < value.length; j++) {
303
+ values.push(value[j]);
304
+ }
305
+ if (currentIndex != null) {
306
+ currentIndex += value.length;
307
+ }
308
+ }
309
+ else if (value) {
310
+ values.push(value);
311
+ if (currentIndex != null) {
312
+ currentIndex++;
313
+ }
314
+ }
315
+ }
316
+ }
317
+ }
286
318
  }
287
- return values1;
319
+ return values;
320
+ }
321
+ function stripSpecialProps(props) {
322
+ let _;
323
+ let result;
324
+ ({ key: _, ref: _, copy: _, hydrate: _, children: _, ...result } = props);
325
+ return result;
288
326
  }
289
- const defaultRendererImpl = {
327
+ const defaultAdapter = {
290
328
  create() {
291
- throw new Error("Not implemented");
329
+ throw new Error("adapter must implement create");
292
330
  },
293
- hydrate() {
294
- throw new Error("Not implemented");
331
+ adopt() {
332
+ throw new Error("adapter must implement adopt() for hydration");
295
333
  },
296
- scope: IDENTITY,
297
- read: IDENTITY,
298
- text: IDENTITY,
299
- raw: IDENTITY,
334
+ scope: ({ scope }) => scope,
335
+ read: (value) => value,
336
+ text: ({ value }) => value,
337
+ raw: ({ value }) => value,
300
338
  patch: NOOP,
301
339
  arrange: NOOP,
302
- dispose: NOOP,
303
- flush: NOOP,
340
+ remove: NOOP,
341
+ finalize: NOOP,
304
342
  };
305
- const _RendererImpl = Symbol.for("crank.RendererImpl");
306
343
  /**
307
344
  * An abstract class which is subclassed to render to different target
308
- * environments. Subclasses will typically call super() with a custom
309
- * RendererImpl. This class is responsible for kicking off the rendering
310
- * process and caching previous trees by root.
345
+ * environments. Subclasses call super() with a custom RenderAdapter object.
346
+ * This class is responsible for kicking off the rendering process and caching
347
+ * previous trees by root.
311
348
  *
312
349
  * @template TNode - The type of the node for a rendering environment.
313
350
  * @template TScope - Data which is passed down the tree.
@@ -315,125 +352,128 @@ const _RendererImpl = Symbol.for("crank.RendererImpl");
315
352
  * @template TResult - The type of exposed values.
316
353
  */
317
354
  class Renderer {
318
- constructor(impl) {
355
+ constructor(adapter) {
319
356
  this.cache = new WeakMap();
320
- this[_RendererImpl] = {
321
- ...defaultRendererImpl,
322
- ...impl,
323
- };
357
+ this.adapter = { ...defaultAdapter, ...adapter };
324
358
  }
325
359
  /**
326
360
  * Renders an element tree into a specific root.
327
361
  *
328
- * @param children - An element tree. You can render null with a previously
329
- * used root to delete the previously rendered element tree from the cache.
330
- * @param root - The node to be rendered into. The renderer will cache
331
- * element trees per root.
332
- * @param bridge - An optional context that will be the ancestor context of all
333
- * elements in the tree. Useful for connecting different renderers so that
334
- * events/provisions properly propagate. The context for a given root must be
335
- * the same or an error will be thrown.
362
+ * @param children - An element tree. Rendering null deletes cached renders.
363
+ * @param root - The root to be rendered into. The renderer caches renders
364
+ * per root.
365
+ * @param bridge - An optional context that will be the ancestor context of
366
+ * all elements in the tree. Useful for connecting different renderers so
367
+ * that events/provisions/errors properly propagate. The context for a given
368
+ * root must be the same between renders.
336
369
  *
337
370
  * @returns The result of rendering the children, or a possible promise of
338
371
  * the result if the element tree renders asynchronously.
339
372
  */
340
373
  render(children, root, bridge) {
341
- let ret;
342
- const ctx = bridge && bridge[_ContextImpl];
343
- if (typeof root === "object" && root !== null) {
344
- ret = this.cache.get(root);
345
- }
346
- let oldProps;
347
- if (ret === undefined) {
348
- ret = new Retainer(createElement(Portal, { children, root }));
349
- ret.value = root;
350
- ret.ctx = ctx;
351
- if (typeof root === "object" && root !== null && children != null) {
352
- this.cache.set(root, ret);
353
- }
354
- }
355
- else if (ret.ctx !== ctx) {
356
- throw new Error("Context mismatch");
357
- }
358
- else {
359
- oldProps = ret.el.props;
360
- ret.el = createElement(Portal, { children, root });
361
- if (typeof root === "object" && root !== null && children == null) {
362
- this.cache.delete(root);
363
- }
364
- }
365
- const impl = this[_RendererImpl];
366
- const childValues = diffChildren(impl, root, ret, ctx, impl.scope(undefined, Portal, ret.el.props), ret, children, undefined);
367
- // We return the child values of the portal because portal elements
368
- // themselves have no readable value.
369
- if (isPromiseLike(childValues)) {
370
- return childValues.then((childValues) => commitRootRender(impl, root, ctx, ret, childValues, oldProps));
371
- }
372
- return commitRootRender(impl, root, ctx, ret, childValues, oldProps);
374
+ const ret = getRootRetainer(this, bridge, { children, root });
375
+ return renderRoot(this.adapter, root, ret, children);
373
376
  }
374
377
  hydrate(children, root, bridge) {
375
- const impl = this[_RendererImpl];
376
- const ctx = bridge && bridge[_ContextImpl];
377
- let ret;
378
- ret = this.cache.get(root);
379
- if (ret !== undefined) {
380
- // If there is a retainer for the root, hydration is not necessary.
381
- return this.render(children, root, bridge);
382
- }
383
- let oldProps;
384
- ret = new Retainer(createElement(Portal, { children, root }));
378
+ const ret = getRootRetainer(this, bridge, {
379
+ children,
380
+ root,
381
+ hydrate: true,
382
+ });
383
+ return renderRoot(this.adapter, root, ret, children);
384
+ }
385
+ }
386
+ /*** PRIVATE RENDERER FUNCTIONS ***/
387
+ function getRootRetainer(renderer, bridge, { children, root, hydrate, }) {
388
+ let ret;
389
+ const bridgeCtx = bridge && bridge[_ContextState];
390
+ if (typeof root === "object" && root !== null) {
391
+ ret = renderer.cache.get(root);
392
+ }
393
+ const adapter = renderer.adapter;
394
+ if (ret === undefined) {
395
+ ret = new Retainer(createElement(Portal, { children, root, hydrate }));
385
396
  ret.value = root;
397
+ ret.ctx = bridgeCtx;
398
+ ret.scope = adapter.scope({
399
+ tag: Portal,
400
+ tagName: getTagName(Portal),
401
+ props: stripSpecialProps(ret.el.props),
402
+ scope: undefined,
403
+ });
404
+ // remember that typeof null === "object"
386
405
  if (typeof root === "object" && root !== null && children != null) {
387
- this.cache.set(root, ret);
406
+ renderer.cache.set(root, ret);
388
407
  }
389
- const hydrationData = impl.hydrate(Portal, root, {});
390
- const childValues = diffChildren(impl, root, ret, ctx, impl.scope(undefined, Portal, ret.el.props), ret, children, hydrationData);
391
- // We return the child values of the portal because portal elements
392
- // themselves have no readable value.
393
- if (isPromiseLike(childValues)) {
394
- return childValues.then((childValues) => commitRootRender(impl, root, ctx, ret, childValues, oldProps));
408
+ }
409
+ else if (ret.ctx !== bridgeCtx) {
410
+ throw new Error("A previous call to render() was passed a different context");
411
+ }
412
+ else {
413
+ ret.el = createElement(Portal, { children, root, hydrate });
414
+ if (typeof root === "object" && root !== null && children == null) {
415
+ renderer.cache.delete(root);
395
416
  }
396
- return commitRootRender(impl, root, ctx, ret, childValues, oldProps);
397
417
  }
418
+ return ret;
398
419
  }
399
- /*** PRIVATE RENDERER FUNCTIONS ***/
400
- function commitRootRender(renderer, root, ctx, ret, childValues, oldProps) {
401
- // element is a host or portal element
402
- if (root != null) {
403
- renderer.arrange(Portal, root, ret.el.props, childValues, oldProps, wrap(ret.cachedChildValues));
404
- flush(renderer, root);
420
+ function renderRoot(adapter, root, ret, children) {
421
+ const diff = diffChildren(adapter, root, ret, ret.ctx, ret.scope, ret, children);
422
+ const schedulePromises = [];
423
+ if (_utils.isPromiseLike(diff)) {
424
+ return diff.then(() => {
425
+ commit(adapter, ret, ret, ret.ctx, ret.scope, 0, schedulePromises, undefined);
426
+ if (schedulePromises.length > 0) {
427
+ return Promise.all(schedulePromises).then(() => {
428
+ if (typeof root !== "object" || root === null) {
429
+ unmount(adapter, ret, ret.ctx, ret, false);
430
+ }
431
+ return adapter.read(_utils.unwrap(getChildValues(ret)));
432
+ });
433
+ }
434
+ if (typeof root !== "object" || root === null) {
435
+ unmount(adapter, ret, ret.ctx, ret, false);
436
+ }
437
+ return adapter.read(_utils.unwrap(getChildValues(ret)));
438
+ });
439
+ }
440
+ commit(adapter, ret, ret, ret.ctx, ret.scope, 0, schedulePromises, undefined);
441
+ if (schedulePromises.length > 0) {
442
+ return Promise.all(schedulePromises).then(() => {
443
+ if (typeof root !== "object" || root === null) {
444
+ unmount(adapter, ret, ret.ctx, ret, false);
445
+ }
446
+ return adapter.read(_utils.unwrap(getChildValues(ret)));
447
+ });
405
448
  }
406
- ret.cachedChildValues = unwrap(childValues);
407
- if (root == null) {
408
- unmount(renderer, ret, ctx, ret);
449
+ if (typeof root !== "object" || root === null) {
450
+ unmount(adapter, ret, ret.ctx, ret, false);
409
451
  }
410
- return renderer.read(ret.cachedChildValues);
452
+ return adapter.read(_utils.unwrap(getChildValues(ret)));
411
453
  }
412
- function diffChildren(renderer, root, host, ctx, scope, parent, children, hydrationData) {
413
- const oldRetained = wrap(parent.children);
454
+ function diffChildren(adapter, root, host, ctx, scope, parent, newChildren) {
455
+ const oldRetained = _utils.wrap(parent.children);
414
456
  const newRetained = [];
415
- const newChildren = arrayify(children);
416
- const values = [];
417
- let graveyard;
457
+ const newChildren1 = _utils.arrayify(newChildren);
458
+ const diffs = [];
418
459
  let childrenByKey;
419
460
  let seenKeys;
420
461
  let isAsync = false;
421
- // When hydrating, sibling element trees must be rendered in order, because
422
- // we do not know how many DOM nodes an element will render.
423
- let hydrationBlock;
424
462
  let oi = 0;
425
463
  let oldLength = oldRetained.length;
426
- for (let ni = 0, newLength = newChildren.length; ni < newLength; ni++) {
464
+ let graveyard;
465
+ for (let ni = 0, newLength = newChildren1.length; ni < newLength; ni++) {
427
466
  // length checks to prevent index out of bounds deoptimizations.
428
467
  let ret = oi >= oldLength ? undefined : oldRetained[oi];
429
- let child = narrow(newChildren[ni]);
468
+ let child = narrow(newChildren1[ni]);
430
469
  {
431
470
  // aligning new children with old retainers
432
- let oldKey = typeof ret === "object" ? ret.el.key : undefined;
433
- let newKey = typeof child === "object" ? child.key : undefined;
471
+ let oldKey = typeof ret === "object" ? ret.el.props.key : undefined;
472
+ let newKey = typeof child === "object" ? child.props.key : undefined;
434
473
  if (newKey !== undefined && seenKeys && seenKeys.has(newKey)) {
435
- console.error("Duplicate key", newKey);
436
- newKey = undefined;
474
+ console.error(`Duplicate key found in <${getTagName(parent.el.tag)}>`, newKey);
475
+ child = cloneElement(child);
476
+ newKey = child.props.key = undefined;
437
477
  }
438
478
  if (oldKey === newKey) {
439
479
  if (childrenByKey !== undefined && newKey !== undefined) {
@@ -447,7 +487,7 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children, hydrat
447
487
  while (ret !== undefined && oldKey !== undefined) {
448
488
  oi++;
449
489
  ret = oldRetained[oi];
450
- oldKey = typeof ret === "object" ? ret.el.key : undefined;
490
+ oldKey = typeof ret === "object" ? ret.el.props.key : undefined;
451
491
  }
452
492
  oi++;
453
493
  }
@@ -460,405 +500,748 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children, hydrat
460
500
  }
461
501
  }
462
502
  }
463
- // Updating
464
- let value;
503
+ let diff = undefined;
465
504
  if (typeof child === "object") {
466
- if (child.tag === Copy || (typeof ret === "object" && ret.el === child)) {
467
- value = getInflightValue(ret);
505
+ let childCopied = false;
506
+ if (child.tag === Copy) {
507
+ childCopied = true;
508
+ }
509
+ else if (typeof ret === "object" &&
510
+ ret.el === child &&
511
+ getFlag(ret, DidCommit)) {
512
+ // If the child is the same as the retained element, we skip
513
+ // re-rendering.
514
+ childCopied = true;
468
515
  }
469
516
  else {
470
- let oldProps;
471
- let copy = false;
472
- if (typeof ret === "object" && ret.el.tag === child.tag) {
473
- oldProps = ret.el.props;
517
+ if (ret && ret.el.tag === child.tag) {
474
518
  ret.el = child;
475
- if (child.copy) {
476
- value = getInflightValue(ret);
477
- copy = true;
519
+ if (child.props.copy && typeof child.props.copy !== "string") {
520
+ childCopied = true;
478
521
  }
479
522
  }
480
- else {
481
- if (typeof ret === "object") {
482
- (graveyard = graveyard || []).push(ret);
523
+ else if (ret) {
524
+ // we do not need to add the retainer to the graveyard if it is the
525
+ // fallback of another retainer
526
+ // search for the tag in fallback chain
527
+ let candidateFound = false;
528
+ for (let predecessor = ret, candidate = ret.fallback; candidate; predecessor = candidate, candidate = candidate.fallback) {
529
+ if (candidate.el.tag === child.tag) {
530
+ // If we find a retainer in the fallback chain with the same tag,
531
+ // we reuse it rather than creating a new retainer to preserve
532
+ // state. This behavior is useful for when a Suspense component
533
+ // re-renders and the children are re-rendered quickly.
534
+ const clone = cloneRetainer(candidate);
535
+ setFlag(clone, IsResurrecting);
536
+ predecessor.fallback = clone;
537
+ const fallback = ret;
538
+ ret = candidate;
539
+ ret.el = child;
540
+ ret.fallback = fallback;
541
+ setFlag(ret, DidDiff, false);
542
+ candidateFound = true;
543
+ break;
544
+ }
545
+ }
546
+ if (!candidateFound) {
547
+ const fallback = ret;
548
+ ret = new Retainer(child);
549
+ ret.fallback = fallback;
483
550
  }
484
- const fallback = ret;
485
- ret = new Retainer(child);
486
- ret.fallbackValue = fallback;
487
551
  }
488
- if (copy) ;
489
- else if (child.tag === Raw) {
490
- value = hydrationBlock
491
- ? hydrationBlock.then(() => updateRaw(renderer, ret, scope, oldProps, hydrationData))
492
- : updateRaw(renderer, ret, scope, oldProps, hydrationData);
552
+ else {
553
+ ret = new Retainer(child);
493
554
  }
555
+ if (childCopied && getFlag(ret, DidCommit)) ;
556
+ else if (child.tag === Raw || child.tag === Text) ;
494
557
  else if (child.tag === Fragment) {
495
- value = hydrationBlock
496
- ? hydrationBlock.then(() => updateFragment(renderer, root, host, ctx, scope, ret, hydrationData))
497
- : updateFragment(renderer, root, host, ctx, scope, ret, hydrationData);
558
+ diff = diffChildren(adapter, root, host, ctx, scope, ret, ret.el.props.children);
498
559
  }
499
560
  else if (typeof child.tag === "function") {
500
- value = hydrationBlock
501
- ? hydrationBlock.then(() => updateComponent(renderer, root, host, ctx, scope, ret, oldProps, hydrationData))
502
- : updateComponent(renderer, root, host, ctx, scope, ret, oldProps, hydrationData);
561
+ diff = diffComponent(adapter, root, host, ctx, scope, ret);
562
+ }
563
+ else {
564
+ diff = diffHost(adapter, root, ctx, scope, ret);
565
+ }
566
+ }
567
+ if (typeof ret === "object") {
568
+ if (childCopied) {
569
+ setFlag(ret, IsCopied);
570
+ diff = getInflightDiff(ret);
503
571
  }
504
572
  else {
505
- value = hydrationBlock
506
- ? hydrationBlock.then(() => updateHost(renderer, root, ctx, scope, ret, oldProps, hydrationData))
507
- : updateHost(renderer, root, ctx, scope, ret, oldProps, hydrationData);
573
+ setFlag(ret, IsCopied, false);
508
574
  }
509
575
  }
510
- if (isPromiseLike(value)) {
576
+ if (_utils.isPromiseLike(diff)) {
511
577
  isAsync = true;
512
- if (hydrationData !== undefined) {
513
- hydrationBlock = value;
578
+ }
579
+ }
580
+ else if (typeof child === "string") {
581
+ if (typeof ret === "object" && ret.el.tag === Text) {
582
+ ret.el.props.value = child;
583
+ }
584
+ else {
585
+ if (typeof ret === "object") {
586
+ (graveyard = graveyard || []).push(ret);
514
587
  }
588
+ ret = new Retainer(createElement(Text, { value: child }));
515
589
  }
516
590
  }
517
591
  else {
518
- // child is a string or undefined
519
592
  if (typeof ret === "object") {
520
593
  (graveyard = graveyard || []).push(ret);
521
594
  }
522
- if (typeof child === "string") {
523
- value = ret = renderer.text(child, scope, hydrationData);
524
- }
525
- else {
526
- ret = undefined;
527
- }
595
+ ret = undefined;
528
596
  }
529
- values[ni] = value;
597
+ diffs[ni] = diff;
530
598
  newRetained[ni] = ret;
531
599
  }
532
600
  // cleanup remaining retainers
533
601
  for (; oi < oldLength; oi++) {
534
602
  const ret = oldRetained[oi];
535
603
  if (typeof ret === "object" &&
536
- (typeof ret.el.key === "undefined" ||
604
+ (typeof ret.el.props.key === "undefined" ||
537
605
  !seenKeys ||
538
- !seenKeys.has(ret.el.key))) {
606
+ !seenKeys.has(ret.el.props.key))) {
539
607
  (graveyard = graveyard || []).push(ret);
540
608
  }
541
609
  }
542
610
  if (childrenByKey !== undefined && childrenByKey.size > 0) {
543
- (graveyard = graveyard || []).push(...childrenByKey.values());
611
+ graveyard = graveyard || [];
612
+ for (const ret of childrenByKey.values()) {
613
+ graveyard.push(ret);
614
+ }
544
615
  }
545
- parent.children = unwrap(newRetained);
616
+ parent.children = _utils.unwrap(newRetained);
546
617
  if (isAsync) {
547
- let childValues1 = Promise.all(values).finally(() => {
618
+ const diffs1 = Promise.all(diffs)
619
+ .then(() => undefined)
620
+ .finally(() => {
621
+ setFlag(parent, DidDiff);
548
622
  if (graveyard) {
549
- for (let i = 0; i < graveyard.length; i++) {
550
- unmount(renderer, host, ctx, graveyard[i]);
623
+ if (parent.graveyard) {
624
+ for (let i = 0; i < graveyard.length; i++) {
625
+ parent.graveyard.push(graveyard[i]);
626
+ }
627
+ }
628
+ else {
629
+ parent.graveyard = graveyard;
551
630
  }
552
631
  }
553
632
  });
554
- let onChildValues;
555
- childValues1 = Promise.race([
556
- childValues1,
557
- new Promise((resolve) => (onChildValues = resolve)),
558
- ]);
559
- if (parent.onNextValues) {
560
- parent.onNextValues(childValues1);
561
- }
562
- parent.onNextValues = onChildValues;
563
- return childValues1.then((childValues) => {
564
- parent.inflightValue = parent.fallbackValue = undefined;
565
- return normalize(childValues);
566
- });
633
+ let onNextDiffs;
634
+ const diffs2 = (parent.pendingDiff = _utils.safeRace([
635
+ diffs1,
636
+ new Promise((resolve) => (onNextDiffs = resolve)),
637
+ ]));
638
+ if (parent.onNextDiff) {
639
+ parent.onNextDiff(diffs2);
640
+ }
641
+ parent.onNextDiff = onNextDiffs;
642
+ return diffs2;
567
643
  }
568
644
  else {
645
+ setFlag(parent, DidDiff);
569
646
  if (graveyard) {
570
- for (let i = 0; i < graveyard.length; i++) {
571
- unmount(renderer, host, ctx, graveyard[i]);
647
+ if (parent.graveyard) {
648
+ for (let i = 0; i < graveyard.length; i++) {
649
+ parent.graveyard.push(graveyard[i]);
650
+ }
651
+ }
652
+ else {
653
+ parent.graveyard = graveyard;
572
654
  }
573
655
  }
574
- if (parent.onNextValues) {
575
- parent.onNextValues(values);
576
- parent.onNextValues = undefined;
656
+ if (parent.onNextDiff) {
657
+ parent.onNextDiff(diffs);
658
+ parent.onNextDiff = undefined;
577
659
  }
578
- parent.inflightValue = parent.fallbackValue = undefined;
579
- // We can assert there are no promises in the array because isAsync is false
580
- return normalize(values);
660
+ parent.pendingDiff = undefined;
661
+ }
662
+ }
663
+ function getInflightDiff(ret) {
664
+ // It is not enough to check pendingDiff because pendingDiff is the diff for
665
+ // children, but not the diff of an async component retainer's current run.
666
+ // For the latter we check ctx.inflight.
667
+ if (ret.ctx && ret.ctx.inflight) {
668
+ return ret.ctx.inflight[1];
669
+ }
670
+ else if (ret.pendingDiff) {
671
+ return ret.pendingDiff;
581
672
  }
582
673
  }
583
674
  function createChildrenByKey(children, offset) {
584
675
  const childrenByKey = new Map();
585
676
  for (let i = offset; i < children.length; i++) {
586
677
  const child = children[i];
587
- if (typeof child === "object" && typeof child.el.key !== "undefined") {
588
- childrenByKey.set(child.el.key, child);
678
+ if (typeof child === "object" &&
679
+ typeof child.el.props.key !== "undefined") {
680
+ childrenByKey.set(child.el.props.key, child);
589
681
  }
590
682
  }
591
683
  return childrenByKey;
592
684
  }
593
- function getInflightValue(child) {
594
- if (typeof child !== "object") {
595
- return child;
596
- }
597
- const ctx = typeof child.el.tag === "function" ? child.ctx : undefined;
598
- if (ctx && ctx.f & IsUpdating && ctx.inflightValue) {
599
- return ctx.inflightValue;
685
+ function diffHost(adapter, root, ctx, scope, ret) {
686
+ const el = ret.el;
687
+ const tag = el.tag;
688
+ if (el.tag === Portal) {
689
+ root = ret.value = el.props.root;
600
690
  }
601
- else if (child.inflightValue) {
602
- return child.inflightValue;
691
+ if (getFlag(ret, DidCommit)) {
692
+ scope = ret.scope;
603
693
  }
604
- return getValue(child);
605
- }
606
- function updateRaw(renderer, ret, scope, oldProps, hydrationData) {
607
- const props = ret.el.props;
608
- if (!oldProps || oldProps.value !== props.value) {
609
- ret.value = renderer.raw(props.value, scope, hydrationData);
610
- if (typeof ret.el.ref === "function") {
611
- ret.el.ref(ret.value);
612
- }
694
+ else {
695
+ scope = ret.scope = adapter.scope({
696
+ tag,
697
+ tagName: getTagName(tag),
698
+ props: el.props,
699
+ scope,
700
+ });
613
701
  }
614
- return ret.value;
702
+ return diffChildren(adapter, root, ret, ctx, scope, ret, ret.el.props.children);
615
703
  }
616
- function updateFragment(renderer, root, host, ctx, scope, ret, hydrationData) {
617
- const childValues = diffChildren(renderer, root, host, ctx, scope, ret, ret.el.props.children, hydrationData);
618
- if (isPromiseLike(childValues)) {
619
- ret.inflightValue = childValues.then((childValues) => unwrap(childValues));
620
- return ret.inflightValue;
704
+ function commit(adapter, host, ret, ctx, scope, index, schedulePromises, hydrationNodes) {
705
+ if (getFlag(ret, IsCopied) && getFlag(ret, DidCommit)) {
706
+ return getValue(ret);
621
707
  }
622
- return unwrap(childValues);
623
- }
624
- function updateHost(renderer, root, ctx, scope, ret, oldProps, hydrationData) {
625
708
  const el = ret.el;
626
709
  const tag = el.tag;
627
- let hydrationValue;
628
- if (el.tag === Portal) {
629
- root = ret.value = el.props.root;
710
+ if (typeof tag === "function" ||
711
+ tag === Fragment ||
712
+ tag === Portal ||
713
+ tag === Raw ||
714
+ tag === Text) {
715
+ if (typeof el.props.copy === "string") {
716
+ console.error(`String copy prop ignored for <${getTagName(tag)}>. Use booleans instead.`);
717
+ }
718
+ if (typeof el.props.hydrate === "string") {
719
+ console.error(`String hydrate prop ignored for <${getTagName(tag)}>. Use booleans instead.`);
720
+ }
721
+ }
722
+ let value;
723
+ let skippedHydrationNodes;
724
+ if (hydrationNodes &&
725
+ el.props.hydrate != null &&
726
+ !el.props.hydrate &&
727
+ typeof el.props.hydrate !== "string") {
728
+ skippedHydrationNodes = hydrationNodes;
729
+ hydrationNodes = undefined;
730
+ }
731
+ if (typeof tag === "function") {
732
+ ret.ctx.index = index;
733
+ value = commitComponent(ret.ctx, schedulePromises, hydrationNodes);
630
734
  }
631
735
  else {
632
- if (hydrationData !== undefined) {
633
- const value = hydrationData.children.shift();
634
- hydrationValue = value;
736
+ if (tag === Fragment) {
737
+ value = commitChildren(adapter, host, ctx, scope, ret, index, schedulePromises, hydrationNodes);
635
738
  }
636
- }
637
- scope = renderer.scope(scope, tag, el.props);
638
- let childHydrationData;
639
- if (hydrationValue != null && typeof hydrationValue !== "string") {
640
- childHydrationData = renderer.hydrate(tag, hydrationValue, el.props);
641
- if (childHydrationData === undefined) {
642
- hydrationValue = undefined;
739
+ else if (tag === Text) {
740
+ value = commitText(adapter, ret, el, scope, hydrationNodes);
741
+ }
742
+ else if (tag === Raw) {
743
+ value = commitRaw(adapter, host, ret, scope, hydrationNodes);
744
+ }
745
+ else {
746
+ value = commitHost(adapter, ret, ctx, schedulePromises, hydrationNodes);
747
+ }
748
+ if (ret.fallback) {
749
+ unmount(adapter, host, ctx, ret.fallback, false);
750
+ ret.fallback = undefined;
643
751
  }
644
752
  }
645
- const childValues = diffChildren(renderer, root, ret, ctx, scope, ret, ret.el.props.children, childHydrationData);
646
- if (isPromiseLike(childValues)) {
647
- ret.inflightValue = childValues.then((childValues) => commitHost(renderer, scope, ret, childValues, oldProps, hydrationValue));
648
- return ret.inflightValue;
753
+ if (skippedHydrationNodes) {
754
+ skippedHydrationNodes.splice(0, _utils.wrap(value).length);
649
755
  }
650
- return commitHost(renderer, scope, ret, childValues, oldProps, hydrationValue);
651
- }
652
- function commitHost(renderer, scope, ret, childValues, oldProps, hydrationValue) {
653
- const tag = ret.el.tag;
654
- let value = ret.value;
655
- if (hydrationValue != null) {
656
- value = ret.value = hydrationValue;
657
- if (typeof ret.el.ref === "function") {
658
- ret.el.ref(value);
756
+ if (!getFlag(ret, DidCommit)) {
757
+ setFlag(ret, DidCommit);
758
+ if (typeof tag !== "function" &&
759
+ tag !== Fragment &&
760
+ tag !== Portal &&
761
+ typeof el.props.ref === "function") {
762
+ el.props.ref(adapter.read(value));
659
763
  }
660
764
  }
661
- let props = ret.el.props;
662
- let copied;
663
- if (tag !== Portal) {
664
- if (value == null) {
665
- // This assumes that renderer.create does not return nullish values.
666
- value = ret.value = renderer.create(tag, props, scope);
667
- if (typeof ret.el.ref === "function") {
668
- ret.el.ref(value);
765
+ return value;
766
+ }
767
+ function commitChildren(adapter, host, ctx, scope, parent, index, schedulePromises, hydrationNodes) {
768
+ let values = [];
769
+ for (let i = 0, children = _utils.wrap(parent.children); i < children.length; i++) {
770
+ let child = children[i];
771
+ let schedulePromises1;
772
+ let isSchedulingFallback = false;
773
+ while (child &&
774
+ ((!getFlag(child, DidDiff) && child.fallback) ||
775
+ getFlag(child, IsScheduling))) {
776
+ // If the child is scheduling, it is a component retainer so ctx will be
777
+ // defined.
778
+ if (getFlag(child, IsScheduling) && child.ctx.schedule) {
779
+ (schedulePromises1 = schedulePromises1 || []).push(child.ctx.schedule.promise);
780
+ isSchedulingFallback = true;
781
+ }
782
+ if (!getFlag(child, DidDiff) && getFlag(child, DidCommit)) {
783
+ // If this child has not diffed but has committed, it means it is a
784
+ // fallback that is being resurrected.
785
+ for (const node of getChildValues(child)) {
786
+ adapter.remove({
787
+ node,
788
+ parentNode: host.value,
789
+ isNested: false,
790
+ });
791
+ }
792
+ }
793
+ child = child.fallback;
794
+ // When a scheduling component is mounting asynchronously but diffs
795
+ // immediately, it will cause previous async diffs to settle due to the
796
+ // chasing mechanism. This would cause earlier renders to resolve sooner
797
+ // than expected, because the render would be missing both its usual
798
+ // children and the children of the scheduling render. Therefore, we need
799
+ // to defer the settling of previous renders until either that render
800
+ // settles, or the scheduling component finally finishes scheduling.
801
+ //
802
+ // To do this, we take advantage of the fact that commits for aborted
803
+ // renders will still fire and walk the tree. During that commit walk,
804
+ // when we encounter a scheduling element, we push a race of the
805
+ // scheduling promise with the inflight diff of the async fallback
806
+ // fallback to schedulePromises to delay the initiator.
807
+ //
808
+ // However, we need to make sure we only use the inflight diffs for the
809
+ // fallback which we are trying to delay, in the case of multiple renders
810
+ // and fallbacks. To do this, we take advantage of the fact that when
811
+ // multiple renders race (e.g., render1->render2->render3->scheduling
812
+ // component), the chasing mechanism will call stale commits in reverse
813
+ // order.
814
+ //
815
+ // We can use this ordering to delay to find which fallbacks we need to
816
+ // add to the race. Each commit call progressively marks an additional
817
+ // fallback as a scheduling fallback, and does not contribute to the
818
+ // scheduling promises if it is further than the last seen level.
819
+ //
820
+ // This prevents promise contamination where newer renders settle early
821
+ // due to diffs from older renders.
822
+ if (schedulePromises1 && isSchedulingFallback && child) {
823
+ if (!getFlag(child, DidDiff)) {
824
+ const inflightDiff = getInflightDiff(child);
825
+ schedulePromises1.push(inflightDiff);
826
+ }
827
+ else {
828
+ // If a scheduling component's fallback has already diffed, we do not
829
+ // need delay the render.
830
+ schedulePromises1 = undefined;
831
+ }
832
+ if (getFlag(child, IsSchedulingFallback)) {
833
+ // This fallback was marked by a more recent commit - keep processing
834
+ // deeper levels
835
+ isSchedulingFallback = true;
836
+ }
837
+ else {
838
+ // First unmarked fallback we've encountered - mark it and stop
839
+ // contributing to schedulePromises1 for deeper levels.
840
+ setFlag(child, IsSchedulingFallback, true);
841
+ isSchedulingFallback = false;
842
+ }
669
843
  }
670
844
  }
671
- for (const propName in { ...oldProps, ...props }) {
672
- const propValue = props[propName];
673
- if (propValue === Copy) {
674
- // TODO: The Copy tag doubles as a way to skip the patching of a prop.
675
- // Not sure about this feature. Should probably be removed.
676
- (copied = copied || new Set()).add(propName);
845
+ if (schedulePromises1 && schedulePromises1.length > 1) {
846
+ schedulePromises.push(_utils.safeRace(schedulePromises1));
847
+ }
848
+ if (child) {
849
+ const value = commit(adapter, host, child, ctx, scope, index, schedulePromises, hydrationNodes);
850
+ if (Array.isArray(value)) {
851
+ for (let j = 0; j < value.length; j++) {
852
+ values.push(value[j]);
853
+ }
854
+ index += value.length;
677
855
  }
678
- else if (!SPECIAL_PROPS.has(propName)) {
679
- renderer.patch(tag, value, propName, propValue, oldProps && oldProps[propName], scope);
856
+ else if (value) {
857
+ values.push(value);
858
+ index++;
680
859
  }
681
860
  }
682
861
  }
683
- if (copied) {
684
- props = { ...ret.el.props };
685
- for (const name of copied) {
686
- props[name] = oldProps && oldProps[name];
862
+ if (parent.graveyard) {
863
+ for (let i = 0; i < parent.graveyard.length; i++) {
864
+ const child = parent.graveyard[i];
865
+ unmount(adapter, host, ctx, child, false);
687
866
  }
688
- ret.el = new Element(tag, props);
867
+ parent.graveyard = undefined;
689
868
  }
690
- renderer.arrange(tag, value, props, childValues, oldProps, wrap(ret.cachedChildValues));
691
- ret.cachedChildValues = unwrap(childValues);
692
- if (tag === Portal) {
693
- flush(renderer, ret.value);
694
- return;
869
+ if (parent.lingerers) {
870
+ // if parent.lingerers is set, a descendant component is unmounting
871
+ // asynchronously, so we overwrite values to include lingerering DOM nodes.
872
+ values = getChildValues(parent);
695
873
  }
874
+ return values;
875
+ }
876
+ function commitText(adapter, ret, el, scope, hydrationNodes) {
877
+ const value = adapter.text({
878
+ value: el.props.value,
879
+ scope,
880
+ oldNode: ret.value,
881
+ hydrationNodes,
882
+ });
883
+ ret.value = value;
696
884
  return value;
697
885
  }
698
- function flush(renderer, root, initiator) {
699
- renderer.flush(root);
700
- if (typeof root !== "object" || root === null) {
701
- return;
886
+ function commitRaw(adapter, host, ret, scope, hydrationNodes) {
887
+ if (!ret.oldProps || ret.oldProps.value !== ret.el.props.value) {
888
+ const oldNodes = _utils.wrap(ret.value);
889
+ for (let i = 0; i < oldNodes.length; i++) {
890
+ const oldNode = oldNodes[i];
891
+ adapter.remove({
892
+ node: oldNode,
893
+ parentNode: host.value,
894
+ isNested: false,
895
+ });
896
+ }
897
+ ret.value = adapter.raw({
898
+ value: ret.el.props.value,
899
+ scope,
900
+ hydrationNodes,
901
+ });
902
+ }
903
+ ret.oldProps = stripSpecialProps(ret.el.props);
904
+ return ret.value;
905
+ }
906
+ function commitHost(adapter, ret, ctx, schedulePromises, hydrationNodes) {
907
+ if (getFlag(ret, IsCopied) && getFlag(ret, DidCommit)) {
908
+ return getValue(ret);
702
909
  }
703
- const flushMap = flushMaps.get(root);
704
- if (flushMap) {
705
- if (initiator) {
706
- const flushMap1 = new Map();
707
- for (let [ctx, callbacks] of flushMap) {
708
- if (!ctxContains(initiator, ctx)) {
709
- flushMap.delete(ctx);
710
- flushMap1.set(ctx, callbacks);
910
+ const tag = ret.el.tag;
911
+ const props = stripSpecialProps(ret.el.props);
912
+ const oldProps = ret.oldProps;
913
+ let node = ret.value;
914
+ let copyProps;
915
+ let copyChildren = false;
916
+ if (oldProps) {
917
+ for (const propName in props) {
918
+ if (props[propName] === Copy) {
919
+ // The Copy tag can be used to skip the patching of a prop.
920
+ // <div class={shouldPatchClass ? "class-name" : Copy} />
921
+ props[propName] = oldProps[propName];
922
+ (copyProps = copyProps || new Set()).add(propName);
923
+ }
924
+ }
925
+ if (typeof ret.el.props.copy === "string") {
926
+ const copyMetaProp = new MetaProp("copy", ret.el.props.copy);
927
+ if (copyMetaProp.include) {
928
+ for (const propName of copyMetaProp.props) {
929
+ if (propName in oldProps) {
930
+ props[propName] = oldProps[propName];
931
+ (copyProps = copyProps || new Set()).add(propName);
932
+ }
933
+ }
934
+ }
935
+ else {
936
+ for (const propName in oldProps) {
937
+ if (!copyMetaProp.props.has(propName)) {
938
+ props[propName] = oldProps[propName];
939
+ (copyProps = copyProps || new Set()).add(propName);
940
+ }
941
+ }
942
+ }
943
+ copyChildren = copyMetaProp.includes("children");
944
+ }
945
+ }
946
+ const scope = ret.scope;
947
+ let childHydrationNodes;
948
+ let quietProps;
949
+ let hydrationMetaProp;
950
+ if (!getFlag(ret, DidCommit)) {
951
+ if (tag === Portal) {
952
+ if (ret.el.props.hydrate && typeof ret.el.props.hydrate !== "string") {
953
+ childHydrationNodes = adapter.adopt({
954
+ tag,
955
+ tagName: getTagName(tag),
956
+ node,
957
+ props,
958
+ scope,
959
+ });
960
+ if (childHydrationNodes) {
961
+ for (let i = 0; i < childHydrationNodes.length; i++) {
962
+ adapter.remove({
963
+ node: childHydrationNodes[i],
964
+ parentNode: node,
965
+ isNested: false,
966
+ });
967
+ }
968
+ }
969
+ }
970
+ }
971
+ else {
972
+ if (!node && hydrationNodes) {
973
+ const nextChild = hydrationNodes.shift();
974
+ if (typeof ret.el.props.hydrate === "string") {
975
+ hydrationMetaProp = new MetaProp("hydration", ret.el.props.hydrate);
976
+ if (hydrationMetaProp.include) {
977
+ // if we're in inclusive mode, we add all props to quietProps and
978
+ // remove props specified in the metaprop
979
+ quietProps = new Set(Object.keys(props));
980
+ for (const propName of hydrationMetaProp.props) {
981
+ quietProps.delete(propName);
982
+ }
983
+ }
984
+ else {
985
+ quietProps = hydrationMetaProp.props;
986
+ }
987
+ }
988
+ childHydrationNodes = adapter.adopt({
989
+ tag,
990
+ tagName: getTagName(tag),
991
+ node: nextChild,
992
+ props,
993
+ scope,
994
+ });
995
+ if (childHydrationNodes) {
996
+ node = nextChild;
997
+ for (let i = 0; i < childHydrationNodes.length; i++) {
998
+ adapter.remove({
999
+ node: childHydrationNodes[i],
1000
+ parentNode: node,
1001
+ isNested: false,
1002
+ });
1003
+ }
711
1004
  }
712
1005
  }
713
- if (flushMap1.size) {
714
- flushMaps.set(root, flushMap1);
1006
+ // TODO: For some reason, there are cases where the node is already set
1007
+ // and the DidCommit flag is false. Not checking for node fails a test
1008
+ // where a child dispatches an event in a schedule callback, the parent
1009
+ // listens for this event and refreshes.
1010
+ if (!node) {
1011
+ node = adapter.create({
1012
+ tag,
1013
+ tagName: getTagName(tag),
1014
+ props,
1015
+ scope,
1016
+ });
1017
+ }
1018
+ ret.value = node;
1019
+ }
1020
+ }
1021
+ if (tag !== Portal) {
1022
+ adapter.patch({
1023
+ tag,
1024
+ tagName: getTagName(tag),
1025
+ node,
1026
+ props,
1027
+ oldProps,
1028
+ scope,
1029
+ copyProps,
1030
+ isHydrating: !!childHydrationNodes,
1031
+ quietProps,
1032
+ });
1033
+ }
1034
+ if (!copyChildren) {
1035
+ const children = commitChildren(adapter, ret, ctx, scope, ret, 0, schedulePromises, hydrationMetaProp && !hydrationMetaProp.includes("children")
1036
+ ? undefined
1037
+ : childHydrationNodes);
1038
+ adapter.arrange({
1039
+ tag,
1040
+ tagName: getTagName(tag),
1041
+ node: node,
1042
+ props,
1043
+ children,
1044
+ oldProps,
1045
+ });
1046
+ }
1047
+ ret.oldProps = props;
1048
+ if (tag === Portal) {
1049
+ flush(adapter, ret.value);
1050
+ // The root passed to Portal elements are opaque to parents so we return
1051
+ // undefined here.
1052
+ return;
1053
+ }
1054
+ return node;
1055
+ }
1056
+ class MetaProp {
1057
+ constructor(propName, propValue) {
1058
+ this.include = true;
1059
+ this.props = new Set();
1060
+ let noBangs = true;
1061
+ let allBangs = true;
1062
+ const tokens = propValue.split(/[,\s]+/);
1063
+ for (let i = 0; i < tokens.length; i++) {
1064
+ const token = tokens[i].trim();
1065
+ if (!token) {
1066
+ continue;
1067
+ }
1068
+ else if (token.startsWith("!")) {
1069
+ noBangs = false;
1070
+ this.props.add(token.slice(1));
715
1071
  }
716
1072
  else {
717
- flushMaps.delete(root);
1073
+ allBangs = false;
1074
+ this.props.add(token);
718
1075
  }
719
1076
  }
1077
+ if (!allBangs && !noBangs) {
1078
+ console.error(`Invalid ${propName} prop "${propValue}".\nUse prop or !prop but not both.`);
1079
+ this.include = true;
1080
+ this.props.clear();
1081
+ }
1082
+ else {
1083
+ this.include = noBangs;
1084
+ }
1085
+ }
1086
+ includes(propName) {
1087
+ if (this.include) {
1088
+ return this.props.has(propName);
1089
+ }
1090
+ else {
1091
+ return !this.props.has(propName);
1092
+ }
1093
+ }
1094
+ }
1095
+ function contextContains(parent, child) {
1096
+ for (let current = child; current !== undefined; current = current.parent) {
1097
+ if (current === parent) {
1098
+ return true;
1099
+ }
1100
+ }
1101
+ return false;
1102
+ }
1103
+ // When rendering is done without a root, we use this special anonymous root to
1104
+ // make sure after callbacks are still called.
1105
+ const ANONYMOUS_ROOT = {};
1106
+ function flush(adapter, root, initiator) {
1107
+ if (root != null) {
1108
+ adapter.finalize(root);
1109
+ }
1110
+ if (typeof root !== "object" || root === null) {
1111
+ root = ANONYMOUS_ROOT;
1112
+ }
1113
+ // The initiator is the context which initiated the rendering process. If
1114
+ // initiator is defined we call and clear all flush callbacks which are
1115
+ // registered with the initiator or with a child context of the initiator,
1116
+ // because they are fully rendered.
1117
+ //
1118
+ // If no initiator is provided, we can call and clear all flush callbacks
1119
+ // which are not scheduling.
1120
+ const afterMap = afterMapByRoot.get(root);
1121
+ if (afterMap) {
1122
+ const afterMap1 = new Map();
1123
+ for (const [ctx, callbacks] of afterMap) {
1124
+ if (getFlag(ctx.ret, IsScheduling) ||
1125
+ (initiator && !contextContains(initiator, ctx))) {
1126
+ // copy over callbacks to the new map (defer them)
1127
+ afterMap.delete(ctx);
1128
+ afterMap1.set(ctx, callbacks);
1129
+ }
1130
+ }
1131
+ if (afterMap1.size) {
1132
+ afterMapByRoot.set(root, afterMap1);
1133
+ }
720
1134
  else {
721
- flushMaps.delete(root);
1135
+ afterMapByRoot.delete(root);
722
1136
  }
723
- for (const [ctx, callbacks] of flushMap) {
724
- const value = renderer.read(getValue(ctx.ret));
1137
+ for (const [ctx, callbacks] of afterMap) {
1138
+ const value = adapter.read(getValue(ctx.ret));
725
1139
  for (const callback of callbacks) {
726
1140
  callback(value);
727
1141
  }
728
1142
  }
729
1143
  }
730
1144
  }
731
- function unmount(renderer, host, ctx, ret) {
1145
+ function unmount(adapter, host, ctx, ret, isNested) {
1146
+ // TODO: set the IsUnmounted flag consistently for all retainers
1147
+ if (ret.fallback) {
1148
+ unmount(adapter, host, ctx, ret.fallback, isNested);
1149
+ ret.fallback = undefined;
1150
+ }
1151
+ if (getFlag(ret, IsResurrecting)) {
1152
+ return;
1153
+ }
1154
+ if (ret.lingerers) {
1155
+ for (let i = 0; i < ret.lingerers.length; i++) {
1156
+ const lingerers = ret.lingerers[i];
1157
+ if (lingerers) {
1158
+ for (const lingerer of lingerers) {
1159
+ unmount(adapter, host, ctx, lingerer, isNested);
1160
+ }
1161
+ }
1162
+ }
1163
+ ret.lingerers = undefined;
1164
+ }
732
1165
  if (typeof ret.el.tag === "function") {
733
- ctx = ret.ctx;
734
- unmountComponent(ctx);
1166
+ unmountComponent(ret.ctx, isNested);
1167
+ }
1168
+ else if (ret.el.tag === Fragment) {
1169
+ unmountChildren(adapter, host, ctx, ret, isNested);
735
1170
  }
736
1171
  else if (ret.el.tag === Portal) {
737
- host = ret;
738
- renderer.arrange(Portal, host.value, host.el.props, [], host.el.props, wrap(host.cachedChildValues));
739
- flush(renderer, host.value);
1172
+ unmountChildren(adapter, ret, ctx, ret, false);
1173
+ if (ret.value != null) {
1174
+ adapter.finalize(ret.value);
1175
+ }
740
1176
  }
741
- else if (ret.el.tag !== Fragment) {
742
- if (isEventTarget(ret.value)) {
743
- const records = getListenerRecords(ctx, host);
744
- for (let i = 0; i < records.length; i++) {
745
- const record = records[i];
746
- ret.value.removeEventListener(record.type, record.callback, record.options);
747
- }
1177
+ else {
1178
+ unmountChildren(adapter, ret, ctx, ret, true);
1179
+ if (getFlag(ret, DidCommit)) {
1180
+ if (ctx) {
1181
+ // Remove the value from every context which shares the same host.
1182
+ eventTarget.removeEventTargetDelegates(ctx.ctx, [ret.value], (ctx1) => ctx1[_ContextState].host === host);
1183
+ }
1184
+ adapter.remove({
1185
+ node: ret.value,
1186
+ parentNode: host.value,
1187
+ isNested,
1188
+ });
748
1189
  }
749
- renderer.dispose(ret.el.tag, ret.value, ret.el.props);
750
- host = ret;
751
1190
  }
752
- const children = wrap(ret.children);
753
- for (let i = 0; i < children.length; i++) {
1191
+ }
1192
+ function unmountChildren(adapter, host, ctx, ret, isNested) {
1193
+ if (ret.graveyard) {
1194
+ for (let i = 0; i < ret.graveyard.length; i++) {
1195
+ const child = ret.graveyard[i];
1196
+ unmount(adapter, host, ctx, child, isNested);
1197
+ }
1198
+ ret.graveyard = undefined;
1199
+ }
1200
+ for (let i = 0, children = _utils.wrap(ret.children); i < children.length; i++) {
754
1201
  const child = children[i];
755
1202
  if (typeof child === "object") {
756
- unmount(renderer, host, ctx, child);
1203
+ unmount(adapter, host, ctx, child, isNested);
757
1204
  }
758
1205
  }
759
1206
  }
760
- /*** CONTEXT FLAGS ***/
761
- /**
762
- * A flag which is true when the component is initialized or updated by an
763
- * ancestor component or the root render call.
764
- *
765
- * Used to determine things like whether the nearest host ancestor needs to be
766
- * rearranged.
767
- */
768
- const IsUpdating = 1 << 0;
769
- /**
770
- * A flag which is true when the component is synchronously executing.
771
- *
772
- * Used to guard against components triggering stack overflow or generator error.
773
- */
774
- const IsSyncExecuting = 1 << 1;
775
- /**
776
- * A flag which is true when the component is in a for...of loop.
777
- */
778
- const IsInForOfLoop = 1 << 2;
1207
+ const provisionMaps = new WeakMap();
1208
+ const scheduleMap = new WeakMap();
1209
+ const cleanupMap = new WeakMap();
1210
+ // keys are roots
1211
+ const afterMapByRoot = new WeakMap();
1212
+ // TODO: allow ContextState to be initialized for testing purposes
779
1213
  /**
780
- * A flag which is true when the component is in a for await...of loop.
1214
+ * @internal
1215
+ * The internal class which holds context data.
781
1216
  */
782
- const IsInForAwaitOfLoop = 1 << 3;
783
- /**
784
- * A flag which is true when the component starts the render loop but has not
785
- * yielded yet.
786
- *
787
- * Used to make sure that components yield at least once per loop.
788
- */
789
- const NeedsToYield = 1 << 4;
790
- /**
791
- * A flag used by async generator components in conjunction with the
792
- * onAvailable callback to mark whether new props can be pulled via the context
793
- * async iterator. See the Symbol.asyncIterator method and the
794
- * resumeCtxIterator function.
795
- */
796
- const PropsAvailable = 1 << 5;
797
- /**
798
- * A flag which is set when a component errors.
799
- *
800
- * This is mainly used to prevent some false positives in "component yields or
801
- * returns undefined" warnings. The reason we’re using this versus IsUnmounted
802
- * is a very troubling test (cascades sync generator parent and sync generator
803
- * child) where synchronous code causes a stack overflow error in a
804
- * non-deterministic way. Deeply disturbing stuff.
805
- */
806
- const IsErrored = 1 << 6;
807
- /**
808
- * A flag which is set when the component is unmounted. Unmounted components
809
- * are no longer in the element tree and cannot refresh or rerender.
810
- */
811
- const IsUnmounted = 1 << 7;
812
- /**
813
- * A flag which indicates that the component is a sync generator component.
814
- */
815
- const IsSyncGen = 1 << 8;
816
- /**
817
- * A flag which indicates that the component is an async generator component.
818
- */
819
- const IsAsyncGen = 1 << 9;
820
- /**
821
- * A flag which is set while schedule callbacks are called.
822
- */
823
- const IsScheduling = 1 << 10;
824
- /**
825
- * A flag which is set when a schedule callback calls refresh.
826
- */
827
- const IsSchedulingRefresh = 1 << 11;
828
- const provisionMaps = new WeakMap();
829
- const scheduleMap = new WeakMap();
830
- const cleanupMap = new WeakMap();
831
- // keys are roots
832
- const flushMaps = new WeakMap();
833
- /**
834
- * @internal
835
- * The internal class which holds context data.
836
- */
837
- class ContextImpl {
838
- constructor(renderer, root, host, parent, scope, ret) {
839
- this.f = 0;
840
- this.owner = new Context(this);
841
- this.renderer = renderer;
1217
+ class ContextState {
1218
+ constructor(adapter, root, host, parent, scope, ret) {
1219
+ this.adapter = adapter;
842
1220
  this.root = root;
843
1221
  this.host = host;
844
1222
  this.parent = parent;
1223
+ // This property must be set after this.parent is set because the Context
1224
+ // constructor reads this.parent.
1225
+ this.ctx = new Context(this);
845
1226
  this.scope = scope;
846
1227
  this.ret = ret;
847
1228
  this.iterator = undefined;
848
- this.inflightBlock = undefined;
849
- this.inflightValue = undefined;
850
- this.enqueuedBlock = undefined;
851
- this.enqueuedValue = undefined;
852
- this.onProps = undefined;
1229
+ this.inflight = undefined;
1230
+ this.enqueued = undefined;
1231
+ this.onPropsProvided = undefined;
853
1232
  this.onPropsRequested = undefined;
1233
+ this.pull = undefined;
1234
+ this.index = 0;
1235
+ this.schedule = undefined;
854
1236
  }
855
1237
  }
856
- const _ContextImpl = Symbol.for("crank.ContextImpl");
1238
+ const _ContextState = Symbol.for("crank.ContextState");
857
1239
  /**
858
1240
  * A class which is instantiated and passed to every component as its this
859
- * value. Contexts form a tree just like elements and all components in the
860
- * element tree are connected via contexts. Components can use this tree to
861
- * communicate data upwards via events and downwards via provisions.
1241
+ * value/second parameter. Contexts form a tree just like elements and all
1242
+ * components in the element tree are connected via contexts. Components can
1243
+ * use this tree to communicate data upwards via events and downwards via
1244
+ * provisions.
862
1245
  *
863
1246
  * @template [T=*] - The expected shape of the props passed to the component,
864
1247
  * or a component function. Used to strongly type the Context iterator methods.
@@ -866,17 +1249,18 @@ const _ContextImpl = Symbol.for("crank.ContextImpl");
866
1249
  * places such as the return value of refresh and the argument passed to
867
1250
  * schedule and cleanup callbacks.
868
1251
  */
869
- class Context {
1252
+ class Context extends eventTarget.CustomEventTarget {
870
1253
  // TODO: If we could make the constructor function take a nicer value, it
871
1254
  // would be useful for testing purposes.
872
- constructor(impl) {
873
- this[_ContextImpl] = impl;
1255
+ constructor(state) {
1256
+ super(state.parent ? state.parent.ctx : null);
1257
+ this[_ContextState] = state;
874
1258
  }
875
1259
  /**
876
1260
  * The current props of the associated element.
877
1261
  */
878
1262
  get props() {
879
- return this[_ContextImpl].ret.el.props;
1263
+ return this[_ContextState].ret.el.props;
880
1264
  }
881
1265
  /**
882
1266
  * The current value of the associated element.
@@ -884,47 +1268,51 @@ class Context {
884
1268
  * @deprecated
885
1269
  */
886
1270
  get value() {
887
- return this[_ContextImpl].renderer.read(getValue(this[_ContextImpl].ret));
1271
+ console.warn("Context.value is deprecated.");
1272
+ return this[_ContextState].adapter.read(getValue(this[_ContextState].ret));
1273
+ }
1274
+ get isExecuting() {
1275
+ return getFlag(this[_ContextState].ret, IsExecuting);
1276
+ }
1277
+ get isUnmounted() {
1278
+ return getFlag(this[_ContextState].ret, IsUnmounted);
888
1279
  }
889
1280
  *[Symbol.iterator]() {
890
- const ctx = this[_ContextImpl];
1281
+ const ctx = this[_ContextState];
1282
+ setFlag(ctx.ret, IsInForOfLoop);
891
1283
  try {
892
- ctx.f |= IsInForOfLoop;
893
- while (!(ctx.f & IsUnmounted)) {
894
- if (ctx.f & NeedsToYield) {
895
- throw new Error("Context iterated twice without a yield");
1284
+ while (!getFlag(ctx.ret, IsUnmounted) && !getFlag(ctx.ret, IsErrored)) {
1285
+ if (getFlag(ctx.ret, NeedsToYield)) {
1286
+ throw new Error(`<${getTagName(ctx.ret.el.tag)}> context iterated twice without a yield`);
896
1287
  }
897
1288
  else {
898
- ctx.f |= NeedsToYield;
1289
+ setFlag(ctx.ret, NeedsToYield);
899
1290
  }
900
1291
  yield ctx.ret.el.props;
901
1292
  }
902
1293
  }
903
1294
  finally {
904
- ctx.f &= -5;
1295
+ setFlag(ctx.ret, IsInForOfLoop, false);
905
1296
  }
906
1297
  }
907
1298
  async *[Symbol.asyncIterator]() {
908
- const ctx = this[_ContextImpl];
909
- if (ctx.f & IsSyncGen) {
910
- throw new Error("Use for...of in sync generator components");
911
- }
1299
+ const ctx = this[_ContextState];
1300
+ setFlag(ctx.ret, IsInForAwaitOfLoop);
912
1301
  try {
913
- ctx.f |= IsInForAwaitOfLoop;
914
- while (!(ctx.f & IsUnmounted)) {
915
- if (ctx.f & NeedsToYield) {
916
- throw new Error("Context iterated twice without a yield");
1302
+ while (!getFlag(ctx.ret, IsUnmounted) && !getFlag(ctx.ret, IsErrored)) {
1303
+ if (getFlag(ctx.ret, NeedsToYield)) {
1304
+ throw new Error(`<${getTagName(ctx.ret.el.tag)}> context iterated twice without a yield`);
917
1305
  }
918
1306
  else {
919
- ctx.f |= NeedsToYield;
1307
+ setFlag(ctx.ret, NeedsToYield);
920
1308
  }
921
- if (ctx.f & PropsAvailable) {
922
- ctx.f &= ~PropsAvailable;
1309
+ if (getFlag(ctx.ret, PropsAvailable)) {
1310
+ setFlag(ctx.ret, PropsAvailable, false);
923
1311
  yield ctx.ret.el.props;
924
1312
  }
925
1313
  else {
926
- const props = await new Promise((resolve) => (ctx.onProps = resolve));
927
- if (ctx.f & IsUnmounted) {
1314
+ const props = await new Promise((resolve) => (ctx.onPropsProvided = resolve));
1315
+ if (getFlag(ctx.ret, IsUnmounted) || getFlag(ctx.ret, IsErrored)) {
928
1316
  break;
929
1317
  }
930
1318
  yield props;
@@ -936,7 +1324,7 @@ class Context {
936
1324
  }
937
1325
  }
938
1326
  finally {
939
- ctx.f &= -9;
1327
+ setFlag(ctx.ret, IsInForAwaitOfLoop, false);
940
1328
  if (ctx.onPropsRequested) {
941
1329
  ctx.onPropsRequested();
942
1330
  ctx.onPropsRequested = undefined;
@@ -946,37 +1334,108 @@ class Context {
946
1334
  /**
947
1335
  * Re-executes a component.
948
1336
  *
949
- * @returns The rendered value of the component or a promise thereof if the
1337
+ * @param callback - Optional callback to execute before refresh
1338
+ * @returns The rendered result of the component or a promise thereof if the
950
1339
  * component or its children execute asynchronously.
951
- *
952
- * The refresh method works a little differently for async generator
953
- * components, in that it will resume the Context’s props async iterator
954
- * rather than resuming execution. This is because async generator components
955
- * are perpetually resumed independent of updates, and rely on the props
956
- * async iterator to suspend.
957
1340
  */
958
- refresh() {
959
- const ctx = this[_ContextImpl];
960
- if (ctx.f & IsUnmounted) {
961
- console.error("Component is unmounted");
962
- return ctx.renderer.read(undefined);
1341
+ refresh(callback) {
1342
+ const ctx = this[_ContextState];
1343
+ if (getFlag(ctx.ret, IsUnmounted)) {
1344
+ console.error(`Component <${getTagName(ctx.ret.el.tag)}> is unmounted. Check the isUnmounted property if necessary.`);
1345
+ return ctx.adapter.read(getValue(ctx.ret));
1346
+ }
1347
+ else if (getFlag(ctx.ret, IsExecuting)) {
1348
+ console.error(`Component <${getTagName(ctx.ret.el.tag)}> is already executing Check the isExecuting property if necessary.`);
1349
+ return ctx.adapter.read(getValue(ctx.ret));
1350
+ }
1351
+ if (callback) {
1352
+ const result = callback();
1353
+ if (_utils.isPromiseLike(result)) {
1354
+ return Promise.resolve(result).then(() => {
1355
+ if (!getFlag(ctx.ret, IsUnmounted)) {
1356
+ return this.refresh();
1357
+ }
1358
+ return ctx.adapter.read(getValue(ctx.ret));
1359
+ });
1360
+ }
963
1361
  }
964
- else if (ctx.f & IsSyncExecuting) {
965
- console.error("Component is already executing");
966
- return ctx.renderer.read(getValue(ctx.ret));
1362
+ let diff;
1363
+ const schedulePromises = [];
1364
+ try {
1365
+ setFlag(ctx.ret, IsRefreshing);
1366
+ diff = enqueueComponent(ctx);
1367
+ if (_utils.isPromiseLike(diff)) {
1368
+ return diff
1369
+ .then(() => ctx.adapter.read(commitComponent(ctx, schedulePromises)))
1370
+ .then((result) => {
1371
+ if (schedulePromises.length) {
1372
+ return Promise.all(schedulePromises).then(() => {
1373
+ return ctx.adapter.read(getValue(ctx.ret));
1374
+ });
1375
+ }
1376
+ return result;
1377
+ })
1378
+ .catch((err) => {
1379
+ const diff = propagateError(ctx, err, schedulePromises);
1380
+ if (diff) {
1381
+ return diff.then(() => {
1382
+ if (schedulePromises.length) {
1383
+ return Promise.all(schedulePromises).then(() => {
1384
+ return ctx.adapter.read(getValue(ctx.ret));
1385
+ });
1386
+ }
1387
+ return ctx.adapter.read(getValue(ctx.ret));
1388
+ });
1389
+ }
1390
+ if (schedulePromises.length) {
1391
+ return Promise.all(schedulePromises).then(() => {
1392
+ return ctx.adapter.read(getValue(ctx.ret));
1393
+ });
1394
+ }
1395
+ return ctx.adapter.read(getValue(ctx.ret));
1396
+ })
1397
+ .finally(() => setFlag(ctx.ret, IsRefreshing, false));
1398
+ }
1399
+ const result = ctx.adapter.read(commitComponent(ctx, schedulePromises));
1400
+ if (schedulePromises.length) {
1401
+ return Promise.all(schedulePromises).then(() => {
1402
+ return ctx.adapter.read(getValue(ctx.ret));
1403
+ });
1404
+ }
1405
+ return result;
1406
+ }
1407
+ catch (err) {
1408
+ // TODO: await schedulePromises
1409
+ const diff = propagateError(ctx, err, schedulePromises);
1410
+ if (diff) {
1411
+ return diff
1412
+ .then(() => {
1413
+ if (schedulePromises.length) {
1414
+ return Promise.all(schedulePromises).then(() => {
1415
+ return ctx.adapter.read(getValue(ctx.ret));
1416
+ });
1417
+ }
1418
+ })
1419
+ .then(() => ctx.adapter.read(getValue(ctx.ret)));
1420
+ }
1421
+ if (schedulePromises.length) {
1422
+ return Promise.all(schedulePromises).then(() => {
1423
+ return ctx.adapter.read(getValue(ctx.ret));
1424
+ });
1425
+ }
1426
+ return ctx.adapter.read(getValue(ctx.ret));
967
1427
  }
968
- const value = enqueueComponentRun(ctx);
969
- if (isPromiseLike(value)) {
970
- return value.then((value) => ctx.renderer.read(value));
1428
+ finally {
1429
+ if (!_utils.isPromiseLike(diff)) {
1430
+ setFlag(ctx.ret, IsRefreshing, false);
1431
+ }
971
1432
  }
972
- return ctx.renderer.read(value);
973
1433
  }
974
- /**
975
- * Registers a callback which fires when the component commits. Will only
976
- * fire once per callback and update.
977
- */
978
1434
  schedule(callback) {
979
- const ctx = this[_ContextImpl];
1435
+ if (!callback) {
1436
+ return new Promise((resolve) => this.schedule(resolve));
1437
+ }
1438
+ const ctx = this[_ContextState];
980
1439
  let callbacks = scheduleMap.get(ctx);
981
1440
  if (!callbacks) {
982
1441
  callbacks = new Set();
@@ -984,35 +1443,35 @@ class Context {
984
1443
  }
985
1444
  callbacks.add(callback);
986
1445
  }
987
- /**
988
- * Registers a callback which fires when the component’s children are
989
- * rendered into the root. Will only fire once per callback and render.
990
- */
991
- flush(callback) {
992
- const ctx = this[_ContextImpl];
993
- if (typeof ctx.root !== "object" || ctx.root === null) {
994
- return;
1446
+ after(callback) {
1447
+ if (!callback) {
1448
+ return new Promise((resolve) => this.after(resolve));
995
1449
  }
996
- let flushMap = flushMaps.get(ctx.root);
997
- if (!flushMap) {
998
- flushMap = new Map();
999
- flushMaps.set(ctx.root, flushMap);
1450
+ const ctx = this[_ContextState];
1451
+ const root = ctx.root || ANONYMOUS_ROOT;
1452
+ let afterMap = afterMapByRoot.get(root);
1453
+ if (!afterMap) {
1454
+ afterMap = new Map();
1455
+ afterMapByRoot.set(root, afterMap);
1000
1456
  }
1001
- let callbacks = flushMap.get(ctx);
1457
+ let callbacks = afterMap.get(ctx);
1002
1458
  if (!callbacks) {
1003
1459
  callbacks = new Set();
1004
- flushMap.set(ctx, callbacks);
1460
+ afterMap.set(ctx, callbacks);
1005
1461
  }
1006
1462
  callbacks.add(callback);
1007
1463
  }
1008
- /**
1009
- * Registers a callback which fires when the component unmounts. Will only
1010
- * fire once per callback.
1011
- */
1464
+ flush(callback) {
1465
+ console.error("Context.flush() method has been renamed to after()");
1466
+ this.after(callback);
1467
+ }
1012
1468
  cleanup(callback) {
1013
- const ctx = this[_ContextImpl];
1014
- if (ctx.f & IsUnmounted) {
1015
- const value = ctx.renderer.read(getValue(ctx.ret));
1469
+ if (!callback) {
1470
+ return new Promise((resolve) => this.cleanup(resolve));
1471
+ }
1472
+ const ctx = this[_ContextState];
1473
+ if (getFlag(ctx.ret, IsUnmounted)) {
1474
+ const value = ctx.adapter.read(getValue(ctx.ret));
1016
1475
  callback(value);
1017
1476
  return;
1018
1477
  }
@@ -1024,7 +1483,7 @@ class Context {
1024
1483
  callbacks.add(callback);
1025
1484
  }
1026
1485
  consume(key) {
1027
- for (let ctx = this[_ContextImpl].parent; ctx !== undefined; ctx = ctx.parent) {
1486
+ for (let ctx = this[_ContextState].parent; ctx !== undefined; ctx = ctx.parent) {
1028
1487
  const provisions = provisionMaps.get(ctx);
1029
1488
  if (provisions && provisions.has(key)) {
1030
1489
  return provisions.get(key);
@@ -1032,7 +1491,7 @@ class Context {
1032
1491
  }
1033
1492
  }
1034
1493
  provide(key, value) {
1035
- const ctx = this[_ContextImpl];
1494
+ const ctx = this[_ContextState];
1036
1495
  let provisions = provisionMaps.get(ctx);
1037
1496
  if (!provisions) {
1038
1497
  provisions = new Map();
@@ -1040,976 +1499,780 @@ class Context {
1040
1499
  }
1041
1500
  provisions.set(key, value);
1042
1501
  }
1043
- addEventListener(type, listener, options) {
1044
- const ctx = this[_ContextImpl];
1045
- let listeners;
1046
- if (!isListenerOrListenerObject(listener)) {
1047
- return;
1502
+ [eventTarget.CustomEventTarget.dispatchEventOnSelf](ev) {
1503
+ const ctx = this[_ContextState];
1504
+ // dispatchEvent calls the prop callback if it exists
1505
+ let propCallback = ctx.ret.el.props["on" + ev.type];
1506
+ if (typeof propCallback === "function") {
1507
+ propCallback(ev);
1048
1508
  }
1049
1509
  else {
1050
- const listeners1 = listenersMap.get(ctx);
1051
- if (listeners1) {
1052
- listeners = listeners1;
1053
- }
1054
- else {
1055
- listeners = [];
1056
- listenersMap.set(ctx, listeners);
1057
- }
1058
- }
1059
- options = normalizeListenerOptions(options);
1060
- let callback;
1061
- if (typeof listener === "object") {
1062
- callback = () => listener.handleEvent.apply(listener, arguments);
1063
- }
1064
- else {
1065
- callback = listener;
1066
- }
1067
- const record = { type, listener, callback, options };
1068
- if (options.once) {
1069
- record.callback = function () {
1070
- const i = listeners.indexOf(record);
1071
- if (i !== -1) {
1072
- listeners.splice(i, 1);
1073
- }
1074
- return callback.apply(this, arguments);
1075
- };
1076
- }
1077
- if (listeners.some((record1) => record.type === record1.type &&
1078
- record.listener === record1.listener &&
1079
- !record.options.capture === !record1.options.capture)) {
1080
- return;
1081
- }
1082
- listeners.push(record);
1083
- // TODO: is it possible to separate out the EventTarget delegation logic
1084
- for (const value of getChildValues(ctx.ret)) {
1085
- if (isEventTarget(value)) {
1086
- value.addEventListener(record.type, record.callback, record.options);
1087
- }
1088
- }
1089
- }
1090
- removeEventListener(type, listener, options) {
1091
- const ctx = this[_ContextImpl];
1092
- const listeners = listenersMap.get(ctx);
1093
- if (listeners == null || !isListenerOrListenerObject(listener)) {
1094
- return;
1095
- }
1096
- const options1 = normalizeListenerOptions(options);
1097
- const i = listeners.findIndex((record) => record.type === type &&
1098
- record.listener === listener &&
1099
- !record.options.capture === !options1.capture);
1100
- if (i === -1) {
1101
- return;
1102
- }
1103
- const record = listeners[i];
1104
- listeners.splice(i, 1);
1105
- // TODO: is it possible to separate out the EventTarget delegation logic
1106
- for (const value of getChildValues(ctx.ret)) {
1107
- if (isEventTarget(value)) {
1108
- value.removeEventListener(record.type, record.callback, record.options);
1109
- }
1110
- }
1111
- }
1112
- dispatchEvent(ev) {
1113
- const ctx = this[_ContextImpl];
1114
- const path = [];
1115
- for (let parent = ctx.parent; parent !== undefined; parent = parent.parent) {
1116
- path.push(parent);
1117
- }
1118
- // We patch the stopImmediatePropagation method because ev.cancelBubble
1119
- // only informs us if stopPropagation was called and there are no
1120
- // properties which inform us if stopImmediatePropagation was called.
1121
- let immediateCancelBubble = false;
1122
- const stopImmediatePropagation = ev.stopImmediatePropagation;
1123
- setEventProperty(ev, "stopImmediatePropagation", () => {
1124
- immediateCancelBubble = true;
1125
- return stopImmediatePropagation.call(ev);
1126
- });
1127
- setEventProperty(ev, "target", ctx.owner);
1128
- // The only possible errors in this block are errors thrown by callbacks,
1129
- // and dispatchEvent will only log these errors rather than throwing
1130
- // them. Therefore, we place all code in a try block, log errors in the
1131
- // catch block, and use an unsafe return statement in the finally block.
1132
- //
1133
- // Each early return within the try block returns true because while the
1134
- // return value is overridden in the finally block, TypeScript
1135
- // (justifiably) does not recognize the unsafe return statement.
1136
- try {
1137
- setEventProperty(ev, "eventPhase", CAPTURING_PHASE);
1138
- for (let i = path.length - 1; i >= 0; i--) {
1139
- const target = path[i];
1140
- const listeners = listenersMap.get(target);
1141
- if (listeners) {
1142
- setEventProperty(ev, "currentTarget", target.owner);
1143
- for (const record of listeners) {
1144
- if (record.type === ev.type && record.options.capture) {
1145
- try {
1146
- record.callback.call(target.owner, ev);
1147
- }
1148
- catch (err) {
1149
- console.error(err);
1150
- }
1151
- if (immediateCancelBubble) {
1152
- return true;
1153
- }
1154
- }
1510
+ for (const propName in ctx.ret.el.props) {
1511
+ if (propName.toLowerCase() === "on" + ev.type.toLowerCase()) {
1512
+ propCallback = ctx.ret.el.props[propName];
1513
+ if (typeof propCallback === "function") {
1514
+ propCallback(ev);
1155
1515
  }
1156
1516
  }
1157
- if (ev.cancelBubble) {
1158
- return true;
1159
- }
1160
1517
  }
1161
- {
1162
- setEventProperty(ev, "eventPhase", AT_TARGET);
1163
- setEventProperty(ev, "currentTarget", ctx.owner);
1164
- // dispatchEvent calls the prop callback if it exists
1165
- let propCallback = ctx.ret.el.props["on" + ev.type];
1166
- if (typeof propCallback === "function") {
1167
- propCallback(ev);
1168
- if (immediateCancelBubble || ev.cancelBubble) {
1169
- return true;
1170
- }
1171
- }
1172
- else {
1173
- // Checks for camel-cased event props
1174
- for (const propName in ctx.ret.el.props) {
1175
- if (propName.toLowerCase() === "on" + ev.type.toLowerCase()) {
1176
- propCallback = ctx.ret.el.props[propName];
1177
- if (typeof propCallback === "function") {
1178
- propCallback(ev);
1179
- if (immediateCancelBubble || ev.cancelBubble) {
1180
- return true;
1181
- }
1182
- }
1183
- }
1184
- }
1185
- }
1186
- const listeners = listenersMap.get(ctx);
1187
- if (listeners) {
1188
- for (const record of listeners) {
1189
- if (record.type === ev.type) {
1190
- try {
1191
- record.callback.call(ctx.owner, ev);
1192
- }
1193
- catch (err) {
1194
- console.error(err);
1195
- }
1196
- if (immediateCancelBubble) {
1197
- return true;
1198
- }
1199
- }
1200
- }
1201
- if (ev.cancelBubble) {
1202
- return true;
1203
- }
1204
- }
1205
- }
1206
- if (ev.bubbles) {
1207
- setEventProperty(ev, "eventPhase", BUBBLING_PHASE);
1208
- for (let i = 0; i < path.length; i++) {
1209
- const target = path[i];
1210
- const listeners = listenersMap.get(target);
1211
- if (listeners) {
1212
- setEventProperty(ev, "currentTarget", target.owner);
1213
- for (const record of listeners) {
1214
- if (record.type === ev.type && !record.options.capture) {
1215
- try {
1216
- record.callback.call(target.owner, ev);
1217
- }
1218
- catch (err) {
1219
- console.error(err);
1220
- }
1221
- if (immediateCancelBubble) {
1222
- return true;
1223
- }
1224
- }
1225
- }
1226
- }
1227
- if (ev.cancelBubble) {
1228
- return true;
1229
- }
1230
- }
1231
- }
1232
- }
1233
- finally {
1234
- setEventProperty(ev, "eventPhase", NONE);
1235
- setEventProperty(ev, "currentTarget", null);
1236
- // eslint-disable-next-line no-unsafe-finally
1237
- return !ev.defaultPrevented;
1238
- }
1239
- }
1240
- }
1241
- /*** PRIVATE CONTEXT FUNCTIONS ***/
1242
- function ctxContains(parent, child) {
1243
- for (let current = child; current !== undefined; current = current.parent) {
1244
- if (current === parent) {
1245
- return true;
1246
1518
  }
1247
1519
  }
1248
- return false;
1249
1520
  }
1250
- function updateComponent(renderer, root, host, parent, scope, ret, oldProps, hydrationData) {
1521
+ function diffComponent(adapter, root, host, parent, scope, ret) {
1251
1522
  let ctx;
1252
- if (oldProps) {
1523
+ if (ret.ctx) {
1253
1524
  ctx = ret.ctx;
1254
- if (ctx.f & IsSyncExecuting) {
1255
- console.error("Component is already executing");
1256
- return ret.cachedChildValues;
1525
+ if (getFlag(ctx.ret, IsExecuting)) {
1526
+ console.error(`Component <${getTagName(ctx.ret.el.tag)}> is already executing`);
1527
+ return;
1528
+ }
1529
+ else if (ctx.schedule) {
1530
+ return ctx.schedule.promise.then(() => {
1531
+ return diffComponent(adapter, root, host, parent, scope, ret);
1532
+ });
1257
1533
  }
1258
1534
  }
1259
1535
  else {
1260
- ctx = ret.ctx = new ContextImpl(renderer, root, host, parent, scope, ret);
1536
+ ctx = ret.ctx = new ContextState(adapter, root, host, parent, scope, ret);
1261
1537
  }
1262
- ctx.f |= IsUpdating;
1263
- return enqueueComponentRun(ctx, hydrationData);
1538
+ setFlag(ctx.ret, IsUpdating);
1539
+ return enqueueComponent(ctx);
1264
1540
  }
1265
- function updateComponentChildren(ctx, children, hydrationData) {
1266
- if (ctx.f & IsUnmounted) {
1267
- return;
1268
- }
1269
- else if (ctx.f & IsErrored) {
1270
- // This branch is necessary for some race conditions where this function is
1271
- // called after iterator.throw() in async generator components.
1541
+ function diffComponentChildren(ctx, children, isYield) {
1542
+ if (getFlag(ctx.ret, IsUnmounted) || getFlag(ctx.ret, IsErrored)) {
1272
1543
  return;
1273
1544
  }
1274
1545
  else if (children === undefined) {
1275
- console.error("A component has returned or yielded undefined. If this was intentional, return or yield null instead.");
1546
+ console.error(`Component <${getTagName(ctx.ret.el.tag)}> has ${isYield ? "yielded" : "returned"} undefined. If this was intentional, ${isYield ? "yield" : "return"} null instead.`);
1276
1547
  }
1277
- let childValues;
1548
+ let diff;
1278
1549
  try {
1279
- // TODO: WAT
1550
+ // TODO: Use a different flag here to indicate the component is
1551
+ // synchronously rendering children
1280
1552
  // We set the isExecuting flag in case a child component dispatches an event
1281
1553
  // which bubbles to this component and causes a synchronous refresh().
1282
- ctx.f |= IsSyncExecuting;
1283
- childValues = diffChildren(ctx.renderer, ctx.root, ctx.host, ctx, ctx.scope, ctx.ret, narrow(children), hydrationData);
1284
- }
1285
- finally {
1286
- ctx.f &= -3;
1287
- }
1288
- if (isPromiseLike(childValues)) {
1289
- ctx.ret.inflightValue = childValues.then((childValues) => commitComponent(ctx, childValues));
1290
- return ctx.ret.inflightValue;
1291
- }
1292
- return commitComponent(ctx, childValues);
1293
- }
1294
- function commitComponent(ctx, values) {
1295
- if (ctx.f & IsUnmounted) {
1296
- return;
1297
- }
1298
- const listeners = listenersMap.get(ctx);
1299
- if (listeners && listeners.length) {
1300
- for (let i = 0; i < values.length; i++) {
1301
- const value = values[i];
1302
- if (isEventTarget(value)) {
1303
- for (let j = 0; j < listeners.length; j++) {
1304
- const record = listeners[j];
1305
- value.addEventListener(record.type, record.callback, record.options);
1306
- }
1307
- }
1308
- }
1309
- }
1310
- const oldValues = wrap(ctx.ret.cachedChildValues);
1311
- let value = (ctx.ret.cachedChildValues = unwrap(values));
1312
- if (ctx.f & IsScheduling) {
1313
- ctx.f |= IsSchedulingRefresh;
1314
- }
1315
- else if (!(ctx.f & IsUpdating)) {
1316
- // If we’re not updating the component, which happens when components are
1317
- // refreshed, or when async generator components iterate, we have to do a
1318
- // little bit housekeeping when a component’s child values have changed.
1319
- if (!arrayEqual(oldValues, values)) {
1320
- const records = getListenerRecords(ctx.parent, ctx.host);
1321
- if (records.length) {
1322
- for (let i = 0; i < values.length; i++) {
1323
- const value = values[i];
1324
- if (isEventTarget(value)) {
1325
- for (let j = 0; j < records.length; j++) {
1326
- const record = records[j];
1327
- value.addEventListener(record.type, record.callback, record.options);
1328
- }
1329
- }
1330
- }
1331
- }
1332
- // rearranging the nearest ancestor host element
1333
- const host = ctx.host;
1334
- const oldHostValues = wrap(host.cachedChildValues);
1335
- invalidate(ctx, host);
1336
- const hostValues = getChildValues(host);
1337
- ctx.renderer.arrange(host.el.tag, host.value, host.el.props, hostValues,
1338
- // props and oldProps are the same because the host isn’t updated.
1339
- host.el.props, oldHostValues);
1554
+ setFlag(ctx.ret, IsExecuting);
1555
+ diff = diffChildren(ctx.adapter, ctx.root, ctx.host, ctx, ctx.scope, ctx.ret, narrow(children));
1556
+ if (diff) {
1557
+ diff = diff.catch((err) => handleChildError(ctx, err));
1340
1558
  }
1341
- flush(ctx.renderer, ctx.root, ctx);
1342
1559
  }
1343
- const callbacks = scheduleMap.get(ctx);
1344
- if (callbacks) {
1345
- scheduleMap.delete(ctx);
1346
- ctx.f |= IsScheduling;
1347
- const value1 = ctx.renderer.read(value);
1348
- for (const callback of callbacks) {
1349
- callback(value1);
1350
- }
1351
- ctx.f &= -1025;
1352
- // Handles an edge case where refresh() is called during a schedule().
1353
- if (ctx.f & IsSchedulingRefresh) {
1354
- ctx.f &= -2049;
1355
- value = getValue(ctx.ret);
1356
- }
1357
- }
1358
- ctx.f &= -2;
1359
- return value;
1360
- }
1361
- function invalidate(ctx, host) {
1362
- for (let parent = ctx.parent; parent !== undefined && parent.host === host; parent = parent.parent) {
1363
- parent.ret.cachedChildValues = undefined;
1364
- }
1365
- host.cachedChildValues = undefined;
1366
- }
1367
- function arrayEqual(arr1, arr2) {
1368
- if (arr1.length !== arr2.length) {
1369
- return false;
1560
+ catch (err) {
1561
+ diff = handleChildError(ctx, err);
1370
1562
  }
1371
- for (let i = 0; i < arr1.length; i++) {
1372
- const value1 = arr1[i];
1373
- const value2 = arr2[i];
1374
- if (value1 !== value2) {
1375
- return false;
1376
- }
1563
+ finally {
1564
+ setFlag(ctx.ret, IsExecuting, false);
1377
1565
  }
1378
- return true;
1566
+ return diff;
1379
1567
  }
1380
1568
  /** Enqueues and executes the component associated with the context. */
1381
- function enqueueComponentRun(ctx, hydrationData) {
1382
- if (ctx.f & IsAsyncGen && !(ctx.f & IsInForOfLoop)) {
1383
- if (hydrationData !== undefined) {
1384
- throw new Error("Hydration error");
1385
- }
1386
- // This branch will run for non-initial renders of async generator
1387
- // components when they are not in for...of loops. When in a for...of loop,
1388
- // async generator components will behave normally.
1389
- //
1390
- // Async gen componennts can be in one of three states:
1391
- //
1392
- // 1. propsAvailable flag is true: "available"
1393
- //
1394
- // The component is suspended somewhere in the loop. When the component
1395
- // reaches the bottom of the loop, it will run again with the next props.
1396
- //
1397
- // 2. onAvailable callback is defined: "suspended"
1398
- //
1399
- // The component has suspended at the bottom of the loop and is waiting
1400
- // for new props.
1401
- //
1402
- // 3. neither 1 or 2: "Running"
1403
- //
1404
- // The component is suspended somewhere in the loop. When the component
1405
- // reaches the bottom of the loop, it will suspend.
1406
- //
1407
- // Components will never be both available and suspended at
1408
- // the same time.
1409
- //
1410
- // If the component is at the loop bottom, this means that the next value
1411
- // produced by the component will have the most up to date props, so we can
1412
- // simply return the current inflight value. Otherwise, we have to wait for
1413
- // the bottom of the loop to be reached before returning the inflight
1414
- // value.
1415
- const isAtLoopbottom = ctx.f & IsInForAwaitOfLoop && !ctx.onProps;
1416
- resumePropsAsyncIterator(ctx);
1417
- if (isAtLoopbottom) {
1418
- if (ctx.inflightBlock == null) {
1419
- ctx.inflightBlock = new Promise((resolve) => (ctx.onPropsRequested = resolve));
1420
- }
1421
- return ctx.inflightBlock.then(() => {
1422
- ctx.inflightBlock = undefined;
1423
- return ctx.inflightValue;
1424
- });
1425
- }
1426
- return ctx.inflightValue;
1427
- }
1428
- else if (!ctx.inflightBlock) {
1429
- try {
1430
- const [block, value] = runComponent(ctx, hydrationData);
1431
- if (block) {
1432
- ctx.inflightBlock = block
1433
- // TODO: there is some fuckery going on here related to async
1434
- // generator components resuming when they’re meant to be returned.
1435
- .then((v) => v)
1436
- .finally(() => advanceComponent(ctx));
1437
- // stepComponent will only return a block if the value is asynchronous
1438
- ctx.inflightValue = value;
1439
- }
1440
- return value;
1441
- }
1442
- catch (err) {
1443
- if (!(ctx.f & IsUpdating)) {
1444
- if (!ctx.parent) {
1445
- throw err;
1446
- }
1447
- return propagateError(ctx.parent, err);
1448
- }
1449
- throw err;
1450
- }
1451
- }
1452
- else if (!ctx.enqueuedBlock) {
1453
- if (hydrationData !== undefined) {
1454
- throw new Error("Hydration error");
1455
- }
1456
- // We need to assign enqueuedBlock and enqueuedValue synchronously, hence
1457
- // the Promise constructor call here.
1458
- let resolveEnqueuedBlock;
1459
- ctx.enqueuedBlock = new Promise((resolve) => (resolveEnqueuedBlock = resolve));
1460
- ctx.enqueuedValue = ctx.inflightBlock.then(() => {
1461
- try {
1462
- const [block, value] = runComponent(ctx);
1463
- if (block) {
1464
- resolveEnqueuedBlock(block.finally(() => advanceComponent(ctx)));
1465
- }
1466
- return value;
1467
- }
1468
- catch (err) {
1469
- if (!(ctx.f & IsUpdating)) {
1470
- if (!ctx.parent) {
1471
- throw err;
1472
- }
1473
- return propagateError(ctx.parent, err);
1474
- }
1475
- throw err;
1476
- }
1477
- });
1478
- }
1479
- return ctx.enqueuedValue;
1569
+ function enqueueComponent(ctx) {
1570
+ if (!ctx.inflight) {
1571
+ const [block, diff] = runComponent(ctx);
1572
+ if (block) {
1573
+ // if block is a promise, diff is a promise
1574
+ ctx.inflight = [block.finally(() => advanceComponent(ctx)), diff];
1575
+ }
1576
+ return diff;
1577
+ }
1578
+ else if (!ctx.enqueued) {
1579
+ // The enqueuedBlock and enqueuedDiff properties must be set
1580
+ // simultaneously, hence the usage of the Promise constructor.
1581
+ let resolve;
1582
+ ctx.enqueued = [
1583
+ new Promise((resolve1) => (resolve = resolve1)).finally(() => advanceComponent(ctx)),
1584
+ ctx.inflight[0].finally(() => {
1585
+ const [block, diff] = runComponent(ctx);
1586
+ resolve(block);
1587
+ return diff;
1588
+ }),
1589
+ ];
1590
+ }
1591
+ return ctx.enqueued[1];
1480
1592
  }
1481
1593
  /** Called when the inflight block promise settles. */
1482
1594
  function advanceComponent(ctx) {
1483
- if (ctx.f & IsAsyncGen && !(ctx.f & IsInForOfLoop)) {
1484
- return;
1485
- }
1486
- ctx.inflightBlock = ctx.enqueuedBlock;
1487
- ctx.inflightValue = ctx.enqueuedValue;
1488
- ctx.enqueuedBlock = undefined;
1489
- ctx.enqueuedValue = undefined;
1595
+ ctx.inflight = ctx.enqueued;
1596
+ ctx.enqueued = undefined;
1490
1597
  }
1491
1598
  /**
1492
- * This function is responsible for executing the component and handling all
1493
- * the different component types. We cannot identify whether a component is a
1494
- * generator or async without calling it and inspecting the return value.
1599
+ * This function is responsible for executing components, and handling the
1600
+ * different component types.
1495
1601
  *
1496
- * @returns {[block, value]} A tuple where
1497
- * block - A possible promise which represents the duration during which the
1498
- * component is blocked from updating.
1499
- * value - A possible promise resolving to the rendered value of children.
1602
+ * @returns {[block, diff]} A tuple where:
1603
+ * - block is a promise or undefined which represents the duration during which
1604
+ * the component is blocked.
1605
+ * - diff is a promise or undefined which represents the duration for diffing
1606
+ * of children.
1500
1607
  *
1501
- * Each component type will block according to the type of the component.
1502
- * - Sync function components never block and will transparently pass updates
1503
- * to children.
1504
- * - Async function components and async generator components block while
1505
- * executing itself, but will not block for async children.
1506
- * - Sync generator components block while any children are executing, because
1507
- * they are expected to only resume when they’ve actually rendered.
1608
+ * While a component is blocked, further updates to the component are enqueued.
1609
+ *
1610
+ * Each component type blocks according to its implementation:
1611
+ * - Sync function components never block; when props or state change,
1612
+ * updates are immediately passed to children.
1613
+ * - Async function components block only while awaiting their own async work
1614
+ * (e.g., during an await), but do not block while their async children are rendering.
1615
+ * - Sync generator components block while their children are rendering;
1616
+ * they only resume once their children have finished.
1617
+ * - Async generator components can block in two different ways:
1618
+ * - By default, they behave like sync generator components, blocking while
1619
+ * the component or its children are rendering.
1620
+ * - Within a for await...of loop, they block only while waiting for new
1621
+ * props to be requested, and not while children are rendering.
1508
1622
  */
1509
- function runComponent(ctx, hydrationData) {
1623
+ function runComponent(ctx) {
1624
+ if (getFlag(ctx.ret, IsUnmounted)) {
1625
+ return [undefined, undefined];
1626
+ }
1510
1627
  const ret = ctx.ret;
1511
1628
  const initial = !ctx.iterator;
1512
1629
  if (initial) {
1513
- resumePropsAsyncIterator(ctx);
1514
- ctx.f |= IsSyncExecuting;
1515
- clearEventListeners(ctx);
1516
- let result;
1630
+ setFlag(ctx.ret, IsExecuting);
1631
+ eventTarget.clearEventListeners(ctx.ctx);
1632
+ let returned;
1517
1633
  try {
1518
- result = ret.el.tag.call(ctx.owner, ret.el.props, ctx.owner);
1634
+ returned = ret.el.tag.call(ctx.ctx, ret.el.props, ctx.ctx);
1519
1635
  }
1520
1636
  catch (err) {
1521
- ctx.f |= IsErrored;
1637
+ setFlag(ctx.ret, IsErrored);
1522
1638
  throw err;
1523
1639
  }
1524
1640
  finally {
1525
- ctx.f &= -3;
1641
+ setFlag(ctx.ret, IsExecuting, false);
1526
1642
  }
1527
- if (isIteratorLike(result)) {
1528
- ctx.iterator = result;
1643
+ if (_utils.isIteratorLike(returned)) {
1644
+ ctx.iterator = returned;
1529
1645
  }
1530
- else if (isPromiseLike(result)) {
1531
- // async function component
1532
- const result1 = result instanceof Promise ? result : Promise.resolve(result);
1533
- const value = result1.then((result) => updateComponentChildren(ctx, result, hydrationData), (err) => {
1534
- ctx.f |= IsErrored;
1535
- throw err;
1536
- });
1537
- return [result1.catch(NOOP), value];
1538
- }
1539
- else {
1646
+ else if (!_utils.isPromiseLike(returned)) {
1540
1647
  // sync function component
1541
1648
  return [
1542
1649
  undefined,
1543
- updateComponentChildren(ctx, result, hydrationData),
1650
+ diffComponentChildren(ctx, returned, false),
1651
+ ];
1652
+ }
1653
+ else {
1654
+ // async function component
1655
+ const returned1 = returned instanceof Promise ? returned : Promise.resolve(returned);
1656
+ return [
1657
+ returned1.catch(NOOP),
1658
+ returned1.then((returned) => diffComponentChildren(ctx, returned, false), (err) => {
1659
+ setFlag(ctx.ret, IsErrored);
1660
+ throw err;
1661
+ }),
1544
1662
  ];
1545
1663
  }
1546
- }
1547
- else if (hydrationData !== undefined) {
1548
- // hydration data should only be passed on the initial render
1549
- throw new Error("Hydration error");
1550
1664
  }
1551
1665
  let iteration;
1552
1666
  if (initial) {
1553
1667
  try {
1554
- ctx.f |= IsSyncExecuting;
1668
+ setFlag(ctx.ret, IsExecuting);
1555
1669
  iteration = ctx.iterator.next();
1556
1670
  }
1557
1671
  catch (err) {
1558
- ctx.f |= IsErrored;
1672
+ setFlag(ctx.ret, IsErrored);
1559
1673
  throw err;
1560
1674
  }
1561
1675
  finally {
1562
- ctx.f &= -3;
1676
+ setFlag(ctx.ret, IsExecuting, false);
1563
1677
  }
1564
- if (isPromiseLike(iteration)) {
1565
- ctx.f |= IsAsyncGen;
1678
+ if (_utils.isPromiseLike(iteration)) {
1679
+ setFlag(ctx.ret, IsAsyncGen);
1566
1680
  }
1567
1681
  else {
1568
- ctx.f |= IsSyncGen;
1682
+ setFlag(ctx.ret, IsSyncGen);
1569
1683
  }
1570
1684
  }
1571
- if (ctx.f & IsSyncGen) {
1685
+ if (getFlag(ctx.ret, IsSyncGen)) {
1572
1686
  // sync generator component
1573
1687
  if (!initial) {
1574
1688
  try {
1575
- ctx.f |= IsSyncExecuting;
1576
- iteration = ctx.iterator.next(ctx.renderer.read(getValue(ret)));
1689
+ setFlag(ctx.ret, IsExecuting);
1690
+ const oldResult = ctx.adapter.read(getValue(ctx.ret));
1691
+ iteration = ctx.iterator.next(oldResult);
1577
1692
  }
1578
1693
  catch (err) {
1579
- ctx.f |= IsErrored;
1694
+ setFlag(ctx.ret, IsErrored);
1580
1695
  throw err;
1581
1696
  }
1582
1697
  finally {
1583
- ctx.f &= -3;
1698
+ setFlag(ctx.ret, IsExecuting, false);
1584
1699
  }
1585
1700
  }
1586
- if (isPromiseLike(iteration)) {
1701
+ if (_utils.isPromiseLike(iteration)) {
1587
1702
  throw new Error("Mixed generator component");
1588
1703
  }
1589
- if (ctx.f & IsInForOfLoop &&
1590
- !(ctx.f & NeedsToYield) &&
1591
- !(ctx.f & IsUnmounted)) {
1592
- console.error("Component yielded more than once in for...of loop");
1704
+ if (getFlag(ctx.ret, IsInForOfLoop) &&
1705
+ !getFlag(ctx.ret, NeedsToYield) &&
1706
+ !getFlag(ctx.ret, IsUnmounted) &&
1707
+ !getFlag(ctx.ret, IsScheduling)) {
1708
+ console.error(`Component <${getTagName(ctx.ret.el.tag)}> yielded/returned more than once in for...of loop`);
1593
1709
  }
1594
- ctx.f &= -17;
1710
+ setFlag(ctx.ret, NeedsToYield, false);
1595
1711
  if (iteration.done) {
1596
- ctx.f &= -257;
1712
+ setFlag(ctx.ret, IsSyncGen, false);
1597
1713
  ctx.iterator = undefined;
1598
1714
  }
1599
- let value;
1600
- try {
1601
- value = updateComponentChildren(ctx,
1602
- // Children can be void so we eliminate that here
1603
- iteration.value, hydrationData);
1604
- if (isPromiseLike(value)) {
1605
- value = value.catch((err) => handleChildError(ctx, err));
1606
- }
1607
- }
1608
- catch (err) {
1609
- value = handleChildError(ctx, err);
1610
- }
1611
- const block = isPromiseLike(value) ? value.catch(NOOP) : undefined;
1612
- return [block, value];
1715
+ const diff = diffComponentChildren(ctx, iteration.value, !iteration.done);
1716
+ const block = _utils.isPromiseLike(diff) ? diff.catch(NOOP) : undefined;
1717
+ return [block, diff];
1613
1718
  }
1614
1719
  else {
1615
- if (ctx.f & IsInForOfLoop) {
1616
- // Async generator component using for...of loops behave similar to sync
1617
- // generator components. This allows for easier refactoring of sync to
1618
- // async generator components.
1720
+ if (getFlag(ctx.ret, IsInForAwaitOfLoop)) {
1721
+ // initializes the async generator loop
1722
+ pullComponent(ctx, iteration);
1723
+ const block = resumePropsAsyncIterator(ctx);
1724
+ return [block, ctx.pull && ctx.pull.diff];
1725
+ }
1726
+ else {
1727
+ // We call resumePropsAsyncIterator in case the component exits the
1728
+ // for...of loop
1729
+ resumePropsAsyncIterator(ctx);
1619
1730
  if (!initial) {
1620
1731
  try {
1621
- ctx.f |= IsSyncExecuting;
1622
- iteration = ctx.iterator.next(ctx.renderer.read(getValue(ret)));
1732
+ setFlag(ctx.ret, IsExecuting);
1733
+ const oldResult = ctx.adapter.read(getValue(ctx.ret));
1734
+ iteration = ctx.iterator.next(oldResult);
1623
1735
  }
1624
1736
  catch (err) {
1625
- ctx.f |= IsErrored;
1737
+ setFlag(ctx.ret, IsErrored);
1626
1738
  throw err;
1627
1739
  }
1628
1740
  finally {
1629
- ctx.f &= -3;
1741
+ setFlag(ctx.ret, IsExecuting, false);
1630
1742
  }
1631
1743
  }
1632
- if (!isPromiseLike(iteration)) {
1744
+ if (!_utils.isPromiseLike(iteration)) {
1633
1745
  throw new Error("Mixed generator component");
1634
1746
  }
1635
- const block = iteration.catch(NOOP);
1636
- const value = iteration.then((iteration) => {
1637
- let value;
1638
- if (!(ctx.f & IsInForOfLoop)) {
1639
- runAsyncGenComponent(ctx, Promise.resolve(iteration), hydrationData);
1747
+ const diff = iteration.then((iteration) => {
1748
+ if (getFlag(ctx.ret, IsInForAwaitOfLoop)) {
1749
+ // We have entered a for await...of loop, so we start pulling
1750
+ pullComponent(ctx, iteration);
1640
1751
  }
1641
1752
  else {
1642
- if (!(ctx.f & NeedsToYield) && !(ctx.f & IsUnmounted)) {
1643
- console.error("Component yielded more than once in for...of loop");
1644
- }
1645
- }
1646
- ctx.f &= -17;
1647
- try {
1648
- value = updateComponentChildren(ctx,
1649
- // Children can be void so we eliminate that here
1650
- iteration.value, hydrationData);
1651
- if (isPromiseLike(value)) {
1652
- value = value.catch((err) => handleChildError(ctx, err));
1753
+ if (getFlag(ctx.ret, IsInForOfLoop) &&
1754
+ !getFlag(ctx.ret, NeedsToYield) &&
1755
+ !getFlag(ctx.ret, IsUnmounted) &&
1756
+ !getFlag(ctx.ret, IsScheduling)) {
1757
+ console.error(`Component <${getTagName(ctx.ret.el.tag)}> yielded/returned more than once in for...of loop`);
1653
1758
  }
1654
1759
  }
1655
- catch (err) {
1656
- value = handleChildError(ctx, err);
1760
+ setFlag(ctx.ret, NeedsToYield, false);
1761
+ if (iteration.done) {
1762
+ setFlag(ctx.ret, IsAsyncGen, false);
1763
+ ctx.iterator = undefined;
1657
1764
  }
1658
- return value;
1765
+ return diffComponentChildren(ctx,
1766
+ // Children can be void so we eliminate that here
1767
+ iteration.value, !iteration.done);
1659
1768
  }, (err) => {
1660
- ctx.f |= IsErrored;
1769
+ setFlag(ctx.ret, IsErrored);
1661
1770
  throw err;
1662
1771
  });
1663
- return [block, value];
1772
+ return [diff.catch(NOOP), diff];
1664
1773
  }
1665
- else {
1666
- runAsyncGenComponent(ctx, iteration, hydrationData, initial);
1667
- return [ctx.inflightBlock, ctx.inflightValue];
1774
+ }
1775
+ }
1776
+ /**
1777
+ * Called to resume the props async iterator for async generator components.
1778
+ *
1779
+ * @returns {Promise<undefined> | undefined} A possible promise which
1780
+ * represents the duration during which the component is blocked.
1781
+ */
1782
+ function resumePropsAsyncIterator(ctx) {
1783
+ if (ctx.onPropsProvided) {
1784
+ ctx.onPropsProvided(ctx.ret.el.props);
1785
+ ctx.onPropsProvided = undefined;
1786
+ setFlag(ctx.ret, PropsAvailable, false);
1787
+ }
1788
+ else {
1789
+ setFlag(ctx.ret, PropsAvailable);
1790
+ if (getFlag(ctx.ret, IsInForAwaitOfLoop)) {
1791
+ return new Promise((resolve) => (ctx.onPropsRequested = resolve));
1668
1792
  }
1669
1793
  }
1794
+ return (ctx.pull && ctx.pull.iterationP && ctx.pull.iterationP.then(NOOP, NOOP));
1670
1795
  }
1671
- async function runAsyncGenComponent(ctx, iterationP, hydrationData, initial = false) {
1796
+ /**
1797
+ * The logic for pulling from async generator components when they are in a for
1798
+ * await...of loop is implemented here.
1799
+ *
1800
+ * It makes sense to group this logic in a single async loop to prevent race
1801
+ * conditions caused by calling next(), throw() and return() concurrently.
1802
+ */
1803
+ async function pullComponent(ctx, iterationP) {
1804
+ if (!iterationP || ctx.pull) {
1805
+ return;
1806
+ }
1807
+ ctx.pull = { iterationP: undefined, diff: undefined, onChildError: undefined };
1808
+ // TODO: replace done with iteration
1809
+ //let iteration: ChildrenIteratorResult | undefined;
1672
1810
  let done = false;
1673
1811
  try {
1812
+ let childError;
1674
1813
  while (!done) {
1675
- if (ctx.f & IsInForOfLoop) {
1676
- break;
1677
- }
1678
- // inflightValue must be set synchronously.
1679
- let onValue;
1680
- ctx.inflightValue = new Promise((resolve) => (onValue = resolve));
1681
- if (ctx.f & IsUpdating) {
1682
- // We should not swallow unhandled promise rejections if the component is
1683
- // updating independently.
1684
- // TODO: Does this handle this.refresh() calls?
1685
- ctx.inflightValue.catch(NOOP);
1814
+ if (_utils.isPromiseLike(iterationP)) {
1815
+ ctx.pull.iterationP = iterationP;
1686
1816
  }
1817
+ let onDiff;
1818
+ ctx.pull.diff = new Promise((resolve) => (onDiff = resolve)).then(() => {
1819
+ if (!(getFlag(ctx.ret, IsUpdating) || getFlag(ctx.ret, IsRefreshing))) {
1820
+ commitComponent(ctx, []);
1821
+ }
1822
+ }, (err) => {
1823
+ if (!(getFlag(ctx.ret, IsUpdating) || getFlag(ctx.ret, IsRefreshing)) ||
1824
+ // TODO: is this flag necessary?
1825
+ !getFlag(ctx.ret, NeedsToYield)) {
1826
+ return propagateError(ctx, err, []);
1827
+ }
1828
+ throw err;
1829
+ });
1687
1830
  let iteration;
1688
1831
  try {
1689
1832
  iteration = await iterationP;
1690
1833
  }
1691
1834
  catch (err) {
1692
1835
  done = true;
1693
- ctx.f |= IsErrored;
1694
- onValue(Promise.reject(err));
1836
+ setFlag(ctx.ret, IsErrored);
1837
+ setFlag(ctx.ret, NeedsToYield, false);
1838
+ onDiff(Promise.reject(err));
1695
1839
  break;
1696
1840
  }
1697
- if (!(ctx.f & IsInForAwaitOfLoop)) {
1698
- ctx.f &= ~PropsAvailable;
1841
+ // this must be set after iterationP is awaited
1842
+ let oldResult;
1843
+ {
1844
+ // The 'floating' flag tracks whether the promise passed to the generator
1845
+ // is handled (via await, then, or catch). If handled, we reject the
1846
+ // promise so the user can catch errors. If not, we inject the error back
1847
+ // into the generator using throw, like for sync generator components.
1848
+ let floating = true;
1849
+ const oldResult1 = new Promise((resolve, reject) => {
1850
+ ctx.ctx.schedule(resolve);
1851
+ ctx.pull.onChildError = (err) => {
1852
+ reject(err);
1853
+ if (floating) {
1854
+ childError = err;
1855
+ resumePropsAsyncIterator(ctx);
1856
+ return ctx.pull.diff;
1857
+ }
1858
+ };
1859
+ });
1860
+ oldResult1.catch(NOOP);
1861
+ // We use Object.create() to clone the promise for float detection
1862
+ // because modern JS engines skip calling .then() on promises awaited
1863
+ // with await.
1864
+ oldResult = Object.create(oldResult1);
1865
+ oldResult.then = function (onfulfilled, onrejected) {
1866
+ floating = false;
1867
+ return oldResult1.then(onfulfilled, onrejected);
1868
+ };
1869
+ oldResult.catch = function (onrejected) {
1870
+ floating = false;
1871
+ return oldResult1.catch(onrejected);
1872
+ };
1873
+ }
1874
+ if (childError != null) {
1875
+ try {
1876
+ setFlag(ctx.ret, IsExecuting);
1877
+ if (typeof ctx.iterator.throw !== "function") {
1878
+ throw childError;
1879
+ }
1880
+ iteration = await ctx.iterator.throw(childError);
1881
+ }
1882
+ catch (err) {
1883
+ done = true;
1884
+ setFlag(ctx.ret, IsErrored);
1885
+ setFlag(ctx.ret, NeedsToYield, false);
1886
+ onDiff(Promise.reject(err));
1887
+ break;
1888
+ }
1889
+ finally {
1890
+ childError = undefined;
1891
+ setFlag(ctx.ret, IsExecuting, false);
1892
+ }
1893
+ }
1894
+ // this makes sure we pause before entering a loop if we yield before it
1895
+ if (!getFlag(ctx.ret, IsInForAwaitOfLoop)) {
1896
+ setFlag(ctx.ret, PropsAvailable, false);
1699
1897
  }
1700
1898
  done = !!iteration.done;
1701
- let value;
1899
+ let diff;
1702
1900
  try {
1703
- if (!(ctx.f & NeedsToYield) &&
1704
- ctx.f & PropsAvailable &&
1705
- ctx.f & IsInForAwaitOfLoop &&
1706
- !initial &&
1707
- !done) {
1708
- // We skip stale iterations in for await...of loops.
1709
- value = ctx.ret.inflightValue || getValue(ctx.ret);
1901
+ if (!_utils.isPromiseLike(iterationP)) {
1902
+ // if iterationP is an iteration and not a promise, the component was
1903
+ // not in a for await...of loop when the iteration started, so we can
1904
+ // skip the diffing of children as it is handled elsewhere.
1905
+ diff = undefined;
1906
+ }
1907
+ else if (!getFlag(ctx.ret, NeedsToYield) &&
1908
+ getFlag(ctx.ret, PropsAvailable) &&
1909
+ getFlag(ctx.ret, IsInForAwaitOfLoop)) {
1910
+ // logic to skip yielded children in a stale for await of iteration.
1911
+ diff = undefined;
1710
1912
  }
1711
1913
  else {
1712
- value = updateComponentChildren(ctx, iteration.value, hydrationData);
1713
- hydrationData = undefined;
1714
- if (isPromiseLike(value)) {
1715
- value = value.catch((err) => handleChildError(ctx, err));
1716
- }
1914
+ diff = diffComponentChildren(ctx, iteration.value, !iteration.done);
1717
1915
  }
1718
- ctx.f &= ~NeedsToYield;
1719
1916
  }
1720
1917
  catch (err) {
1721
- // Do we need to catch potential errors here in the case of unhandled
1722
- // promise rejections?
1723
- value = handleChildError(ctx, err);
1918
+ onDiff(Promise.reject(err));
1724
1919
  }
1725
1920
  finally {
1726
- onValue(value);
1727
- }
1728
- let oldResult;
1729
- if (ctx.ret.inflightValue) {
1730
- // The value passed back into the generator as the argument to the next
1731
- // method is a promise if an async generator component has async
1732
- // children. Sync generator components only resume when their children
1733
- // have fulfilled so the element’s inflight child values will never be
1734
- // defined.
1735
- oldResult = ctx.ret.inflightValue.then((value) => ctx.renderer.read(value));
1736
- oldResult.catch((err) => {
1737
- if (ctx.f & IsUpdating) {
1738
- return;
1921
+ onDiff(diff);
1922
+ setFlag(ctx.ret, NeedsToYield, false);
1923
+ }
1924
+ if (getFlag(ctx.ret, IsUnmounted)) {
1925
+ // TODO: move this unmounted branch outside the loop
1926
+ while ((!iteration || !iteration.done) &&
1927
+ ctx.iterator &&
1928
+ getFlag(ctx.ret, IsInForAwaitOfLoop)) {
1929
+ try {
1930
+ setFlag(ctx.ret, IsExecuting);
1931
+ iteration = await ctx.iterator.next(oldResult);
1739
1932
  }
1740
- if (!ctx.parent) {
1933
+ catch (err) {
1934
+ setFlag(ctx.ret, IsErrored);
1935
+ // we throw the error here to cause an unhandled rejection because
1936
+ // the promise returned from pullComponent is never awaited
1741
1937
  throw err;
1742
1938
  }
1743
- return propagateError(ctx.parent, err);
1744
- });
1745
- }
1746
- else {
1747
- oldResult = ctx.renderer.read(getValue(ctx.ret));
1748
- }
1749
- if (ctx.f & IsUnmounted) {
1750
- if (ctx.f & IsInForAwaitOfLoop) {
1939
+ finally {
1940
+ setFlag(ctx.ret, IsExecuting, false);
1941
+ }
1942
+ }
1943
+ if ((!iteration || !iteration.done) &&
1944
+ ctx.iterator &&
1945
+ typeof ctx.iterator.return === "function") {
1751
1946
  try {
1752
- ctx.f |= IsSyncExecuting;
1753
- iterationP = ctx.iterator.next(oldResult);
1947
+ setFlag(ctx.ret, IsExecuting);
1948
+ await ctx.iterator.return();
1949
+ }
1950
+ catch (err) {
1951
+ setFlag(ctx.ret, IsErrored);
1952
+ throw err;
1754
1953
  }
1755
1954
  finally {
1756
- ctx.f &= ~IsSyncExecuting;
1955
+ setFlag(ctx.ret, IsExecuting, false);
1757
1956
  }
1758
1957
  }
1759
- else {
1760
- returnComponent(ctx);
1761
- break;
1762
- }
1958
+ break;
1959
+ }
1960
+ else if (!getFlag(ctx.ret, IsInForAwaitOfLoop)) {
1961
+ // we have exited the for...await of, so updates will be handled by the
1962
+ // regular runComponent/enqueueComponent logic.
1963
+ break;
1763
1964
  }
1764
- else if (!done && !(ctx.f & IsInForOfLoop)) {
1965
+ else if (!iteration.done) {
1765
1966
  try {
1766
- ctx.f |= IsSyncExecuting;
1967
+ setFlag(ctx.ret, IsExecuting);
1767
1968
  iterationP = ctx.iterator.next(oldResult);
1768
1969
  }
1769
1970
  finally {
1770
- ctx.f &= ~IsSyncExecuting;
1971
+ setFlag(ctx.ret, IsExecuting, false);
1771
1972
  }
1772
1973
  }
1773
- initial = false;
1774
1974
  }
1775
1975
  }
1776
1976
  finally {
1777
1977
  if (done) {
1778
- ctx.f &= -513;
1978
+ setFlag(ctx.ret, IsAsyncGen, false);
1779
1979
  ctx.iterator = undefined;
1780
1980
  }
1981
+ ctx.pull = undefined;
1781
1982
  }
1782
1983
  }
1783
- /**
1784
- * Called to resume the props async iterator for async generator components.
1785
- */
1786
- function resumePropsAsyncIterator(ctx) {
1787
- if (ctx.onProps) {
1788
- ctx.onProps(ctx.ret.el.props);
1789
- ctx.onProps = undefined;
1790
- ctx.f &= -33;
1984
+ function commitComponent(ctx, schedulePromises, hydrationNodes) {
1985
+ if (ctx.schedule) {
1986
+ ctx.schedule.promise.then(() => {
1987
+ commitComponent(ctx, []);
1988
+ propagateComponent(ctx);
1989
+ });
1990
+ return getValue(ctx.ret);
1991
+ }
1992
+ const wasScheduling = getFlag(ctx.ret, IsScheduling);
1993
+ const values = commitChildren(ctx.adapter, ctx.host, ctx, ctx.scope, ctx.ret, ctx.index, schedulePromises, hydrationNodes);
1994
+ if (getFlag(ctx.ret, IsUnmounted)) {
1995
+ return;
1996
+ }
1997
+ eventTarget.addEventTargetDelegates(ctx.ctx, values);
1998
+ // Execute schedule callbacks early to check for async deferral
1999
+ const callbacks = scheduleMap.get(ctx);
2000
+ let schedulePromises1;
2001
+ if (callbacks) {
2002
+ scheduleMap.delete(ctx);
2003
+ // TODO: think about error handling for schedule callbacks
2004
+ setFlag(ctx.ret, IsScheduling);
2005
+ const result = ctx.adapter.read(_utils.unwrap(values));
2006
+ for (const callback of callbacks) {
2007
+ const scheduleResult = callback(result);
2008
+ if (_utils.isPromiseLike(scheduleResult)) {
2009
+ (schedulePromises1 = schedulePromises1 || []).push(scheduleResult);
2010
+ }
2011
+ }
2012
+ if (schedulePromises1 && !getFlag(ctx.ret, DidCommit)) {
2013
+ const scheduleCallbacksP = Promise.all(schedulePromises1).then(() => {
2014
+ setFlag(ctx.ret, IsScheduling, false);
2015
+ propagateComponent(ctx);
2016
+ if (ctx.ret.fallback) {
2017
+ unmount(ctx.adapter, ctx.host, ctx.parent, ctx.ret.fallback, false);
2018
+ }
2019
+ ctx.ret.fallback = undefined;
2020
+ });
2021
+ let onAbort;
2022
+ const scheduleP = _utils.safeRace([
2023
+ scheduleCallbacksP,
2024
+ new Promise((resolve) => (onAbort = resolve)),
2025
+ ]).finally(() => {
2026
+ ctx.schedule = undefined;
2027
+ });
2028
+ ctx.schedule = { promise: scheduleP, onAbort };
2029
+ schedulePromises.push(scheduleP);
2030
+ }
2031
+ else {
2032
+ setFlag(ctx.ret, IsScheduling, wasScheduling);
2033
+ }
1791
2034
  }
1792
2035
  else {
1793
- ctx.f |= PropsAvailable;
2036
+ setFlag(ctx.ret, IsScheduling, wasScheduling);
1794
2037
  }
2038
+ if (!getFlag(ctx.ret, IsScheduling)) {
2039
+ if (!getFlag(ctx.ret, IsUpdating)) {
2040
+ propagateComponent(ctx);
2041
+ }
2042
+ if (ctx.ret.fallback) {
2043
+ unmount(ctx.adapter, ctx.host, ctx.parent, ctx.ret.fallback, false);
2044
+ }
2045
+ ctx.ret.fallback = undefined;
2046
+ setFlag(ctx.ret, IsUpdating, false);
2047
+ }
2048
+ setFlag(ctx.ret, DidCommit);
2049
+ // We always use getValue() instead of the unwrapping values because there
2050
+ // are various ways in which the values could have been updated, especially
2051
+ // if schedule callbacks call refresh() or async mounting is happening.
2052
+ return getValue(ctx.ret, true);
2053
+ }
2054
+ /**
2055
+ * Propagates component changes up to ancestors when rendering starts from a
2056
+ * component via refresh() or multiple for await...of renders. This handles
2057
+ * event listeners and DOM arrangement that would normally happen during
2058
+ * top-down rendering.
2059
+ */
2060
+ function propagateComponent(ctx) {
2061
+ const values = getChildValues(ctx.ret, ctx.index);
2062
+ eventTarget.addEventTargetDelegates(ctx.ctx, values, (ctx1) => ctx1[_ContextState].host === ctx.host);
2063
+ const host = ctx.host;
2064
+ const props = stripSpecialProps(host.el.props);
2065
+ ctx.adapter.arrange({
2066
+ tag: host.el.tag,
2067
+ tagName: getTagName(host.el.tag),
2068
+ node: host.value,
2069
+ props,
2070
+ oldProps: props,
2071
+ children: getChildValues(host, 0),
2072
+ });
2073
+ flush(ctx.adapter, ctx.root, ctx);
1795
2074
  }
1796
- // TODO: async unmounting
1797
- function unmountComponent(ctx) {
1798
- if (ctx.f & IsUnmounted) {
2075
+ async function unmountComponent(ctx, isNested) {
2076
+ if (getFlag(ctx.ret, IsUnmounted)) {
1799
2077
  return;
1800
2078
  }
1801
- clearEventListeners(ctx);
2079
+ let cleanupPromises;
2080
+ // TODO: think about errror handling for callbacks
1802
2081
  const callbacks = cleanupMap.get(ctx);
1803
2082
  if (callbacks) {
2083
+ const oldResult = ctx.adapter.read(getValue(ctx.ret));
1804
2084
  cleanupMap.delete(ctx);
1805
- const value = ctx.renderer.read(getValue(ctx.ret));
1806
2085
  for (const callback of callbacks) {
1807
- callback(value);
2086
+ const cleanup = callback(oldResult);
2087
+ if (_utils.isPromiseLike(cleanup)) {
2088
+ (cleanupPromises = cleanupPromises || []).push(cleanup);
2089
+ }
2090
+ }
2091
+ }
2092
+ let didLinger = false;
2093
+ if (!isNested && cleanupPromises && getChildValues(ctx.ret).length > 0) {
2094
+ didLinger = true;
2095
+ const index = ctx.index;
2096
+ const lingerers = ctx.host.lingerers || (ctx.host.lingerers = []);
2097
+ let set = lingerers[index];
2098
+ if (set == null) {
2099
+ set = new Set();
2100
+ lingerers[index] = set;
2101
+ }
2102
+ set.add(ctx.ret);
2103
+ await Promise.all(cleanupPromises);
2104
+ set.delete(ctx.ret);
2105
+ if (set.size === 0) {
2106
+ lingerers[index] = undefined;
2107
+ }
2108
+ if (!lingerers.some(Boolean)) {
2109
+ // If there are no lingerers remaining, we can remove the lingerers array
2110
+ ctx.host.lingerers = undefined;
2111
+ }
2112
+ }
2113
+ if (getFlag(ctx.ret, IsUnmounted)) {
2114
+ // If the component was unmounted while awaiting the cleanup callbacks,
2115
+ // we do not need to continue unmounting.
2116
+ return;
2117
+ }
2118
+ setFlag(ctx.ret, IsUnmounted);
2119
+ // If component has pending schedule promises, resolve them since component
2120
+ // is unmounting
2121
+ if (ctx.schedule) {
2122
+ ctx.schedule.onAbort();
2123
+ ctx.schedule = undefined;
2124
+ }
2125
+ eventTarget.clearEventListeners(ctx.ctx);
2126
+ unmountChildren(ctx.adapter, ctx.host, ctx, ctx.ret, isNested);
2127
+ if (didLinger) {
2128
+ // If we lingered, we call finalize to ensure rendering is finalized
2129
+ if (ctx.root != null) {
2130
+ ctx.adapter.finalize(ctx.root);
1808
2131
  }
1809
2132
  }
1810
- ctx.f |= IsUnmounted;
1811
2133
  if (ctx.iterator) {
1812
- if (ctx.f & IsSyncGen) {
1813
- let value;
1814
- if (ctx.f & IsInForOfLoop) {
1815
- value = enqueueComponentRun(ctx);
1816
- }
1817
- if (isPromiseLike(value)) {
1818
- value.then(() => {
1819
- if (ctx.f & IsInForOfLoop) {
1820
- unmountComponent(ctx);
1821
- }
1822
- else {
1823
- returnComponent(ctx);
1824
- }
1825
- }, (err) => {
1826
- if (!ctx.parent) {
1827
- throw err;
2134
+ if (ctx.pull) {
2135
+ // we let pullComponent handle unmounting
2136
+ resumePropsAsyncIterator(ctx);
2137
+ return;
2138
+ }
2139
+ // we wait for inflight value so yields resume with the most up to date
2140
+ // props
2141
+ if (ctx.inflight) {
2142
+ await ctx.inflight[1];
2143
+ }
2144
+ let iteration;
2145
+ if (getFlag(ctx.ret, IsInForOfLoop)) {
2146
+ try {
2147
+ setFlag(ctx.ret, IsExecuting);
2148
+ const oldResult = ctx.adapter.read(getValue(ctx.ret));
2149
+ const iterationP = ctx.iterator.next(oldResult);
2150
+ if (_utils.isPromiseLike(iterationP)) {
2151
+ if (!getFlag(ctx.ret, IsAsyncGen)) {
2152
+ throw new Error("Mixed generator component");
1828
2153
  }
1829
- return propagateError(ctx.parent, err);
1830
- });
1831
- }
1832
- else {
1833
- if (ctx.f & IsInForOfLoop) {
1834
- unmountComponent(ctx);
2154
+ iteration = await iterationP;
1835
2155
  }
1836
2156
  else {
1837
- returnComponent(ctx);
2157
+ if (!getFlag(ctx.ret, IsSyncGen)) {
2158
+ throw new Error("Mixed generator component");
2159
+ }
2160
+ iteration = iterationP;
1838
2161
  }
1839
2162
  }
1840
- }
1841
- else if (ctx.f & IsAsyncGen) {
1842
- if (ctx.f & IsInForOfLoop) {
1843
- const value = enqueueComponentRun(ctx);
1844
- value.then(() => {
1845
- if (ctx.f & IsInForOfLoop) {
1846
- unmountComponent(ctx);
1847
- }
1848
- else {
1849
- returnComponent(ctx);
1850
- }
1851
- }, (err) => {
1852
- if (!ctx.parent) {
1853
- throw err;
1854
- }
1855
- return propagateError(ctx.parent, err);
1856
- });
2163
+ catch (err) {
2164
+ setFlag(ctx.ret, IsErrored);
2165
+ throw err;
1857
2166
  }
1858
- else {
1859
- // The logic for unmounting async generator components is in the
1860
- // runAsyncGenComponent function.
1861
- resumePropsAsyncIterator(ctx);
2167
+ finally {
2168
+ setFlag(ctx.ret, IsExecuting, false);
1862
2169
  }
1863
2170
  }
1864
- }
1865
- }
1866
- function returnComponent(ctx) {
1867
- resumePropsAsyncIterator(ctx);
1868
- if (ctx.iterator && typeof ctx.iterator.return === "function") {
1869
- try {
1870
- ctx.f |= IsSyncExecuting;
1871
- const iteration = ctx.iterator.return();
1872
- if (isPromiseLike(iteration)) {
1873
- iteration.catch((err) => {
1874
- if (!ctx.parent) {
1875
- throw err;
2171
+ if ((!iteration || !iteration.done) &&
2172
+ ctx.iterator &&
2173
+ typeof ctx.iterator.return === "function") {
2174
+ try {
2175
+ setFlag(ctx.ret, IsExecuting);
2176
+ const iterationP = ctx.iterator.return();
2177
+ if (_utils.isPromiseLike(iterationP)) {
2178
+ if (!getFlag(ctx.ret, IsAsyncGen)) {
2179
+ throw new Error("Mixed generator component");
1876
2180
  }
1877
- return propagateError(ctx.parent, err);
1878
- });
1879
- }
1880
- }
1881
- finally {
1882
- ctx.f &= -3;
1883
- }
1884
- }
1885
- }
1886
- /*** EVENT TARGET UTILITIES ***/
1887
- // EVENT PHASE CONSTANTS
1888
- // https://developer.mozilla.org/en-US/docs/Web/API/Event/eventPhase
1889
- const NONE = 0;
1890
- const CAPTURING_PHASE = 1;
1891
- const AT_TARGET = 2;
1892
- const BUBBLING_PHASE = 3;
1893
- const listenersMap = new WeakMap();
1894
- function isListenerOrListenerObject(value) {
1895
- return (typeof value === "function" ||
1896
- (value !== null &&
1897
- typeof value === "object" &&
1898
- typeof value.handleEvent === "function"));
1899
- }
1900
- function normalizeListenerOptions(options) {
1901
- if (typeof options === "boolean") {
1902
- return { capture: options };
1903
- }
1904
- else if (options == null) {
1905
- return {};
1906
- }
1907
- return options;
1908
- }
1909
- function isEventTarget(value) {
1910
- return (value != null &&
1911
- typeof value.addEventListener === "function" &&
1912
- typeof value.removeEventListener === "function" &&
1913
- typeof value.dispatchEvent === "function");
1914
- }
1915
- function setEventProperty(ev, key, value) {
1916
- Object.defineProperty(ev, key, { value, writable: false, configurable: true });
1917
- }
1918
- // TODO: Maybe we can pass in the current context directly, rather than
1919
- // starting from the parent?
1920
- /**
1921
- * A function to reconstruct an array of every listener given a context and a
1922
- * host element.
1923
- *
1924
- * This function exploits the fact that contexts retain their nearest ancestor
1925
- * host element. We can determine all the contexts which are directly listening
1926
- * to an element by traversing up the context tree and checking that the host
1927
- * element passed in matches the parent context’s host element.
1928
- */
1929
- function getListenerRecords(ctx, ret) {
1930
- let listeners = [];
1931
- while (ctx !== undefined && ctx.host === ret) {
1932
- const listeners1 = listenersMap.get(ctx);
1933
- if (listeners1) {
1934
- listeners = listeners.concat(listeners1);
1935
- }
1936
- ctx = ctx.parent;
1937
- }
1938
- return listeners;
1939
- }
1940
- function clearEventListeners(ctx) {
1941
- const listeners = listenersMap.get(ctx);
1942
- if (listeners && listeners.length) {
1943
- for (const value of getChildValues(ctx.ret)) {
1944
- if (isEventTarget(value)) {
1945
- for (const record of listeners) {
1946
- value.removeEventListener(record.type, record.callback, record.options);
2181
+ iteration = await iterationP;
2182
+ }
2183
+ else {
2184
+ if (!getFlag(ctx.ret, IsSyncGen)) {
2185
+ throw new Error("Mixed generator component");
2186
+ }
2187
+ iteration = iterationP;
1947
2188
  }
1948
2189
  }
2190
+ catch (err) {
2191
+ setFlag(ctx.ret, IsErrored);
2192
+ throw err;
2193
+ }
2194
+ finally {
2195
+ setFlag(ctx.ret, IsExecuting, false);
2196
+ }
1949
2197
  }
1950
- listeners.length = 0;
1951
2198
  }
1952
2199
  }
1953
2200
  /*** ERROR HANDLING UTILITIES ***/
1954
2201
  function handleChildError(ctx, err) {
1955
- if (!ctx.iterator || typeof ctx.iterator.throw !== "function") {
2202
+ if (!ctx.iterator) {
2203
+ throw err;
2204
+ }
2205
+ if (ctx.pull) {
2206
+ // we let pullComponent handle child errors
2207
+ ctx.pull.onChildError(err);
2208
+ return ctx.pull.diff;
2209
+ }
2210
+ if (!ctx.iterator.throw) {
1956
2211
  throw err;
1957
2212
  }
1958
2213
  resumePropsAsyncIterator(ctx);
1959
2214
  let iteration;
1960
2215
  try {
1961
- ctx.f |= IsSyncExecuting;
2216
+ setFlag(ctx.ret, IsExecuting);
1962
2217
  iteration = ctx.iterator.throw(err);
1963
2218
  }
1964
2219
  catch (err) {
1965
- ctx.f |= IsErrored;
2220
+ setFlag(ctx.ret, IsErrored);
1966
2221
  throw err;
1967
2222
  }
1968
2223
  finally {
1969
- ctx.f &= -3;
2224
+ setFlag(ctx.ret, IsExecuting, false);
1970
2225
  }
1971
- if (isPromiseLike(iteration)) {
2226
+ if (_utils.isPromiseLike(iteration)) {
1972
2227
  return iteration.then((iteration) => {
1973
2228
  if (iteration.done) {
1974
- ctx.f &= -513;
2229
+ setFlag(ctx.ret, IsSyncGen, false);
2230
+ setFlag(ctx.ret, IsAsyncGen, false);
1975
2231
  ctx.iterator = undefined;
1976
2232
  }
1977
- return updateComponentChildren(ctx, iteration.value);
2233
+ return diffComponentChildren(ctx, iteration.value, !iteration.done);
1978
2234
  }, (err) => {
1979
- ctx.f |= IsErrored;
2235
+ setFlag(ctx.ret, IsErrored);
1980
2236
  throw err;
1981
2237
  });
1982
2238
  }
1983
2239
  if (iteration.done) {
1984
- ctx.f &= -257;
1985
- ctx.f &= -513;
2240
+ setFlag(ctx.ret, IsSyncGen, false);
2241
+ setFlag(ctx.ret, IsAsyncGen, false);
1986
2242
  ctx.iterator = undefined;
1987
2243
  }
1988
- return updateComponentChildren(ctx, iteration.value);
2244
+ return diffComponentChildren(ctx, iteration.value, !iteration.done);
1989
2245
  }
1990
- function propagateError(ctx, err) {
1991
- let result;
2246
+ /**
2247
+ * Propagates an error up the context tree by calling handleChildError with
2248
+ * each parent.
2249
+ *
2250
+ * @returns A promise which resolves to undefined when the error has been
2251
+ * handled, or undefined if the error was handled synchronously.
2252
+ */
2253
+ function propagateError(ctx, err, schedulePromises) {
2254
+ const parent = ctx.parent;
2255
+ if (!parent) {
2256
+ throw err;
2257
+ }
2258
+ let diff;
1992
2259
  try {
1993
- result = handleChildError(ctx, err);
2260
+ diff = handleChildError(parent, err);
1994
2261
  }
1995
2262
  catch (err) {
1996
- if (!ctx.parent) {
1997
- throw err;
1998
- }
1999
- return propagateError(ctx.parent, err);
2263
+ return propagateError(parent, err, schedulePromises);
2000
2264
  }
2001
- if (isPromiseLike(result)) {
2002
- return result.catch((err) => {
2003
- if (!ctx.parent) {
2004
- throw err;
2005
- }
2006
- return propagateError(ctx.parent, err);
2007
- });
2265
+ if (_utils.isPromiseLike(diff)) {
2266
+ return diff.then(() => void commitComponent(parent, schedulePromises), (err) => propagateError(parent, err, schedulePromises));
2008
2267
  }
2009
- return result;
2268
+ commitComponent(parent, schedulePromises);
2010
2269
  }
2011
- // Some JSX transpilation tools expect these functions to be defined on the
2012
- // default export. Prefer named exports when importing directly.
2270
+ /**
2271
+ * A re-export of some Crank exports as the default export.
2272
+ *
2273
+ * Some JSX tools expect things like createElement/Fragment to be defined on
2274
+ * the default export. Prefer using the named exports directly.
2275
+ */
2013
2276
  var crank = { createElement, Fragment };
2014
2277
 
2015
2278
  exports.Context = Context;
@@ -2019,6 +2282,7 @@ exports.Fragment = Fragment;
2019
2282
  exports.Portal = Portal;
2020
2283
  exports.Raw = Raw;
2021
2284
  exports.Renderer = Renderer;
2285
+ exports.Text = Text;
2022
2286
  exports.cloneElement = cloneElement;
2023
2287
  exports.createElement = createElement;
2024
2288
  exports.default = crank;