@microsoft/fast-element 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE_FASTELEMENT.md +63 -0
- package/ARCHITECTURE_HTML_TAGGED_TEMPLATE_LITERAL.md +34 -0
- package/ARCHITECTURE_INTRO.md +10 -0
- package/ARCHITECTURE_OVERVIEW.md +52 -0
- package/ARCHITECTURE_UPDATES.md +11 -0
- package/CHANGELOG.json +44 -0
- package/CHANGELOG.md +18 -1
- package/dist/dts/components/attributes.d.ts +1 -1
- package/dist/dts/index.d.ts +1 -1
- package/dist/dts/observation/arrays.d.ts +40 -0
- package/dist/dts/templating/repeat.d.ts +4 -3
- package/dist/esm/components/attributes.js +3 -5
- package/dist/esm/debug.js +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/observation/arrays.js +91 -8
- package/dist/esm/templating/repeat.js +25 -2
- package/dist/fast-element.api.json +286 -5
- package/dist/fast-element.debug.js +121 -17
- package/dist/fast-element.debug.min.js +2 -2
- package/dist/fast-element.js +120 -16
- package/dist/fast-element.min.js +2 -2
- package/dist/fast-element.untrimmed.d.ts +47 -3
- package/docs/api-report.api.md +19 -1
- package/package.json +1 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# FASTElement
|
|
2
|
+
|
|
3
|
+
The `FASTElement` is our extension of the `HTMLElement`. As such it leverages the lifecycles but also requires additional setup before before the class `constructor` or the `connectedCallback` method is executed which are explained in the [overview](./ARCHITECTURE_OVERVIEW.md). This document explains specifically what `FASTElement` leverages inside the `HTMLElement`s lifecycle hooks.
|
|
4
|
+
|
|
5
|
+
For an explanation into `HTMLElement` lifecycle hooks refer to the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks).
|
|
6
|
+
|
|
7
|
+
## A Custom Element is Detected by the Browser
|
|
8
|
+
|
|
9
|
+
Because the `FASTElement` is an extension of the `HTMLElement`, it makes use of the same lifecycle hooks and extends them with `$fastController`. It also initializes using another class `ElementController`, the methods this are called during the custom elements native `constructor`, `connectedCallback`, `disconnectedCallback`, and `attributeChangedCallback` lifecycle hooks.
|
|
10
|
+
|
|
11
|
+
### 🔄 **Lifecycle**: Initialization
|
|
12
|
+
|
|
13
|
+
```mermaid
|
|
14
|
+
flowchart TD
|
|
15
|
+
A[A <code>FASTElement</code> web component is added to the <code>DOM</code>] --> B
|
|
16
|
+
B[<code>FASTElement</code> initializes the <code>ElementController.forCustomElement</code> passing the Custom Element instance] --> C
|
|
17
|
+
B --> F[Observables applied to the <code>FASTElement</code> are updated on the FAST global without values]
|
|
18
|
+
C{Is an <code>ElementController</code> available?}
|
|
19
|
+
C --> |yes|E
|
|
20
|
+
C --> |no|D
|
|
21
|
+
D[Initialize a new <code>ElementController</code> referred to as setting an element controller strategy]
|
|
22
|
+
D --> F
|
|
23
|
+
E[<code>ElementController</code> captures the Custom Element instance and the definition and attaches them to the <code>$fastController</code> which is then attached to the instance]
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 🔄 **Lifecycle**: Component is connected
|
|
27
|
+
|
|
28
|
+
```mermaid
|
|
29
|
+
flowchart TD
|
|
30
|
+
A[browser <code>HTMLElement</code>'s <code>connectedCallback</code> is called] --> B
|
|
31
|
+
B[<code>this.$fastController.connect</code> in FASTElement is called] --> C
|
|
32
|
+
B --> D
|
|
33
|
+
B --> E
|
|
34
|
+
B --> F
|
|
35
|
+
C[bind observables by capturing the Custom Elements properties and setting the values from the bound observables on the Custom Element]
|
|
36
|
+
D[connect behaviors by call the <code>connectedCallback</code> on all behaviors]
|
|
37
|
+
E[render template, execute an <code>ElementViewTemplate</code>'s render method]
|
|
38
|
+
F[add styles either as an appended <code>StyleElement</code> node or an <code>adoptedStylesheet</code> which may include and attach behaviors]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
#### Render Template
|
|
42
|
+
|
|
43
|
+
The rendering of the template on the `ElementController` is by the `renderTemplate` method which is called during the `ElementController.connect` method which is triggered by `HTMLElement`s `connectedCallback` lifecycle.
|
|
44
|
+
|
|
45
|
+
The `renderTemplate` identifies the Custom Element, and the shadow root associated with the Custom Element. This then places a rendering of the template (an `ElementView`) onto the internal `view` of the controller. When creating the `ElementView`/`HTMLView` using the `ViewTemplate.render`, the `Compile.compile()` method identifies a `DocumentFragment` either by using an existing `<template>` tag, or creating one to wrap the contents of the shadow root. A new `CompilationContext` is created and the `compileAttributes` function is called, this results in the replacement of the placeholder attributes initally set-up during the pre-render step with their values if a value has been assigned. The factories with the associated nodes identified are then passed to the context. The view then binds all behaviors to the source element. The `CompilationContext.createView` is executed with the `DocumentFragment` as the root, and returns an `HTMLView`. This `HTMLView` includes an `appendTo` method to attach the fragment to the host element, which it then does. It should be noted that the compiled HTML is a `string`, which when set on the `DocumentFragment` as `innerHTML`, this allows the browser to dictate the creation of HTML nodes.
|
|
46
|
+
|
|
47
|
+
### 🔄 **Lifecycle**: Component is disconnected
|
|
48
|
+
|
|
49
|
+
When a component is disconnected, a cleanup step is created to remove associated behaviors.
|
|
50
|
+
|
|
51
|
+
### 🔄 **Lifecycle**: Attribute has been changed
|
|
52
|
+
|
|
53
|
+
Attributes have an `AttributeDefinition` which allows for converters, attachment of the `<attributName>Changed` aspect of the `@attr` decorator among other capabilities.
|
|
54
|
+
|
|
55
|
+
```mermaid
|
|
56
|
+
flowchart TD
|
|
57
|
+
A[The browser <code>HTMLElement</code>'s <code>attributeChangedCallback</code> is called] --> B
|
|
58
|
+
B[<code>this.$fastController.onAttributeChangedCallback</code> in <code>FASTElement</code> is called] --> C
|
|
59
|
+
C[calls the attribute definitions <code>onAttributeChangedCallback</code> method with the updated value] --> D
|
|
60
|
+
D[An <code>Updates.enqueue</code> is called which places the update in the task queue which is then executed, these are performed async unless otherwise specified]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
These changes are observed and similar to the way `Observables` work, they utilize an `Accessor` pattern which has a `getValue` and `setValue` in which DOM updates are applied.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# `html` tagged template literal
|
|
2
|
+
|
|
3
|
+
The `html` export from `@microsoft/fast-element` is used to create the template logic for the custom element.
|
|
4
|
+
|
|
5
|
+
## Pre-processing the `html` tagged template literal contents
|
|
6
|
+
|
|
7
|
+
Before the template can be used it goes through a step to convert it into a `ViewTemplate` which it does via the `ViewTemplate.create()` method. This is then used during the `compose` step, before `FASTElement` is instantiated.
|
|
8
|
+
|
|
9
|
+
During the `Compiler.compile()` method(triggered by `ViewTemplate.create()` method), the following happens for each string:
|
|
10
|
+
- Factories with unique IDs are created for each tag template literal argument (or `TemplateValue`) which matches with the corresponding string
|
|
11
|
+
- A binding is created from the `TemplateValue`
|
|
12
|
+
|
|
13
|
+
A resulting string using a `createHTML()` function is produced using the `HTMLDirective`s executed for each factory. The behavior is augmented by the previous string from the `html` tag template which determines the aspect if one exists, these aspects are the `@`, `:`, or other binding aspect attached to attributes.
|
|
14
|
+
|
|
15
|
+
The `createHTML()` function utilizes a `Markup` attribute which is assigned to a factory's unique ID. The strings are concatenated and passed to a new `ViewTemplate` with all the factories (empty until one is assigned) that act as a dictionary with the unique IDs as the key to look up each factory once it has been created. The string this creates is injected into a `<template>` as `innerHTML`, which allows the browser to create the nodes and placeholder factory IDs, with the only `DOM` node that is explicitly created being the wrapping `<template>` element.
|
|
16
|
+
|
|
17
|
+
## Directives
|
|
18
|
+
|
|
19
|
+
The `HTMLBindingDirective` applies bindings to items identified as various `TemplateValue`s within the `html` tagged template. The `createHTML` step uses the factory associated with the binding to create strings in the markup using the factory's ID.
|
|
20
|
+
|
|
21
|
+
```mermaid
|
|
22
|
+
flowchart TD
|
|
23
|
+
A[A <code>new HTMLBindingDirective</code> is created with a data binding which has a policy, options, <code>createObserver</code>, and an evaluate method assigned to the passed arrow function such as <pre>x => x.foo</pre>]
|
|
24
|
+
B[<code>oneTime</code> binding passes the corresponding tag template argument, an arrow function]
|
|
25
|
+
C[<code>oneWay</code> binding passes a copy of the corresponding tag template argument, an arrow function]
|
|
26
|
+
D[An already specified binding such as a <code>repeat</code> or <code>when</code> directive is passed]
|
|
27
|
+
A --> B
|
|
28
|
+
A --> C
|
|
29
|
+
A --> D
|
|
30
|
+
E[When a <code>createObserver</code> is called, an <code>Observable.binding</code> is created passing the arrow function to be evaluated and the subscriber to be notified]
|
|
31
|
+
C --> E
|
|
32
|
+
F[When a <code>createObserver</code> is called, the instance of the one time binding is returned which includes a bind method returning the arrow function executed against the controller source and context]
|
|
33
|
+
B --> F
|
|
34
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Introduction
|
|
2
|
+
|
|
3
|
+
This document (and the linked documents) explains how the exports and side effects of `@microsoft/fast-element` are used to create custom elements.
|
|
4
|
+
|
|
5
|
+
## Glossary
|
|
6
|
+
|
|
7
|
+
- [Overview](./ARCHITECTURE_OVERVIEW.md): How the `@microsoft/fast-element` should be used by a developer and what code is executed during the first render.
|
|
8
|
+
- [`FASTElement`](./ARCHITECTURE_FASTELEMENT.md): How the `FASTElement` is architected.
|
|
9
|
+
- [`html` tagged template literal](./ARCHITECTURE_HTML_TAGGED_TEMPLATE_LITERAL.md): How the `html` tagged template literal takes and converts the contents into a `VIEWTemplate`.
|
|
10
|
+
- [`Updates` queue](./ARCHITECTURE_UPDATES.md): How updates to attributes and observables are processed.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# General FAST usage
|
|
2
|
+
|
|
3
|
+
`FASTElement` is an extension of `HTMLElement` which makes use of Custom Element APIs native to the browser. It also supplies the following methods:
|
|
4
|
+
|
|
5
|
+
- The `compose` method combines the Custom Element name, template, style, and other options to create the definition for the Custom Element.
|
|
6
|
+
- The `define` method makes use of the native Custom Element [`define()`](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define) to register the Custom Element with a Custom Element Registry.
|
|
7
|
+
- The `from` method allows the use of Customized Built-in elements, which extend from native elements such as `HTMLButtonElement`.
|
|
8
|
+
|
|
9
|
+
### Creating a Custom Element from FASTElement
|
|
10
|
+
|
|
11
|
+
A basic developer flow for defining a custom element looks like this:
|
|
12
|
+
|
|
13
|
+
```mermaid
|
|
14
|
+
flowchart TD
|
|
15
|
+
A[Create a <code>FASTElement</code> web component by extending the <code>FASTElement</code> class] --> F
|
|
16
|
+
F[Compose with <code>FASTElement.compose</code> to include template and styles] --> G[Define the component in the browser with <code>FASTElement.define</code>]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Let's take a look at the compose step to see what the FAST architecture is doing at this stage.
|
|
20
|
+
|
|
21
|
+
### Composing a Custom Element
|
|
22
|
+
|
|
23
|
+
The `FASTElement.compose()` function creates a new `FASTElementDefinition`, which includes all metadata needed for the element (such as templates, attributes, and styles). The element definition is registered with the global `FAST` for re-use, and the `FASTElementDefinition` is returned. The resulting object can be retrieved via `ElementController.definition`.
|
|
24
|
+
|
|
25
|
+
## A Custom Element in JavaScript is sent to the Browser
|
|
26
|
+
|
|
27
|
+
Let's step back from defining the Custom Element and consider what is happening when we import from the `@microsoft/fast-element` package.
|
|
28
|
+
|
|
29
|
+
First, a global `FAST` property will be created if one does not already exist, typically in browser on the `window`.
|
|
30
|
+
|
|
31
|
+
Additionally, when Custom Elements are included in a script a few things might happen even before a Custom Element gets detected by the browser. First, there are initial side effects caused by the use of decorators. These include the `attr` and `observable` decorators made available by the `@microsoft/fast-element` package.
|
|
32
|
+
|
|
33
|
+
Here is a basic flow of what code is executed and when during initial load of a script that contains a FAST defined Custom Element:
|
|
34
|
+
|
|
35
|
+
```mermaid
|
|
36
|
+
flowchart TD
|
|
37
|
+
A@{ shape: circle, label: "The browser loads the JavaScript file containing FAST and FAST Custom Element definitions" }
|
|
38
|
+
B@{ shape: rect, label: "The FAST global is added to the window" }
|
|
39
|
+
A --> B
|
|
40
|
+
C@{ shape: rect, label: "A Custom Element executes the <code>compose</code> step"}
|
|
41
|
+
B --> C
|
|
42
|
+
D@{ shape: procs, label: "<ul style="text-align: left"><li>Any defined observable decorators are added to the FAST global</li><li>An attribute decorator locates the associated Custom Elements constructor which it uses to push itself to the Custom Elements attribute collection</li></ul>"}
|
|
43
|
+
C --> D
|
|
44
|
+
F@{ shape: rect, label: "An HTML tagged template literal is executed for the FAST Custom Element and applied to the definition" }
|
|
45
|
+
D --> F
|
|
46
|
+
G@{ shape: rect, label: "The <code>define</code> step is hit and the <code>customElement</code> is registered with the Custom Element Registry" }
|
|
47
|
+
F --> G
|
|
48
|
+
H@{ shape: rect, label: "A Custom Element is detected on the page and the browser initializes it" }
|
|
49
|
+
I@{ shape: dbl-circ, label: "The lifecycle steps for <code>FASTElement</code> are executed" }
|
|
50
|
+
G --> H
|
|
51
|
+
H --> I
|
|
52
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# `Updates`
|
|
2
|
+
|
|
3
|
+
The `Updates` API is part of the `FAST` global. The updates that are queued include updates to attributes, observables, and observed arrays.
|
|
4
|
+
|
|
5
|
+
## Attributes
|
|
6
|
+
|
|
7
|
+
An attribute change may be queued when the value of the attribute is updated. This change is triggered by `HTMLElement` lifecycle hook `attributeChangedCallback`. The `Updates` queue is used to try to reflect the attribute with the updated value onto the element using `DOM` APIs.
|
|
8
|
+
|
|
9
|
+
## Observables
|
|
10
|
+
|
|
11
|
+
The `Reflect` API is used to override `defineProperty` in order to observe a property on an Custom Element, this overrides the getter and setter, which allows subscribers to be notified of changes. Any changes which `set` the value are passed to the `Updates` queue.
|
package/CHANGELOG.json
CHANGED
|
@@ -1,6 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@microsoft/fast-element",
|
|
3
3
|
"entries": [
|
|
4
|
+
{
|
|
5
|
+
"date": "Thu, 13 Feb 2025 17:33:42 GMT",
|
|
6
|
+
"version": "2.1.0",
|
|
7
|
+
"tag": "@microsoft/fast-element_v2.1.0",
|
|
8
|
+
"comments": {
|
|
9
|
+
"minor": [
|
|
10
|
+
{
|
|
11
|
+
"author": "7559015+janechu@users.noreply.github.com",
|
|
12
|
+
"package": "@microsoft/fast-element",
|
|
13
|
+
"commit": "c4836e507f848b4838eb2c106c17dd18c28240b1",
|
|
14
|
+
"comment": "Update the ArrayObserver to better account for sort and reverse"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"none": [
|
|
18
|
+
{
|
|
19
|
+
"author": "7559015+janechu@users.noreply.github.com",
|
|
20
|
+
"package": "@microsoft/fast-element",
|
|
21
|
+
"commit": "c6b9e8a436b7b52e5df02182b663167bb34cad8a",
|
|
22
|
+
"comment": "Add documentation explaining some architectural concepts"
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"date": "Wed, 11 Dec 2024 19:53:31 GMT",
|
|
29
|
+
"version": "2.0.1",
|
|
30
|
+
"tag": "@microsoft/fast-element_v2.0.1",
|
|
31
|
+
"comments": {
|
|
32
|
+
"patch": [
|
|
33
|
+
{
|
|
34
|
+
"author": "7559015+janechu@users.noreply.github.com",
|
|
35
|
+
"package": "@microsoft/fast-element",
|
|
36
|
+
"commit": "33111eeff26ab4ae2bbe1c90145170ed175d2217",
|
|
37
|
+
"comment": "Patch bumping to apply latest tag to the package"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"author": "abaris@null.net",
|
|
41
|
+
"package": "@microsoft/fast-element",
|
|
42
|
+
"commit": "51a79eb3294897fb1e71b5dcdcce0bea62521e80",
|
|
43
|
+
"comment": "fast-element: Simplify conditional checks in element-controller"
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
},
|
|
4
48
|
{
|
|
5
49
|
"date": "Mon, 19 Aug 2024 22:04:19 GMT",
|
|
6
50
|
"version": "2.0.0",
|
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,26 @@
|
|
|
1
1
|
# Change Log - @microsoft/fast-element
|
|
2
2
|
|
|
3
|
-
This log was last generated on
|
|
3
|
+
<!-- This log was last generated on Thu, 13 Feb 2025 17:33:42 GMT and should not be manually modified. -->
|
|
4
4
|
|
|
5
5
|
<!-- Start content -->
|
|
6
6
|
|
|
7
|
+
## 2.1.0
|
|
8
|
+
|
|
9
|
+
Thu, 13 Feb 2025 17:33:42 GMT
|
|
10
|
+
|
|
11
|
+
### Minor changes
|
|
12
|
+
|
|
13
|
+
- Update the ArrayObserver to better account for sort and reverse (7559015+janechu@users.noreply.github.com)
|
|
14
|
+
|
|
15
|
+
## 2.0.1
|
|
16
|
+
|
|
17
|
+
Wed, 11 Dec 2024 19:53:31 GMT
|
|
18
|
+
|
|
19
|
+
### Patches
|
|
20
|
+
|
|
21
|
+
- Patch bumping to apply latest tag to the package (7559015+janechu@users.noreply.github.com)
|
|
22
|
+
- fast-element: Simplify conditional checks in element-controller (abaris@null.net)
|
|
23
|
+
|
|
7
24
|
## 2.0.0
|
|
8
25
|
|
|
9
26
|
Mon, 19 Aug 2024 22:04:19 GMT
|
|
@@ -121,7 +121,7 @@ export declare class AttributeDefinition implements Accessor {
|
|
|
121
121
|
/**
|
|
122
122
|
* Sets the value of the attribute/property on the source element.
|
|
123
123
|
* @param source - The source element to access.
|
|
124
|
-
* @param
|
|
124
|
+
* @param newValue - The value to set the attribute/property to.
|
|
125
125
|
*/
|
|
126
126
|
setValue(source: HTMLElement, newValue: any): void;
|
|
127
127
|
/**
|
package/dist/dts/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { FAST, emptyArray } from "./platform.js";
|
|
|
3
3
|
export { DOMAspect, DOMSink, DOMPolicy, DOM } from "./dom.js";
|
|
4
4
|
export { Accessor, Expression, ExecutionContext, ExpressionController, ExpressionObserver, ExpressionNotifier, Observable, observable, ObservationRecord, SourceLifetime, volatile, } from "./observation/observable.js";
|
|
5
5
|
export { Notifier, PropertyChangeNotifier, Subscriber, SubscriberSet, } from "./observation/notifier.js";
|
|
6
|
-
export { ArrayObserver, LengthObserver, lengthOf, Splice, SpliceStrategy, SpliceStrategySupport, } from "./observation/arrays.js";
|
|
6
|
+
export { ArrayObserver, LengthObserver, lengthOf, Splice, SpliceStrategy, SpliceStrategySupport, Sort, SortObserver, sortedCount, } from "./observation/arrays.js";
|
|
7
7
|
export { UpdateQueue, Updates } from "./observation/update-queue.js";
|
|
8
8
|
export { Binding, BindingDirective } from "./binding/binding.js";
|
|
9
9
|
export { listener, oneWay } from "./binding/one-way.js";
|
|
@@ -33,6 +33,18 @@ export declare class Splice {
|
|
|
33
33
|
*/
|
|
34
34
|
adjustTo(array: any[]): this;
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* A sort array indicates new index positions of array items.
|
|
38
|
+
* @public
|
|
39
|
+
*/
|
|
40
|
+
export declare class Sort {
|
|
41
|
+
sorted?: number[] | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Creates a sort update.
|
|
44
|
+
* @param sorted - The updated index of sorted items.
|
|
45
|
+
*/
|
|
46
|
+
constructor(sorted?: number[] | undefined);
|
|
47
|
+
}
|
|
36
48
|
/**
|
|
37
49
|
* Indicates what level of feature support the splice
|
|
38
50
|
* strategy provides.
|
|
@@ -156,6 +168,17 @@ export interface LengthObserver extends Subscriber {
|
|
|
156
168
|
*/
|
|
157
169
|
length: number;
|
|
158
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Observes array sort.
|
|
173
|
+
* @public
|
|
174
|
+
*/
|
|
175
|
+
export interface SortObserver extends Subscriber {
|
|
176
|
+
/**
|
|
177
|
+
* The sorted times on the observed array, this should be incremented every time
|
|
178
|
+
* an item in the array changes location.
|
|
179
|
+
*/
|
|
180
|
+
sorted: number;
|
|
181
|
+
}
|
|
159
182
|
/**
|
|
160
183
|
* An observer for arrays.
|
|
161
184
|
* @public
|
|
@@ -169,11 +192,20 @@ export interface ArrayObserver extends SubscriberSet {
|
|
|
169
192
|
* The length observer for the array.
|
|
170
193
|
*/
|
|
171
194
|
readonly lengthObserver: LengthObserver;
|
|
195
|
+
/**
|
|
196
|
+
* The sort observer for the array.
|
|
197
|
+
*/
|
|
198
|
+
readonly sortObserver: SortObserver;
|
|
172
199
|
/**
|
|
173
200
|
* Adds a splice to the list of changes.
|
|
174
201
|
* @param splice - The splice to add.
|
|
175
202
|
*/
|
|
176
203
|
addSplice(splice: Splice): void;
|
|
204
|
+
/**
|
|
205
|
+
* Adds a sort to the list of changes.
|
|
206
|
+
* @param sort - The sort to add.
|
|
207
|
+
*/
|
|
208
|
+
addSort(sort: Sort): void;
|
|
177
209
|
/**
|
|
178
210
|
* Indicates that a reset change has occurred.
|
|
179
211
|
* @param oldCollection - The collection as it was before the reset.
|
|
@@ -189,6 +221,7 @@ export interface ArrayObserver extends SubscriberSet {
|
|
|
189
221
|
* @public
|
|
190
222
|
*/
|
|
191
223
|
export declare const ArrayObserver: Readonly<{
|
|
224
|
+
readonly sorted: 0;
|
|
192
225
|
/**
|
|
193
226
|
* Enables the array observation mechanism.
|
|
194
227
|
* @remarks
|
|
@@ -205,3 +238,10 @@ export declare const ArrayObserver: Readonly<{
|
|
|
205
238
|
* @public
|
|
206
239
|
*/
|
|
207
240
|
export declare function lengthOf<T>(array: readonly T[]): number;
|
|
241
|
+
/**
|
|
242
|
+
* Enables observing the sorted property of an array.
|
|
243
|
+
* @param array - The array to observe the sorted property of.
|
|
244
|
+
* @returns The sorted property.
|
|
245
|
+
* @public
|
|
246
|
+
*/
|
|
247
|
+
export declare function sortedCount<T>(array: readonly T[]): number;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Splice } from "../observation/arrays.js";
|
|
1
|
+
import { Sort, Splice } from "../observation/arrays.js";
|
|
2
2
|
import type { Subscriber } from "../observation/notifier.js";
|
|
3
3
|
import { Expression, ExpressionObserver } from "../observation/observable.js";
|
|
4
4
|
import type { Binding, BindingDirective } from "../binding/binding.js";
|
|
@@ -81,9 +81,10 @@ export declare class RepeatBehavior<TSource = any> implements ViewBehavior, Subs
|
|
|
81
81
|
* @param source - The source of the change.
|
|
82
82
|
* @param args - The details about what was changed.
|
|
83
83
|
*/
|
|
84
|
-
handleChange(source: any, args: Splice[] | ExpressionObserver): void;
|
|
84
|
+
handleChange(source: any, args: Splice[] | Sort[] | ExpressionObserver): void;
|
|
85
85
|
private observeItems;
|
|
86
|
-
private
|
|
86
|
+
private updateSortedViews;
|
|
87
|
+
private updateSplicedViews;
|
|
87
88
|
private refreshAllViews;
|
|
88
89
|
private unbindAllViews;
|
|
89
90
|
private hydrateViews;
|
|
@@ -26,13 +26,11 @@ export const booleanConverter = {
|
|
|
26
26
|
return value ? "true" : "false";
|
|
27
27
|
},
|
|
28
28
|
fromView(value) {
|
|
29
|
-
return value === null ||
|
|
29
|
+
return !(value === null ||
|
|
30
30
|
value === void 0 ||
|
|
31
31
|
value === "false" ||
|
|
32
32
|
value === false ||
|
|
33
|
-
value === 0
|
|
34
|
-
? false
|
|
35
|
-
: true;
|
|
33
|
+
value === 0);
|
|
36
34
|
},
|
|
37
35
|
};
|
|
38
36
|
/**
|
|
@@ -104,7 +102,7 @@ export class AttributeDefinition {
|
|
|
104
102
|
/**
|
|
105
103
|
* Sets the value of the attribute/property on the source element.
|
|
106
104
|
* @param source - The source element to access.
|
|
107
|
-
* @param
|
|
105
|
+
* @param newValue - The value to set the attribute/property to.
|
|
108
106
|
*/
|
|
109
107
|
setValue(source, newValue) {
|
|
110
108
|
const oldValue = source[this.fieldName];
|
package/dist/esm/debug.js
CHANGED
|
@@ -8,7 +8,7 @@ if (globalThis.FAST === void 0) {
|
|
|
8
8
|
}
|
|
9
9
|
const FAST = globalThis.FAST;
|
|
10
10
|
const debugMessages = {
|
|
11
|
-
[1101 /* needsArrayObservation */]: "Must call
|
|
11
|
+
[1101 /* needsArrayObservation */]: "Must call ArrayObserver.enable() before observing arrays.",
|
|
12
12
|
[1201 /* onlySetDOMPolicyOnce */]: "The DOM Policy can only be set once.",
|
|
13
13
|
[1202 /* bindingInnerHTMLRequiresTrustedTypes */]: "To bind innerHTML, you must use a TrustedTypesPolicy.",
|
|
14
14
|
[1203 /* twoWayBindingRequiresObservables */]: "View=>Model update skipped. To use twoWay binding, the target property must be observable.",
|
package/dist/esm/index.js
CHANGED
|
@@ -4,7 +4,7 @@ export { DOMAspect, DOM } from "./dom.js";
|
|
|
4
4
|
// Observation
|
|
5
5
|
export { ExecutionContext, Observable, observable, SourceLifetime, volatile, } from "./observation/observable.js";
|
|
6
6
|
export { PropertyChangeNotifier, SubscriberSet, } from "./observation/notifier.js";
|
|
7
|
-
export { ArrayObserver, lengthOf, Splice, SpliceStrategy, SpliceStrategySupport, } from "./observation/arrays.js";
|
|
7
|
+
export { ArrayObserver, lengthOf, Splice, SpliceStrategy, SpliceStrategySupport, Sort, sortedCount, } from "./observation/arrays.js";
|
|
8
8
|
export { Updates } from "./observation/update-queue.js";
|
|
9
9
|
// Binding
|
|
10
10
|
export { Binding } from "./binding/binding.js";
|
|
@@ -44,6 +44,19 @@ export class Splice {
|
|
|
44
44
|
return this;
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* A sort array indicates new index positions of array items.
|
|
49
|
+
* @public
|
|
50
|
+
*/
|
|
51
|
+
export class Sort {
|
|
52
|
+
/**
|
|
53
|
+
* Creates a sort update.
|
|
54
|
+
* @param sorted - The updated index of sorted items.
|
|
55
|
+
*/
|
|
56
|
+
constructor(sorted) {
|
|
57
|
+
this.sorted = sorted;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
47
60
|
/**
|
|
48
61
|
* Indicates what level of feature support the splice
|
|
49
62
|
* strategy provides.
|
|
@@ -362,7 +375,7 @@ function project(array, changes) {
|
|
|
362
375
|
* splices needed to represent the change from the old array to the new array.
|
|
363
376
|
* @public
|
|
364
377
|
*/
|
|
365
|
-
let
|
|
378
|
+
let defaultMutationStrategy = Object.freeze({
|
|
366
379
|
support: SpliceStrategySupport.optimized,
|
|
367
380
|
normalize(previous, current, changes) {
|
|
368
381
|
if (previous === void 0) {
|
|
@@ -388,7 +401,12 @@ let defaultSpliceStrategy = Object.freeze({
|
|
|
388
401
|
},
|
|
389
402
|
reverse(array, observer, reverse, args) {
|
|
390
403
|
const result = reverse.apply(array, args);
|
|
391
|
-
|
|
404
|
+
array.sorted++;
|
|
405
|
+
const sortedItems = [];
|
|
406
|
+
for (let i = array.length - 1; i >= 0; i--) {
|
|
407
|
+
sortedItems.push(i);
|
|
408
|
+
}
|
|
409
|
+
observer.addSort(new Sort(sortedItems));
|
|
392
410
|
return result;
|
|
393
411
|
},
|
|
394
412
|
shift(array, observer, shift, args) {
|
|
@@ -400,8 +418,20 @@ let defaultSpliceStrategy = Object.freeze({
|
|
|
400
418
|
return result;
|
|
401
419
|
},
|
|
402
420
|
sort(array, observer, sort, args) {
|
|
421
|
+
const map = new Map();
|
|
422
|
+
for (let i = 0, ii = array.length; i < ii; ++i) {
|
|
423
|
+
const mapValue = map.get(array[i]) || [];
|
|
424
|
+
map.set(array[i], [...mapValue, i]);
|
|
425
|
+
}
|
|
403
426
|
const result = sort.apply(array, args);
|
|
404
|
-
|
|
427
|
+
array.sorted++;
|
|
428
|
+
const sortedItems = [];
|
|
429
|
+
for (let i = 0, ii = array.length; i < ii; ++i) {
|
|
430
|
+
const indexs = map.get(array[i]);
|
|
431
|
+
sortedItems.push(indexs[0]);
|
|
432
|
+
map.set(array[i], indexs.splice(1));
|
|
433
|
+
}
|
|
434
|
+
observer.addSort(new Sort(sortedItems));
|
|
405
435
|
return result;
|
|
406
436
|
},
|
|
407
437
|
splice(array, observer, splice, args) {
|
|
@@ -429,13 +459,14 @@ export const SpliceStrategy = Object.freeze({
|
|
|
429
459
|
* @param strategy - The splice strategy to use.
|
|
430
460
|
*/
|
|
431
461
|
setDefaultStrategy(strategy) {
|
|
432
|
-
|
|
462
|
+
defaultMutationStrategy = strategy;
|
|
433
463
|
},
|
|
434
464
|
});
|
|
435
|
-
function setNonEnumerable(target, property, value) {
|
|
465
|
+
function setNonEnumerable(target, property, value, writable = true) {
|
|
436
466
|
Reflect.defineProperty(target, property, {
|
|
437
467
|
value,
|
|
438
468
|
enumerable: false,
|
|
469
|
+
writable,
|
|
439
470
|
});
|
|
440
471
|
}
|
|
441
472
|
class DefaultArrayObserver extends SubscriberSet {
|
|
@@ -443,9 +474,11 @@ class DefaultArrayObserver extends SubscriberSet {
|
|
|
443
474
|
super(subject);
|
|
444
475
|
this.oldCollection = void 0;
|
|
445
476
|
this.splices = void 0;
|
|
477
|
+
this.sorts = void 0;
|
|
446
478
|
this.needsQueue = true;
|
|
447
479
|
this._strategy = null;
|
|
448
480
|
this._lengthObserver = void 0;
|
|
481
|
+
this._sortObserver = void 0;
|
|
449
482
|
this.call = this.flush;
|
|
450
483
|
setNonEnumerable(subject, "$fastController", this);
|
|
451
484
|
}
|
|
@@ -472,6 +505,23 @@ class DefaultArrayObserver extends SubscriberSet {
|
|
|
472
505
|
}
|
|
473
506
|
return observer;
|
|
474
507
|
}
|
|
508
|
+
get sortObserver() {
|
|
509
|
+
let observer = this._sortObserver;
|
|
510
|
+
if (observer === void 0) {
|
|
511
|
+
const array = this.subject;
|
|
512
|
+
this._sortObserver = observer = {
|
|
513
|
+
sorted: array.sorted,
|
|
514
|
+
handleChange() {
|
|
515
|
+
if (this.sorted !== array.sorted) {
|
|
516
|
+
this.sorted = array.sorted;
|
|
517
|
+
Observable.notify(observer, "sorted");
|
|
518
|
+
}
|
|
519
|
+
},
|
|
520
|
+
};
|
|
521
|
+
this.subscribe(observer);
|
|
522
|
+
}
|
|
523
|
+
return observer;
|
|
524
|
+
}
|
|
475
525
|
subscribe(subscriber) {
|
|
476
526
|
this.flush();
|
|
477
527
|
super.subscribe(subscriber);
|
|
@@ -485,6 +535,15 @@ class DefaultArrayObserver extends SubscriberSet {
|
|
|
485
535
|
}
|
|
486
536
|
this.enqueue();
|
|
487
537
|
}
|
|
538
|
+
addSort(sort) {
|
|
539
|
+
if (this.sorts === void 0) {
|
|
540
|
+
this.sorts = [sort];
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
this.sorts.push(sort);
|
|
544
|
+
}
|
|
545
|
+
this.enqueue();
|
|
546
|
+
}
|
|
488
547
|
reset(oldCollection) {
|
|
489
548
|
this.oldCollection = oldCollection;
|
|
490
549
|
this.enqueue();
|
|
@@ -492,14 +551,18 @@ class DefaultArrayObserver extends SubscriberSet {
|
|
|
492
551
|
flush() {
|
|
493
552
|
var _a;
|
|
494
553
|
const splices = this.splices;
|
|
554
|
+
const sorts = this.sorts;
|
|
495
555
|
const oldCollection = this.oldCollection;
|
|
496
|
-
if (splices === void 0 && oldCollection === void 0) {
|
|
556
|
+
if (splices === void 0 && oldCollection === void 0 && sorts === void 0) {
|
|
497
557
|
return;
|
|
498
558
|
}
|
|
499
559
|
this.needsQueue = true;
|
|
500
560
|
this.splices = void 0;
|
|
561
|
+
this.sorts = void 0;
|
|
501
562
|
this.oldCollection = void 0;
|
|
502
|
-
|
|
563
|
+
sorts !== void 0
|
|
564
|
+
? this.notify(sorts)
|
|
565
|
+
: this.notify(((_a = this._strategy) !== null && _a !== void 0 ? _a : defaultMutationStrategy).normalize(oldCollection, this.subject, splices));
|
|
503
566
|
}
|
|
504
567
|
enqueue() {
|
|
505
568
|
if (this.needsQueue) {
|
|
@@ -514,6 +577,7 @@ let enabled = false;
|
|
|
514
577
|
* @public
|
|
515
578
|
*/
|
|
516
579
|
export const ArrayObserver = Object.freeze({
|
|
580
|
+
sorted: 0,
|
|
517
581
|
/**
|
|
518
582
|
* Enables the array observation mechanism.
|
|
519
583
|
* @remarks
|
|
@@ -530,6 +594,7 @@ export const ArrayObserver = Object.freeze({
|
|
|
530
594
|
const proto = Array.prototype;
|
|
531
595
|
if (!proto.$fastPatch) {
|
|
532
596
|
setNonEnumerable(proto, "$fastPatch", 1);
|
|
597
|
+
setNonEnumerable(proto, "sorted", 0);
|
|
533
598
|
[
|
|
534
599
|
proto.pop,
|
|
535
600
|
proto.push,
|
|
@@ -544,7 +609,7 @@ export const ArrayObserver = Object.freeze({
|
|
|
544
609
|
const o = this.$fastController;
|
|
545
610
|
return o === void 0
|
|
546
611
|
? method.apply(this, args)
|
|
547
|
-
: ((_a = o.strategy) !== null && _a !== void 0 ? _a :
|
|
612
|
+
: ((_a = o.strategy) !== null && _a !== void 0 ? _a : defaultMutationStrategy)[method.name](this, o, method, args);
|
|
548
613
|
};
|
|
549
614
|
});
|
|
550
615
|
}
|
|
@@ -568,3 +633,21 @@ export function lengthOf(array) {
|
|
|
568
633
|
Observable.track(arrayObserver.lengthObserver, "length");
|
|
569
634
|
return array.length;
|
|
570
635
|
}
|
|
636
|
+
/**
|
|
637
|
+
* Enables observing the sorted property of an array.
|
|
638
|
+
* @param array - The array to observe the sorted property of.
|
|
639
|
+
* @returns The sorted property.
|
|
640
|
+
* @public
|
|
641
|
+
*/
|
|
642
|
+
export function sortedCount(array) {
|
|
643
|
+
if (!array) {
|
|
644
|
+
return 0;
|
|
645
|
+
}
|
|
646
|
+
let arrayObserver = array.$fastController;
|
|
647
|
+
if (arrayObserver === void 0) {
|
|
648
|
+
ArrayObserver.enable();
|
|
649
|
+
arrayObserver = Observable.getNotifier(array);
|
|
650
|
+
}
|
|
651
|
+
Observable.track(arrayObserver.sortObserver, "sorted");
|
|
652
|
+
return array.sorted;
|
|
653
|
+
}
|
|
@@ -112,8 +112,11 @@ export class RepeatBehavior {
|
|
|
112
112
|
else if (args[0].reset) {
|
|
113
113
|
this.refreshAllViews();
|
|
114
114
|
}
|
|
115
|
+
else if (args[0].sorted) {
|
|
116
|
+
this.updateSortedViews(args);
|
|
117
|
+
}
|
|
115
118
|
else {
|
|
116
|
-
this.
|
|
119
|
+
this.updateSplicedViews(args);
|
|
117
120
|
}
|
|
118
121
|
}
|
|
119
122
|
observeItems(force = false) {
|
|
@@ -131,7 +134,27 @@ export class RepeatBehavior {
|
|
|
131
134
|
newObserver.subscribe(this);
|
|
132
135
|
}
|
|
133
136
|
}
|
|
134
|
-
|
|
137
|
+
updateSortedViews(sorts) {
|
|
138
|
+
const views = this.views;
|
|
139
|
+
for (let i = 0, ii = sorts.length; i < ii; ++i) {
|
|
140
|
+
const sortedItems = sorts[i].sorted.slice();
|
|
141
|
+
const unsortedItems = sortedItems.slice().sort();
|
|
142
|
+
for (let j = 0, jj = sortedItems.length; j < jj; ++j) {
|
|
143
|
+
const sortedIndex = sortedItems.find(value => sortedItems[j] === unsortedItems[value]);
|
|
144
|
+
if (sortedIndex !== j) {
|
|
145
|
+
const removedItems = unsortedItems.splice(sortedIndex, 1);
|
|
146
|
+
unsortedItems.splice(j, 0, ...removedItems);
|
|
147
|
+
const neighbor = views[j];
|
|
148
|
+
const location = neighbor ? neighbor.firstChild : this.location;
|
|
149
|
+
views[sortedIndex].remove();
|
|
150
|
+
views[sortedIndex].insertBefore(location);
|
|
151
|
+
const removedViews = views.splice(sortedIndex, 1);
|
|
152
|
+
views.splice(j, 0, ...removedViews);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
updateSplicedViews(splices) {
|
|
135
158
|
const views = this.views;
|
|
136
159
|
const bindView = this.bindView;
|
|
137
160
|
const items = this.items;
|