@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.
- 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 +138 -1
- package/CHANGELOG.md +18 -2
- package/DESIGN.md +510 -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 +45 -4
- package/dist/dts/templating/html-directive.d.ts +7 -1
- package/dist/dts/templating/render.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/tsdoc-metadata.json +1 -1
- 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 +73 -6
- 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/render.js +12 -1
- package/dist/esm/templating/template.js +19 -1
- package/dist/esm/templating/view.js +13 -0
- package/dist/fast-element.api.json +20 -4
- package/dist/fast-element.debug.js +202 -230
- package/dist/fast-element.debug.min.js +2 -2
- package/dist/fast-element.js +202 -230
- package/dist/fast-element.min.js +2 -2
- package/dist/fast-element.untrimmed.d.ts +89 -7
- package/docs/api-report.api.md +4 -4
- package/package.json +8 -19
- package/tsconfig.api-extractor.json +6 -0
- package/.eslintrc.json +0 -19
- 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
|
|
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
|
-
*
|
|
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
|
-
/**
|
|
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];
|
|
@@ -3696,7 +3803,7 @@ class HTMLBindingDirective {
|
|
|
3696
3803
|
case DOMAspect.content:
|
|
3697
3804
|
controller.onUnbind(this);
|
|
3698
3805
|
// intentional fall through
|
|
3699
|
-
default:
|
|
3806
|
+
default: {
|
|
3700
3807
|
const observer = (_a = target[this.data]) !== null && _a !== void 0 ? _a : (target[this.data] = this.dataBinding.createObserver(this, this));
|
|
3701
3808
|
observer.target = target;
|
|
3702
3809
|
observer.controller = controller;
|
|
@@ -3709,6 +3816,7 @@ class HTMLBindingDirective {
|
|
|
3709
3816
|
}
|
|
3710
3817
|
this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
|
|
3711
3818
|
break;
|
|
3819
|
+
}
|
|
3712
3820
|
}
|
|
3713
3821
|
}
|
|
3714
3822
|
/** @internal */
|
|
@@ -3720,7 +3828,14 @@ class HTMLBindingDirective {
|
|
|
3720
3828
|
view.needsBindOnly = true;
|
|
3721
3829
|
}
|
|
3722
3830
|
}
|
|
3723
|
-
/**
|
|
3831
|
+
/**
|
|
3832
|
+
* Implements the EventListener interface. When a DOM event fires on the target
|
|
3833
|
+
* element, this method retrieves the ViewController stored on the element,
|
|
3834
|
+
* sets the event on the ExecutionContext so `c.event` is available to the
|
|
3835
|
+
* binding expression, and evaluates the expression. If the expression returns
|
|
3836
|
+
* anything other than `true`, the event's default action is prevented.
|
|
3837
|
+
* @internal
|
|
3838
|
+
*/
|
|
3724
3839
|
handleEvent(event) {
|
|
3725
3840
|
const controller = event.currentTarget[this.data];
|
|
3726
3841
|
if (controller.isBound) {
|
|
@@ -3732,15 +3847,38 @@ class HTMLBindingDirective {
|
|
|
3732
3847
|
}
|
|
3733
3848
|
}
|
|
3734
3849
|
}
|
|
3735
|
-
/**
|
|
3850
|
+
/**
|
|
3851
|
+
* Called by the ExpressionObserver when a tracked dependency changes.
|
|
3852
|
+
* Re-evaluates the binding expression via observer.bind() and pushes
|
|
3853
|
+
* the new value to the DOM through the updateTarget sink function.
|
|
3854
|
+
* This is the reactive update path that keeps the DOM in sync with data.
|
|
3855
|
+
*
|
|
3856
|
+
* Guards against stale notifications: when a view is unbound (e.g., after
|
|
3857
|
+
* a parent `when` directive tears down a child element), coupled-lifetime
|
|
3858
|
+
* observers may still hold active subscriptions. If a property change fires
|
|
3859
|
+
* on the source element while the view is inactive, this guard prevents
|
|
3860
|
+
* the binding expression from evaluating with a null source.
|
|
3861
|
+
* @internal
|
|
3862
|
+
*/
|
|
3736
3863
|
handleChange(binding, observer) {
|
|
3737
|
-
const target = observer.target;
|
|
3738
3864
|
const controller = observer.controller;
|
|
3865
|
+
// https://github.com/microsoft/fast/issues/7444
|
|
3866
|
+
// This guard will be reconsidered in the next major version.
|
|
3867
|
+
if (!controller.isBound) {
|
|
3868
|
+
return;
|
|
3869
|
+
}
|
|
3870
|
+
const target = observer.target;
|
|
3739
3871
|
this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
|
|
3740
3872
|
}
|
|
3741
3873
|
}
|
|
3742
3874
|
HTMLDirective.define(HTMLBindingDirective, { aspected: true });
|
|
3743
3875
|
|
|
3876
|
+
/**
|
|
3877
|
+
* Builds a hierarchical node ID by appending the child index to the parent's ID.
|
|
3878
|
+
* For example, the third child of root is "r.2", and its first child is "r.2.0".
|
|
3879
|
+
* These IDs are used as property names on the targets prototype so that each
|
|
3880
|
+
* binding's target DOM node can be lazily resolved via a chain of childNodes lookups.
|
|
3881
|
+
*/
|
|
3744
3882
|
const targetIdFrom = (parentId, nodeIndex) => `${parentId}.${nodeIndex}`;
|
|
3745
3883
|
const descriptorCache = {};
|
|
3746
3884
|
// used to prevent creating lots of objects just to track node and index while compiling
|
|
@@ -3790,6 +3928,13 @@ class CompilationContext {
|
|
|
3790
3928
|
this.proto = Object.create(null, this.descriptors);
|
|
3791
3929
|
return this;
|
|
3792
3930
|
}
|
|
3931
|
+
/**
|
|
3932
|
+
* Registers a lazy getter on the targets prototype that resolves a DOM node
|
|
3933
|
+
* by navigating from its parent's childNodes at the given index. Getters are
|
|
3934
|
+
* chained: accessing targets["r.0.2"] first resolves targets["r.0"] (which
|
|
3935
|
+
* resolves targets["r"]), then returns childNodes[2]. Results are cached so
|
|
3936
|
+
* each node is resolved at most once per view instance.
|
|
3937
|
+
*/
|
|
3793
3938
|
addTargetDescriptor(parentId, targetId, targetIndex) {
|
|
3794
3939
|
const descriptors = this.descriptors;
|
|
3795
3940
|
if (targetId === "r" || // root
|
|
@@ -3800,7 +3945,7 @@ class CompilationContext {
|
|
|
3800
3945
|
if (!descriptors[parentId]) {
|
|
3801
3946
|
const index = parentId.lastIndexOf(".");
|
|
3802
3947
|
const grandparentId = parentId.substring(0, index);
|
|
3803
|
-
const childIndex = parseInt(parentId.substring(index + 1));
|
|
3948
|
+
const childIndex = parseInt(parentId.substring(index + 1), 10);
|
|
3804
3949
|
this.addTargetDescriptor(grandparentId, parentId, childIndex);
|
|
3805
3950
|
}
|
|
3806
3951
|
let descriptor = descriptorCache[targetId];
|
|
@@ -3815,13 +3960,20 @@ class CompilationContext {
|
|
|
3815
3960
|
}
|
|
3816
3961
|
descriptors[targetId] = descriptor;
|
|
3817
3962
|
}
|
|
3963
|
+
/**
|
|
3964
|
+
* Creates a new HTMLView by cloning the compiled DocumentFragment and building
|
|
3965
|
+
* a targets object. The targets prototype contains lazy getters that resolve
|
|
3966
|
+
* each binding's target DOM node via childNodes traversal. Accessing every
|
|
3967
|
+
* registered nodeId eagerly triggers the getter chain so all nodes are resolved
|
|
3968
|
+
* before behaviors are bound.
|
|
3969
|
+
*/
|
|
3818
3970
|
createView(hostBindingTarget) {
|
|
3819
3971
|
const fragment = this.fragment.cloneNode(true);
|
|
3820
3972
|
const targets = Object.create(this.proto);
|
|
3821
|
-
targets.r = fragment;
|
|
3822
|
-
targets.h = hostBindingTarget !== null && hostBindingTarget !== void 0 ? hostBindingTarget : warningHost;
|
|
3973
|
+
targets.r = fragment; // root — the cloned DocumentFragment
|
|
3974
|
+
targets.h = hostBindingTarget !== null && hostBindingTarget !== void 0 ? hostBindingTarget : warningHost; // host — the custom element
|
|
3823
3975
|
for (const id of this.nodeIds) {
|
|
3824
|
-
targets
|
|
3976
|
+
Reflect.get(targets, id); // trigger lazy getter to resolve and cache the DOM node
|
|
3825
3977
|
}
|
|
3826
3978
|
return new HTMLView(fragment, this.factories, targets);
|
|
3827
3979
|
}
|
|
@@ -3841,7 +3993,6 @@ function compileAttributes(context, parentId, node, nodeId, nodeIndex, includeBa
|
|
|
3841
3993
|
}
|
|
3842
3994
|
}
|
|
3843
3995
|
else {
|
|
3844
|
-
/* eslint-disable-next-line @typescript-eslint/no-use-before-define */
|
|
3845
3996
|
result = Compiler.aggregate(parseResult, context.policy);
|
|
3846
3997
|
}
|
|
3847
3998
|
if (result !== null) {
|
|
@@ -3886,7 +4037,6 @@ function compileChildren(context, parent, parentId) {
|
|
|
3886
4037
|
let nodeIndex = 0;
|
|
3887
4038
|
let childNode = parent.firstChild;
|
|
3888
4039
|
while (childNode) {
|
|
3889
|
-
/* eslint-disable-next-line @typescript-eslint/no-use-before-define */
|
|
3890
4040
|
const result = compileNode(context, parentId, childNode, nodeIndex);
|
|
3891
4041
|
childNode = result.node;
|
|
3892
4042
|
nodeIndex = result.index;
|
|
@@ -3901,14 +4051,14 @@ function compileNode(context, parentId, node, nodeIndex) {
|
|
|
3901
4051
|
break;
|
|
3902
4052
|
case 3: // text node
|
|
3903
4053
|
return compileContent(context, node, parentId, nodeId, nodeIndex);
|
|
3904
|
-
case 8:
|
|
4054
|
+
case 8: {
|
|
4055
|
+
// comment
|
|
3905
4056
|
const parts = Parser.parse(node.data, context.directives);
|
|
3906
4057
|
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);
|
|
4058
|
+
context.addFactory(Compiler.aggregate(parts), parentId, nodeId, nodeIndex, null);
|
|
3910
4059
|
}
|
|
3911
4060
|
break;
|
|
4061
|
+
}
|
|
3912
4062
|
}
|
|
3913
4063
|
next.index = nodeIndex + 1;
|
|
3914
4064
|
next.node = node.nextSibling;
|
|
@@ -3916,7 +4066,7 @@ function compileNode(context, parentId, node, nodeIndex) {
|
|
|
3916
4066
|
}
|
|
3917
4067
|
function isMarker(node, directives) {
|
|
3918
4068
|
return (node &&
|
|
3919
|
-
node.nodeType
|
|
4069
|
+
node.nodeType === 8 &&
|
|
3920
4070
|
Parser.parse(node.data, directives) !== null);
|
|
3921
4071
|
}
|
|
3922
4072
|
const templateTag = "TEMPLATE";
|
|
@@ -4053,6 +4203,8 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
|
4053
4203
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
4054
4204
|
PERFORMANCE OF THIS SOFTWARE.
|
|
4055
4205
|
***************************************************************************** */
|
|
4206
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
4207
|
+
|
|
4056
4208
|
|
|
4057
4209
|
function __awaiter(thisArg, _arguments, P, generator) {
|
|
4058
4210
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
@@ -4557,7 +4709,25 @@ class ViewTemplate {
|
|
|
4557
4709
|
return view;
|
|
4558
4710
|
}
|
|
4559
4711
|
/**
|
|
4560
|
-
*
|
|
4712
|
+
* Processes the tagged template literal's static strings and interpolated values and
|
|
4713
|
+
* creates a ViewTemplate.
|
|
4714
|
+
*
|
|
4715
|
+
* For each interpolated value:
|
|
4716
|
+
* 1. Functions (binding expressions, e.g., `x => x.name`) → wrapped in a one-way HTMLBindingDirective
|
|
4717
|
+
* 2. Binding instances → wrapped in an HTMLBindingDirective
|
|
4718
|
+
* 3. HTMLDirective instances → used as-is
|
|
4719
|
+
* 4. Static values (strings, numbers) → wrapped in a one-time HTMLBindingDirective
|
|
4720
|
+
*
|
|
4721
|
+
* Each directive's createHTML() is called with an `add` callback that registers
|
|
4722
|
+
* the factory in the factories record under a unique ID and returns that ID.
|
|
4723
|
+
* The directive inserts a placeholder marker (e.g., `fast-abc123{0}fast-abc123`) into
|
|
4724
|
+
* the HTML string so the compiler can later find and associate it with the factory.
|
|
4725
|
+
*
|
|
4726
|
+
* Aspect detection happens here too: the `lastAttributeNameRegex` checks whether
|
|
4727
|
+
* the placeholder appears inside an attribute value, and if so, assignAspect()
|
|
4728
|
+
* sets the correct DOMAspect (attribute, property, event, etc.) based on the
|
|
4729
|
+
* attribute name prefix.
|
|
4730
|
+
*
|
|
4561
4731
|
* @param strings - The static strings to create the template with.
|
|
4562
4732
|
* @param values - The dynamic values to create the template with.
|
|
4563
4733
|
* @param policy - The DOMPolicy to associated with the template.
|
|
@@ -4671,8 +4841,19 @@ class RenderBehavior {
|
|
|
4671
4841
|
view.needsBindOnly = true;
|
|
4672
4842
|
}
|
|
4673
4843
|
}
|
|
4674
|
-
/**
|
|
4844
|
+
/**
|
|
4845
|
+
* Handles changes from data or template binding observers.
|
|
4846
|
+
* Guards against stale notifications that may arrive after the
|
|
4847
|
+
* controller's view has been unbound (e.g., when a parent `when`
|
|
4848
|
+
* directive tears down and later recreates a child element).
|
|
4849
|
+
* @internal
|
|
4850
|
+
*/
|
|
4675
4851
|
handleChange(source, observer) {
|
|
4852
|
+
// https://github.com/microsoft/fast/issues/7444
|
|
4853
|
+
// This guard will be reconsidered in the next major version.
|
|
4854
|
+
if (!this.controller.isBound) {
|
|
4855
|
+
return;
|
|
4856
|
+
}
|
|
4676
4857
|
if (observer === this.dataBindingObserver) {
|
|
4677
4858
|
this.data = this.dataBindingObserver.bind(this.controller);
|
|
4678
4859
|
}
|
|
@@ -4753,13 +4934,7 @@ class RenderDirective {
|
|
|
4753
4934
|
}
|
|
4754
4935
|
}
|
|
4755
4936
|
HTMLDirective.define(RenderDirective);
|
|
4756
|
-
function isElementRenderOptions(object) {
|
|
4757
|
-
return !!object.element || !!object.tagName;
|
|
4758
|
-
}
|
|
4759
4937
|
const typeToInstructionLookup = new Map();
|
|
4760
|
-
/* eslint @typescript-eslint/naming-convention: "off"*/
|
|
4761
|
-
const defaultAttributes = { ":model": (x) => x };
|
|
4762
|
-
const brand = Symbol("RenderInstruction");
|
|
4763
4938
|
const defaultViewName = "default-view";
|
|
4764
4939
|
const nullTemplate = html `
|
|
4765
4940
|
|
|
@@ -4770,87 +4945,6 @@ function instructionToTemplate(def) {
|
|
|
4770
4945
|
}
|
|
4771
4946
|
return def.template;
|
|
4772
4947
|
}
|
|
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
4948
|
function getByType(type, name) {
|
|
4855
4949
|
const entries = typeToInstructionLookup.get(type);
|
|
4856
4950
|
if (entries === void 0) {
|
|
@@ -4864,63 +4958,6 @@ function getForInstance(object, name) {
|
|
|
4864
4958
|
}
|
|
4865
4959
|
return void 0;
|
|
4866
4960
|
}
|
|
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
4961
|
/**
|
|
4925
4962
|
* @internal
|
|
4926
4963
|
*/
|
|
@@ -5511,71 +5548,6 @@ class UnobservableMutationObserver extends MutationObserver {
|
|
|
5511
5548
|
}
|
|
5512
5549
|
}
|
|
5513
5550
|
}
|
|
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
5551
|
|
|
5580
5552
|
const defaultEventOptions = {
|
|
5581
5553
|
bubbles: true,
|