@microsoft/fast-element 2.0.0-beta.9 → 2.0.1
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 +518 -0
- package/CHANGELOG.md +181 -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 +7 -1
- 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 +19 -6
- 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 +9789 -5667
- package/dist/fast-element.d.ts +813 -2392
- package/dist/fast-element.debug.js +2788 -974
- package/dist/fast-element.debug.min.js +3 -1
- package/dist/fast-element.js +2641 -833
- package/dist/fast-element.min.js +3 -1
- package/dist/fast-element.untrimmed.d.ts +662 -314
- 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
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
const rootDir = path.join(__dirname, '..');
|
|
9
|
+
const tensileConfig = 'tensile.config.js';
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const esmOutput = path.join(rootDir, 'dist', 'esm');
|
|
13
|
+
const items = await fs.readdir(esmOutput);
|
|
14
|
+
|
|
15
|
+
// Collect all component folders
|
|
16
|
+
const folders = [];
|
|
17
|
+
for (const item of items) {
|
|
18
|
+
const itemPath = path.join(esmOutput, item);
|
|
19
|
+
const stats = await fs.lstat(itemPath);
|
|
20
|
+
if (stats.isDirectory()) {
|
|
21
|
+
folders.push(item);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Collect all .bench.js files
|
|
26
|
+
const benchFiles = [];
|
|
27
|
+
for (const folder of folders) {
|
|
28
|
+
const folderPath = path.join(esmOutput, folder);
|
|
29
|
+
const files = await fs.readdir(folderPath);
|
|
30
|
+
const filteredFiles = files.filter(file => file.endsWith('.bench.js'));
|
|
31
|
+
benchFiles.push(...filteredFiles.map(file => path.relative(rootDir, path.join(folderPath, file))));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Execute tensile for each .bench.js file
|
|
35
|
+
for (const file of benchFiles) {
|
|
36
|
+
try {
|
|
37
|
+
// eslint-disable-next-line no-undef
|
|
38
|
+
execSync(`tensile --file ./${file} --config ${tensileConfig} ${process.argv[2]}`, { stdio: 'inherit' });
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error(`Error executing command for file ${file}: ${error.message}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error(`Error reading directory: ${error.message}`);
|
|
46
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const config = {
|
|
2
|
+
// Browsers to test against
|
|
3
|
+
browsers: ['chrome'],
|
|
4
|
+
|
|
5
|
+
// Importmaps for your test.
|
|
6
|
+
// See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap
|
|
7
|
+
imports: {
|
|
8
|
+
'@tensile-perf/web-components': '/node_modules/@tensile-perf/web-components/lib/index.js',
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default config;
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type { Callable } from "../interfaces.js";
|
|
2
|
-
/**
|
|
3
|
-
* Common DOM APIs.
|
|
4
|
-
* @public
|
|
5
|
-
*/
|
|
6
|
-
export declare const DOM: Readonly<{
|
|
7
|
-
/**
|
|
8
|
-
* @deprecated
|
|
9
|
-
* Use Updates.enqueue().
|
|
10
|
-
*/
|
|
11
|
-
queueUpdate: (callable: Callable) => void;
|
|
12
|
-
/**
|
|
13
|
-
* @deprecated
|
|
14
|
-
* Use Updates.next()
|
|
15
|
-
*/
|
|
16
|
-
nextUpdate: () => Promise<void>;
|
|
17
|
-
/**
|
|
18
|
-
* @deprecated
|
|
19
|
-
* Use Updates.process()
|
|
20
|
-
*/
|
|
21
|
-
processUpdates: () => void;
|
|
22
|
-
/**
|
|
23
|
-
* Sets an attribute value on an element.
|
|
24
|
-
* @param element - The element to set the attribute value on.
|
|
25
|
-
* @param attributeName - The attribute name to set.
|
|
26
|
-
* @param value - The value of the attribute to set.
|
|
27
|
-
* @remarks
|
|
28
|
-
* If the value is `null` or `undefined`, the attribute is removed, otherwise
|
|
29
|
-
* it is set to the provided value using the standard `setAttribute` API.
|
|
30
|
-
*/
|
|
31
|
-
setAttribute(element: HTMLElement, attributeName: string, value: any): void;
|
|
32
|
-
/**
|
|
33
|
-
* Sets a boolean attribute value.
|
|
34
|
-
* @param element - The element to set the boolean attribute value on.
|
|
35
|
-
* @param attributeName - The attribute name to set.
|
|
36
|
-
* @param value - The value of the attribute to set.
|
|
37
|
-
* @remarks
|
|
38
|
-
* If the value is true, the attribute is added; otherwise it is removed.
|
|
39
|
-
*/
|
|
40
|
-
setBooleanAttribute(element: HTMLElement, attributeName: string, value: boolean): void;
|
|
41
|
-
}>;
|
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
import { isFunction } from "../interfaces.js";
|
|
2
|
-
import { ExecutionContext, Observable, } from "../observation/observable.js";
|
|
3
|
-
import { FAST } from "../platform.js";
|
|
4
|
-
import { DOM } from "./dom.js";
|
|
5
|
-
import { Aspect, Binding, HTMLDirective, } from "./html-directive.js";
|
|
6
|
-
import { Markup, nextId } from "./markup.js";
|
|
7
|
-
const createInnerHTMLBinding = globalThis.TrustedHTML
|
|
8
|
-
? (binding) => (s, c) => {
|
|
9
|
-
const value = binding(s, c);
|
|
10
|
-
if (value instanceof TrustedHTML) {
|
|
11
|
-
return value;
|
|
12
|
-
}
|
|
13
|
-
throw FAST.error(1202 /* Message.bindingInnerHTMLRequiresTrustedTypes */);
|
|
14
|
-
}
|
|
15
|
-
: (binding) => binding;
|
|
16
|
-
class OnChangeBinding extends Binding {
|
|
17
|
-
createObserver(_, subscriber) {
|
|
18
|
-
return Observable.binding(this.evaluate, subscriber, this.isVolatile);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
class OneTimeBinding extends Binding {
|
|
22
|
-
createObserver() {
|
|
23
|
-
return this;
|
|
24
|
-
}
|
|
25
|
-
bind(controller) {
|
|
26
|
-
return this.evaluate(controller.source, controller.context);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
function updateContent(target, aspect, value, controller) {
|
|
30
|
-
// If there's no actual value, then this equates to the
|
|
31
|
-
// empty string for the purposes of content bindings.
|
|
32
|
-
if (value === null || value === undefined) {
|
|
33
|
-
value = "";
|
|
34
|
-
}
|
|
35
|
-
// If the value has a "create" method, then it's a ContentTemplate.
|
|
36
|
-
if (value.create) {
|
|
37
|
-
target.textContent = "";
|
|
38
|
-
let view = target.$fastView;
|
|
39
|
-
// If there's no previous view that we might be able to
|
|
40
|
-
// reuse then create a new view from the template.
|
|
41
|
-
if (view === void 0) {
|
|
42
|
-
view = value.create();
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
// If there is a previous view, but it wasn't created
|
|
46
|
-
// from the same template as the new value, then we
|
|
47
|
-
// need to remove the old view if it's still in the DOM
|
|
48
|
-
// and create a new view from the template.
|
|
49
|
-
if (target.$fastTemplate !== value) {
|
|
50
|
-
if (view.isComposed) {
|
|
51
|
-
view.remove();
|
|
52
|
-
view.unbind();
|
|
53
|
-
}
|
|
54
|
-
view = value.create();
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
// It's possible that the value is the same as the previous template
|
|
58
|
-
// and that there's actually no need to compose it.
|
|
59
|
-
if (!view.isComposed) {
|
|
60
|
-
view.isComposed = true;
|
|
61
|
-
view.bind(controller.source, controller.context);
|
|
62
|
-
view.insertBefore(target);
|
|
63
|
-
target.$fastView = view;
|
|
64
|
-
target.$fastTemplate = value;
|
|
65
|
-
}
|
|
66
|
-
else if (view.needsBindOnly) {
|
|
67
|
-
view.needsBindOnly = false;
|
|
68
|
-
view.bind(controller.source, controller.context);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
const view = target.$fastView;
|
|
73
|
-
// If there is a view and it's currently composed into
|
|
74
|
-
// the DOM, then we need to remove it.
|
|
75
|
-
if (view !== void 0 && view.isComposed) {
|
|
76
|
-
view.isComposed = false;
|
|
77
|
-
view.remove();
|
|
78
|
-
if (view.needsBindOnly) {
|
|
79
|
-
view.needsBindOnly = false;
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
view.unbind();
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
target.textContent = value;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
function updateTokenList(target, aspect, value) {
|
|
89
|
-
var _a;
|
|
90
|
-
const lookup = `${this.id}-t`;
|
|
91
|
-
const state = (_a = target[lookup]) !== null && _a !== void 0 ? _a : (target[lookup] = { c: 0, v: Object.create(null) });
|
|
92
|
-
const versions = state.v;
|
|
93
|
-
let currentVersion = state.c;
|
|
94
|
-
const tokenList = target[aspect];
|
|
95
|
-
// Add the classes, tracking the version at which they were added.
|
|
96
|
-
if (value !== null && value !== undefined && value.length) {
|
|
97
|
-
const names = value.split(/\s+/);
|
|
98
|
-
for (let i = 0, ii = names.length; i < ii; ++i) {
|
|
99
|
-
const currentName = names[i];
|
|
100
|
-
if (currentName === "") {
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
versions[currentName] = currentVersion;
|
|
104
|
-
tokenList.add(currentName);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
state.v = currentVersion + 1;
|
|
108
|
-
// If this is the first call to add classes, there's no need to remove old ones.
|
|
109
|
-
if (currentVersion === 0) {
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
// Remove classes from the previous version.
|
|
113
|
-
currentVersion -= 1;
|
|
114
|
-
for (const name in versions) {
|
|
115
|
-
if (versions[name] === currentVersion) {
|
|
116
|
-
tokenList.remove(name);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
const setProperty = (t, a, v) => (t[a] = v);
|
|
121
|
-
const eventTarget = () => void 0;
|
|
122
|
-
/**
|
|
123
|
-
* A directive that applies bindings.
|
|
124
|
-
* @public
|
|
125
|
-
*/
|
|
126
|
-
export class HTMLBindingDirective {
|
|
127
|
-
/**
|
|
128
|
-
* Creates an instance of HTMLBindingDirective.
|
|
129
|
-
* @param dataBinding - The binding configuration to apply.
|
|
130
|
-
*/
|
|
131
|
-
constructor(dataBinding) {
|
|
132
|
-
this.dataBinding = dataBinding;
|
|
133
|
-
this.updateTarget = null;
|
|
134
|
-
/**
|
|
135
|
-
* The unique id of the factory.
|
|
136
|
-
*/
|
|
137
|
-
this.id = nextId();
|
|
138
|
-
/**
|
|
139
|
-
* The type of aspect to target.
|
|
140
|
-
*/
|
|
141
|
-
this.aspectType = Aspect.content;
|
|
142
|
-
/** @internal */
|
|
143
|
-
this.bind = this.bindDefault;
|
|
144
|
-
this.data = `${this.id}-d`;
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* Creates HTML to be used within a template.
|
|
148
|
-
* @param add - Can be used to add behavior factories to a template.
|
|
149
|
-
*/
|
|
150
|
-
createHTML(add) {
|
|
151
|
-
return Markup.interpolation(add(this));
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Creates a behavior.
|
|
155
|
-
*/
|
|
156
|
-
createBehavior() {
|
|
157
|
-
if (this.updateTarget === null) {
|
|
158
|
-
if (this.targetAspect === "innerHTML") {
|
|
159
|
-
this.dataBinding.evaluate = createInnerHTMLBinding(this.dataBinding.evaluate);
|
|
160
|
-
}
|
|
161
|
-
switch (this.aspectType) {
|
|
162
|
-
case 1:
|
|
163
|
-
this.updateTarget = DOM.setAttribute;
|
|
164
|
-
break;
|
|
165
|
-
case 2:
|
|
166
|
-
this.updateTarget = DOM.setBooleanAttribute;
|
|
167
|
-
break;
|
|
168
|
-
case 3:
|
|
169
|
-
this.updateTarget = setProperty;
|
|
170
|
-
break;
|
|
171
|
-
case 4:
|
|
172
|
-
this.bind = this.bindContent;
|
|
173
|
-
this.updateTarget = updateContent;
|
|
174
|
-
break;
|
|
175
|
-
case 5:
|
|
176
|
-
this.updateTarget = updateTokenList;
|
|
177
|
-
break;
|
|
178
|
-
case 6:
|
|
179
|
-
this.bind = this.bindEvent;
|
|
180
|
-
this.updateTarget = eventTarget;
|
|
181
|
-
break;
|
|
182
|
-
default:
|
|
183
|
-
throw FAST.error(1205 /* Message.unsupportedBindingBehavior */);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
return this;
|
|
187
|
-
}
|
|
188
|
-
/** @internal */
|
|
189
|
-
bindDefault(controller) {
|
|
190
|
-
var _a;
|
|
191
|
-
const target = controller.targets[this.nodeId];
|
|
192
|
-
const observer = (_a = target[this.data]) !== null && _a !== void 0 ? _a : (target[this.data] = this.dataBinding.createObserver(this, this));
|
|
193
|
-
observer.target = target;
|
|
194
|
-
observer.controller = controller;
|
|
195
|
-
this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
|
|
196
|
-
if (this.updateTarget === updateContent) {
|
|
197
|
-
controller.onUnbind(this);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
/** @internal */
|
|
201
|
-
bindContent(controller) {
|
|
202
|
-
this.bindDefault(controller);
|
|
203
|
-
controller.onUnbind(this);
|
|
204
|
-
}
|
|
205
|
-
/** @internal */
|
|
206
|
-
bindEvent(controller) {
|
|
207
|
-
const target = controller.targets[this.nodeId];
|
|
208
|
-
target[this.data] = controller;
|
|
209
|
-
target.addEventListener(this.targetAspect, this, this.dataBinding.options);
|
|
210
|
-
}
|
|
211
|
-
/** @internal */
|
|
212
|
-
unbind(controller) {
|
|
213
|
-
const target = controller.targets[this.nodeId];
|
|
214
|
-
const view = target.$fastView;
|
|
215
|
-
if (view !== void 0 && view.isComposed) {
|
|
216
|
-
view.unbind();
|
|
217
|
-
view.needsBindOnly = true;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
/** @internal */
|
|
221
|
-
handleEvent(event) {
|
|
222
|
-
const target = event.currentTarget;
|
|
223
|
-
ExecutionContext.setEvent(event);
|
|
224
|
-
const controller = target[this.data];
|
|
225
|
-
const result = this.dataBinding.evaluate(controller.source, controller.context);
|
|
226
|
-
ExecutionContext.setEvent(null);
|
|
227
|
-
if (result !== true) {
|
|
228
|
-
event.preventDefault();
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
/** @internal */
|
|
232
|
-
handleChange(binding, observer) {
|
|
233
|
-
const target = observer.target;
|
|
234
|
-
const controller = observer.controller;
|
|
235
|
-
this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
HTMLDirective.define(HTMLBindingDirective, { aspected: true });
|
|
239
|
-
/**
|
|
240
|
-
* Creates an standard binding.
|
|
241
|
-
* @param binding - The binding to refresh when changed.
|
|
242
|
-
* @param isVolatile - Indicates whether the binding is volatile or not.
|
|
243
|
-
* @returns A binding configuration.
|
|
244
|
-
* @public
|
|
245
|
-
*/
|
|
246
|
-
export function bind(binding, isVolatile = Observable.isVolatileBinding(binding)) {
|
|
247
|
-
return new OnChangeBinding(binding, isVolatile);
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* Creates a one time binding
|
|
251
|
-
* @param binding - The binding to refresh when signaled.
|
|
252
|
-
* @returns A binding configuration.
|
|
253
|
-
* @public
|
|
254
|
-
*/
|
|
255
|
-
export function oneTime(binding) {
|
|
256
|
-
return new OneTimeBinding(binding);
|
|
257
|
-
}
|
|
258
|
-
/**
|
|
259
|
-
* Creates an event listener binding.
|
|
260
|
-
* @param binding - The binding to invoke when the event is raised.
|
|
261
|
-
* @param options - Event listener options.
|
|
262
|
-
* @returns A binding configuration.
|
|
263
|
-
* @public
|
|
264
|
-
*/
|
|
265
|
-
export function listener(binding, options) {
|
|
266
|
-
const config = new OnChangeBinding(binding, false);
|
|
267
|
-
config.options = options;
|
|
268
|
-
return config;
|
|
269
|
-
}
|
|
270
|
-
/**
|
|
271
|
-
* Normalizes the input value into a binding.
|
|
272
|
-
* @param value - The value to create the default binding for.
|
|
273
|
-
* @returns A binding configuration for the provided value.
|
|
274
|
-
* @public
|
|
275
|
-
*/
|
|
276
|
-
export function normalizeBinding(value) {
|
|
277
|
-
return isFunction(value)
|
|
278
|
-
? bind(value)
|
|
279
|
-
: value instanceof Binding
|
|
280
|
-
? value
|
|
281
|
-
: oneTime(() => value);
|
|
282
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { Updates } from "../observation/update-queue.js";
|
|
2
|
-
/**
|
|
3
|
-
* Common DOM APIs.
|
|
4
|
-
* @public
|
|
5
|
-
*/
|
|
6
|
-
export const DOM = Object.freeze({
|
|
7
|
-
/**
|
|
8
|
-
* @deprecated
|
|
9
|
-
* Use Updates.enqueue().
|
|
10
|
-
*/
|
|
11
|
-
queueUpdate: Updates.enqueue,
|
|
12
|
-
/**
|
|
13
|
-
* @deprecated
|
|
14
|
-
* Use Updates.next()
|
|
15
|
-
*/
|
|
16
|
-
nextUpdate: Updates.next,
|
|
17
|
-
/**
|
|
18
|
-
* @deprecated
|
|
19
|
-
* Use Updates.process()
|
|
20
|
-
*/
|
|
21
|
-
processUpdates: Updates.process,
|
|
22
|
-
/**
|
|
23
|
-
* Sets an attribute value on an element.
|
|
24
|
-
* @param element - The element to set the attribute value on.
|
|
25
|
-
* @param attributeName - The attribute name to set.
|
|
26
|
-
* @param value - The value of the attribute to set.
|
|
27
|
-
* @remarks
|
|
28
|
-
* If the value is `null` or `undefined`, the attribute is removed, otherwise
|
|
29
|
-
* it is set to the provided value using the standard `setAttribute` API.
|
|
30
|
-
*/
|
|
31
|
-
setAttribute(element, attributeName, value) {
|
|
32
|
-
value === null || value === undefined
|
|
33
|
-
? element.removeAttribute(attributeName)
|
|
34
|
-
: element.setAttribute(attributeName, value);
|
|
35
|
-
},
|
|
36
|
-
/**
|
|
37
|
-
* Sets a boolean attribute value.
|
|
38
|
-
* @param element - The element to set the boolean attribute value on.
|
|
39
|
-
* @param attributeName - The attribute name to set.
|
|
40
|
-
* @param value - The value of the attribute to set.
|
|
41
|
-
* @remarks
|
|
42
|
-
* If the value is true, the attribute is added; otherwise it is removed.
|
|
43
|
-
*/
|
|
44
|
-
setBooleanAttribute(element, attributeName, value) {
|
|
45
|
-
value
|
|
46
|
-
? element.setAttribute(attributeName, "")
|
|
47
|
-
: element.removeAttribute(attributeName);
|
|
48
|
-
},
|
|
49
|
-
});
|
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: declaring-templates
|
|
3
|
-
title: Declaring Templates
|
|
4
|
-
sidebar_label: Declaring Templates
|
|
5
|
-
custom_edit_url: https://github.com/microsoft/fast/edit/master/packages/web-components/fast-element/docs/guide/declaring-templates.md
|
|
6
|
-
description: While you can create and update nodes in the Shadow DOM manually, FASTElement provides a streamlined templating system for the most common rendering scenarios.
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Basic templates
|
|
10
|
-
|
|
11
|
-
While you can create and update nodes in the Shadow DOM manually, `FASTElement` provides a streamlined templating system for the most common rendering scenarios. To create an HTML template for an element, import and use the `html` tagged template helper and pass the template to the `@customElement` decorator.
|
|
12
|
-
|
|
13
|
-
Here's how we would add a template for our `name-tag` component that renders some basic structure as well as our `greeting`:
|
|
14
|
-
|
|
15
|
-
**Example: Adding a Template to a `FASTElement`**
|
|
16
|
-
|
|
17
|
-
```ts
|
|
18
|
-
import { FASTElement, customElement, attr, html } from '@microsoft/fast-element';
|
|
19
|
-
|
|
20
|
-
const template = html<NameTag>`
|
|
21
|
-
<div class="header">
|
|
22
|
-
<h3>${x => x.greeting.toUpperCase()}</h3>
|
|
23
|
-
<h4>my name is</h4>
|
|
24
|
-
</div>
|
|
25
|
-
|
|
26
|
-
<div class="body">TODO: Name Here</div>
|
|
27
|
-
|
|
28
|
-
<div class="footer"></div>
|
|
29
|
-
`;
|
|
30
|
-
|
|
31
|
-
@customElement({
|
|
32
|
-
name: 'name-tag',
|
|
33
|
-
template
|
|
34
|
-
})
|
|
35
|
-
export class NameTag extends FASTElement {
|
|
36
|
-
@attr greeting: string = 'Hello';
|
|
37
|
-
}
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
There are several important details in the above example, so let's break them down one-by-one.
|
|
41
|
-
|
|
42
|
-
First, we create a template by using a [tagged template literal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals). The tag, `html`, provides special processing for the HTML string that follows, returning an instance of `ViewTemplate`.
|
|
43
|
-
|
|
44
|
-
Within a template, we provide *bindings* that declare the *dynamic parts* of our template. These bindings are declared with [arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions). Because the template is typed, the input to your arrow function will be an instance of the data model you declared in your `html` tag. When the `html` tag processes your template, it identifies these dynamic expressions and builds up an optimized model, capable of high-performance rendering, and efficient, incremental batched updates.
|
|
45
|
-
|
|
46
|
-
Finally, we associate the template with our custom element by using a new form of the `@customElement` decorator, which allows us to pass more options. In this configuration, we pass an options object specifying the `name` and the `template`.
|
|
47
|
-
|
|
48
|
-
With this in place, we now have a `name-tag` element that will render its template into the Shadow DOM and automatically update the `h3` content whenever the name tag's `greeting` attribute changes. Give it a try!
|
|
49
|
-
|
|
50
|
-
### Typed Templates
|
|
51
|
-
|
|
52
|
-
Your templates can be *typed* to the data model that they are rendering over. In TypeScript, we provide the type as part of the tag: `html<NameTag>`. For TypeScript 3.8 or higher, you can import `ViewTemplate` as a type:
|
|
53
|
-
|
|
54
|
-
```ts
|
|
55
|
-
import type { ViewTemplate } from '@microsoft/fast-element';
|
|
56
|
-
|
|
57
|
-
const template: ViewTemplate<NameTag> = html`
|
|
58
|
-
<div>${x => x.greeting}</div>
|
|
59
|
-
`;
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
## Understanding bindings
|
|
63
|
-
|
|
64
|
-
We've seen how arrow functions can be used to declare dynamic parts of templates. Let's look at a few more examples to see the breadth of what is available to you.
|
|
65
|
-
|
|
66
|
-
### Content
|
|
67
|
-
|
|
68
|
-
To bind the content of an element, simply provide the expression within the start and end tags of the element. It can be the sole content of the element or interwoven with other elements and text.
|
|
69
|
-
|
|
70
|
-
**Example: Basic Text Content**
|
|
71
|
-
|
|
72
|
-
```html
|
|
73
|
-
<h3>${x => x.greeting.toUpperCase()}</h3>
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
**Example: Interpolated Text Content**
|
|
77
|
-
|
|
78
|
-
```html
|
|
79
|
-
<h3>${x => x.greeting}, my name is ${x => x.name}.</h3>
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
**Example: Heterogeneous Content**
|
|
83
|
-
|
|
84
|
-
```html
|
|
85
|
-
<h3>
|
|
86
|
-
${x => x.greeting}, my name is
|
|
87
|
-
<span class="name">${x => x.name}</span>.
|
|
88
|
-
</h3>
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
:::note
|
|
92
|
-
Dynamic content is set via the `textContent` HTML property for security reasons. You *cannot* set HTML content this way. See below for the explicit, opt-in mechanism for setting HTML.
|
|
93
|
-
:::
|
|
94
|
-
|
|
95
|
-
### Attributes
|
|
96
|
-
|
|
97
|
-
You can also use an expression to set an attribute value on an HTML Element. Simply place the expression where the value of the HTML attribute would go. The template engine will then use your expression to set the value using `setAttribute(...)`, whenever it needs to be updated. Additionally, some attributes are known as [boolean attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes#Boolean_Attributes) (e.g. required, readonly, disabled). These attributes behave differently from normal attributes and need special value handling. The templating engine will handle this for you if you prepend the attribute name with a `?`.
|
|
98
|
-
|
|
99
|
-
**Example: Basic Attribute Values**
|
|
100
|
-
|
|
101
|
-
```html
|
|
102
|
-
<a href="${x => x.aboutLink}">About</a>
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
**Example: Interpolated Attribute Values**
|
|
106
|
-
|
|
107
|
-
```html
|
|
108
|
-
<a href="products/${x => x.id}">
|
|
109
|
-
${x => x.name}
|
|
110
|
-
</a>
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
```html
|
|
114
|
-
<li class="list-item ${x => x.type}">
|
|
115
|
-
...
|
|
116
|
-
</li>
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
:::tip
|
|
120
|
-
When binding to `class`, the underlying engine will not over-write classes added to the element via other mechanisms. It only adds and removes classes that result directly from the binding. This "safe by default" behavior does come at a slight performance cost. To opt-out of this feature and squeeze out every ounce of performance by always overwriting all classes, use a property binding (see below) on the `className` property. e.g. `:className="list-item ${x => x.type}"`.
|
|
121
|
-
:::
|
|
122
|
-
|
|
123
|
-
```html
|
|
124
|
-
<span style="text-decoration: ${x => x.done ? 'line-through' : ''}">
|
|
125
|
-
${x => x.description}
|
|
126
|
-
</span>
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
**Example: ARIA Attributes**
|
|
130
|
-
|
|
131
|
-
```html
|
|
132
|
-
<div role="progressbar"
|
|
133
|
-
aria-valuenow="${x => x.value}"
|
|
134
|
-
aria-valuemin="${x => x.min}"
|
|
135
|
-
aria-valuemax="${x => x.max}">
|
|
136
|
-
</div>
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
**Example: Boolean Attributes**
|
|
140
|
-
|
|
141
|
-
```html
|
|
142
|
-
<button type="submit" ?disabled="${x => !x.enabled}">Submit</button>
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
**Example: Nullish value**
|
|
146
|
-
|
|
147
|
-
Some cases may occur where an attribute needs to have a value when used however not present if unused. These are different than boolean attributes by where their presence alone triggers their effect. In this situation, a nullish value (`null` or `undefined`) may be provided so the attribute won't exist in that condition.
|
|
148
|
-
|
|
149
|
-
```html
|
|
150
|
-
<div aria-hidden="${x => x.isViewable ? 'true' : null}"></div>
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
### Properties
|
|
154
|
-
|
|
155
|
-
Properties can also be set directly on an HTML element. To do so, prepend the property name with `:` to indicate a property binding. The template engine will then use your expression to assign the element's property value.
|
|
156
|
-
|
|
157
|
-
**Example: Basic Property Values**
|
|
158
|
-
|
|
159
|
-
```html
|
|
160
|
-
<my-element :myCustomProperty="${x => x.mySpecialData}">
|
|
161
|
-
...
|
|
162
|
-
</my-element>
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
**Example: Inner HTML**
|
|
166
|
-
|
|
167
|
-
```html
|
|
168
|
-
<div :innerHTML="${x => x.someDangerousHTMLContent}"></div>
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
:::warning
|
|
172
|
-
Avoid scenarios that require you to directly set HTML, especially when the content is coming from an external source. If you must do this, you should always sanitize the HTML.
|
|
173
|
-
|
|
174
|
-
The best way to accomplish HTML sanitization is to configure [a trusted types policy](https://w3c.github.io/webappsec-trusted-types/dist/spec/) with FASTElement's template engine. FASTElement ensures that all HTML strings pass through the configured policy. Also, by leveraging the platform's trusted types capabilities, you get native enforcement of the policy through CSP headers. Here's an example of how to configure a custom policy to sanitize HTML:
|
|
175
|
-
|
|
176
|
-
```ts
|
|
177
|
-
import { DOM } from '@microsoft/fast-element';
|
|
178
|
-
|
|
179
|
-
const myPolicy = trustedTypes.createPolicy('my-policy', {
|
|
180
|
-
createHTML(html) {
|
|
181
|
-
// TODO: invoke a sanitization library on the html before returning it
|
|
182
|
-
return html;
|
|
183
|
-
}
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
DOM.setHTMLPolicy(myPolicy);
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
For security reasons, the HTML Policy can only be set once. For this reason, it should be set by application developers and not by component authors, and it should be done immediately during the startup sequence of the application.
|
|
190
|
-
:::
|
|
191
|
-
|
|
192
|
-
### Events
|
|
193
|
-
|
|
194
|
-
Besides rendering content, attributes, and properties, you'll often want to add event listeners and execute code when events fire. To do that, prepend the event name with `@` and provide the expression to be called when that event fires. Within an event binding, you also have access to a special *context* argument from which you can access the `event` object.
|
|
195
|
-
|
|
196
|
-
**Example: Basic Events**
|
|
197
|
-
|
|
198
|
-
```html
|
|
199
|
-
<button @click="${x => x.remove()}">Remove</button>
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
**Example: Accessing Event Details**
|
|
203
|
-
|
|
204
|
-
```html
|
|
205
|
-
<input type="text"
|
|
206
|
-
:value="${x => x.description}"
|
|
207
|
-
@input="${(x, c) => x.handleDescriptionChange(c.event)}">
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
:::important
|
|
211
|
-
In both examples above, after your event handler is executed, `preventDefault()` will be called on the event object by default. You can return `true` from your handler to opt-out of this behavior.
|
|
212
|
-
:::
|
|
213
|
-
|
|
214
|
-
The second example demonstrates an important characteristic of the templating engine: it only supports *unidirectional data flow* (model => view). It does not support *two-way data binding* (model <=> view). As shown above, pushing data from the view back to the model should be handled with explicit events that call into your model's API.
|
|
215
|
-
|
|
216
|
-
## Templating and the element lifecycle
|
|
217
|
-
|
|
218
|
-
It is during the `connectedCallback` phase of the Custom Element lifecycle that `FASTElement` creates templates and binds the resulting view. The creation of the template only occurs the first time the element is connected, while binding happens every time the element is connected (with unbinding happening during the `disconnectedCallback` for symmetry).
|
|
219
|
-
|
|
220
|
-
:::note
|
|
221
|
-
In the future, we're planning new optimizations that will enable us to safely determine when we do not need to unbind/rebind certain views.
|
|
222
|
-
:::
|
|
223
|
-
|
|
224
|
-
In most cases, the template that `FASTElement` renders is determined by the `template` property of the Custom Element's configuration. However, you can also implement a method on your Custom Element class named `resolveTemplate()` that returns a template instance. If this method is present, it will be called during `connectedCallback` to obtain the template to use. This allows the element author to dynamically select completely different templates based on the state of the element at the time of connection.
|
|
225
|
-
|
|
226
|
-
In addition to dynamic template selection during the `connectedCallback`, the `$fastController` property of `FASTElement` enables dynamically changing the template at any time by setting the controller's `template` property to any valid template.
|
|
227
|
-
|
|
228
|
-
:::tip
|
|
229
|
-
Check out [our Cheat Sheet](../resources/cheat-sheet.md#bindings) for a quick guide on bindings.
|
|
230
|
-
:::
|