@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.
- package/ARCHITECTURE_FASTELEMENT.md +1 -1
- package/ARCHITECTURE_HTML_TAGGED_TEMPLATE_LITERAL.md +3 -1
- package/ARCHITECTURE_INTRO.md +1 -1
- package/ARCHITECTURE_OVERVIEW.md +1 -1
- package/CHANGELOG.json +153 -1
- package/CHANGELOG.md +18 -2
- package/DESIGN.md +506 -0
- package/biome.json +4 -0
- package/dist/context/context.api.json +17 -1
- package/dist/di/di.api.json +17 -1
- package/dist/dts/hydration/target-builder.d.ts +17 -1
- package/dist/dts/templating/html-binding-directive.d.ts +39 -4
- package/dist/dts/templating/html-directive.d.ts +7 -1
- package/dist/dts/templating/template.d.ts +19 -1
- package/dist/dts/templating/view.d.ts +11 -0
- package/dist/dts/testing/models.d.ts +20 -0
- package/dist/dts/tsdoc-metadata.json +1 -1
- package/dist/esm/components/element-controller.js +3 -0
- package/dist/esm/components/hydration.js +17 -0
- package/dist/esm/hydration/target-builder.js +22 -1
- package/dist/esm/templating/compiler.js +29 -11
- package/dist/esm/templating/html-binding-directive.js +59 -4
- package/dist/esm/templating/html-directive.js +7 -1
- package/dist/esm/templating/install-hydratable-view-templates.js +6 -0
- package/dist/esm/templating/markup.js +8 -0
- package/dist/esm/templating/template.js +19 -1
- package/dist/esm/templating/view.js +13 -0
- package/dist/esm/testing/models.js +58 -0
- package/dist/fast-element.api.json +20 -4
- package/dist/fast-element.debug.js +179 -227
- package/dist/fast-element.debug.min.js +2 -2
- package/dist/fast-element.js +179 -227
- package/dist/fast-element.min.js +2 -2
- package/dist/fast-element.untrimmed.d.ts +76 -6
- package/docs/api-report.api.md +3 -3
- package/package.json +7 -19
- package/playwright.config.ts +8 -0
- package/test/main.ts +95 -1
- package/tsconfig.api-extractor.json +6 -0
- package/.eslintrc.json +0 -19
- package/karma.conf.cjs +0 -148
package/dist/fast-element.js
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
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
|
-
/**
|
|
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];
|
|
@@ -3650,7 +3757,14 @@ class HTMLBindingDirective {
|
|
|
3650
3757
|
view.needsBindOnly = true;
|
|
3651
3758
|
}
|
|
3652
3759
|
}
|
|
3653
|
-
/**
|
|
3760
|
+
/**
|
|
3761
|
+
* Implements the EventListener interface. When a DOM event fires on the target
|
|
3762
|
+
* element, this method retrieves the ViewController stored on the element,
|
|
3763
|
+
* sets the event on the ExecutionContext so `c.event` is available to the
|
|
3764
|
+
* binding expression, and evaluates the expression. If the expression returns
|
|
3765
|
+
* anything other than `true`, the event's default action is prevented.
|
|
3766
|
+
* @internal
|
|
3767
|
+
*/
|
|
3654
3768
|
handleEvent(event) {
|
|
3655
3769
|
const controller = event.currentTarget[this.data];
|
|
3656
3770
|
if (controller.isBound) {
|
|
@@ -3662,7 +3776,13 @@ class HTMLBindingDirective {
|
|
|
3662
3776
|
}
|
|
3663
3777
|
}
|
|
3664
3778
|
}
|
|
3665
|
-
/**
|
|
3779
|
+
/**
|
|
3780
|
+
* Called by the ExpressionObserver when a tracked dependency changes.
|
|
3781
|
+
* Re-evaluates the binding expression via observer.bind() and pushes
|
|
3782
|
+
* the new value to the DOM through the updateTarget sink function.
|
|
3783
|
+
* This is the reactive update path that keeps the DOM in sync with data.
|
|
3784
|
+
* @internal
|
|
3785
|
+
*/
|
|
3666
3786
|
handleChange(binding, observer) {
|
|
3667
3787
|
const target = observer.target;
|
|
3668
3788
|
const controller = observer.controller;
|
|
@@ -3671,6 +3791,12 @@ class HTMLBindingDirective {
|
|
|
3671
3791
|
}
|
|
3672
3792
|
HTMLDirective.define(HTMLBindingDirective, { aspected: true });
|
|
3673
3793
|
|
|
3794
|
+
/**
|
|
3795
|
+
* Builds a hierarchical node ID by appending the child index to the parent's ID.
|
|
3796
|
+
* For example, the third child of root is "r.2", and its first child is "r.2.0".
|
|
3797
|
+
* These IDs are used as property names on the targets prototype so that each
|
|
3798
|
+
* binding's target DOM node can be lazily resolved via a chain of childNodes lookups.
|
|
3799
|
+
*/
|
|
3674
3800
|
const targetIdFrom = (parentId, nodeIndex) => `${parentId}.${nodeIndex}`;
|
|
3675
3801
|
const descriptorCache = {};
|
|
3676
3802
|
// used to prevent creating lots of objects just to track node and index while compiling
|
|
@@ -3720,6 +3846,13 @@ class CompilationContext {
|
|
|
3720
3846
|
this.proto = Object.create(null, this.descriptors);
|
|
3721
3847
|
return this;
|
|
3722
3848
|
}
|
|
3849
|
+
/**
|
|
3850
|
+
* Registers a lazy getter on the targets prototype that resolves a DOM node
|
|
3851
|
+
* by navigating from its parent's childNodes at the given index. Getters are
|
|
3852
|
+
* chained: accessing targets["r.0.2"] first resolves targets["r.0"] (which
|
|
3853
|
+
* resolves targets["r"]), then returns childNodes[2]. Results are cached so
|
|
3854
|
+
* each node is resolved at most once per view instance.
|
|
3855
|
+
*/
|
|
3723
3856
|
addTargetDescriptor(parentId, targetId, targetIndex) {
|
|
3724
3857
|
const descriptors = this.descriptors;
|
|
3725
3858
|
if (targetId === "r" || // root
|
|
@@ -3730,7 +3863,7 @@ class CompilationContext {
|
|
|
3730
3863
|
if (!descriptors[parentId]) {
|
|
3731
3864
|
const index = parentId.lastIndexOf(".");
|
|
3732
3865
|
const grandparentId = parentId.substring(0, index);
|
|
3733
|
-
const childIndex = parseInt(parentId.substring(index + 1));
|
|
3866
|
+
const childIndex = parseInt(parentId.substring(index + 1), 10);
|
|
3734
3867
|
this.addTargetDescriptor(grandparentId, parentId, childIndex);
|
|
3735
3868
|
}
|
|
3736
3869
|
let descriptor = descriptorCache[targetId];
|
|
@@ -3745,13 +3878,20 @@ class CompilationContext {
|
|
|
3745
3878
|
}
|
|
3746
3879
|
descriptors[targetId] = descriptor;
|
|
3747
3880
|
}
|
|
3881
|
+
/**
|
|
3882
|
+
* Creates a new HTMLView by cloning the compiled DocumentFragment and building
|
|
3883
|
+
* a targets object. The targets prototype contains lazy getters that resolve
|
|
3884
|
+
* each binding's target DOM node via childNodes traversal. Accessing every
|
|
3885
|
+
* registered nodeId eagerly triggers the getter chain so all nodes are resolved
|
|
3886
|
+
* before behaviors are bound.
|
|
3887
|
+
*/
|
|
3748
3888
|
createView(hostBindingTarget) {
|
|
3749
3889
|
const fragment = this.fragment.cloneNode(true);
|
|
3750
3890
|
const targets = Object.create(this.proto);
|
|
3751
|
-
targets.r = fragment;
|
|
3752
|
-
targets.h = hostBindingTarget !== null && hostBindingTarget !== void 0 ? hostBindingTarget : warningHost;
|
|
3891
|
+
targets.r = fragment; // root — the cloned DocumentFragment
|
|
3892
|
+
targets.h = hostBindingTarget !== null && hostBindingTarget !== void 0 ? hostBindingTarget : warningHost; // host — the custom element
|
|
3753
3893
|
for (const id of this.nodeIds) {
|
|
3754
|
-
targets
|
|
3894
|
+
Reflect.get(targets, id); // trigger lazy getter to resolve and cache the DOM node
|
|
3755
3895
|
}
|
|
3756
3896
|
return new HTMLView(fragment, this.factories, targets);
|
|
3757
3897
|
}
|
|
@@ -3771,7 +3911,6 @@ function compileAttributes(context, parentId, node, nodeId, nodeIndex, includeBa
|
|
|
3771
3911
|
}
|
|
3772
3912
|
}
|
|
3773
3913
|
else {
|
|
3774
|
-
/* eslint-disable-next-line @typescript-eslint/no-use-before-define */
|
|
3775
3914
|
result = Compiler.aggregate(parseResult, context.policy);
|
|
3776
3915
|
}
|
|
3777
3916
|
if (result !== null) {
|
|
@@ -3816,7 +3955,6 @@ function compileChildren(context, parent, parentId) {
|
|
|
3816
3955
|
let nodeIndex = 0;
|
|
3817
3956
|
let childNode = parent.firstChild;
|
|
3818
3957
|
while (childNode) {
|
|
3819
|
-
/* eslint-disable-next-line @typescript-eslint/no-use-before-define */
|
|
3820
3958
|
const result = compileNode(context, parentId, childNode, nodeIndex);
|
|
3821
3959
|
childNode = result.node;
|
|
3822
3960
|
nodeIndex = result.index;
|
|
@@ -3831,14 +3969,14 @@ function compileNode(context, parentId, node, nodeIndex) {
|
|
|
3831
3969
|
break;
|
|
3832
3970
|
case 3: // text node
|
|
3833
3971
|
return compileContent(context, node, parentId, nodeId, nodeIndex);
|
|
3834
|
-
case 8:
|
|
3972
|
+
case 8: {
|
|
3973
|
+
// comment
|
|
3835
3974
|
const parts = Parser.parse(node.data, context.directives);
|
|
3836
3975
|
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);
|
|
3976
|
+
context.addFactory(Compiler.aggregate(parts), parentId, nodeId, nodeIndex, null);
|
|
3840
3977
|
}
|
|
3841
3978
|
break;
|
|
3979
|
+
}
|
|
3842
3980
|
}
|
|
3843
3981
|
next.index = nodeIndex + 1;
|
|
3844
3982
|
next.node = node.nextSibling;
|
|
@@ -3846,7 +3984,7 @@ function compileNode(context, parentId, node, nodeIndex) {
|
|
|
3846
3984
|
}
|
|
3847
3985
|
function isMarker(node, directives) {
|
|
3848
3986
|
return (node &&
|
|
3849
|
-
node.nodeType
|
|
3987
|
+
node.nodeType === 8 &&
|
|
3850
3988
|
Parser.parse(node.data, directives) !== null);
|
|
3851
3989
|
}
|
|
3852
3990
|
const templateTag = "TEMPLATE";
|
|
@@ -3983,6 +4121,8 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
|
3983
4121
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
3984
4122
|
PERFORMANCE OF THIS SOFTWARE.
|
|
3985
4123
|
***************************************************************************** */
|
|
4124
|
+
/* global Reflect, Promise, SuppressedError, Symbol */
|
|
4125
|
+
|
|
3986
4126
|
|
|
3987
4127
|
function __awaiter(thisArg, _arguments, P, generator) {
|
|
3988
4128
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
@@ -4487,7 +4627,25 @@ class ViewTemplate {
|
|
|
4487
4627
|
return view;
|
|
4488
4628
|
}
|
|
4489
4629
|
/**
|
|
4490
|
-
*
|
|
4630
|
+
* Processes the tagged template literal's static strings and interpolated values and
|
|
4631
|
+
* creates a ViewTemplate.
|
|
4632
|
+
*
|
|
4633
|
+
* For each interpolated value:
|
|
4634
|
+
* 1. Functions (binding expressions, e.g., `x => x.name`) → wrapped in a one-way HTMLBindingDirective
|
|
4635
|
+
* 2. Binding instances → wrapped in an HTMLBindingDirective
|
|
4636
|
+
* 3. HTMLDirective instances → used as-is
|
|
4637
|
+
* 4. Static values (strings, numbers) → wrapped in a one-time HTMLBindingDirective
|
|
4638
|
+
*
|
|
4639
|
+
* Each directive's createHTML() is called with an `add` callback that registers
|
|
4640
|
+
* the factory in the factories record under a unique ID and returns that ID.
|
|
4641
|
+
* The directive inserts a placeholder marker (e.g., `fast-abc123{0}fast-abc123`) into
|
|
4642
|
+
* the HTML string so the compiler can later find and associate it with the factory.
|
|
4643
|
+
*
|
|
4644
|
+
* Aspect detection happens here too: the `lastAttributeNameRegex` checks whether
|
|
4645
|
+
* the placeholder appears inside an attribute value, and if so, assignAspect()
|
|
4646
|
+
* sets the correct DOMAspect (attribute, property, event, etc.) based on the
|
|
4647
|
+
* attribute name prefix.
|
|
4648
|
+
*
|
|
4491
4649
|
* @param strings - The static strings to create the template with.
|
|
4492
4650
|
* @param values - The dynamic values to create the template with.
|
|
4493
4651
|
* @param policy - The DOMPolicy to associated with the template.
|
|
@@ -4683,13 +4841,7 @@ class RenderDirective {
|
|
|
4683
4841
|
}
|
|
4684
4842
|
}
|
|
4685
4843
|
HTMLDirective.define(RenderDirective);
|
|
4686
|
-
function isElementRenderOptions(object) {
|
|
4687
|
-
return !!object.element || !!object.tagName;
|
|
4688
|
-
}
|
|
4689
4844
|
const typeToInstructionLookup = new Map();
|
|
4690
|
-
/* eslint @typescript-eslint/naming-convention: "off"*/
|
|
4691
|
-
const defaultAttributes = { ":model": (x) => x };
|
|
4692
|
-
const brand = Symbol("RenderInstruction");
|
|
4693
4845
|
const defaultViewName = "default-view";
|
|
4694
4846
|
const nullTemplate = html `
|
|
4695
4847
|
|
|
@@ -4700,87 +4852,6 @@ function instructionToTemplate(def) {
|
|
|
4700
4852
|
}
|
|
4701
4853
|
return def.template;
|
|
4702
4854
|
}
|
|
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
4855
|
function getByType(type, name) {
|
|
4785
4856
|
const entries = typeToInstructionLookup.get(type);
|
|
4786
4857
|
if (entries === void 0) {
|
|
@@ -4794,63 +4865,6 @@ function getForInstance(object, name) {
|
|
|
4794
4865
|
}
|
|
4795
4866
|
return void 0;
|
|
4796
4867
|
}
|
|
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
4868
|
/**
|
|
4855
4869
|
* @internal
|
|
4856
4870
|
*/
|
|
@@ -5441,71 +5455,6 @@ class UnobservableMutationObserver extends MutationObserver {
|
|
|
5441
5455
|
}
|
|
5442
5456
|
}
|
|
5443
5457
|
}
|
|
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
5458
|
|
|
5510
5459
|
const defaultEventOptions = {
|
|
5511
5460
|
bubbles: true,
|
|
@@ -6228,6 +6177,9 @@ class HydratableElementController extends ElementController {
|
|
|
6228
6177
|
// Initialize needsHydration on first connect
|
|
6229
6178
|
this.needsHydration =
|
|
6230
6179
|
(_a = this.needsHydration) !== null && _a !== void 0 ? _a : this.source.hasAttribute(needsHydrationAttribute);
|
|
6180
|
+
if (this.needsHydration) {
|
|
6181
|
+
this.addHydratingInstance();
|
|
6182
|
+
}
|
|
6231
6183
|
// If the `defer-hydration` attribute exists on the source,
|
|
6232
6184
|
// wait for it to be removed before continuing connection behavior.
|
|
6233
6185
|
if (this.source.hasAttribute(deferHydrationAttribute)) {
|