@b9g/crank 0.5.0-beta.3 → 0.5.0-beta.5

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/umd.js CHANGED
@@ -737,12 +737,6 @@
737
737
  * resumeCtxIterator function.
738
738
  */
739
739
  const PropsAvailable = 1 << 4;
740
- /**
741
- * A flag which is set when a generator components returns, i.e. the done
742
- * property on the iteration is true. Generator components will stick to their
743
- * last rendered value and ignore further updates.
744
- */
745
- const IsDone = 1 << 5;
746
740
  /**
747
741
  * A flag which is set when a component errors.
748
742
  *
@@ -786,7 +780,7 @@
786
780
  class ContextImpl {
787
781
  constructor(renderer, root, host, parent, scope, ret) {
788
782
  this.f = 0;
789
- this.ctx = new Context(this);
783
+ this.owner = new Context(this);
790
784
  this.renderer = renderer;
791
785
  this.root = root;
792
786
  this.host = host;
@@ -843,65 +837,65 @@
843
837
  return this[_ContextImpl].renderer.read(getValue(this[_ContextImpl].ret));
844
838
  }
845
839
  *[Symbol.iterator]() {
846
- const impl = this[_ContextImpl];
847
- if (impl.f & IsAsyncGen) {
840
+ const ctx = this[_ContextImpl];
841
+ if (ctx.f & IsAsyncGen) {
848
842
  throw new Error("Use for await…of in async generator components");
849
843
  }
850
844
  try {
851
- impl.f |= IsInRenderLoop;
852
- while (!(impl.f & IsUnmounted)) {
853
- if (impl.f & NeedsToYield) {
845
+ ctx.f |= IsInRenderLoop;
846
+ while (!(ctx.f & IsUnmounted)) {
847
+ if (ctx.f & NeedsToYield) {
854
848
  throw new Error("Context iterated twice without a yield");
855
849
  }
856
850
  else {
857
- impl.f |= NeedsToYield;
851
+ ctx.f |= NeedsToYield;
858
852
  }
859
- yield impl.ret.el.props;
853
+ yield ctx.ret.el.props;
860
854
  }
861
855
  }
862
856
  finally {
863
- impl.f &= ~IsInRenderLoop;
857
+ ctx.f &= ~IsInRenderLoop;
864
858
  }
865
859
  }
866
860
  async *[Symbol.asyncIterator]() {
867
- const impl = this[_ContextImpl];
868
- if (impl.f & IsSyncGen) {
861
+ const ctx = this[_ContextImpl];
862
+ if (ctx.f & IsSyncGen) {
869
863
  throw new Error("Use for…of in sync generator components");
870
864
  }
871
865
  try {
872
866
  // await an empty promise to prevent the IsInRenderLoop flag from
873
867
  // returning false positives in the case of async generator components
874
868
  // which immediately enter the loop
875
- impl.f |= IsInRenderLoop;
876
- while (!(impl.f & IsUnmounted)) {
877
- if (impl.f & NeedsToYield) {
869
+ ctx.f |= IsInRenderLoop;
870
+ while (!(ctx.f & IsUnmounted)) {
871
+ if (ctx.f & NeedsToYield) {
878
872
  throw new Error("Context iterated twice without a yield");
879
873
  }
880
874
  else {
881
- impl.f |= NeedsToYield;
875
+ ctx.f |= NeedsToYield;
882
876
  }
883
- if (impl.f & PropsAvailable) {
884
- impl.f &= ~PropsAvailable;
885
- yield impl.ret.el.props;
877
+ if (ctx.f & PropsAvailable) {
878
+ ctx.f &= ~PropsAvailable;
879
+ yield ctx.ret.el.props;
886
880
  }
887
881
  else {
888
- const props = await new Promise((resolve) => (impl.onProps = resolve));
889
- if (impl.f & IsUnmounted) {
882
+ const props = await new Promise((resolve) => (ctx.onProps = resolve));
883
+ if (ctx.f & IsUnmounted) {
890
884
  break;
891
885
  }
892
886
  yield props;
893
887
  }
894
- if (impl.onPropsRequested) {
895
- impl.onPropsRequested();
896
- impl.onPropsRequested = undefined;
888
+ if (ctx.onPropsRequested) {
889
+ ctx.onPropsRequested();
890
+ ctx.onPropsRequested = undefined;
897
891
  }
898
892
  }
899
893
  }
900
894
  finally {
901
- impl.f &= ~IsInRenderLoop;
902
- if (impl.onPropsRequested) {
903
- impl.onPropsRequested();
904
- impl.onPropsRequested = undefined;
895
+ ctx.f &= ~IsInRenderLoop;
896
+ if (ctx.onPropsRequested) {
897
+ ctx.onPropsRequested();
898
+ ctx.onPropsRequested = undefined;
905
899
  }
906
900
  }
907
901
  }
@@ -918,31 +912,31 @@
918
912
  * async iterator to suspend.
919
913
  */
920
914
  refresh() {
921
- const impl = this[_ContextImpl];
922
- if (impl.f & IsUnmounted) {
915
+ const ctx = this[_ContextImpl];
916
+ if (ctx.f & IsUnmounted) {
923
917
  console.error("Component is unmounted");
924
- return impl.renderer.read(undefined);
918
+ return ctx.renderer.read(undefined);
925
919
  }
926
- else if (impl.f & IsSyncExecuting) {
920
+ else if (ctx.f & IsSyncExecuting) {
927
921
  console.error("Component is already executing");
928
922
  return this.value;
929
923
  }
930
- const value = enqueueComponentRun(impl);
924
+ const value = enqueueComponentRun(ctx);
931
925
  if (isPromiseLike(value)) {
932
- return value.then((value) => impl.renderer.read(value));
926
+ return value.then((value) => ctx.renderer.read(value));
933
927
  }
934
- return impl.renderer.read(value);
928
+ return ctx.renderer.read(value);
935
929
  }
936
930
  /**
937
931
  * Registers a callback which fires when the component commits. Will only
938
932
  * fire once per callback and update.
939
933
  */
940
934
  schedule(callback) {
941
- const impl = this[_ContextImpl];
942
- let callbacks = scheduleMap.get(impl);
935
+ const ctx = this[_ContextImpl];
936
+ let callbacks = scheduleMap.get(ctx);
943
937
  if (!callbacks) {
944
938
  callbacks = new Set();
945
- scheduleMap.set(impl, callbacks);
939
+ scheduleMap.set(ctx, callbacks);
946
940
  }
947
941
  callbacks.add(callback);
948
942
  }
@@ -951,19 +945,19 @@
951
945
  * rendered into the root. Will only fire once per callback and render.
952
946
  */
953
947
  flush(callback) {
954
- const impl = this[_ContextImpl];
955
- if (typeof impl.root !== "object" || impl.root === null) {
948
+ const ctx = this[_ContextImpl];
949
+ if (typeof ctx.root !== "object" || ctx.root === null) {
956
950
  return;
957
951
  }
958
- let flushMap = flushMaps.get(impl.root);
952
+ let flushMap = flushMaps.get(ctx.root);
959
953
  if (!flushMap) {
960
954
  flushMap = new Map();
961
- flushMaps.set(impl.root, flushMap);
955
+ flushMaps.set(ctx.root, flushMap);
962
956
  }
963
- let callbacks = flushMap.get(impl);
957
+ let callbacks = flushMap.get(ctx);
964
958
  if (!callbacks) {
965
959
  callbacks = new Set();
966
- flushMap.set(impl, callbacks);
960
+ flushMap.set(ctx, callbacks);
967
961
  }
968
962
  callbacks.add(callback);
969
963
  }
@@ -972,45 +966,45 @@
972
966
  * fire once per callback.
973
967
  */
974
968
  cleanup(callback) {
975
- const impl = this[_ContextImpl];
976
- let callbacks = cleanupMap.get(impl);
969
+ const ctx = this[_ContextImpl];
970
+ let callbacks = cleanupMap.get(ctx);
977
971
  if (!callbacks) {
978
972
  callbacks = new Set();
979
- cleanupMap.set(impl, callbacks);
973
+ cleanupMap.set(ctx, callbacks);
980
974
  }
981
975
  callbacks.add(callback);
982
976
  }
983
977
  consume(key) {
984
- for (let parent = this[_ContextImpl].parent; parent !== undefined; parent = parent.parent) {
985
- const provisions = provisionMaps.get(parent);
978
+ for (let ctx = this[_ContextImpl].parent; ctx !== undefined; ctx = ctx.parent) {
979
+ const provisions = provisionMaps.get(ctx);
986
980
  if (provisions && provisions.has(key)) {
987
981
  return provisions.get(key);
988
982
  }
989
983
  }
990
984
  }
991
985
  provide(key, value) {
992
- const impl = this[_ContextImpl];
993
- let provisions = provisionMaps.get(impl);
986
+ const ctx = this[_ContextImpl];
987
+ let provisions = provisionMaps.get(ctx);
994
988
  if (!provisions) {
995
989
  provisions = new Map();
996
- provisionMaps.set(impl, provisions);
990
+ provisionMaps.set(ctx, provisions);
997
991
  }
998
992
  provisions.set(key, value);
999
993
  }
1000
994
  addEventListener(type, listener, options) {
1001
- const impl = this[_ContextImpl];
995
+ const ctx = this[_ContextImpl];
1002
996
  let listeners;
1003
997
  if (!isListenerOrListenerObject(listener)) {
1004
998
  return;
1005
999
  }
1006
1000
  else {
1007
- const listeners1 = listenersMap.get(impl);
1001
+ const listeners1 = listenersMap.get(ctx);
1008
1002
  if (listeners1) {
1009
1003
  listeners = listeners1;
1010
1004
  }
1011
1005
  else {
1012
1006
  listeners = [];
1013
- listenersMap.set(impl, listeners);
1007
+ listenersMap.set(ctx, listeners);
1014
1008
  }
1015
1009
  }
1016
1010
  options = normalizeListenerOptions(options);
@@ -1021,7 +1015,7 @@
1021
1015
  else {
1022
1016
  callback = listener;
1023
1017
  }
1024
- const record = { type, callback, listener, options };
1018
+ const record = { type, listener, callback, options };
1025
1019
  if (options.once) {
1026
1020
  record.callback = function () {
1027
1021
  const i = listeners.indexOf(record);
@@ -1038,15 +1032,15 @@
1038
1032
  }
1039
1033
  listeners.push(record);
1040
1034
  // TODO: is it possible to separate out the EventTarget delegation logic
1041
- for (const value of getChildValues(impl.ret)) {
1035
+ for (const value of getChildValues(ctx.ret)) {
1042
1036
  if (isEventTarget(value)) {
1043
1037
  value.addEventListener(record.type, record.callback, record.options);
1044
1038
  }
1045
1039
  }
1046
1040
  }
1047
1041
  removeEventListener(type, listener, options) {
1048
- const impl = this[_ContextImpl];
1049
- const listeners = listenersMap.get(impl);
1042
+ const ctx = this[_ContextImpl];
1043
+ const listeners = listenersMap.get(ctx);
1050
1044
  if (listeners == null || !isListenerOrListenerObject(listener)) {
1051
1045
  return;
1052
1046
  }
@@ -1060,16 +1054,16 @@
1060
1054
  const record = listeners[i];
1061
1055
  listeners.splice(i, 1);
1062
1056
  // TODO: is it possible to separate out the EventTarget delegation logic
1063
- for (const value of getChildValues(impl.ret)) {
1057
+ for (const value of getChildValues(ctx.ret)) {
1064
1058
  if (isEventTarget(value)) {
1065
1059
  value.removeEventListener(record.type, record.callback, record.options);
1066
1060
  }
1067
1061
  }
1068
1062
  }
1069
1063
  dispatchEvent(ev) {
1070
- const impl = this[_ContextImpl];
1064
+ const ctx = this[_ContextImpl];
1071
1065
  const path = [];
1072
- for (let parent = impl.parent; parent !== undefined; parent = parent.parent) {
1066
+ for (let parent = ctx.parent; parent !== undefined; parent = parent.parent) {
1073
1067
  path.push(parent);
1074
1068
  }
1075
1069
  // We patch the stopImmediatePropagation method because ev.cancelBubble
@@ -1081,7 +1075,7 @@
1081
1075
  immediateCancelBubble = true;
1082
1076
  return stopImmediatePropagation.call(ev);
1083
1077
  });
1084
- setEventProperty(ev, "target", impl.ctx);
1078
+ setEventProperty(ev, "target", ctx.owner);
1085
1079
  // The only possible errors in this block are errors thrown by callbacks,
1086
1080
  // and dispatchEvent will only log these errors rather than throwing
1087
1081
  // them. Therefore, we place all code in a try block, log errors in the
@@ -1090,18 +1084,21 @@
1090
1084
  // Each early return within the try block returns true because while the
1091
1085
  // return value is overridden in the finally block, TypeScript
1092
1086
  // (justifiably) does not recognize the unsafe return statement.
1093
- //
1094
- // TODO: Run all callbacks even if one of them errors
1095
1087
  try {
1096
1088
  setEventProperty(ev, "eventPhase", CAPTURING_PHASE);
1097
1089
  for (let i = path.length - 1; i >= 0; i--) {
1098
1090
  const target = path[i];
1099
1091
  const listeners = listenersMap.get(target);
1100
1092
  if (listeners) {
1101
- setEventProperty(ev, "currentTarget", target.ctx);
1093
+ setEventProperty(ev, "currentTarget", target.owner);
1102
1094
  for (const record of listeners) {
1103
1095
  if (record.type === ev.type && record.options.capture) {
1104
- record.callback.call(target.ctx, ev);
1096
+ try {
1097
+ record.callback.call(target.owner, ev);
1098
+ }
1099
+ catch (err) {
1100
+ console.error(err);
1101
+ }
1105
1102
  if (immediateCancelBubble) {
1106
1103
  return true;
1107
1104
  }
@@ -1113,13 +1110,25 @@
1113
1110
  }
1114
1111
  }
1115
1112
  {
1116
- const listeners = listenersMap.get(impl);
1113
+ setEventProperty(ev, "eventPhase", AT_TARGET);
1114
+ setEventProperty(ev, "currentTarget", ctx.owner);
1115
+ const propCallback = ctx.ret.el.props["on" + ev.type];
1116
+ if (propCallback != null) {
1117
+ propCallback(ev);
1118
+ if (immediateCancelBubble || ev.cancelBubble) {
1119
+ return true;
1120
+ }
1121
+ }
1122
+ const listeners = listenersMap.get(ctx);
1117
1123
  if (listeners) {
1118
- setEventProperty(ev, "eventPhase", AT_TARGET);
1119
- setEventProperty(ev, "currentTarget", impl.ctx);
1120
1124
  for (const record of listeners) {
1121
1125
  if (record.type === ev.type) {
1122
- record.callback.call(impl.ctx, ev);
1126
+ try {
1127
+ record.callback.call(ctx.owner, ev);
1128
+ }
1129
+ catch (err) {
1130
+ console.error(err);
1131
+ }
1123
1132
  if (immediateCancelBubble) {
1124
1133
  return true;
1125
1134
  }
@@ -1136,10 +1145,15 @@
1136
1145
  const target = path[i];
1137
1146
  const listeners = listenersMap.get(target);
1138
1147
  if (listeners) {
1139
- setEventProperty(ev, "currentTarget", target.ctx);
1148
+ setEventProperty(ev, "currentTarget", target.owner);
1140
1149
  for (const record of listeners) {
1141
1150
  if (record.type === ev.type && !record.options.capture) {
1142
- record.callback.call(target.ctx, ev);
1151
+ try {
1152
+ record.callback.call(target.owner, ev);
1153
+ }
1154
+ catch (err) {
1155
+ console.error(err);
1156
+ }
1143
1157
  if (immediateCancelBubble) {
1144
1158
  return true;
1145
1159
  }
@@ -1152,10 +1166,6 @@
1152
1166
  }
1153
1167
  }
1154
1168
  }
1155
- catch (err) {
1156
- // TODO: Use setTimeout to rethrow the error.
1157
- console.error(err);
1158
- }
1159
1169
  finally {
1160
1170
  setEventProperty(ev, "eventPhase", NONE);
1161
1171
  setEventProperty(ev, "currentTarget", null);
@@ -1189,7 +1199,12 @@
1189
1199
  return enqueueComponentRun(ctx);
1190
1200
  }
1191
1201
  function updateComponentChildren(ctx, children) {
1192
- if (ctx.f & IsUnmounted || ctx.f & IsErrored) {
1202
+ if (ctx.f & IsUnmounted) {
1203
+ return;
1204
+ }
1205
+ else if (ctx.f & IsErrored) {
1206
+ // This branch is necessary for some race conditions where this function is
1207
+ // called after iterator.throw() in async generator components.
1193
1208
  return;
1194
1209
  }
1195
1210
  else if (children === undefined) {
@@ -1416,9 +1431,6 @@
1416
1431
  */
1417
1432
  function runComponent(ctx) {
1418
1433
  const ret = ctx.ret;
1419
- if (ctx.f & IsDone) {
1420
- return [undefined, getValue(ret)];
1421
- }
1422
1434
  const initial = !ctx.iterator;
1423
1435
  if (initial) {
1424
1436
  resumePropsIterator(ctx);
@@ -1426,7 +1438,7 @@
1426
1438
  clearEventListeners(ctx);
1427
1439
  let result;
1428
1440
  try {
1429
- result = ret.el.tag.call(ctx.ctx, ret.el.props);
1441
+ result = ret.el.tag.call(ctx.owner, ret.el.props);
1430
1442
  }
1431
1443
  catch (err) {
1432
1444
  ctx.f |= IsErrored;
@@ -1459,7 +1471,7 @@
1459
1471
  iteration = ctx.iterator.next();
1460
1472
  }
1461
1473
  catch (err) {
1462
- ctx.f |= IsDone | IsErrored;
1474
+ ctx.f |= IsErrored;
1463
1475
  throw err;
1464
1476
  }
1465
1477
  finally {
@@ -1479,11 +1491,10 @@
1479
1491
  if (!initial) {
1480
1492
  try {
1481
1493
  ctx.f |= IsSyncExecuting;
1482
- const oldValue = ctx.renderer.read(getValue(ret));
1483
- iteration = ctx.iterator.next(oldValue);
1494
+ iteration = ctx.iterator.next(ctx.renderer.read(getValue(ret)));
1484
1495
  }
1485
1496
  catch (err) {
1486
- ctx.f |= IsDone | IsErrored;
1497
+ ctx.f |= IsErrored;
1487
1498
  throw err;
1488
1499
  }
1489
1500
  finally {
@@ -1494,7 +1505,8 @@
1494
1505
  throw new Error("Sync generator component returned an async iteration");
1495
1506
  }
1496
1507
  if (iteration.done) {
1497
- ctx.f |= IsDone;
1508
+ ctx.f &= ~IsSyncGen;
1509
+ ctx.iterator = undefined;
1498
1510
  }
1499
1511
  let value;
1500
1512
  try {
@@ -1508,10 +1520,8 @@
1508
1520
  catch (err) {
1509
1521
  value = handleChildError(ctx, err);
1510
1522
  }
1511
- if (isPromiseLike(value)) {
1512
- return [value.catch(NOOP), value];
1513
- }
1514
- return [undefined, value];
1523
+ const block = isPromiseLike(value) ? value.catch(NOOP) : undefined;
1524
+ return [block, value];
1515
1525
  }
1516
1526
  else {
1517
1527
  // async generator component
@@ -1519,65 +1529,80 @@
1519
1529
  }
1520
1530
  }
1521
1531
  async function runAsyncGenComponent(ctx, iterationP) {
1522
- do {
1523
- // block and value must be assigned at the same time.
1524
- let onValue;
1525
- ctx.inflightValue = new Promise((resolve) => (onValue = resolve));
1526
- if (ctx.f & IsUpdating) {
1527
- // We should not swallow unhandled promise rejections if the component is
1528
- // updating independently.
1529
- // TODO: Does this handle this.refresh() calls?
1530
- ctx.inflightValue.catch(NOOP);
1531
- }
1532
- let iteration;
1533
- try {
1534
- iteration = await iterationP;
1535
- }
1536
- catch (err) {
1537
- ctx.f |= IsDone;
1538
- ctx.f |= IsErrored;
1539
- onValue(Promise.reject(err));
1540
- break;
1541
- }
1542
- finally {
1543
- if (!(ctx.f & IsInRenderLoop)) {
1544
- ctx.f &= ~PropsAvailable;
1532
+ let done = false;
1533
+ try {
1534
+ while (!done) {
1535
+ // inflightValue must be set synchronously.
1536
+ let onValue;
1537
+ ctx.inflightValue = new Promise((resolve) => (onValue = resolve));
1538
+ if (ctx.f & IsUpdating) {
1539
+ // We should not swallow unhandled promise rejections if the component is
1540
+ // updating independently.
1541
+ // TODO: Does this handle this.refresh() calls?
1542
+ ctx.inflightValue.catch(NOOP);
1543
+ }
1544
+ let iteration;
1545
+ try {
1546
+ iteration = await iterationP;
1545
1547
  }
1546
- }
1547
- if (ctx.f & NeedsToYield) {
1548
- ctx.f &= ~NeedsToYield;
1549
- }
1550
- if (iteration.done) {
1551
- ctx.f |= IsDone;
1552
- }
1553
- let value;
1554
- try {
1555
- value = updateComponentChildren(ctx, iteration.value);
1556
- if (isPromiseLike(value)) {
1557
- value = value.catch((err) => handleChildError(ctx, err));
1548
+ catch (err) {
1549
+ done = true;
1550
+ ctx.f |= IsErrored;
1551
+ onValue(Promise.reject(err));
1552
+ break;
1558
1553
  }
1559
- onValue(value);
1560
- }
1561
- catch (err) {
1562
- // Do we need to catch potential errors here in the case of unhandled
1563
- // promise rejections?
1564
- value = handleChildError(ctx, err);
1565
- }
1566
- // TODO: this can be done more elegantly
1567
- let oldValue;
1568
- if (ctx.ret.inflightValue) {
1569
- // The value passed back into the generator as the argument to the next
1570
- // method is a promise if an async generator component has async
1571
- // children. Sync generator components only resume when their children
1572
- // have fulfilled so the element’s inflight child values will never be
1573
- // defined.
1574
- oldValue = ctx.ret.inflightValue.then((value) => ctx.renderer.read(value), () => ctx.renderer.read(undefined));
1575
- }
1576
- else {
1577
- oldValue = ctx.renderer.read(getValue(ctx.ret));
1578
- }
1579
- if (ctx.f & IsUnmounted) {
1580
- if (ctx.f & IsInRenderLoop) {
1554
+ finally {
1555
+ ctx.f &= ~NeedsToYield;
1556
+ if (!(ctx.f & IsInRenderLoop)) {
1557
+ ctx.f &= ~PropsAvailable;
1558
+ }
1559
+ }
1560
+ done = !!iteration.done;
1561
+ let value;
1562
+ try {
1563
+ value = updateComponentChildren(ctx, iteration.value);
1564
+ if (isPromiseLike(value)) {
1565
+ value = value.catch((err) => handleChildError(ctx, err));
1566
+ }
1567
+ }
1568
+ catch (err) {
1569
+ done = true;
1570
+ // Do we need to catch potential errors here in the case of unhandled
1571
+ // promise rejections?
1572
+ value = handleChildError(ctx, err);
1573
+ }
1574
+ finally {
1575
+ onValue(value);
1576
+ }
1577
+ // TODO: this can be done more elegantly
1578
+ let oldValue;
1579
+ if (ctx.ret.inflightValue) {
1580
+ // The value passed back into the generator as the argument to the next
1581
+ // method is a promise if an async generator component has async
1582
+ // children. Sync generator components only resume when their children
1583
+ // have fulfilled so the element’s inflight child values will never be
1584
+ // defined.
1585
+ oldValue = ctx.ret.inflightValue.then((value) => ctx.renderer.read(value), () => ctx.renderer.read(undefined));
1586
+ }
1587
+ else {
1588
+ oldValue = ctx.renderer.read(getValue(ctx.ret));
1589
+ }
1590
+ if (ctx.f & IsUnmounted) {
1591
+ if (ctx.f & IsInRenderLoop) {
1592
+ try {
1593
+ ctx.f |= IsSyncExecuting;
1594
+ iterationP = ctx.iterator.next(oldValue);
1595
+ }
1596
+ finally {
1597
+ ctx.f &= ~IsSyncExecuting;
1598
+ }
1599
+ }
1600
+ else {
1601
+ returnComponent(ctx);
1602
+ break;
1603
+ }
1604
+ }
1605
+ else if (!done) {
1581
1606
  try {
1582
1607
  ctx.f |= IsSyncExecuting;
1583
1608
  iterationP = ctx.iterator.next(oldValue);
@@ -1586,25 +1611,15 @@
1586
1611
  ctx.f &= ~IsSyncExecuting;
1587
1612
  }
1588
1613
  }
1589
- else {
1590
- returnComponent(ctx);
1591
- break;
1592
- }
1593
1614
  }
1594
- else if (!(ctx.f & IsDone)) {
1595
- try {
1596
- ctx.f |= IsSyncExecuting;
1597
- iterationP = ctx.iterator.next(oldValue);
1598
- }
1599
- finally {
1600
- ctx.f &= ~IsSyncExecuting;
1601
- }
1602
- }
1603
- } while (!(ctx.f & IsDone));
1615
+ }
1616
+ finally {
1617
+ ctx.f &= ~IsAsyncGen;
1618
+ ctx.iterator = undefined;
1619
+ }
1604
1620
  }
1605
1621
  /**
1606
- * Called to make props available to the props async iterator for async
1607
- * generator components.
1622
+ * Called to resume the props async iterator for async generator components.
1608
1623
  */
1609
1624
  function resumePropsIterator(ctx) {
1610
1625
  if (ctx.onProps) {
@@ -1628,7 +1643,7 @@
1628
1643
  }
1629
1644
  }
1630
1645
  ctx.f |= IsUnmounted;
1631
- if (ctx.iterator && !(ctx.f & IsDone)) {
1646
+ if (ctx.iterator) {
1632
1647
  if (ctx.f & IsSyncGen) {
1633
1648
  let value;
1634
1649
  if (ctx.f & IsInRenderLoop) {
@@ -1655,17 +1670,18 @@
1655
1670
  }
1656
1671
  }
1657
1672
  }
1658
- else {
1659
- // async generator component
1673
+ else if (ctx.f & IsAsyncGen) {
1674
+ // The logic for unmounting async generator components is in the
1675
+ // runAsyncGenComponent function.
1660
1676
  resumePropsIterator(ctx);
1661
1677
  }
1662
1678
  }
1663
1679
  }
1664
1680
  function returnComponent(ctx) {
1665
1681
  resumePropsIterator(ctx);
1666
- if (!(ctx.f & IsDone) && typeof ctx.iterator.return === "function") {
1667
- ctx.f |= IsSyncExecuting;
1682
+ if (ctx.iterator && typeof ctx.iterator.return === "function") {
1668
1683
  try {
1684
+ ctx.f |= IsSyncExecuting;
1669
1685
  const iteration = ctx.iterator.return();
1670
1686
  if (isPromiseLike(iteration)) {
1671
1687
  iteration.catch((err) => propagateError(ctx.parent, err));
@@ -1675,7 +1691,6 @@
1675
1691
  ctx.f &= ~IsSyncExecuting;
1676
1692
  }
1677
1693
  }
1678
- ctx.f |= IsDone;
1679
1694
  }
1680
1695
  /*** EVENT TARGET UTILITIES ***/
1681
1696
  // EVENT PHASE CONSTANTS
@@ -1745,11 +1760,8 @@
1745
1760
  }
1746
1761
  }
1747
1762
  /*** ERROR HANDLING UTILITIES ***/
1748
- // TODO: generator components which throw errors should be recoverable
1749
1763
  function handleChildError(ctx, err) {
1750
- if (ctx.f & IsDone ||
1751
- !ctx.iterator ||
1752
- typeof ctx.iterator.throw !== "function") {
1764
+ if (!ctx.iterator || typeof ctx.iterator.throw !== "function") {
1753
1765
  throw err;
1754
1766
  }
1755
1767
  resumePropsIterator(ctx);
@@ -1759,7 +1771,7 @@
1759
1771
  iteration = ctx.iterator.throw(err);
1760
1772
  }
1761
1773
  catch (err) {
1762
- ctx.f |= IsDone | IsErrored;
1774
+ ctx.f |= IsErrored;
1763
1775
  throw err;
1764
1776
  }
1765
1777
  finally {
@@ -1768,16 +1780,18 @@
1768
1780
  if (isPromiseLike(iteration)) {
1769
1781
  return iteration.then((iteration) => {
1770
1782
  if (iteration.done) {
1771
- ctx.f |= IsDone;
1783
+ ctx.f &= ~IsAsyncGen;
1784
+ ctx.iterator = undefined;
1772
1785
  }
1773
1786
  return updateComponentChildren(ctx, iteration.value);
1774
1787
  }, (err) => {
1775
- ctx.f |= IsDone | IsErrored;
1788
+ ctx.f |= IsErrored;
1776
1789
  throw err;
1777
1790
  });
1778
1791
  }
1779
1792
  if (iteration.done) {
1780
- ctx.f |= IsDone;
1793
+ ctx.f &= ~IsSyncGen;
1794
+ ctx.iterator = undefined;
1781
1795
  }
1782
1796
  return updateComponentChildren(ctx, iteration.value);
1783
1797
  }
@@ -1798,6 +1812,429 @@
1798
1812
  return result;
1799
1813
  }
1800
1814
 
1815
+ const cache = new Map();
1816
+ function jsx(spans, ...expressions) {
1817
+ const key = JSON.stringify(spans.raw);
1818
+ let parseResult = cache.get(key);
1819
+ if (parseResult == null) {
1820
+ parseResult = parse(spans.raw);
1821
+ cache.set(key, parseResult);
1822
+ }
1823
+ const { element, targets } = parseResult;
1824
+ for (let i = 0; i < expressions.length; i++) {
1825
+ const exp = expressions[i];
1826
+ const target = targets[i];
1827
+ if (target) {
1828
+ if (target.type === "error") {
1829
+ throw new SyntaxError(target.message.replace("${}", formatTagForError(exp)));
1830
+ }
1831
+ target.value = exp;
1832
+ }
1833
+ }
1834
+ return build(element);
1835
+ }
1836
+ /**
1837
+ * Matches first significant character in children mode.
1838
+ *
1839
+ * Group 1: newline
1840
+ * Group 2: comment
1841
+ * Group 3: tag
1842
+ * Group 4: closing slash
1843
+ * Group 5: tag name
1844
+ *
1845
+ * The comment group must appear first because the tag group can potentially
1846
+ * match a comment, so that we can handle tag expressions where we’ve reached
1847
+ * the end of a span.
1848
+ */
1849
+ const CHILDREN_RE = /((?:\r|\n|\r\n)\s*)|(<!--[\S\s]*?(?:-->|$))|(<\s*(\/{0,2})\s*([-_$\w]*))/g;
1850
+ /**
1851
+ * Matches props after element tags.
1852
+ *
1853
+ * Group 1: tag end
1854
+ * Group 2: spread props
1855
+ * Group 3: prop name
1856
+ * Group 4: equals
1857
+ * Group 5: prop value string
1858
+ */
1859
+ const PROPS_RE = /\s*(?:(\/?\s*>)|(\.\.\.\s*)|(?:([-_$\w]+)\s*(=)?\s*(?:("(\\"|[\S\s])*?(?:"|$)|'(?:\\'|[\S\s])*?(?:'|$)))?))/g;
1860
+ const CLOSING_BRACKET_RE = />/g;
1861
+ const CLOSING_SINGLE_QUOTE_RE = /[^\\]?'/g;
1862
+ const CLOSING_DOUBLE_QUOTE_RE = /[^\\]?"/g;
1863
+ const CLOSING_COMMENT_RE = /-->/g;
1864
+ function parse(spans) {
1865
+ let matcher = CHILDREN_RE;
1866
+ const stack = [];
1867
+ let element = {
1868
+ type: "element",
1869
+ open: { type: "tag", slash: "", value: "" },
1870
+ close: null,
1871
+ props: [],
1872
+ children: [],
1873
+ };
1874
+ const targets = [];
1875
+ let lineStart = true;
1876
+ for (let s = 0; s < spans.length; s++) {
1877
+ const span = spans[s];
1878
+ // Whether or not an expression is upcoming. Used to provide better errors.
1879
+ const expressing = s < spans.length - 1;
1880
+ let expressionTarget = null;
1881
+ for (let i = 0, end = i; i < span.length; i = end) {
1882
+ matcher.lastIndex = i;
1883
+ const match = matcher.exec(span);
1884
+ end = match ? match.index + match[0].length : span.length;
1885
+ switch (matcher) {
1886
+ case CHILDREN_RE: {
1887
+ if (match) {
1888
+ const [, newline, comment, tag, closingSlash, tagName] = match;
1889
+ if (i < match.index) {
1890
+ let before = span.slice(i, match.index);
1891
+ if (lineStart) {
1892
+ before = before.replace(/^\s*/, "");
1893
+ }
1894
+ if (newline) {
1895
+ if (span[Math.max(0, match.index - 1)] === "\\") {
1896
+ // We preserve whitespace before escaped newlines and have to
1897
+ // remove the backslash.
1898
+ // jsx` \
1899
+ // `
1900
+ before = before.slice(0, -1);
1901
+ }
1902
+ else {
1903
+ before = before.replace(/\s*$/, "");
1904
+ }
1905
+ }
1906
+ if (before) {
1907
+ element.children.push({ type: "value", value: before });
1908
+ }
1909
+ }
1910
+ lineStart = !!newline;
1911
+ if (comment) {
1912
+ if (end === span.length) {
1913
+ // Expression in a comment:
1914
+ // jsx`<!-- ${exp} -->`
1915
+ matcher = CLOSING_COMMENT_RE;
1916
+ }
1917
+ }
1918
+ else if (tag) {
1919
+ if (closingSlash) {
1920
+ element.close = {
1921
+ type: "tag",
1922
+ slash: closingSlash,
1923
+ value: tagName,
1924
+ };
1925
+ if (!stack.length) {
1926
+ if (end !== span.length) {
1927
+ throw new SyntaxError(`Unmatched closing tag "${tagName}"`);
1928
+ }
1929
+ // ERROR EXPRESSION
1930
+ expressionTarget = {
1931
+ type: "error",
1932
+ message: "Unmatched closing tag ${}",
1933
+ value: null,
1934
+ };
1935
+ }
1936
+ else {
1937
+ if (end === span.length) {
1938
+ // TAG EXPRESSION
1939
+ expressionTarget = element.close;
1940
+ }
1941
+ element = stack.pop();
1942
+ matcher = CLOSING_BRACKET_RE;
1943
+ }
1944
+ }
1945
+ else {
1946
+ const next = {
1947
+ type: "element",
1948
+ open: {
1949
+ type: "tag",
1950
+ slash: "",
1951
+ value: tagName,
1952
+ },
1953
+ close: null,
1954
+ props: [],
1955
+ children: [],
1956
+ };
1957
+ element.children.push(next);
1958
+ stack.push(element);
1959
+ element = next;
1960
+ matcher = PROPS_RE;
1961
+ if (end === span.length) {
1962
+ // TAG EXPRESSION
1963
+ expressionTarget = element.open;
1964
+ }
1965
+ }
1966
+ }
1967
+ }
1968
+ else {
1969
+ if (i < span.length) {
1970
+ let after = span.slice(i);
1971
+ if (!expressing) {
1972
+ // trim trailing whitespace
1973
+ after = after.replace(/\s*$/, "");
1974
+ }
1975
+ if (after) {
1976
+ element.children.push({ type: "value", value: after });
1977
+ }
1978
+ }
1979
+ }
1980
+ break;
1981
+ }
1982
+ case PROPS_RE: {
1983
+ if (match) {
1984
+ const [, tagEnd, spread, name, equals, string] = match;
1985
+ if (i < match.index) {
1986
+ throw new SyntaxError(`Unexpected text \`${span.slice(i, match.index).trim()}\``);
1987
+ }
1988
+ if (tagEnd) {
1989
+ if (tagEnd[0] === "/") {
1990
+ // This is a self-closing element, so there will always be a
1991
+ // result on the stack.
1992
+ element = stack.pop();
1993
+ }
1994
+ matcher = CHILDREN_RE;
1995
+ }
1996
+ else if (spread) {
1997
+ const value = {
1998
+ type: "value",
1999
+ value: null,
2000
+ };
2001
+ element.props.push(value);
2002
+ // SPREAD PROP EXPRESSION
2003
+ expressionTarget = value;
2004
+ if (!(expressing && end === span.length)) {
2005
+ throw new SyntaxError('Expression expected after "..."');
2006
+ }
2007
+ }
2008
+ else if (name) {
2009
+ let value;
2010
+ if (string == null) {
2011
+ if (!equals) {
2012
+ value = { type: "value", value: true };
2013
+ }
2014
+ else if (end < span.length) {
2015
+ throw new SyntaxError(`Unexpected text \`${span.slice(end, end + 20)}\``);
2016
+ }
2017
+ else {
2018
+ value = { type: "value", value: null };
2019
+ // PROP EXPRESSION
2020
+ expressionTarget = value;
2021
+ if (!(expressing && end === span.length)) {
2022
+ throw new SyntaxError(`Expression expected for prop "${name}"`);
2023
+ }
2024
+ }
2025
+ }
2026
+ else {
2027
+ const quote = string[0];
2028
+ value = { type: "propString", parts: [] };
2029
+ value.parts.push(string);
2030
+ if (end === span.length) {
2031
+ matcher =
2032
+ quote === "'"
2033
+ ? CLOSING_SINGLE_QUOTE_RE
2034
+ : CLOSING_DOUBLE_QUOTE_RE;
2035
+ }
2036
+ }
2037
+ const prop = {
2038
+ type: "prop",
2039
+ name,
2040
+ value,
2041
+ };
2042
+ element.props.push(prop);
2043
+ }
2044
+ }
2045
+ else {
2046
+ if (!expressing) {
2047
+ if (i === span.length) {
2048
+ throw new SyntaxError(`Expected props but reached end of document`);
2049
+ }
2050
+ else {
2051
+ throw new SyntaxError(`Unexpected text \`${span.slice(i, i + 20).trim()}\``);
2052
+ }
2053
+ }
2054
+ // Unexpected expression errors are handled in the outer loop.
2055
+ //
2056
+ // This would most likely be the starting point for the logic of
2057
+ // prop name expressions.
2058
+ // jsx`<p ${name}=${value}>`
2059
+ }
2060
+ break;
2061
+ }
2062
+ case CLOSING_BRACKET_RE: {
2063
+ // We’re in a closing tag and looking for the >.
2064
+ if (match) {
2065
+ if (i < match.index) {
2066
+ throw new SyntaxError(`Unexpected text \`${span.slice(i, match.index).trim()}\``);
2067
+ }
2068
+ matcher = CHILDREN_RE;
2069
+ }
2070
+ else {
2071
+ if (!expressing) {
2072
+ throw new SyntaxError(`Unexpected text \`${span.slice(i, i + 20).trim()}\``);
2073
+ }
2074
+ }
2075
+ break;
2076
+ }
2077
+ case CLOSING_SINGLE_QUOTE_RE:
2078
+ case CLOSING_DOUBLE_QUOTE_RE: {
2079
+ const string = span.slice(i, end);
2080
+ const prop = element.props[element.props.length - 1];
2081
+ const propString = prop.value;
2082
+ propString.parts.push(string);
2083
+ if (match) {
2084
+ matcher = PROPS_RE;
2085
+ }
2086
+ else {
2087
+ if (!expressing) {
2088
+ throw new SyntaxError(`Missing \`${matcher === CLOSING_SINGLE_QUOTE_RE ? "'" : '"'}\``);
2089
+ }
2090
+ }
2091
+ break;
2092
+ }
2093
+ case CLOSING_COMMENT_RE: {
2094
+ if (match) {
2095
+ matcher = CHILDREN_RE;
2096
+ }
2097
+ else {
2098
+ if (!expressing) {
2099
+ throw new SyntaxError("Expected `-->` but reached end of template");
2100
+ }
2101
+ }
2102
+ break;
2103
+ }
2104
+ }
2105
+ }
2106
+ if (expressing) {
2107
+ if (expressionTarget) {
2108
+ targets.push(expressionTarget);
2109
+ if (expressionTarget.type === "error") {
2110
+ break;
2111
+ }
2112
+ continue;
2113
+ }
2114
+ switch (matcher) {
2115
+ case CHILDREN_RE: {
2116
+ const target = { type: "value", value: null };
2117
+ element.children.push(target);
2118
+ targets.push(target);
2119
+ break;
2120
+ }
2121
+ case CLOSING_SINGLE_QUOTE_RE:
2122
+ case CLOSING_DOUBLE_QUOTE_RE: {
2123
+ const prop = element.props[element.props.length - 1];
2124
+ const target = { type: "value", value: null };
2125
+ prop.value.parts.push(target);
2126
+ targets.push(target);
2127
+ break;
2128
+ }
2129
+ case CLOSING_COMMENT_RE:
2130
+ targets.push(null);
2131
+ break;
2132
+ default:
2133
+ throw new SyntaxError("Unexpected expression");
2134
+ }
2135
+ }
2136
+ else if (expressionTarget) {
2137
+ throw new SyntaxError("Expression expected");
2138
+ }
2139
+ lineStart = false;
2140
+ }
2141
+ if (stack.length) {
2142
+ const ti = targets.indexOf(element.open);
2143
+ if (ti === -1) {
2144
+ throw new SyntaxError(`Unmatched opening tag "${element.open.value}"`);
2145
+ }
2146
+ targets[ti] = {
2147
+ type: "error",
2148
+ message: "Unmatched opening tag ${}",
2149
+ value: null,
2150
+ };
2151
+ }
2152
+ if (element.children.length === 1 && element.children[0].type === "element") {
2153
+ element = element.children[0];
2154
+ }
2155
+ return { element, targets };
2156
+ }
2157
+ function build(parsed) {
2158
+ if (parsed.close !== null &&
2159
+ parsed.close.slash !== "//" &&
2160
+ parsed.open.value !== parsed.close.value) {
2161
+ throw new SyntaxError(`Unmatched closing tag ${formatTagForError(parsed.close.value)}, expected ${formatTagForError(parsed.open.value)}`);
2162
+ }
2163
+ const children = [];
2164
+ for (let i = 0; i < parsed.children.length; i++) {
2165
+ const child = parsed.children[i];
2166
+ children.push(child.type === "element" ? build(child) : child.value);
2167
+ }
2168
+ let props = parsed.props.length ? {} : null;
2169
+ for (let i = 0; i < parsed.props.length; i++) {
2170
+ const prop = parsed.props[i];
2171
+ if (prop.type === "prop") {
2172
+ let value;
2173
+ if (prop.value.type === "value") {
2174
+ value = prop.value.value;
2175
+ }
2176
+ else {
2177
+ let string = "";
2178
+ for (let i = 0; i < prop.value.parts.length; i++) {
2179
+ const part = prop.value.parts[i];
2180
+ if (typeof part === "string") {
2181
+ string += part;
2182
+ }
2183
+ else if (typeof part.value !== "boolean" && part.value != null) {
2184
+ string +=
2185
+ typeof part.value === "string" ? part.value : String(part.value);
2186
+ }
2187
+ }
2188
+ value = string
2189
+ // remove quotes
2190
+ .slice(1, -1)
2191
+ // unescape things
2192
+ // adapted from https://stackoverflow.com/a/57330383/1825413
2193
+ .replace(/\\x[0-9a-f]{2}|\\u[0-9a-f]{4}|\\u\{[0-9a-f]+\}|\\./gi, (match) => {
2194
+ switch (match[1]) {
2195
+ case "b":
2196
+ return "\b";
2197
+ case "f":
2198
+ return "\f";
2199
+ case "n":
2200
+ return "\n";
2201
+ case "r":
2202
+ return "\r";
2203
+ case "t":
2204
+ return "\t";
2205
+ case "v":
2206
+ return "\v";
2207
+ case "x":
2208
+ return String.fromCharCode(parseInt(match.slice(2), 16));
2209
+ case "u":
2210
+ if (match[2] === "{") {
2211
+ return String.fromCodePoint(parseInt(match.slice(3, -1), 16));
2212
+ }
2213
+ return String.fromCharCode(parseInt(match.slice(2), 16));
2214
+ case "0":
2215
+ return "\0";
2216
+ default:
2217
+ return match.slice(1);
2218
+ }
2219
+ });
2220
+ }
2221
+ props[prop.name] = value;
2222
+ }
2223
+ else {
2224
+ // spread prop
2225
+ props = { ...props, ...prop.value };
2226
+ }
2227
+ }
2228
+ return createElement(parsed.open.value, props, ...children);
2229
+ }
2230
+ function formatTagForError(tag) {
2231
+ return typeof tag === "function"
2232
+ ? tag.name + "()"
2233
+ : typeof tag === "string"
2234
+ ? `"${tag}"`
2235
+ : JSON.stringify(tag);
2236
+ }
2237
+
1801
2238
  const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
1802
2239
  const impl$1 = {
1803
2240
  parse(text) {
@@ -2178,6 +2615,7 @@
2178
2615
  exports.dom = dom;
2179
2616
  exports.html = html;
2180
2617
  exports.isElement = isElement;
2618
+ exports.jsx = jsx;
2181
2619
 
2182
2620
  Object.defineProperty(exports, '__esModule', { value: true });
2183
2621