@microsoft/fast-element 2.0.0-beta.2 → 2.0.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.
Files changed (54) hide show
  1. package/CHANGELOG.json +147 -0
  2. package/CHANGELOG.md +42 -1
  3. package/dist/dts/components/fast-definitions.d.ts +9 -8
  4. package/dist/dts/components/fast-element.d.ts +12 -24
  5. package/dist/dts/context.d.ts +1 -1
  6. package/dist/dts/di/di.d.ts +858 -0
  7. package/dist/dts/hooks.d.ts +2 -2
  8. package/dist/dts/interfaces.d.ts +40 -7
  9. package/dist/dts/observation/observable.d.ts +19 -13
  10. package/dist/dts/styles/element-styles.d.ts +6 -0
  11. package/dist/dts/templating/binding-signal.d.ts +10 -27
  12. package/dist/dts/templating/binding-two-way.d.ts +16 -41
  13. package/dist/dts/templating/binding.d.ts +79 -118
  14. package/dist/dts/templating/html-directive.d.ts +31 -3
  15. package/dist/dts/templating/render.d.ts +277 -0
  16. package/dist/dts/templating/repeat.d.ts +12 -16
  17. package/dist/dts/templating/template.d.ts +3 -3
  18. package/dist/dts/templating/when.d.ts +3 -3
  19. package/dist/dts/testing/exports.d.ts +2 -0
  20. package/dist/dts/testing/fixture.d.ts +90 -0
  21. package/dist/dts/testing/timeout.d.ts +7 -0
  22. package/dist/esm/components/fast-definitions.js +25 -27
  23. package/dist/esm/components/fast-element.js +20 -11
  24. package/dist/esm/context.js +5 -1
  25. package/dist/esm/debug.js +36 -4
  26. package/dist/esm/di/di.js +1351 -0
  27. package/dist/esm/observation/arrays.js +303 -2
  28. package/dist/esm/observation/observable.js +11 -6
  29. package/dist/esm/platform.js +1 -1
  30. package/dist/esm/styles/element-styles.js +14 -0
  31. package/dist/esm/templating/binding-signal.js +56 -61
  32. package/dist/esm/templating/binding-two-way.js +56 -34
  33. package/dist/esm/templating/binding.js +137 -156
  34. package/dist/esm/templating/compiler.js +30 -7
  35. package/dist/esm/templating/html-directive.js +16 -2
  36. package/dist/esm/templating/render.js +392 -0
  37. package/dist/esm/templating/repeat.js +57 -40
  38. package/dist/esm/templating/template.js +8 -5
  39. package/dist/esm/templating/view.js +3 -1
  40. package/dist/esm/templating/when.js +5 -4
  41. package/dist/esm/testing/exports.js +2 -0
  42. package/dist/esm/testing/fixture.js +88 -0
  43. package/dist/esm/testing/timeout.js +24 -0
  44. package/dist/fast-element.api.json +2828 -2758
  45. package/dist/fast-element.d.ts +218 -230
  46. package/dist/fast-element.debug.js +656 -257
  47. package/dist/fast-element.debug.min.js +1 -1
  48. package/dist/fast-element.js +620 -253
  49. package/dist/fast-element.min.js +1 -1
  50. package/dist/fast-element.untrimmed.d.ts +226 -235
  51. package/docs/api-report.md +88 -91
  52. package/package.json +15 -6
  53. package/dist/dts/observation/splice-strategies.d.ts +0 -13
  54. package/dist/esm/observation/splice-strategies.js +0 -400
@@ -97,19 +97,51 @@ const debugMessages = {
97
97
  [1101 /* needsArrayObservation */]: "Must call enableArrayObservation before observing arrays.",
98
98
  [1201 /* onlySetHTMLPolicyOnce */]: "The HTML policy can only be set once.",
99
99
  [1202 /* bindingInnerHTMLRequiresTrustedTypes */]: "To bind innerHTML, you must use a TrustedTypesPolicy.",
100
+ [1203 /* twoWayBindingRequiresObservables */]: "View=>Model update skipped. To use twoWay binding, the target property must be observable.",
101
+ [1204 /* hostBindingWithoutHost */]: "No host element is present. Cannot bind host with ${name}.",
102
+ [1205 /* unsupportedBindingBehavior */]: "The requested binding behavior is not supported by the binding engine.",
100
103
  [1401 /* missingElementDefinition */]: "Missing FASTElement definition.",
104
+ [1501 /* noRegistrationForContext */]: "No registration for Context/Interface '${name}'.",
105
+ [1502 /* noFactoryForResolver */]: "Dependency injection resolver for '${key}' returned a null factory.",
106
+ [1503 /* invalidResolverStrategy */]: "Invalid dependency injection resolver strategy specified '${strategy}'.",
107
+ [1504 /* cannotAutoregisterDependency */]: "Unable to autoregister dependency.",
108
+ [1505 /* cannotResolveKey */]: "Unable to resolve dependency injection key '${key}'.",
109
+ [1506 /* cannotConstructNativeFunction */]: "'${name}' is a native function and therefore cannot be safely constructed by DI. If this is intentional, please use a callback or cachedCallback resolver.",
110
+ [1507 /* cannotJITRegisterNonConstructor */]: "Attempted to jitRegister something that is not a constructor '${value}'. Did you forget to register this dependency?",
111
+ [1508 /* cannotJITRegisterIntrinsic */]: "Attempted to jitRegister an intrinsic type '${value}'. Did you forget to add @inject(Key)?",
112
+ [1509 /* cannotJITRegisterInterface */]: "Attempted to jitRegister an interface '${value}'.",
113
+ [1510 /* invalidResolver */]: "A valid resolver was not returned from the register method.",
114
+ [1511 /* invalidKey */]: "Key/value cannot be null or undefined. Are you trying to inject/register something that doesn't exist with DI?",
115
+ [1512 /* noDefaultResolver */]: "'${key}' not registered. Did you forget to add @singleton()?",
116
+ [1513 /* cyclicDependency */]: "Cyclic dependency found '${name}'.",
117
+ [1514 /* connectUpdateRequiresController */]: "Injected properties that are updated on changes to DOM connectivity require the target object to be an instance of FASTElement.",
101
118
  };
119
+ const allPlaceholders = /(\$\{\w+?})/g;
120
+ const placeholder = /\$\{(\w+?)}/g;
121
+ const noValues = Object.freeze({});
122
+ function formatMessage(message, values) {
123
+ return message
124
+ .split(allPlaceholders)
125
+ .map(v => {
126
+ var _a;
127
+ const replaced = v.replace(placeholder, "$1");
128
+ return String((_a = values[replaced]) !== null && _a !== void 0 ? _a : v);
129
+ })
130
+ .join("");
131
+ }
102
132
  Object.assign(FAST$1, {
103
133
  addMessages(messages) {
104
134
  Object.assign(debugMessages, messages);
105
135
  },
106
- warn(code, ...args) {
136
+ warn(code, values = noValues) {
107
137
  var _a;
108
- console.warn((_a = debugMessages[code]) !== null && _a !== void 0 ? _a : "Unknown Warning");
138
+ const message = (_a = debugMessages[code]) !== null && _a !== void 0 ? _a : "Unknown Warning";
139
+ console.warn(formatMessage(message, values));
109
140
  },
110
- error(code, ...args) {
141
+ error(code, values = noValues) {
111
142
  var _a;
112
- return new Error((_a = debugMessages[code]) !== null && _a !== void 0 ? _a : "Unknown Error");
143
+ const message = (_a = debugMessages[code]) !== null && _a !== void 0 ? _a : "Unknown Error";
144
+ return new Error(formatMessage(message, values));
113
145
  },
114
146
  });
115
147
 
@@ -141,7 +173,7 @@ if (FAST.error === void 0) {
141
173
  Object.assign(FAST, {
142
174
  warn() { },
143
175
  error(code) {
144
- return new Error(`Code ${code}`);
176
+ return new Error(`Error ${code}`);
145
177
  },
146
178
  addMessages() { },
147
179
  });
@@ -478,7 +510,7 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
478
510
  }
479
511
  }
480
512
  }
481
- class BindingObserverImplementation extends SubscriberSet {
513
+ class ExpressionNotifierImplementation extends SubscriberSet {
482
514
  constructor(binding, initialSubscriber, isVolatileBinding = false) {
483
515
  super(binding, initialSubscriber);
484
516
  this.binding = binding;
@@ -503,8 +535,13 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
503
535
  const previousWatcher = watcher;
504
536
  watcher = this.needsRefresh ? this : void 0;
505
537
  this.needsRefresh = this.isVolatileBinding;
506
- const result = this.binding(source, context !== null && context !== void 0 ? context : ExecutionContext.default);
507
- watcher = previousWatcher;
538
+ let result;
539
+ try {
540
+ result = this.binding(source, context !== null && context !== void 0 ? context : ExecutionContext.default);
541
+ }
542
+ finally {
543
+ watcher = previousWatcher;
544
+ }
508
545
  return result;
509
546
  }
510
547
  dispose() {
@@ -633,14 +670,14 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
633
670
  */
634
671
  getAccessors,
635
672
  /**
636
- * Creates a {@link BindingObserver} that can watch the
637
- * provided {@link Binding} for changes.
673
+ * Creates a {@link ExpressionNotifier} that can watch the
674
+ * provided {@link Expression} for changes.
638
675
  * @param binding - The binding to observe.
639
676
  * @param initialSubscriber - An initial subscriber to changes in the binding value.
640
677
  * @param isVolatileBinding - Indicates whether the binding's dependency list must be re-evaluated on every value evaluation.
641
678
  */
642
679
  binding(binding, initialSubscriber, isVolatileBinding = this.isVolatileBinding(binding)) {
643
- return new BindingObserverImplementation(binding, initialSubscriber, isVolatileBinding);
680
+ return new ExpressionNotifierImplementation(binding, initialSubscriber, isVolatileBinding);
644
681
  },
645
682
  /**
646
683
  * Determines whether a binding expression is volatile and needs to have its dependency list re-evaluated
@@ -874,10 +911,311 @@ const SpliceStrategySupport = Object.freeze({
874
911
  const reset = new Splice(0, emptyArray, 0);
875
912
  reset.reset = true;
876
913
  const resetSplices = [reset];
914
+ // Note: This function is *based* on the computation of the Levenshtein
915
+ // "edit" distance. The one change is that "updates" are treated as two
916
+ // edits - not one. With Array splices, an update is really a delete
917
+ // followed by an add. By retaining this, we optimize for "keeping" the
918
+ // maximum array items in the original array. For example:
919
+ //
920
+ // 'xxxx123' to '123yyyy'
921
+ //
922
+ // With 1-edit updates, the shortest path would be just to update all seven
923
+ // characters. With 2-edit updates, we delete 4, leave 3, and add 4. This
924
+ // leaves the substring '123' intact.
925
+ function calcEditDistances(current, currentStart, currentEnd, old, oldStart, oldEnd) {
926
+ // "Deletion" columns
927
+ const rowCount = oldEnd - oldStart + 1;
928
+ const columnCount = currentEnd - currentStart + 1;
929
+ const distances = new Array(rowCount);
930
+ let north;
931
+ let west;
932
+ // "Addition" rows. Initialize null column.
933
+ for (let i = 0; i < rowCount; ++i) {
934
+ distances[i] = new Array(columnCount);
935
+ distances[i][0] = i;
936
+ }
937
+ // Initialize null row
938
+ for (let j = 0; j < columnCount; ++j) {
939
+ distances[0][j] = j;
940
+ }
941
+ for (let i = 1; i < rowCount; ++i) {
942
+ for (let j = 1; j < columnCount; ++j) {
943
+ if (current[currentStart + j - 1] === old[oldStart + i - 1]) {
944
+ distances[i][j] = distances[i - 1][j - 1];
945
+ }
946
+ else {
947
+ north = distances[i - 1][j] + 1;
948
+ west = distances[i][j - 1] + 1;
949
+ distances[i][j] = north < west ? north : west;
950
+ }
951
+ }
952
+ }
953
+ return distances;
954
+ }
955
+ // This starts at the final weight, and walks "backward" by finding
956
+ // the minimum previous weight recursively until the origin of the weight
957
+ // matrix.
958
+ function spliceOperationsFromEditDistances(distances) {
959
+ let i = distances.length - 1;
960
+ let j = distances[0].length - 1;
961
+ let current = distances[i][j];
962
+ const edits = [];
963
+ while (i > 0 || j > 0) {
964
+ if (i === 0) {
965
+ edits.push(2 /* Edit.add */);
966
+ j--;
967
+ continue;
968
+ }
969
+ if (j === 0) {
970
+ edits.push(3 /* Edit.delete */);
971
+ i--;
972
+ continue;
973
+ }
974
+ const northWest = distances[i - 1][j - 1];
975
+ const west = distances[i - 1][j];
976
+ const north = distances[i][j - 1];
977
+ let min;
978
+ if (west < north) {
979
+ min = west < northWest ? west : northWest;
980
+ }
981
+ else {
982
+ min = north < northWest ? north : northWest;
983
+ }
984
+ if (min === northWest) {
985
+ if (northWest === current) {
986
+ edits.push(0 /* Edit.leave */);
987
+ }
988
+ else {
989
+ edits.push(1 /* Edit.update */);
990
+ current = northWest;
991
+ }
992
+ i--;
993
+ j--;
994
+ }
995
+ else if (min === west) {
996
+ edits.push(3 /* Edit.delete */);
997
+ i--;
998
+ current = west;
999
+ }
1000
+ else {
1001
+ edits.push(2 /* Edit.add */);
1002
+ j--;
1003
+ current = north;
1004
+ }
1005
+ }
1006
+ return edits.reverse();
1007
+ }
1008
+ function sharedPrefix(current, old, searchLength) {
1009
+ for (let i = 0; i < searchLength; ++i) {
1010
+ if (current[i] !== old[i]) {
1011
+ return i;
1012
+ }
1013
+ }
1014
+ return searchLength;
1015
+ }
1016
+ function sharedSuffix(current, old, searchLength) {
1017
+ let index1 = current.length;
1018
+ let index2 = old.length;
1019
+ let count = 0;
1020
+ while (count < searchLength && current[--index1] === old[--index2]) {
1021
+ count++;
1022
+ }
1023
+ return count;
1024
+ }
1025
+ function intersect(start1, end1, start2, end2) {
1026
+ // Disjoint
1027
+ if (end1 < start2 || end2 < start1) {
1028
+ return -1;
1029
+ }
1030
+ // Adjacent
1031
+ if (end1 === start2 || end2 === start1) {
1032
+ return 0;
1033
+ }
1034
+ // Non-zero intersect, span1 first
1035
+ if (start1 < start2) {
1036
+ if (end1 < end2) {
1037
+ return end1 - start2; // Overlap
1038
+ }
1039
+ return end2 - start2; // Contained
1040
+ }
1041
+ // Non-zero intersect, span2 first
1042
+ if (end2 < end1) {
1043
+ return end2 - start1; // Overlap
1044
+ }
1045
+ return end1 - start1; // Contained
1046
+ }
1047
+ /**
1048
+ * @remarks
1049
+ * Lacking individual splice mutation information, the minimal set of
1050
+ * splices can be synthesized given the previous state and final state of an
1051
+ * array. The basic approach is to calculate the edit distance matrix and
1052
+ * choose the shortest path through it.
1053
+ *
1054
+ * Complexity: O(l * p)
1055
+ * l: The length of the current array
1056
+ * p: The length of the old array
1057
+ */
1058
+ function calc(current, currentStart, currentEnd, old, oldStart, oldEnd) {
1059
+ let prefixCount = 0;
1060
+ let suffixCount = 0;
1061
+ const minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart);
1062
+ if (currentStart === 0 && oldStart === 0) {
1063
+ prefixCount = sharedPrefix(current, old, minLength);
1064
+ }
1065
+ if (currentEnd === current.length && oldEnd === old.length) {
1066
+ suffixCount = sharedSuffix(current, old, minLength - prefixCount);
1067
+ }
1068
+ currentStart += prefixCount;
1069
+ oldStart += prefixCount;
1070
+ currentEnd -= suffixCount;
1071
+ oldEnd -= suffixCount;
1072
+ if (currentEnd - currentStart === 0 && oldEnd - oldStart === 0) {
1073
+ return emptyArray;
1074
+ }
1075
+ if (currentStart === currentEnd) {
1076
+ const splice = new Splice(currentStart, [], 0);
1077
+ while (oldStart < oldEnd) {
1078
+ splice.removed.push(old[oldStart++]);
1079
+ }
1080
+ return [splice];
1081
+ }
1082
+ else if (oldStart === oldEnd) {
1083
+ return [new Splice(currentStart, [], currentEnd - currentStart)];
1084
+ }
1085
+ const ops = spliceOperationsFromEditDistances(calcEditDistances(current, currentStart, currentEnd, old, oldStart, oldEnd));
1086
+ const splices = [];
1087
+ let splice = void 0;
1088
+ let index = currentStart;
1089
+ let oldIndex = oldStart;
1090
+ for (let i = 0; i < ops.length; ++i) {
1091
+ switch (ops[i]) {
1092
+ case 0 /* Edit.leave */:
1093
+ if (splice !== void 0) {
1094
+ splices.push(splice);
1095
+ splice = void 0;
1096
+ }
1097
+ index++;
1098
+ oldIndex++;
1099
+ break;
1100
+ case 1 /* Edit.update */:
1101
+ if (splice === void 0) {
1102
+ splice = new Splice(index, [], 0);
1103
+ }
1104
+ splice.addedCount++;
1105
+ index++;
1106
+ splice.removed.push(old[oldIndex]);
1107
+ oldIndex++;
1108
+ break;
1109
+ case 2 /* Edit.add */:
1110
+ if (splice === void 0) {
1111
+ splice = new Splice(index, [], 0);
1112
+ }
1113
+ splice.addedCount++;
1114
+ index++;
1115
+ break;
1116
+ case 3 /* Edit.delete */:
1117
+ if (splice === void 0) {
1118
+ splice = new Splice(index, [], 0);
1119
+ }
1120
+ splice.removed.push(old[oldIndex]);
1121
+ oldIndex++;
1122
+ break;
1123
+ // no default
1124
+ }
1125
+ }
1126
+ if (splice !== void 0) {
1127
+ splices.push(splice);
1128
+ }
1129
+ return splices;
1130
+ }
1131
+ function merge(splice, splices) {
1132
+ let inserted = false;
1133
+ let insertionOffset = 0;
1134
+ for (let i = 0; i < splices.length; i++) {
1135
+ const current = splices[i];
1136
+ current.index += insertionOffset;
1137
+ if (inserted) {
1138
+ continue;
1139
+ }
1140
+ const intersectCount = intersect(splice.index, splice.index + splice.removed.length, current.index, current.index + current.addedCount);
1141
+ if (intersectCount >= 0) {
1142
+ // Merge the two splices
1143
+ splices.splice(i, 1);
1144
+ i--;
1145
+ insertionOffset -= current.addedCount - current.removed.length;
1146
+ splice.addedCount += current.addedCount - intersectCount;
1147
+ const deleteCount = splice.removed.length + current.removed.length - intersectCount;
1148
+ if (!splice.addedCount && !deleteCount) {
1149
+ // merged splice is a noop. discard.
1150
+ inserted = true;
1151
+ }
1152
+ else {
1153
+ let currentRemoved = current.removed;
1154
+ if (splice.index < current.index) {
1155
+ // some prefix of splice.removed is prepended to current.removed.
1156
+ const prepend = splice.removed.slice(0, current.index - splice.index);
1157
+ prepend.push(...currentRemoved);
1158
+ currentRemoved = prepend;
1159
+ }
1160
+ if (splice.index + splice.removed.length >
1161
+ current.index + current.addedCount) {
1162
+ // some suffix of splice.removed is appended to current.removed.
1163
+ const append = splice.removed.slice(current.index + current.addedCount - splice.index);
1164
+ currentRemoved.push(...append);
1165
+ }
1166
+ splice.removed = currentRemoved;
1167
+ if (current.index < splice.index) {
1168
+ splice.index = current.index;
1169
+ }
1170
+ }
1171
+ }
1172
+ else if (splice.index < current.index) {
1173
+ // Insert splice here.
1174
+ inserted = true;
1175
+ splices.splice(i, 0, splice);
1176
+ i++;
1177
+ const offset = splice.addedCount - splice.removed.length;
1178
+ current.index += offset;
1179
+ insertionOffset += offset;
1180
+ }
1181
+ }
1182
+ if (!inserted) {
1183
+ splices.push(splice);
1184
+ }
1185
+ }
1186
+ function project(array, changes) {
1187
+ let splices = [];
1188
+ const initialSplices = [];
1189
+ for (let i = 0, ii = changes.length; i < ii; i++) {
1190
+ merge(changes[i], initialSplices);
1191
+ }
1192
+ for (let i = 0, ii = initialSplices.length; i < ii; ++i) {
1193
+ const splice = initialSplices[i];
1194
+ if (splice.addedCount === 1 && splice.removed.length === 1) {
1195
+ if (splice.removed[0] !== array[splice.index]) {
1196
+ splices.push(splice);
1197
+ }
1198
+ continue;
1199
+ }
1200
+ splices = splices.concat(calc(array, splice.index, splice.index + splice.addedCount, splice.removed, 0, splice.removed.length));
1201
+ }
1202
+ return splices;
1203
+ }
1204
+ /**
1205
+ * A SpliceStrategy that attempts to merge all splices into the minimal set of
1206
+ * splices needed to represent the change from the old array to the new array.
1207
+ * @public
1208
+ */
877
1209
  let defaultSpliceStrategy = Object.freeze({
878
- support: SpliceStrategySupport.splice,
1210
+ support: SpliceStrategySupport.optimized,
879
1211
  normalize(previous, current, changes) {
880
- return previous === void 0 ? changes !== null && changes !== void 0 ? changes : emptyArray : resetSplices;
1212
+ if (previous === void 0) {
1213
+ if (changes === void 0) {
1214
+ return emptyArray;
1215
+ }
1216
+ return changes.length > 1 ? project(current, changes) : changes;
1217
+ }
1218
+ return resetSplices;
881
1219
  },
882
1220
  pop(array, observer, pop, args) {
883
1221
  const notEmpty = array.length > 0;
@@ -1146,6 +1484,20 @@ class ElementStyles {
1146
1484
  static setDefaultStrategy(Strategy) {
1147
1485
  DefaultStyleStrategy = Strategy;
1148
1486
  }
1487
+ /**
1488
+ * Normalizes a set of composable style options.
1489
+ * @param styles - The style options to normalize.
1490
+ * @returns A singular ElementStyles instance or undefined.
1491
+ */
1492
+ static normalize(styles) {
1493
+ return styles === void 0
1494
+ ? void 0
1495
+ : Array.isArray(styles)
1496
+ ? new ElementStyles(styles)
1497
+ : styles instanceof ElementStyles
1498
+ ? styles
1499
+ : new ElementStyles([styles]);
1500
+ }
1149
1501
  }
1150
1502
  /**
1151
1503
  * Indicates whether the DOM supports the adoptedStyleSheets feature.
@@ -1472,6 +1824,13 @@ function htmlDirective(options) {
1472
1824
  HTMLDirective.define(type, options);
1473
1825
  };
1474
1826
  }
1827
+ /**
1828
+ * Captures a binding expression along with related information and capabilities.
1829
+ *
1830
+ * @public
1831
+ */
1832
+ class Binding {
1833
+ }
1475
1834
  /**
1476
1835
  * The type of HTML aspect to target.
1477
1836
  * @public
@@ -1509,12 +1868,15 @@ const Aspect = Object.freeze({
1509
1868
  *
1510
1869
  * @param directive - The directive to assign the aspect to.
1511
1870
  * @param value - The value to base the aspect determination on.
1871
+ * @remarks
1872
+ * If a falsy value is provided, then the content aspect will be assigned.
1512
1873
  */
1513
1874
  assign(directive, value) {
1514
- directive.sourceAspect = value;
1515
1875
  if (!value) {
1876
+ directive.aspectType = Aspect.content;
1516
1877
  return;
1517
1878
  }
1879
+ directive.sourceAspect = value;
1518
1880
  switch (value[0]) {
1519
1881
  case ":":
1520
1882
  directive.targetAspect = value.substring(1);
@@ -1562,6 +1924,10 @@ class StatelessAttachedAttributeDirective {
1562
1924
  */
1563
1925
  constructor(options) {
1564
1926
  this.options = options;
1927
+ /**
1928
+ * The unique id of the factory.
1929
+ */
1930
+ this.id = nextId();
1565
1931
  }
1566
1932
  /**
1567
1933
  * Creates a behavior.
@@ -1590,99 +1956,28 @@ const createInnerHTMLBinding = globalThis.TrustedHTML
1590
1956
  throw FAST.error(1202 /* Message.bindingInnerHTMLRequiresTrustedTypes */);
1591
1957
  }
1592
1958
  : (binding) => binding;
1593
- /**
1594
- * Describes how aspects of an HTML element will be affected by bindings.
1595
- * @public
1596
- */
1597
- const BindingMode = Object.freeze({
1598
- /**
1599
- * Creates a binding mode based on the supplied behavior types.
1600
- * @param UpdateType - The base behavior type used to update aspects.
1601
- * @param EventType - The base behavior type used to respond to events.
1602
- * @returns A new binding mode.
1603
- */
1604
- define(UpdateType, EventType = EventBinding) {
1605
- return Object.freeze({
1606
- [1]: d => new UpdateType(d, DOM.setAttribute),
1607
- [2]: d => new UpdateType(d, DOM.setBooleanAttribute),
1608
- [3]: d => new UpdateType(d, (t, a, v) => (t[a] = v)),
1609
- [4]: d => new (createContentBinding(UpdateType))(d, updateContentTarget),
1610
- [5]: d => new UpdateType(d, updateTokenListTarget),
1611
- [6]: d => new EventType(d),
1612
- });
1613
- },
1614
- });
1615
- /**
1616
- * Describes the configuration for a binding expression.
1617
- * @public
1618
- */
1619
- const BindingConfig = Object.freeze({
1620
- /**
1621
- * Creates a binding configuration based on the provided mode and options.
1622
- * @param mode - The mode to use for the configuration.
1623
- * @param defaultOptions - The default options to use for the configuration.
1624
- * @returns A new binding configuration.
1625
- */
1626
- define(mode, defaultOptions) {
1627
- const config = (options) => {
1628
- return {
1629
- mode: config.mode,
1630
- options: Object.assign({}, defaultOptions, options),
1631
- };
1632
- };
1633
- config.options = defaultOptions;
1634
- config.mode = mode;
1635
- return config;
1636
- },
1637
- });
1638
- /**
1639
- * A base binding behavior for DOM updates.
1640
- * @public
1641
- */
1642
- class UpdateBinding {
1643
- /**
1644
- * Creates an instance of UpdateBinding.
1645
- * @param directive - The directive that has the configuration for this behavior.
1646
- * @param updateTarget - The function used to update the target with the latest value.
1647
- */
1648
- constructor(directive, updateTarget) {
1649
- this.directive = directive;
1650
- this.updateTarget = updateTarget;
1959
+ class OnChangeBinding extends Binding {
1960
+ constructor(evaluate, isVolatile) {
1961
+ super();
1962
+ this.evaluate = evaluate;
1963
+ this.isVolatile = isVolatile;
1651
1964
  }
1652
- /**
1653
- * Bind this behavior to the source.
1654
- * @param source - The source to bind to.
1655
- * @param context - The execution context that the binding is operating within.
1656
- * @param targets - The targets that behaviors in a view can attach to.
1657
- */
1658
- bind(source, context, targets) { }
1659
- /**
1660
- * Unbinds this behavior from the source.
1661
- * @param source - The source to unbind from.
1662
- * @param context - The execution context that the binding is operating within.
1663
- * @param targets - The targets that behaviors in a view can attach to.
1664
- */
1665
- unbind(source, context, targets) { }
1666
- /**
1667
- * Creates a behavior.
1668
- * @param targets - The targets available for behaviors to be attached to.
1669
- */
1670
- createBehavior(targets) {
1671
- return this;
1965
+ createObserver(_, subscriber) {
1966
+ return Observable.binding(this.evaluate, subscriber, this.isVolatile);
1672
1967
  }
1673
1968
  }
1674
- function createContentBinding(Type) {
1675
- return class extends Type {
1676
- unbind(source, context, targets) {
1677
- super.unbind(source, context, targets);
1678
- const target = targets[this.directive.nodeId];
1679
- const view = target.$fastView;
1680
- if (view !== void 0 && view.isComposed) {
1681
- view.unbind();
1682
- view.needsBindOnly = true;
1683
- }
1684
- }
1685
- };
1969
+ class OneTimeBinding extends Binding {
1970
+ constructor(evaluate) {
1971
+ super();
1972
+ this.evaluate = evaluate;
1973
+ }
1974
+ createObserver() {
1975
+ return this;
1976
+ }
1977
+ observe(source, context) {
1978
+ return this.evaluate(source, context);
1979
+ }
1980
+ dispose() { }
1686
1981
  }
1687
1982
  function updateContentTarget(target, aspect, value, source, context) {
1688
1983
  // If there's no actual value, then this equates to the
@@ -1690,7 +1985,7 @@ function updateContentTarget(target, aspect, value, source, context) {
1690
1985
  if (value === null || value === undefined) {
1691
1986
  value = "";
1692
1987
  }
1693
- // If the value has a "create" method, then it's a template-like.
1988
+ // If the value has a "create" method, then it's a ContentTemplate.
1694
1989
  if (value.create) {
1695
1990
  target.textContent = "";
1696
1991
  let view = target.$fastView;
@@ -1776,46 +2071,21 @@ function updateTokenListTarget(target, aspect, value) {
1776
2071
  }
1777
2072
  }
1778
2073
  }
1779
- /**
1780
- * A binding behavior for one-time bindings.
1781
- * @public
1782
- */
1783
- class OneTimeBinding extends UpdateBinding {
1784
- /**
1785
- * Bind this behavior to the source.
1786
- * @param source - The source to bind to.
1787
- * @param context - The execution context that the binding is operating within.
1788
- * @param targets - The targets that behaviors in a view can attach to.
1789
- */
1790
- bind(source, context, targets) {
1791
- const directive = this.directive;
1792
- this.updateTarget(targets[directive.nodeId], directive.targetAspect, directive.binding(source, context), source, context);
1793
- }
1794
- }
1795
2074
  /**
1796
2075
  * A binding behavior for bindings that change.
1797
2076
  * @public
1798
2077
  */
1799
- class ChangeBinding extends UpdateBinding {
2078
+ class BindingBehavior {
1800
2079
  /**
1801
2080
  * Creates an instance of ChangeBinding.
1802
2081
  * @param directive - The directive that has the configuration for this behavior.
1803
2082
  * @param updateTarget - The function used to update the target with the latest value.
1804
2083
  */
1805
2084
  constructor(directive, updateTarget) {
1806
- super(directive, updateTarget);
1807
- this.isBindingVolatile = Observable.isVolatileBinding(directive.binding);
2085
+ this.directive = directive;
2086
+ this.updateTarget = updateTarget;
1808
2087
  this.observerProperty = `${directive.id}-o`;
1809
2088
  }
1810
- /**
1811
- * Returns the binding observer used to update the node.
1812
- * @param target - The target node.
1813
- * @returns A BindingObserver.
1814
- */
1815
- getObserver(target) {
1816
- var _a;
1817
- return ((_a = target[this.observerProperty]) !== null && _a !== void 0 ? _a : (target[this.observerProperty] = Observable.binding(this.directive.binding, this, this.isBindingVolatile)));
1818
- }
1819
2089
  /**
1820
2090
  * Bind this behavior to the source.
1821
2091
  * @param source - The source to bind to.
@@ -1852,12 +2122,49 @@ class ChangeBinding extends UpdateBinding {
1852
2122
  const context = observer.context;
1853
2123
  this.updateTarget(target, this.directive.targetAspect, observer.observe(source, context), source, context);
1854
2124
  }
2125
+ /**
2126
+ * Returns the binding observer used to update the node.
2127
+ * @param target - The target node.
2128
+ * @returns A BindingObserver.
2129
+ */
2130
+ getObserver(target) {
2131
+ var _a;
2132
+ return ((_a = target[this.observerProperty]) !== null && _a !== void 0 ? _a : (target[this.observerProperty] = this.directive.dataBinding.createObserver(this.directive, this)));
2133
+ }
2134
+ /**
2135
+ * Creates a behavior.
2136
+ * @param targets - The targets available for behaviors to be attached to.
2137
+ */
2138
+ createBehavior(targets) {
2139
+ return this;
2140
+ }
2141
+ }
2142
+ /**
2143
+ * A special binding behavior that can bind node content.
2144
+ * @public
2145
+ */
2146
+ class ContentBehavior extends BindingBehavior {
2147
+ /**
2148
+ * Unbinds this behavior from the source.
2149
+ * @param source - The source to unbind from.
2150
+ * @param context - The execution context that the binding is operating within.
2151
+ * @param targets - The targets that behaviors in a view can attach to.
2152
+ */
2153
+ unbind(source, context, targets) {
2154
+ super.unbind(source, context, targets);
2155
+ const target = targets[this.directive.nodeId];
2156
+ const view = target.$fastView;
2157
+ if (view !== void 0 && view.isComposed) {
2158
+ view.unbind();
2159
+ view.needsBindOnly = true;
2160
+ }
2161
+ }
1855
2162
  }
1856
2163
  /**
1857
2164
  * A binding behavior for handling events.
1858
2165
  * @public
1859
2166
  */
1860
- class EventBinding {
2167
+ class EventBehavior {
1861
2168
  /**
1862
2169
  * Creates an instance of EventBinding.
1863
2170
  * @param directive - The directive that has the configuration for this behavior.
@@ -1878,7 +2185,7 @@ class EventBinding {
1878
2185
  const target = targets[directive.nodeId];
1879
2186
  target[this.sourceProperty] = source;
1880
2187
  target[this.contextProperty] = context;
1881
- target.addEventListener(directive.targetAspect, this, directive.options);
2188
+ target.addEventListener(directive.targetAspect, this, directive.dataBinding.options);
1882
2189
  }
1883
2190
  /**
1884
2191
  * Unbinds this behavior from the source.
@@ -1890,7 +2197,7 @@ class EventBinding {
1890
2197
  const directive = this.directive;
1891
2198
  const target = targets[directive.nodeId];
1892
2199
  target[this.sourceProperty] = target[this.contextProperty] = null;
1893
- target.removeEventListener(directive.targetAspect, this, directive.options);
2200
+ target.removeEventListener(directive.targetAspect, this, directive.dataBinding.options);
1894
2201
  }
1895
2202
  /**
1896
2203
  * Creates a behavior.
@@ -1905,25 +2212,13 @@ class EventBinding {
1905
2212
  handleEvent(event) {
1906
2213
  const target = event.currentTarget;
1907
2214
  ExecutionContext.setEvent(event);
1908
- const result = this.directive.binding(target[this.sourceProperty], target[this.contextProperty]);
2215
+ const result = this.directive.dataBinding.evaluate(target[this.sourceProperty], target[this.contextProperty]);
1909
2216
  ExecutionContext.setEvent(null);
1910
2217
  if (result !== true) {
1911
2218
  event.preventDefault();
1912
2219
  }
1913
2220
  }
1914
2221
  }
1915
- /**
1916
- * The default onChange binding configuration.
1917
- * @public
1918
- */
1919
- const onChange = BindingConfig.define(BindingMode.define(ChangeBinding), {});
1920
- /**
1921
- * The default onTime binding configuration.
1922
- * @public
1923
- */
1924
- const oneTime = BindingConfig.define(BindingMode.define(OneTimeBinding), {
1925
- once: true,
1926
- });
1927
2222
  /**
1928
2223
  * A directive that applies bindings.
1929
2224
  * @public
@@ -1931,15 +2226,15 @@ const oneTime = BindingConfig.define(BindingMode.define(OneTimeBinding), {
1931
2226
  class HTMLBindingDirective {
1932
2227
  /**
1933
2228
  * Creates an instance of HTMLBindingDirective.
1934
- * @param binding - The binding to apply.
1935
- * @param mode - The binding mode to use when applying the binding.
1936
- * @param options - The options to configure the binding with.
2229
+ * @param dataBinding - The binding configuration to apply.
1937
2230
  */
1938
- constructor(binding, mode, options) {
1939
- this.binding = binding;
1940
- this.mode = mode;
1941
- this.options = options;
2231
+ constructor(dataBinding) {
2232
+ this.dataBinding = dataBinding;
1942
2233
  this.factory = null;
2234
+ /**
2235
+ * The unique id of the factory.
2236
+ */
2237
+ this.id = nextId();
1943
2238
  /**
1944
2239
  * The type of aspect to target.
1945
2240
  */
@@ -1959,26 +2254,78 @@ class HTMLBindingDirective {
1959
2254
  createBehavior(targets) {
1960
2255
  if (this.factory == null) {
1961
2256
  if (this.targetAspect === "innerHTML") {
1962
- this.binding = createInnerHTMLBinding(this.binding);
2257
+ this.dataBinding.evaluate = createInnerHTMLBinding(this.dataBinding.evaluate);
2258
+ }
2259
+ switch (this.aspectType) {
2260
+ case 1:
2261
+ this.factory = new BindingBehavior(this, DOM.setAttribute);
2262
+ break;
2263
+ case 2:
2264
+ this.factory = new BindingBehavior(this, DOM.setBooleanAttribute);
2265
+ break;
2266
+ case 3:
2267
+ this.factory = new BindingBehavior(this, (t, a, v) => (t[a] = v));
2268
+ break;
2269
+ case 4:
2270
+ this.factory = new ContentBehavior(this, updateContentTarget);
2271
+ break;
2272
+ case 5:
2273
+ this.factory = new BindingBehavior(this, updateTokenListTarget);
2274
+ break;
2275
+ case 6:
2276
+ this.factory = new EventBehavior(this);
2277
+ break;
2278
+ default:
2279
+ throw FAST.error(1205 /* Message.unsupportedBindingBehavior */);
1963
2280
  }
1964
- this.factory = this.mode[this.aspectType](this);
1965
2281
  }
1966
2282
  return this.factory.createBehavior(targets);
1967
2283
  }
1968
2284
  }
1969
2285
  HTMLDirective.define(HTMLBindingDirective, { aspected: true });
1970
2286
  /**
1971
- * Creates a binding directive with the specified configuration.
1972
- * @param binding - The binding expression.
1973
- * @param config - The binding configuration.
1974
- * @returns A binding directive.
2287
+ * Creates an standard binding.
2288
+ * @param binding - The binding to refresh when changed.
2289
+ * @param isVolatile - Indicates whether the binding is volatile or not.
2290
+ * @returns A binding configuration.
1975
2291
  * @public
1976
2292
  */
1977
- function bind(binding, config = onChange) {
1978
- if (!("mode" in config)) {
1979
- config = onChange(config);
1980
- }
1981
- return new HTMLBindingDirective(binding, config.mode, config.options);
2293
+ function bind(binding, isVolatile = Observable.isVolatileBinding(binding)) {
2294
+ return new OnChangeBinding(binding, isVolatile);
2295
+ }
2296
+ /**
2297
+ * Creates a one time binding
2298
+ * @param binding - The binding to refresh when signaled.
2299
+ * @returns A binding configuration.
2300
+ * @public
2301
+ */
2302
+ function oneTime(binding) {
2303
+ return new OneTimeBinding(binding);
2304
+ }
2305
+ /**
2306
+ * Creates an event listener binding.
2307
+ * @param binding - The binding to invoke when the event is raised.
2308
+ * @param options - Event listener options.
2309
+ * @returns A binding configuration.
2310
+ * @public
2311
+ */
2312
+ function listener(binding, options) {
2313
+ const config = new OnChangeBinding(binding, false);
2314
+ config.options = options;
2315
+ return config;
2316
+ }
2317
+ /**
2318
+ * Normalizes the input value into a binding.
2319
+ * @param value - The value to create the default binding for.
2320
+ * @returns A binding configuration for the provided value.
2321
+ * @public
2322
+ */
2323
+ function normalizeBinding(value) {
2324
+ return isFunction(value)
2325
+ ? bind(value)
2326
+ : value instanceof Binding
2327
+ ? value
2328
+ : oneTime(() => value);
1982
2329
  }
1983
2330
 
1984
2331
  function removeNodeSequence(firstNode, lastNode) {
@@ -2034,8 +2381,10 @@ class HTMLView {
2034
2381
  node.parentNode.insertBefore(this.fragment, node);
2035
2382
  }
2036
2383
  else {
2037
- const parentNode = node.parentNode;
2038
2384
  const end = this.lastChild;
2385
+ if (node.previousSibling === end)
2386
+ return;
2387
+ const parentNode = node.parentNode;
2039
2388
  let current = this.firstChild;
2040
2389
  let next;
2041
2390
  while (current !== end) {
@@ -2145,6 +2494,22 @@ const next = {
2145
2494
  index: 0,
2146
2495
  node: null,
2147
2496
  };
2497
+ function tryWarn(name) {
2498
+ if (!name.startsWith("fast-")) {
2499
+ FAST.warn(1204 /* Message.hostBindingWithoutHost */, { name });
2500
+ }
2501
+ }
2502
+ const warningHost = new Proxy(document.createElement("div"), {
2503
+ get(target, property) {
2504
+ tryWarn(property);
2505
+ const value = Reflect.get(target, property);
2506
+ return isFunction(value) ? value.bind(target) : value;
2507
+ },
2508
+ set(target, property, value) {
2509
+ tryWarn(property);
2510
+ return Reflect.set(target, property, value);
2511
+ },
2512
+ });
2148
2513
  class CompilationContext {
2149
2514
  constructor(fragment, directives) {
2150
2515
  this.fragment = fragment;
@@ -2195,7 +2560,7 @@ class CompilationContext {
2195
2560
  const fragment = this.fragment.cloneNode(true);
2196
2561
  const targets = Object.create(this.proto);
2197
2562
  targets.r = fragment;
2198
- targets.h = hostBindingTarget !== null && hostBindingTarget !== void 0 ? hostBindingTarget : fragment;
2563
+ targets.h = hostBindingTarget !== null && hostBindingTarget !== void 0 ? hostBindingTarget : warningHost;
2199
2564
  for (const id of this.nodeIds) {
2200
2565
  targets[id]; // trigger locator
2201
2566
  }
@@ -2212,7 +2577,7 @@ function compileAttributes(context, parentId, node, nodeId, nodeIndex, includeBa
2212
2577
  let result = null;
2213
2578
  if (parseResult === null) {
2214
2579
  if (includeBasicValues) {
2215
- result = bind(() => attrValue, oneTime);
2580
+ result = new HTMLBindingDirective(oneTime(() => attrValue));
2216
2581
  Aspect.assign(result, attr.name);
2217
2582
  }
2218
2583
  }
@@ -2249,6 +2614,7 @@ function compileContent(context, node, parentId, nodeId, nodeIndex) {
2249
2614
  }
2250
2615
  else {
2251
2616
  currentNode.textContent = " ";
2617
+ Aspect.assign(currentPart);
2252
2618
  context.addFactory(currentPart, parentId, nodeId, nodeIndex);
2253
2619
  }
2254
2620
  lastNode = currentNode;
@@ -2381,22 +2747,28 @@ const Compiler = {
2381
2747
  return parts[0];
2382
2748
  }
2383
2749
  let sourceAspect;
2750
+ let binding;
2751
+ let isVolatile = false;
2384
2752
  const partCount = parts.length;
2385
2753
  const finalParts = parts.map((x) => {
2386
2754
  if (isString(x)) {
2387
2755
  return () => x;
2388
2756
  }
2389
2757
  sourceAspect = x.sourceAspect || sourceAspect;
2390
- return x.binding;
2758
+ binding = x.dataBinding || binding;
2759
+ isVolatile = isVolatile || x.dataBinding.isVolatile;
2760
+ return x.dataBinding.evaluate;
2391
2761
  });
2392
- const binding = (scope, context) => {
2762
+ const expression = (scope, context) => {
2393
2763
  let output = "";
2394
2764
  for (let i = 0; i < partCount; ++i) {
2395
2765
  output += finalParts[i](scope, context);
2396
2766
  }
2397
2767
  return output;
2398
2768
  };
2399
- const directive = bind(binding);
2769
+ binding.evaluate = expression;
2770
+ binding.isVolatile = isVolatile;
2771
+ const directive = new HTMLBindingDirective(binding);
2400
2772
  Aspect.assign(directive, sourceAspect);
2401
2773
  return directive;
2402
2774
  },
@@ -2476,12 +2848,12 @@ function html(strings, ...values) {
2476
2848
  let definition;
2477
2849
  html += currentString;
2478
2850
  if (isFunction(currentValue)) {
2479
- html += createAspectedHTML(bind(currentValue), currentString, add);
2851
+ html += createAspectedHTML(new HTMLBindingDirective(bind(currentValue)), currentString, add);
2480
2852
  }
2481
2853
  else if (isString(currentValue)) {
2482
2854
  const match = lastAttributeNameRegex.exec(currentString);
2483
2855
  if (match !== null) {
2484
- const directive = bind(() => currentValue, oneTime);
2856
+ const directive = new HTMLBindingDirective(oneTime(() => currentValue));
2485
2857
  Aspect.assign(directive, match[2]);
2486
2858
  html += directive.createHTML(add);
2487
2859
  }
@@ -2489,8 +2861,11 @@ function html(strings, ...values) {
2489
2861
  html += currentValue;
2490
2862
  }
2491
2863
  }
2864
+ else if (currentValue instanceof Binding) {
2865
+ html += createAspectedHTML(new HTMLBindingDirective(currentValue), currentString, add);
2866
+ }
2492
2867
  else if ((definition = HTMLDirective.getForInstance(currentValue)) === void 0) {
2493
- html += createAspectedHTML(bind(() => currentValue, oneTime), currentString, add);
2868
+ html += createAspectedHTML(new HTMLBindingDirective(oneTime(() => currentValue)), currentString, add);
2494
2869
  }
2495
2870
  else {
2496
2871
  if (definition.aspected) {
@@ -2535,16 +2910,17 @@ const ref = (propertyName) => new RefDirective(propertyName);
2535
2910
 
2536
2911
  /**
2537
2912
  * A directive that enables basic conditional rendering in a template.
2538
- * @param binding - The condition to test for rendering.
2913
+ * @param condition - The condition to test for rendering.
2539
2914
  * @param templateOrTemplateBinding - The template or a binding that gets
2540
2915
  * the template to render when the condition is true.
2541
2916
  * @public
2542
2917
  */
2543
- function when(binding, templateOrTemplateBinding) {
2544
- const getTemplate = isFunction(templateOrTemplateBinding)
2918
+ function when(condition, templateOrTemplateBinding) {
2919
+ const dataBinding = isFunction(condition) ? condition : () => condition;
2920
+ const templateBinding = isFunction(templateOrTemplateBinding)
2545
2921
  ? templateOrTemplateBinding
2546
2922
  : () => templateOrTemplateBinding;
2547
- return (source, context) => binding(source, context) ? getTemplate(source, context) : null;
2923
+ return (source, context) => dataBinding(source, context) ? templateBinding(source, context) : null;
2548
2924
  }
2549
2925
 
2550
2926
  const defaultRepeatOptions = Object.freeze({
@@ -2565,17 +2941,15 @@ class RepeatBehavior {
2565
2941
  /**
2566
2942
  * Creates an instance of RepeatBehavior.
2567
2943
  * @param location - The location in the DOM to render the repeat.
2568
- * @param itemsBinding - The array to render.
2944
+ * @param dataBinding - The array to render.
2569
2945
  * @param isItemsBindingVolatile - Indicates whether the items binding has volatile dependencies.
2570
2946
  * @param templateBinding - The template to render for each item.
2571
2947
  * @param isTemplateBindingVolatile - Indicates whether the template binding has volatile dependencies.
2572
2948
  * @param options - Options used to turn on special repeat features.
2573
2949
  */
2574
- constructor(location, itemsBinding, isItemsBindingVolatile, templateBinding, isTemplateBindingVolatile, options) {
2950
+ constructor(directive, location) {
2951
+ this.directive = directive;
2575
2952
  this.location = location;
2576
- this.itemsBinding = itemsBinding;
2577
- this.templateBinding = templateBinding;
2578
- this.options = options;
2579
2953
  this.source = null;
2580
2954
  this.views = [];
2581
2955
  this.items = null;
@@ -2583,9 +2957,9 @@ class RepeatBehavior {
2583
2957
  this.context = void 0;
2584
2958
  this.childContext = void 0;
2585
2959
  this.bindView = bindWithoutPositioning;
2586
- this.itemsBindingObserver = Observable.binding(itemsBinding, this, isItemsBindingVolatile);
2587
- this.templateBindingObserver = Observable.binding(templateBinding, this, isTemplateBindingVolatile);
2588
- if (options.positioning) {
2960
+ this.itemsBindingObserver = directive.dataBinding.createObserver(directive, this);
2961
+ this.templateBindingObserver = directive.templateBinding.createObserver(directive, this);
2962
+ if (directive.options.positioning) {
2589
2963
  this.bindView = bindWithPositioning;
2590
2964
  }
2591
2965
  }
@@ -2623,15 +2997,18 @@ class RepeatBehavior {
2623
2997
  * @param args - The details about what was changed.
2624
2998
  */
2625
2999
  handleChange(source, args) {
2626
- if (source === this.itemsBinding) {
3000
+ if (args === this.itemsBindingObserver) {
2627
3001
  this.items = this.itemsBindingObserver.observe(this.source, this.context);
2628
3002
  this.observeItems();
2629
3003
  this.refreshAllViews();
2630
3004
  }
2631
- else if (source === this.templateBinding) {
3005
+ else if (args === this.templateBindingObserver) {
2632
3006
  this.template = this.templateBindingObserver.observe(this.source, this.context);
2633
3007
  this.refreshAllViews(true);
2634
3008
  }
3009
+ else if (!args[0]) {
3010
+ return;
3011
+ }
2635
3012
  else if (args[0].reset) {
2636
3013
  this.refreshAllViews();
2637
3014
  }
@@ -2657,36 +3034,51 @@ class RepeatBehavior {
2657
3034
  updateViews(splices) {
2658
3035
  const views = this.views;
2659
3036
  const childContext = this.childContext;
2660
- const totalRemoved = [];
2661
3037
  const bindView = this.bindView;
2662
- let removeDelta = 0;
2663
- for (let i = 0, ii = splices.length; i < ii; ++i) {
2664
- const splice = splices[i];
2665
- const removed = splice.removed;
2666
- totalRemoved.push(...views.splice(splice.index + removeDelta, removed.length));
2667
- removeDelta -= splice.addedCount;
2668
- }
2669
3038
  const items = this.items;
2670
3039
  const template = this.template;
3040
+ const recycle = this.directive.options.recycle;
3041
+ const leftoverViews = [];
3042
+ let leftoverIndex = 0;
3043
+ let availableViews = 0;
2671
3044
  for (let i = 0, ii = splices.length; i < ii; ++i) {
2672
3045
  const splice = splices[i];
3046
+ const removed = splice.removed;
3047
+ let removeIndex = 0;
2673
3048
  let addIndex = splice.index;
2674
3049
  const end = addIndex + splice.addedCount;
3050
+ const removedViews = views.splice(splice.index, removed.length);
3051
+ availableViews = leftoverViews.length + removedViews.length;
2675
3052
  for (; addIndex < end; ++addIndex) {
2676
3053
  const neighbor = views[addIndex];
2677
3054
  const location = neighbor ? neighbor.firstChild : this.location;
2678
- const view = this.options.recycle && totalRemoved.length > 0
2679
- ? totalRemoved.shift()
2680
- : template.create();
3055
+ let view;
3056
+ if (recycle && availableViews > 0) {
3057
+ if (removeIndex <= availableViews && removedViews.length > 0) {
3058
+ view = removedViews[removeIndex];
3059
+ removeIndex++;
3060
+ }
3061
+ else {
3062
+ view = leftoverViews[leftoverIndex];
3063
+ leftoverIndex++;
3064
+ }
3065
+ availableViews--;
3066
+ }
3067
+ else {
3068
+ view = template.create();
3069
+ }
2681
3070
  views.splice(addIndex, 0, view);
2682
3071
  bindView(view, items, addIndex, childContext);
2683
3072
  view.insertBefore(location);
2684
3073
  }
3074
+ if (removedViews[removeIndex]) {
3075
+ leftoverViews.push(...removedViews.slice(removeIndex));
3076
+ }
2685
3077
  }
2686
- for (let i = 0, ii = totalRemoved.length; i < ii; ++i) {
2687
- totalRemoved[i].dispose();
3078
+ for (let i = leftoverIndex, ii = leftoverViews.length; i < ii; ++i) {
3079
+ leftoverViews[i].dispose();
2688
3080
  }
2689
- if (this.options.positioning) {
3081
+ if (this.directive.options.positioning) {
2690
3082
  for (let i = 0, ii = views.length; i < ii; ++i) {
2691
3083
  views[i].context.updatePosition(i, ii);
2692
3084
  }
@@ -2701,7 +3093,7 @@ class RepeatBehavior {
2701
3093
  let itemsLength = items.length;
2702
3094
  let views = this.views;
2703
3095
  let viewsLength = views.length;
2704
- if (itemsLength === 0 || templateChanged || !this.options.recycle) {
3096
+ if (itemsLength === 0 || templateChanged || !this.directive.options.recycle) {
2705
3097
  // all views need to be removed
2706
3098
  HTMLView.disposeContiguousBatch(views);
2707
3099
  viewsLength = 0;
@@ -2751,17 +3143,19 @@ class RepeatBehavior {
2751
3143
  class RepeatDirective {
2752
3144
  /**
2753
3145
  * Creates an instance of RepeatDirective.
2754
- * @param itemsBinding - The binding that provides the array to render.
3146
+ * @param dataBinding - The binding that provides the array to render.
2755
3147
  * @param templateBinding - The template binding used to obtain a template to render for each item in the array.
2756
3148
  * @param options - Options used to turn on special repeat features.
2757
3149
  */
2758
- constructor(itemsBinding, templateBinding, options) {
2759
- this.itemsBinding = itemsBinding;
3150
+ constructor(dataBinding, templateBinding, options) {
3151
+ this.dataBinding = dataBinding;
2760
3152
  this.templateBinding = templateBinding;
2761
3153
  this.options = options;
3154
+ /**
3155
+ * The unique id of the factory.
3156
+ */
3157
+ this.id = nextId();
2762
3158
  ArrayObserver.enable();
2763
- this.isItemsBindingVolatile = Observable.isVolatileBinding(itemsBinding);
2764
- this.isTemplateBindingVolatile = Observable.isVolatileBinding(templateBinding);
2765
3159
  }
2766
3160
  /**
2767
3161
  * Creates a placeholder string based on the directive's index within the template.
@@ -2775,23 +3169,22 @@ class RepeatDirective {
2775
3169
  * @param target - The node instance to create the behavior for.
2776
3170
  */
2777
3171
  createBehavior(targets) {
2778
- return new RepeatBehavior(targets[this.nodeId], this.itemsBinding, this.isItemsBindingVolatile, this.templateBinding, this.isTemplateBindingVolatile, this.options);
3172
+ return new RepeatBehavior(this, targets[this.nodeId]);
2779
3173
  }
2780
3174
  }
2781
3175
  HTMLDirective.define(RepeatDirective);
2782
3176
  /**
2783
3177
  * A directive that enables list rendering.
2784
- * @param itemsBinding - The array to render.
2785
- * @param templateOrTemplateBinding - The template or a template binding used obtain a template
3178
+ * @param items - The array to render.
3179
+ * @param template - The template or a template binding used obtain a template
2786
3180
  * to render for each item in the array.
2787
3181
  * @param options - Options used to turn on special repeat features.
2788
3182
  * @public
2789
3183
  */
2790
- function repeat(itemsBinding, templateOrTemplateBinding, options = defaultRepeatOptions) {
2791
- const templateBinding = isFunction(templateOrTemplateBinding)
2792
- ? templateOrTemplateBinding
2793
- : () => templateOrTemplateBinding;
2794
- return new RepeatDirective(itemsBinding, templateBinding, options);
3184
+ function repeat(items, template, options = defaultRepeatOptions) {
3185
+ const dataBinding = normalizeBinding(items);
3186
+ const templateBinding = normalizeBinding(template);
3187
+ return new RepeatDirective(dataBinding, templateBinding, Object.assign(Object.assign({}, defaultRepeatOptions), options));
2795
3188
  }
2796
3189
 
2797
3190
  const selectElements = (value) => value.nodeType === 1;
@@ -3176,19 +3569,15 @@ const fastElementRegistry = FAST.getById(4 /* KernelServiceId.elementRegistry */
3176
3569
  * @public
3177
3570
  */
3178
3571
  class FASTElementDefinition {
3179
- /**
3180
- * Creates an instance of FASTElementDefinition.
3181
- * @param type - The type this definition is being created for.
3182
- * @param nameOrConfig - The name of the element to define or a config object
3183
- * that describes the element to define.
3184
- */
3185
3572
  constructor(type, nameOrConfig = type.definition) {
3573
+ this.platformDefined = false;
3186
3574
  if (isString(nameOrConfig)) {
3187
3575
  nameOrConfig = { name: nameOrConfig };
3188
3576
  }
3189
3577
  this.type = type;
3190
3578
  this.name = nameOrConfig.name;
3191
3579
  this.template = nameOrConfig.template;
3580
+ const proto = type.prototype;
3192
3581
  const attributes = AttributeDefinition.collect(type, nameOrConfig.attributes);
3193
3582
  const observedAttributes = new Array(attributes.length);
3194
3583
  const propertyLookup = {};
@@ -3198,9 +3587,13 @@ class FASTElementDefinition {
3198
3587
  observedAttributes[i] = current.attribute;
3199
3588
  propertyLookup[current.name] = current;
3200
3589
  attributeLookup[current.attribute] = current;
3590
+ Observable.defineProperty(proto, current);
3201
3591
  }
3592
+ Reflect.defineProperty(type, "observedAttributes", {
3593
+ value: observedAttributes,
3594
+ enumerable: true,
3595
+ });
3202
3596
  this.attributes = attributes;
3203
- this.observedAttributes = observedAttributes;
3204
3597
  this.propertyLookup = propertyLookup;
3205
3598
  this.attributeLookup = attributeLookup;
3206
3599
  this.shadowOptions =
@@ -3213,20 +3606,14 @@ class FASTElementDefinition {
3213
3606
  nameOrConfig.elementOptions === void 0
3214
3607
  ? defaultElementOptions
3215
3608
  : Object.assign(Object.assign({}, defaultElementOptions), nameOrConfig.elementOptions);
3216
- this.styles =
3217
- nameOrConfig.styles === void 0
3218
- ? void 0
3219
- : Array.isArray(nameOrConfig.styles)
3220
- ? new ElementStyles(nameOrConfig.styles)
3221
- : nameOrConfig.styles instanceof ElementStyles
3222
- ? nameOrConfig.styles
3223
- : new ElementStyles([nameOrConfig.styles]);
3609
+ this.styles = ElementStyles.normalize(nameOrConfig.styles);
3610
+ fastElementRegistry.register(this);
3224
3611
  }
3225
3612
  /**
3226
3613
  * Indicates if this element has been defined in at least one registry.
3227
3614
  */
3228
3615
  get isDefined() {
3229
- return !!fastElementRegistry.getByType(this.type);
3616
+ return this.platformDefined;
3230
3617
  }
3231
3618
  /**
3232
3619
  * Defines a custom element based on this definition.
@@ -3236,22 +3623,26 @@ class FASTElementDefinition {
3236
3623
  */
3237
3624
  define(registry = customElements) {
3238
3625
  const type = this.type;
3239
- if (fastElementRegistry.register(this)) {
3240
- const attributes = this.attributes;
3241
- const proto = type.prototype;
3242
- for (let i = 0, ii = attributes.length; i < ii; ++i) {
3243
- Observable.defineProperty(proto, attributes[i]);
3244
- }
3245
- Reflect.defineProperty(type, "observedAttributes", {
3246
- value: this.observedAttributes,
3247
- enumerable: true,
3248
- });
3249
- }
3250
3626
  if (!registry.get(this.name)) {
3627
+ this.platformDefined = true;
3251
3628
  registry.define(this.name, type, this.elementOptions);
3252
3629
  }
3253
3630
  return this;
3254
3631
  }
3632
+ /**
3633
+ * Creates an instance of FASTElementDefinition.
3634
+ * @param type - The type this definition is being created for.
3635
+ * @param nameOrDef - The name of the element to define or a config object
3636
+ * that describes the element to define.
3637
+ */
3638
+ static compose(type, nameOrDef) {
3639
+ const found = fastElementRegistry.getByType(type);
3640
+ if (found) {
3641
+ return new FASTElementDefinition(class extends type {
3642
+ }, nameOrDef);
3643
+ }
3644
+ return new FASTElementDefinition(type, nameOrDef);
3645
+ }
3255
3646
  }
3256
3647
  /**
3257
3648
  * Gets the element definition associated with the specified type.
@@ -3669,6 +4060,21 @@ function createFASTElement(BaseType) {
3669
4060
  }
3670
4061
  };
3671
4062
  }
4063
+ function compose(type, nameOrDef) {
4064
+ if (isFunction(type)) {
4065
+ return FASTElementDefinition.compose(type, nameOrDef);
4066
+ }
4067
+ return FASTElementDefinition.compose(this, type);
4068
+ }
4069
+ function define(type, nameOrDef) {
4070
+ if (isFunction(type)) {
4071
+ return FASTElementDefinition.compose(type, nameOrDef).define().type;
4072
+ }
4073
+ return FASTElementDefinition.compose(this, type).define().type;
4074
+ }
4075
+ function from(BaseType) {
4076
+ return createFASTElement(BaseType);
4077
+ }
3672
4078
  /**
3673
4079
  * A minimal base class for FASTElements that also provides
3674
4080
  * static helpers for working with FASTElements.
@@ -3680,26 +4086,19 @@ const FASTElement = Object.assign(createFASTElement(HTMLElement), {
3680
4086
  * provided base type.
3681
4087
  * @param BaseType - The base element type to inherit from.
3682
4088
  */
3683
- from(BaseType) {
3684
- return createFASTElement(BaseType);
3685
- },
4089
+ from,
3686
4090
  /**
3687
4091
  * Defines a platform custom element based on the provided type and definition.
3688
4092
  * @param type - The custom element type to define.
3689
4093
  * @param nameOrDef - The name of the element to define or a definition object
3690
4094
  * that describes the element to define.
3691
4095
  */
3692
- define(type, nameOrDef) {
3693
- return this.metadata(type, nameOrDef).define().type;
3694
- },
4096
+ define,
3695
4097
  /**
3696
4098
  * Defines metadata for a FASTElement which can be used to later define the element.
3697
- * IMPORTANT: This API will be renamed to "compose" in a future beta.
3698
4099
  * @public
3699
4100
  */
3700
- metadata(type, nameOrDef) {
3701
- return new FASTElementDefinition(type, nameOrDef);
3702
- },
4101
+ compose,
3703
4102
  });
3704
4103
  /**
3705
4104
  * Decorator: Defines a platform custom element based on `FASTElement`.
@@ -3710,8 +4109,8 @@ const FASTElement = Object.assign(createFASTElement(HTMLElement), {
3710
4109
  function customElement(nameOrDef) {
3711
4110
  /* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
3712
4111
  return function (type) {
3713
- FASTElement.define(type, nameOrDef);
4112
+ define(type, nameOrDef);
3714
4113
  };
3715
4114
  }
3716
4115
 
3717
- export { AdoptedStyleSheetsStrategy, ArrayObserver, Aspect, AttributeDefinition, BindingConfig, BindingMode, CSSDirective, ChangeBinding, ChildrenDirective, Compiler, Controller, DOM, ElementStyles, EventBinding, ExecutionContext, FAST, FASTElement, FASTElementDefinition, HTMLBindingDirective, HTMLDirective, HTMLView, Markup, NodeObservationDirective, Observable, OneTimeBinding, Parser, PropertyChangeNotifier, RefDirective, RepeatBehavior, RepeatDirective, SlottedDirective, Splice, SpliceStrategy, SpliceStrategySupport, StatelessAttachedAttributeDirective, SubscriberSet, UpdateBinding, Updates, ViewTemplate, attr, bind, booleanConverter, children, createTypeRegistry, css, cssDirective, cssPartial, customElement, elements, emptyArray, html, htmlDirective, lengthOf, nullableNumberConverter, observable, onChange, oneTime, ref, repeat, slotted, volatile, when };
4116
+ export { AdoptedStyleSheetsStrategy, ArrayObserver, Aspect, AttributeDefinition, Binding, BindingBehavior, CSSDirective, ChildrenDirective, Compiler, ContentBehavior, Controller, DOM, ElementStyles, EventBehavior, ExecutionContext, FAST, FASTElement, FASTElementDefinition, HTMLBindingDirective, HTMLDirective, HTMLView, Markup, NodeObservationDirective, Observable, Parser, PropertyChangeNotifier, RefDirective, RepeatBehavior, RepeatDirective, SlottedDirective, Splice, SpliceStrategy, SpliceStrategySupport, StatelessAttachedAttributeDirective, SubscriberSet, Updates, ViewTemplate, attr, bind, booleanConverter, children, createTypeRegistry, css, cssDirective, cssPartial, customElement, elements, emptyArray, html, htmlDirective, lengthOf, listener, normalizeBinding, nullableNumberConverter, observable, oneTime, ref, repeat, slotted, volatile, when };