@microsoft/fast-html 1.0.0-alpha.16 → 1.0.0-alpha.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/dts/components/element.d.ts +10 -10
  2. package/dist/dts/components/index.d.ts +1 -0
  3. package/dist/dts/components/observer-map.d.ts +143 -0
  4. package/dist/dts/components/observer-map.spec.d.ts +1 -0
  5. package/dist/dts/components/template.d.ts +11 -0
  6. package/dist/dts/components/utilities.d.ts +60 -1
  7. package/dist/dts/fixtures/observer-map/main.d.ts +1 -0
  8. package/dist/dts/fixtures/observer-map/observer-map.spec.d.ts +1 -0
  9. package/dist/dts/index.d.ts +1 -1
  10. package/dist/esm/components/element.js +24 -17
  11. package/dist/esm/components/index.js +1 -0
  12. package/dist/esm/components/observer-map.js +551 -0
  13. package/dist/esm/components/observer-map.spec.js +613 -0
  14. package/dist/esm/components/template.js +103 -85
  15. package/dist/esm/components/utilities.js +301 -50
  16. package/dist/esm/components/utilities.spec.js +109 -1
  17. package/dist/esm/fixtures/attribute/main.js +3 -3
  18. package/dist/esm/fixtures/binding/main.js +5 -5
  19. package/dist/esm/fixtures/children/main.js +3 -3
  20. package/dist/esm/fixtures/dot-syntax/dot-syntax.spec.js +109 -2
  21. package/dist/esm/fixtures/dot-syntax/main.js +30 -4
  22. package/dist/esm/fixtures/event/main.js +6 -6
  23. package/dist/esm/fixtures/observer-map/main.js +304 -0
  24. package/dist/esm/fixtures/observer-map/observer-map.spec.js +174 -0
  25. package/dist/esm/fixtures/partial/main.js +3 -2
  26. package/dist/esm/fixtures/ref/main.js +3 -2
  27. package/dist/esm/fixtures/repeat/main.js +5 -5
  28. package/dist/esm/fixtures/slotted/main.js +3 -3
  29. package/dist/esm/fixtures/when/main.js +21 -21
  30. package/dist/esm/index.js +1 -1
  31. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  32. package/dist/fast-html.api.json +247 -32
  33. package/dist/fast-html.d.ts +214 -6
  34. package/dist/fast-html.untrimmed.d.ts +214 -6
  35. package/package.json +5 -4
@@ -1,7 +1,7 @@
1
1
  import { __decorate, __metadata } from "tslib";
2
2
  import { RenderableFASTElement, TemplateElement } from "@microsoft/fast-html";
3
- import { observable } from "@microsoft/fast-element";
4
- class TestElement extends RenderableFASTElement {
3
+ import { FASTElement, observable } from "@microsoft/fast-element";
4
+ class TestElement extends FASTElement {
5
5
  constructor() {
6
6
  super(...arguments);
7
7
  this.listItems = [];
@@ -16,7 +16,7 @@ __decorate([
16
16
  observable,
17
17
  __metadata("design:type", Array)
18
18
  ], TestElement.prototype, "list", void 0);
19
- TestElement.defineAsync({
19
+ RenderableFASTElement(TestElement).defineAsync({
20
20
  name: "test-element",
21
21
  templateOptions: "defer-and-hydrate",
22
22
  });
@@ -1,9 +1,116 @@
1
1
  import { __awaiter } from "tslib";
2
2
  import { expect, test } from "@playwright/test";
3
- test.describe("f-template", () => __awaiter(void 0, void 0, void 0, function* () {
3
+ test.describe("f-template dot-syntax bindings", () => __awaiter(void 0, void 0, void 0, function* () {
4
4
  test("create a object property reference using dot syntax in a binding", ({ page }) => __awaiter(void 0, void 0, void 0, function* () {
5
5
  yield page.goto("/dot-syntax");
6
6
  const customElement = yield page.locator("test-element");
7
- yield expect(customElement).toHaveText("bar");
7
+ yield expect(customElement.locator("span").nth(0)).toHaveText("bar");
8
+ }));
9
+ test("should display initial property values correctly", ({ page }) => __awaiter(void 0, void 0, void 0, function* () {
10
+ yield page.goto("/dot-syntax");
11
+ const customElement = yield page.locator("test-element");
12
+ // Check initial values
13
+ yield expect(customElement.locator("span").nth(0)).toHaveText("bar");
14
+ yield expect(customElement.locator("span").nth(1)).toHaveText("");
15
+ yield expect(customElement.locator("span").nth(2)).toHaveText("FOO");
16
+ }));
17
+ test("should update object.b when 'Set b' button is clicked", ({ page }) => __awaiter(void 0, void 0, void 0, function* () {
18
+ yield page.goto("/dot-syntax");
19
+ const customElement = yield page.locator("test-element");
20
+ const setBButton = customElement.locator("button").nth(0);
21
+ const bSpan = customElement.locator("span").nth(0);
22
+ // Verify initial state
23
+ yield expect(bSpan).toHaveText("bar");
24
+ // Click the "Set b" button
25
+ yield setBButton.click();
26
+ // Verify the value updated
27
+ yield expect(bSpan).toHaveText("Hello");
28
+ }));
29
+ test("should update object.a.b1 when 'Set a.b1' button is clicked", ({ page }) => __awaiter(void 0, void 0, void 0, function* () {
30
+ yield page.goto("/dot-syntax");
31
+ const customElement = yield page.locator("test-element");
32
+ const setAB1Button = customElement.locator("button").nth(1);
33
+ const ab1Span = customElement.locator("span").nth(1);
34
+ // Verify initial state (should be empty/undefined)
35
+ yield expect(ab1Span).toHaveText("");
36
+ // Click the "Set a.b1" button
37
+ yield setAB1Button.click();
38
+ // Verify the value updated
39
+ yield expect(ab1Span).toHaveText("World");
40
+ }));
41
+ test("should update object.a.b2.c when 'Set a.b2.c' button is clicked", ({ page }) => __awaiter(void 0, void 0, void 0, function* () {
42
+ yield page.goto("/dot-syntax");
43
+ const customElement = yield page.locator("test-element");
44
+ const setAB2CButton = customElement.locator("button").nth(2);
45
+ const ab2cSpan = customElement.locator("span").nth(2);
46
+ // Click the "Set a.b2.c" button
47
+ yield setAB2CButton.click();
48
+ // Verify the value updated
49
+ yield expect(ab2cSpan).toHaveText("Pluto");
50
+ }));
51
+ test("should handle multiple property updates independently", ({ page }) => __awaiter(void 0, void 0, void 0, function* () {
52
+ yield page.goto("/dot-syntax");
53
+ const customElement = yield page.locator("test-element");
54
+ const setBButton = customElement.locator("button").nth(0);
55
+ const setAB1Button = customElement.locator("button").nth(1);
56
+ const setAB2CButton = customElement.locator("button").nth(2);
57
+ const bSpan = customElement.locator("span").nth(0);
58
+ const ab1Span = customElement.locator("span").nth(1);
59
+ const ab2cSpan = customElement.locator("span").nth(2);
60
+ // Update multiple properties
61
+ yield setBButton.click();
62
+ yield setAB1Button.click();
63
+ yield setAB2CButton.click();
64
+ // Verify all values are updated correctly
65
+ yield expect(bSpan).toHaveText("Hello");
66
+ yield expect(ab1Span).toHaveText("World");
67
+ yield expect(ab2cSpan).toHaveText("Pluto");
68
+ }));
69
+ test("should maintain property values after multiple clicks", ({ page }) => __awaiter(void 0, void 0, void 0, function* () {
70
+ yield page.goto("/dot-syntax");
71
+ const customElement = yield page.locator("test-element");
72
+ const setBButton = customElement.locator("button").nth(0);
73
+ const bSpan = customElement.locator("span").nth(0);
74
+ // Click multiple times to ensure consistency
75
+ yield setBButton.click();
76
+ yield expect(bSpan).toHaveText("Hello");
77
+ yield setBButton.click();
78
+ yield expect(bSpan).toHaveText("Hello"); // Should remain the same
79
+ yield setBButton.click();
80
+ yield expect(bSpan).toHaveText("Hello"); // Should still be the same
81
+ }));
82
+ test("should update nested properties correctly", ({ page }) => __awaiter(void 0, void 0, void 0, function* () {
83
+ yield page.goto("/dot-syntax");
84
+ const customElement = yield page.locator("test-element");
85
+ const setAB1Button = customElement.locator("button").nth(1);
86
+ const setAB2CButton = customElement.locator("button").nth(2);
87
+ const ab1Span = customElement.locator("span").nth(1);
88
+ const ab2cSpan = customElement.locator("span").nth(2);
89
+ // Test nested property updates
90
+ yield setAB1Button.click();
91
+ yield expect(ab1Span).toHaveText("World");
92
+ yield setAB2CButton.click();
93
+ yield expect(ab2cSpan).toHaveText("Pluto");
94
+ // Verify both nested properties coexist
95
+ yield expect(ab1Span).toHaveText("World");
96
+ yield expect(ab2cSpan).toHaveText("Pluto");
97
+ }));
98
+ test("should have correct button labels", ({ page }) => __awaiter(void 0, void 0, void 0, function* () {
99
+ yield page.goto("/dot-syntax");
100
+ const customElement = yield page.locator("test-element");
101
+ // Verify button labels
102
+ yield expect(customElement.locator("button").nth(0)).toHaveText("Set b");
103
+ yield expect(customElement.locator("button").nth(1)).toHaveText("Set a.b1");
104
+ yield expect(customElement.locator("button").nth(2)).toHaveText("Set a.b2.c");
105
+ }));
106
+ test("should reflect property changes in DOM immediately", ({ page }) => __awaiter(void 0, void 0, void 0, function* () {
107
+ yield page.goto("/dot-syntax");
108
+ const customElement = yield page.locator("test-element");
109
+ const setBButton = customElement.locator("button").nth(0);
110
+ const bSpan = customElement.locator("span").nth(0);
111
+ // Verify immediate DOM update without additional waiting
112
+ yield setBButton.click();
113
+ // Should update immediately due to reactive system
114
+ yield expect(bSpan).toHaveText("Hello", { timeout: 1000 });
8
115
  }));
9
116
  }));
@@ -1,16 +1,42 @@
1
+ import { FASTElement } from "@microsoft/fast-element";
1
2
  import { RenderableFASTElement, TemplateElement } from "@microsoft/fast-html";
2
- class TestElement extends RenderableFASTElement {
3
+ class TestElement extends FASTElement {
3
4
  constructor() {
4
5
  super(...arguments);
5
6
  this.object = {
6
- foo: "bar",
7
+ b: "bar",
8
+ a: {
9
+ b2: {
10
+ c: "FOO",
11
+ },
12
+ },
13
+ };
14
+ this.handleBClick = () => {
15
+ this.object.b = "Hello";
16
+ };
17
+ this.handleAB1Click = () => {
18
+ if (this.object.a) {
19
+ this.object.a.b1 = "World";
20
+ }
21
+ else {
22
+ this.object.a = {
23
+ b1: "World",
24
+ };
25
+ }
26
+ };
27
+ this.handleAB2CClick = () => {
28
+ this.object.a.b2.c = "Pluto";
7
29
  };
8
30
  }
9
31
  }
10
- TestElement.defineAsync({
32
+ RenderableFASTElement(TestElement).defineAsync({
11
33
  name: "test-element",
12
34
  templateOptions: "defer-and-hydrate",
13
35
  });
14
- TemplateElement.define({
36
+ TemplateElement.options({
37
+ "test-element": {
38
+ observerMap: "all",
39
+ },
40
+ }).define({
15
41
  name: "f-template",
16
42
  });
@@ -1,7 +1,7 @@
1
1
  import { __decorate, __metadata } from "tslib";
2
2
  import { RenderableFASTElement, TemplateElement } from "@microsoft/fast-html";
3
- import { attr } from "@microsoft/fast-element";
4
- class TestElement extends RenderableFASTElement {
3
+ import { attr, FASTElement } from "@microsoft/fast-element";
4
+ class TestElement extends FASTElement {
5
5
  constructor() {
6
6
  super(...arguments);
7
7
  this.foo = "";
@@ -14,16 +14,16 @@ class TestElement extends RenderableFASTElement {
14
14
  this.handleAttributeArgClick = (foo) => {
15
15
  console.log(foo);
16
16
  };
17
- }
18
- handleModifyAttributeClick() {
19
- this.foo = "modified-by-click";
17
+ this.handleModifyAttributeClick = () => {
18
+ this.foo = "modified-by-click";
19
+ };
20
20
  }
21
21
  }
22
22
  __decorate([
23
23
  attr,
24
24
  __metadata("design:type", String)
25
25
  ], TestElement.prototype, "foo", void 0);
26
- TestElement.defineAsync({
26
+ RenderableFASTElement(TestElement).defineAsync({
27
27
  name: "test-element",
28
28
  templateOptions: "defer-and-hydrate",
29
29
  });
@@ -0,0 +1,304 @@
1
+ import { FASTElement } from "@microsoft/fast-element";
2
+ import { RenderableFASTElement, TemplateElement } from "../../components/index.js";
3
+ class ObserverMapTestElement extends FASTElement {
4
+ constructor() {
5
+ super(...arguments);
6
+ this.users = [
7
+ {
8
+ id: 1,
9
+ name: "Alice Johnson",
10
+ details: {
11
+ personal: {
12
+ age: 28,
13
+ location: {
14
+ city: "New York",
15
+ country: "USA",
16
+ coordinates: {
17
+ lat: 40.7128,
18
+ lng: -74.006,
19
+ },
20
+ },
21
+ },
22
+ preferences: {
23
+ theme: "dark",
24
+ notifications: {
25
+ email: true,
26
+ push: false,
27
+ settings: {
28
+ frequency: "daily",
29
+ categories: ["tech", "news"],
30
+ },
31
+ },
32
+ },
33
+ },
34
+ posts: [
35
+ {
36
+ id: 101,
37
+ title: "First Post",
38
+ content: "Hello World!",
39
+ metadata: {
40
+ views: 150,
41
+ likes: 25,
42
+ tags: ["introduction", "hello"],
43
+ author: {
44
+ name: "Alice Johnson",
45
+ verified: true,
46
+ },
47
+ },
48
+ },
49
+ {
50
+ id: 102,
51
+ title: "Tech Update",
52
+ content: "Latest in technology...",
53
+ metadata: {
54
+ views: 320,
55
+ likes: 45,
56
+ tags: ["tech", "update"],
57
+ author: {
58
+ name: "Alice Johnson",
59
+ verified: true,
60
+ },
61
+ },
62
+ },
63
+ ],
64
+ },
65
+ {
66
+ id: 2,
67
+ name: "Bob Smith",
68
+ details: {
69
+ personal: {
70
+ age: 35,
71
+ location: {
72
+ city: "London",
73
+ country: "UK",
74
+ coordinates: {
75
+ lat: 51.5074,
76
+ lng: -0.1278,
77
+ },
78
+ },
79
+ },
80
+ preferences: {
81
+ theme: "light",
82
+ notifications: {
83
+ email: false,
84
+ push: true,
85
+ settings: {
86
+ frequency: "weekly",
87
+ categories: ["sports", "music"],
88
+ },
89
+ },
90
+ },
91
+ },
92
+ posts: [
93
+ {
94
+ id: 201,
95
+ title: "Music Review",
96
+ content: "Amazing concert last night...",
97
+ metadata: {
98
+ views: 89,
99
+ likes: 12,
100
+ tags: ["music", "review"],
101
+ author: {
102
+ name: "Bob Smith",
103
+ verified: false,
104
+ },
105
+ },
106
+ },
107
+ ],
108
+ },
109
+ ];
110
+ this.selectedUserId = 1;
111
+ this.stats = {
112
+ totalUsers: 2,
113
+ activeUsers: 1,
114
+ metrics: {
115
+ engagement: {
116
+ daily: 45,
117
+ weekly: 120,
118
+ monthly: 500,
119
+ },
120
+ performance: {
121
+ loadTime: 1.2,
122
+ renderTime: 0.8,
123
+ },
124
+ },
125
+ };
126
+ // Methods to test deeply nested property changes
127
+ this.updateUserAge = (userId) => {
128
+ const user = this.users.find(u => u.id === userId);
129
+ if (user) {
130
+ user.details.personal.age += 1;
131
+ }
132
+ };
133
+ this.toggleUserTheme = (userId) => {
134
+ const user = this.users.find(u => u.id === userId);
135
+ if (user) {
136
+ user.details.preferences.theme =
137
+ user.details.preferences.theme === "dark" ? "light" : "dark";
138
+ }
139
+ };
140
+ this.updateUserLocation = (userId) => {
141
+ const user = this.users.find(u => u.id === userId);
142
+ if (user) {
143
+ // Use hardcoded values since we can only pass single argument
144
+ user.details.personal.location.city = "Tokyo";
145
+ user.details.personal.location.country = "Japan";
146
+ // Update coordinates randomly for demo
147
+ user.details.personal.location.coordinates.lat = Math.random() * 180 - 90;
148
+ user.details.personal.location.coordinates.lng = Math.random() * 360 - 180;
149
+ }
150
+ };
151
+ this.addPostToUser = (userId) => {
152
+ const user = this.users.find(u => u.id === userId);
153
+ if (user) {
154
+ const newPostId = Math.max(...user.posts.map((p) => p.id)) + 1;
155
+ user.posts.push({
156
+ id: newPostId,
157
+ title: `New Post ${newPostId}`,
158
+ content: `This is a new post with ID ${newPostId}`,
159
+ metadata: {
160
+ views: 0,
161
+ likes: 0,
162
+ tags: ["new", "auto-generated"],
163
+ author: {
164
+ name: user.name,
165
+ verified: Math.random() > 0.5,
166
+ },
167
+ },
168
+ });
169
+ }
170
+ };
171
+ this.incrementPostLikes = (postId) => {
172
+ // Find the post across all users since we only have postId
173
+ for (const user of this.users) {
174
+ const post = user.posts.find((p) => p.id === postId);
175
+ if (post) {
176
+ post.metadata.likes += 1;
177
+ post.metadata.views += Math.floor(Math.random() * 5) + 1;
178
+ break;
179
+ }
180
+ }
181
+ };
182
+ this.updateNotificationSettings = (userId) => {
183
+ const user = this.users.find(u => u.id === userId);
184
+ if (user) {
185
+ user.details.preferences.notifications.email =
186
+ !user.details.preferences.notifications.email;
187
+ user.details.preferences.notifications.push =
188
+ !user.details.preferences.notifications.push;
189
+ // Cycle through frequency options
190
+ const frequencies = ["daily", "weekly", "monthly"];
191
+ const currentIndex = frequencies.indexOf(user.details.preferences.notifications.settings.frequency);
192
+ const nextIndex = (currentIndex + 1) % frequencies.length;
193
+ user.details.preferences.notifications.settings.frequency =
194
+ frequencies[nextIndex];
195
+ }
196
+ };
197
+ this.addNotificationCategory = (userId) => {
198
+ const user = this.users.find(u => u.id === userId);
199
+ if (user) {
200
+ // Use hardcoded category since we can only pass single argument
201
+ const category = "sports";
202
+ if (!user.details.preferences.notifications.settings.categories.includes(category)) {
203
+ user.details.preferences.notifications.settings.categories.push(category);
204
+ }
205
+ }
206
+ };
207
+ this.removeNotificationCategory = (userId) => {
208
+ const user = this.users.find(u => u.id === userId);
209
+ if (user) {
210
+ // Use hardcoded category since we can only pass single argument
211
+ const category = "tech";
212
+ const index = user.details.preferences.notifications.settings.categories.indexOf(category);
213
+ if (index > -1) {
214
+ user.details.preferences.notifications.settings.categories.splice(index, 1);
215
+ }
216
+ }
217
+ };
218
+ this.addNewUser = () => {
219
+ const newId = Math.max(...this.users.map(u => u.id)) + 1;
220
+ this.users.push({
221
+ id: newId,
222
+ name: `User ${newId}`,
223
+ details: {
224
+ personal: {
225
+ age: Math.floor(Math.random() * 50) + 18,
226
+ location: {
227
+ city: "Random City",
228
+ country: "Random Country",
229
+ coordinates: {
230
+ lat: Math.random() * 180 - 90,
231
+ lng: Math.random() * 360 - 180,
232
+ },
233
+ },
234
+ },
235
+ preferences: {
236
+ theme: Math.random() > 0.5 ? "dark" : "light",
237
+ notifications: {
238
+ email: Math.random() > 0.5,
239
+ push: Math.random() > 0.5,
240
+ settings: {
241
+ frequency: ["daily", "weekly", "monthly"][Math.floor(Math.random() * 3)],
242
+ categories: ["general"],
243
+ },
244
+ },
245
+ },
246
+ },
247
+ posts: [],
248
+ });
249
+ this.stats.totalUsers = this.users.length;
250
+ };
251
+ this.removeUser = (userId) => {
252
+ const index = this.users.findIndex(u => u.id === userId);
253
+ if (index > -1) {
254
+ this.users.splice(index, 1);
255
+ this.stats.totalUsers = this.users.length;
256
+ }
257
+ };
258
+ this.updateStats = () => {
259
+ this.stats.metrics.engagement.daily += Math.floor(Math.random() * 10);
260
+ this.stats.metrics.engagement.weekly += Math.floor(Math.random() * 20);
261
+ this.stats.metrics.engagement.monthly += Math.floor(Math.random() * 50);
262
+ this.stats.metrics.performance.loadTime = Math.random() * 2 + 0.5;
263
+ this.stats.metrics.performance.renderTime = Math.random() * 1 + 0.3;
264
+ };
265
+ this.selectUser = (userId) => {
266
+ this.selectedUserId = userId;
267
+ };
268
+ }
269
+ }
270
+ RenderableFASTElement(ObserverMapTestElement).defineAsync({
271
+ name: "observer-map-test-element",
272
+ templateOptions: "defer-and-hydrate",
273
+ });
274
+ class ObserverMapInternalTestElement extends FASTElement {
275
+ constructor() {
276
+ super(...arguments);
277
+ this.a = {
278
+ b: {},
279
+ };
280
+ }
281
+ defineB() {
282
+ this.a.b = {
283
+ c: "Hello world",
284
+ };
285
+ }
286
+ updateC() {
287
+ this.a.b.c = "Hello pluto";
288
+ }
289
+ }
290
+ RenderableFASTElement(ObserverMapInternalTestElement).defineAsync({
291
+ name: "observer-map-internal-test-element",
292
+ templateOptions: "defer-and-hydrate",
293
+ });
294
+ // Configure TemplateElement with observerMap enabled for this test
295
+ TemplateElement.options({
296
+ "observer-map-test-element": {
297
+ observerMap: "all", // Enable ObserverMap to track all the nested property changes
298
+ },
299
+ "observer-map-internal-test-element": {
300
+ observerMap: "all", // Enable ObserverMap to track all the nested property changes
301
+ },
302
+ }).define({
303
+ name: "f-template",
304
+ });