@microsoft/fast-element 2.0.0-beta.1 → 2.0.0-beta.11
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/CHANGELOG.json +348 -0
- package/CHANGELOG.md +114 -1
- package/dist/dts/components/attributes.d.ts +10 -0
- package/dist/dts/components/{controller.d.ts → element-controller.d.ts} +49 -25
- package/dist/dts/components/fast-definitions.d.ts +43 -9
- package/dist/dts/components/fast-element.d.ts +15 -21
- package/dist/dts/context.d.ts +157 -0
- package/dist/dts/di/di.d.ts +899 -0
- package/dist/dts/index.d.ts +2 -2
- package/dist/dts/interfaces.d.ts +45 -14
- package/dist/dts/metadata.d.ts +25 -0
- package/dist/dts/observation/arrays.d.ts +1 -1
- package/dist/dts/observation/observable.d.ts +101 -75
- package/dist/dts/pending-task.d.ts +20 -0
- package/dist/dts/platform.d.ts +7 -0
- package/dist/dts/polyfills.d.ts +1 -8
- package/dist/dts/state/exports.d.ts +3 -0
- package/dist/dts/state/reactive.d.ts +8 -0
- package/dist/dts/state/state.d.ts +141 -0
- package/dist/dts/state/visitor.d.ts +6 -0
- package/dist/dts/state/watch.d.ts +10 -0
- package/dist/dts/styles/css-directive.d.ts +2 -2
- package/dist/dts/styles/element-styles.d.ts +10 -17
- package/dist/dts/styles/host.d.ts +68 -0
- package/dist/dts/templating/binding-signal.d.ts +21 -0
- package/dist/dts/templating/binding-two-way.d.ts +39 -0
- package/dist/dts/templating/binding.d.ts +69 -294
- package/dist/dts/templating/children.d.ts +1 -1
- package/dist/dts/templating/compiler.d.ts +1 -2
- package/dist/dts/templating/html-directive.d.ts +93 -35
- package/dist/dts/templating/node-observation.d.ts +4 -5
- package/dist/dts/templating/ref.d.ts +5 -13
- package/dist/dts/templating/render.d.ts +272 -0
- package/dist/dts/templating/repeat.d.ts +20 -75
- package/dist/dts/templating/slotted.d.ts +1 -1
- package/dist/dts/templating/template.d.ts +12 -61
- package/dist/dts/templating/view.d.ts +77 -12
- package/dist/dts/templating/when.d.ts +3 -3
- package/dist/dts/testing/exports.d.ts +3 -0
- package/dist/dts/testing/fakes.d.ts +4 -0
- package/dist/dts/testing/fixture.d.ts +84 -0
- package/dist/dts/testing/timeout.d.ts +7 -0
- package/dist/{tsdoc-metadata.json → dts/tsdoc-metadata.json} +0 -0
- package/dist/dts/utilities.d.ts +0 -18
- package/dist/esm/components/attributes.js +13 -4
- package/dist/esm/components/{controller.js → element-controller.js} +188 -109
- package/dist/esm/components/fast-definitions.js +38 -28
- package/dist/esm/components/fast-element.js +31 -12
- package/dist/esm/context.js +163 -0
- package/dist/esm/debug.js +36 -4
- package/dist/esm/di/di.js +1435 -0
- package/dist/esm/index.js +2 -1
- package/dist/esm/interfaces.js +4 -0
- package/dist/esm/metadata.js +60 -0
- package/dist/esm/observation/arrays.js +304 -3
- package/dist/esm/observation/observable.js +81 -87
- package/dist/esm/pending-task.js +16 -0
- package/dist/esm/platform.js +26 -1
- package/dist/esm/polyfills.js +1 -55
- package/dist/esm/state/exports.js +3 -0
- package/dist/esm/state/reactive.js +34 -0
- package/dist/esm/state/state.js +148 -0
- package/dist/esm/state/visitor.js +28 -0
- package/dist/esm/state/watch.js +36 -0
- package/dist/esm/styles/css.js +4 -4
- package/dist/esm/styles/element-styles.js +14 -33
- package/dist/esm/{observation/behavior.js → styles/host.js} +0 -0
- package/dist/esm/templating/binding-signal.js +83 -0
- package/dist/esm/templating/binding-two-way.js +103 -0
- package/dist/esm/templating/binding.js +134 -414
- package/dist/esm/templating/compiler.js +30 -7
- package/dist/esm/templating/html-directive.js +100 -28
- package/dist/esm/templating/node-observation.js +9 -8
- package/dist/esm/templating/ref.js +4 -12
- package/dist/esm/templating/render.js +391 -0
- package/dist/esm/templating/repeat.js +96 -72
- package/dist/esm/templating/template.js +11 -29
- package/dist/esm/templating/view.js +107 -29
- package/dist/esm/templating/when.js +5 -4
- package/dist/esm/testing/exports.js +3 -0
- package/dist/esm/testing/fakes.js +76 -0
- package/dist/esm/testing/fixture.js +86 -0
- package/dist/esm/testing/timeout.js +24 -0
- package/dist/esm/utilities.js +0 -95
- package/dist/fast-element.api.json +9278 -10745
- package/dist/fast-element.d.ts +707 -813
- package/dist/fast-element.debug.js +1229 -944
- package/dist/fast-element.debug.min.js +1 -1
- package/dist/fast-element.js +1191 -938
- package/dist/fast-element.min.js +1 -1
- package/dist/fast-element.untrimmed.d.ts +716 -824
- package/docs/api-report.md +265 -319
- package/package.json +39 -14
- package/dist/dts/hooks.d.ts +0 -20
- package/dist/dts/observation/behavior.d.ts +0 -19
- package/dist/dts/observation/splice-strategies.d.ts +0 -13
- package/dist/esm/hooks.js +0 -32
- package/dist/esm/observation/splice-strategies.js +0 -400
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import { FASTElementDefinition } from "../components/fast-definitions.js";
|
|
2
|
+
import { isFunction, isString } from "../interfaces.js";
|
|
3
|
+
import { bind, normalizeBinding, oneTime, } from "./binding.js";
|
|
4
|
+
import { Binding, HTMLDirective, } from "./html-directive.js";
|
|
5
|
+
import { Markup } from "./markup.js";
|
|
6
|
+
import { html, } from "./template.js";
|
|
7
|
+
/**
|
|
8
|
+
* A Behavior that enables advanced rendering.
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
export class RenderBehavior {
|
|
12
|
+
/**
|
|
13
|
+
* Creates an instance of RenderBehavior.
|
|
14
|
+
* @param directive - The render directive that created this behavior.
|
|
15
|
+
*/
|
|
16
|
+
constructor(directive) {
|
|
17
|
+
this.directive = directive;
|
|
18
|
+
this.location = null;
|
|
19
|
+
this.controller = null;
|
|
20
|
+
this.view = null;
|
|
21
|
+
this.data = null;
|
|
22
|
+
this.dataBindingObserver = directive.dataBinding.createObserver(directive, this);
|
|
23
|
+
this.templateBindingObserver = directive.templateBinding.createObserver(directive, this);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Bind this behavior.
|
|
27
|
+
* @param controller - The view controller that manages the lifecycle of this behavior.
|
|
28
|
+
*/
|
|
29
|
+
bind(controller) {
|
|
30
|
+
this.location = controller.targets[this.directive.nodeId];
|
|
31
|
+
this.controller = controller;
|
|
32
|
+
this.data = this.dataBindingObserver.bind(controller);
|
|
33
|
+
this.template = this.templateBindingObserver.bind(controller);
|
|
34
|
+
controller.onUnbind(this);
|
|
35
|
+
this.refreshView();
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Unbinds this behavior.
|
|
39
|
+
* @param controller - The view controller that manages the lifecycle of this behavior.
|
|
40
|
+
*/
|
|
41
|
+
unbind(controller) {
|
|
42
|
+
const view = this.view;
|
|
43
|
+
if (view !== null && view.isComposed) {
|
|
44
|
+
view.unbind();
|
|
45
|
+
view.needsBindOnly = true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** @internal */
|
|
49
|
+
handleChange(source, observer) {
|
|
50
|
+
if (observer === this.dataBindingObserver) {
|
|
51
|
+
this.data = this.dataBindingObserver.bind(this.controller);
|
|
52
|
+
}
|
|
53
|
+
if (this.directive.templateBindingDependsOnData ||
|
|
54
|
+
observer === this.templateBindingObserver) {
|
|
55
|
+
this.template = this.templateBindingObserver.bind(this.controller);
|
|
56
|
+
}
|
|
57
|
+
this.refreshView();
|
|
58
|
+
}
|
|
59
|
+
refreshView() {
|
|
60
|
+
let view = this.view;
|
|
61
|
+
const template = this.template;
|
|
62
|
+
if (view === null) {
|
|
63
|
+
this.view = view = template.create();
|
|
64
|
+
this.view.context.parent = this.controller.source;
|
|
65
|
+
this.view.context.parentContext = this.controller.context;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// If there is a previous view, but it wasn't created
|
|
69
|
+
// from the same template as the new value, then we
|
|
70
|
+
// need to remove the old view if it's still in the DOM
|
|
71
|
+
// and create a new view from the template.
|
|
72
|
+
if (view.$fastTemplate !== template) {
|
|
73
|
+
if (view.isComposed) {
|
|
74
|
+
view.remove();
|
|
75
|
+
view.unbind();
|
|
76
|
+
}
|
|
77
|
+
this.view = view = template.create();
|
|
78
|
+
this.view.context.parent = this.controller.source;
|
|
79
|
+
this.view.context.parentContext = this.controller.context;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// It's possible that the value is the same as the previous template
|
|
83
|
+
// and that there's actually no need to compose it.
|
|
84
|
+
if (!view.isComposed) {
|
|
85
|
+
view.isComposed = true;
|
|
86
|
+
view.bind(this.data);
|
|
87
|
+
view.insertBefore(this.location);
|
|
88
|
+
view.$fastTemplate = template;
|
|
89
|
+
}
|
|
90
|
+
else if (view.needsBindOnly) {
|
|
91
|
+
view.needsBindOnly = false;
|
|
92
|
+
view.bind(this.data);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* A Directive that enables use of the RenderBehavior.
|
|
98
|
+
* @public
|
|
99
|
+
*/
|
|
100
|
+
export class RenderDirective {
|
|
101
|
+
/**
|
|
102
|
+
* Creates an instance of RenderDirective.
|
|
103
|
+
* @param dataBinding - A binding expression that returns the data to render.
|
|
104
|
+
* @param templateBinding - A binding expression that returns the template to use to render the data.
|
|
105
|
+
*/
|
|
106
|
+
constructor(dataBinding, templateBinding, templateBindingDependsOnData) {
|
|
107
|
+
this.dataBinding = dataBinding;
|
|
108
|
+
this.templateBinding = templateBinding;
|
|
109
|
+
this.templateBindingDependsOnData = templateBindingDependsOnData;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Creates HTML to be used within a template.
|
|
113
|
+
* @param add - Can be used to add behavior factories to a template.
|
|
114
|
+
*/
|
|
115
|
+
createHTML(add) {
|
|
116
|
+
return Markup.comment(add(this));
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Creates a behavior.
|
|
120
|
+
* @param targets - The targets available for behaviors to be attached to.
|
|
121
|
+
*/
|
|
122
|
+
createBehavior() {
|
|
123
|
+
return new RenderBehavior(this);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
HTMLDirective.define(RenderDirective);
|
|
127
|
+
function isElementRenderOptions(object) {
|
|
128
|
+
return !!object.element || !!object.tagName;
|
|
129
|
+
}
|
|
130
|
+
const typeToInstructionLookup = new Map();
|
|
131
|
+
/* eslint @typescript-eslint/naming-convention: "off"*/
|
|
132
|
+
const defaultAttributes = { ":model": x => x };
|
|
133
|
+
const brand = Symbol("RenderInstruction");
|
|
134
|
+
const defaultViewName = "default-view";
|
|
135
|
+
const nullTemplate = html `
|
|
136
|
+
|
|
137
|
+
`;
|
|
138
|
+
function instructionToTemplate(def) {
|
|
139
|
+
if (def === void 0) {
|
|
140
|
+
return nullTemplate;
|
|
141
|
+
}
|
|
142
|
+
return def.template;
|
|
143
|
+
}
|
|
144
|
+
function createElementTemplate(tagName, attributes, content) {
|
|
145
|
+
const markup = [];
|
|
146
|
+
const values = [];
|
|
147
|
+
if (attributes) {
|
|
148
|
+
const attrNames = Object.getOwnPropertyNames(attributes);
|
|
149
|
+
markup.push(`<${tagName}`);
|
|
150
|
+
for (let i = 0, ii = attrNames.length; i < ii; ++i) {
|
|
151
|
+
const name = attrNames[i];
|
|
152
|
+
if (i === 0) {
|
|
153
|
+
markup[0] = `${markup[0]} ${name}="`;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
markup.push(`" ${name}="`);
|
|
157
|
+
}
|
|
158
|
+
values.push(attributes[name]);
|
|
159
|
+
}
|
|
160
|
+
markup.push(`">`);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
markup.push(`<${tagName}>`);
|
|
164
|
+
}
|
|
165
|
+
if (content && isFunction(content.create)) {
|
|
166
|
+
values.push(content);
|
|
167
|
+
markup.push(`</${tagName}>`);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
const lastIndex = markup.length - 1;
|
|
171
|
+
markup[lastIndex] = `${markup[lastIndex]}${content !== null && content !== void 0 ? content : ""}</${tagName}>`;
|
|
172
|
+
}
|
|
173
|
+
return html(markup, ...values);
|
|
174
|
+
}
|
|
175
|
+
function create(options) {
|
|
176
|
+
var _a, _b;
|
|
177
|
+
const name = (_a = options.name) !== null && _a !== void 0 ? _a : defaultViewName;
|
|
178
|
+
let template;
|
|
179
|
+
if (isElementRenderOptions(options)) {
|
|
180
|
+
let tagName = options.tagName;
|
|
181
|
+
if (!tagName) {
|
|
182
|
+
const def = FASTElementDefinition.getByType(options.element);
|
|
183
|
+
if (def) {
|
|
184
|
+
tagName = def.name;
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
throw new Error("Invalid element for model rendering.");
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
template = createElementTemplate(tagName, (_b = options.attributes) !== null && _b !== void 0 ? _b : defaultAttributes, options.content);
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
template = options.template;
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
brand,
|
|
197
|
+
type: options.type,
|
|
198
|
+
name,
|
|
199
|
+
template,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function instanceOf(object) {
|
|
203
|
+
return object && object.brand === brand;
|
|
204
|
+
}
|
|
205
|
+
function register(optionsOrInstruction) {
|
|
206
|
+
let lookup = typeToInstructionLookup.get(optionsOrInstruction.type);
|
|
207
|
+
if (lookup === void 0) {
|
|
208
|
+
typeToInstructionLookup.set(optionsOrInstruction.type, (lookup = Object.create(null)));
|
|
209
|
+
}
|
|
210
|
+
const instruction = instanceOf(optionsOrInstruction)
|
|
211
|
+
? optionsOrInstruction
|
|
212
|
+
: create(optionsOrInstruction);
|
|
213
|
+
return (lookup[instruction.name] = instruction);
|
|
214
|
+
}
|
|
215
|
+
function getByType(type, name) {
|
|
216
|
+
const entries = typeToInstructionLookup.get(type);
|
|
217
|
+
if (entries === void 0) {
|
|
218
|
+
return void 0;
|
|
219
|
+
}
|
|
220
|
+
return entries[name !== null && name !== void 0 ? name : defaultViewName];
|
|
221
|
+
}
|
|
222
|
+
function getForInstance(object, name) {
|
|
223
|
+
if (object) {
|
|
224
|
+
return getByType(object.constructor, name);
|
|
225
|
+
}
|
|
226
|
+
return void 0;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Provides APIs for creating and interacting with render instructions.
|
|
230
|
+
* @public
|
|
231
|
+
*/
|
|
232
|
+
export const RenderInstruction = Object.freeze({
|
|
233
|
+
/**
|
|
234
|
+
* Checks whether the provided object is a RenderInstruction.
|
|
235
|
+
* @param object - The object to check.
|
|
236
|
+
* @returns true if the object is a RenderInstruction; false otherwise
|
|
237
|
+
*/
|
|
238
|
+
instanceOf,
|
|
239
|
+
/**
|
|
240
|
+
* Creates a RenderInstruction for a set of options.
|
|
241
|
+
* @param options - The options to use when creating the RenderInstruction.
|
|
242
|
+
*/
|
|
243
|
+
create,
|
|
244
|
+
/**
|
|
245
|
+
* Creates a template based on a tag name.
|
|
246
|
+
* @param tagName - The tag name to use when creating the template.
|
|
247
|
+
* @param attributes - The attributes to apply to the element.
|
|
248
|
+
* @param content - The content to insert into the element.
|
|
249
|
+
* @returns A template based on the provided specifications.
|
|
250
|
+
*/
|
|
251
|
+
createElementTemplate,
|
|
252
|
+
/**
|
|
253
|
+
* Creates and registers an instruction.
|
|
254
|
+
* @param options The options to use when creating the RenderInstruction.
|
|
255
|
+
* @remarks
|
|
256
|
+
* A previously created RenderInstruction can also be registered.
|
|
257
|
+
*/
|
|
258
|
+
register,
|
|
259
|
+
/**
|
|
260
|
+
* Finds a previously registered RenderInstruction by type and optionally by name.
|
|
261
|
+
* @param type - The type to retrieve the RenderInstruction for.
|
|
262
|
+
* @param name - An optional name used in differentiating between multiple registered instructions.
|
|
263
|
+
* @returns The located RenderInstruction that matches the criteria or undefined if none is found.
|
|
264
|
+
*/
|
|
265
|
+
getByType,
|
|
266
|
+
/**
|
|
267
|
+
* Finds a previously registered RenderInstruction for the instance's type and optionally by name.
|
|
268
|
+
* @param object - The instance to retrieve the RenderInstruction for.
|
|
269
|
+
* @param name - An optional name used in differentiating between multiple registered instructions.
|
|
270
|
+
* @returns The located RenderInstruction that matches the criteria or undefined if none is found.
|
|
271
|
+
*/
|
|
272
|
+
getForInstance,
|
|
273
|
+
});
|
|
274
|
+
export function renderWith(value, name) {
|
|
275
|
+
return function (type) {
|
|
276
|
+
if (isFunction(value)) {
|
|
277
|
+
register({ type, element: value, name });
|
|
278
|
+
}
|
|
279
|
+
else if (isFunction(value.create)) {
|
|
280
|
+
register({ type, template: value, name });
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
register(Object.assign({ type }, value));
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* @internal
|
|
289
|
+
*/
|
|
290
|
+
export class NodeTemplate {
|
|
291
|
+
constructor(node) {
|
|
292
|
+
this.node = node;
|
|
293
|
+
node.$fastTemplate = this;
|
|
294
|
+
}
|
|
295
|
+
get context() {
|
|
296
|
+
// HACK
|
|
297
|
+
return this;
|
|
298
|
+
}
|
|
299
|
+
bind(source) { }
|
|
300
|
+
unbind() { }
|
|
301
|
+
insertBefore(refNode) {
|
|
302
|
+
refNode.parentNode.insertBefore(this.node, refNode);
|
|
303
|
+
}
|
|
304
|
+
remove() {
|
|
305
|
+
this.node.parentNode.removeChild(this.node);
|
|
306
|
+
}
|
|
307
|
+
create() {
|
|
308
|
+
return this;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Creates a RenderDirective for use in advanced rendering scenarios.
|
|
313
|
+
* @param value - The binding expression that returns the data to be rendered. The expression
|
|
314
|
+
* can also return a Node to render directly.
|
|
315
|
+
* @param template - A template to render the data with
|
|
316
|
+
* or a string to indicate which RenderInstruction to use when looking up a RenderInstruction.
|
|
317
|
+
* Expressions can also be provided to dynamically determine either the template or the name.
|
|
318
|
+
* @returns A RenderDirective suitable for use in a template.
|
|
319
|
+
* @remarks
|
|
320
|
+
* If no binding is provided, then a default binding that returns the source is created.
|
|
321
|
+
* If no template is provided, then a binding is created that will use registered
|
|
322
|
+
* RenderInstructions to determine the view.
|
|
323
|
+
* If the template binding returns a string, then it will be used to look up a
|
|
324
|
+
* RenderInstruction to determine the view.
|
|
325
|
+
* @public
|
|
326
|
+
*/
|
|
327
|
+
export function render(value, template) {
|
|
328
|
+
let dataBinding;
|
|
329
|
+
if (value === void 0) {
|
|
330
|
+
dataBinding = oneTime((source) => source);
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
dataBinding = normalizeBinding(value);
|
|
334
|
+
}
|
|
335
|
+
let templateBinding;
|
|
336
|
+
let templateBindingDependsOnData = false;
|
|
337
|
+
if (template === void 0) {
|
|
338
|
+
templateBindingDependsOnData = true;
|
|
339
|
+
templateBinding = oneTime((s, c) => {
|
|
340
|
+
var _a;
|
|
341
|
+
const data = dataBinding.evaluate(s, c);
|
|
342
|
+
if (data instanceof Node) {
|
|
343
|
+
return (_a = data.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(data);
|
|
344
|
+
}
|
|
345
|
+
return instructionToTemplate(getForInstance(data));
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
else if (isFunction(template)) {
|
|
349
|
+
templateBinding = bind((s, c) => {
|
|
350
|
+
var _a;
|
|
351
|
+
let result = template(s, c);
|
|
352
|
+
if (isString(result)) {
|
|
353
|
+
result = instructionToTemplate(getForInstance(dataBinding.evaluate(s, c), result));
|
|
354
|
+
}
|
|
355
|
+
else if (result instanceof Node) {
|
|
356
|
+
result = (_a = result.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(result);
|
|
357
|
+
}
|
|
358
|
+
return result;
|
|
359
|
+
}, true);
|
|
360
|
+
}
|
|
361
|
+
else if (isString(template)) {
|
|
362
|
+
templateBindingDependsOnData = true;
|
|
363
|
+
templateBinding = oneTime((s, c) => {
|
|
364
|
+
var _a;
|
|
365
|
+
const data = dataBinding.evaluate(s, c);
|
|
366
|
+
if (data instanceof Node) {
|
|
367
|
+
return (_a = data.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(data);
|
|
368
|
+
}
|
|
369
|
+
return instructionToTemplate(getForInstance(data, template));
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
else if (template instanceof Binding) {
|
|
373
|
+
const evaluateTemplate = template.evaluate;
|
|
374
|
+
template.evaluate = (s, c) => {
|
|
375
|
+
var _a;
|
|
376
|
+
let result = evaluateTemplate(s, c);
|
|
377
|
+
if (isString(result)) {
|
|
378
|
+
result = instructionToTemplate(getForInstance(dataBinding.evaluate(s, c), result));
|
|
379
|
+
}
|
|
380
|
+
else if (result instanceof Node) {
|
|
381
|
+
result = (_a = result.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(result);
|
|
382
|
+
}
|
|
383
|
+
return result;
|
|
384
|
+
};
|
|
385
|
+
templateBinding = template;
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
templateBinding = oneTime((s, c) => template);
|
|
389
|
+
}
|
|
390
|
+
return new RenderDirective(dataBinding, templateBinding, templateBindingDependsOnData);
|
|
391
|
+
}
|
|
@@ -1,19 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Observable, } from "../observation/observable.js";
|
|
1
|
+
import { Observable } from "../observation/observable.js";
|
|
3
2
|
import { emptyArray } from "../platform.js";
|
|
4
3
|
import { ArrayObserver } from "../observation/arrays.js";
|
|
5
|
-
import { Markup } from "./markup.js";
|
|
4
|
+
import { Markup, nextId } from "./markup.js";
|
|
6
5
|
import { HTMLDirective, } from "./html-directive.js";
|
|
7
6
|
import { HTMLView } from "./view.js";
|
|
7
|
+
import { normalizeBinding } from "./binding.js";
|
|
8
8
|
const defaultRepeatOptions = Object.freeze({
|
|
9
9
|
positioning: false,
|
|
10
10
|
recycle: true,
|
|
11
11
|
});
|
|
12
|
-
function bindWithoutPositioning(view, items, index,
|
|
13
|
-
view.
|
|
12
|
+
function bindWithoutPositioning(view, items, index, controller) {
|
|
13
|
+
view.context.parent = controller.source;
|
|
14
|
+
view.context.parentContext = controller.context;
|
|
15
|
+
view.bind(items[index]);
|
|
14
16
|
}
|
|
15
|
-
function bindWithPositioning(view, items, index,
|
|
16
|
-
view.
|
|
17
|
+
function bindWithPositioning(view, items, index, controller) {
|
|
18
|
+
view.context.parent = controller.source;
|
|
19
|
+
view.context.parentContext = controller.context;
|
|
20
|
+
view.context.length = items.length;
|
|
21
|
+
view.context.index = index;
|
|
22
|
+
view.bind(items[index]);
|
|
17
23
|
}
|
|
18
24
|
/**
|
|
19
25
|
* A behavior that renders a template for each item in an array.
|
|
@@ -23,57 +29,45 @@ export class RepeatBehavior {
|
|
|
23
29
|
/**
|
|
24
30
|
* Creates an instance of RepeatBehavior.
|
|
25
31
|
* @param location - The location in the DOM to render the repeat.
|
|
26
|
-
* @param
|
|
32
|
+
* @param dataBinding - The array to render.
|
|
27
33
|
* @param isItemsBindingVolatile - Indicates whether the items binding has volatile dependencies.
|
|
28
34
|
* @param templateBinding - The template to render for each item.
|
|
29
35
|
* @param isTemplateBindingVolatile - Indicates whether the template binding has volatile dependencies.
|
|
30
36
|
* @param options - Options used to turn on special repeat features.
|
|
31
37
|
*/
|
|
32
|
-
constructor(
|
|
33
|
-
this.
|
|
34
|
-
this.itemsBinding = itemsBinding;
|
|
35
|
-
this.templateBinding = templateBinding;
|
|
36
|
-
this.options = options;
|
|
37
|
-
this.source = null;
|
|
38
|
+
constructor(directive) {
|
|
39
|
+
this.directive = directive;
|
|
38
40
|
this.views = [];
|
|
39
41
|
this.items = null;
|
|
40
42
|
this.itemsObserver = null;
|
|
41
|
-
this.context = void 0;
|
|
42
|
-
this.childContext = void 0;
|
|
43
43
|
this.bindView = bindWithoutPositioning;
|
|
44
|
-
this.itemsBindingObserver =
|
|
45
|
-
this.templateBindingObserver =
|
|
46
|
-
if (options.positioning) {
|
|
44
|
+
this.itemsBindingObserver = directive.dataBinding.createObserver(directive, this);
|
|
45
|
+
this.templateBindingObserver = directive.templateBinding.createObserver(directive, this);
|
|
46
|
+
if (directive.options.positioning) {
|
|
47
47
|
this.bindView = bindWithPositioning;
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
51
|
-
* Bind this behavior
|
|
52
|
-
* @param
|
|
53
|
-
* @param context - The execution context that the binding is operating within.
|
|
51
|
+
* Bind this behavior.
|
|
52
|
+
* @param controller - The view controller that manages the lifecycle of this behavior.
|
|
54
53
|
*/
|
|
55
|
-
bind(
|
|
56
|
-
this.
|
|
57
|
-
this.
|
|
58
|
-
this.
|
|
59
|
-
this.
|
|
60
|
-
this.template = this.templateBindingObserver.observe(source, this.context);
|
|
54
|
+
bind(controller) {
|
|
55
|
+
this.location = controller.targets[this.directive.nodeId];
|
|
56
|
+
this.controller = controller;
|
|
57
|
+
this.items = this.itemsBindingObserver.bind(controller);
|
|
58
|
+
this.template = this.templateBindingObserver.bind(controller);
|
|
61
59
|
this.observeItems(true);
|
|
62
60
|
this.refreshAllViews();
|
|
61
|
+
controller.onUnbind(this);
|
|
63
62
|
}
|
|
64
63
|
/**
|
|
65
|
-
* Unbinds this behavior
|
|
66
|
-
* @param source - The source to unbind from.
|
|
64
|
+
* Unbinds this behavior.
|
|
67
65
|
*/
|
|
68
66
|
unbind() {
|
|
69
|
-
this.source = null;
|
|
70
|
-
this.items = null;
|
|
71
67
|
if (this.itemsObserver !== null) {
|
|
72
68
|
this.itemsObserver.unsubscribe(this);
|
|
73
69
|
}
|
|
74
70
|
this.unbindAllViews();
|
|
75
|
-
this.itemsBindingObserver.dispose();
|
|
76
|
-
this.templateBindingObserver.dispose();
|
|
77
71
|
}
|
|
78
72
|
/**
|
|
79
73
|
* Handles changes in the array, its items, and the repeat template.
|
|
@@ -81,15 +75,18 @@ export class RepeatBehavior {
|
|
|
81
75
|
* @param args - The details about what was changed.
|
|
82
76
|
*/
|
|
83
77
|
handleChange(source, args) {
|
|
84
|
-
if (
|
|
85
|
-
this.items = this.itemsBindingObserver.
|
|
78
|
+
if (args === this.itemsBindingObserver) {
|
|
79
|
+
this.items = this.itemsBindingObserver.bind(this.controller);
|
|
86
80
|
this.observeItems();
|
|
87
81
|
this.refreshAllViews();
|
|
88
82
|
}
|
|
89
|
-
else if (
|
|
90
|
-
this.template = this.templateBindingObserver.
|
|
83
|
+
else if (args === this.templateBindingObserver) {
|
|
84
|
+
this.template = this.templateBindingObserver.bind(this.controller);
|
|
91
85
|
this.refreshAllViews(true);
|
|
92
86
|
}
|
|
87
|
+
else if (!args[0]) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
93
90
|
else if (args[0].reset) {
|
|
94
91
|
this.refreshAllViews();
|
|
95
92
|
}
|
|
@@ -114,39 +111,57 @@ export class RepeatBehavior {
|
|
|
114
111
|
}
|
|
115
112
|
updateViews(splices) {
|
|
116
113
|
const views = this.views;
|
|
117
|
-
const childContext = this.childContext;
|
|
118
|
-
const totalRemoved = [];
|
|
119
114
|
const bindView = this.bindView;
|
|
120
|
-
let removeDelta = 0;
|
|
121
|
-
for (let i = 0, ii = splices.length; i < ii; ++i) {
|
|
122
|
-
const splice = splices[i];
|
|
123
|
-
const removed = splice.removed;
|
|
124
|
-
totalRemoved.push(...views.splice(splice.index + removeDelta, removed.length));
|
|
125
|
-
removeDelta -= splice.addedCount;
|
|
126
|
-
}
|
|
127
115
|
const items = this.items;
|
|
128
116
|
const template = this.template;
|
|
117
|
+
const controller = this.controller;
|
|
118
|
+
const recycle = this.directive.options.recycle;
|
|
119
|
+
const leftoverViews = [];
|
|
120
|
+
let leftoverIndex = 0;
|
|
121
|
+
let availableViews = 0;
|
|
129
122
|
for (let i = 0, ii = splices.length; i < ii; ++i) {
|
|
130
123
|
const splice = splices[i];
|
|
124
|
+
const removed = splice.removed;
|
|
125
|
+
let removeIndex = 0;
|
|
131
126
|
let addIndex = splice.index;
|
|
132
127
|
const end = addIndex + splice.addedCount;
|
|
128
|
+
const removedViews = views.splice(splice.index, removed.length);
|
|
129
|
+
const totalAvailableViews = (availableViews =
|
|
130
|
+
leftoverViews.length + removedViews.length);
|
|
133
131
|
for (; addIndex < end; ++addIndex) {
|
|
134
132
|
const neighbor = views[addIndex];
|
|
135
133
|
const location = neighbor ? neighbor.firstChild : this.location;
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
134
|
+
let view;
|
|
135
|
+
if (recycle && availableViews > 0) {
|
|
136
|
+
if (removeIndex <= totalAvailableViews && removedViews.length > 0) {
|
|
137
|
+
view = removedViews[removeIndex];
|
|
138
|
+
removeIndex++;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
view = leftoverViews[leftoverIndex];
|
|
142
|
+
leftoverIndex++;
|
|
143
|
+
}
|
|
144
|
+
availableViews--;
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
view = template.create();
|
|
148
|
+
}
|
|
139
149
|
views.splice(addIndex, 0, view);
|
|
140
|
-
bindView(view, items, addIndex,
|
|
150
|
+
bindView(view, items, addIndex, controller);
|
|
141
151
|
view.insertBefore(location);
|
|
142
152
|
}
|
|
153
|
+
if (removedViews[removeIndex]) {
|
|
154
|
+
leftoverViews.push(...removedViews.slice(removeIndex));
|
|
155
|
+
}
|
|
143
156
|
}
|
|
144
|
-
for (let i =
|
|
145
|
-
|
|
157
|
+
for (let i = leftoverIndex, ii = leftoverViews.length; i < ii; ++i) {
|
|
158
|
+
leftoverViews[i].dispose();
|
|
146
159
|
}
|
|
147
|
-
if (this.options.positioning) {
|
|
160
|
+
if (this.directive.options.positioning) {
|
|
148
161
|
for (let i = 0, ii = views.length; i < ii; ++i) {
|
|
149
|
-
views[i].context
|
|
162
|
+
const context = views[i].context;
|
|
163
|
+
context.length = i;
|
|
164
|
+
context.index = ii;
|
|
150
165
|
}
|
|
151
166
|
}
|
|
152
167
|
}
|
|
@@ -155,11 +170,11 @@ export class RepeatBehavior {
|
|
|
155
170
|
const template = this.template;
|
|
156
171
|
const location = this.location;
|
|
157
172
|
const bindView = this.bindView;
|
|
158
|
-
const
|
|
173
|
+
const controller = this.controller;
|
|
159
174
|
let itemsLength = items.length;
|
|
160
175
|
let views = this.views;
|
|
161
176
|
let viewsLength = views.length;
|
|
162
|
-
if (itemsLength === 0 || templateChanged) {
|
|
177
|
+
if (itemsLength === 0 || templateChanged || !this.directive.options.recycle) {
|
|
163
178
|
// all views need to be removed
|
|
164
179
|
HTMLView.disposeContiguousBatch(views);
|
|
165
180
|
viewsLength = 0;
|
|
@@ -169,7 +184,7 @@ export class RepeatBehavior {
|
|
|
169
184
|
this.views = views = new Array(itemsLength);
|
|
170
185
|
for (let i = 0; i < itemsLength; ++i) {
|
|
171
186
|
const view = template.create();
|
|
172
|
-
bindView(view, items, i,
|
|
187
|
+
bindView(view, items, i, controller);
|
|
173
188
|
views[i] = view;
|
|
174
189
|
view.insertBefore(location);
|
|
175
190
|
}
|
|
@@ -180,11 +195,11 @@ export class RepeatBehavior {
|
|
|
180
195
|
for (; i < itemsLength; ++i) {
|
|
181
196
|
if (i < viewsLength) {
|
|
182
197
|
const view = views[i];
|
|
183
|
-
bindView(view, items, i,
|
|
198
|
+
bindView(view, items, i, controller);
|
|
184
199
|
}
|
|
185
200
|
else {
|
|
186
201
|
const view = template.create();
|
|
187
|
-
bindView(view, items, i,
|
|
202
|
+
bindView(view, items, i, controller);
|
|
188
203
|
views.push(view);
|
|
189
204
|
view.insertBefore(location);
|
|
190
205
|
}
|
|
@@ -209,17 +224,19 @@ export class RepeatBehavior {
|
|
|
209
224
|
export class RepeatDirective {
|
|
210
225
|
/**
|
|
211
226
|
* Creates an instance of RepeatDirective.
|
|
212
|
-
* @param
|
|
227
|
+
* @param dataBinding - The binding that provides the array to render.
|
|
213
228
|
* @param templateBinding - The template binding used to obtain a template to render for each item in the array.
|
|
214
229
|
* @param options - Options used to turn on special repeat features.
|
|
215
230
|
*/
|
|
216
|
-
constructor(
|
|
217
|
-
this.
|
|
231
|
+
constructor(dataBinding, templateBinding, options) {
|
|
232
|
+
this.dataBinding = dataBinding;
|
|
218
233
|
this.templateBinding = templateBinding;
|
|
219
234
|
this.options = options;
|
|
235
|
+
/**
|
|
236
|
+
* The unique id of the factory.
|
|
237
|
+
*/
|
|
238
|
+
this.id = nextId();
|
|
220
239
|
ArrayObserver.enable();
|
|
221
|
-
this.isItemsBindingVolatile = Observable.isVolatileBinding(itemsBinding);
|
|
222
|
-
this.isTemplateBindingVolatile = Observable.isVolatileBinding(templateBinding);
|
|
223
240
|
}
|
|
224
241
|
/**
|
|
225
242
|
* Creates a placeholder string based on the directive's index within the template.
|
|
@@ -232,14 +249,21 @@ export class RepeatDirective {
|
|
|
232
249
|
* Creates a behavior for the provided target node.
|
|
233
250
|
* @param target - The node instance to create the behavior for.
|
|
234
251
|
*/
|
|
235
|
-
createBehavior(
|
|
236
|
-
return new RepeatBehavior(
|
|
252
|
+
createBehavior() {
|
|
253
|
+
return new RepeatBehavior(this);
|
|
237
254
|
}
|
|
238
255
|
}
|
|
239
256
|
HTMLDirective.define(RepeatDirective);
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
257
|
+
/**
|
|
258
|
+
* A directive that enables list rendering.
|
|
259
|
+
* @param items - The array to render.
|
|
260
|
+
* @param template - The template or a template binding used obtain a template
|
|
261
|
+
* to render for each item in the array.
|
|
262
|
+
* @param options - Options used to turn on special repeat features.
|
|
263
|
+
* @public
|
|
264
|
+
*/
|
|
265
|
+
export function repeat(items, template, options = defaultRepeatOptions) {
|
|
266
|
+
const dataBinding = normalizeBinding(items);
|
|
267
|
+
const templateBinding = normalizeBinding(template);
|
|
268
|
+
return new RepeatDirective(dataBinding, templateBinding, Object.assign(Object.assign({}, defaultRepeatOptions), options));
|
|
245
269
|
}
|