@microsoft/fast-element 2.0.0-beta.9 → 2.0.0
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/.eslintrc.json +1 -1
- package/CHANGELOG.json +497 -0
- package/CHANGELOG.md +172 -1
- package/README.md +1 -9
- package/api-extractor.context.json +14 -0
- package/api-extractor.di.json +14 -0
- package/dist/context/context.api.json +1068 -0
- package/dist/di/di.api.json +4929 -0
- package/dist/dts/binding/binding.d.ts +49 -0
- package/dist/dts/binding/normalize.d.ts +9 -0
- package/dist/dts/binding/one-time.d.ts +11 -0
- package/dist/dts/binding/one-way.d.ts +20 -0
- package/dist/dts/{templating/binding-signal.d.ts → binding/signal.d.ts} +19 -4
- package/dist/dts/{templating/binding-two-way.d.ts → binding/two-way.d.ts} +9 -5
- package/dist/dts/components/attributes.d.ts +6 -0
- package/dist/dts/components/element-controller.d.ts +104 -8
- package/dist/dts/components/element-hydration.d.ts +2 -0
- package/dist/dts/components/fast-definitions.d.ts +6 -0
- package/dist/dts/components/hydration.d.ts +56 -0
- package/dist/dts/components/install-hydration.d.ts +1 -0
- package/dist/dts/context.d.ts +29 -15
- package/dist/dts/di/di.d.ts +0 -5
- package/dist/dts/dom-policy.d.ts +83 -0
- package/dist/dts/dom.d.ts +100 -0
- package/dist/dts/hydration/target-builder.d.ts +63 -0
- package/dist/dts/index.d.ts +33 -26
- package/dist/dts/index.rollup.d.ts +0 -1
- package/dist/dts/index.rollup.debug.d.ts +0 -1
- package/dist/dts/interfaces.d.ts +32 -82
- package/dist/dts/metadata.d.ts +6 -5
- package/dist/dts/observation/arrays.d.ts +1 -1
- package/dist/dts/observation/observable.bench.d.ts +18 -0
- package/dist/dts/observation/observable.d.ts +5 -5
- package/dist/dts/pending-task.d.ts +19 -7
- package/dist/dts/platform.d.ts +11 -2
- package/dist/dts/polyfills.d.ts +0 -8
- package/dist/dts/styles/css-binding-directive.d.ts +60 -0
- package/dist/dts/styles/css.d.ts +9 -7
- package/dist/dts/styles/element-styles.d.ts +1 -14
- package/dist/dts/styles/host.d.ts +2 -5
- package/dist/dts/styles/style-strategy.d.ts +42 -0
- package/dist/dts/templating/compiler.d.ts +11 -13
- package/dist/dts/templating/{binding.d.ts → html-binding-directive.d.ts} +21 -41
- package/dist/dts/templating/html-directive.d.ts +44 -140
- package/dist/dts/templating/install-hydratable-view-templates.d.ts +1 -0
- package/dist/dts/templating/node-observation.d.ts +11 -1
- package/dist/dts/templating/ref.d.ts +4 -0
- package/dist/dts/templating/render.bench.d.ts +3 -0
- package/dist/dts/templating/render.d.ts +49 -9
- package/dist/dts/templating/repeat-basic-reverse.bench.d.ts +3 -0
- package/dist/dts/templating/repeat-basic-shift.bench.d.ts +3 -0
- package/dist/dts/templating/repeat.d.ts +31 -9
- package/dist/dts/templating/template.d.ts +97 -12
- package/dist/dts/templating/view.d.ts +146 -29
- package/dist/dts/templating/when-basic.bench.d.ts +3 -0
- package/dist/dts/templating/when-conditional.bench.d.ts +3 -0
- package/dist/dts/templating/when-switch.bench.d.ts +3 -0
- package/dist/dts/templating/when.d.ts +3 -1
- package/dist/dts/testing/fakes.d.ts +12 -1
- package/dist/dts/tsdoc-metadata.json +1 -1
- package/dist/dts/utilities.d.ts +55 -1
- package/dist/esm/binding/binding.js +18 -0
- package/dist/esm/binding/normalize.js +17 -0
- package/dist/esm/binding/one-time.js +21 -0
- package/dist/esm/binding/one-way.js +30 -0
- package/dist/esm/{templating/binding-signal.js → binding/signal.js} +22 -6
- package/dist/esm/{templating/binding-two-way.js → binding/two-way.js} +18 -12
- package/dist/esm/components/attributes.js +16 -1
- package/dist/esm/components/element-controller.js +319 -49
- package/dist/esm/components/element-hydration.js +2 -0
- package/dist/esm/components/fast-definitions.js +12 -4
- package/dist/esm/components/fast-element.js +3 -1
- package/dist/esm/components/hydration.js +104 -0
- package/dist/esm/components/install-hydration.js +3 -0
- package/dist/esm/context.js +26 -4
- package/dist/esm/debug.js +8 -2
- package/dist/esm/di/di.js +9 -12
- package/dist/esm/dom-policy.js +345 -0
- package/dist/esm/dom.js +101 -0
- package/dist/esm/hydration/target-builder.js +175 -0
- package/dist/esm/index.js +34 -25
- package/dist/esm/index.rollup.debug.js +3 -1
- package/dist/esm/index.rollup.js +3 -1
- package/dist/esm/interfaces.js +51 -3
- package/dist/esm/metadata.js +11 -8
- package/dist/esm/observation/arrays.js +1 -1
- package/dist/esm/observation/observable.bench.js +79 -0
- package/dist/esm/observation/observable.js +20 -15
- package/dist/esm/observation/update-queue.js +2 -2
- package/dist/esm/pending-task.js +13 -1
- package/dist/esm/platform.js +12 -2
- package/dist/esm/polyfills.js +3 -61
- package/dist/esm/styles/css-binding-directive.js +76 -0
- package/dist/esm/styles/css.js +14 -7
- package/dist/esm/styles/element-styles.js +0 -33
- package/dist/esm/styles/style-strategy.js +1 -0
- package/dist/esm/templating/children.js +8 -4
- package/dist/esm/templating/compiler.js +37 -44
- package/dist/esm/templating/html-binding-directive.js +218 -0
- package/dist/esm/templating/html-directive.js +25 -152
- package/dist/esm/templating/install-hydratable-view-templates.js +17 -0
- package/dist/esm/templating/node-observation.js +14 -8
- package/dist/esm/templating/ref.js +1 -1
- package/dist/esm/templating/render.bench.js +56 -0
- package/dist/esm/templating/render.js +74 -30
- package/dist/esm/templating/repeat-basic-reverse.bench.js +43 -0
- package/dist/esm/templating/repeat-basic-shift.bench.js +43 -0
- package/dist/esm/templating/repeat.js +116 -17
- package/dist/esm/templating/template.js +135 -60
- package/dist/esm/templating/view.js +254 -34
- package/dist/esm/templating/when-basic.bench.js +36 -0
- package/dist/esm/templating/when-conditional.bench.js +39 -0
- package/dist/esm/templating/when-switch.bench.js +68 -0
- package/dist/esm/templating/when.js +12 -5
- package/dist/esm/testing/fakes.js +32 -1
- package/dist/esm/testing/fixture.js +1 -1
- package/dist/esm/utilities.js +97 -1
- package/dist/fast-element.api.json +9788 -5666
- package/dist/fast-element.d.ts +813 -2392
- package/dist/fast-element.debug.js +2785 -969
- package/dist/fast-element.debug.min.js +3 -1
- package/dist/fast-element.js +2638 -828
- package/dist/fast-element.min.js +3 -1
- package/dist/fast-element.untrimmed.d.ts +661 -313
- package/docs/{api-report.md → api-report.api.md} +238 -151
- package/docs/context/api-report.api.md +69 -0
- package/docs/di/api-report.api.md +315 -0
- package/karma.conf.cjs +2 -1
- package/package.json +59 -47
- package/scripts/run-api-extractor.js +51 -0
- package/scripts/run-benchmarks.js +46 -0
- package/tensile.config.js +12 -0
- package/dist/dts/templating/dom.d.ts +0 -41
- package/dist/esm/templating/binding.js +0 -282
- package/dist/esm/templating/dom.js +0 -49
- package/docs/guide/declaring-templates.md +0 -230
- package/docs/guide/defining-elements.md +0 -214
- package/docs/guide/leveraging-css.md +0 -253
- package/docs/guide/next-steps.md +0 -13
- package/docs/guide/observables-and-state.md +0 -213
- package/docs/guide/using-directives.md +0 -576
- package/docs/guide/working-with-shadow-dom.md +0 -296
|
@@ -1,576 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: using-directives
|
|
3
|
-
title: Using Directives
|
|
4
|
-
sidebar_label: Using Directives
|
|
5
|
-
custom_edit_url: https://github.com/microsoft/fast/edit/master/packages/web-components/fast-element/docs/guide/using-directives.md
|
|
6
|
-
description: In addition to declaring dynamic parts of templates with expressions, you also have access to several powerful directives, which aid in common scenarios.
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
In addition to declaring dynamic parts of templates with expressions, you also have access to several powerful *directives*, which aid in common scenarios.
|
|
10
|
-
|
|
11
|
-
## Structural directives
|
|
12
|
-
|
|
13
|
-
Structural directives change the shape of the DOM itself by adding and removing nodes based on the state of your element.
|
|
14
|
-
|
|
15
|
-
### The `when` directive
|
|
16
|
-
|
|
17
|
-
The `when` directive enables you to conditionally render blocks of HTML. When you provide an expression to `when` it will render the child template into the DOM when the expression evaluates to `true` and remove the child template when it evaluates to `false` (or if it is never `true`, the rendering will be skipped entirely).
|
|
18
|
-
|
|
19
|
-
**Example: Conditional Rendering**
|
|
20
|
-
|
|
21
|
-
```ts
|
|
22
|
-
import { FASTElement, customElement, observable, html, when } from '@microsoft/fast-element';
|
|
23
|
-
|
|
24
|
-
const template = html<MyApp>`
|
|
25
|
-
<h1>My App</h1>
|
|
26
|
-
|
|
27
|
-
${when(x => !x.ready, html<MyApp>`
|
|
28
|
-
Loading...
|
|
29
|
-
`)}
|
|
30
|
-
`;
|
|
31
|
-
|
|
32
|
-
@customElement({
|
|
33
|
-
name: 'my-app',
|
|
34
|
-
template
|
|
35
|
-
})
|
|
36
|
-
export class MyApp extends FASTElement {
|
|
37
|
-
@observable ready: boolean = false;
|
|
38
|
-
@observable data: any = null;
|
|
39
|
-
|
|
40
|
-
connectedCallback() {
|
|
41
|
-
super.connectedCallback();
|
|
42
|
-
this.loadData();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async loadData() {
|
|
46
|
-
const response = await fetch('some/resource');
|
|
47
|
-
const data = await response.json();
|
|
48
|
-
|
|
49
|
-
this.data = data;
|
|
50
|
-
this.ready = true;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
:::note
|
|
56
|
-
The `@observable` decorator creates a property that the template system can watch for changes. It is similar to `@attr`, but the property is not surfaced as an HTML attribute on the element itself. While `@attr` can only be used in a `FASTElement`, `@observable` can be used in any class.
|
|
57
|
-
:::
|
|
58
|
-
|
|
59
|
-
In addition to providing a template to conditionally render, you can also provide an expression that evaluates to a template. This enables you to dynamically change what you are conditionally rendering.
|
|
60
|
-
|
|
61
|
-
**Example: Conditional Rendering with Dynamic Template**
|
|
62
|
-
|
|
63
|
-
```ts
|
|
64
|
-
import { FASTElement, customElement, observable, html, when } from '@microsoft/fast-element';
|
|
65
|
-
|
|
66
|
-
const template = html<MyApp>`
|
|
67
|
-
<h1>My App</h1>
|
|
68
|
-
|
|
69
|
-
${when(x => x.ready, x => x.dataTemplate)}
|
|
70
|
-
`;
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### The `repeat` directive
|
|
74
|
-
|
|
75
|
-
To render a list of data, use the `repeat` directive, providing the list to render and a template to use in rendering each item.
|
|
76
|
-
|
|
77
|
-
**Example: List Rendering**
|
|
78
|
-
|
|
79
|
-
```ts
|
|
80
|
-
import { FASTElement, customElement, observable, html, repeat } from '@microsoft/fast-element';
|
|
81
|
-
|
|
82
|
-
const template = html<FriendList>`
|
|
83
|
-
<h1>Friends</h1>
|
|
84
|
-
|
|
85
|
-
<form @submit=${x => x.addFriend()}>
|
|
86
|
-
<input type="text" :value=${x => x.name} @input=${(x, c) => x.handleNameInput(c.event)}>
|
|
87
|
-
<button type="submit">Add Friend</button>
|
|
88
|
-
</form>
|
|
89
|
-
<ul>
|
|
90
|
-
${repeat(x => x.friends, html<string>`
|
|
91
|
-
<li>${x => x}</li>
|
|
92
|
-
`)}
|
|
93
|
-
</ul>
|
|
94
|
-
`;
|
|
95
|
-
|
|
96
|
-
@customElement({
|
|
97
|
-
name: 'friend-list',
|
|
98
|
-
template
|
|
99
|
-
})
|
|
100
|
-
export class FriendList extends FASTElement {
|
|
101
|
-
@observable friends: string[] = [];
|
|
102
|
-
@observable name: string = '';
|
|
103
|
-
|
|
104
|
-
addFriend() {
|
|
105
|
-
if (!this.name) {
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
this.friends.push(this.name);
|
|
110
|
-
this.name = '';
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
handleNameInput(event: Event) {
|
|
114
|
-
this.name = (event.target! as HTMLInputElement).value;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
Similar to event handlers, within a `repeat` block you have access to a special context object. Here is a list of the properties that are available on the context:
|
|
120
|
-
|
|
121
|
-
* `event` - The event object when inside an event handler.
|
|
122
|
-
* `parent` - The parent view-model when inside a `repeat` block.
|
|
123
|
-
* `parentContext` - The parent `ExecutionContext` when inside a `repeat` block. This is useful when repeats are nested and the inner-most repeat needs access to the root view-model.
|
|
124
|
-
* `index` - The index of the current item when inside a `repeat` block (opt-in).
|
|
125
|
-
* `length` - The length of the array when inside a `repeat` block (opt-in).
|
|
126
|
-
* `isEven` - True if the index of the current item is even when inside a `repeat` block (opt-in).
|
|
127
|
-
* `isOdd` - True if the index of the current item is odd when inside a `repeat` block (opt-in).
|
|
128
|
-
* `isFirst` - True if the current item is first in the array inside a `repeat` block (opt-in).
|
|
129
|
-
* `isInMiddle` - True if the current item is somewhere in the middle of the array inside a `repeat` block (opt-in).
|
|
130
|
-
* `isLast` - True if the current item is last in the array inside a `repeat` block (opt-in).
|
|
131
|
-
|
|
132
|
-
Some context properties are opt-in because they are more costly to update. So, for performance reasons, they are not available by default. To opt into the positioning properties, pass options to the repeat directive, with the setting `positioning: true`. For example, here's how we would use the `index` in our friends template from above:
|
|
133
|
-
|
|
134
|
-
**Example: List Rendering with Item Index**
|
|
135
|
-
|
|
136
|
-
```html
|
|
137
|
-
<ul>
|
|
138
|
-
${repeat(x => x.friends, html<string>`
|
|
139
|
-
<li>${(x, c) => c.index} ${x => x}</li>
|
|
140
|
-
`, { positioning: true })}
|
|
141
|
-
</ul>
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
Whether or not a repeat directive re-uses item views can be controlled with the `recycle` option setting. When `recycle: true`, which is the default value, the repeat directive may reuse views rather than create new ones from the template. When `recycle: false`
|
|
145
|
-
previously used views are always discarded and each item will always be assigned a new view. Recyling previously used views may improve performance in some situations but may also be "dirty" from the previously displayed item.
|
|
146
|
-
|
|
147
|
-
**Example: List Rendering without view recycling**
|
|
148
|
-
|
|
149
|
-
```html
|
|
150
|
-
<ul>
|
|
151
|
-
${repeat(x => x.friends, html<string>`
|
|
152
|
-
<li>${(x, c) => c.index} ${x => x}</li>
|
|
153
|
-
`, { positioning: true, recycle: false })}
|
|
154
|
-
</ul>
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
In addition to providing a template to render the items with, you can also provide an expression that evaluates to a template. This enables you to dynamically change what you are using to render the items. Each item will still be rendered with the same template, but you can use techniques from "Composing Templates" below to render a different template depending on the item itself.
|
|
158
|
-
|
|
159
|
-
### Composing templates
|
|
160
|
-
|
|
161
|
-
The `ViewTemplate` returned from the `html` tag helper has special handling when it is used inside of another template. This is done so that you can create templates and compose them into other templates.
|
|
162
|
-
|
|
163
|
-
**Example: Composing Templates**
|
|
164
|
-
|
|
165
|
-
```ts
|
|
166
|
-
import { FASTElement, customElement, observable, html, repeat, when } from '@microsoft/fast-element';
|
|
167
|
-
|
|
168
|
-
interface Named {
|
|
169
|
-
name: string;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
class Person {
|
|
173
|
-
@observable name: string;
|
|
174
|
-
|
|
175
|
-
constructor(name: string) {
|
|
176
|
-
this.name = name;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const nameTemplate = html<Named>`
|
|
181
|
-
<span class="name">${x => x.name}</span>
|
|
182
|
-
`;
|
|
183
|
-
|
|
184
|
-
const template = html<FriendList>`
|
|
185
|
-
<h1>Friends</h1>
|
|
186
|
-
|
|
187
|
-
<form @submit=${x => x.addFriend()}>
|
|
188
|
-
<input type="text" :value=${x => x.name} @input=${(x, c) => x.handleNameInput(c.event)}>
|
|
189
|
-
|
|
190
|
-
${when(x => x.name, html`
|
|
191
|
-
<div>Next Name: ${nameTemplate}</div>
|
|
192
|
-
`)}
|
|
193
|
-
|
|
194
|
-
<div class="button-bar">
|
|
195
|
-
<button type="submit">Add Friend</button>
|
|
196
|
-
</div>
|
|
197
|
-
</form>
|
|
198
|
-
<ul>
|
|
199
|
-
${repeat(x => x.friends, html`
|
|
200
|
-
<li>${nameTemplate}</li>
|
|
201
|
-
`)}
|
|
202
|
-
</ul>
|
|
203
|
-
`;
|
|
204
|
-
|
|
205
|
-
@customElement({
|
|
206
|
-
name: 'friend-list',
|
|
207
|
-
template
|
|
208
|
-
})
|
|
209
|
-
export class FriendList extends FASTElement {
|
|
210
|
-
@observable friends: Person[] = [];
|
|
211
|
-
@observable name: string = '';
|
|
212
|
-
|
|
213
|
-
addFriend() {
|
|
214
|
-
if (!this.name) {
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
this.friends.push(new Person(this.name));
|
|
219
|
-
this.name = '';
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
handleNameInput(event: Event) {
|
|
223
|
-
this.name = (event.target! as HTMLInputElement).value;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
In the above example, we create an independent `nameTemplate` and then use it in two different places. First inside of a `when` template and then later inside of a `repeat` template.
|
|
229
|
-
|
|
230
|
-
But content composition is actually more powerful than that because you aren't limited to *static composition* of templates. You can also provide any expression that returns a template. As a result, when the `@observable` dependencies of the expression change, you can dynamically change which template is selected for rendering. If you don't want to render anything, you can also handle that by returning `null` or `undefined`. Here are a few examples of what you can do with content composition:
|
|
231
|
-
|
|
232
|
-
**Example: Dynamic Composition**
|
|
233
|
-
|
|
234
|
-
```ts
|
|
235
|
-
const defaultTemplate = html`...`;
|
|
236
|
-
const templatesByType = {
|
|
237
|
-
foo: html`...`,
|
|
238
|
-
bar: html`...`,
|
|
239
|
-
baz: html`...`
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
const template = html<MyElement>`
|
|
243
|
-
<div>${x => x.selectTemplate()}</div>
|
|
244
|
-
`;
|
|
245
|
-
|
|
246
|
-
@customElement({
|
|
247
|
-
name: 'my-element',
|
|
248
|
-
template
|
|
249
|
-
})
|
|
250
|
-
export class MyElement extends FASTElement {
|
|
251
|
-
@observable data;
|
|
252
|
-
|
|
253
|
-
selectTemplate() {
|
|
254
|
-
return templatesByType[this.data.type] || defaultTemplate;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
**Example: Override Templates**
|
|
260
|
-
|
|
261
|
-
```ts
|
|
262
|
-
const myCustomTemplate = html`...`
|
|
263
|
-
|
|
264
|
-
@customElement({
|
|
265
|
-
name: 'my-derived-element',
|
|
266
|
-
template
|
|
267
|
-
})
|
|
268
|
-
export class MyDerivedElement extends MyElement {
|
|
269
|
-
selectTemplate() {
|
|
270
|
-
return myCustomTemplate;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
**Example: Complex Conditional**
|
|
276
|
-
|
|
277
|
-
```ts
|
|
278
|
-
const dataTemplate = html`...`;
|
|
279
|
-
const loadingTemplate = html`...`;
|
|
280
|
-
|
|
281
|
-
const template = html<MyElement>`
|
|
282
|
-
<div>
|
|
283
|
-
${x => {
|
|
284
|
-
if (x.ready) {
|
|
285
|
-
return dataTemplate;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// Any logic can go here to determine which template to use.
|
|
289
|
-
// Which template to use will be re-evaluated whenever @observable
|
|
290
|
-
// properties from this method implementation change.
|
|
291
|
-
|
|
292
|
-
return loadingTemplate;
|
|
293
|
-
}}
|
|
294
|
-
</div>
|
|
295
|
-
`;
|
|
296
|
-
|
|
297
|
-
@customElement({
|
|
298
|
-
name: 'my-element',
|
|
299
|
-
template
|
|
300
|
-
})
|
|
301
|
-
export class MyElement extends FASTElement {
|
|
302
|
-
@observable ready: boolean = false;
|
|
303
|
-
@observable data: any = null;
|
|
304
|
-
|
|
305
|
-
connectedCallback() {
|
|
306
|
-
super.connectedCallback();
|
|
307
|
-
this.loadData();
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
async loadData() {
|
|
311
|
-
const response = await fetch('some/resource');
|
|
312
|
-
const data = await response.json();
|
|
313
|
-
|
|
314
|
-
this.data = data;
|
|
315
|
-
this.ready = true;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
**Example: Per Item List Types**
|
|
321
|
-
|
|
322
|
-
```ts
|
|
323
|
-
const defaultTemplate = html`...`;
|
|
324
|
-
const templatesByType = {
|
|
325
|
-
foo: html`...`,
|
|
326
|
-
bar: html`...`,
|
|
327
|
-
baz: html`...`
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
const template = html<MyElement>`
|
|
331
|
-
<ul>
|
|
332
|
-
${repeat(x => x.items, html`
|
|
333
|
-
<li>
|
|
334
|
-
${(x, c) => c.parent.selectTemplate(x)}
|
|
335
|
-
</li>
|
|
336
|
-
`)}
|
|
337
|
-
</ul>
|
|
338
|
-
`;
|
|
339
|
-
|
|
340
|
-
@customElement({
|
|
341
|
-
name: 'my-element',
|
|
342
|
-
template
|
|
343
|
-
})
|
|
344
|
-
export class MyElement extends FASTElement {
|
|
345
|
-
@observable items: any[] = [];
|
|
346
|
-
|
|
347
|
-
selectTemplate(item) {
|
|
348
|
-
return templatesByType[item.type] || defaultTemplate;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
**Example: Custom Rendering Override**
|
|
354
|
-
|
|
355
|
-
```ts
|
|
356
|
-
const defaultTemplate = html`...`;
|
|
357
|
-
const template = html<MyElement>`
|
|
358
|
-
<div>${x => x.selectTemplate()}</div>
|
|
359
|
-
`;
|
|
360
|
-
|
|
361
|
-
@customElement({
|
|
362
|
-
name: 'my-element',
|
|
363
|
-
template
|
|
364
|
-
})
|
|
365
|
-
export class MyElement extends FASTElement {
|
|
366
|
-
selectTemplate() {
|
|
367
|
-
return defaultTemplate;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
export class MyCustomTemplate implements SyntheticViewTemplate {
|
|
372
|
-
create(): SyntheticView {
|
|
373
|
-
// construct your own implementation of SyntheticView
|
|
374
|
-
return customView;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
const customTemplate = new MyCustomTemplate();
|
|
379
|
-
|
|
380
|
-
@customElement({
|
|
381
|
-
name: 'my-derived-element',
|
|
382
|
-
template
|
|
383
|
-
})
|
|
384
|
-
export class MyDerivedElement extends MyElement {
|
|
385
|
-
selectTemplate() {
|
|
386
|
-
return customTemplate;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
:::important
|
|
392
|
-
When composing templates, extract the composed template to an external variable. If you define the template inline, within your method, property, or expression, then each time that is invoked, a new instance of the template will be created, rather than reusing the template. This will result in an unnecessary performance cost.
|
|
393
|
-
:::
|
|
394
|
-
|
|
395
|
-
**Example: The `when` Directive**
|
|
396
|
-
|
|
397
|
-
Now that we've explained how content composition works, you may find it interesting to know that `when` is actually just *syntax sugar* on top of the core composition system. Let's look at the implementation of `when` itself to see how it works:
|
|
398
|
-
|
|
399
|
-
```ts
|
|
400
|
-
export function when(condition, templateOrTemplateExpression) {
|
|
401
|
-
const getTemplate = typeof templateOrTemplateExpression === "function"
|
|
402
|
-
? templateOrTemplateExpression
|
|
403
|
-
: () => templateOrTemplateExpression;
|
|
404
|
-
|
|
405
|
-
return (source, context) =>
|
|
406
|
-
condition(source, context)
|
|
407
|
-
? getTemplate(source, context)
|
|
408
|
-
: null;
|
|
409
|
-
}
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
As you can see, all that `when` does is compose a new function that checks your condition. If it's `true,` it invokes your template provider function; if `false`, it returns `null`, indicating nothing should be rendered.
|
|
413
|
-
|
|
414
|
-
## Referential directives
|
|
415
|
-
|
|
416
|
-
Referential directives allow you to easily get references to DOM nodes in various scenarios.
|
|
417
|
-
|
|
418
|
-
### The `ref` directive
|
|
419
|
-
|
|
420
|
-
Sometimes you need a direct reference to a single DOM node from your template. This might be because you want to control the playback of a `video` element, use the drawing context of a `canvas` element, or pass an element to a 3rd party library. Whatever the reason, you can get a reference to the DOM node by using the `ref` directive.
|
|
421
|
-
|
|
422
|
-
**Example: Referencing an Element**
|
|
423
|
-
|
|
424
|
-
```ts
|
|
425
|
-
import { FASTElement, customElement, attr, html, ref } from '@microsoft/fast-element';
|
|
426
|
-
|
|
427
|
-
const template = html<MP4Player>`
|
|
428
|
-
<video ${ref('video')}>
|
|
429
|
-
<source src=${x => x.src} type="video/mp4">
|
|
430
|
-
</video>
|
|
431
|
-
`;
|
|
432
|
-
|
|
433
|
-
@customElement({
|
|
434
|
-
name: 'mp4-player',
|
|
435
|
-
template
|
|
436
|
-
})
|
|
437
|
-
export class MP4Player extends FASTElement {
|
|
438
|
-
@attr src: string;
|
|
439
|
-
video: HTMLVideoElement;
|
|
440
|
-
|
|
441
|
-
connectedCallback() {
|
|
442
|
-
super.connectedCallback();
|
|
443
|
-
this.video.play();
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
Place the `ref` directive on the element you want to reference and provide it with a property name to assign the reference to. Once the `connectedCallback` lifecycle event runs, your property will be set to the reference, ready for use.
|
|
449
|
-
|
|
450
|
-
:::tip
|
|
451
|
-
If you provide a type for your HTML template, TypeScript will type check the property name you provide to ensure that it actually exists on your element.
|
|
452
|
-
:::
|
|
453
|
-
|
|
454
|
-
### The `children` directive
|
|
455
|
-
|
|
456
|
-
Besides using `ref` to reference a single DOM node, you can use `children` to get references to all child nodes of a particular element.
|
|
457
|
-
|
|
458
|
-
**Example: Referencing Child Nodes**
|
|
459
|
-
|
|
460
|
-
```ts
|
|
461
|
-
import { FASTElement, customElement, html, children, repeat } from '@microsoft/fast-element';
|
|
462
|
-
|
|
463
|
-
const template = html<FriendList>`
|
|
464
|
-
<ul ${children('listItems')}>
|
|
465
|
-
${repeat(x => x.friends, html<string>`
|
|
466
|
-
<li>${x => x}</li>
|
|
467
|
-
`)}
|
|
468
|
-
</ul>
|
|
469
|
-
`;
|
|
470
|
-
|
|
471
|
-
@customElement({
|
|
472
|
-
name: 'friend-list',
|
|
473
|
-
template
|
|
474
|
-
})
|
|
475
|
-
export class FriendList extends FASTElement {
|
|
476
|
-
@observable listItems: Node[];
|
|
477
|
-
@observable friends: string[] = [];
|
|
478
|
-
|
|
479
|
-
connectedCallback() {
|
|
480
|
-
super.connectedCallback();
|
|
481
|
-
console.log(this.listItems);
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
In the example above, the `listItems` property will be populated with all child nodes of the `ul` element. If `listItems` is decorated with `@observable` then it will be updated dynamically as the child nodes change. Like any observable, you can optionally implement a *propertyName*Changed method to be notified when the nodes change. Additionally, you can provide an `options` object to the `children` directive to specify a customized configuration for the underlying [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver).
|
|
487
|
-
|
|
488
|
-
:::important
|
|
489
|
-
Like `ref`, the child nodes are not available until the `connectedCallback` lifecycle event.
|
|
490
|
-
:::
|
|
491
|
-
|
|
492
|
-
You can also provide a `filter` function to control which child nodes are synchronized to your property. As a convenience, we provide an `elements` filter that lets you optionally specify a selector. Taking the above example, if we wanted to ensure that our `listItems` array only included `li` elements (and not any text nodes or other potential child nodes), we could author our template like this:
|
|
493
|
-
|
|
494
|
-
**Example: Filtering Child Nodes**
|
|
495
|
-
|
|
496
|
-
```ts
|
|
497
|
-
import { FASTElement, customElement, html, children, repeat, elements } from '@microsoft/fast-element';
|
|
498
|
-
|
|
499
|
-
const template = html<FriendList>`
|
|
500
|
-
<ul ${children({ property: 'listItems', filter: elements('li') })}>
|
|
501
|
-
${repeat(x => x.friends, html<string>`
|
|
502
|
-
<li>${x => x}</li>
|
|
503
|
-
`)}
|
|
504
|
-
</ul>
|
|
505
|
-
`;
|
|
506
|
-
```
|
|
507
|
-
|
|
508
|
-
If using the `subtree` option for `children` then a `selector` is *required* in place of a `filter`. This enables more efficient collection of the desired nodes in the presence of a potential large node quantity throughout the subtree.
|
|
509
|
-
|
|
510
|
-
### The `slotted` directive
|
|
511
|
-
|
|
512
|
-
Sometimes you may want references to all nodes that are assigned to a particular slot. To accomplish this, use the `slotted` directive. (For more on slots, see [Working with Shadow DOM](./working-with-shadow-dom.md).)
|
|
513
|
-
|
|
514
|
-
```ts
|
|
515
|
-
import { FASTElement, customElement, html, slotted } from '@microsoft/fast-element';
|
|
516
|
-
|
|
517
|
-
const template = html<MyElement>`
|
|
518
|
-
<div>
|
|
519
|
-
<slot ${slotted('slottedNodes')}></slot>
|
|
520
|
-
</div>
|
|
521
|
-
`;
|
|
522
|
-
|
|
523
|
-
@customElement({
|
|
524
|
-
name: 'my-element',
|
|
525
|
-
template
|
|
526
|
-
})
|
|
527
|
-
export class MyElement extends FASTElement {
|
|
528
|
-
@observable slottedNodes: Node[];
|
|
529
|
-
|
|
530
|
-
slottedNodesChanged() {
|
|
531
|
-
// respond to changes in slotted node
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
```
|
|
535
|
-
|
|
536
|
-
Similar to the `children` directive, the `slotted` directive will populate the `slottedNodes` property with nodes assigned to the slot. If `slottedNodes` is decorated with `@observable` then it will be updated dynamically as the assigned nodes change. Like any observable, you can optionally implement a *propertyName*Changed method to be notified when the nodes change. Additionally, you can provide an `options` object to the `slotted` directive to specify a customized configuration for the underlying [assignedNodes() API call](https://developer.mozilla.org/en-US/docs/Web/API/HTMLSlotElement/assignedNodes) or specify a `filter`.
|
|
537
|
-
|
|
538
|
-
:::tip
|
|
539
|
-
It's best to leverage a change handler for slotted nodes rather than assuming that the nodes will be present in the `connectedCallback`.
|
|
540
|
-
:::
|
|
541
|
-
|
|
542
|
-
## Host directives
|
|
543
|
-
|
|
544
|
-
So far, our bindings and directives have only affected elements within the Shadow DOM of the component. However, sometimes you want to affect the host element itself, based on property state. For example, a progress component might want to write various `aria` attributes to the host, based on the progress state. In order to facilitate scenarios like this, you can use a `template` element as the root of your template, and it will represent the host element. Any attribute or directive you place on the `template` element will be applied to the host itself.
|
|
545
|
-
|
|
546
|
-
**Example: Host Directive Template**
|
|
547
|
-
|
|
548
|
-
```ts
|
|
549
|
-
const template = html<MyProgress>`
|
|
550
|
-
<template (Represents my-progress element)
|
|
551
|
-
role="progressbar"
|
|
552
|
-
$aria-valuenow={x => x.value}
|
|
553
|
-
$aria-valuemin={x => x.min}
|
|
554
|
-
$aria-valuemax={x => x.max}>
|
|
555
|
-
(template targeted at Shadow DOM here)
|
|
556
|
-
</template>
|
|
557
|
-
`;
|
|
558
|
-
```
|
|
559
|
-
|
|
560
|
-
**Example: DOM with Host Directive Output**
|
|
561
|
-
|
|
562
|
-
```html
|
|
563
|
-
<my-progress
|
|
564
|
-
min="0" (from user)
|
|
565
|
-
max="100" (from user)
|
|
566
|
-
value="50" (from user)
|
|
567
|
-
role="progressbar" (from host directive)
|
|
568
|
-
aria-valuenow="50" (from host directive)
|
|
569
|
-
aria-valuemin="0" (from host directive)
|
|
570
|
-
aria-valuemax="100" (from host directive)>
|
|
571
|
-
</my-progress>
|
|
572
|
-
```
|
|
573
|
-
|
|
574
|
-
:::tip
|
|
575
|
-
Using the `children` directive on the `template` element will provide you with references to all Light DOM child nodes of your custom element, regardless of if or where they are slotted.
|
|
576
|
-
:::
|