@microsoft/fast-element 2.10.2 → 2.10.4

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 (38) hide show
  1. package/ARCHITECTURE_FASTELEMENT.md +1 -1
  2. package/ARCHITECTURE_HTML_TAGGED_TEMPLATE_LITERAL.md +3 -1
  3. package/ARCHITECTURE_INTRO.md +1 -1
  4. package/ARCHITECTURE_OVERVIEW.md +1 -1
  5. package/CHANGELOG.json +138 -1
  6. package/CHANGELOG.md +18 -2
  7. package/DESIGN.md +510 -0
  8. package/biome.json +4 -0
  9. package/dist/context/context.api.json +17 -1
  10. package/dist/di/di.api.json +17 -1
  11. package/dist/dts/hydration/target-builder.d.ts +17 -1
  12. package/dist/dts/templating/html-binding-directive.d.ts +45 -4
  13. package/dist/dts/templating/html-directive.d.ts +7 -1
  14. package/dist/dts/templating/render.d.ts +7 -1
  15. package/dist/dts/templating/template.d.ts +19 -1
  16. package/dist/dts/templating/view.d.ts +11 -0
  17. package/dist/dts/tsdoc-metadata.json +1 -1
  18. package/dist/esm/components/hydration.js +17 -0
  19. package/dist/esm/hydration/target-builder.js +22 -1
  20. package/dist/esm/templating/compiler.js +29 -11
  21. package/dist/esm/templating/html-binding-directive.js +73 -6
  22. package/dist/esm/templating/html-directive.js +7 -1
  23. package/dist/esm/templating/install-hydratable-view-templates.js +6 -0
  24. package/dist/esm/templating/markup.js +8 -0
  25. package/dist/esm/templating/render.js +12 -1
  26. package/dist/esm/templating/template.js +19 -1
  27. package/dist/esm/templating/view.js +13 -0
  28. package/dist/fast-element.api.json +20 -4
  29. package/dist/fast-element.debug.js +202 -230
  30. package/dist/fast-element.debug.min.js +2 -2
  31. package/dist/fast-element.js +202 -230
  32. package/dist/fast-element.min.js +2 -2
  33. package/dist/fast-element.untrimmed.d.ts +89 -7
  34. package/docs/api-report.api.md +4 -4
  35. package/package.json +8 -19
  36. package/tsconfig.api-extractor.json +6 -0
  37. package/.eslintrc.json +0 -19
  38. package/karma.conf.cjs +0 -148
@@ -2296,6 +2296,12 @@ css.partial = (strings, ...values) => {
2296
2296
  return new CSSPartial(styles, behaviors);
2297
2297
  };
2298
2298
 
2299
+ /**
2300
+ * A unique per-session random marker string used to create placeholder tokens in HTML.
2301
+ * Bindings embedded in template literals are replaced with interpolation markers
2302
+ * of the form `fast-xxxxxx{id}fast-xxxxxx` so the compiler can later locate them in the
2303
+ * parsed DOM and associate each marker with its ViewBehaviorFactory.
2304
+ */
2299
2305
  const marker = `fast-${Math.random().toString(36).substring(2, 8)}`;
2300
2306
  const interpolationStart = `${marker}{`;
2301
2307
  const interpolationEnd = `}${marker}`;
@@ -2347,6 +2353,8 @@ const Parser = Object.freeze({
2347
2353
  * directives or null if no directives are found in the string.
2348
2354
  */
2349
2355
  parse(value, factories) {
2356
+ // Split on the interpolation start marker. If there's only one part,
2357
+ // no placeholders exist and we return null to signal "no directives here."
2350
2358
  const parts = value.split(interpolationStart);
2351
2359
  if (parts.length === 1) {
2352
2360
  return null;
@@ -2400,7 +2408,13 @@ const HTMLDirective = Object.freeze({
2400
2408
  return type;
2401
2409
  },
2402
2410
  /**
2403
- *
2411
+ * Determines the DOM aspect type for a directive based on attribute name prefix.
2412
+ * The prefix convention maps to aspect types as follows:
2413
+ * - No prefix (e.g. "class") → DOMAspect.attribute
2414
+ * - ":" prefix (e.g. ":value") → DOMAspect.property (":classList" → DOMAspect.tokenList)
2415
+ * - "?" prefix (e.g. "?disabled") → DOMAspect.booleanAttribute
2416
+ * - `@` prefix (e.g. `@click`) → DOMAspect.event
2417
+ * - Falsy or absent value → DOMAspect.content (see remarks)
2404
2418
  * @param directive - The directive to assign the aspect to.
2405
2419
  * @param value - The value to base the aspect determination on.
2406
2420
  * @remarks
@@ -2625,6 +2639,23 @@ function children(propertyOrOptions) {
2625
2639
  return new ChildrenDirective(propertyOrOptions);
2626
2640
  }
2627
2641
 
2642
+ /**
2643
+ * Regex patterns for parsing hydration markers embedded as HTML comments by the SSR renderer.
2644
+ * Each marker type encodes factory indices so the client can map markers back to ViewBehaviorFactories.
2645
+ *
2646
+ * Content binding markers bracket text/template content:
2647
+ * <!-- fe-b$$start$$<factoryIndex>$$<uniqueId>$$fe-b -->
2648
+ * ...content...
2649
+ * <!-- fe-b$$end$$<factoryIndex>$$<uniqueId>$$fe-b -->
2650
+ *
2651
+ * Repeat markers bracket each repeated item:
2652
+ * <!-- fe-repeat$$start$$<itemIndex>$$fe-repeat -->
2653
+ * <!-- fe-repeat$$end$$<itemIndex>$$fe-repeat -->
2654
+ *
2655
+ * Element boundary markers demarcate nested custom elements so parent walkers can skip them:
2656
+ * <!-- fe-eb$$start$$<elementId>$$fe-eb -->
2657
+ * <!-- fe-eb$$end$$<elementId>$$fe-eb -->
2658
+ */
2628
2659
  const bindingStartMarker = /fe-b\$\$start\$\$(\d+)\$\$(.+)\$\$fe-b/;
2629
2660
  const bindingEndMarker = /fe-b\$\$end\$\$(\d+)\$\$(.+)\$\$fe-b/;
2630
2661
  const repeatViewStartMarker = /fe-repeat\$\$start\$\$(\d+)\$\$fe-repeat/;
@@ -2842,7 +2873,23 @@ function isShadowRoot(node) {
2842
2873
  return node instanceof DocumentFragment && "mode" in node;
2843
2874
  }
2844
2875
  /**
2845
- * Maps {@link CompiledViewBehaviorFactory} ids to the corresponding node targets for the view.
2876
+ * Maps compiled ViewBehaviorFactory IDs to their corresponding DOM nodes in the
2877
+ * server-rendered shadow root. Uses a TreeWalker to scan the existing DOM between
2878
+ * firstNode and lastNode, parsing hydration markers to build the targets map.
2879
+ *
2880
+ * For element nodes: parses `data-fe-b` (or variant) attributes to identify which
2881
+ * factories target each element, then removes the marker attribute.
2882
+ *
2883
+ * For comment nodes: parses content binding markers (`fe-b$$start/end$$`) to find
2884
+ * the DOM range controlled by each content binding. Single text nodes become the
2885
+ * direct target; multi-node ranges are stored in boundaries for structural directives.
2886
+ * Element boundary markers (`fe-eb$$start/end$$`) cause the walker to skip over
2887
+ * nested custom elements that handle their own hydration.
2888
+ *
2889
+ * Host bindings (targetNodeId='h') appear at the start of the factories array but
2890
+ * have no SSR markers — getHydrationIndexOffset() computes how many to skip so that
2891
+ * marker indices align with the correct non-host factories.
2892
+ *
2846
2893
  * @param firstNode - The first node of the view.
2847
2894
  * @param lastNode - The last node of the view.
2848
2895
  * @param factories - The Compiled View Behavior Factories that belong to the view.
@@ -2968,6 +3015,11 @@ function skipToElementBoundaryEndMarker(node, walker) {
2968
3015
  current = walker.nextSibling();
2969
3016
  }
2970
3017
  }
3018
+ /**
3019
+ * Counts how many factories at the start of the array are host bindings (targetNodeId='h').
3020
+ * Host bindings target the custom element itself and are not represented by SSR markers,
3021
+ * so the marker indices must be offset by this count to align with the correct factory.
3022
+ */
2971
3023
  function getHydrationIndexOffset(factories) {
2972
3024
  let offset = 0;
2973
3025
  for (let i = 0, ii = factories.length; i < ii; ++i) {
@@ -3163,6 +3215,17 @@ class HTMLView extends DefaultExecutionContext {
3163
3215
  }
3164
3216
  /**
3165
3217
  * Binds a view's behaviors to its binding source.
3218
+ *
3219
+ * On the first call, this iterates through all compiled factories, calling
3220
+ * createBehavior() on each to produce a ViewBehavior instance (e.g., an
3221
+ * HTMLBindingDirective), and then immediately binds it. This is where event
3222
+ * listeners are registered, expression observers are created, and initial
3223
+ * DOM values are set.
3224
+ *
3225
+ * On subsequent calls with a new source, existing behaviors are re-bound
3226
+ * to the new data source, which re-evaluates all binding expressions and
3227
+ * updates the DOM accordingly.
3228
+ *
3166
3229
  * @param source - The binding source for the view's binding behaviors.
3167
3230
  * @param context - The execution context to run the behaviors within.
3168
3231
  */
@@ -3172,6 +3235,8 @@ class HTMLView extends DefaultExecutionContext {
3172
3235
  }
3173
3236
  let behaviors = this.behaviors;
3174
3237
  if (behaviors === null) {
3238
+ // First bind: create behaviors from factories and bind each one.
3239
+ // The view (this) acts as the ViewController, providing targets and source.
3175
3240
  this.source = source;
3176
3241
  this.context = context;
3177
3242
  this.behaviors = behaviors = new Array(this.factories.length);
@@ -3463,6 +3528,14 @@ makeSerializationNoop(HydrationView);
3463
3528
  function isContentTemplate(value) {
3464
3529
  return value.create !== undefined;
3465
3530
  }
3531
+ /**
3532
+ * Sink function for DOMAspect.content bindings (text content interpolation).
3533
+ * Handles two cases:
3534
+ * - If the value is a ContentTemplate (has a create() method), it composes a child
3535
+ * view into the DOM, managing view lifecycle (create/reuse/remove/bind).
3536
+ * - If the value is a primitive, it sets target.textContent directly, first removing
3537
+ * any previously composed view.
3538
+ */
3466
3539
  function updateContent(target, aspect, value, controller) {
3467
3540
  // If there's no actual value, then this equates to the
3468
3541
  // empty string for the purposes of content bindings.
@@ -3531,6 +3604,12 @@ function updateContent(target, aspect, value, controller) {
3531
3604
  target.textContent = value;
3532
3605
  }
3533
3606
  }
3607
+ /**
3608
+ * Sink function for DOMAspect.tokenList bindings (e.g., :classList).
3609
+ * Uses a versioning scheme to efficiently track which CSS classes were added
3610
+ * in the current update vs. the previous one. Classes from the previous version
3611
+ * that aren't present in the new value are automatically removed.
3612
+ */
3534
3613
  function updateTokenList(target, aspect, value) {
3535
3614
  var _a;
3536
3615
  const lookup = `${this.id}-t`;
@@ -3563,6 +3642,12 @@ function updateTokenList(target, aspect, value) {
3563
3642
  }
3564
3643
  }
3565
3644
  }
3645
+ /**
3646
+ * Maps each DOMAspect type to its corresponding DOM update ("sink") function.
3647
+ * When a binding value changes, the sink function for the binding's aspect type
3648
+ * is called to push the new value into the DOM. Events are handled separately
3649
+ * via addEventListener in bind(), so the event sink is a no-op.
3650
+ */
3566
3651
  const sinkLookup = {
3567
3652
  [DOMAspect.attribute]: DOM.setAttribute,
3568
3653
  [DOMAspect.booleanAttribute]: DOM.setBooleanAttribute,
@@ -3572,7 +3657,18 @@ const sinkLookup = {
3572
3657
  [DOMAspect.event]: () => void 0,
3573
3658
  };
3574
3659
  /**
3575
- * A directive that applies bindings.
3660
+ * The central binding directive that bridges data expressions and DOM updates.
3661
+ *
3662
+ * HTMLBindingDirective fulfills three roles simultaneously:
3663
+ * - **HTMLDirective**: Produces placeholder HTML via createHTML() during template authoring.
3664
+ * - **ViewBehaviorFactory**: Creates behaviors (returns itself) during view creation.
3665
+ * - **ViewBehavior / EventListener**: Attaches to a DOM node during bind, manages
3666
+ * expression observers for reactive updates, and handles DOM events directly.
3667
+ *
3668
+ * The aspectType (set by HTMLDirective.assignAspect during template processing)
3669
+ * determines which DOM "sink" function is used to apply values — e.g.,
3670
+ * setAttribute for attributes, addEventListener for events, textContent for content.
3671
+ *
3576
3672
  * @public
3577
3673
  */
3578
3674
  class HTMLBindingDirective {
@@ -3611,7 +3707,18 @@ class HTMLBindingDirective {
3611
3707
  }
3612
3708
  return this;
3613
3709
  }
3614
- /** @internal */
3710
+ /**
3711
+ * Attaches this binding to its target DOM node.
3712
+ * - For events: stores the controller reference on the target element and registers
3713
+ * this directive as the EventListener via addEventListener. The directive's
3714
+ * handleEvent() method will be called when the event fires.
3715
+ * - For content bindings: registers an unbind handler, then falls through to the
3716
+ * default path.
3717
+ * - For all non-event bindings: creates (or reuses) an ExpressionObserver, evaluates
3718
+ * the binding expression, and applies the result to the DOM via the updateTarget
3719
+ * sink function. The observer will call handleChange() on future data changes.
3720
+ * @internal
3721
+ */
3615
3722
  bind(controller) {
3616
3723
  var _a;
3617
3724
  const target = controller.targets[this.targetNodeId];
@@ -3626,7 +3733,7 @@ class HTMLBindingDirective {
3626
3733
  case DOMAspect.content:
3627
3734
  controller.onUnbind(this);
3628
3735
  // intentional fall through
3629
- default:
3736
+ default: {
3630
3737
  const observer = (_a = target[this.data]) !== null && _a !== void 0 ? _a : (target[this.data] = this.dataBinding.createObserver(this, this));
3631
3738
  observer.target = target;
3632
3739
  observer.controller = controller;
@@ -3639,6 +3746,7 @@ class HTMLBindingDirective {
3639
3746
  }
3640
3747
  this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
3641
3748
  break;
3749
+ }
3642
3750
  }
3643
3751
  }
3644
3752
  /** @internal */
@@ -3650,7 +3758,14 @@ class HTMLBindingDirective {
3650
3758
  view.needsBindOnly = true;
3651
3759
  }
3652
3760
  }
3653
- /** @internal */
3761
+ /**
3762
+ * Implements the EventListener interface. When a DOM event fires on the target
3763
+ * element, this method retrieves the ViewController stored on the element,
3764
+ * sets the event on the ExecutionContext so `c.event` is available to the
3765
+ * binding expression, and evaluates the expression. If the expression returns
3766
+ * anything other than `true`, the event's default action is prevented.
3767
+ * @internal
3768
+ */
3654
3769
  handleEvent(event) {
3655
3770
  const controller = event.currentTarget[this.data];
3656
3771
  if (controller.isBound) {
@@ -3662,15 +3777,38 @@ class HTMLBindingDirective {
3662
3777
  }
3663
3778
  }
3664
3779
  }
3665
- /** @internal */
3780
+ /**
3781
+ * Called by the ExpressionObserver when a tracked dependency changes.
3782
+ * Re-evaluates the binding expression via observer.bind() and pushes
3783
+ * the new value to the DOM through the updateTarget sink function.
3784
+ * This is the reactive update path that keeps the DOM in sync with data.
3785
+ *
3786
+ * Guards against stale notifications: when a view is unbound (e.g., after
3787
+ * a parent `when` directive tears down a child element), coupled-lifetime
3788
+ * observers may still hold active subscriptions. If a property change fires
3789
+ * on the source element while the view is inactive, this guard prevents
3790
+ * the binding expression from evaluating with a null source.
3791
+ * @internal
3792
+ */
3666
3793
  handleChange(binding, observer) {
3667
- const target = observer.target;
3668
3794
  const controller = observer.controller;
3795
+ // https://github.com/microsoft/fast/issues/7444
3796
+ // This guard will be reconsidered in the next major version.
3797
+ if (!controller.isBound) {
3798
+ return;
3799
+ }
3800
+ const target = observer.target;
3669
3801
  this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
3670
3802
  }
3671
3803
  }
3672
3804
  HTMLDirective.define(HTMLBindingDirective, { aspected: true });
3673
3805
 
3806
+ /**
3807
+ * Builds a hierarchical node ID by appending the child index to the parent's ID.
3808
+ * For example, the third child of root is "r.2", and its first child is "r.2.0".
3809
+ * These IDs are used as property names on the targets prototype so that each
3810
+ * binding's target DOM node can be lazily resolved via a chain of childNodes lookups.
3811
+ */
3674
3812
  const targetIdFrom = (parentId, nodeIndex) => `${parentId}.${nodeIndex}`;
3675
3813
  const descriptorCache = {};
3676
3814
  // used to prevent creating lots of objects just to track node and index while compiling
@@ -3720,6 +3858,13 @@ class CompilationContext {
3720
3858
  this.proto = Object.create(null, this.descriptors);
3721
3859
  return this;
3722
3860
  }
3861
+ /**
3862
+ * Registers a lazy getter on the targets prototype that resolves a DOM node
3863
+ * by navigating from its parent's childNodes at the given index. Getters are
3864
+ * chained: accessing targets["r.0.2"] first resolves targets["r.0"] (which
3865
+ * resolves targets["r"]), then returns childNodes[2]. Results are cached so
3866
+ * each node is resolved at most once per view instance.
3867
+ */
3723
3868
  addTargetDescriptor(parentId, targetId, targetIndex) {
3724
3869
  const descriptors = this.descriptors;
3725
3870
  if (targetId === "r" || // root
@@ -3730,7 +3875,7 @@ class CompilationContext {
3730
3875
  if (!descriptors[parentId]) {
3731
3876
  const index = parentId.lastIndexOf(".");
3732
3877
  const grandparentId = parentId.substring(0, index);
3733
- const childIndex = parseInt(parentId.substring(index + 1));
3878
+ const childIndex = parseInt(parentId.substring(index + 1), 10);
3734
3879
  this.addTargetDescriptor(grandparentId, parentId, childIndex);
3735
3880
  }
3736
3881
  let descriptor = descriptorCache[targetId];
@@ -3745,13 +3890,20 @@ class CompilationContext {
3745
3890
  }
3746
3891
  descriptors[targetId] = descriptor;
3747
3892
  }
3893
+ /**
3894
+ * Creates a new HTMLView by cloning the compiled DocumentFragment and building
3895
+ * a targets object. The targets prototype contains lazy getters that resolve
3896
+ * each binding's target DOM node via childNodes traversal. Accessing every
3897
+ * registered nodeId eagerly triggers the getter chain so all nodes are resolved
3898
+ * before behaviors are bound.
3899
+ */
3748
3900
  createView(hostBindingTarget) {
3749
3901
  const fragment = this.fragment.cloneNode(true);
3750
3902
  const targets = Object.create(this.proto);
3751
- targets.r = fragment;
3752
- targets.h = hostBindingTarget !== null && hostBindingTarget !== void 0 ? hostBindingTarget : warningHost;
3903
+ targets.r = fragment; // root — the cloned DocumentFragment
3904
+ targets.h = hostBindingTarget !== null && hostBindingTarget !== void 0 ? hostBindingTarget : warningHost; // host — the custom element
3753
3905
  for (const id of this.nodeIds) {
3754
- targets[id]; // trigger locator
3906
+ Reflect.get(targets, id); // trigger lazy getter to resolve and cache the DOM node
3755
3907
  }
3756
3908
  return new HTMLView(fragment, this.factories, targets);
3757
3909
  }
@@ -3771,7 +3923,6 @@ function compileAttributes(context, parentId, node, nodeId, nodeIndex, includeBa
3771
3923
  }
3772
3924
  }
3773
3925
  else {
3774
- /* eslint-disable-next-line @typescript-eslint/no-use-before-define */
3775
3926
  result = Compiler.aggregate(parseResult, context.policy);
3776
3927
  }
3777
3928
  if (result !== null) {
@@ -3816,7 +3967,6 @@ function compileChildren(context, parent, parentId) {
3816
3967
  let nodeIndex = 0;
3817
3968
  let childNode = parent.firstChild;
3818
3969
  while (childNode) {
3819
- /* eslint-disable-next-line @typescript-eslint/no-use-before-define */
3820
3970
  const result = compileNode(context, parentId, childNode, nodeIndex);
3821
3971
  childNode = result.node;
3822
3972
  nodeIndex = result.index;
@@ -3831,14 +3981,14 @@ function compileNode(context, parentId, node, nodeIndex) {
3831
3981
  break;
3832
3982
  case 3: // text node
3833
3983
  return compileContent(context, node, parentId, nodeId, nodeIndex);
3834
- case 8: // comment
3984
+ case 8: {
3985
+ // comment
3835
3986
  const parts = Parser.parse(node.data, context.directives);
3836
3987
  if (parts !== null) {
3837
- context.addFactory(
3838
- /* eslint-disable-next-line @typescript-eslint/no-use-before-define */
3839
- Compiler.aggregate(parts), parentId, nodeId, nodeIndex, null);
3988
+ context.addFactory(Compiler.aggregate(parts), parentId, nodeId, nodeIndex, null);
3840
3989
  }
3841
3990
  break;
3991
+ }
3842
3992
  }
3843
3993
  next.index = nodeIndex + 1;
3844
3994
  next.node = node.nextSibling;
@@ -3846,7 +3996,7 @@ function compileNode(context, parentId, node, nodeIndex) {
3846
3996
  }
3847
3997
  function isMarker(node, directives) {
3848
3998
  return (node &&
3849
- node.nodeType == 8 &&
3999
+ node.nodeType === 8 &&
3850
4000
  Parser.parse(node.data, directives) !== null);
3851
4001
  }
3852
4002
  const templateTag = "TEMPLATE";
@@ -3983,6 +4133,8 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
3983
4133
  OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
3984
4134
  PERFORMANCE OF THIS SOFTWARE.
3985
4135
  ***************************************************************************** */
4136
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
4137
+
3986
4138
 
3987
4139
  function __awaiter(thisArg, _arguments, P, generator) {
3988
4140
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
@@ -4487,7 +4639,25 @@ class ViewTemplate {
4487
4639
  return view;
4488
4640
  }
4489
4641
  /**
4490
- * Creates a template based on a set of static strings and dynamic values.
4642
+ * Processes the tagged template literal's static strings and interpolated values and
4643
+ * creates a ViewTemplate.
4644
+ *
4645
+ * For each interpolated value:
4646
+ * 1. Functions (binding expressions, e.g., `x => x.name`) → wrapped in a one-way HTMLBindingDirective
4647
+ * 2. Binding instances → wrapped in an HTMLBindingDirective
4648
+ * 3. HTMLDirective instances → used as-is
4649
+ * 4. Static values (strings, numbers) → wrapped in a one-time HTMLBindingDirective
4650
+ *
4651
+ * Each directive's createHTML() is called with an `add` callback that registers
4652
+ * the factory in the factories record under a unique ID and returns that ID.
4653
+ * The directive inserts a placeholder marker (e.g., `fast-abc123{0}fast-abc123`) into
4654
+ * the HTML string so the compiler can later find and associate it with the factory.
4655
+ *
4656
+ * Aspect detection happens here too: the `lastAttributeNameRegex` checks whether
4657
+ * the placeholder appears inside an attribute value, and if so, assignAspect()
4658
+ * sets the correct DOMAspect (attribute, property, event, etc.) based on the
4659
+ * attribute name prefix.
4660
+ *
4491
4661
  * @param strings - The static strings to create the template with.
4492
4662
  * @param values - The dynamic values to create the template with.
4493
4663
  * @param policy - The DOMPolicy to associated with the template.
@@ -4601,8 +4771,19 @@ class RenderBehavior {
4601
4771
  view.needsBindOnly = true;
4602
4772
  }
4603
4773
  }
4604
- /** @internal */
4774
+ /**
4775
+ * Handles changes from data or template binding observers.
4776
+ * Guards against stale notifications that may arrive after the
4777
+ * controller's view has been unbound (e.g., when a parent `when`
4778
+ * directive tears down and later recreates a child element).
4779
+ * @internal
4780
+ */
4605
4781
  handleChange(source, observer) {
4782
+ // https://github.com/microsoft/fast/issues/7444
4783
+ // This guard will be reconsidered in the next major version.
4784
+ if (!this.controller.isBound) {
4785
+ return;
4786
+ }
4606
4787
  if (observer === this.dataBindingObserver) {
4607
4788
  this.data = this.dataBindingObserver.bind(this.controller);
4608
4789
  }
@@ -4683,13 +4864,7 @@ class RenderDirective {
4683
4864
  }
4684
4865
  }
4685
4866
  HTMLDirective.define(RenderDirective);
4686
- function isElementRenderOptions(object) {
4687
- return !!object.element || !!object.tagName;
4688
- }
4689
4867
  const typeToInstructionLookup = new Map();
4690
- /* eslint @typescript-eslint/naming-convention: "off"*/
4691
- const defaultAttributes = { ":model": (x) => x };
4692
- const brand = Symbol("RenderInstruction");
4693
4868
  const defaultViewName = "default-view";
4694
4869
  const nullTemplate = html `
4695
4870
  &nbsp;
@@ -4700,87 +4875,6 @@ function instructionToTemplate(def) {
4700
4875
  }
4701
4876
  return def.template;
4702
4877
  }
4703
- function createElementTemplate(tagName, options) {
4704
- const markup = [];
4705
- const values = [];
4706
- const { attributes, directives, content, policy } = options !== null && options !== void 0 ? options : {};
4707
- markup.push(`<${tagName}`);
4708
- if (attributes) {
4709
- const attrNames = Object.getOwnPropertyNames(attributes);
4710
- for (let i = 0, ii = attrNames.length; i < ii; ++i) {
4711
- const name = attrNames[i];
4712
- if (i === 0) {
4713
- markup[0] = `${markup[0]} ${name}="`;
4714
- }
4715
- else {
4716
- markup.push(`" ${name}="`);
4717
- }
4718
- values.push(attributes[name]);
4719
- }
4720
- markup.push(`"`);
4721
- }
4722
- if (directives) {
4723
- markup[markup.length - 1] += " ";
4724
- for (let i = 0, ii = directives.length; i < ii; ++i) {
4725
- const directive = directives[i];
4726
- markup.push(i > 0 ? "" : " ");
4727
- values.push(directive);
4728
- }
4729
- }
4730
- markup[markup.length - 1] += ">";
4731
- if (content && isFunction(content.create)) {
4732
- values.push(content);
4733
- markup.push(`</${tagName}>`);
4734
- }
4735
- else {
4736
- const lastIndex = markup.length - 1;
4737
- markup[lastIndex] = `${markup[lastIndex]}${content !== null && content !== void 0 ? content : ""}</${tagName}>`;
4738
- }
4739
- return ViewTemplate.create(markup, values, policy);
4740
- }
4741
- function create(options) {
4742
- var _a;
4743
- const name = (_a = options.name) !== null && _a !== void 0 ? _a : defaultViewName;
4744
- let template;
4745
- if (isElementRenderOptions(options)) {
4746
- let tagName = options.tagName;
4747
- if (!tagName) {
4748
- const def = FASTElementDefinition.getByType(options.element);
4749
- if (def) {
4750
- tagName = def.name;
4751
- }
4752
- else {
4753
- throw new Error("Invalid element for model rendering.");
4754
- }
4755
- }
4756
- if (!options.attributes) {
4757
- options.attributes = defaultAttributes;
4758
- }
4759
- template = createElementTemplate(tagName, options);
4760
- }
4761
- else {
4762
- template = options.template;
4763
- }
4764
- return {
4765
- brand,
4766
- type: options.type,
4767
- name,
4768
- template,
4769
- };
4770
- }
4771
- function instanceOf(object) {
4772
- return object && object.brand === brand;
4773
- }
4774
- function register(optionsOrInstruction) {
4775
- let lookup = typeToInstructionLookup.get(optionsOrInstruction.type);
4776
- if (lookup === void 0) {
4777
- typeToInstructionLookup.set(optionsOrInstruction.type, (lookup = Object.create(null)));
4778
- }
4779
- const instruction = instanceOf(optionsOrInstruction)
4780
- ? optionsOrInstruction
4781
- : create(optionsOrInstruction);
4782
- return (lookup[instruction.name] = instruction);
4783
- }
4784
4878
  function getByType(type, name) {
4785
4879
  const entries = typeToInstructionLookup.get(type);
4786
4880
  if (entries === void 0) {
@@ -4794,63 +4888,6 @@ function getForInstance(object, name) {
4794
4888
  }
4795
4889
  return void 0;
4796
4890
  }
4797
- /**
4798
- * Provides APIs for creating and interacting with render instructions.
4799
- * @public
4800
- */
4801
- Object.freeze({
4802
- /**
4803
- * Checks whether the provided object is a RenderInstruction.
4804
- * @param object - The object to check.
4805
- * @returns true if the object is a RenderInstruction; false otherwise
4806
- */
4807
- instanceOf,
4808
- /**
4809
- * Creates a RenderInstruction for a set of options.
4810
- * @param options - The options to use when creating the RenderInstruction.
4811
- * @remarks
4812
- * This API should be used with caution. When providing attributes or content,
4813
- * if not done properly, you can open up the application to XSS attacks. When using this API,
4814
- * provide a strong DOMPolicy that can properly sanitize and also be sure to manually sanitize
4815
- * content and attribute values particularly if they can come from user input.
4816
- */
4817
- create,
4818
- /**
4819
- * Creates a template based on a tag name.
4820
- * @param tagName - The tag name to use when creating the template.
4821
- * @param attributes - The attributes to apply to the element.
4822
- * @param content - The content to insert into the element.
4823
- * @param policy - The DOMPolicy to create the template with.
4824
- * @returns A template based on the provided specifications.
4825
- * @remarks
4826
- * This API should be used with caution. When providing attributes or content,
4827
- * if not done properly, you can open up the application to XSS attacks. When using this API,
4828
- * provide a strong DOMPolicy that can properly sanitize and also be sure to manually sanitize
4829
- * content and attribute values particularly if they can come from user input.
4830
- */
4831
- createElementTemplate,
4832
- /**
4833
- * Creates and registers an instruction.
4834
- * @param options The options to use when creating the RenderInstruction.
4835
- * @remarks
4836
- * A previously created RenderInstruction can also be registered.
4837
- */
4838
- register,
4839
- /**
4840
- * Finds a previously registered RenderInstruction by type and optionally by name.
4841
- * @param type - The type to retrieve the RenderInstruction for.
4842
- * @param name - An optional name used in differentiating between multiple registered instructions.
4843
- * @returns The located RenderInstruction that matches the criteria or undefined if none is found.
4844
- */
4845
- getByType,
4846
- /**
4847
- * Finds a previously registered RenderInstruction for the instance's type and optionally by name.
4848
- * @param object - The instance to retrieve the RenderInstruction for.
4849
- * @param name - An optional name used in differentiating between multiple registered instructions.
4850
- * @returns The located RenderInstruction that matches the criteria or undefined if none is found.
4851
- */
4852
- getForInstance,
4853
- });
4854
4891
  /**
4855
4892
  * @internal
4856
4893
  */
@@ -5441,71 +5478,6 @@ class UnobservableMutationObserver extends MutationObserver {
5441
5478
  }
5442
5479
  }
5443
5480
  }
5444
- /**
5445
- * Bridges between ViewBehaviors and HostBehaviors, enabling a host to
5446
- * control ViewBehaviors.
5447
- * @public
5448
- */
5449
- Object.freeze({
5450
- /**
5451
- * Creates a ViewBehaviorOrchestrator.
5452
- * @param source - The source to to associate behaviors with.
5453
- * @returns A ViewBehaviorOrchestrator.
5454
- */
5455
- create(source) {
5456
- const behaviors = [];
5457
- const targets = {};
5458
- let unbindables = null;
5459
- let isConnected = false;
5460
- return {
5461
- source,
5462
- context: ExecutionContext.default,
5463
- targets,
5464
- get isBound() {
5465
- return isConnected;
5466
- },
5467
- addBehaviorFactory(factory, target) {
5468
- var _a, _b, _c, _d;
5469
- const compiled = factory;
5470
- compiled.id = (_a = compiled.id) !== null && _a !== void 0 ? _a : nextId();
5471
- compiled.targetNodeId = (_b = compiled.targetNodeId) !== null && _b !== void 0 ? _b : nextId();
5472
- compiled.targetTagName = (_c = target.tagName) !== null && _c !== void 0 ? _c : null;
5473
- compiled.policy = (_d = compiled.policy) !== null && _d !== void 0 ? _d : DOM.policy;
5474
- this.addTarget(compiled.targetNodeId, target);
5475
- this.addBehavior(compiled.createBehavior());
5476
- },
5477
- addTarget(nodeId, target) {
5478
- targets[nodeId] = target;
5479
- },
5480
- addBehavior(behavior) {
5481
- behaviors.push(behavior);
5482
- if (isConnected) {
5483
- behavior.bind(this);
5484
- }
5485
- },
5486
- onUnbind(unbindable) {
5487
- if (unbindables === null) {
5488
- unbindables = [];
5489
- }
5490
- unbindables.push(unbindable);
5491
- },
5492
- connectedCallback(controller) {
5493
- if (!isConnected) {
5494
- isConnected = true;
5495
- behaviors.forEach(x => x.bind(this));
5496
- }
5497
- },
5498
- disconnectedCallback(controller) {
5499
- if (isConnected) {
5500
- isConnected = false;
5501
- if (unbindables !== null) {
5502
- unbindables.forEach(x => x.unbind(this));
5503
- }
5504
- }
5505
- },
5506
- };
5507
- },
5508
- });
5509
5481
 
5510
5482
  const defaultEventOptions = {
5511
5483
  bubbles: true,