@microsoft/fast-html 1.0.0-alpha.22 → 1.0.0-alpha.24
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/dist/dts/components/schema.d.ts +11 -1
- package/dist/dts/components/utilities.d.ts +19 -1
- package/dist/esm/components/schema.js +45 -11
- package/dist/esm/components/schema.spec.js +197 -2
- package/dist/esm/components/template.js +5 -5
- package/dist/esm/components/utilities.js +169 -17
- package/dist/esm/components/utilities.spec.js +72 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/fast-html.d.ts +15 -1
- package/dist/fast-html.untrimmed.d.ts +15 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Observable } from "@microsoft/fast-element/observable.js";
|
|
2
|
-
import { defsPropertyName, refPropertyName, } from "./schema.js";
|
|
2
|
+
import { defsPropertyName, refPropertyName, Schema, } from "./schema.js";
|
|
3
3
|
const openClientSideBinding = "{";
|
|
4
4
|
const closeClientSideBinding = "}";
|
|
5
5
|
const openContentBinding = "{{";
|
|
@@ -14,6 +14,10 @@ const startInnerHTMLDiv = `<div :innerHTML="{{`;
|
|
|
14
14
|
const startInnerHTMLDivLength = startInnerHTMLDiv.length;
|
|
15
15
|
const endInnerHTMLDiv = `}}"></div>`;
|
|
16
16
|
const endInnerHTMLDivLength = endInnerHTMLDiv.length;
|
|
17
|
+
/**
|
|
18
|
+
* A map of proxied objects
|
|
19
|
+
*/
|
|
20
|
+
const objectTargetsMap = new WeakMap();
|
|
17
21
|
/**
|
|
18
22
|
* Get the index of the next matching tag
|
|
19
23
|
* @param openingTagStartSlice - The slice starting from the opening tag
|
|
@@ -281,9 +285,10 @@ function pathWithContextResolver(splitPath, self) {
|
|
|
281
285
|
}, accessibleObject);
|
|
282
286
|
};
|
|
283
287
|
}
|
|
284
|
-
export function bindingResolver(rootPropertyName, path, parentContext, type, schema, currentContext, level) {
|
|
288
|
+
export function bindingResolver(previousString, rootPropertyName, path, parentContext, type, schema, currentContext, level) {
|
|
285
289
|
rootPropertyName = getRootPropertyName(rootPropertyName, path, currentContext, type);
|
|
286
290
|
if (type !== "event" && rootPropertyName !== null) {
|
|
291
|
+
const childrenMap = getChildrenMap(previousString);
|
|
287
292
|
schema.addPath({
|
|
288
293
|
pathConfig: {
|
|
289
294
|
type,
|
|
@@ -292,6 +297,7 @@ export function bindingResolver(rootPropertyName, path, parentContext, type, sch
|
|
|
292
297
|
path,
|
|
293
298
|
},
|
|
294
299
|
rootPropertyName,
|
|
300
|
+
childrenMap,
|
|
295
301
|
});
|
|
296
302
|
}
|
|
297
303
|
return pathResolver(path, currentContext, level, schema.getSchema(rootPropertyName));
|
|
@@ -574,6 +580,40 @@ function getDataType(data) {
|
|
|
574
580
|
return "object";
|
|
575
581
|
return "primitive";
|
|
576
582
|
}
|
|
583
|
+
/**
|
|
584
|
+
* Get properties from an anyOf array
|
|
585
|
+
* @param anyOf - The anyOf array in a JSON schema
|
|
586
|
+
* @returns The array item matching a ref if it exists
|
|
587
|
+
*/
|
|
588
|
+
function getSchemaPropertiesFromAnyOf(anyOf) {
|
|
589
|
+
let propertiesFromAnyOf = null;
|
|
590
|
+
for (const anyOfItem of anyOf) {
|
|
591
|
+
if (anyOfItem[refPropertyName]) {
|
|
592
|
+
const splitRef = anyOfItem[refPropertyName].split("/");
|
|
593
|
+
const customElement = splitRef.slice(-2)[0];
|
|
594
|
+
const attributeName = splitRef.slice(-1)[0].slice(0, -5);
|
|
595
|
+
if (Schema.jsonSchemaMap.has(customElement)) {
|
|
596
|
+
const customElementSchemaMap = Schema.jsonSchemaMap.get(customElement);
|
|
597
|
+
propertiesFromAnyOf = customElementSchemaMap.get(attributeName);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return propertiesFromAnyOf;
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Gets a properties definition if one exists
|
|
605
|
+
* @param schema - The JSON schema to get properties from
|
|
606
|
+
* @returns A JSON schema with properties or null
|
|
607
|
+
*/
|
|
608
|
+
function getSchemaProperties(schema) {
|
|
609
|
+
if (schema === null || schema === void 0 ? void 0 : schema.properties) {
|
|
610
|
+
return schema.properties;
|
|
611
|
+
}
|
|
612
|
+
else if (schema === null || schema === void 0 ? void 0 : schema.anyOf) {
|
|
613
|
+
return getSchemaPropertiesFromAnyOf(schema.anyOf);
|
|
614
|
+
}
|
|
615
|
+
return null;
|
|
616
|
+
}
|
|
577
617
|
/**
|
|
578
618
|
* Assigns Observable properties to items in an array and sets up change notifications
|
|
579
619
|
* @param proxiedData - The array data to make observable
|
|
@@ -612,6 +652,33 @@ function getDefFromRef(defName) {
|
|
|
612
652
|
const splitName = defName.split("/");
|
|
613
653
|
return splitName.at(-1);
|
|
614
654
|
}
|
|
655
|
+
/**
|
|
656
|
+
* Find a definition
|
|
657
|
+
* This may exist as a $ref at the root or as a $ref in any anyOf or not at all
|
|
658
|
+
* if the Observer Map has not been enabled on a child component
|
|
659
|
+
* @param schema - The JSON schema to find the ref in
|
|
660
|
+
* @returns The definition or null
|
|
661
|
+
*/
|
|
662
|
+
export function findDef(schema) {
|
|
663
|
+
const defStartingString = "#/$defs";
|
|
664
|
+
if (schema[refPropertyName] &&
|
|
665
|
+
schema[refPropertyName].startsWith(defStartingString)) {
|
|
666
|
+
return getDefFromRef(schema[refPropertyName]);
|
|
667
|
+
}
|
|
668
|
+
if (schema.anyOf) {
|
|
669
|
+
const index = schema.anyOf.findIndex((anyOfItem) => {
|
|
670
|
+
return (!!anyOfItem[refPropertyName] &&
|
|
671
|
+
anyOfItem[refPropertyName].startsWith(defStartingString));
|
|
672
|
+
});
|
|
673
|
+
if (index > -1) {
|
|
674
|
+
const ref = schema.anyOf[index][refPropertyName];
|
|
675
|
+
if (ref.startsWith(defStartingString)) {
|
|
676
|
+
return getDefFromRef(ref);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
615
682
|
/**
|
|
616
683
|
* Assign observables to data
|
|
617
684
|
* @param schema - The schema
|
|
@@ -627,8 +694,10 @@ export function assignObservables(schema, rootSchema, data, target, rootProperty
|
|
|
627
694
|
let proxiedData = data;
|
|
628
695
|
switch (dataType) {
|
|
629
696
|
case "array": {
|
|
630
|
-
const context =
|
|
631
|
-
|
|
697
|
+
const context = findDef(schema);
|
|
698
|
+
if (context) {
|
|
699
|
+
proxiedData = assignObservablesToArray(proxiedData, (_a = rootSchema[defsPropertyName]) === null || _a === void 0 ? void 0 : _a[context], rootSchema);
|
|
700
|
+
}
|
|
632
701
|
break;
|
|
633
702
|
}
|
|
634
703
|
case "object": {
|
|
@@ -666,26 +735,62 @@ function assignProxyToItemsInArray(item, originalItem, schema, rootSchema) {
|
|
|
666
735
|
function assignProxyToItemsInObject(target, rootProperty, data, schema, rootSchema) {
|
|
667
736
|
var _a;
|
|
668
737
|
const type = getDataType(data);
|
|
738
|
+
const schemaProperties = getSchemaProperties(schema);
|
|
669
739
|
let proxiedData = data;
|
|
670
|
-
if (type === "object" &&
|
|
740
|
+
if (type === "object" && schemaProperties) {
|
|
671
741
|
// navigate through all items in the object
|
|
672
|
-
Object.keys(
|
|
673
|
-
if (proxiedData[property] && schema &&
|
|
674
|
-
proxiedData[property] = assignProxyToItemsInObject(target, rootProperty, proxiedData[property],
|
|
742
|
+
Object.keys(schemaProperties).forEach(property => {
|
|
743
|
+
if (proxiedData[property] && schema && schemaProperties) {
|
|
744
|
+
proxiedData[property] = assignProxyToItemsInObject(target, rootProperty, proxiedData[property], schemaProperties[property], rootSchema);
|
|
675
745
|
}
|
|
676
746
|
});
|
|
677
747
|
// assign a Proxy to the object
|
|
678
748
|
proxiedData = assignProxy(schema, rootSchema, target, rootProperty, data);
|
|
749
|
+
// Add this target to the object's target list
|
|
750
|
+
addTargetToObject(proxiedData, target, rootProperty);
|
|
679
751
|
}
|
|
680
752
|
else if (type === "array") {
|
|
681
|
-
const context =
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
753
|
+
const context = findDef(schema.items);
|
|
754
|
+
if (context) {
|
|
755
|
+
const definition = (_a = rootSchema[defsPropertyName]) === null || _a === void 0 ? void 0 : _a[context];
|
|
756
|
+
if ((definition === null || definition === void 0 ? void 0 : definition.type) === "object") {
|
|
757
|
+
proxiedData = assignObservablesToArray(proxiedData, definition, rootSchema);
|
|
758
|
+
}
|
|
685
759
|
}
|
|
686
760
|
}
|
|
687
761
|
return proxiedData;
|
|
688
762
|
}
|
|
763
|
+
/**
|
|
764
|
+
* Add a target to an object's target list
|
|
765
|
+
* @param object - The object to associate with the target
|
|
766
|
+
* @param target - The target custom element
|
|
767
|
+
* @param rootProperty - The root property name
|
|
768
|
+
*/
|
|
769
|
+
function addTargetToObject(object, target, rootProperty) {
|
|
770
|
+
if (!objectTargetsMap.has(object)) {
|
|
771
|
+
objectTargetsMap.set(object, []);
|
|
772
|
+
}
|
|
773
|
+
const targets = objectTargetsMap.get(object);
|
|
774
|
+
targets.push({ target, rootProperty });
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Get all targets for an object
|
|
778
|
+
* @param object - The object to get targets for
|
|
779
|
+
* @returns Array of target info objects
|
|
780
|
+
*/
|
|
781
|
+
function getTargetsForObject(object) {
|
|
782
|
+
return objectTargetsMap.get(object) || [];
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Notify any observables mapped to the object
|
|
786
|
+
* @param targetObject The object that is mapped to a target and rootProperty
|
|
787
|
+
*/
|
|
788
|
+
function notifyObservables(targetObject) {
|
|
789
|
+
getTargetsForObject(targetObject).forEach((targetItem) => {
|
|
790
|
+
// Trigger notification for property changes
|
|
791
|
+
Observable.notify(targetItem.target, targetItem.rootProperty);
|
|
792
|
+
});
|
|
793
|
+
}
|
|
689
794
|
/**
|
|
690
795
|
* Assign a proxy to an object
|
|
691
796
|
* @param schema - The current schema
|
|
@@ -698,11 +803,10 @@ function assignProxyToItemsInObject(target, rootProperty, data, schema, rootSche
|
|
|
698
803
|
export function assignProxy(schema, rootSchema, target, rootProperty, object) {
|
|
699
804
|
if (object.$isProxy === undefined) {
|
|
700
805
|
// Create a proxy for the object that triggers Observable.notify on mutations
|
|
701
|
-
|
|
806
|
+
const proxy = new Proxy(object, {
|
|
702
807
|
set: (obj, prop, value) => {
|
|
703
808
|
obj[prop] = assignObservables(schema, rootSchema, value, target, rootProperty);
|
|
704
|
-
|
|
705
|
-
Observable.notify(target, rootProperty);
|
|
809
|
+
notifyObservables(proxy);
|
|
706
810
|
return true;
|
|
707
811
|
},
|
|
708
812
|
get: (target, key) => {
|
|
@@ -714,13 +818,13 @@ export function assignProxy(schema, rootSchema, target, rootProperty, object) {
|
|
|
714
818
|
deleteProperty: (obj, prop) => {
|
|
715
819
|
if (prop in obj) {
|
|
716
820
|
delete obj[prop];
|
|
717
|
-
|
|
718
|
-
Observable.notify(target, rootProperty);
|
|
821
|
+
notifyObservables(proxy);
|
|
719
822
|
return true;
|
|
720
823
|
}
|
|
721
824
|
return false;
|
|
722
825
|
},
|
|
723
826
|
});
|
|
827
|
+
return proxy;
|
|
724
828
|
}
|
|
725
829
|
return object;
|
|
726
830
|
}
|
|
@@ -737,3 +841,51 @@ export function getRootPropertyName(rootPropertyName, path, context, type) {
|
|
|
737
841
|
? path.split(".")[0]
|
|
738
842
|
: rootPropertyName;
|
|
739
843
|
}
|
|
844
|
+
/**
|
|
845
|
+
* Get details of bindings to the attributes of child custom elements
|
|
846
|
+
* @param previousString - The previous string before the binding
|
|
847
|
+
* @returns null, or a custom element name and attribute name
|
|
848
|
+
*/
|
|
849
|
+
export function getChildrenMap(previousString) {
|
|
850
|
+
if (typeof previousString === "string" &&
|
|
851
|
+
isAttribute(previousString, previousString.length)) {
|
|
852
|
+
const customElementName = getAttributesCustomElementName(previousString);
|
|
853
|
+
if (customElementName) {
|
|
854
|
+
return {
|
|
855
|
+
customElementName,
|
|
856
|
+
attributeName: getAttributeName(previousString),
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
return null;
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Get the HTML element that is passing the attribute binding
|
|
864
|
+
* @param previousString - The previous string before the binding
|
|
865
|
+
* @returns null if this is not a custom element, or the custom element that is passing the binding as an attribute
|
|
866
|
+
*/
|
|
867
|
+
function getAttributesCustomElementName(previousString) {
|
|
868
|
+
const indexOfElementTagStart = previousString.lastIndexOf("<") + 1;
|
|
869
|
+
const indexOfElementTagEnd = previousString.slice(indexOfElementTagStart).indexOf(" ") +
|
|
870
|
+
indexOfElementTagStart;
|
|
871
|
+
const elementName = previousString.slice(indexOfElementTagStart, indexOfElementTagEnd);
|
|
872
|
+
if (elementName.includes("-")) {
|
|
873
|
+
return elementName;
|
|
874
|
+
}
|
|
875
|
+
return null;
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* Gets a non-aspected attribute name
|
|
879
|
+
* @param previousString - The previous string before the binding
|
|
880
|
+
* @returns The attribute name with any aspects (:, ?, @) removed
|
|
881
|
+
*/
|
|
882
|
+
function getAttributeName(previousString) {
|
|
883
|
+
const indexOfAttributeStart = previousString.lastIndexOf(" ") + 1;
|
|
884
|
+
const indexOfAttributeEnd = previousString.slice(indexOfAttributeStart).indexOf("=") + indexOfAttributeStart;
|
|
885
|
+
const attributeName = previousString.slice(indexOfAttributeStart, indexOfAttributeEnd);
|
|
886
|
+
const potentialAspect = attributeName.charAt(0);
|
|
887
|
+
if (potentialAspect === ":" || potentialAspect === "@" || potentialAspect === "?") {
|
|
888
|
+
return attributeName.slice(1);
|
|
889
|
+
}
|
|
890
|
+
return attributeName;
|
|
891
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { __awaiter } from "tslib";
|
|
2
2
|
import { expect, test } from "@playwright/test";
|
|
3
|
-
import {
|
|
3
|
+
import { refPropertyName } from "./schema.js";
|
|
4
|
+
import { getNextBehavior, getIndexOfNextMatchingTag, pathResolver, transformInnerHTML, getExpressionChain, extractPathsFromChainedExpression, getChildrenMap, findDef, } from "./utilities.js";
|
|
4
5
|
test.describe("utilities", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
5
6
|
test.describe("content", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
6
7
|
test("get the next content binding", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -359,4 +360,74 @@ test.describe("utilities", () => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
359
360
|
expect(paths.has("app.user.profile.settings.theme")).toBe(true);
|
|
360
361
|
}));
|
|
361
362
|
}));
|
|
363
|
+
test.describe("getChildrenMap", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
364
|
+
test("should get a ChildrenMap if an attribute is part of a custom element", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
365
|
+
const childrenMap = getChildrenMap(`<template><my-element foo="`);
|
|
366
|
+
expect(childrenMap).not.toBeNull();
|
|
367
|
+
expect(childrenMap === null || childrenMap === void 0 ? void 0 : childrenMap.attributeName).toEqual("foo");
|
|
368
|
+
expect(childrenMap === null || childrenMap === void 0 ? void 0 : childrenMap.customElementName).toEqual("my-element");
|
|
369
|
+
}));
|
|
370
|
+
test("should not get a ChildrenMap if an attribute is part of a non-custom element", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
371
|
+
const childrenMap = getChildrenMap(`<template><button foo="`);
|
|
372
|
+
expect(childrenMap).toBeNull();
|
|
373
|
+
}));
|
|
374
|
+
test("should remove any aspected attributes from an attribute name", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
375
|
+
const childrenMap = getChildrenMap(`<template><my-element :foo="`);
|
|
376
|
+
expect(childrenMap).not.toBeNull();
|
|
377
|
+
expect(childrenMap === null || childrenMap === void 0 ? void 0 : childrenMap.attributeName).toEqual("foo");
|
|
378
|
+
expect(childrenMap === null || childrenMap === void 0 ? void 0 : childrenMap.customElementName).toEqual("my-element");
|
|
379
|
+
}));
|
|
380
|
+
test("should not get a ChildreMap if the previous string indicates the binding was not an attribute", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
381
|
+
const childrenMap = getChildrenMap(`<template><my-element>`);
|
|
382
|
+
expect(childrenMap).toBeNull();
|
|
383
|
+
}));
|
|
384
|
+
test("should get a ChildrenMap if there are multiple attributes are listed before this attribute", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
385
|
+
const childrenMap = getChildrenMap(`<template><my-element foo="" bar="" bat="`);
|
|
386
|
+
expect(childrenMap).not.toBeNull();
|
|
387
|
+
expect(childrenMap === null || childrenMap === void 0 ? void 0 : childrenMap.attributeName).toEqual("bat");
|
|
388
|
+
expect(childrenMap === null || childrenMap === void 0 ? void 0 : childrenMap.customElementName).toEqual("my-element");
|
|
389
|
+
}));
|
|
390
|
+
}));
|
|
391
|
+
test.describe("schema functions", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
392
|
+
test.describe("findDef", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
393
|
+
test("should resolve from the root of a schema", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
394
|
+
expect(findDef({
|
|
395
|
+
[refPropertyName]: "#/$defs/MyType"
|
|
396
|
+
})).toEqual("MyType");
|
|
397
|
+
}));
|
|
398
|
+
test("should resolve as null from an anyOf array containing a reference to another component", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
399
|
+
expect(findDef({
|
|
400
|
+
anyOf: [
|
|
401
|
+
{
|
|
402
|
+
[refPropertyName]: "https://fast.design/schemas/test-element/c.json"
|
|
403
|
+
}
|
|
404
|
+
]
|
|
405
|
+
})).toEqual(null);
|
|
406
|
+
}));
|
|
407
|
+
test("should resolve from an anyOf array containing a reference to a $def", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
408
|
+
expect(findDef({
|
|
409
|
+
anyOf: [
|
|
410
|
+
{
|
|
411
|
+
[refPropertyName]: "#/$defs/MyType"
|
|
412
|
+
}
|
|
413
|
+
]
|
|
414
|
+
})).toEqual("MyType");
|
|
415
|
+
}));
|
|
416
|
+
test("should resolve from an anyOf array containing a reference to another component and a reference to a $def", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
417
|
+
expect(findDef({
|
|
418
|
+
anyOf: [
|
|
419
|
+
{
|
|
420
|
+
[refPropertyName]: "https://fast.design/schemas/test-element/c.json"
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
[refPropertyName]: "#/$defs/MyType"
|
|
424
|
+
}
|
|
425
|
+
]
|
|
426
|
+
})).toEqual("MyType");
|
|
427
|
+
}));
|
|
428
|
+
test("should resolve as null if not found", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
429
|
+
expect(findDef({})).toEqual(null);
|
|
430
|
+
}));
|
|
431
|
+
}));
|
|
432
|
+
}));
|
|
362
433
|
}));
|