@microsoft/fast-element 2.0.0-beta.1 → 2.0.0-beta.10

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 (96) hide show
  1. package/CHANGELOG.json +333 -0
  2. package/CHANGELOG.md +106 -1
  3. package/dist/dts/components/attributes.d.ts +10 -0
  4. package/dist/dts/components/{controller.d.ts → element-controller.d.ts} +24 -25
  5. package/dist/dts/components/fast-definitions.d.ts +43 -9
  6. package/dist/dts/components/fast-element.d.ts +15 -21
  7. package/dist/dts/context.d.ts +157 -0
  8. package/dist/dts/di/di.d.ts +899 -0
  9. package/dist/dts/index.d.ts +2 -2
  10. package/dist/dts/interfaces.d.ts +44 -12
  11. package/dist/dts/metadata.d.ts +25 -0
  12. package/dist/dts/observation/arrays.d.ts +1 -1
  13. package/dist/dts/observation/observable.d.ts +101 -75
  14. package/dist/dts/pending-task.d.ts +20 -0
  15. package/dist/dts/platform.d.ts +6 -0
  16. package/dist/dts/state/exports.d.ts +3 -0
  17. package/dist/dts/state/reactive.d.ts +8 -0
  18. package/dist/dts/state/state.d.ts +141 -0
  19. package/dist/dts/state/visitor.d.ts +6 -0
  20. package/dist/dts/state/watch.d.ts +10 -0
  21. package/dist/dts/styles/css-directive.d.ts +2 -2
  22. package/dist/dts/styles/element-styles.d.ts +9 -3
  23. package/dist/dts/styles/host.d.ts +68 -0
  24. package/dist/dts/templating/binding-signal.d.ts +21 -0
  25. package/dist/dts/templating/binding-two-way.d.ts +39 -0
  26. package/dist/dts/templating/binding.d.ts +69 -294
  27. package/dist/dts/templating/children.d.ts +1 -1
  28. package/dist/dts/templating/compiler.d.ts +1 -2
  29. package/dist/dts/templating/html-directive.d.ts +93 -35
  30. package/dist/dts/templating/node-observation.d.ts +4 -5
  31. package/dist/dts/templating/ref.d.ts +5 -13
  32. package/dist/dts/templating/render.d.ts +272 -0
  33. package/dist/dts/templating/repeat.d.ts +20 -75
  34. package/dist/dts/templating/slotted.d.ts +1 -1
  35. package/dist/dts/templating/template.d.ts +12 -61
  36. package/dist/dts/templating/view.d.ts +77 -12
  37. package/dist/dts/templating/when.d.ts +3 -3
  38. package/dist/dts/testing/exports.d.ts +3 -0
  39. package/dist/dts/testing/fakes.d.ts +4 -0
  40. package/dist/dts/testing/fixture.d.ts +84 -0
  41. package/dist/dts/testing/timeout.d.ts +7 -0
  42. package/dist/{tsdoc-metadata.json → dts/tsdoc-metadata.json} +0 -0
  43. package/dist/dts/utilities.d.ts +0 -18
  44. package/dist/esm/components/attributes.js +13 -4
  45. package/dist/esm/components/{controller.js → element-controller.js} +95 -105
  46. package/dist/esm/components/fast-definitions.js +38 -28
  47. package/dist/esm/components/fast-element.js +31 -12
  48. package/dist/esm/context.js +163 -0
  49. package/dist/esm/debug.js +36 -4
  50. package/dist/esm/di/di.js +1435 -0
  51. package/dist/esm/index.js +2 -1
  52. package/dist/esm/interfaces.js +4 -0
  53. package/dist/esm/metadata.js +60 -0
  54. package/dist/esm/observation/arrays.js +304 -3
  55. package/dist/esm/observation/observable.js +81 -87
  56. package/dist/esm/pending-task.js +16 -0
  57. package/dist/esm/platform.js +25 -1
  58. package/dist/esm/state/exports.js +3 -0
  59. package/dist/esm/state/reactive.js +34 -0
  60. package/dist/esm/state/state.js +148 -0
  61. package/dist/esm/state/visitor.js +28 -0
  62. package/dist/esm/state/watch.js +36 -0
  63. package/dist/esm/styles/css.js +4 -4
  64. package/dist/esm/styles/element-styles.js +14 -0
  65. package/dist/esm/{observation/behavior.js → styles/host.js} +0 -0
  66. package/dist/esm/templating/binding-signal.js +83 -0
  67. package/dist/esm/templating/binding-two-way.js +103 -0
  68. package/dist/esm/templating/binding.js +134 -414
  69. package/dist/esm/templating/compiler.js +30 -7
  70. package/dist/esm/templating/html-directive.js +100 -28
  71. package/dist/esm/templating/node-observation.js +9 -8
  72. package/dist/esm/templating/ref.js +4 -12
  73. package/dist/esm/templating/render.js +391 -0
  74. package/dist/esm/templating/repeat.js +96 -72
  75. package/dist/esm/templating/template.js +11 -29
  76. package/dist/esm/templating/view.js +107 -29
  77. package/dist/esm/templating/when.js +5 -4
  78. package/dist/esm/testing/exports.js +3 -0
  79. package/dist/esm/testing/fakes.js +76 -0
  80. package/dist/esm/testing/fixture.js +86 -0
  81. package/dist/esm/testing/timeout.js +24 -0
  82. package/dist/esm/utilities.js +0 -95
  83. package/dist/fast-element.api.json +9034 -10524
  84. package/dist/fast-element.d.ts +707 -811
  85. package/dist/fast-element.debug.js +1133 -850
  86. package/dist/fast-element.debug.min.js +1 -1
  87. package/dist/fast-element.js +1097 -846
  88. package/dist/fast-element.min.js +1 -1
  89. package/dist/fast-element.untrimmed.d.ts +724 -818
  90. package/docs/api-report.md +264 -305
  91. package/package.json +39 -10
  92. package/dist/dts/hooks.d.ts +0 -20
  93. package/dist/dts/observation/behavior.d.ts +0 -19
  94. package/dist/dts/observation/splice-strategies.d.ts +0 -13
  95. package/dist/esm/hooks.js +0 -32
  96. package/dist/esm/observation/splice-strategies.js +0 -400
@@ -112,7 +112,7 @@ if (FAST.error === void 0) {
112
112
  Object.assign(FAST, {
113
113
  warn() { },
114
114
  error(code) {
115
- return new Error(`Code ${code}`);
115
+ return new Error(`Error ${code}`);
116
116
  },
117
117
  addMessages() { },
118
118
  });
@@ -143,10 +143,34 @@ function createTypeRegistry() {
143
143
  return typeToDefinition.get(key);
144
144
  },
145
145
  getForInstance(object) {
146
+ if (object === null || object === void 0) {
147
+ return void 0;
148
+ }
146
149
  return typeToDefinition.get(object.constructor);
147
150
  },
148
151
  });
149
152
  }
153
+ /**
154
+ * Creates a function capable of locating metadata associated with a type.
155
+ * @returns A metadata locator function.
156
+ * @internal
157
+ */
158
+ function createMetadataLocator() {
159
+ const metadataLookup = new WeakMap();
160
+ return function (target) {
161
+ let metadata = metadataLookup.get(target);
162
+ if (metadata === void 0) {
163
+ let currentTarget = Reflect.getPrototypeOf(target);
164
+ while (metadata === void 0 && currentTarget !== null) {
165
+ metadata = metadataLookup.get(currentTarget);
166
+ currentTarget = Reflect.getPrototypeOf(currentTarget);
167
+ }
168
+ metadata = metadata === void 0 ? [] : metadata.slice(0);
169
+ metadataLookup.set(target, metadata);
170
+ }
171
+ return metadata;
172
+ };
173
+ }
150
174
 
151
175
  /**
152
176
  * @internal
@@ -388,6 +412,21 @@ class PropertyChangeNotifier {
388
412
  }
389
413
  }
390
414
 
415
+ /**
416
+ * Describes how the source's lifetime relates to its controller's lifetime.
417
+ * @public
418
+ */
419
+ const SourceLifetime = Object.freeze({
420
+ /**
421
+ * The source to controller lifetime relationship is unknown.
422
+ */
423
+ unknown: void 0,
424
+ /**
425
+ * The source and controller lifetimes are coupled to one another.
426
+ * They can/will be GC'd together.
427
+ */
428
+ coupled: 1,
429
+ });
391
430
  /**
392
431
  * Common Observable APIs.
393
432
  * @public
@@ -396,7 +435,6 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
396
435
  const queueUpdate = Updates.enqueue;
397
436
  const volatileRegex = /(:|&&|\|\||if)/;
398
437
  const notifierLookup = new WeakMap();
399
- const accessorLookup = new WeakMap();
400
438
  let watcher = void 0;
401
439
  let createArrayObserver = (array) => {
402
440
  throw FAST.error(1101 /* Message.needsArrayObservation */);
@@ -411,19 +449,7 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
411
449
  }
412
450
  return found;
413
451
  }
414
- function getAccessors(target) {
415
- let accessors = accessorLookup.get(target);
416
- if (accessors === void 0) {
417
- let currentTarget = Reflect.getPrototypeOf(target);
418
- while (accessors === void 0 && currentTarget !== null) {
419
- accessors = accessorLookup.get(currentTarget);
420
- currentTarget = Reflect.getPrototypeOf(currentTarget);
421
- }
422
- accessors = accessors === void 0 ? [] : accessors.slice(0);
423
- accessorLookup.set(target, accessors);
424
- }
425
- return accessors;
426
- }
452
+ const getAccessors = createMetadataLocator();
427
453
  class DefaultObservableAccessor {
428
454
  constructor(name) {
429
455
  this.name = name;
@@ -449,10 +475,10 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
449
475
  }
450
476
  }
451
477
  }
452
- class BindingObserverImplementation extends SubscriberSet {
453
- constructor(binding, initialSubscriber, isVolatileBinding = false) {
454
- super(binding, initialSubscriber);
455
- this.binding = binding;
478
+ class ExpressionNotifierImplementation extends SubscriberSet {
479
+ constructor(expression, initialSubscriber, isVolatileBinding = false) {
480
+ super(expression, initialSubscriber);
481
+ this.expression = expression;
456
482
  this.isVolatileBinding = isVolatileBinding;
457
483
  this.needsRefresh = true;
458
484
  this.needsQueue = true;
@@ -467,6 +493,22 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
467
493
  setMode(isAsync) {
468
494
  this.isAsync = this.needsQueue = isAsync;
469
495
  }
496
+ bind(controller) {
497
+ this.controller = controller;
498
+ const value = this.observe(controller.source, controller.context);
499
+ if (!controller.isBound && this.requiresUnbind(controller)) {
500
+ controller.onUnbind(this);
501
+ }
502
+ return value;
503
+ }
504
+ requiresUnbind(controller) {
505
+ return (controller.sourceLifetime !== SourceLifetime.coupled ||
506
+ this.first !== this.last ||
507
+ this.first.propertySource !== controller.source);
508
+ }
509
+ unbind(controller) {
510
+ this.dispose();
511
+ }
470
512
  observe(source, context) {
471
513
  if (this.needsRefresh && this.last !== null) {
472
514
  this.dispose();
@@ -474,10 +516,19 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
474
516
  const previousWatcher = watcher;
475
517
  watcher = this.needsRefresh ? this : void 0;
476
518
  this.needsRefresh = this.isVolatileBinding;
477
- const result = this.binding(source, context !== null && context !== void 0 ? context : ExecutionContext.default);
478
- watcher = previousWatcher;
519
+ let result;
520
+ try {
521
+ result = this.expression(source, context);
522
+ }
523
+ finally {
524
+ watcher = previousWatcher;
525
+ }
479
526
  return result;
480
527
  }
528
+ // backwards compat with v1 kernel
529
+ disconnect() {
530
+ this.dispose();
531
+ }
481
532
  dispose() {
482
533
  if (this.last !== null) {
483
534
  let current = this.first;
@@ -604,22 +655,22 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
604
655
  */
605
656
  getAccessors,
606
657
  /**
607
- * Creates a {@link BindingObserver} that can watch the
608
- * provided {@link Binding} for changes.
609
- * @param binding - The binding to observe.
658
+ * Creates a {@link ExpressionNotifier} that can watch the
659
+ * provided {@link Expression} for changes.
660
+ * @param expression - The binding to observe.
610
661
  * @param initialSubscriber - An initial subscriber to changes in the binding value.
611
662
  * @param isVolatileBinding - Indicates whether the binding's dependency list must be re-evaluated on every value evaluation.
612
663
  */
613
- binding(binding, initialSubscriber, isVolatileBinding = this.isVolatileBinding(binding)) {
614
- return new BindingObserverImplementation(binding, initialSubscriber, isVolatileBinding);
664
+ binding(expression, initialSubscriber, isVolatileBinding = this.isVolatileBinding(expression)) {
665
+ return new ExpressionNotifierImplementation(expression, initialSubscriber, isVolatileBinding);
615
666
  },
616
667
  /**
617
668
  * Determines whether a binding expression is volatile and needs to have its dependency list re-evaluated
618
669
  * on every evaluation of the value.
619
- * @param binding - The binding to inspect.
670
+ * @param expression - The binding to inspect.
620
671
  */
621
- isVolatileBinding(binding) {
622
- return volatileRegex.test(binding.toString());
672
+ isVolatileBinding(expression) {
673
+ return volatileRegex.test(expression.toString());
623
674
  },
624
675
  });
625
676
  });
@@ -658,73 +709,40 @@ const contextEvent = FAST.getById(3 /* KernelServiceId.contextEvent */, () => {
658
709
  },
659
710
  };
660
711
  });
661
- class DefaultExecutionContext {
662
- constructor(parentSource = null, parentContext = null) {
663
- this.index = 0;
664
- this.length = 0;
665
- this.parent = parentSource;
666
- this.parentContext = parentContext;
667
- }
668
- get event() {
669
- return contextEvent.get();
670
- }
671
- get isEven() {
672
- return this.index % 2 === 0;
673
- }
674
- get isOdd() {
675
- return this.index % 2 !== 0;
676
- }
677
- get isFirst() {
678
- return this.index === 0;
679
- }
680
- get isInMiddle() {
681
- return !this.isFirst && !this.isLast;
682
- }
683
- get isLast() {
684
- return this.index === this.length - 1;
685
- }
686
- eventDetail() {
687
- return this.event.detail;
688
- }
689
- eventTarget() {
690
- return this.event.target;
691
- }
692
- updatePosition(index, length) {
693
- this.index = index;
694
- this.length = length;
695
- }
696
- createChildContext(parentSource) {
697
- return new DefaultExecutionContext(parentSource, this);
698
- }
699
- createItemContext(index, length) {
700
- const childContext = Object.create(this);
701
- childContext.index = index;
702
- childContext.length = length;
703
- return childContext;
704
- }
705
- }
706
- Observable.defineProperty(DefaultExecutionContext.prototype, "index");
707
- Observable.defineProperty(DefaultExecutionContext.prototype, "length");
708
712
  /**
709
- * The common execution context APIs.
713
+ * Provides additional contextual information available to behaviors and expressions.
710
714
  * @public
711
715
  */
712
716
  const ExecutionContext = Object.freeze({
713
- default: new DefaultExecutionContext(),
714
717
  /**
715
- * Sets the event for the current execution context.
716
- * @param event - The event to set.
717
- * @internal
718
+ * A default execution context.
718
719
  */
719
- setEvent(event) {
720
- contextEvent.set(event);
720
+ default: {
721
+ index: 0,
722
+ length: 0,
723
+ get event() {
724
+ return ExecutionContext.getEvent();
725
+ },
726
+ eventDetail() {
727
+ return this.event.detail;
728
+ },
729
+ eventTarget() {
730
+ return this.event.target;
731
+ },
721
732
  },
722
733
  /**
723
- * Creates a new root execution context.
724
- * @returns A new execution context.
734
+ * Gets the current event.
735
+ * @returns An event object.
725
736
  */
726
- create() {
727
- return new DefaultExecutionContext();
737
+ getEvent() {
738
+ return contextEvent.get();
739
+ },
740
+ /**
741
+ * Sets the current event.
742
+ * @param event - An event object.
743
+ */
744
+ setEvent(event) {
745
+ contextEvent.set(event);
728
746
  },
729
747
  });
730
748
 
@@ -793,10 +811,311 @@ const SpliceStrategySupport = Object.freeze({
793
811
  const reset = new Splice(0, emptyArray, 0);
794
812
  reset.reset = true;
795
813
  const resetSplices = [reset];
814
+ // Note: This function is *based* on the computation of the Levenshtein
815
+ // "edit" distance. The one change is that "updates" are treated as two
816
+ // edits - not one. With Array splices, an update is really a delete
817
+ // followed by an add. By retaining this, we optimize for "keeping" the
818
+ // maximum array items in the original array. For example:
819
+ //
820
+ // 'xxxx123' to '123yyyy'
821
+ //
822
+ // With 1-edit updates, the shortest path would be just to update all seven
823
+ // characters. With 2-edit updates, we delete 4, leave 3, and add 4. This
824
+ // leaves the substring '123' intact.
825
+ function calcEditDistances(current, currentStart, currentEnd, old, oldStart, oldEnd) {
826
+ // "Deletion" columns
827
+ const rowCount = oldEnd - oldStart + 1;
828
+ const columnCount = currentEnd - currentStart + 1;
829
+ const distances = new Array(rowCount);
830
+ let north;
831
+ let west;
832
+ // "Addition" rows. Initialize null column.
833
+ for (let i = 0; i < rowCount; ++i) {
834
+ distances[i] = new Array(columnCount);
835
+ distances[i][0] = i;
836
+ }
837
+ // Initialize null row
838
+ for (let j = 0; j < columnCount; ++j) {
839
+ distances[0][j] = j;
840
+ }
841
+ for (let i = 1; i < rowCount; ++i) {
842
+ for (let j = 1; j < columnCount; ++j) {
843
+ if (current[currentStart + j - 1] === old[oldStart + i - 1]) {
844
+ distances[i][j] = distances[i - 1][j - 1];
845
+ }
846
+ else {
847
+ north = distances[i - 1][j] + 1;
848
+ west = distances[i][j - 1] + 1;
849
+ distances[i][j] = north < west ? north : west;
850
+ }
851
+ }
852
+ }
853
+ return distances;
854
+ }
855
+ // This starts at the final weight, and walks "backward" by finding
856
+ // the minimum previous weight recursively until the origin of the weight
857
+ // matrix.
858
+ function spliceOperationsFromEditDistances(distances) {
859
+ let i = distances.length - 1;
860
+ let j = distances[0].length - 1;
861
+ let current = distances[i][j];
862
+ const edits = [];
863
+ while (i > 0 || j > 0) {
864
+ if (i === 0) {
865
+ edits.push(2 /* Edit.add */);
866
+ j--;
867
+ continue;
868
+ }
869
+ if (j === 0) {
870
+ edits.push(3 /* Edit.delete */);
871
+ i--;
872
+ continue;
873
+ }
874
+ const northWest = distances[i - 1][j - 1];
875
+ const west = distances[i - 1][j];
876
+ const north = distances[i][j - 1];
877
+ let min;
878
+ if (west < north) {
879
+ min = west < northWest ? west : northWest;
880
+ }
881
+ else {
882
+ min = north < northWest ? north : northWest;
883
+ }
884
+ if (min === northWest) {
885
+ if (northWest === current) {
886
+ edits.push(0 /* Edit.leave */);
887
+ }
888
+ else {
889
+ edits.push(1 /* Edit.update */);
890
+ current = northWest;
891
+ }
892
+ i--;
893
+ j--;
894
+ }
895
+ else if (min === west) {
896
+ edits.push(3 /* Edit.delete */);
897
+ i--;
898
+ current = west;
899
+ }
900
+ else {
901
+ edits.push(2 /* Edit.add */);
902
+ j--;
903
+ current = north;
904
+ }
905
+ }
906
+ return edits.reverse();
907
+ }
908
+ function sharedPrefix(current, old, searchLength) {
909
+ for (let i = 0; i < searchLength; ++i) {
910
+ if (current[i] !== old[i]) {
911
+ return i;
912
+ }
913
+ }
914
+ return searchLength;
915
+ }
916
+ function sharedSuffix(current, old, searchLength) {
917
+ let index1 = current.length;
918
+ let index2 = old.length;
919
+ let count = 0;
920
+ while (count < searchLength && current[--index1] === old[--index2]) {
921
+ count++;
922
+ }
923
+ return count;
924
+ }
925
+ function intersect(start1, end1, start2, end2) {
926
+ // Disjoint
927
+ if (end1 < start2 || end2 < start1) {
928
+ return -1;
929
+ }
930
+ // Adjacent
931
+ if (end1 === start2 || end2 === start1) {
932
+ return 0;
933
+ }
934
+ // Non-zero intersect, span1 first
935
+ if (start1 < start2) {
936
+ if (end1 < end2) {
937
+ return end1 - start2; // Overlap
938
+ }
939
+ return end2 - start2; // Contained
940
+ }
941
+ // Non-zero intersect, span2 first
942
+ if (end2 < end1) {
943
+ return end2 - start1; // Overlap
944
+ }
945
+ return end1 - start1; // Contained
946
+ }
947
+ /**
948
+ * @remarks
949
+ * Lacking individual splice mutation information, the minimal set of
950
+ * splices can be synthesized given the previous state and final state of an
951
+ * array. The basic approach is to calculate the edit distance matrix and
952
+ * choose the shortest path through it.
953
+ *
954
+ * Complexity: O(l * p)
955
+ * l: The length of the current array
956
+ * p: The length of the old array
957
+ */
958
+ function calc(current, currentStart, currentEnd, old, oldStart, oldEnd) {
959
+ let prefixCount = 0;
960
+ let suffixCount = 0;
961
+ const minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart);
962
+ if (currentStart === 0 && oldStart === 0) {
963
+ prefixCount = sharedPrefix(current, old, minLength);
964
+ }
965
+ if (currentEnd === current.length && oldEnd === old.length) {
966
+ suffixCount = sharedSuffix(current, old, minLength - prefixCount);
967
+ }
968
+ currentStart += prefixCount;
969
+ oldStart += prefixCount;
970
+ currentEnd -= suffixCount;
971
+ oldEnd -= suffixCount;
972
+ if (currentEnd - currentStart === 0 && oldEnd - oldStart === 0) {
973
+ return emptyArray;
974
+ }
975
+ if (currentStart === currentEnd) {
976
+ const splice = new Splice(currentStart, [], 0);
977
+ while (oldStart < oldEnd) {
978
+ splice.removed.push(old[oldStart++]);
979
+ }
980
+ return [splice];
981
+ }
982
+ else if (oldStart === oldEnd) {
983
+ return [new Splice(currentStart, [], currentEnd - currentStart)];
984
+ }
985
+ const ops = spliceOperationsFromEditDistances(calcEditDistances(current, currentStart, currentEnd, old, oldStart, oldEnd));
986
+ const splices = [];
987
+ let splice = void 0;
988
+ let index = currentStart;
989
+ let oldIndex = oldStart;
990
+ for (let i = 0; i < ops.length; ++i) {
991
+ switch (ops[i]) {
992
+ case 0 /* Edit.leave */:
993
+ if (splice !== void 0) {
994
+ splices.push(splice);
995
+ splice = void 0;
996
+ }
997
+ index++;
998
+ oldIndex++;
999
+ break;
1000
+ case 1 /* Edit.update */:
1001
+ if (splice === void 0) {
1002
+ splice = new Splice(index, [], 0);
1003
+ }
1004
+ splice.addedCount++;
1005
+ index++;
1006
+ splice.removed.push(old[oldIndex]);
1007
+ oldIndex++;
1008
+ break;
1009
+ case 2 /* Edit.add */:
1010
+ if (splice === void 0) {
1011
+ splice = new Splice(index, [], 0);
1012
+ }
1013
+ splice.addedCount++;
1014
+ index++;
1015
+ break;
1016
+ case 3 /* Edit.delete */:
1017
+ if (splice === void 0) {
1018
+ splice = new Splice(index, [], 0);
1019
+ }
1020
+ splice.removed.push(old[oldIndex]);
1021
+ oldIndex++;
1022
+ break;
1023
+ // no default
1024
+ }
1025
+ }
1026
+ if (splice !== void 0) {
1027
+ splices.push(splice);
1028
+ }
1029
+ return splices;
1030
+ }
1031
+ function merge(splice, splices) {
1032
+ let inserted = false;
1033
+ let insertionOffset = 0;
1034
+ for (let i = 0; i < splices.length; i++) {
1035
+ const current = splices[i];
1036
+ current.index += insertionOffset;
1037
+ if (inserted) {
1038
+ continue;
1039
+ }
1040
+ const intersectCount = intersect(splice.index, splice.index + splice.removed.length, current.index, current.index + current.addedCount);
1041
+ if (intersectCount >= 0) {
1042
+ // Merge the two splices
1043
+ splices.splice(i, 1);
1044
+ i--;
1045
+ insertionOffset -= current.addedCount - current.removed.length;
1046
+ splice.addedCount += current.addedCount - intersectCount;
1047
+ const deleteCount = splice.removed.length + current.removed.length - intersectCount;
1048
+ if (!splice.addedCount && !deleteCount) {
1049
+ // merged splice is a noop. discard.
1050
+ inserted = true;
1051
+ }
1052
+ else {
1053
+ let currentRemoved = current.removed;
1054
+ if (splice.index < current.index) {
1055
+ // some prefix of splice.removed is prepended to current.removed.
1056
+ const prepend = splice.removed.slice(0, current.index - splice.index);
1057
+ prepend.push(...currentRemoved);
1058
+ currentRemoved = prepend;
1059
+ }
1060
+ if (splice.index + splice.removed.length >
1061
+ current.index + current.addedCount) {
1062
+ // some suffix of splice.removed is appended to current.removed.
1063
+ const append = splice.removed.slice(current.index + current.addedCount - splice.index);
1064
+ currentRemoved.push(...append);
1065
+ }
1066
+ splice.removed = currentRemoved;
1067
+ if (current.index < splice.index) {
1068
+ splice.index = current.index;
1069
+ }
1070
+ }
1071
+ }
1072
+ else if (splice.index < current.index) {
1073
+ // Insert splice here.
1074
+ inserted = true;
1075
+ splices.splice(i, 0, splice);
1076
+ i++;
1077
+ const offset = splice.addedCount - splice.removed.length;
1078
+ current.index += offset;
1079
+ insertionOffset += offset;
1080
+ }
1081
+ }
1082
+ if (!inserted) {
1083
+ splices.push(splice);
1084
+ }
1085
+ }
1086
+ function project(array, changes) {
1087
+ let splices = [];
1088
+ const initialSplices = [];
1089
+ for (let i = 0, ii = changes.length; i < ii; i++) {
1090
+ merge(changes[i], initialSplices);
1091
+ }
1092
+ for (let i = 0, ii = initialSplices.length; i < ii; ++i) {
1093
+ const splice = initialSplices[i];
1094
+ if (splice.addedCount === 1 && splice.removed.length === 1) {
1095
+ if (splice.removed[0] !== array[splice.index]) {
1096
+ splices.push(splice);
1097
+ }
1098
+ continue;
1099
+ }
1100
+ splices = splices.concat(calc(array, splice.index, splice.index + splice.addedCount, splice.removed, 0, splice.removed.length));
1101
+ }
1102
+ return splices;
1103
+ }
1104
+ /**
1105
+ * A SpliceStrategy that attempts to merge all splices into the minimal set of
1106
+ * splices needed to represent the change from the old array to the new array.
1107
+ * @public
1108
+ */
796
1109
  let defaultSpliceStrategy = Object.freeze({
797
- support: SpliceStrategySupport.splice,
1110
+ support: SpliceStrategySupport.optimized,
798
1111
  normalize(previous, current, changes) {
799
- return previous === void 0 ? changes !== null && changes !== void 0 ? changes : emptyArray : resetSplices;
1112
+ if (previous === void 0) {
1113
+ if (changes === void 0) {
1114
+ return emptyArray;
1115
+ }
1116
+ return changes.length > 1 ? project(current, changes) : changes;
1117
+ }
1118
+ return resetSplices;
800
1119
  },
801
1120
  pop(array, observer, pop, args) {
802
1121
  const notEmpty = array.length > 0;
@@ -981,7 +1300,7 @@ const ArrayObserver = Object.freeze({
981
1300
  * @returns The length of the array.
982
1301
  * @public
983
1302
  */
984
- function length(array) {
1303
+ function lengthOf(array) {
985
1304
  if (!array) {
986
1305
  return 0;
987
1306
  }
@@ -1065,6 +1384,20 @@ class ElementStyles {
1065
1384
  static setDefaultStrategy(Strategy) {
1066
1385
  DefaultStyleStrategy = Strategy;
1067
1386
  }
1387
+ /**
1388
+ * Normalizes a set of composable style options.
1389
+ * @param styles - The style options to normalize.
1390
+ * @returns A singular ElementStyles instance or undefined.
1391
+ */
1392
+ static normalize(styles) {
1393
+ return styles === void 0
1394
+ ? void 0
1395
+ : Array.isArray(styles)
1396
+ ? new ElementStyles(styles)
1397
+ : styles instanceof ElementStyles
1398
+ ? styles
1399
+ : new ElementStyles([styles]);
1400
+ }
1068
1401
  }
1069
1402
  /**
1070
1403
  * Indicates whether the DOM supports the adoptedStyleSheets feature.
@@ -1209,11 +1542,11 @@ class CSSPartial {
1209
1542
  }
1210
1543
  return this.css;
1211
1544
  }
1212
- bind(el) {
1213
- el.$fastController.addStyles(this.styles);
1545
+ addedCallback(controller) {
1546
+ controller.addStyles(this.styles);
1214
1547
  }
1215
- unbind(el) {
1216
- el.$fastController.removeStyles(this.styles);
1548
+ removedCallback(controller) {
1549
+ controller.removeStyles(this.styles);
1217
1550
  }
1218
1551
  }
1219
1552
  CSSDirective.define(CSSPartial);
@@ -1352,6 +1685,67 @@ const Parser = Object.freeze({
1352
1685
  },
1353
1686
  });
1354
1687
 
1688
+ /**
1689
+ * Bridges between ViewBehaviors and HostBehaviors, enabling a host to
1690
+ * control ViewBehaviors.
1691
+ * @public
1692
+ */
1693
+ const ViewBehaviorOrchestrator = Object.freeze({
1694
+ /**
1695
+ * Creates a ViewBehaviorOrchestrator.
1696
+ * @param source - The source to to associate behaviors with.
1697
+ * @returns A ViewBehaviorOrchestrator.
1698
+ */
1699
+ create(source) {
1700
+ const behaviors = [];
1701
+ const targets = {};
1702
+ let unbindables = null;
1703
+ let isConnected = false;
1704
+ return {
1705
+ source,
1706
+ context: ExecutionContext.default,
1707
+ targets,
1708
+ get isBound() {
1709
+ return isConnected;
1710
+ },
1711
+ addBehaviorFactory(factory, target) {
1712
+ const nodeId = factory.nodeId || (factory.nodeId = nextId());
1713
+ factory.id || (factory.id = nextId());
1714
+ this.addTarget(nodeId, target);
1715
+ this.addBehavior(factory.createBehavior());
1716
+ },
1717
+ addTarget(nodeId, target) {
1718
+ targets[nodeId] = target;
1719
+ },
1720
+ addBehavior(behavior) {
1721
+ behaviors.push(behavior);
1722
+ if (isConnected) {
1723
+ behavior.bind(this);
1724
+ }
1725
+ },
1726
+ onUnbind(unbindable) {
1727
+ if (unbindables === null) {
1728
+ unbindables = [];
1729
+ }
1730
+ unbindables.push(unbindable);
1731
+ },
1732
+ connectedCallback(controller) {
1733
+ if (!isConnected) {
1734
+ isConnected = true;
1735
+ behaviors.forEach(x => x.bind(this));
1736
+ }
1737
+ },
1738
+ disconnectedCallback(controller) {
1739
+ if (isConnected) {
1740
+ isConnected = false;
1741
+ if (unbindables !== null) {
1742
+ unbindables.forEach(x => x.unbind(this));
1743
+ }
1744
+ }
1745
+ },
1746
+ };
1747
+ },
1748
+ });
1355
1749
  const registry = createTypeRegistry();
1356
1750
  /**
1357
1751
  * Instructs the template engine to apply behavior to a node.
@@ -1391,6 +1785,22 @@ function htmlDirective(options) {
1391
1785
  HTMLDirective.define(type, options);
1392
1786
  };
1393
1787
  }
1788
+ /**
1789
+ * Captures a binding expression along with related information and capabilities.
1790
+ *
1791
+ * @public
1792
+ */
1793
+ class Binding {
1794
+ /**
1795
+ * Creates a binding.
1796
+ * @param evaluate - Evaluates the binding.
1797
+ * @param isVolatile - Indicates whether the binding is volatile.
1798
+ */
1799
+ constructor(evaluate, isVolatile = false) {
1800
+ this.evaluate = evaluate;
1801
+ this.isVolatile = isVolatile;
1802
+ }
1803
+ }
1394
1804
  /**
1395
1805
  * The type of HTML aspect to target.
1396
1806
  * @public
@@ -1428,26 +1838,22 @@ const Aspect = Object.freeze({
1428
1838
  *
1429
1839
  * @param directive - The directive to assign the aspect to.
1430
1840
  * @param value - The value to base the aspect determination on.
1841
+ * @remarks
1842
+ * If a falsy value is provided, then the content aspect will be assigned.
1431
1843
  */
1432
1844
  assign(directive, value) {
1433
- directive.sourceAspect = value;
1434
1845
  if (!value) {
1846
+ directive.aspectType = Aspect.content;
1435
1847
  return;
1436
1848
  }
1849
+ directive.sourceAspect = value;
1437
1850
  switch (value[0]) {
1438
1851
  case ":":
1439
1852
  directive.targetAspect = value.substring(1);
1440
- switch (directive.targetAspect) {
1441
- case "innerHTML":
1442
- directive.aspectType = Aspect.property;
1443
- break;
1444
- case "classList":
1445
- directive.aspectType = Aspect.tokenList;
1446
- break;
1447
- default:
1448
- directive.aspectType = Aspect.property;
1449
- break;
1450
- }
1853
+ directive.aspectType =
1854
+ directive.targetAspect === "classList"
1855
+ ? Aspect.tokenList
1856
+ : Aspect.property;
1451
1857
  break;
1452
1858
  case "?":
1453
1859
  directive.targetAspect = value.substring(1);
@@ -1458,14 +1864,8 @@ const Aspect = Object.freeze({
1458
1864
  directive.aspectType = Aspect.event;
1459
1865
  break;
1460
1866
  default:
1461
- if (value === "class") {
1462
- directive.targetAspect = "className";
1463
- directive.aspectType = Aspect.property;
1464
- }
1465
- else {
1466
- directive.targetAspect = value;
1467
- directive.aspectType = Aspect.attribute;
1468
- }
1867
+ directive.targetAspect = value;
1868
+ directive.aspectType = Aspect.attribute;
1469
1869
  break;
1470
1870
  }
1471
1871
  },
@@ -1481,13 +1881,10 @@ class StatelessAttachedAttributeDirective {
1481
1881
  */
1482
1882
  constructor(options) {
1483
1883
  this.options = options;
1484
- }
1485
- /**
1486
- * Creates a behavior.
1487
- * @param targets - The targets available for behaviors to be attached to.
1488
- */
1489
- createBehavior(targets) {
1490
- return this;
1884
+ /**
1885
+ * The unique id of the factory.
1886
+ */
1887
+ this.id = nextId();
1491
1888
  }
1492
1889
  /**
1493
1890
  * Creates a placeholder string based on the directive's index within the template.
@@ -1498,6 +1895,13 @@ class StatelessAttachedAttributeDirective {
1498
1895
  createHTML(add) {
1499
1896
  return Markup.attribute(add(this));
1500
1897
  }
1898
+ /**
1899
+ * Creates a behavior.
1900
+ * @param targets - The targets available for behaviors to be attached to.
1901
+ */
1902
+ createBehavior() {
1903
+ return this;
1904
+ }
1501
1905
  }
1502
1906
 
1503
1907
  const createInnerHTMLBinding = globalThis.TrustedHTML
@@ -1509,107 +1913,26 @@ const createInnerHTMLBinding = globalThis.TrustedHTML
1509
1913
  throw FAST.error(1202 /* Message.bindingInnerHTMLRequiresTrustedTypes */);
1510
1914
  }
1511
1915
  : (binding) => binding;
1512
- /**
1513
- * Describes how aspects of an HTML element will be affected by bindings.
1514
- * @public
1515
- */
1516
- const BindingMode = Object.freeze({
1517
- /**
1518
- * Creates a binding mode based on the supplied behavior types.
1519
- * @param UpdateType - The base behavior type used to update aspects.
1520
- * @param EventType - The base behavior type used to respond to events.
1521
- * @returns A new binding mode.
1522
- */
1523
- define(UpdateType, EventType = EventBinding) {
1524
- return Object.freeze({
1525
- [1]: d => new UpdateType(d, DOM.setAttribute),
1526
- [2]: d => new UpdateType(d, DOM.setBooleanAttribute),
1527
- [3]: d => new UpdateType(d, (t, a, v) => (t[a] = v)),
1528
- [4]: d => new (createContentBinding(UpdateType))(d, updateContentTarget),
1529
- [5]: d => new UpdateType(d, updateTokenListTarget),
1530
- [6]: d => new EventType(d),
1531
- });
1532
- },
1533
- });
1534
- /**
1535
- * Describes the configuration for a binding expression.
1536
- * @public
1537
- */
1538
- const BindingConfig = Object.freeze({
1539
- /**
1540
- * Creates a binding configuration based on the provided mode and options.
1541
- * @param mode - The mode to use for the configuration.
1542
- * @param defaultOptions - The default options to use for the configuration.
1543
- * @returns A new binding configuration.
1544
- */
1545
- define(mode, defaultOptions) {
1546
- const config = (options) => {
1547
- return {
1548
- mode: config.mode,
1549
- options: Object.assign({}, defaultOptions, options),
1550
- };
1551
- };
1552
- config.options = defaultOptions;
1553
- config.mode = mode;
1554
- return config;
1555
- },
1556
- });
1557
- /**
1558
- * A base binding behavior for DOM updates.
1559
- * @public
1560
- */
1561
- class UpdateBinding {
1562
- /**
1563
- * Creates an instance of UpdateBinding.
1564
- * @param directive - The directive that has the configuration for this behavior.
1565
- * @param updateTarget - The function used to update the target with the latest value.
1566
- */
1567
- constructor(directive, updateTarget) {
1568
- this.directive = directive;
1569
- this.updateTarget = updateTarget;
1916
+ class OnChangeBinding extends Binding {
1917
+ createObserver(_, subscriber) {
1918
+ return Observable.binding(this.evaluate, subscriber, this.isVolatile);
1570
1919
  }
1571
- /**
1572
- * Bind this behavior to the source.
1573
- * @param source - The source to bind to.
1574
- * @param context - The execution context that the binding is operating within.
1575
- * @param targets - The targets that behaviors in a view can attach to.
1576
- */
1577
- bind(source, context, targets) { }
1578
- /**
1579
- * Unbinds this behavior from the source.
1580
- * @param source - The source to unbind from.
1581
- * @param context - The execution context that the binding is operating within.
1582
- * @param targets - The targets that behaviors in a view can attach to.
1583
- */
1584
- unbind(source, context, targets) { }
1585
- /**
1586
- * Creates a behavior.
1587
- * @param targets - The targets available for behaviors to be attached to.
1588
- */
1589
- createBehavior(targets) {
1920
+ }
1921
+ class OneTimeBinding extends Binding {
1922
+ createObserver() {
1590
1923
  return this;
1591
1924
  }
1925
+ bind(controller) {
1926
+ return this.evaluate(controller.source, controller.context);
1927
+ }
1592
1928
  }
1593
- function createContentBinding(Type) {
1594
- return class extends Type {
1595
- unbind(source, context, targets) {
1596
- super.unbind(source, context, targets);
1597
- const target = targets[this.directive.nodeId];
1598
- const view = target.$fastView;
1599
- if (view !== void 0 && view.isComposed) {
1600
- view.unbind();
1601
- view.needsBindOnly = true;
1602
- }
1603
- }
1604
- };
1605
- }
1606
- function updateContentTarget(target, aspect, value, source, context) {
1929
+ function updateContent(target, aspect, value, controller) {
1607
1930
  // If there's no actual value, then this equates to the
1608
1931
  // empty string for the purposes of content bindings.
1609
1932
  if (value === null || value === undefined) {
1610
1933
  value = "";
1611
1934
  }
1612
- // If the value has a "create" method, then it's a template-like.
1935
+ // If the value has a "create" method, then it's a ContentTemplate.
1613
1936
  if (value.create) {
1614
1937
  target.textContent = "";
1615
1938
  let view = target.$fastView;
@@ -1635,14 +1958,14 @@ function updateContentTarget(target, aspect, value, source, context) {
1635
1958
  // and that there's actually no need to compose it.
1636
1959
  if (!view.isComposed) {
1637
1960
  view.isComposed = true;
1638
- view.bind(source, context);
1961
+ view.bind(controller.source, controller.context);
1639
1962
  view.insertBefore(target);
1640
1963
  target.$fastView = view;
1641
1964
  target.$fastTemplate = value;
1642
1965
  }
1643
1966
  else if (view.needsBindOnly) {
1644
1967
  view.needsBindOnly = false;
1645
- view.bind(source, context);
1968
+ view.bind(controller.source, controller.context);
1646
1969
  }
1647
1970
  }
1648
1971
  else {
@@ -1662,10 +1985,9 @@ function updateContentTarget(target, aspect, value, source, context) {
1662
1985
  target.textContent = value;
1663
1986
  }
1664
1987
  }
1665
- function updateTokenListTarget(target, aspect, value) {
1988
+ function updateTokenList(target, aspect, value) {
1666
1989
  var _a;
1667
- const directive = this.directive;
1668
- const lookup = `${directive.id}-t`;
1990
+ const lookup = `${this.id}-t`;
1669
1991
  const state = (_a = target[lookup]) !== null && _a !== void 0 ? _a : (target[lookup] = { c: 0, v: Object.create(null) });
1670
1992
  const versions = state.v;
1671
1993
  let currentVersion = state.c;
@@ -1695,311 +2017,8 @@ function updateTokenListTarget(target, aspect, value) {
1695
2017
  }
1696
2018
  }
1697
2019
  }
1698
- /**
1699
- * A binding behavior for one-time bindings.
1700
- * @public
1701
- */
1702
- class OneTimeBinding extends UpdateBinding {
1703
- /**
1704
- * Bind this behavior to the source.
1705
- * @param source - The source to bind to.
1706
- * @param context - The execution context that the binding is operating within.
1707
- * @param targets - The targets that behaviors in a view can attach to.
1708
- */
1709
- bind(source, context, targets) {
1710
- const directive = this.directive;
1711
- this.updateTarget(targets[directive.nodeId], directive.targetAspect, directive.binding(source, context), source, context);
1712
- }
1713
- }
1714
- const signals = Object.create(null);
1715
- /**
1716
- * A binding behavior for signal bindings.
1717
- * @public
1718
- */
1719
- class SignalBinding extends UpdateBinding {
1720
- constructor() {
1721
- super(...arguments);
1722
- this.handlerProperty = `${this.directive.id}-h`;
1723
- }
1724
- /**
1725
- * Bind this behavior to the source.
1726
- * @param source - The source to bind to.
1727
- * @param context - The execution context that the binding is operating within.
1728
- * @param targets - The targets that behaviors in a view can attach to.
1729
- */
1730
- bind(source, context, targets) {
1731
- const directive = this.directive;
1732
- const target = targets[directive.nodeId];
1733
- const signal = this.getSignal(source, context);
1734
- const handler = (target[this.handlerProperty] = () => {
1735
- this.updateTarget(target, directive.targetAspect, directive.binding(source, context), source, context);
1736
- });
1737
- handler();
1738
- const found = signals[signal];
1739
- if (found) {
1740
- Array.isArray(found)
1741
- ? found.push(handler)
1742
- : (signals[signal] = [found, handler]);
1743
- }
1744
- else {
1745
- signals[signal] = handler;
1746
- }
1747
- }
1748
- /**
1749
- * Unbinds this behavior from the source.
1750
- * @param source - The source to unbind from.
1751
- * @param context - The execution context that the binding is operating within.
1752
- * @param targets - The targets that behaviors in a view can attach to.
1753
- */
1754
- unbind(source, context, targets) {
1755
- const signal = this.getSignal(source, context);
1756
- const found = signals[signal];
1757
- if (found && Array.isArray(found)) {
1758
- const directive = this.directive;
1759
- const target = targets[directive.nodeId];
1760
- const handler = target[this.handlerProperty];
1761
- const index = found.indexOf(handler);
1762
- if (index !== -1) {
1763
- found.splice(index, 1);
1764
- }
1765
- }
1766
- else {
1767
- signals[signal] = void 0;
1768
- }
1769
- }
1770
- getSignal(source, context) {
1771
- const options = this.directive.options;
1772
- return isString(options) ? options : options(source, context);
1773
- }
1774
- /**
1775
- * Sends the specified signal to signaled bindings.
1776
- * @param signal - The signal to send.
1777
- * @public
1778
- */
1779
- static send(signal) {
1780
- const found = signals[signal];
1781
- if (found) {
1782
- Array.isArray(found) ? found.forEach(x => x()) : found();
1783
- }
1784
- }
1785
- }
1786
- /**
1787
- * A binding behavior for bindings that change.
1788
- * @public
1789
- */
1790
- class ChangeBinding extends UpdateBinding {
1791
- /**
1792
- * Creates an instance of ChangeBinding.
1793
- * @param directive - The directive that has the configuration for this behavior.
1794
- * @param updateTarget - The function used to update the target with the latest value.
1795
- */
1796
- constructor(directive, updateTarget) {
1797
- super(directive, updateTarget);
1798
- this.isBindingVolatile = Observable.isVolatileBinding(directive.binding);
1799
- this.observerProperty = `${directive.id}-o`;
1800
- }
1801
- /**
1802
- * Returns the binding observer used to update the node.
1803
- * @param target - The target node.
1804
- * @returns A BindingObserver.
1805
- */
1806
- getObserver(target) {
1807
- var _a;
1808
- return ((_a = target[this.observerProperty]) !== null && _a !== void 0 ? _a : (target[this.observerProperty] = Observable.binding(this.directive.binding, this, this.isBindingVolatile)));
1809
- }
1810
- /**
1811
- * Bind this behavior to the source.
1812
- * @param source - The source to bind to.
1813
- * @param context - The execution context that the binding is operating within.
1814
- * @param targets - The targets that behaviors in a view can attach to.
1815
- */
1816
- bind(source, context, targets) {
1817
- const directive = this.directive;
1818
- const target = targets[directive.nodeId];
1819
- const observer = this.getObserver(target);
1820
- observer.target = target;
1821
- observer.source = source;
1822
- observer.context = context;
1823
- this.updateTarget(target, directive.targetAspect, observer.observe(source, context), source, context);
1824
- }
1825
- /**
1826
- * Unbinds this behavior from the source.
1827
- * @param source - The source to unbind from.
1828
- * @param context - The execution context that the binding is operating within.
1829
- * @param targets - The targets that behaviors in a view can attach to.
1830
- */
1831
- unbind(source, context, targets) {
1832
- const target = targets[this.directive.nodeId];
1833
- const observer = this.getObserver(target);
1834
- observer.dispose();
1835
- observer.target = null;
1836
- observer.source = null;
1837
- observer.context = null;
1838
- }
1839
- /** @internal */
1840
- handleChange(binding, observer) {
1841
- const target = observer.target;
1842
- const source = observer.source;
1843
- const context = observer.context;
1844
- this.updateTarget(target, this.directive.targetAspect, observer.observe(source, context), source, context);
1845
- }
1846
- }
1847
- /**
1848
- * A binding behavior for handling events.
1849
- * @public
1850
- */
1851
- class EventBinding {
1852
- /**
1853
- * Creates an instance of EventBinding.
1854
- * @param directive - The directive that has the configuration for this behavior.
1855
- */
1856
- constructor(directive) {
1857
- this.directive = directive;
1858
- this.sourceProperty = `${directive.id}-s`;
1859
- this.contextProperty = `${directive.id}-c`;
1860
- }
1861
- /**
1862
- * Bind this behavior to the source.
1863
- * @param source - The source to bind to.
1864
- * @param context - The execution context that the binding is operating within.
1865
- * @param targets - The targets that behaviors in a view can attach to.
1866
- */
1867
- bind(source, context, targets) {
1868
- const directive = this.directive;
1869
- const target = targets[directive.nodeId];
1870
- target[this.sourceProperty] = source;
1871
- target[this.contextProperty] = context;
1872
- target.addEventListener(directive.targetAspect, this, directive.options);
1873
- }
1874
- /**
1875
- * Unbinds this behavior from the source.
1876
- * @param source - The source to unbind from.
1877
- * @param context - The execution context that the binding is operating within.
1878
- * @param targets - The targets that behaviors in a view can attach to.
1879
- */
1880
- unbind(source, context, targets) {
1881
- const directive = this.directive;
1882
- const target = targets[directive.nodeId];
1883
- target[this.sourceProperty] = target[this.contextProperty] = null;
1884
- target.removeEventListener(directive.targetAspect, this, directive.options);
1885
- }
1886
- /**
1887
- * Creates a behavior.
1888
- * @param targets - The targets available for behaviors to be attached to.
1889
- */
1890
- createBehavior(targets) {
1891
- return this;
1892
- }
1893
- /**
1894
- * @internal
1895
- */
1896
- handleEvent(event) {
1897
- const target = event.currentTarget;
1898
- ExecutionContext.setEvent(event);
1899
- const result = this.directive.binding(target[this.sourceProperty], target[this.contextProperty]);
1900
- ExecutionContext.setEvent(null);
1901
- if (result !== true) {
1902
- event.preventDefault();
1903
- }
1904
- }
1905
- }
1906
- let twoWaySettings = {
1907
- determineChangeEvent() {
1908
- return "change";
1909
- },
1910
- };
1911
- /**
1912
- * A binding behavior for bindings that update in two directions.
1913
- * @public
1914
- */
1915
- class TwoWayBinding extends ChangeBinding {
1916
- /**
1917
- * Bind this behavior to the source.
1918
- * @param source - The source to bind to.
1919
- * @param context - The execution context that the binding is operating within.
1920
- * @param targets - The targets that behaviors in a view can attach to.
1921
- */
1922
- bind(source, context, targets) {
1923
- var _a;
1924
- super.bind(source, context, targets);
1925
- const directive = this.directive;
1926
- const target = targets[directive.nodeId];
1927
- if (!this.changeEvent) {
1928
- this.changeEvent =
1929
- (_a = directive.options.changeEvent) !== null && _a !== void 0 ? _a : twoWaySettings.determineChangeEvent(directive, target);
1930
- }
1931
- target.addEventListener(this.changeEvent, this);
1932
- }
1933
- /**
1934
- * Unbinds this behavior from the source.
1935
- * @param source - The source to unbind from.
1936
- * @param context - The execution context that the binding is operating within.
1937
- * @param targets - The targets that behaviors in a view can attach to.
1938
- */
1939
- unbind(source, context, targets) {
1940
- super.unbind(source, context, targets);
1941
- targets[this.directive.nodeId].removeEventListener(this.changeEvent, this);
1942
- }
1943
- /** @internal */
1944
- handleEvent(event) {
1945
- const directive = this.directive;
1946
- const target = event.currentTarget;
1947
- let value;
1948
- switch (directive.aspectType) {
1949
- case 1:
1950
- value = target.getAttribute(directive.targetAspect);
1951
- break;
1952
- case 2:
1953
- value = target.hasAttribute(directive.targetAspect);
1954
- break;
1955
- case 4:
1956
- value = target.innerText;
1957
- break;
1958
- default:
1959
- value = target[directive.targetAspect];
1960
- break;
1961
- }
1962
- const observer = this.getObserver(target);
1963
- const last = observer.last; // using internal API!!!
1964
- last.propertySource[last.propertyName] = directive.options.fromView(value);
1965
- }
1966
- /**
1967
- * Configures two-way binding.
1968
- * @param settings - The settings to use for the two-way binding system.
1969
- */
1970
- static configure(settings) {
1971
- twoWaySettings = settings;
1972
- }
1973
- }
1974
- /**
1975
- * The default onChange binding configuration.
1976
- * @public
1977
- */
1978
- const onChange = BindingConfig.define(BindingMode.define(ChangeBinding), {});
1979
- /**
1980
- * The default twoWay binding configuration.
1981
- * @public
1982
- */
1983
- const twoWay = BindingConfig.define(BindingMode.define(TwoWayBinding), {
1984
- fromView: v => v,
1985
- });
1986
- /**
1987
- * The default onTime binding configuration.
1988
- * @public
1989
- */
1990
- const oneTime = BindingConfig.define(BindingMode.define(OneTimeBinding), {
1991
- once: true,
1992
- });
1993
- const signalMode = BindingMode.define(SignalBinding);
1994
- /**
1995
- * Creates a signal binding configuration with the supplied options.
1996
- * @param options - The signal name or a binding to use to retrieve the signal name.
1997
- * @returns A binding configuration.
1998
- * @public
1999
- */
2000
- const signal = (options) => {
2001
- return { mode: signalMode, options };
2002
- };
2020
+ const setProperty = (t, a, v) => (t[a] = v);
2021
+ const eventTarget = () => void 0;
2003
2022
  /**
2004
2023
  * A directive that applies bindings.
2005
2024
  * @public
@@ -2007,19 +2026,22 @@ const signal = (options) => {
2007
2026
  class HTMLBindingDirective {
2008
2027
  /**
2009
2028
  * Creates an instance of HTMLBindingDirective.
2010
- * @param binding - The binding to apply.
2011
- * @param mode - The binding mode to use when applying the binding.
2012
- * @param options - The options to configure the binding with.
2029
+ * @param dataBinding - The binding configuration to apply.
2013
2030
  */
2014
- constructor(binding, mode, options) {
2015
- this.binding = binding;
2016
- this.mode = mode;
2017
- this.options = options;
2018
- this.factory = null;
2031
+ constructor(dataBinding) {
2032
+ this.dataBinding = dataBinding;
2033
+ this.updateTarget = null;
2034
+ /**
2035
+ * The unique id of the factory.
2036
+ */
2037
+ this.id = nextId();
2019
2038
  /**
2020
2039
  * The type of aspect to target.
2021
2040
  */
2022
2041
  this.aspectType = Aspect.content;
2042
+ /** @internal */
2043
+ this.bind = this.bindDefault;
2044
+ this.data = `${this.id}-d`;
2023
2045
  }
2024
2046
  /**
2025
2047
  * Creates HTML to be used within a template.
@@ -2030,31 +2052,133 @@ class HTMLBindingDirective {
2030
2052
  }
2031
2053
  /**
2032
2054
  * Creates a behavior.
2033
- * @param targets - The targets available for behaviors to be attached to.
2034
2055
  */
2035
- createBehavior(targets) {
2036
- if (this.factory == null) {
2056
+ createBehavior() {
2057
+ if (this.updateTarget === null) {
2037
2058
  if (this.targetAspect === "innerHTML") {
2038
- this.binding = createInnerHTMLBinding(this.binding);
2059
+ this.dataBinding.evaluate = createInnerHTMLBinding(this.dataBinding.evaluate);
2039
2060
  }
2040
- this.factory = this.mode[this.aspectType](this);
2061
+ switch (this.aspectType) {
2062
+ case 1:
2063
+ this.updateTarget = DOM.setAttribute;
2064
+ break;
2065
+ case 2:
2066
+ this.updateTarget = DOM.setBooleanAttribute;
2067
+ break;
2068
+ case 3:
2069
+ this.updateTarget = setProperty;
2070
+ break;
2071
+ case 4:
2072
+ this.bind = this.bindContent;
2073
+ this.updateTarget = updateContent;
2074
+ break;
2075
+ case 5:
2076
+ this.updateTarget = updateTokenList;
2077
+ break;
2078
+ case 6:
2079
+ this.bind = this.bindEvent;
2080
+ this.updateTarget = eventTarget;
2081
+ break;
2082
+ default:
2083
+ throw FAST.error(1205 /* Message.unsupportedBindingBehavior */);
2084
+ }
2085
+ }
2086
+ return this;
2087
+ }
2088
+ /** @internal */
2089
+ bindDefault(controller) {
2090
+ var _a;
2091
+ const target = controller.targets[this.nodeId];
2092
+ const observer = (_a = target[this.data]) !== null && _a !== void 0 ? _a : (target[this.data] = this.dataBinding.createObserver(this, this));
2093
+ observer.target = target;
2094
+ observer.controller = controller;
2095
+ this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
2096
+ if (this.updateTarget === updateContent) {
2097
+ controller.onUnbind(this);
2098
+ }
2099
+ }
2100
+ /** @internal */
2101
+ bindContent(controller) {
2102
+ this.bindDefault(controller);
2103
+ controller.onUnbind(this);
2104
+ }
2105
+ /** @internal */
2106
+ bindEvent(controller) {
2107
+ const target = controller.targets[this.nodeId];
2108
+ target[this.data] = controller;
2109
+ target.addEventListener(this.targetAspect, this, this.dataBinding.options);
2110
+ }
2111
+ /** @internal */
2112
+ unbind(controller) {
2113
+ const target = controller.targets[this.nodeId];
2114
+ const view = target.$fastView;
2115
+ if (view !== void 0 && view.isComposed) {
2116
+ view.unbind();
2117
+ view.needsBindOnly = true;
2118
+ }
2119
+ }
2120
+ /** @internal */
2121
+ handleEvent(event) {
2122
+ const target = event.currentTarget;
2123
+ ExecutionContext.setEvent(event);
2124
+ const controller = target[this.data];
2125
+ const result = this.dataBinding.evaluate(controller.source, controller.context);
2126
+ ExecutionContext.setEvent(null);
2127
+ if (result !== true) {
2128
+ event.preventDefault();
2041
2129
  }
2042
- return this.factory.createBehavior(targets);
2130
+ }
2131
+ /** @internal */
2132
+ handleChange(binding, observer) {
2133
+ const target = observer.target;
2134
+ const controller = observer.controller;
2135
+ this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
2043
2136
  }
2044
2137
  }
2045
2138
  HTMLDirective.define(HTMLBindingDirective, { aspected: true });
2046
2139
  /**
2047
- * Creates a binding directive with the specified configuration.
2048
- * @param binding - The binding expression.
2049
- * @param config - The binding configuration.
2050
- * @returns A binding directive.
2140
+ * Creates an standard binding.
2141
+ * @param binding - The binding to refresh when changed.
2142
+ * @param isVolatile - Indicates whether the binding is volatile or not.
2143
+ * @returns A binding configuration.
2144
+ * @public
2145
+ */
2146
+ function bind(binding, isVolatile = Observable.isVolatileBinding(binding)) {
2147
+ return new OnChangeBinding(binding, isVolatile);
2148
+ }
2149
+ /**
2150
+ * Creates a one time binding
2151
+ * @param binding - The binding to refresh when signaled.
2152
+ * @returns A binding configuration.
2051
2153
  * @public
2052
2154
  */
2053
- function bind(binding, config = onChange) {
2054
- if (!("mode" in config)) {
2055
- config = onChange(config);
2056
- }
2057
- return new HTMLBindingDirective(binding, config.mode, config.options);
2155
+ function oneTime(binding) {
2156
+ return new OneTimeBinding(binding);
2157
+ }
2158
+ /**
2159
+ * Creates an event listener binding.
2160
+ * @param binding - The binding to invoke when the event is raised.
2161
+ * @param options - Event listener options.
2162
+ * @returns A binding configuration.
2163
+ * @public
2164
+ */
2165
+ function listener(binding, options) {
2166
+ const config = new OnChangeBinding(binding, false);
2167
+ config.options = options;
2168
+ return config;
2169
+ }
2170
+ /**
2171
+ * Normalizes the input value into a binding.
2172
+ * @param value - The value to create the default binding for.
2173
+ * @returns A binding configuration for the provided value.
2174
+ * @public
2175
+ */
2176
+ function normalizeBinding(value) {
2177
+ return isFunction(value)
2178
+ ? bind(value)
2179
+ : value instanceof Binding
2180
+ ? value
2181
+ : oneTime(() => value);
2058
2182
  }
2059
2183
 
2060
2184
  function removeNodeSequence(firstNode, lastNode) {
@@ -2083,17 +2207,87 @@ class HTMLView {
2083
2207
  this.factories = factories;
2084
2208
  this.targets = targets;
2085
2209
  this.behaviors = null;
2210
+ this.unbindables = [];
2086
2211
  /**
2087
2212
  * The data that the view is bound to.
2088
2213
  */
2089
2214
  this.source = null;
2215
+ /**
2216
+ * Indicates whether the controller is bound.
2217
+ */
2218
+ this.isBound = false;
2219
+ /**
2220
+ * Indicates how the source's lifetime relates to the controller's lifetime.
2221
+ */
2222
+ this.sourceLifetime = SourceLifetime.unknown;
2090
2223
  /**
2091
2224
  * The execution context the view is running within.
2092
2225
  */
2093
- this.context = null;
2226
+ this.context = this;
2227
+ /**
2228
+ * The index of the current item within a repeat context.
2229
+ */
2230
+ this.index = 0;
2231
+ /**
2232
+ * The length of the current collection within a repeat context.
2233
+ */
2234
+ this.length = 0;
2094
2235
  this.firstChild = fragment.firstChild;
2095
2236
  this.lastChild = fragment.lastChild;
2096
2237
  }
2238
+ /**
2239
+ * The current event within an event handler.
2240
+ */
2241
+ get event() {
2242
+ return ExecutionContext.getEvent();
2243
+ }
2244
+ /**
2245
+ * Indicates whether the current item within a repeat context
2246
+ * has an even index.
2247
+ */
2248
+ get isEven() {
2249
+ return this.index % 2 === 0;
2250
+ }
2251
+ /**
2252
+ * Indicates whether the current item within a repeat context
2253
+ * has an odd index.
2254
+ */
2255
+ get isOdd() {
2256
+ return this.index % 2 !== 0;
2257
+ }
2258
+ /**
2259
+ * Indicates whether the current item within a repeat context
2260
+ * is the first item in the collection.
2261
+ */
2262
+ get isFirst() {
2263
+ return this.index === 0;
2264
+ }
2265
+ /**
2266
+ * Indicates whether the current item within a repeat context
2267
+ * is somewhere in the middle of the collection.
2268
+ */
2269
+ get isInMiddle() {
2270
+ return !this.isFirst && !this.isLast;
2271
+ }
2272
+ /**
2273
+ * Indicates whether the current item within a repeat context
2274
+ * is the last item in the collection.
2275
+ */
2276
+ get isLast() {
2277
+ return this.index === this.length - 1;
2278
+ }
2279
+ /**
2280
+ * Returns the typed event detail of a custom event.
2281
+ */
2282
+ eventDetail() {
2283
+ return this.event.detail;
2284
+ }
2285
+ /**
2286
+ * Returns the typed event target of the event.
2287
+ */
2288
+ eventTarget() {
2289
+ return this.event.target;
2290
+ }
2097
2291
  /**
2098
2292
  * Appends the view's DOM nodes to the referenced node.
2099
2293
  * @param node - The parent node to append the view's DOM nodes to.
@@ -2110,8 +2304,10 @@ class HTMLView {
2110
2304
  node.parentNode.insertBefore(this.fragment, node);
2111
2305
  }
2112
2306
  else {
2113
- const parentNode = node.parentNode;
2114
2307
  const end = this.lastChild;
2308
+ if (node.previousSibling === end)
2309
+ return;
2310
+ const parentNode = node.parentNode;
2115
2311
  let current = this.firstChild;
2116
2312
  let next;
2117
2313
  while (current !== end) {
@@ -2146,58 +2342,61 @@ class HTMLView {
2146
2342
  removeNodeSequence(this.firstChild, this.lastChild);
2147
2343
  this.unbind();
2148
2344
  }
2345
+ onUnbind(behavior) {
2346
+ this.unbindables.push(behavior);
2347
+ }
2149
2348
  /**
2150
2349
  * Binds a view's behaviors to its binding source.
2151
2350
  * @param source - The binding source for the view's binding behaviors.
2152
2351
  * @param context - The execution context to run the behaviors within.
2153
2352
  */
2154
- bind(source, context) {
2155
- let behaviors = this.behaviors;
2156
- const oldSource = this.source;
2157
- if (oldSource === source) {
2353
+ bind(source, context = this) {
2354
+ if (this.source === source) {
2158
2355
  return;
2159
2356
  }
2160
- this.source = source;
2161
- this.context = context;
2162
- const targets = this.targets;
2163
- if (oldSource !== null) {
2164
- for (let i = 0, ii = behaviors.length; i < ii; ++i) {
2165
- const current = behaviors[i];
2166
- current.unbind(oldSource, context, targets);
2167
- current.bind(source, context, targets);
2168
- }
2169
- }
2170
- else if (behaviors === null) {
2357
+ let behaviors = this.behaviors;
2358
+ if (behaviors === null) {
2359
+ this.source = source;
2360
+ this.context = context;
2171
2361
  this.behaviors = behaviors = new Array(this.factories.length);
2172
2362
  const factories = this.factories;
2173
2363
  for (let i = 0, ii = factories.length; i < ii; ++i) {
2174
- const behavior = factories[i].createBehavior(targets);
2175
- behavior.bind(source, context, targets);
2364
+ const behavior = factories[i].createBehavior();
2365
+ behavior.bind(this);
2176
2366
  behaviors[i] = behavior;
2177
2367
  }
2178
2368
  }
2179
2369
  else {
2370
+ if (this.source !== null) {
2371
+ this.evaluateUnbindables();
2372
+ }
2373
+ this.isBound = false;
2374
+ this.source = source;
2375
+ this.context = context;
2180
2376
  for (let i = 0, ii = behaviors.length; i < ii; ++i) {
2181
- behaviors[i].bind(source, context, targets);
2377
+ behaviors[i].bind(this);
2182
2378
  }
2183
2379
  }
2380
+ this.isBound = true;
2184
2381
  }
2185
2382
  /**
2186
2383
  * Unbinds a view's behaviors from its binding source.
2187
2384
  */
2188
2385
  unbind() {
2189
- const oldSource = this.source;
2190
- if (oldSource === null) {
2386
+ if (!this.isBound || this.source === null) {
2191
2387
  return;
2192
2388
  }
2193
- const targets = this.targets;
2194
- const context = this.context;
2195
- const behaviors = this.behaviors;
2196
- for (let i = 0, ii = behaviors.length; i < ii; ++i) {
2197
- behaviors[i].unbind(oldSource, context, targets);
2198
- }
2389
+ this.evaluateUnbindables();
2199
2390
  this.source = null;
2200
- this.context = null;
2391
+ this.context = this;
2392
+ this.isBound = false;
2393
+ }
2394
+ evaluateUnbindables() {
2395
+ const unbindables = this.unbindables;
2396
+ for (let i = 0, ii = unbindables.length; i < ii; ++i) {
2397
+ unbindables[i].unbind(this);
2398
+ }
2399
+ unbindables.length = 0;
2201
2400
  }
2202
2401
  /**
2203
2402
  * Efficiently disposes of a contiguous range of synthetic view instances.
@@ -2213,6 +2412,8 @@ class HTMLView {
2213
2412
  }
2214
2413
  }
2215
2414
  }
2415
+ Observable.defineProperty(HTMLView.prototype, "index");
2416
+ Observable.defineProperty(HTMLView.prototype, "length");
2216
2417
 
2217
2418
  const targetIdFrom = (parentId, nodeIndex) => `${parentId}.${nodeIndex}`;
2218
2419
  const descriptorCache = {};
@@ -2221,6 +2422,22 @@ const next = {
2221
2422
  index: 0,
2222
2423
  node: null,
2223
2424
  };
2425
+ function tryWarn(name) {
2426
+ if (!name.startsWith("fast-")) {
2427
+ FAST.warn(1204 /* Message.hostBindingWithoutHost */, { name });
2428
+ }
2429
+ }
2430
+ const warningHost = new Proxy(document.createElement("div"), {
2431
+ get(target, property) {
2432
+ tryWarn(property);
2433
+ const value = Reflect.get(target, property);
2434
+ return isFunction(value) ? value.bind(target) : value;
2435
+ },
2436
+ set(target, property, value) {
2437
+ tryWarn(property);
2438
+ return Reflect.set(target, property, value);
2439
+ },
2440
+ });
2224
2441
  class CompilationContext {
2225
2442
  constructor(fragment, directives) {
2226
2443
  this.fragment = fragment;
@@ -2271,7 +2488,7 @@ class CompilationContext {
2271
2488
  const fragment = this.fragment.cloneNode(true);
2272
2489
  const targets = Object.create(this.proto);
2273
2490
  targets.r = fragment;
2274
- targets.h = hostBindingTarget !== null && hostBindingTarget !== void 0 ? hostBindingTarget : fragment;
2491
+ targets.h = hostBindingTarget !== null && hostBindingTarget !== void 0 ? hostBindingTarget : warningHost;
2275
2492
  for (const id of this.nodeIds) {
2276
2493
  targets[id]; // trigger locator
2277
2494
  }
@@ -2288,7 +2505,7 @@ function compileAttributes(context, parentId, node, nodeId, nodeIndex, includeBa
2288
2505
  let result = null;
2289
2506
  if (parseResult === null) {
2290
2507
  if (includeBasicValues) {
2291
- result = bind(() => attrValue, oneTime);
2508
+ result = new HTMLBindingDirective(oneTime(() => attrValue));
2292
2509
  Aspect.assign(result, attr.name);
2293
2510
  }
2294
2511
  }
@@ -2325,6 +2542,7 @@ function compileContent(context, node, parentId, nodeId, nodeIndex) {
2325
2542
  }
2326
2543
  else {
2327
2544
  currentNode.textContent = " ";
2545
+ Aspect.assign(currentPart);
2328
2546
  context.addFactory(currentPart, parentId, nodeId, nodeIndex);
2329
2547
  }
2330
2548
  lastNode = currentNode;
@@ -2457,22 +2675,28 @@ const Compiler = {
2457
2675
  return parts[0];
2458
2676
  }
2459
2677
  let sourceAspect;
2678
+ let binding;
2679
+ let isVolatile = false;
2460
2680
  const partCount = parts.length;
2461
2681
  const finalParts = parts.map((x) => {
2462
2682
  if (isString(x)) {
2463
2683
  return () => x;
2464
2684
  }
2465
2685
  sourceAspect = x.sourceAspect || sourceAspect;
2466
- return x.binding;
2686
+ binding = x.dataBinding || binding;
2687
+ isVolatile = isVolatile || x.dataBinding.isVolatile;
2688
+ return x.dataBinding.evaluate;
2467
2689
  });
2468
- const binding = (scope, context) => {
2690
+ const expression = (scope, context) => {
2469
2691
  let output = "";
2470
2692
  for (let i = 0; i < partCount; ++i) {
2471
2693
  output += finalParts[i](scope, context);
2472
2694
  }
2473
2695
  return output;
2474
2696
  };
2475
- const directive = bind(binding);
2697
+ binding.evaluate = expression;
2698
+ binding.isVolatile = isVolatile;
2699
+ const directive = new HTMLBindingDirective(binding);
2476
2700
  Aspect.assign(directive, sourceAspect);
2477
2701
  return directive;
2478
2702
  },
@@ -2510,9 +2734,9 @@ class ViewTemplate {
2510
2734
  * @param hostBindingTarget - An HTML element to target the host bindings at if different from the
2511
2735
  * host that the template is being attached to.
2512
2736
  */
2513
- render(source, host, hostBindingTarget, context) {
2514
- const view = this.create(hostBindingTarget !== null && hostBindingTarget !== void 0 ? hostBindingTarget : host);
2515
- view.bind(source, context !== null && context !== void 0 ? context : ExecutionContext.default);
2737
+ render(source, host, hostBindingTarget) {
2738
+ const view = this.create(hostBindingTarget);
2739
+ view.bind(source);
2516
2740
  view.appendTo(host);
2517
2741
  return view;
2518
2742
  }
@@ -2552,12 +2776,12 @@ function html(strings, ...values) {
2552
2776
  let definition;
2553
2777
  html += currentString;
2554
2778
  if (isFunction(currentValue)) {
2555
- html += createAspectedHTML(bind(currentValue), currentString, add);
2779
+ html += createAspectedHTML(new HTMLBindingDirective(bind(currentValue)), currentString, add);
2556
2780
  }
2557
2781
  else if (isString(currentValue)) {
2558
2782
  const match = lastAttributeNameRegex.exec(currentString);
2559
2783
  if (match !== null) {
2560
- const directive = bind(() => currentValue, oneTime);
2784
+ const directive = new HTMLBindingDirective(oneTime(() => currentValue));
2561
2785
  Aspect.assign(directive, match[2]);
2562
2786
  html += directive.createHTML(add);
2563
2787
  }
@@ -2565,8 +2789,11 @@ function html(strings, ...values) {
2565
2789
  html += currentValue;
2566
2790
  }
2567
2791
  }
2792
+ else if (currentValue instanceof Binding) {
2793
+ html += createAspectedHTML(new HTMLBindingDirective(currentValue), currentString, add);
2794
+ }
2568
2795
  else if ((definition = HTMLDirective.getForInstance(currentValue)) === void 0) {
2569
- html += createAspectedHTML(bind(() => currentValue, oneTime), currentString, add);
2796
+ html += createAspectedHTML(new HTMLBindingDirective(oneTime(() => currentValue)), currentString, add);
2570
2797
  }
2571
2798
  else {
2572
2799
  if (definition.aspected) {
@@ -2579,26 +2806,6 @@ function html(strings, ...values) {
2579
2806
  }
2580
2807
  return new ViewTemplate(html + strings[strings.length - 1], factories);
2581
2808
  }
2582
- /**
2583
- * Transforms a template literal string into a ChildViewTemplate.
2584
- * @param strings - The string fragments that are interpolated with the values.
2585
- * @param values - The values that are interpolated with the string fragments.
2586
- * @remarks
2587
- * The html helper supports interpolation of strings, numbers, binding expressions,
2588
- * other template instances, and Directive instances.
2589
- * @public
2590
- */
2591
- const child = html;
2592
- /**
2593
- * Transforms a template literal string into an ItemViewTemplate.
2594
- * @param strings - The string fragments that are interpolated with the values.
2595
- * @param values - The values that are interpolated with the string fragments.
2596
- * @remarks
2597
- * The html helper supports interpolation of strings, numbers, binding expressions,
2598
- * other template instances, and Directive instances.
2599
- * @public
2600
- */
2601
- const item = html;
2602
2809
 
2603
2810
  /**
2604
2811
  * The runtime behavior for template references.
@@ -2606,20 +2813,12 @@ const item = html;
2606
2813
  */
2607
2814
  class RefDirective extends StatelessAttachedAttributeDirective {
2608
2815
  /**
2609
- * Bind this behavior to the source.
2610
- * @param source - The source to bind to.
2611
- * @param context - The execution context that the binding is operating within.
2612
- * @param targets - The targets that behaviors in a view can attach to.
2816
+ * Bind this behavior.
2817
+ * @param controller - The view controller that manages the lifecycle of this behavior.
2613
2818
  */
2614
- bind(source, context, targets) {
2615
- source[this.options] = targets[this.nodeId];
2819
+ bind(controller) {
2820
+ controller.source[this.options] = controller.targets[this.nodeId];
2616
2821
  }
2617
- /**
2618
- * Unbinds this behavior from the source.
2619
- * @param source - The source to unbind from.
2620
- */
2621
- /* eslint-disable-next-line @typescript-eslint/no-empty-function */
2622
- unbind() { }
2623
2822
  }
2624
2823
  HTMLDirective.define(RefDirective);
2625
2824
  /**
@@ -2631,27 +2830,34 @@ const ref = (propertyName) => new RefDirective(propertyName);
2631
2830
 
2632
2831
  /**
2633
2832
  * A directive that enables basic conditional rendering in a template.
2634
- * @param binding - The condition to test for rendering.
2833
+ * @param condition - The condition to test for rendering.
2635
2834
  * @param templateOrTemplateBinding - The template or a binding that gets
2636
2835
  * the template to render when the condition is true.
2637
2836
  * @public
2638
2837
  */
2639
- function when(binding, templateOrTemplateBinding) {
2640
- const getTemplate = isFunction(templateOrTemplateBinding)
2838
+ function when(condition, templateOrTemplateBinding) {
2839
+ const dataBinding = isFunction(condition) ? condition : () => condition;
2840
+ const templateBinding = isFunction(templateOrTemplateBinding)
2641
2841
  ? templateOrTemplateBinding
2642
2842
  : () => templateOrTemplateBinding;
2643
- return (source, context) => binding(source, context) ? getTemplate(source, context) : null;
2843
+ return (source, context) => dataBinding(source, context) ? templateBinding(source, context) : null;
2644
2844
  }
2645
2845
 
2646
2846
  const defaultRepeatOptions = Object.freeze({
2647
2847
  positioning: false,
2648
2848
  recycle: true,
2649
2849
  });
2650
- function bindWithoutPositioning(view, items, index, context) {
2651
- view.bind(items[index], context);
2850
+ function bindWithoutPositioning(view, items, index, controller) {
2851
+ view.context.parent = controller.source;
2852
+ view.context.parentContext = controller.context;
2853
+ view.bind(items[index]);
2652
2854
  }
2653
- function bindWithPositioning(view, items, index, context) {
2654
- view.bind(items[index], context.createItemContext(index, items.length));
2855
+ function bindWithPositioning(view, items, index, controller) {
2856
+ view.context.parent = controller.source;
2857
+ view.context.parentContext = controller.context;
2858
+ view.context.length = items.length;
2859
+ view.context.index = index;
2860
+ view.bind(items[index]);
2655
2861
  }
2656
2862
  /**
2657
2863
  * A behavior that renders a template for each item in an array.
@@ -2661,57 +2867,45 @@ class RepeatBehavior {
2661
2867
  /**
2662
2868
  * Creates an instance of RepeatBehavior.
2663
2869
  * @param location - The location in the DOM to render the repeat.
2664
- * @param itemsBinding - The array to render.
2870
+ * @param dataBinding - The array to render.
2665
2871
  * @param isItemsBindingVolatile - Indicates whether the items binding has volatile dependencies.
2666
2872
  * @param templateBinding - The template to render for each item.
2667
2873
  * @param isTemplateBindingVolatile - Indicates whether the template binding has volatile dependencies.
2668
2874
  * @param options - Options used to turn on special repeat features.
2669
2875
  */
2670
- constructor(location, itemsBinding, isItemsBindingVolatile, templateBinding, isTemplateBindingVolatile, options) {
2671
- this.location = location;
2672
- this.itemsBinding = itemsBinding;
2673
- this.templateBinding = templateBinding;
2674
- this.options = options;
2675
- this.source = null;
2876
+ constructor(directive) {
2877
+ this.directive = directive;
2676
2878
  this.views = [];
2677
2879
  this.items = null;
2678
2880
  this.itemsObserver = null;
2679
- this.context = void 0;
2680
- this.childContext = void 0;
2681
2881
  this.bindView = bindWithoutPositioning;
2682
- this.itemsBindingObserver = Observable.binding(itemsBinding, this, isItemsBindingVolatile);
2683
- this.templateBindingObserver = Observable.binding(templateBinding, this, isTemplateBindingVolatile);
2684
- if (options.positioning) {
2882
+ this.itemsBindingObserver = directive.dataBinding.createObserver(directive, this);
2883
+ this.templateBindingObserver = directive.templateBinding.createObserver(directive, this);
2884
+ if (directive.options.positioning) {
2685
2885
  this.bindView = bindWithPositioning;
2686
2886
  }
2687
2887
  }
2688
2888
  /**
2689
- * Bind this behavior to the source.
2690
- * @param source - The source to bind to.
2691
- * @param context - The execution context that the binding is operating within.
2889
+ * Bind this behavior.
2890
+ * @param controller - The view controller that manages the lifecycle of this behavior.
2692
2891
  */
2693
- bind(source, context) {
2694
- this.source = source;
2695
- this.context = context;
2696
- this.childContext = context.createChildContext(source);
2697
- this.items = this.itemsBindingObserver.observe(source, this.context);
2698
- this.template = this.templateBindingObserver.observe(source, this.context);
2892
+ bind(controller) {
2893
+ this.location = controller.targets[this.directive.nodeId];
2894
+ this.controller = controller;
2895
+ this.items = this.itemsBindingObserver.bind(controller);
2896
+ this.template = this.templateBindingObserver.bind(controller);
2699
2897
  this.observeItems(true);
2700
2898
  this.refreshAllViews();
2899
+ controller.onUnbind(this);
2701
2900
  }
2702
2901
  /**
2703
- * Unbinds this behavior from the source.
2704
- * @param source - The source to unbind from.
2902
+ * Unbinds this behavior.
2705
2903
  */
2706
2904
  unbind() {
2707
- this.source = null;
2708
- this.items = null;
2709
2905
  if (this.itemsObserver !== null) {
2710
2906
  this.itemsObserver.unsubscribe(this);
2711
2907
  }
2712
2908
  this.unbindAllViews();
2713
- this.itemsBindingObserver.dispose();
2714
- this.templateBindingObserver.dispose();
2715
2909
  }
2716
2910
  /**
2717
2911
  * Handles changes in the array, its items, and the repeat template.
@@ -2719,15 +2913,18 @@ class RepeatBehavior {
2719
2913
  * @param args - The details about what was changed.
2720
2914
  */
2721
2915
  handleChange(source, args) {
2722
- if (source === this.itemsBinding) {
2723
- this.items = this.itemsBindingObserver.observe(this.source, this.context);
2916
+ if (args === this.itemsBindingObserver) {
2917
+ this.items = this.itemsBindingObserver.bind(this.controller);
2724
2918
  this.observeItems();
2725
2919
  this.refreshAllViews();
2726
2920
  }
2727
- else if (source === this.templateBinding) {
2728
- this.template = this.templateBindingObserver.observe(this.source, this.context);
2921
+ else if (args === this.templateBindingObserver) {
2922
+ this.template = this.templateBindingObserver.bind(this.controller);
2729
2923
  this.refreshAllViews(true);
2730
2924
  }
2925
+ else if (!args[0]) {
2926
+ return;
2927
+ }
2731
2928
  else if (args[0].reset) {
2732
2929
  this.refreshAllViews();
2733
2930
  }
@@ -2752,39 +2949,57 @@ class RepeatBehavior {
2752
2949
  }
2753
2950
  updateViews(splices) {
2754
2951
  const views = this.views;
2755
- const childContext = this.childContext;
2756
- const totalRemoved = [];
2757
2952
  const bindView = this.bindView;
2758
- let removeDelta = 0;
2759
- for (let i = 0, ii = splices.length; i < ii; ++i) {
2760
- const splice = splices[i];
2761
- const removed = splice.removed;
2762
- totalRemoved.push(...views.splice(splice.index + removeDelta, removed.length));
2763
- removeDelta -= splice.addedCount;
2764
- }
2765
2953
  const items = this.items;
2766
2954
  const template = this.template;
2955
+ const controller = this.controller;
2956
+ const recycle = this.directive.options.recycle;
2957
+ const leftoverViews = [];
2958
+ let leftoverIndex = 0;
2959
+ let availableViews = 0;
2767
2960
  for (let i = 0, ii = splices.length; i < ii; ++i) {
2768
2961
  const splice = splices[i];
2962
+ const removed = splice.removed;
2963
+ let removeIndex = 0;
2769
2964
  let addIndex = splice.index;
2770
2965
  const end = addIndex + splice.addedCount;
2966
+ const removedViews = views.splice(splice.index, removed.length);
2967
+ const totalAvailableViews = (availableViews =
2968
+ leftoverViews.length + removedViews.length);
2771
2969
  for (; addIndex < end; ++addIndex) {
2772
2970
  const neighbor = views[addIndex];
2773
2971
  const location = neighbor ? neighbor.firstChild : this.location;
2774
- const view = this.options.recycle && totalRemoved.length > 0
2775
- ? totalRemoved.shift()
2776
- : template.create();
2972
+ let view;
2973
+ if (recycle && availableViews > 0) {
2974
+ if (removeIndex <= totalAvailableViews && removedViews.length > 0) {
2975
+ view = removedViews[removeIndex];
2976
+ removeIndex++;
2977
+ }
2978
+ else {
2979
+ view = leftoverViews[leftoverIndex];
2980
+ leftoverIndex++;
2981
+ }
2982
+ availableViews--;
2983
+ }
2984
+ else {
2985
+ view = template.create();
2986
+ }
2777
2987
  views.splice(addIndex, 0, view);
2778
- bindView(view, items, addIndex, childContext);
2988
+ bindView(view, items, addIndex, controller);
2779
2989
  view.insertBefore(location);
2780
2990
  }
2991
+ if (removedViews[removeIndex]) {
2992
+ leftoverViews.push(...removedViews.slice(removeIndex));
2993
+ }
2781
2994
  }
2782
- for (let i = 0, ii = totalRemoved.length; i < ii; ++i) {
2783
- totalRemoved[i].dispose();
2995
+ for (let i = leftoverIndex, ii = leftoverViews.length; i < ii; ++i) {
2996
+ leftoverViews[i].dispose();
2784
2997
  }
2785
- if (this.options.positioning) {
2998
+ if (this.directive.options.positioning) {
2786
2999
  for (let i = 0, ii = views.length; i < ii; ++i) {
2787
- views[i].context.updatePosition(i, ii);
3000
+ const context = views[i].context;
3001
+ context.length = i;
3002
+ context.index = ii;
2788
3003
  }
2789
3004
  }
2790
3005
  }
@@ -2793,11 +3008,11 @@ class RepeatBehavior {
2793
3008
  const template = this.template;
2794
3009
  const location = this.location;
2795
3010
  const bindView = this.bindView;
2796
- const childContext = this.childContext;
3011
+ const controller = this.controller;
2797
3012
  let itemsLength = items.length;
2798
3013
  let views = this.views;
2799
3014
  let viewsLength = views.length;
2800
- if (itemsLength === 0 || templateChanged) {
3015
+ if (itemsLength === 0 || templateChanged || !this.directive.options.recycle) {
2801
3016
  // all views need to be removed
2802
3017
  HTMLView.disposeContiguousBatch(views);
2803
3018
  viewsLength = 0;
@@ -2807,7 +3022,7 @@ class RepeatBehavior {
2807
3022
  this.views = views = new Array(itemsLength);
2808
3023
  for (let i = 0; i < itemsLength; ++i) {
2809
3024
  const view = template.create();
2810
- bindView(view, items, i, childContext);
3025
+ bindView(view, items, i, controller);
2811
3026
  views[i] = view;
2812
3027
  view.insertBefore(location);
2813
3028
  }
@@ -2818,11 +3033,11 @@ class RepeatBehavior {
2818
3033
  for (; i < itemsLength; ++i) {
2819
3034
  if (i < viewsLength) {
2820
3035
  const view = views[i];
2821
- bindView(view, items, i, childContext);
3036
+ bindView(view, items, i, controller);
2822
3037
  }
2823
3038
  else {
2824
3039
  const view = template.create();
2825
- bindView(view, items, i, childContext);
3040
+ bindView(view, items, i, controller);
2826
3041
  views.push(view);
2827
3042
  view.insertBefore(location);
2828
3043
  }
@@ -2847,17 +3062,19 @@ class RepeatBehavior {
2847
3062
  class RepeatDirective {
2848
3063
  /**
2849
3064
  * Creates an instance of RepeatDirective.
2850
- * @param itemsBinding - The binding that provides the array to render.
3065
+ * @param dataBinding - The binding that provides the array to render.
2851
3066
  * @param templateBinding - The template binding used to obtain a template to render for each item in the array.
2852
3067
  * @param options - Options used to turn on special repeat features.
2853
3068
  */
2854
- constructor(itemsBinding, templateBinding, options) {
2855
- this.itemsBinding = itemsBinding;
3069
+ constructor(dataBinding, templateBinding, options) {
3070
+ this.dataBinding = dataBinding;
2856
3071
  this.templateBinding = templateBinding;
2857
3072
  this.options = options;
3073
+ /**
3074
+ * The unique id of the factory.
3075
+ */
3076
+ this.id = nextId();
2858
3077
  ArrayObserver.enable();
2859
- this.isItemsBindingVolatile = Observable.isVolatileBinding(itemsBinding);
2860
- this.isTemplateBindingVolatile = Observable.isVolatileBinding(templateBinding);
2861
3078
  }
2862
3079
  /**
2863
3080
  * Creates a placeholder string based on the directive's index within the template.
@@ -2870,16 +3087,23 @@ class RepeatDirective {
2870
3087
  * Creates a behavior for the provided target node.
2871
3088
  * @param target - The node instance to create the behavior for.
2872
3089
  */
2873
- createBehavior(targets) {
2874
- return new RepeatBehavior(targets[this.nodeId], this.itemsBinding, this.isItemsBindingVolatile, this.templateBinding, this.isTemplateBindingVolatile, this.options);
3090
+ createBehavior() {
3091
+ return new RepeatBehavior(this);
2875
3092
  }
2876
3093
  }
2877
3094
  HTMLDirective.define(RepeatDirective);
2878
- function repeat(itemsBinding, templateOrTemplateBinding, options = defaultRepeatOptions) {
2879
- const templateBinding = isFunction(templateOrTemplateBinding)
2880
- ? templateOrTemplateBinding
2881
- : () => templateOrTemplateBinding;
2882
- return new RepeatDirective(itemsBinding, templateBinding, options);
3095
+ /**
3096
+ * A directive that enables list rendering.
3097
+ * @param items - The array to render.
3098
+ * @param template - The template or a template binding used obtain a template
3099
+ * to render for each item in the array.
3100
+ * @param options - Options used to turn on special repeat features.
3101
+ * @public
3102
+ */
3103
+ function repeat(items, template, options = defaultRepeatOptions) {
3104
+ const dataBinding = normalizeBinding(items);
3105
+ const templateBinding = normalizeBinding(template);
3106
+ return new RepeatDirective(dataBinding, templateBinding, Object.assign(Object.assign({}, defaultRepeatOptions), options));
2883
3107
  }
2884
3108
 
2885
3109
  const selectElements = (value) => value.nodeType === 1;
@@ -2908,11 +3132,12 @@ class NodeObservationDirective extends StatelessAttachedAttributeDirective {
2908
3132
  * @param context - The execution context that the binding is operating within.
2909
3133
  * @param targets - The targets that behaviors in a view can attach to.
2910
3134
  */
2911
- bind(source, context, targets) {
2912
- const target = targets[this.nodeId];
2913
- target[this.sourceProperty] = source;
2914
- this.updateTarget(source, this.computeNodes(target));
3135
+ bind(controller) {
3136
+ const target = controller.targets[this.nodeId];
3137
+ target[this.sourceProperty] = controller.source;
3138
+ this.updateTarget(controller.source, this.computeNodes(target));
2915
3139
  this.observe(target);
3140
+ controller.onUnbind(this);
2916
3141
  }
2917
3142
  /**
2918
3143
  * Unbinds this behavior from the source.
@@ -2920,9 +3145,9 @@ class NodeObservationDirective extends StatelessAttachedAttributeDirective {
2920
3145
  * @param context - The execution context that the binding is operating within.
2921
3146
  * @param targets - The targets that behaviors in a view can attach to.
2922
3147
  */
2923
- unbind(source, context, targets) {
2924
- const target = targets[this.nodeId];
2925
- this.updateTarget(source, emptyArray);
3148
+ unbind(controller) {
3149
+ const target = controller.targets[this.nodeId];
3150
+ this.updateTarget(controller.source, emptyArray);
2926
3151
  this.disconnect(target);
2927
3152
  target[this.sourceProperty] = null;
2928
3153
  }
@@ -3071,6 +3296,16 @@ function children(propertyOrOptions) {
3071
3296
 
3072
3297
  const booleanMode = "boolean";
3073
3298
  const reflectMode = "reflect";
3299
+ /**
3300
+ * Metadata used to configure a custom attribute's behavior.
3301
+ * @public
3302
+ */
3303
+ const AttributeConfiguration = Object.freeze({
3304
+ /**
3305
+ * Locates all attribute configurations associated with a type.
3306
+ */
3307
+ locate: createMetadataLocator(),
3308
+ });
3074
3309
  /**
3075
3310
  * A {@link ValueConverter} that converts to and from `boolean` values.
3076
3311
  * @remarks
@@ -3208,7 +3443,7 @@ class AttributeDefinition {
3208
3443
  */
3209
3444
  static collect(Owner, ...attributeLists) {
3210
3445
  const attributes = [];
3211
- attributeLists.push(Owner.attributes);
3446
+ attributeLists.push(AttributeConfiguration.locate(Owner));
3212
3447
  for (let i = 0, ii = attributeLists.length; i < ii; ++i) {
3213
3448
  const list = attributeLists[i];
3214
3449
  if (list === void 0) {
@@ -3238,9 +3473,7 @@ function attr(configOrTarget, prop) {
3238
3473
  // - @attr({...opts})
3239
3474
  config.property = $prop;
3240
3475
  }
3241
- const attributes = $target.constructor.attributes ||
3242
- ($target.constructor.attributes = []);
3243
- attributes.push(config);
3476
+ AttributeConfiguration.locate($target.constructor).push(config);
3244
3477
  }
3245
3478
  if (arguments.length > 1) {
3246
3479
  // Non invocation:
@@ -3258,25 +3491,24 @@ function attr(configOrTarget, prop) {
3258
3491
 
3259
3492
  const defaultShadowOptions = { mode: "open" };
3260
3493
  const defaultElementOptions = {};
3494
+ const fastElementBaseTypes = new Set();
3261
3495
  const fastElementRegistry = FAST.getById(4 /* KernelServiceId.elementRegistry */, () => createTypeRegistry());
3262
3496
  /**
3263
3497
  * Defines metadata for a FASTElement.
3264
3498
  * @public
3265
3499
  */
3266
3500
  class FASTElementDefinition {
3267
- /**
3268
- * Creates an instance of FASTElementDefinition.
3269
- * @param type - The type this definition is being created for.
3270
- * @param nameOrConfig - The name of the element to define or a config object
3271
- * that describes the element to define.
3272
- */
3273
3501
  constructor(type, nameOrConfig = type.definition) {
3502
+ var _a;
3503
+ this.platformDefined = false;
3274
3504
  if (isString(nameOrConfig)) {
3275
3505
  nameOrConfig = { name: nameOrConfig };
3276
3506
  }
3277
3507
  this.type = type;
3278
3508
  this.name = nameOrConfig.name;
3279
3509
  this.template = nameOrConfig.template;
3510
+ this.registry = (_a = nameOrConfig.registry) !== null && _a !== void 0 ? _a : customElements;
3511
+ const proto = type.prototype;
3280
3512
  const attributes = AttributeDefinition.collect(type, nameOrConfig.attributes);
3281
3513
  const observedAttributes = new Array(attributes.length);
3282
3514
  const propertyLookup = {};
@@ -3286,9 +3518,13 @@ class FASTElementDefinition {
3286
3518
  observedAttributes[i] = current.attribute;
3287
3519
  propertyLookup[current.name] = current;
3288
3520
  attributeLookup[current.attribute] = current;
3521
+ Observable.defineProperty(proto, current);
3289
3522
  }
3523
+ Reflect.defineProperty(type, "observedAttributes", {
3524
+ value: observedAttributes,
3525
+ enumerable: true,
3526
+ });
3290
3527
  this.attributes = attributes;
3291
- this.observedAttributes = observedAttributes;
3292
3528
  this.propertyLookup = propertyLookup;
3293
3529
  this.attributeLookup = attributeLookup;
3294
3530
  this.shadowOptions =
@@ -3301,43 +3537,50 @@ class FASTElementDefinition {
3301
3537
  nameOrConfig.elementOptions === void 0
3302
3538
  ? defaultElementOptions
3303
3539
  : Object.assign(Object.assign({}, defaultElementOptions), nameOrConfig.elementOptions);
3304
- this.styles =
3305
- nameOrConfig.styles === void 0
3306
- ? void 0
3307
- : Array.isArray(nameOrConfig.styles)
3308
- ? new ElementStyles(nameOrConfig.styles)
3309
- : nameOrConfig.styles instanceof ElementStyles
3310
- ? nameOrConfig.styles
3311
- : new ElementStyles([nameOrConfig.styles]);
3540
+ this.styles = ElementStyles.normalize(nameOrConfig.styles);
3541
+ fastElementRegistry.register(this);
3312
3542
  }
3313
3543
  /**
3314
3544
  * Indicates if this element has been defined in at least one registry.
3315
3545
  */
3316
3546
  get isDefined() {
3317
- return !!fastElementRegistry.getByType(this.type);
3547
+ return this.platformDefined;
3318
3548
  }
3319
3549
  /**
3320
3550
  * Defines a custom element based on this definition.
3321
3551
  * @param registry - The element registry to define the element in.
3552
+ * @remarks
3553
+ * This operation is idempotent per registry.
3322
3554
  */
3323
- define(registry = customElements) {
3555
+ define(registry = this.registry) {
3324
3556
  const type = this.type;
3325
- if (fastElementRegistry.register(this)) {
3326
- const attributes = this.attributes;
3327
- const proto = type.prototype;
3328
- for (let i = 0, ii = attributes.length; i < ii; ++i) {
3329
- Observable.defineProperty(proto, attributes[i]);
3330
- }
3331
- Reflect.defineProperty(type, "observedAttributes", {
3332
- value: this.observedAttributes,
3333
- enumerable: true,
3334
- });
3335
- }
3336
3557
  if (!registry.get(this.name)) {
3558
+ this.platformDefined = true;
3337
3559
  registry.define(this.name, type, this.elementOptions);
3338
3560
  }
3339
3561
  return this;
3340
3562
  }
3563
+ /**
3564
+ * Creates an instance of FASTElementDefinition.
3565
+ * @param type - The type this definition is being created for.
3566
+ * @param nameOrDef - The name of the element to define or a config object
3567
+ * that describes the element to define.
3568
+ */
3569
+ static compose(type, nameOrDef) {
3570
+ if (fastElementBaseTypes.has(type) || fastElementRegistry.getByType(type)) {
3571
+ return new FASTElementDefinition(class extends type {
3572
+ }, nameOrDef);
3573
+ }
3574
+ return new FASTElementDefinition(type, nameOrDef);
3575
+ }
3576
+ /**
3577
+ * Registers a FASTElement base type.
3578
+ * @param type - The type to register as a base type.
3579
+ * @internal
3580
+ */
3581
+ static registerBaseType(type) {
3582
+ fastElementBaseTypes.add(type);
3583
+ }
3341
3584
  }
3342
3585
  /**
3343
3586
  * Gets the element definition associated with the specified type.
@@ -3350,22 +3593,22 @@ FASTElementDefinition.getByType = fastElementRegistry.getByType;
3350
3593
  */
3351
3594
  FASTElementDefinition.getForInstance = fastElementRegistry.getForInstance;
3352
3595
 
3353
- const shadowRoots = new WeakMap();
3354
3596
  const defaultEventOptions = {
3355
3597
  bubbles: true,
3356
3598
  composed: true,
3357
3599
  cancelable: true,
3358
3600
  };
3601
+ const isConnectedPropertyName = "isConnected";
3602
+ const shadowRoots = new WeakMap();
3359
3603
  function getShadowRoot(element) {
3360
3604
  var _a, _b;
3361
3605
  return (_b = (_a = element.shadowRoot) !== null && _a !== void 0 ? _a : shadowRoots.get(element)) !== null && _b !== void 0 ? _b : null;
3362
3606
  }
3363
- const isConnectedPropertyName = "isConnected";
3364
3607
  /**
3365
3608
  * Controls the lifecycle and rendering of a `FASTElement`.
3366
3609
  * @public
3367
3610
  */
3368
- class Controller extends PropertyChangeNotifier {
3611
+ class ElementController extends PropertyChangeNotifier {
3369
3612
  /**
3370
3613
  * Creates a Controller to control the specified element.
3371
3614
  * @param element - The element to be controlled by this controller.
@@ -3376,12 +3619,12 @@ class Controller extends PropertyChangeNotifier {
3376
3619
  constructor(element, definition) {
3377
3620
  super(element);
3378
3621
  this.boundObservables = null;
3379
- this.behaviors = null;
3380
3622
  this.needsInitialization = true;
3381
3623
  this.hasExistingShadowRoot = false;
3382
3624
  this._template = null;
3383
- this._styles = null;
3384
3625
  this._isConnected = false;
3626
+ this.behaviors = null;
3627
+ this._mainStyles = null;
3385
3628
  /**
3386
3629
  * This allows Observable.getNotifier(...) to return the Controller
3387
3630
  * when the notifier for the Controller itself is being requested. The
@@ -3397,7 +3640,7 @@ class Controller extends PropertyChangeNotifier {
3397
3640
  * If `null` then the element is managing its own rendering.
3398
3641
  */
3399
3642
  this.view = null;
3400
- this.element = element;
3643
+ this.source = element;
3401
3644
  this.definition = definition;
3402
3645
  const shadowOptions = definition.shadowOptions;
3403
3646
  if (shadowOptions !== void 0) {
@@ -3451,9 +3694,9 @@ class Controller extends PropertyChangeNotifier {
3451
3694
  // 1. Template overrides take top precedence.
3452
3695
  if (this._template === null) {
3453
3696
  const definition = this.definition;
3454
- if (this.element.resolveTemplate) {
3697
+ if (this.source.resolveTemplate) {
3455
3698
  // 2. Allow for element instance overrides next.
3456
- this._template = this.element.resolveTemplate();
3699
+ this._template = this.source.resolveTemplate();
3457
3700
  }
3458
3701
  else if (definition.template) {
3459
3702
  // 3. Default to the static definition.
@@ -3472,48 +3715,92 @@ class Controller extends PropertyChangeNotifier {
3472
3715
  }
3473
3716
  }
3474
3717
  /**
3475
- * Gets/sets the primary styles used for the component.
3476
- * @remarks
3477
- * This value can only be accurately read after connect but can be set at any time.
3718
+ * The main set of styles used for the component, independent
3719
+ * of any dynamically added styles.
3478
3720
  */
3479
- get styles() {
3721
+ get mainStyles() {
3480
3722
  var _a;
3481
3723
  // 1. Styles overrides take top precedence.
3482
- if (this._styles === null) {
3724
+ if (this._mainStyles === null) {
3483
3725
  const definition = this.definition;
3484
- if (this.element.resolveStyles) {
3726
+ if (this.source.resolveStyles) {
3485
3727
  // 2. Allow for element instance overrides next.
3486
- this._styles = this.element.resolveStyles();
3728
+ this._mainStyles = this.source.resolveStyles();
3487
3729
  }
3488
3730
  else if (definition.styles) {
3489
3731
  // 3. Default to the static definition.
3490
- this._styles = (_a = definition.styles) !== null && _a !== void 0 ? _a : null;
3732
+ this._mainStyles = (_a = definition.styles) !== null && _a !== void 0 ? _a : null;
3491
3733
  }
3492
3734
  }
3493
- return this._styles;
3735
+ return this._mainStyles;
3494
3736
  }
3495
- set styles(value) {
3496
- if (this._styles === value) {
3737
+ set mainStyles(value) {
3738
+ if (this._mainStyles === value) {
3497
3739
  return;
3498
3740
  }
3499
- if (this._styles !== null) {
3500
- this.removeStyles(this._styles);
3741
+ if (this._mainStyles !== null) {
3742
+ this.removeStyles(this._mainStyles);
3501
3743
  }
3502
- this._styles = value;
3744
+ this._mainStyles = value;
3503
3745
  if (!this.needsInitialization) {
3504
3746
  this.addStyles(value);
3505
3747
  }
3506
3748
  }
3749
+ /**
3750
+ * Adds the behavior to the component.
3751
+ * @param behavior - The behavior to add.
3752
+ */
3753
+ addBehavior(behavior) {
3754
+ var _a, _b;
3755
+ const targetBehaviors = (_a = this.behaviors) !== null && _a !== void 0 ? _a : (this.behaviors = new Map());
3756
+ const count = (_b = targetBehaviors.get(behavior)) !== null && _b !== void 0 ? _b : 0;
3757
+ if (count === 0) {
3758
+ targetBehaviors.set(behavior, 1);
3759
+ behavior.addedCallback && behavior.addedCallback(this);
3760
+ if (behavior.connectedCallback && this.isConnected) {
3761
+ behavior.connectedCallback(this);
3762
+ }
3763
+ }
3764
+ else {
3765
+ targetBehaviors.set(behavior, count + 1);
3766
+ }
3767
+ }
3768
+ /**
3769
+ * Removes the behavior from the component.
3770
+ * @param behavior - The behavior to remove.
3771
+ * @param force - Forces removal even if this behavior was added more than once.
3772
+ */
3773
+ removeBehavior(behavior, force = false) {
3774
+ const targetBehaviors = this.behaviors;
3775
+ if (targetBehaviors === null) {
3776
+ return;
3777
+ }
3778
+ const count = targetBehaviors.get(behavior);
3779
+ if (count === void 0) {
3780
+ return;
3781
+ }
3782
+ if (count === 1 || force) {
3783
+ targetBehaviors.delete(behavior);
3784
+ if (behavior.disconnectedCallback && this.isConnected) {
3785
+ behavior.disconnectedCallback(this);
3786
+ }
3787
+ behavior.removedCallback && behavior.removedCallback(this);
3788
+ }
3789
+ else {
3790
+ targetBehaviors.set(behavior, count - 1);
3791
+ }
3792
+ }
3507
3793
  /**
3508
3794
  * Adds styles to this element. Providing an HTMLStyleElement will attach the element instance to the shadowRoot.
3509
3795
  * @param styles - The styles to add.
3510
3796
  */
3511
3797
  addStyles(styles) {
3798
+ var _a;
3512
3799
  if (!styles) {
3513
3800
  return;
3514
3801
  }
3515
- const target = getShadowRoot(this.element) ||
3516
- this.element.getRootNode();
3802
+ const source = this.source;
3803
+ const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : source.getRootNode();
3517
3804
  if (styles instanceof HTMLElement) {
3518
3805
  target.append(styles);
3519
3806
  }
@@ -3521,7 +3808,9 @@ class Controller extends PropertyChangeNotifier {
3521
3808
  const sourceBehaviors = styles.behaviors;
3522
3809
  styles.addStylesTo(target);
3523
3810
  if (sourceBehaviors !== null) {
3524
- this.addBehaviors(sourceBehaviors);
3811
+ for (let i = 0, ii = sourceBehaviors.length; i < ii; ++i) {
3812
+ this.addBehavior(sourceBehaviors[i]);
3813
+ }
3525
3814
  }
3526
3815
  }
3527
3816
  }
@@ -3530,11 +3819,12 @@ class Controller extends PropertyChangeNotifier {
3530
3819
  * @param styles - the styles to remove.
3531
3820
  */
3532
3821
  removeStyles(styles) {
3822
+ var _a;
3533
3823
  if (!styles) {
3534
3824
  return;
3535
3825
  }
3536
- const target = getShadowRoot(this.element) ||
3537
- this.element.getRootNode();
3826
+ const source = this.source;
3827
+ const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : source.getRootNode();
3538
3828
  if (styles instanceof HTMLElement) {
3539
3829
  target.removeChild(styles);
3540
3830
  }
@@ -3542,85 +3832,29 @@ class Controller extends PropertyChangeNotifier {
3542
3832
  const sourceBehaviors = styles.behaviors;
3543
3833
  styles.removeStylesFrom(target);
3544
3834
  if (sourceBehaviors !== null) {
3545
- this.removeBehaviors(sourceBehaviors);
3546
- }
3547
- }
3548
- }
3549
- /**
3550
- * Adds behaviors to this element.
3551
- * @param behaviors - The behaviors to add.
3552
- */
3553
- addBehaviors(behaviors) {
3554
- var _a;
3555
- const targetBehaviors = (_a = this.behaviors) !== null && _a !== void 0 ? _a : (this.behaviors = new Map());
3556
- const length = behaviors.length;
3557
- const behaviorsToBind = [];
3558
- for (let i = 0; i < length; ++i) {
3559
- const behavior = behaviors[i];
3560
- if (targetBehaviors.has(behavior)) {
3561
- targetBehaviors.set(behavior, targetBehaviors.get(behavior) + 1);
3562
- }
3563
- else {
3564
- targetBehaviors.set(behavior, 1);
3565
- behaviorsToBind.push(behavior);
3566
- }
3567
- }
3568
- if (this._isConnected) {
3569
- const element = this.element;
3570
- const context = ExecutionContext.default;
3571
- for (let i = 0; i < behaviorsToBind.length; ++i) {
3572
- behaviorsToBind[i].bind(element, context);
3573
- }
3574
- }
3575
- }
3576
- /**
3577
- * Removes behaviors from this element.
3578
- * @param behaviors - The behaviors to remove.
3579
- * @param force - Forces unbinding of behaviors.
3580
- */
3581
- removeBehaviors(behaviors, force = false) {
3582
- const targetBehaviors = this.behaviors;
3583
- if (targetBehaviors === null) {
3584
- return;
3585
- }
3586
- const length = behaviors.length;
3587
- const behaviorsToUnbind = [];
3588
- for (let i = 0; i < length; ++i) {
3589
- const behavior = behaviors[i];
3590
- if (targetBehaviors.has(behavior)) {
3591
- const count = targetBehaviors.get(behavior) - 1;
3592
- count === 0 || force
3593
- ? targetBehaviors.delete(behavior) && behaviorsToUnbind.push(behavior)
3594
- : targetBehaviors.set(behavior, count);
3595
- }
3596
- }
3597
- if (this._isConnected) {
3598
- const element = this.element;
3599
- const context = ExecutionContext.default;
3600
- for (let i = 0; i < behaviorsToUnbind.length; ++i) {
3601
- behaviorsToUnbind[i].unbind(element, context);
3835
+ for (let i = 0, ii = sourceBehaviors.length; i < ii; ++i) {
3836
+ this.addBehavior(sourceBehaviors[i]);
3837
+ }
3602
3838
  }
3603
3839
  }
3604
3840
  }
3605
3841
  /**
3606
3842
  * Runs connected lifecycle behavior on the associated element.
3607
3843
  */
3608
- onConnectedCallback() {
3844
+ connect() {
3609
3845
  if (this._isConnected) {
3610
3846
  return;
3611
3847
  }
3612
- const element = this.element;
3613
- const context = ExecutionContext.default;
3614
3848
  if (this.needsInitialization) {
3615
3849
  this.finishInitialization();
3616
3850
  }
3617
3851
  else if (this.view !== null) {
3618
- this.view.bind(element, context);
3852
+ this.view.bind(this.source);
3619
3853
  }
3620
3854
  const behaviors = this.behaviors;
3621
3855
  if (behaviors !== null) {
3622
- for (const behavior of behaviors.keys()) {
3623
- behavior.bind(element, context);
3856
+ for (const key of behaviors.keys()) {
3857
+ key.connectedCallback && key.connectedCallback(this);
3624
3858
  }
3625
3859
  }
3626
3860
  this.setIsConnected(true);
@@ -3628,21 +3862,18 @@ class Controller extends PropertyChangeNotifier {
3628
3862
  /**
3629
3863
  * Runs disconnected lifecycle behavior on the associated element.
3630
3864
  */
3631
- onDisconnectedCallback() {
3865
+ disconnect() {
3632
3866
  if (!this._isConnected) {
3633
3867
  return;
3634
3868
  }
3635
3869
  this.setIsConnected(false);
3636
- const view = this.view;
3637
- if (view !== null) {
3638
- view.unbind();
3870
+ if (this.view !== null) {
3871
+ this.view.unbind();
3639
3872
  }
3640
3873
  const behaviors = this.behaviors;
3641
3874
  if (behaviors !== null) {
3642
- const element = this.element;
3643
- const context = ExecutionContext.default;
3644
- for (const behavior of behaviors.keys()) {
3645
- behavior.unbind(element, context);
3875
+ for (const key of behaviors.keys()) {
3876
+ key.disconnectedCallback && key.disconnectedCallback(this);
3646
3877
  }
3647
3878
  }
3648
3879
  }
@@ -3655,7 +3886,7 @@ class Controller extends PropertyChangeNotifier {
3655
3886
  onAttributeChangedCallback(name, oldValue, newValue) {
3656
3887
  const attrDef = this.definition.attributeLookup[name];
3657
3888
  if (attrDef !== void 0) {
3658
- attrDef.onAttributeChangedCallback(this.element, newValue);
3889
+ attrDef.onAttributeChangedCallback(this.source, newValue);
3659
3890
  }
3660
3891
  }
3661
3892
  /**
@@ -3668,12 +3899,12 @@ class Controller extends PropertyChangeNotifier {
3668
3899
  */
3669
3900
  emit(type, detail, options) {
3670
3901
  if (this._isConnected) {
3671
- return this.element.dispatchEvent(new CustomEvent(type, Object.assign(Object.assign({ detail }, defaultEventOptions), options)));
3902
+ return this.source.dispatchEvent(new CustomEvent(type, Object.assign(Object.assign({ detail }, defaultEventOptions), options)));
3672
3903
  }
3673
3904
  return false;
3674
3905
  }
3675
3906
  finishInitialization() {
3676
- const element = this.element;
3907
+ const element = this.source;
3677
3908
  const boundObservables = this.boundObservables;
3678
3909
  // If we have any observables that were bound, re-apply their values.
3679
3910
  if (boundObservables !== null) {
@@ -3685,15 +3916,15 @@ class Controller extends PropertyChangeNotifier {
3685
3916
  this.boundObservables = null;
3686
3917
  }
3687
3918
  this.renderTemplate(this.template);
3688
- this.addStyles(this.styles);
3919
+ this.addStyles(this.mainStyles);
3689
3920
  this.needsInitialization = false;
3690
3921
  }
3691
3922
  renderTemplate(template) {
3692
3923
  var _a;
3693
- const element = this.element;
3694
3924
  // When getting the host to render to, we start by looking
3695
3925
  // up the shadow root. If there isn't one, then that means
3696
3926
  // we're doing a Light DOM render to the element's direct children.
3927
+ const element = this.source;
3697
3928
  const host = (_a = getShadowRoot(element)) !== null && _a !== void 0 ? _a : element;
3698
3929
  if (this.view !== null) {
3699
3930
  // If there's already a view, we need to unbind and remove through dispose.
@@ -3710,6 +3941,8 @@ class Controller extends PropertyChangeNotifier {
3710
3941
  if (template) {
3711
3942
  // If a new template was provided, render it.
3712
3943
  this.view = template.render(element, host, element);
3944
+ this.view.sourceLifetime =
3945
+ SourceLifetime.coupled;
3713
3946
  }
3714
3947
  }
3715
3948
  /**
@@ -3729,31 +3962,48 @@ class Controller extends PropertyChangeNotifier {
3729
3962
  if (definition === void 0) {
3730
3963
  throw FAST.error(1401 /* Message.missingElementDefinition */);
3731
3964
  }
3732
- return (element.$fastController = new Controller(element, definition));
3965
+ return (element.$fastController = new ElementController(element, definition));
3733
3966
  }
3734
3967
  }
3735
3968
 
3736
3969
  /* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
3737
3970
  function createFASTElement(BaseType) {
3738
- return class extends BaseType {
3971
+ const type = class extends BaseType {
3739
3972
  constructor() {
3740
3973
  /* eslint-disable-next-line */
3741
3974
  super();
3742
- Controller.forCustomElement(this);
3975
+ ElementController.forCustomElement(this);
3743
3976
  }
3744
3977
  $emit(type, detail, options) {
3745
3978
  return this.$fastController.emit(type, detail, options);
3746
3979
  }
3747
3980
  connectedCallback() {
3748
- this.$fastController.onConnectedCallback();
3981
+ this.$fastController.connect();
3749
3982
  }
3750
3983
  disconnectedCallback() {
3751
- this.$fastController.onDisconnectedCallback();
3984
+ this.$fastController.disconnect();
3752
3985
  }
3753
3986
  attributeChangedCallback(name, oldValue, newValue) {
3754
3987
  this.$fastController.onAttributeChangedCallback(name, oldValue, newValue);
3755
3988
  }
3756
3989
  };
3990
+ FASTElementDefinition.registerBaseType(type);
3991
+ return type;
3992
+ }
3993
+ function compose(type, nameOrDef) {
3994
+ if (isFunction(type)) {
3995
+ return FASTElementDefinition.compose(type, nameOrDef);
3996
+ }
3997
+ return FASTElementDefinition.compose(this, type);
3998
+ }
3999
+ function define(type, nameOrDef) {
4000
+ if (isFunction(type)) {
4001
+ return FASTElementDefinition.compose(type, nameOrDef).define().type;
4002
+ }
4003
+ return FASTElementDefinition.compose(this, type).define().type;
4004
+ }
4005
+ function from(BaseType) {
4006
+ return createFASTElement(BaseType);
3757
4007
  }
3758
4008
  /**
3759
4009
  * A minimal base class for FASTElements that also provides
@@ -3766,18 +4016,19 @@ const FASTElement = Object.assign(createFASTElement(HTMLElement), {
3766
4016
  * provided base type.
3767
4017
  * @param BaseType - The base element type to inherit from.
3768
4018
  */
3769
- from(BaseType) {
3770
- return createFASTElement(BaseType);
3771
- },
4019
+ from,
3772
4020
  /**
3773
4021
  * Defines a platform custom element based on the provided type and definition.
3774
4022
  * @param type - The custom element type to define.
3775
4023
  * @param nameOrDef - The name of the element to define or a definition object
3776
4024
  * that describes the element to define.
3777
4025
  */
3778
- define(type, nameOrDef) {
3779
- return new FASTElementDefinition(type, nameOrDef).define().type;
3780
- },
4026
+ define,
4027
+ /**
4028
+ * Defines metadata for a FASTElement which can be used to later define the element.
4029
+ * @public
4030
+ */
4031
+ compose,
3781
4032
  });
3782
4033
  /**
3783
4034
  * Decorator: Defines a platform custom element based on `FASTElement`.
@@ -3788,8 +4039,8 @@ const FASTElement = Object.assign(createFASTElement(HTMLElement), {
3788
4039
  function customElement(nameOrDef) {
3789
4040
  /* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
3790
4041
  return function (type) {
3791
- new FASTElementDefinition(type, nameOrDef).define();
4042
+ define(type, nameOrDef);
3792
4043
  };
3793
4044
  }
3794
4045
 
3795
- 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, SignalBinding, SlottedDirective, Splice, SpliceStrategy, SpliceStrategySupport, StatelessAttachedAttributeDirective, SubscriberSet, TwoWayBinding, UpdateBinding, Updates, ViewTemplate, attr, bind, booleanConverter, child, children, createTypeRegistry, css, cssDirective, cssPartial, customElement, elements, emptyArray, html, htmlDirective, item, length, nullableNumberConverter, observable, onChange, oneTime, ref, repeat, signal, slotted, twoWay, volatile, when };
4046
+ export { AdoptedStyleSheetsStrategy, ArrayObserver, Aspect, AttributeConfiguration, AttributeDefinition, Binding, CSSDirective, ChildrenDirective, Compiler, DOM, ElementController, ElementStyles, ExecutionContext, FAST, FASTElement, FASTElementDefinition, HTMLBindingDirective, HTMLDirective, HTMLView, Markup, NodeObservationDirective, Observable, Parser, PropertyChangeNotifier, RefDirective, RepeatBehavior, RepeatDirective, SlottedDirective, SourceLifetime, Splice, SpliceStrategy, SpliceStrategySupport, StatelessAttachedAttributeDirective, SubscriberSet, Updates, ViewBehaviorOrchestrator, ViewTemplate, attr, bind, booleanConverter, children, createMetadataLocator, createTypeRegistry, css, cssDirective, cssPartial, customElement, elements, emptyArray, html, htmlDirective, lengthOf, listener, normalizeBinding, nullableNumberConverter, observable, oneTime, ref, repeat, slotted, volatile, when };