@microsoft/fast-element 1.10.1 → 2.0.0-beta.2
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 -12
- package/CHANGELOG.json +432 -1
- package/CHANGELOG.md +74 -2
- package/README.md +2 -2
- package/dist/dts/components/attributes.d.ts +4 -1
- package/dist/dts/components/controller.d.ts +12 -11
- package/dist/dts/components/fast-definitions.d.ts +10 -2
- package/dist/dts/components/fast-element.d.ts +12 -5
- package/dist/dts/context.d.ts +157 -0
- package/dist/dts/debug.d.ts +1 -0
- package/dist/dts/hooks.d.ts +20 -0
- package/dist/dts/index.d.ts +16 -15
- package/dist/dts/index.debug.d.ts +2 -0
- package/dist/dts/index.rollup.d.ts +2 -0
- package/dist/dts/index.rollup.debug.d.ts +3 -0
- package/dist/dts/interfaces.d.ts +144 -0
- package/dist/dts/metadata.d.ts +25 -0
- package/dist/dts/observation/arrays.d.ts +207 -0
- package/dist/dts/observation/behavior.d.ts +4 -4
- package/dist/dts/observation/notifier.d.ts +18 -18
- package/dist/dts/observation/observable.d.ts +56 -18
- package/dist/dts/observation/splice-strategies.d.ts +13 -0
- package/dist/dts/observation/update-queue.d.ts +40 -0
- package/dist/dts/platform.d.ts +18 -67
- package/dist/dts/polyfills.d.ts +8 -0
- package/dist/dts/styles/css-directive.d.ts +43 -5
- package/dist/dts/styles/css.d.ts +19 -3
- package/dist/dts/styles/element-styles.d.ts +42 -62
- package/dist/dts/templating/binding-signal.d.ts +38 -0
- package/dist/dts/templating/binding-two-way.d.ts +56 -0
- package/dist/dts/templating/binding.d.ts +233 -65
- package/dist/dts/templating/children.d.ts +18 -15
- package/dist/dts/templating/compiler.d.ts +46 -28
- package/dist/dts/templating/dom.d.ts +41 -0
- package/dist/dts/templating/html-directive.d.ts +179 -43
- package/dist/dts/templating/markup.d.ts +48 -0
- package/dist/dts/templating/node-observation.d.ts +45 -29
- package/dist/dts/templating/ref.d.ts +6 -12
- package/dist/dts/templating/repeat.d.ts +26 -14
- package/dist/dts/templating/slotted.d.ts +13 -14
- package/dist/dts/templating/template.d.ts +27 -21
- package/dist/dts/templating/view.d.ts +15 -22
- package/dist/{tsdoc-metadata.json → dts/tsdoc-metadata.json} +1 -1
- package/dist/dts/utilities.d.ts +40 -0
- package/dist/esm/components/attributes.js +25 -24
- package/dist/esm/components/controller.js +77 -57
- package/dist/esm/components/fast-definitions.js +16 -22
- package/dist/esm/components/fast-element.js +10 -2
- package/dist/esm/context.js +159 -0
- package/dist/esm/debug.js +29 -0
- package/dist/esm/hooks.js +32 -0
- package/dist/esm/index.debug.js +2 -0
- package/dist/esm/index.js +19 -14
- package/dist/esm/index.rollup.debug.js +3 -0
- package/dist/esm/index.rollup.js +2 -0
- package/dist/esm/interfaces.js +8 -1
- package/dist/esm/metadata.js +60 -0
- package/dist/esm/observation/arrays.js +269 -0
- package/dist/esm/observation/notifier.js +75 -83
- package/dist/esm/observation/observable.js +93 -68
- package/dist/esm/observation/{array-change-records.js → splice-strategies.js} +136 -62
- package/dist/esm/observation/update-queue.js +67 -0
- package/dist/esm/platform.js +36 -42
- package/dist/esm/polyfills.js +85 -0
- package/dist/esm/styles/css-directive.js +29 -13
- package/dist/esm/styles/css.js +27 -40
- package/dist/esm/styles/element-styles.js +65 -104
- package/dist/esm/templating/binding-signal.js +84 -0
- package/dist/esm/templating/binding-two-way.js +76 -0
- package/dist/esm/templating/binding.js +306 -153
- package/dist/esm/templating/children.js +33 -23
- package/dist/esm/templating/compiler.js +235 -152
- package/dist/esm/templating/dom.js +49 -0
- package/dist/esm/templating/html-directive.js +125 -40
- package/dist/esm/templating/markup.js +75 -0
- package/dist/esm/templating/node-observation.js +50 -45
- package/dist/esm/templating/ref.js +7 -16
- package/dist/esm/templating/repeat.js +39 -36
- package/dist/esm/templating/slotted.js +23 -20
- package/dist/esm/templating/template.js +51 -95
- package/dist/esm/templating/view.js +44 -43
- package/dist/esm/templating/when.js +2 -1
- package/dist/esm/utilities.js +139 -0
- package/dist/fast-element.api.json +11789 -5377
- package/dist/fast-element.d.ts +1177 -531
- package/dist/fast-element.debug.js +3717 -0
- package/dist/fast-element.debug.min.js +1 -0
- package/dist/fast-element.js +3466 -4022
- package/dist/fast-element.min.js +1 -1
- package/dist/fast-element.untrimmed.d.ts +2697 -0
- package/docs/api-report.md +472 -219
- package/docs/fast-element-2-changes.md +15 -0
- package/docs/guide/declaring-templates.md +5 -4
- package/docs/guide/defining-elements.md +3 -2
- package/docs/guide/leveraging-css.md +1 -0
- package/docs/guide/next-steps.md +3 -2
- package/docs/guide/observables-and-state.md +2 -1
- package/docs/guide/using-directives.md +2 -1
- package/docs/guide/working-with-shadow-dom.md +1 -0
- package/karma.conf.cjs +6 -17
- package/package.json +65 -15
- package/dist/dts/dom.d.ts +0 -112
- package/dist/dts/observation/array-change-records.d.ts +0 -48
- package/dist/dts/observation/array-observer.d.ts +0 -9
- package/dist/esm/dom.js +0 -207
- package/dist/esm/observation/array-observer.js +0 -173
|
@@ -1,86 +1,33 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { isFunction, isString } from "../interfaces.js";
|
|
2
|
+
import { ExecutionContext } from "../observation/observable.js";
|
|
3
|
+
import { bind, oneTime } from "./binding.js";
|
|
4
|
+
import { Compiler } from "./compiler.js";
|
|
5
|
+
import { Aspect, HTMLDirective, } from "./html-directive.js";
|
|
6
|
+
import { nextId } from "./markup.js";
|
|
7
7
|
/**
|
|
8
8
|
* A template capable of creating HTMLView instances or rendering directly to DOM.
|
|
9
9
|
* @public
|
|
10
10
|
*/
|
|
11
|
-
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
|
12
11
|
export class ViewTemplate {
|
|
13
12
|
/**
|
|
14
13
|
* Creates an instance of ViewTemplate.
|
|
15
14
|
* @param html - The html representing what this template will instantiate, including placeholders for directives.
|
|
16
|
-
* @param
|
|
15
|
+
* @param factories - The directives that will be connected to placeholders in the html.
|
|
17
16
|
*/
|
|
18
|
-
constructor(html,
|
|
19
|
-
this.
|
|
20
|
-
this.hasHostBehaviors = false;
|
|
21
|
-
this.fragment = null;
|
|
22
|
-
this.targetOffset = 0;
|
|
23
|
-
this.viewBehaviorFactories = null;
|
|
24
|
-
this.hostBehaviorFactories = null;
|
|
17
|
+
constructor(html, factories) {
|
|
18
|
+
this.result = null;
|
|
25
19
|
this.html = html;
|
|
26
|
-
this.
|
|
20
|
+
this.factories = factories;
|
|
27
21
|
}
|
|
28
22
|
/**
|
|
29
23
|
* Creates an HTMLView instance based on this template definition.
|
|
30
24
|
* @param hostBindingTarget - The element that host behaviors will be bound to.
|
|
31
25
|
*/
|
|
32
26
|
create(hostBindingTarget) {
|
|
33
|
-
if (this.
|
|
34
|
-
|
|
35
|
-
const html = this.html;
|
|
36
|
-
if (typeof html === "string") {
|
|
37
|
-
template = document.createElement("template");
|
|
38
|
-
template.innerHTML = DOM.createHTML(html);
|
|
39
|
-
const fec = template.content.firstElementChild;
|
|
40
|
-
if (fec !== null && fec.tagName === "TEMPLATE") {
|
|
41
|
-
template = fec;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
template = html;
|
|
46
|
-
}
|
|
47
|
-
const result = compileTemplate(template, this.directives);
|
|
48
|
-
this.fragment = result.fragment;
|
|
49
|
-
this.viewBehaviorFactories = result.viewBehaviorFactories;
|
|
50
|
-
this.hostBehaviorFactories = result.hostBehaviorFactories;
|
|
51
|
-
this.targetOffset = result.targetOffset;
|
|
52
|
-
this.behaviorCount =
|
|
53
|
-
this.viewBehaviorFactories.length + this.hostBehaviorFactories.length;
|
|
54
|
-
this.hasHostBehaviors = this.hostBehaviorFactories.length > 0;
|
|
55
|
-
}
|
|
56
|
-
const fragment = this.fragment.cloneNode(true);
|
|
57
|
-
const viewFactories = this.viewBehaviorFactories;
|
|
58
|
-
const behaviors = new Array(this.behaviorCount);
|
|
59
|
-
const walker = DOM.createTemplateWalker(fragment);
|
|
60
|
-
let behaviorIndex = 0;
|
|
61
|
-
let targetIndex = this.targetOffset;
|
|
62
|
-
let node = walker.nextNode();
|
|
63
|
-
for (let ii = viewFactories.length; behaviorIndex < ii; ++behaviorIndex) {
|
|
64
|
-
const factory = viewFactories[behaviorIndex];
|
|
65
|
-
const factoryIndex = factory.targetIndex;
|
|
66
|
-
while (node !== null) {
|
|
67
|
-
if (targetIndex === factoryIndex) {
|
|
68
|
-
behaviors[behaviorIndex] = factory.createBehavior(node);
|
|
69
|
-
break;
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
node = walker.nextNode();
|
|
73
|
-
targetIndex++;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
27
|
+
if (this.result === null) {
|
|
28
|
+
this.result = Compiler.compile(this.html, this.factories);
|
|
76
29
|
}
|
|
77
|
-
|
|
78
|
-
const hostFactories = this.hostBehaviorFactories;
|
|
79
|
-
for (let i = 0, ii = hostFactories.length; i < ii; ++i, ++behaviorIndex) {
|
|
80
|
-
behaviors[behaviorIndex] = hostFactories[i].createBehavior(hostBindingTarget);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return new HTMLView(fragment, behaviors);
|
|
30
|
+
return this.result.createView(hostBindingTarget);
|
|
84
31
|
}
|
|
85
32
|
/**
|
|
86
33
|
* Creates an HTMLView from this template, binds it to the source, and then appends it to the host.
|
|
@@ -89,15 +36,9 @@ export class ViewTemplate {
|
|
|
89
36
|
* @param hostBindingTarget - An HTML element to target the host bindings at if different from the
|
|
90
37
|
* host that the template is being attached to.
|
|
91
38
|
*/
|
|
92
|
-
render(source, host, hostBindingTarget) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
if (hostBindingTarget === void 0) {
|
|
97
|
-
hostBindingTarget = host;
|
|
98
|
-
}
|
|
99
|
-
const view = this.create(hostBindingTarget);
|
|
100
|
-
view.bind(source, defaultExecutionContext);
|
|
39
|
+
render(source, host, hostBindingTarget, context) {
|
|
40
|
+
const view = this.create(hostBindingTarget !== null && hostBindingTarget !== void 0 ? hostBindingTarget : host);
|
|
41
|
+
view.bind(source, context !== null && context !== void 0 ? context : ExecutionContext.default);
|
|
101
42
|
view.appendTo(host);
|
|
102
43
|
return view;
|
|
103
44
|
}
|
|
@@ -106,8 +47,15 @@ export class ViewTemplate {
|
|
|
106
47
|
const lastAttributeNameRegex =
|
|
107
48
|
/* eslint-disable-next-line no-control-regex */
|
|
108
49
|
/([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;
|
|
50
|
+
function createAspectedHTML(value, prevString, add) {
|
|
51
|
+
const match = lastAttributeNameRegex.exec(prevString);
|
|
52
|
+
if (match !== null) {
|
|
53
|
+
Aspect.assign(value, match[2]);
|
|
54
|
+
}
|
|
55
|
+
return value.createHTML(add);
|
|
56
|
+
}
|
|
109
57
|
/**
|
|
110
|
-
* Transforms a template literal string into a
|
|
58
|
+
* Transforms a template literal string into a ViewTemplate.
|
|
111
59
|
* @param strings - The string fragments that are interpolated with the values.
|
|
112
60
|
* @param values - The values that are interpolated with the string fragments.
|
|
113
61
|
* @remarks
|
|
@@ -116,36 +64,44 @@ const lastAttributeNameRegex =
|
|
|
116
64
|
* @public
|
|
117
65
|
*/
|
|
118
66
|
export function html(strings, ...values) {
|
|
119
|
-
const directives = [];
|
|
120
67
|
let html = "";
|
|
68
|
+
const factories = Object.create(null);
|
|
69
|
+
const add = (factory) => {
|
|
70
|
+
var _a;
|
|
71
|
+
const id = (_a = factory.id) !== null && _a !== void 0 ? _a : (factory.id = nextId());
|
|
72
|
+
factories[id] = factory;
|
|
73
|
+
return id;
|
|
74
|
+
};
|
|
121
75
|
for (let i = 0, ii = strings.length - 1; i < ii; ++i) {
|
|
122
76
|
const currentString = strings[i];
|
|
123
|
-
|
|
77
|
+
const currentValue = values[i];
|
|
78
|
+
let definition;
|
|
124
79
|
html += currentString;
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
value = () => template;
|
|
80
|
+
if (isFunction(currentValue)) {
|
|
81
|
+
html += createAspectedHTML(bind(currentValue), currentString, add);
|
|
128
82
|
}
|
|
129
|
-
if (
|
|
130
|
-
value = new HTMLBindingDirective(value);
|
|
131
|
-
}
|
|
132
|
-
if (value instanceof TargetedHTMLDirective) {
|
|
83
|
+
else if (isString(currentValue)) {
|
|
133
84
|
const match = lastAttributeNameRegex.exec(currentString);
|
|
134
85
|
if (match !== null) {
|
|
135
|
-
|
|
86
|
+
const directive = bind(() => currentValue, oneTime);
|
|
87
|
+
Aspect.assign(directive, match[2]);
|
|
88
|
+
html += directive.createHTML(add);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
html += currentValue;
|
|
136
92
|
}
|
|
137
93
|
}
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
// as the index for the placeholder. Instead, we need to
|
|
141
|
-
// use directives.length to get the next index.
|
|
142
|
-
html += value.createPlaceholder(directives.length);
|
|
143
|
-
directives.push(value);
|
|
94
|
+
else if ((definition = HTMLDirective.getForInstance(currentValue)) === void 0) {
|
|
95
|
+
html += createAspectedHTML(bind(() => currentValue, oneTime), currentString, add);
|
|
144
96
|
}
|
|
145
97
|
else {
|
|
146
|
-
|
|
98
|
+
if (definition.aspected) {
|
|
99
|
+
html += createAspectedHTML(currentValue, currentString, add);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
html += currentValue.createHTML(add);
|
|
103
|
+
}
|
|
147
104
|
}
|
|
148
105
|
}
|
|
149
|
-
html
|
|
150
|
-
return new ViewTemplate(html, directives);
|
|
106
|
+
return new ViewTemplate(html + strings[strings.length - 1], factories);
|
|
151
107
|
}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
function removeNodeSequence(firstNode, lastNode) {
|
|
2
|
+
const parent = firstNode.parentNode;
|
|
3
|
+
let current = firstNode;
|
|
4
|
+
let next;
|
|
5
|
+
while (current !== lastNode) {
|
|
6
|
+
next = current.nextSibling;
|
|
7
|
+
parent.removeChild(current);
|
|
8
|
+
current = next;
|
|
9
|
+
}
|
|
10
|
+
parent.removeChild(lastNode);
|
|
11
|
+
}
|
|
4
12
|
/**
|
|
5
13
|
* The standard View implementation, which also implements ElementView and SyntheticView.
|
|
6
14
|
* @public
|
|
@@ -11,9 +19,11 @@ export class HTMLView {
|
|
|
11
19
|
* @param fragment - The html fragment that contains the nodes for this view.
|
|
12
20
|
* @param behaviors - The behaviors to be applied to this view.
|
|
13
21
|
*/
|
|
14
|
-
constructor(fragment,
|
|
22
|
+
constructor(fragment, factories, targets) {
|
|
15
23
|
this.fragment = fragment;
|
|
16
|
-
this.
|
|
24
|
+
this.factories = factories;
|
|
25
|
+
this.targets = targets;
|
|
26
|
+
this.behaviors = null;
|
|
17
27
|
/**
|
|
18
28
|
* The data that the view is bound to.
|
|
19
29
|
*/
|
|
@@ -74,21 +84,8 @@ export class HTMLView {
|
|
|
74
84
|
* Once a view has been disposed, it cannot be inserted or bound again.
|
|
75
85
|
*/
|
|
76
86
|
dispose() {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
let current = this.firstChild;
|
|
80
|
-
let next;
|
|
81
|
-
while (current !== end) {
|
|
82
|
-
next = current.nextSibling;
|
|
83
|
-
parent.removeChild(current);
|
|
84
|
-
current = next;
|
|
85
|
-
}
|
|
86
|
-
parent.removeChild(end);
|
|
87
|
-
const behaviors = this.behaviors;
|
|
88
|
-
const oldSource = this.source;
|
|
89
|
-
for (let i = 0, ii = behaviors.length; i < ii; ++i) {
|
|
90
|
-
behaviors[i].unbind(oldSource);
|
|
91
|
-
}
|
|
87
|
+
removeNodeSequence(this.firstChild, this.lastChild);
|
|
88
|
+
this.unbind();
|
|
92
89
|
}
|
|
93
90
|
/**
|
|
94
91
|
* Binds a view's behaviors to its binding source.
|
|
@@ -96,25 +93,33 @@ export class HTMLView {
|
|
|
96
93
|
* @param context - The execution context to run the behaviors within.
|
|
97
94
|
*/
|
|
98
95
|
bind(source, context) {
|
|
99
|
-
|
|
100
|
-
|
|
96
|
+
let behaviors = this.behaviors;
|
|
97
|
+
const oldSource = this.source;
|
|
98
|
+
if (oldSource === source) {
|
|
101
99
|
return;
|
|
102
100
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
101
|
+
this.source = source;
|
|
102
|
+
this.context = context;
|
|
103
|
+
const targets = this.targets;
|
|
104
|
+
if (oldSource !== null) {
|
|
107
105
|
for (let i = 0, ii = behaviors.length; i < ii; ++i) {
|
|
108
106
|
const current = behaviors[i];
|
|
109
|
-
current.unbind(oldSource);
|
|
110
|
-
current.bind(source, context);
|
|
107
|
+
current.unbind(oldSource, context, targets);
|
|
108
|
+
current.bind(source, context, targets);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else if (behaviors === null) {
|
|
112
|
+
this.behaviors = behaviors = new Array(this.factories.length);
|
|
113
|
+
const factories = this.factories;
|
|
114
|
+
for (let i = 0, ii = factories.length; i < ii; ++i) {
|
|
115
|
+
const behavior = factories[i].createBehavior(targets);
|
|
116
|
+
behavior.bind(source, context, targets);
|
|
117
|
+
behaviors[i] = behavior;
|
|
111
118
|
}
|
|
112
119
|
}
|
|
113
120
|
else {
|
|
114
|
-
this.source = source;
|
|
115
|
-
this.context = context;
|
|
116
121
|
for (let i = 0, ii = behaviors.length; i < ii; ++i) {
|
|
117
|
-
behaviors[i].bind(source, context);
|
|
122
|
+
behaviors[i].bind(source, context, targets);
|
|
118
123
|
}
|
|
119
124
|
}
|
|
120
125
|
}
|
|
@@ -122,15 +127,18 @@ export class HTMLView {
|
|
|
122
127
|
* Unbinds a view's behaviors from its binding source.
|
|
123
128
|
*/
|
|
124
129
|
unbind() {
|
|
125
|
-
|
|
130
|
+
const oldSource = this.source;
|
|
131
|
+
if (oldSource === null) {
|
|
126
132
|
return;
|
|
127
133
|
}
|
|
134
|
+
const targets = this.targets;
|
|
135
|
+
const context = this.context;
|
|
128
136
|
const behaviors = this.behaviors;
|
|
129
|
-
const oldSource = this.source;
|
|
130
137
|
for (let i = 0, ii = behaviors.length; i < ii; ++i) {
|
|
131
|
-
behaviors[i].unbind(oldSource);
|
|
138
|
+
behaviors[i].unbind(oldSource, context, targets);
|
|
132
139
|
}
|
|
133
140
|
this.source = null;
|
|
141
|
+
this.context = null;
|
|
134
142
|
}
|
|
135
143
|
/**
|
|
136
144
|
* Efficiently disposes of a contiguous range of synthetic view instances.
|
|
@@ -140,16 +148,9 @@ export class HTMLView {
|
|
|
140
148
|
if (views.length === 0) {
|
|
141
149
|
return;
|
|
142
150
|
}
|
|
143
|
-
|
|
144
|
-
range.setEndAfter(views[views.length - 1].lastChild);
|
|
145
|
-
range.deleteContents();
|
|
151
|
+
removeNodeSequence(views[0].firstChild, views[views.length - 1].lastChild);
|
|
146
152
|
for (let i = 0, ii = views.length; i < ii; ++i) {
|
|
147
|
-
|
|
148
|
-
const behaviors = view.behaviors;
|
|
149
|
-
const oldSource = view.source;
|
|
150
|
-
for (let j = 0, jj = behaviors.length; j < jj; ++j) {
|
|
151
|
-
behaviors[j].unbind(oldSource);
|
|
152
|
-
}
|
|
153
|
+
views[i].unbind();
|
|
153
154
|
}
|
|
154
155
|
}
|
|
155
156
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isFunction } from "../interfaces.js";
|
|
1
2
|
/**
|
|
2
3
|
* A directive that enables basic conditional rendering in a template.
|
|
3
4
|
* @param binding - The condition to test for rendering.
|
|
@@ -6,7 +7,7 @@
|
|
|
6
7
|
* @public
|
|
7
8
|
*/
|
|
8
9
|
export function when(binding, templateOrTemplateBinding) {
|
|
9
|
-
const getTemplate =
|
|
10
|
+
const getTemplate = isFunction(templateOrTemplateBinding)
|
|
10
11
|
? templateOrTemplateBinding
|
|
11
12
|
: () => templateOrTemplateBinding;
|
|
12
13
|
return (source, context) => binding(source, context) ? getTemplate(source, context) : null;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { ArrayObserver } from "./index.debug.js";
|
|
2
|
+
import { isFunction } from "./interfaces.js";
|
|
3
|
+
import { Observable } from "./observation/observable.js";
|
|
4
|
+
function shouldTraverse(value, traversed) {
|
|
5
|
+
return (value !== null &&
|
|
6
|
+
value !== void 0 &&
|
|
7
|
+
typeof value === "object" &&
|
|
8
|
+
!traversed.has(value));
|
|
9
|
+
}
|
|
10
|
+
function traverseObject(object, deep, visitor, data, traversed) {
|
|
11
|
+
if (!shouldTraverse(object, traversed)) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
traversed.add(object);
|
|
15
|
+
if (Array.isArray(object)) {
|
|
16
|
+
visitor.visitArray(object, data);
|
|
17
|
+
for (const item of object) {
|
|
18
|
+
traverseObject(item, deep, visitor, data, traversed);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
visitor.visitObject(object, data);
|
|
23
|
+
for (const key in object) {
|
|
24
|
+
const value = object[key];
|
|
25
|
+
visitor.visitProperty(object, key, value, data);
|
|
26
|
+
if (deep) {
|
|
27
|
+
traverseObject(value, deep, visitor, data, traversed);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const noop = () => void 0;
|
|
33
|
+
const observed = new WeakSet();
|
|
34
|
+
const makeObserverVisitor = {
|
|
35
|
+
visitObject: noop,
|
|
36
|
+
visitArray: noop,
|
|
37
|
+
visitProperty(object, propertyName, value) {
|
|
38
|
+
Reflect.defineProperty(object, propertyName, {
|
|
39
|
+
enumerable: true,
|
|
40
|
+
get() {
|
|
41
|
+
Observable.track(object, propertyName);
|
|
42
|
+
return value;
|
|
43
|
+
},
|
|
44
|
+
set(newValue) {
|
|
45
|
+
if (value !== newValue) {
|
|
46
|
+
value = newValue;
|
|
47
|
+
Observable.notify(object, propertyName);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
function watchObject(object, data) {
|
|
54
|
+
const notifier = Observable.getNotifier(object);
|
|
55
|
+
notifier.subscribe(data.subscriber);
|
|
56
|
+
data.notifiers.push(notifier);
|
|
57
|
+
}
|
|
58
|
+
const watchVisitor = {
|
|
59
|
+
visitProperty: noop,
|
|
60
|
+
visitObject: watchObject,
|
|
61
|
+
visitArray: watchObject,
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Converts a plain object to an observable object.
|
|
65
|
+
* @param object - The object to make observable.
|
|
66
|
+
* @param deep - Indicates whether or not to deeply convert the oject.
|
|
67
|
+
* @returns The converted object.
|
|
68
|
+
* @beta
|
|
69
|
+
*/
|
|
70
|
+
export function makeObservable(object, deep = false) {
|
|
71
|
+
traverseObject(object, deep, makeObserverVisitor, void 0, observed);
|
|
72
|
+
return object;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Deeply subscribes to changes in existing observable objects.
|
|
76
|
+
* @param object - The observable object to watch.
|
|
77
|
+
* @param subscriber - The handler to call when changes are made to the object.
|
|
78
|
+
* @returns A disposable that can be used to unsubscribe from change updates.
|
|
79
|
+
* @beta
|
|
80
|
+
*/
|
|
81
|
+
export function watch(object, subscriber) {
|
|
82
|
+
const data = {
|
|
83
|
+
notifiers: [],
|
|
84
|
+
subscriber: isFunction(subscriber) ? { handleChange: subscriber } : subscriber,
|
|
85
|
+
};
|
|
86
|
+
ArrayObserver.enable();
|
|
87
|
+
traverseObject(object, true, watchVisitor, data, new Set());
|
|
88
|
+
return {
|
|
89
|
+
dispose() {
|
|
90
|
+
for (const n of data.notifiers) {
|
|
91
|
+
n.unsubscribe(data.subscriber);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Retrieves the "composed parent" element of a node, ignoring DOM tree boundaries.
|
|
98
|
+
* When the parent of a node is a shadow-root, it will return the host
|
|
99
|
+
* element of the shadow root. Otherwise it will return the parent node or null if
|
|
100
|
+
* no parent node exists.
|
|
101
|
+
* @param element - The element for which to retrieve the composed parent
|
|
102
|
+
*
|
|
103
|
+
* @public
|
|
104
|
+
*/
|
|
105
|
+
export function composedParent(element) {
|
|
106
|
+
const parentNode = element.parentElement;
|
|
107
|
+
if (parentNode) {
|
|
108
|
+
return parentNode;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
const rootNode = element.getRootNode();
|
|
112
|
+
if (rootNode.host instanceof HTMLElement) {
|
|
113
|
+
// this is shadow-root
|
|
114
|
+
return rootNode.host;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Determines if the reference element contains the test element in a "composed" DOM tree that
|
|
121
|
+
* ignores shadow DOM boundaries.
|
|
122
|
+
*
|
|
123
|
+
* Returns true of the test element is a descendent of the reference, or exist in
|
|
124
|
+
* a shadow DOM that is a logical descendent of the reference. Otherwise returns false.
|
|
125
|
+
* @param reference - The element to test for containment against.
|
|
126
|
+
* @param test - The element being tested for containment.
|
|
127
|
+
*
|
|
128
|
+
* @public
|
|
129
|
+
*/
|
|
130
|
+
export function composedContains(reference, test) {
|
|
131
|
+
let current = test;
|
|
132
|
+
while (current !== null) {
|
|
133
|
+
if (current === reference) {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
current = composedParent(current);
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
139
|
+
}
|