@microsoft/fast-html 1.0.0-alpha.22 → 1.0.0-alpha.23
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 +10 -0
- package/dist/dts/components/utilities.d.ts +11 -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 +132 -11
- package/dist/esm/components/utilities.spec.js +29 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/fast-html.d.ts +14 -0
- package/dist/fast-html.untrimmed.d.ts +14 -0
- 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
|
|
@@ -666,16 +706,19 @@ function assignProxyToItemsInArray(item, originalItem, schema, rootSchema) {
|
|
|
666
706
|
function assignProxyToItemsInObject(target, rootProperty, data, schema, rootSchema) {
|
|
667
707
|
var _a;
|
|
668
708
|
const type = getDataType(data);
|
|
709
|
+
const schemaProperties = getSchemaProperties(schema);
|
|
669
710
|
let proxiedData = data;
|
|
670
|
-
if (type === "object" &&
|
|
711
|
+
if (type === "object" && schemaProperties) {
|
|
671
712
|
// navigate through all items in the object
|
|
672
|
-
Object.keys(
|
|
673
|
-
if (proxiedData[property] && schema &&
|
|
674
|
-
proxiedData[property] = assignProxyToItemsInObject(target, rootProperty, proxiedData[property],
|
|
713
|
+
Object.keys(schemaProperties).forEach(property => {
|
|
714
|
+
if (proxiedData[property] && schema && schemaProperties) {
|
|
715
|
+
proxiedData[property] = assignProxyToItemsInObject(target, rootProperty, proxiedData[property], schemaProperties[property], rootSchema);
|
|
675
716
|
}
|
|
676
717
|
});
|
|
677
718
|
// assign a Proxy to the object
|
|
678
719
|
proxiedData = assignProxy(schema, rootSchema, target, rootProperty, data);
|
|
720
|
+
// Add this target to the object's target list
|
|
721
|
+
addTargetToObject(proxiedData, target, rootProperty);
|
|
679
722
|
}
|
|
680
723
|
else if (type === "array") {
|
|
681
724
|
const context = getDefFromRef(schema.items[refPropertyName]);
|
|
@@ -686,6 +729,37 @@ function assignProxyToItemsInObject(target, rootProperty, data, schema, rootSche
|
|
|
686
729
|
}
|
|
687
730
|
return proxiedData;
|
|
688
731
|
}
|
|
732
|
+
/**
|
|
733
|
+
* Add a target to an object's target list
|
|
734
|
+
* @param object - The object to associate with the target
|
|
735
|
+
* @param target - The target custom element
|
|
736
|
+
* @param rootProperty - The root property name
|
|
737
|
+
*/
|
|
738
|
+
function addTargetToObject(object, target, rootProperty) {
|
|
739
|
+
if (!objectTargetsMap.has(object)) {
|
|
740
|
+
objectTargetsMap.set(object, []);
|
|
741
|
+
}
|
|
742
|
+
const targets = objectTargetsMap.get(object);
|
|
743
|
+
targets.push({ target, rootProperty });
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Get all targets for an object
|
|
747
|
+
* @param object - The object to get targets for
|
|
748
|
+
* @returns Array of target info objects
|
|
749
|
+
*/
|
|
750
|
+
function getTargetsForObject(object) {
|
|
751
|
+
return objectTargetsMap.get(object) || [];
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Notify any observables mapped to the object
|
|
755
|
+
* @param targetObject The object that is mapped to a target and rootProperty
|
|
756
|
+
*/
|
|
757
|
+
function notifyObservables(targetObject) {
|
|
758
|
+
getTargetsForObject(targetObject).forEach((targetItem) => {
|
|
759
|
+
// Trigger notification for property changes
|
|
760
|
+
Observable.notify(targetItem.target, targetItem.rootProperty);
|
|
761
|
+
});
|
|
762
|
+
}
|
|
689
763
|
/**
|
|
690
764
|
* Assign a proxy to an object
|
|
691
765
|
* @param schema - The current schema
|
|
@@ -698,11 +772,10 @@ function assignProxyToItemsInObject(target, rootProperty, data, schema, rootSche
|
|
|
698
772
|
export function assignProxy(schema, rootSchema, target, rootProperty, object) {
|
|
699
773
|
if (object.$isProxy === undefined) {
|
|
700
774
|
// Create a proxy for the object that triggers Observable.notify on mutations
|
|
701
|
-
|
|
775
|
+
const proxy = new Proxy(object, {
|
|
702
776
|
set: (obj, prop, value) => {
|
|
703
777
|
obj[prop] = assignObservables(schema, rootSchema, value, target, rootProperty);
|
|
704
|
-
|
|
705
|
-
Observable.notify(target, rootProperty);
|
|
778
|
+
notifyObservables(proxy);
|
|
706
779
|
return true;
|
|
707
780
|
},
|
|
708
781
|
get: (target, key) => {
|
|
@@ -714,13 +787,13 @@ export function assignProxy(schema, rootSchema, target, rootProperty, object) {
|
|
|
714
787
|
deleteProperty: (obj, prop) => {
|
|
715
788
|
if (prop in obj) {
|
|
716
789
|
delete obj[prop];
|
|
717
|
-
|
|
718
|
-
Observable.notify(target, rootProperty);
|
|
790
|
+
notifyObservables(proxy);
|
|
719
791
|
return true;
|
|
720
792
|
}
|
|
721
793
|
return false;
|
|
722
794
|
},
|
|
723
795
|
});
|
|
796
|
+
return proxy;
|
|
724
797
|
}
|
|
725
798
|
return object;
|
|
726
799
|
}
|
|
@@ -737,3 +810,51 @@ export function getRootPropertyName(rootPropertyName, path, context, type) {
|
|
|
737
810
|
? path.split(".")[0]
|
|
738
811
|
: rootPropertyName;
|
|
739
812
|
}
|
|
813
|
+
/**
|
|
814
|
+
* Get details of bindings to the attributes of child custom elements
|
|
815
|
+
* @param previousString - The previous string before the binding
|
|
816
|
+
* @returns null, or a custom element name and attribute name
|
|
817
|
+
*/
|
|
818
|
+
export function getChildrenMap(previousString) {
|
|
819
|
+
if (typeof previousString === "string" &&
|
|
820
|
+
isAttribute(previousString, previousString.length)) {
|
|
821
|
+
const customElementName = getAttributesCustomElementName(previousString);
|
|
822
|
+
if (customElementName) {
|
|
823
|
+
return {
|
|
824
|
+
customElementName,
|
|
825
|
+
attributeName: getAttributeName(previousString),
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
return null;
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Get the HTML element that is passing the attribute binding
|
|
833
|
+
* @param previousString - The previous string before the binding
|
|
834
|
+
* @returns null if this is not a custom element, or the custom element that is passing the binding as an attribute
|
|
835
|
+
*/
|
|
836
|
+
function getAttributesCustomElementName(previousString) {
|
|
837
|
+
const indexOfElementTagStart = previousString.lastIndexOf("<") + 1;
|
|
838
|
+
const indexOfElementTagEnd = previousString.slice(indexOfElementTagStart).indexOf(" ") +
|
|
839
|
+
indexOfElementTagStart;
|
|
840
|
+
const elementName = previousString.slice(indexOfElementTagStart, indexOfElementTagEnd);
|
|
841
|
+
if (elementName.includes("-")) {
|
|
842
|
+
return elementName;
|
|
843
|
+
}
|
|
844
|
+
return null;
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Gets a non-aspected attribute name
|
|
848
|
+
* @param previousString - The previous string before the binding
|
|
849
|
+
* @returns The attribute name with any aspects (:, ?, @) removed
|
|
850
|
+
*/
|
|
851
|
+
function getAttributeName(previousString) {
|
|
852
|
+
const indexOfAttributeStart = previousString.lastIndexOf(" ") + 1;
|
|
853
|
+
const indexOfAttributeEnd = previousString.slice(indexOfAttributeStart).indexOf("=") + indexOfAttributeStart;
|
|
854
|
+
const attributeName = previousString.slice(indexOfAttributeStart, indexOfAttributeEnd);
|
|
855
|
+
const potentialAspect = attributeName.charAt(0);
|
|
856
|
+
if (potentialAspect === ":" || potentialAspect === "@" || potentialAspect === "?") {
|
|
857
|
+
return attributeName.slice(1);
|
|
858
|
+
}
|
|
859
|
+
return attributeName;
|
|
860
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { __awaiter } from "tslib";
|
|
2
2
|
import { expect, test } from "@playwright/test";
|
|
3
|
-
import { getNextBehavior, getIndexOfNextMatchingTag, pathResolver, transformInnerHTML, getExpressionChain, extractPathsFromChainedExpression, } from "./utilities.js";
|
|
3
|
+
import { getNextBehavior, getIndexOfNextMatchingTag, pathResolver, transformInnerHTML, getExpressionChain, extractPathsFromChainedExpression, getChildrenMap, } from "./utilities.js";
|
|
4
4
|
test.describe("utilities", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
5
5
|
test.describe("content", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
6
6
|
test("get the next content binding", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -359,4 +359,32 @@ test.describe("utilities", () => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
359
359
|
expect(paths.has("app.user.profile.settings.theme")).toBe(true);
|
|
360
360
|
}));
|
|
361
361
|
}));
|
|
362
|
+
test.describe("getChildrenMap", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
363
|
+
test("should get a ChildrenMap if an attribute is part of a custom element", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
364
|
+
const childrenMap = getChildrenMap(`<template><my-element foo="`);
|
|
365
|
+
expect(childrenMap).not.toBeNull();
|
|
366
|
+
expect(childrenMap === null || childrenMap === void 0 ? void 0 : childrenMap.attributeName).toEqual("foo");
|
|
367
|
+
expect(childrenMap === null || childrenMap === void 0 ? void 0 : childrenMap.customElementName).toEqual("my-element");
|
|
368
|
+
}));
|
|
369
|
+
test("should not get a ChildrenMap if an attribute is part of a non-custom element", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
370
|
+
const childrenMap = getChildrenMap(`<template><button foo="`);
|
|
371
|
+
expect(childrenMap).toBeNull();
|
|
372
|
+
}));
|
|
373
|
+
test("should remove any aspected attributes from an attribute name", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
374
|
+
const childrenMap = getChildrenMap(`<template><my-element :foo="`);
|
|
375
|
+
expect(childrenMap).not.toBeNull();
|
|
376
|
+
expect(childrenMap === null || childrenMap === void 0 ? void 0 : childrenMap.attributeName).toEqual("foo");
|
|
377
|
+
expect(childrenMap === null || childrenMap === void 0 ? void 0 : childrenMap.customElementName).toEqual("my-element");
|
|
378
|
+
}));
|
|
379
|
+
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* () {
|
|
380
|
+
const childrenMap = getChildrenMap(`<template><my-element>`);
|
|
381
|
+
expect(childrenMap).toBeNull();
|
|
382
|
+
}));
|
|
383
|
+
test("should get a ChildrenMap if there are multiple attributes are listed before this attribute", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
384
|
+
const childrenMap = getChildrenMap(`<template><my-element foo="" bar="" bat="`);
|
|
385
|
+
expect(childrenMap).not.toBeNull();
|
|
386
|
+
expect(childrenMap === null || childrenMap === void 0 ? void 0 : childrenMap.attributeName).toEqual("bat");
|
|
387
|
+
expect(childrenMap === null || childrenMap === void 0 ? void 0 : childrenMap.customElementName).toEqual("my-element");
|
|
388
|
+
}));
|
|
389
|
+
}));
|
|
362
390
|
}));
|