@microsoft/fast-element 2.10.1 → 2.10.3

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 (41) 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 +153 -1
  6. package/CHANGELOG.md +18 -2
  7. package/DESIGN.md +506 -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 +39 -4
  13. package/dist/dts/templating/html-directive.d.ts +7 -1
  14. package/dist/dts/templating/template.d.ts +19 -1
  15. package/dist/dts/templating/view.d.ts +11 -0
  16. package/dist/dts/testing/models.d.ts +20 -0
  17. package/dist/dts/tsdoc-metadata.json +1 -1
  18. package/dist/esm/components/element-controller.js +3 -0
  19. package/dist/esm/components/hydration.js +17 -0
  20. package/dist/esm/hydration/target-builder.js +22 -1
  21. package/dist/esm/templating/compiler.js +29 -11
  22. package/dist/esm/templating/html-binding-directive.js +59 -4
  23. package/dist/esm/templating/html-directive.js +7 -1
  24. package/dist/esm/templating/install-hydratable-view-templates.js +6 -0
  25. package/dist/esm/templating/markup.js +8 -0
  26. package/dist/esm/templating/template.js +19 -1
  27. package/dist/esm/templating/view.js +13 -0
  28. package/dist/esm/testing/models.js +58 -0
  29. package/dist/fast-element.api.json +20 -4
  30. package/dist/fast-element.debug.js +179 -227
  31. package/dist/fast-element.debug.min.js +2 -2
  32. package/dist/fast-element.js +179 -227
  33. package/dist/fast-element.min.js +2 -2
  34. package/dist/fast-element.untrimmed.d.ts +76 -6
  35. package/docs/api-report.api.md +3 -3
  36. package/package.json +7 -19
  37. package/playwright.config.ts +8 -0
  38. package/test/main.ts +95 -1
  39. package/tsconfig.api-extractor.json +6 -0
  40. package/.eslintrc.json +0 -19
  41. package/karma.conf.cjs +0 -148
@@ -2366,6 +2366,12 @@ css.partial = (strings, ...values) => {
2366
2366
  return new CSSPartial(styles, behaviors);
2367
2367
  };
2368
2368
 
2369
+ /**
2370
+ * A unique per-session random marker string used to create placeholder tokens in HTML.
2371
+ * Bindings embedded in template literals are replaced with interpolation markers
2372
+ * of the form `fast-xxxxxx{id}fast-xxxxxx` so the compiler can later locate them in the
2373
+ * parsed DOM and associate each marker with its ViewBehaviorFactory.
2374
+ */
2369
2375
  const marker = `fast-${Math.random().toString(36).substring(2, 8)}`;
2370
2376
  const interpolationStart = `${marker}{`;
2371
2377
  const interpolationEnd = `}${marker}`;
@@ -2417,6 +2423,8 @@ const Parser = Object.freeze({
2417
2423
  * directives or null if no directives are found in the string.
2418
2424
  */
2419
2425
  parse(value, factories) {
2426
+ // Split on the interpolation start marker. If there's only one part,
2427
+ // no placeholders exist and we return null to signal "no directives here."
2420
2428
  const parts = value.split(interpolationStart);
2421
2429
  if (parts.length === 1) {
2422
2430
  return null;
@@ -2470,7 +2478,13 @@ const HTMLDirective = Object.freeze({
2470
2478
  return type;
2471
2479
  },
2472
2480
  /**
2473
- *
2481
+ * Determines the DOM aspect type for a directive based on attribute name prefix.
2482
+ * The prefix convention maps to aspect types as follows:
2483
+ * - No prefix (e.g. "class") → DOMAspect.attribute
2484
+ * - ":" prefix (e.g. ":value") → DOMAspect.property (":classList" → DOMAspect.tokenList)
2485
+ * - "?" prefix (e.g. "?disabled") → DOMAspect.booleanAttribute
2486
+ * - `@` prefix (e.g. `@click`) → DOMAspect.event
2487
+ * - Falsy or absent value → DOMAspect.content (see remarks)
2474
2488
  * @param directive - The directive to assign the aspect to.
2475
2489
  * @param value - The value to base the aspect determination on.
2476
2490
  * @remarks
@@ -2695,6 +2709,23 @@ function children(propertyOrOptions) {
2695
2709
  return new ChildrenDirective(propertyOrOptions);
2696
2710
  }
2697
2711
 
2712
+ /**
2713
+ * Regex patterns for parsing hydration markers embedded as HTML comments by the SSR renderer.
2714
+ * Each marker type encodes factory indices so the client can map markers back to ViewBehaviorFactories.
2715
+ *
2716
+ * Content binding markers bracket text/template content:
2717
+ * <!-- fe-b$$start$$<factoryIndex>$$<uniqueId>$$fe-b -->
2718
+ * ...content...
2719
+ * <!-- fe-b$$end$$<factoryIndex>$$<uniqueId>$$fe-b -->
2720
+ *
2721
+ * Repeat markers bracket each repeated item:
2722
+ * <!-- fe-repeat$$start$$<itemIndex>$$fe-repeat -->
2723
+ * <!-- fe-repeat$$end$$<itemIndex>$$fe-repeat -->
2724
+ *
2725
+ * Element boundary markers demarcate nested custom elements so parent walkers can skip them:
2726
+ * <!-- fe-eb$$start$$<elementId>$$fe-eb -->
2727
+ * <!-- fe-eb$$end$$<elementId>$$fe-eb -->
2728
+ */
2698
2729
  const bindingStartMarker = /fe-b\$\$start\$\$(\d+)\$\$(.+)\$\$fe-b/;
2699
2730
  const bindingEndMarker = /fe-b\$\$end\$\$(\d+)\$\$(.+)\$\$fe-b/;
2700
2731
  const repeatViewStartMarker = /fe-repeat\$\$start\$\$(\d+)\$\$fe-repeat/;
@@ -2912,7 +2943,23 @@ function isShadowRoot(node) {
2912
2943
  return node instanceof DocumentFragment && "mode" in node;
2913
2944
  }
2914
2945
  /**
2915
- * Maps {@link CompiledViewBehaviorFactory} ids to the corresponding node targets for the view.
2946
+ * Maps compiled ViewBehaviorFactory IDs to their corresponding DOM nodes in the
2947
+ * server-rendered shadow root. Uses a TreeWalker to scan the existing DOM between
2948
+ * firstNode and lastNode, parsing hydration markers to build the targets map.
2949
+ *
2950
+ * For element nodes: parses `data-fe-b` (or variant) attributes to identify which
2951
+ * factories target each element, then removes the marker attribute.
2952
+ *
2953
+ * For comment nodes: parses content binding markers (`fe-b$$start/end$$`) to find
2954
+ * the DOM range controlled by each content binding. Single text nodes become the
2955
+ * direct target; multi-node ranges are stored in boundaries for structural directives.
2956
+ * Element boundary markers (`fe-eb$$start/end$$`) cause the walker to skip over
2957
+ * nested custom elements that handle their own hydration.
2958
+ *
2959
+ * Host bindings (targetNodeId='h') appear at the start of the factories array but
2960
+ * have no SSR markers — getHydrationIndexOffset() computes how many to skip so that
2961
+ * marker indices align with the correct non-host factories.
2962
+ *
2916
2963
  * @param firstNode - The first node of the view.
2917
2964
  * @param lastNode - The last node of the view.
2918
2965
  * @param factories - The Compiled View Behavior Factories that belong to the view.
@@ -3038,6 +3085,11 @@ function skipToElementBoundaryEndMarker(node, walker) {
3038
3085
  current = walker.nextSibling();
3039
3086
  }
3040
3087
  }
3088
+ /**
3089
+ * Counts how many factories at the start of the array are host bindings (targetNodeId='h').
3090
+ * Host bindings target the custom element itself and are not represented by SSR markers,
3091
+ * so the marker indices must be offset by this count to align with the correct factory.
3092
+ */
3041
3093
  function getHydrationIndexOffset(factories) {
3042
3094
  let offset = 0;
3043
3095
  for (let i = 0, ii = factories.length; i < ii; ++i) {
@@ -3233,6 +3285,17 @@ class HTMLView extends DefaultExecutionContext {
3233
3285
  }
3234
3286
  /**
3235
3287
  * Binds a view's behaviors to its binding source.
3288
+ *
3289
+ * On the first call, this iterates through all compiled factories, calling
3290
+ * createBehavior() on each to produce a ViewBehavior instance (e.g., an
3291
+ * HTMLBindingDirective), and then immediately binds it. This is where event
3292
+ * listeners are registered, expression observers are created, and initial
3293
+ * DOM values are set.
3294
+ *
3295
+ * On subsequent calls with a new source, existing behaviors are re-bound
3296
+ * to the new data source, which re-evaluates all binding expressions and
3297
+ * updates the DOM accordingly.
3298
+ *
3236
3299
  * @param source - The binding source for the view's binding behaviors.
3237
3300
  * @param context - The execution context to run the behaviors within.
3238
3301
  */
@@ -3242,6 +3305,8 @@ class HTMLView extends DefaultExecutionContext {
3242
3305
  }
3243
3306
  let behaviors = this.behaviors;
3244
3307
  if (behaviors === null) {
3308
+ // First bind: create behaviors from factories and bind each one.
3309
+ // The view (this) acts as the ViewController, providing targets and source.
3245
3310
  this.source = source;
3246
3311
  this.context = context;
3247
3312
  this.behaviors = behaviors = new Array(this.factories.length);
@@ -3533,6 +3598,14 @@ makeSerializationNoop(HydrationView);
3533
3598
  function isContentTemplate(value) {
3534
3599
  return value.create !== undefined;
3535
3600
  }
3601
+ /**
3602
+ * Sink function for DOMAspect.content bindings (text content interpolation).
3603
+ * Handles two cases:
3604
+ * - If the value is a ContentTemplate (has a create() method), it composes a child
3605
+ * view into the DOM, managing view lifecycle (create/reuse/remove/bind).
3606
+ * - If the value is a primitive, it sets target.textContent directly, first removing
3607
+ * any previously composed view.
3608
+ */
3536
3609
  function updateContent(target, aspect, value, controller) {
3537
3610
  // If there's no actual value, then this equates to the
3538
3611
  // empty string for the purposes of content bindings.
@@ -3601,6 +3674,12 @@ function updateContent(target, aspect, value, controller) {
3601
3674
  target.textContent = value;
3602
3675
  }
3603
3676
  }
3677
+ /**
3678
+ * Sink function for DOMAspect.tokenList bindings (e.g., :classList).
3679
+ * Uses a versioning scheme to efficiently track which CSS classes were added
3680
+ * in the current update vs. the previous one. Classes from the previous version
3681
+ * that aren't present in the new value are automatically removed.
3682
+ */
3604
3683
  function updateTokenList(target, aspect, value) {
3605
3684
  var _a;
3606
3685
  const lookup = `${this.id}-t`;
@@ -3633,6 +3712,12 @@ function updateTokenList(target, aspect, value) {
3633
3712
  }
3634
3713
  }
3635
3714
  }
3715
+ /**
3716
+ * Maps each DOMAspect type to its corresponding DOM update ("sink") function.
3717
+ * When a binding value changes, the sink function for the binding's aspect type
3718
+ * is called to push the new value into the DOM. Events are handled separately
3719
+ * via addEventListener in bind(), so the event sink is a no-op.
3720
+ */
3636
3721
  const sinkLookup = {
3637
3722
  [DOMAspect.attribute]: DOM.setAttribute,
3638
3723
  [DOMAspect.booleanAttribute]: DOM.setBooleanAttribute,
@@ -3642,7 +3727,18 @@ const sinkLookup = {
3642
3727
  [DOMAspect.event]: () => void 0,
3643
3728
  };
3644
3729
  /**
3645
- * A directive that applies bindings.
3730
+ * The central binding directive that bridges data expressions and DOM updates.
3731
+ *
3732
+ * HTMLBindingDirective fulfills three roles simultaneously:
3733
+ * - **HTMLDirective**: Produces placeholder HTML via createHTML() during template authoring.
3734
+ * - **ViewBehaviorFactory**: Creates behaviors (returns itself) during view creation.
3735
+ * - **ViewBehavior / EventListener**: Attaches to a DOM node during bind, manages
3736
+ * expression observers for reactive updates, and handles DOM events directly.
3737
+ *
3738
+ * The aspectType (set by HTMLDirective.assignAspect during template processing)
3739
+ * determines which DOM "sink" function is used to apply values — e.g.,
3740
+ * setAttribute for attributes, addEventListener for events, textContent for content.
3741
+ *
3646
3742
  * @public
3647
3743
  */
3648
3744
  class HTMLBindingDirective {
@@ -3681,7 +3777,18 @@ class HTMLBindingDirective {
3681
3777
  }
3682
3778
  return this;
3683
3779
  }
3684
- /** @internal */
3780
+ /**
3781
+ * Attaches this binding to its target DOM node.
3782
+ * - For events: stores the controller reference on the target element and registers
3783
+ * this directive as the EventListener via addEventListener. The directive's
3784
+ * handleEvent() method will be called when the event fires.
3785
+ * - For content bindings: registers an unbind handler, then falls through to the
3786
+ * default path.
3787
+ * - For all non-event bindings: creates (or reuses) an ExpressionObserver, evaluates
3788
+ * the binding expression, and applies the result to the DOM via the updateTarget
3789
+ * sink function. The observer will call handleChange() on future data changes.
3790
+ * @internal
3791
+ */
3685
3792
  bind(controller) {
3686
3793
  var _a;
3687
3794
  const target = controller.targets[this.targetNodeId];
@@ -3720,7 +3827,14 @@ class HTMLBindingDirective {
3720
3827
  view.needsBindOnly = true;
3721
3828
  }
3722
3829
  }
3723
- /** @internal */
3830
+ /**
3831
+ * Implements the EventListener interface. When a DOM event fires on the target
3832
+ * element, this method retrieves the ViewController stored on the element,
3833
+ * sets the event on the ExecutionContext so `c.event` is available to the
3834
+ * binding expression, and evaluates the expression. If the expression returns
3835
+ * anything other than `true`, the event's default action is prevented.
3836
+ * @internal
3837
+ */
3724
3838
  handleEvent(event) {
3725
3839
  const controller = event.currentTarget[this.data];
3726
3840
  if (controller.isBound) {
@@ -3732,7 +3846,13 @@ class HTMLBindingDirective {
3732
3846
  }
3733
3847
  }
3734
3848
  }
3735
- /** @internal */
3849
+ /**
3850
+ * Called by the ExpressionObserver when a tracked dependency changes.
3851
+ * Re-evaluates the binding expression via observer.bind() and pushes
3852
+ * the new value to the DOM through the updateTarget sink function.
3853
+ * This is the reactive update path that keeps the DOM in sync with data.
3854
+ * @internal
3855
+ */
3736
3856
  handleChange(binding, observer) {
3737
3857
  const target = observer.target;
3738
3858
  const controller = observer.controller;
@@ -3741,6 +3861,12 @@ class HTMLBindingDirective {
3741
3861
  }
3742
3862
  HTMLDirective.define(HTMLBindingDirective, { aspected: true });
3743
3863
 
3864
+ /**
3865
+ * Builds a hierarchical node ID by appending the child index to the parent's ID.
3866
+ * For example, the third child of root is "r.2", and its first child is "r.2.0".
3867
+ * These IDs are used as property names on the targets prototype so that each
3868
+ * binding's target DOM node can be lazily resolved via a chain of childNodes lookups.
3869
+ */
3744
3870
  const targetIdFrom = (parentId, nodeIndex) => `${parentId}.${nodeIndex}`;
3745
3871
  const descriptorCache = {};
3746
3872
  // used to prevent creating lots of objects just to track node and index while compiling
@@ -3790,6 +3916,13 @@ class CompilationContext {
3790
3916
  this.proto = Object.create(null, this.descriptors);
3791
3917
  return this;
3792
3918
  }
3919
+ /**
3920
+ * Registers a lazy getter on the targets prototype that resolves a DOM node
3921
+ * by navigating from its parent's childNodes at the given index. Getters are
3922
+ * chained: accessing targets["r.0.2"] first resolves targets["r.0"] (which
3923
+ * resolves targets["r"]), then returns childNodes[2]. Results are cached so
3924
+ * each node is resolved at most once per view instance.
3925
+ */
3793
3926
  addTargetDescriptor(parentId, targetId, targetIndex) {
3794
3927
  const descriptors = this.descriptors;
3795
3928
  if (targetId === "r" || // root
@@ -3800,7 +3933,7 @@ class CompilationContext {
3800
3933
  if (!descriptors[parentId]) {
3801
3934
  const index = parentId.lastIndexOf(".");
3802
3935
  const grandparentId = parentId.substring(0, index);
3803
- const childIndex = parseInt(parentId.substring(index + 1));
3936
+ const childIndex = parseInt(parentId.substring(index + 1), 10);
3804
3937
  this.addTargetDescriptor(grandparentId, parentId, childIndex);
3805
3938
  }
3806
3939
  let descriptor = descriptorCache[targetId];
@@ -3815,13 +3948,20 @@ class CompilationContext {
3815
3948
  }
3816
3949
  descriptors[targetId] = descriptor;
3817
3950
  }
3951
+ /**
3952
+ * Creates a new HTMLView by cloning the compiled DocumentFragment and building
3953
+ * a targets object. The targets prototype contains lazy getters that resolve
3954
+ * each binding's target DOM node via childNodes traversal. Accessing every
3955
+ * registered nodeId eagerly triggers the getter chain so all nodes are resolved
3956
+ * before behaviors are bound.
3957
+ */
3818
3958
  createView(hostBindingTarget) {
3819
3959
  const fragment = this.fragment.cloneNode(true);
3820
3960
  const targets = Object.create(this.proto);
3821
- targets.r = fragment;
3822
- targets.h = hostBindingTarget !== null && hostBindingTarget !== void 0 ? hostBindingTarget : warningHost;
3961
+ targets.r = fragment; // root — the cloned DocumentFragment
3962
+ targets.h = hostBindingTarget !== null && hostBindingTarget !== void 0 ? hostBindingTarget : warningHost; // host — the custom element
3823
3963
  for (const id of this.nodeIds) {
3824
- targets[id]; // trigger locator
3964
+ Reflect.get(targets, id); // trigger lazy getter to resolve and cache the DOM node
3825
3965
  }
3826
3966
  return new HTMLView(fragment, this.factories, targets);
3827
3967
  }
@@ -3841,7 +3981,6 @@ function compileAttributes(context, parentId, node, nodeId, nodeIndex, includeBa
3841
3981
  }
3842
3982
  }
3843
3983
  else {
3844
- /* eslint-disable-next-line @typescript-eslint/no-use-before-define */
3845
3984
  result = Compiler.aggregate(parseResult, context.policy);
3846
3985
  }
3847
3986
  if (result !== null) {
@@ -3886,7 +4025,6 @@ function compileChildren(context, parent, parentId) {
3886
4025
  let nodeIndex = 0;
3887
4026
  let childNode = parent.firstChild;
3888
4027
  while (childNode) {
3889
- /* eslint-disable-next-line @typescript-eslint/no-use-before-define */
3890
4028
  const result = compileNode(context, parentId, childNode, nodeIndex);
3891
4029
  childNode = result.node;
3892
4030
  nodeIndex = result.index;
@@ -3901,14 +4039,14 @@ function compileNode(context, parentId, node, nodeIndex) {
3901
4039
  break;
3902
4040
  case 3: // text node
3903
4041
  return compileContent(context, node, parentId, nodeId, nodeIndex);
3904
- case 8: // comment
4042
+ case 8: {
4043
+ // comment
3905
4044
  const parts = Parser.parse(node.data, context.directives);
3906
4045
  if (parts !== null) {
3907
- context.addFactory(
3908
- /* eslint-disable-next-line @typescript-eslint/no-use-before-define */
3909
- Compiler.aggregate(parts), parentId, nodeId, nodeIndex, null);
4046
+ context.addFactory(Compiler.aggregate(parts), parentId, nodeId, nodeIndex, null);
3910
4047
  }
3911
4048
  break;
4049
+ }
3912
4050
  }
3913
4051
  next.index = nodeIndex + 1;
3914
4052
  next.node = node.nextSibling;
@@ -3916,7 +4054,7 @@ function compileNode(context, parentId, node, nodeIndex) {
3916
4054
  }
3917
4055
  function isMarker(node, directives) {
3918
4056
  return (node &&
3919
- node.nodeType == 8 &&
4057
+ node.nodeType === 8 &&
3920
4058
  Parser.parse(node.data, directives) !== null);
3921
4059
  }
3922
4060
  const templateTag = "TEMPLATE";
@@ -4053,6 +4191,8 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
4053
4191
  OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
4054
4192
  PERFORMANCE OF THIS SOFTWARE.
4055
4193
  ***************************************************************************** */
4194
+ /* global Reflect, Promise, SuppressedError, Symbol */
4195
+
4056
4196
 
4057
4197
  function __awaiter(thisArg, _arguments, P, generator) {
4058
4198
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
@@ -4557,7 +4697,25 @@ class ViewTemplate {
4557
4697
  return view;
4558
4698
  }
4559
4699
  /**
4560
- * Creates a template based on a set of static strings and dynamic values.
4700
+ * Processes the tagged template literal's static strings and interpolated values and
4701
+ * creates a ViewTemplate.
4702
+ *
4703
+ * For each interpolated value:
4704
+ * 1. Functions (binding expressions, e.g., `x => x.name`) → wrapped in a one-way HTMLBindingDirective
4705
+ * 2. Binding instances → wrapped in an HTMLBindingDirective
4706
+ * 3. HTMLDirective instances → used as-is
4707
+ * 4. Static values (strings, numbers) → wrapped in a one-time HTMLBindingDirective
4708
+ *
4709
+ * Each directive's createHTML() is called with an `add` callback that registers
4710
+ * the factory in the factories record under a unique ID and returns that ID.
4711
+ * The directive inserts a placeholder marker (e.g., `fast-abc123{0}fast-abc123`) into
4712
+ * the HTML string so the compiler can later find and associate it with the factory.
4713
+ *
4714
+ * Aspect detection happens here too: the `lastAttributeNameRegex` checks whether
4715
+ * the placeholder appears inside an attribute value, and if so, assignAspect()
4716
+ * sets the correct DOMAspect (attribute, property, event, etc.) based on the
4717
+ * attribute name prefix.
4718
+ *
4561
4719
  * @param strings - The static strings to create the template with.
4562
4720
  * @param values - The dynamic values to create the template with.
4563
4721
  * @param policy - The DOMPolicy to associated with the template.
@@ -4753,13 +4911,7 @@ class RenderDirective {
4753
4911
  }
4754
4912
  }
4755
4913
  HTMLDirective.define(RenderDirective);
4756
- function isElementRenderOptions(object) {
4757
- return !!object.element || !!object.tagName;
4758
- }
4759
4914
  const typeToInstructionLookup = new Map();
4760
- /* eslint @typescript-eslint/naming-convention: "off"*/
4761
- const defaultAttributes = { ":model": (x) => x };
4762
- const brand = Symbol("RenderInstruction");
4763
4915
  const defaultViewName = "default-view";
4764
4916
  const nullTemplate = html `
4765
4917
  &nbsp;
@@ -4770,87 +4922,6 @@ function instructionToTemplate(def) {
4770
4922
  }
4771
4923
  return def.template;
4772
4924
  }
4773
- function createElementTemplate(tagName, options) {
4774
- const markup = [];
4775
- const values = [];
4776
- const { attributes, directives, content, policy } = options !== null && options !== void 0 ? options : {};
4777
- markup.push(`<${tagName}`);
4778
- if (attributes) {
4779
- const attrNames = Object.getOwnPropertyNames(attributes);
4780
- for (let i = 0, ii = attrNames.length; i < ii; ++i) {
4781
- const name = attrNames[i];
4782
- if (i === 0) {
4783
- markup[0] = `${markup[0]} ${name}="`;
4784
- }
4785
- else {
4786
- markup.push(`" ${name}="`);
4787
- }
4788
- values.push(attributes[name]);
4789
- }
4790
- markup.push(`"`);
4791
- }
4792
- if (directives) {
4793
- markup[markup.length - 1] += " ";
4794
- for (let i = 0, ii = directives.length; i < ii; ++i) {
4795
- const directive = directives[i];
4796
- markup.push(i > 0 ? "" : " ");
4797
- values.push(directive);
4798
- }
4799
- }
4800
- markup[markup.length - 1] += ">";
4801
- if (content && isFunction(content.create)) {
4802
- values.push(content);
4803
- markup.push(`</${tagName}>`);
4804
- }
4805
- else {
4806
- const lastIndex = markup.length - 1;
4807
- markup[lastIndex] = `${markup[lastIndex]}${content !== null && content !== void 0 ? content : ""}</${tagName}>`;
4808
- }
4809
- return ViewTemplate.create(markup, values, policy);
4810
- }
4811
- function create(options) {
4812
- var _a;
4813
- const name = (_a = options.name) !== null && _a !== void 0 ? _a : defaultViewName;
4814
- let template;
4815
- if (isElementRenderOptions(options)) {
4816
- let tagName = options.tagName;
4817
- if (!tagName) {
4818
- const def = FASTElementDefinition.getByType(options.element);
4819
- if (def) {
4820
- tagName = def.name;
4821
- }
4822
- else {
4823
- throw new Error("Invalid element for model rendering.");
4824
- }
4825
- }
4826
- if (!options.attributes) {
4827
- options.attributes = defaultAttributes;
4828
- }
4829
- template = createElementTemplate(tagName, options);
4830
- }
4831
- else {
4832
- template = options.template;
4833
- }
4834
- return {
4835
- brand,
4836
- type: options.type,
4837
- name,
4838
- template,
4839
- };
4840
- }
4841
- function instanceOf(object) {
4842
- return object && object.brand === brand;
4843
- }
4844
- function register(optionsOrInstruction) {
4845
- let lookup = typeToInstructionLookup.get(optionsOrInstruction.type);
4846
- if (lookup === void 0) {
4847
- typeToInstructionLookup.set(optionsOrInstruction.type, (lookup = Object.create(null)));
4848
- }
4849
- const instruction = instanceOf(optionsOrInstruction)
4850
- ? optionsOrInstruction
4851
- : create(optionsOrInstruction);
4852
- return (lookup[instruction.name] = instruction);
4853
- }
4854
4925
  function getByType(type, name) {
4855
4926
  const entries = typeToInstructionLookup.get(type);
4856
4927
  if (entries === void 0) {
@@ -4864,63 +4935,6 @@ function getForInstance(object, name) {
4864
4935
  }
4865
4936
  return void 0;
4866
4937
  }
4867
- /**
4868
- * Provides APIs for creating and interacting with render instructions.
4869
- * @public
4870
- */
4871
- Object.freeze({
4872
- /**
4873
- * Checks whether the provided object is a RenderInstruction.
4874
- * @param object - The object to check.
4875
- * @returns true if the object is a RenderInstruction; false otherwise
4876
- */
4877
- instanceOf,
4878
- /**
4879
- * Creates a RenderInstruction for a set of options.
4880
- * @param options - The options to use when creating the RenderInstruction.
4881
- * @remarks
4882
- * This API should be used with caution. When providing attributes or content,
4883
- * if not done properly, you can open up the application to XSS attacks. When using this API,
4884
- * provide a strong DOMPolicy that can properly sanitize and also be sure to manually sanitize
4885
- * content and attribute values particularly if they can come from user input.
4886
- */
4887
- create,
4888
- /**
4889
- * Creates a template based on a tag name.
4890
- * @param tagName - The tag name to use when creating the template.
4891
- * @param attributes - The attributes to apply to the element.
4892
- * @param content - The content to insert into the element.
4893
- * @param policy - The DOMPolicy to create the template with.
4894
- * @returns A template based on the provided specifications.
4895
- * @remarks
4896
- * This API should be used with caution. When providing attributes or content,
4897
- * if not done properly, you can open up the application to XSS attacks. When using this API,
4898
- * provide a strong DOMPolicy that can properly sanitize and also be sure to manually sanitize
4899
- * content and attribute values particularly if they can come from user input.
4900
- */
4901
- createElementTemplate,
4902
- /**
4903
- * Creates and registers an instruction.
4904
- * @param options The options to use when creating the RenderInstruction.
4905
- * @remarks
4906
- * A previously created RenderInstruction can also be registered.
4907
- */
4908
- register,
4909
- /**
4910
- * Finds a previously registered RenderInstruction by type and optionally by name.
4911
- * @param type - The type to retrieve the RenderInstruction for.
4912
- * @param name - An optional name used in differentiating between multiple registered instructions.
4913
- * @returns The located RenderInstruction that matches the criteria or undefined if none is found.
4914
- */
4915
- getByType,
4916
- /**
4917
- * Finds a previously registered RenderInstruction for the instance's type and optionally by name.
4918
- * @param object - The instance to retrieve the RenderInstruction for.
4919
- * @param name - An optional name used in differentiating between multiple registered instructions.
4920
- * @returns The located RenderInstruction that matches the criteria or undefined if none is found.
4921
- */
4922
- getForInstance,
4923
- });
4924
4938
  /**
4925
4939
  * @internal
4926
4940
  */
@@ -5511,71 +5525,6 @@ class UnobservableMutationObserver extends MutationObserver {
5511
5525
  }
5512
5526
  }
5513
5527
  }
5514
- /**
5515
- * Bridges between ViewBehaviors and HostBehaviors, enabling a host to
5516
- * control ViewBehaviors.
5517
- * @public
5518
- */
5519
- Object.freeze({
5520
- /**
5521
- * Creates a ViewBehaviorOrchestrator.
5522
- * @param source - The source to to associate behaviors with.
5523
- * @returns A ViewBehaviorOrchestrator.
5524
- */
5525
- create(source) {
5526
- const behaviors = [];
5527
- const targets = {};
5528
- let unbindables = null;
5529
- let isConnected = false;
5530
- return {
5531
- source,
5532
- context: ExecutionContext.default,
5533
- targets,
5534
- get isBound() {
5535
- return isConnected;
5536
- },
5537
- addBehaviorFactory(factory, target) {
5538
- var _a, _b, _c, _d;
5539
- const compiled = factory;
5540
- compiled.id = (_a = compiled.id) !== null && _a !== void 0 ? _a : nextId();
5541
- compiled.targetNodeId = (_b = compiled.targetNodeId) !== null && _b !== void 0 ? _b : nextId();
5542
- compiled.targetTagName = (_c = target.tagName) !== null && _c !== void 0 ? _c : null;
5543
- compiled.policy = (_d = compiled.policy) !== null && _d !== void 0 ? _d : DOM.policy;
5544
- this.addTarget(compiled.targetNodeId, target);
5545
- this.addBehavior(compiled.createBehavior());
5546
- },
5547
- addTarget(nodeId, target) {
5548
- targets[nodeId] = target;
5549
- },
5550
- addBehavior(behavior) {
5551
- behaviors.push(behavior);
5552
- if (isConnected) {
5553
- behavior.bind(this);
5554
- }
5555
- },
5556
- onUnbind(unbindable) {
5557
- if (unbindables === null) {
5558
- unbindables = [];
5559
- }
5560
- unbindables.push(unbindable);
5561
- },
5562
- connectedCallback(controller) {
5563
- if (!isConnected) {
5564
- isConnected = true;
5565
- behaviors.forEach(x => x.bind(this));
5566
- }
5567
- },
5568
- disconnectedCallback(controller) {
5569
- if (isConnected) {
5570
- isConnected = false;
5571
- if (unbindables !== null) {
5572
- unbindables.forEach(x => x.unbind(this));
5573
- }
5574
- }
5575
- },
5576
- };
5577
- },
5578
- });
5579
5528
 
5580
5529
  const defaultEventOptions = {
5581
5530
  bubbles: true,
@@ -6298,6 +6247,9 @@ class HydratableElementController extends ElementController {
6298
6247
  // Initialize needsHydration on first connect
6299
6248
  this.needsHydration =
6300
6249
  (_a = this.needsHydration) !== null && _a !== void 0 ? _a : this.source.hasAttribute(needsHydrationAttribute);
6250
+ if (this.needsHydration) {
6251
+ this.addHydratingInstance();
6252
+ }
6301
6253
  // If the `defer-hydration` attribute exists on the source,
6302
6254
  // wait for it to be removed before continuing connection behavior.
6303
6255
  if (this.source.hasAttribute(deferHydrationAttribute)) {