Merge branch 'master' into users/kingoftac/css-custom-states

This commit is contained in:
Nathan Brown 2024-06-03 18:26:12 -06:00 коммит произвёл GitHub
Родитель 4e25adb8b4 5eb926a3f3
Коммит 9f4011c012
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
38 изменённых файлов: 1515 добавлений и 2619 удалений

2
.github/pull_request_template.md поставляемый
Просмотреть файл

@ -47,7 +47,7 @@ Please provide a summary of the tests affected by this work and any unique strat
- [ ] I have added tests for my changes.
- [ ] I have tested my changes.
- [ ] I have updated the project documentation to reflect my changes.
- [ ] I have read the [CONTRIBUTING](https://github.com/microsoft/fast/blob/master/CONTRIBUTING.md) documentation and followed the [standards](/docs/community/code-of-conduct/#our-standards) for this project.
- [ ] I have read the [CONTRIBUTING](https://github.com/microsoft/fast/blob/master/CONTRIBUTING.md) documentation and followed the [standards](https://github.com/microsoft/fast/blob/master/CODE_OF_CONDUCT.md#our-standards) for this project.
## ⏭ Next Steps

Просмотреть файл

@ -81,7 +81,7 @@ Packages are located within the `packages` folder of the repository. Each packag
If you'd like to contribute by fixing a bug, implementing a feature, or even correcting typos in our documentation, you'll want to submit a pull request. Before submitting a pull request, be sure to [rebase](https://www.atlassian.com/git/tutorials/merging-vs-rebasing) your branch (typically from master) or use the *merge* button provided by GitHub.
:::note
For additional details on branch management, read the [branch guide](../community/branch-guide.md) documentation.
For additional details on branch management, read the [branch guide](./BRANCH_GUIDE.md) documentation.
:::
#### Change Files
@ -118,7 +118,7 @@ If you are finding that your changes are either breaking changes or require mult
### Merging a pull request
If you are merging a pull request, be sure to use the pull request title as the commit title. The title should follow the [conventional commit guidelines](https://www.conventionalcommits.org/). It is recommended that if you are merging in pull requests regularly that you add a browser extension that will auto-correct the title for you. A few that should do this are [Refined GitHub](https://github.com/sindresorhus/refined-github) and [Squashed Merge Message](https://github.com/zachwhaley/squashed-merge-message).
If you are merging a pull request, be sure to use the pull request title as the commit title. The title should follow the [conventional commit guidelines](https://www.conventionalcommits.org/).
### Documenting breaking changes

Просмотреть файл

@ -1,7 +1,7 @@
{
"type": "none",
"comment": "DevDependency update",
"packageName": "@microsoft/fast-react-wrapper",
"comment": "Update documentation site with 2.0.0 content",
"packageName": "@microsoft/fast-element",
"email": "7559015+janechu@users.noreply.github.com",
"dependentChangeType": "none"
}

Просмотреть файл

@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "fix: remove link handler in disconnect",
"packageName": "@microsoft/fast-router",
"email": "1222863+xuwaters@users.noreply.github.com",
"dependentChangeType": "patch"
}

Просмотреть файл

@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "fix: typos in SSR README",
"packageName": "@microsoft/fast-ssr",
"email": "seanmonahan@microsoft.com",
"dependentChangeType": "patch"
}

Просмотреть файл

@ -1,230 +0,0 @@
---
id: declaring-templates
title: Declaring Templates
sidebar_label: Declaring Templates
custom_edit_url: https://github.com/microsoft/fast/edit/master/packages/web-components/fast-element/docs/guide/declaring-templates.md
description: While you can create and update nodes in the Shadow DOM manually, FASTElement provides a streamlined templating system for the most common rendering scenarios.
---
## Basic templates
While you can create and update nodes in the Shadow DOM manually, `FASTElement` provides a streamlined templating system for the most common rendering scenarios. To create an HTML template for an element, import and use the `html` tagged template helper and pass the template to the `@customElement` decorator.
Here's how we would add a template for our `name-tag` component that renders some basic structure as well as our `greeting`:
**Example: Adding a Template to a `FASTElement`**
```ts
import { FASTElement, customElement, attr, html } from '@microsoft/fast-element';
const template = html<NameTag>`
<div class="header">
<h3>${x => x.greeting.toUpperCase()}</h3>
<h4>my name is</h4>
</div>
<div class="body">TODO: Name Here</div>
<div class="footer"></div>
`;
@customElement({
name: 'name-tag',
template
})
export class NameTag extends FASTElement {
@attr greeting: string = 'Hello';
}
```
There are several important details in the above example, so let's break them down one-by-one.
First, we create a template by using a [tagged template literal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals). The tag, `html`, provides special processing for the HTML string that follows, returning an instance of `ViewTemplate`.
Within a template, we provide *bindings* that declare the *dynamic parts* of our template. These bindings are declared with [arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions). Because the template is typed, the input to your arrow function will be an instance of the data model you declared in your `html` tag. When the `html` tag processes your template, it identifies these dynamic expressions and builds up an optimized model, capable of high-performance rendering, and efficient, incremental batched updates.
Finally, we associate the template with our custom element by using a new form of the `@customElement` decorator, which allows us to pass more options. In this configuration, we pass an options object specifying the `name` and the `template`.
With this in place, we now have a `name-tag` element that will render its template into the Shadow DOM and automatically update the `h3` content whenever the name tag's `greeting` attribute changes. Give it a try!
### Typed Templates
Your templates can be *typed* to the data model that they are rendering over. In TypeScript, we provide the type as part of the tag: `html<NameTag>`. For TypeScript 3.8 or higher, you can import `ViewTemplate` as a type:
```ts
import type { ViewTemplate } from '@microsoft/fast-element';
const template: ViewTemplate<NameTag> = html`
<div>${x => x.greeting}</div>
`;
```
## Understanding bindings
We've seen how arrow functions can be used to declare dynamic parts of templates. Let's look at a few more examples to see the breadth of what is available to you.
### Content
To bind the content of an element, simply provide the expression within the start and end tags of the element. It can be the sole content of the element or interwoven with other elements and text.
**Example: Basic Text Content**
```html
<h3>${x => x.greeting.toUpperCase()}</h3>
```
**Example: Interpolated Text Content**
```html
<h3>${x => x.greeting}, my name is ${x => x.name}.</h3>
```
**Example: Heterogeneous Content**
```html
<h3>
${x => x.greeting}, my name is
<span class="name">${x => x.name}</span>.
</h3>
```
:::note
Dynamic content is set via the `textContent` HTML property for security reasons. You *cannot* set HTML content this way. See below for the explicit, opt-in mechanism for setting HTML.
:::
### Attributes
You can also use an expression to set an attribute value on an HTML Element. Simply place the expression where the value of the HTML attribute would go. The template engine will then use your expression to set the value using `setAttribute(...)`, whenever it needs to be updated. Additionally, some attributes are known as [boolean attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes#Boolean_Attributes) (e.g. required, readonly, disabled). These attributes behave differently from normal attributes and need special value handling. The templating engine will handle this for you if you prepend the attribute name with a `?`.
**Example: Basic Attribute Values**
```html
<a href="${x => x.aboutLink}">About</a>
```
**Example: Interpolated Attribute Values**
```html
<a href="products/${x => x.id}">
${x => x.name}
</a>
```
```html
<li class="list-item ${x => x.type}">
...
</li>
```
:::tip
When binding to `class`, the underlying engine will not over-write classes added to the element via other mechanisms. It only adds and removes classes that result directly from the binding. This "safe by default" behavior does come at a slight performance cost. To opt-out of this feature and squeeze out every ounce of performance by always overwriting all classes, use a property binding (see below) on the `className` property. e.g. `:className="list-item ${x => x.type}"`.
:::
```html
<span style="text-decoration: ${x => x.done ? 'line-through' : ''}">
${x => x.description}
</span>
```
**Example: ARIA Attributes**
```html
<div role="progressbar"
aria-valuenow="${x => x.value}"
aria-valuemin="${x => x.min}"
aria-valuemax="${x => x.max}">
</div>
```
**Example: Boolean Attributes**
```html
<button type="submit" ?disabled="${x => !x.enabled}">Submit</button>
```
**Example: Nullish value**
Some cases may occur where an attribute needs to have a value when used however not present if unused. These are different than boolean attributes by where their presence alone triggers their effect. In this situation, a nullish value (`null` or `undefined`) may be provided so the attribute won't exist in that condition.
```html
<div aria-hidden="${x => x.isViewable ? 'true' : null}"></div>
```
### Properties
Properties can also be set directly on an HTML element. To do so, prepend the property name with `:` to indicate a property binding. The template engine will then use your expression to assign the element's property value.
**Example: Basic Property Values**
```html
<my-element :myCustomProperty="${x => x.mySpecialData}">
...
</my-element>
```
**Example: Inner HTML**
```html
<div :innerHTML="${x => x.someDangerousHTMLContent}"></div>
```
:::warning
Avoid scenarios that require you to directly set HTML, especially when the content is coming from an external source. If you must do this, you should always sanitize the HTML.
The best way to accomplish HTML sanitization is to configure [a trusted types policy](https://w3c.github.io/webappsec-trusted-types/dist/spec/) with FASTElement's template engine. FASTElement ensures that all HTML strings pass through the configured policy. Also, by leveraging the platform's trusted types capabilities, you get native enforcement of the policy through CSP headers. Here's an example of how to configure a custom policy to sanitize HTML:
```ts
import { DOM } from '@microsoft/fast-element';
const myPolicy = trustedTypes.createPolicy('my-policy', {
createHTML(html) {
// TODO: invoke a sanitization library on the html before returning it
return html;
}
});
DOM.setHTMLPolicy(myPolicy);
```
For security reasons, the HTML Policy can only be set once. For this reason, it should be set by application developers and not by component authors, and it should be done immediately during the startup sequence of the application.
:::
### Events
Besides rendering content, attributes, and properties, you'll often want to add event listeners and execute code when events fire. To do that, prepend the event name with `@` and provide the expression to be called when that event fires. Within an event binding, you also have access to a special *context* argument from which you can access the `event` object.
**Example: Basic Events**
```html
<button @click="${x => x.remove()}">Remove</button>
```
**Example: Accessing Event Details**
```html
<input type="text"
:value="${x => x.description}"
@input="${(x, c) => x.handleDescriptionChange(c.event)}">
```
:::important
In both examples above, after your event handler is executed, `preventDefault()` will be called on the event object by default. You can return `true` from your handler to opt-out of this behavior.
:::
The second example demonstrates an important characteristic of the templating engine: it only supports *unidirectional data flow* `(model => view)`. It does not support *two-way data binding* `(model <=> view)`. As shown above, pushing data from the view back to the model should be handled with explicit events that call into your model's API.
## Templating and the element lifecycle
It is during the `connectedCallback` phase of the Custom Element lifecycle that `FASTElement` creates templates and binds the resulting view. The creation of the template only occurs the first time the element is connected, while binding happens every time the element is connected (with unbinding happening during the `disconnectedCallback` for symmetry).
:::note
In the future, we're planning new optimizations that will enable us to safely determine when we do not need to unbind/rebind certain views.
:::
In most cases, the template that `FASTElement` renders is determined by the `template` property of the Custom Element's configuration. However, you can also implement a method on your Custom Element class named `resolveTemplate()` that returns a template instance. If this method is present, it will be called during `connectedCallback` to obtain the template to use. This allows the element author to dynamically select completely different templates based on the state of the element at the time of connection.
In addition to dynamic template selection during the `connectedCallback`, the `$fastController` property of `FASTElement` enables dynamically changing the template at any time by setting the controller's `template` property to any valid template.
:::tip
Check out [our Cheat Sheet](../resources/cheat-sheet.md#bindings) for a quick guide on bindings.
:::

Просмотреть файл

@ -1,214 +0,0 @@
---
id: defining-elements
title: Defining Elements
sidebar_label: Defining Elements
custom_edit_url: https://github.com/microsoft/fast/edit/master/packages/web-components/fast-element/docs/guide/defining-elements.md
description: To define a custom element, begin by creating a class that extends FASTElement and decorate it with the @customElement decorator, providing the element name.
---
## Basic elements
To define a custom element, begin by creating a class that extends `FASTElement` and decorate it with the `@customElement` decorator, providing the element name.
**Example: A Basic `FASTElement` Definition**
```ts
import { FASTElement, customElement } from '@microsoft/fast-element';
@customElement('name-tag')
export class NameTag extends FASTElement {
}
```
With this in place, you can now use your `name-tag` element anywhere in HTML with the following markup:
**Example: Using a Web Component**
```html
<name-tag></name-tag>
```
:::important
Web Component names must contain a `-` in order to prevent future conflicts with built-in elements and to namespace components from different libraries. For more information on the basics of Web Components [see this set of articles](https://developers.google.com/web/fundamentals/web-components).
:::
:::note
HTML has a few special tags known as "self-closing tags". Common examples include `<input>` and `<img>`. However, most HTML elements and **all** web components must have an explicit closing tag.
:::
We've got a basic Web Component in place, but it doesn't do much. So, let's add an attribute and make it render something.
**Example: Adding Attributes to a `FASTElement`**
```ts
import { FASTElement, customElement, attr } from '@microsoft/fast-element';
@customElement('name-tag')
export class NameTag extends FASTElement {
@attr greeting: string = 'Hello';
// optional method
greetingChanged() {
...
}
}
```
To add attributes to your HTML element, create properties decorated by the `@attr` decorator. All attributes defined this way will be automatically registered with the platform so that they can be updated through the browser's native `setAttribute` API as well as the property. You can optionally add a method with the naming convention *propertyName*Changed to your class (e.g. `greeting` and `greetingChanged()`), and this method will be called whenever your property changes, whether it changes through the property or the attribute API.
:::note
All properties decorated with `@attr` are also *observable*. See [observables and state](./observables-and-state.md) for information about how observables enable efficient rendering.
:::
By default, anything extending from `FASTElement` will automatically have a `ShadowRoot` attached in order to enable encapsulated rendering.
To see it in action, you can use the same HTML as above, or change the default `greeting` with the following:
**Example: Using a Web Component with Attributes**
```html
<name-tag greeting="Hola"></name-tag>
```
## Customizing attributes
By default, any attribute created with `@attr` will perform no explicit type coercion other than when it reflects its value to the HTML DOM via the `setAttribute` API. However, you can convert DOM attribute string values to and from arbitrary types as well as control the `mode` that is used to reflect property values to the DOM. There are three modes available through the `mode` property of the attribute configuration:
* `reflect` - The *default* mode that is used if none is specified. This reflects property changes to the DOM. If a `converter` is supplied, it will invoke the converter before calling the `setAttribute` DOM API.
* `boolean` - This mode causes your attribute to function using the HTML boolean attribute behavior. When your attribute is present in the DOM or equal to its own name, the value will be true. When the attribute is absent from the DOM, the value of the property will be false. Setting the property will also update the DOM by adding/removing the attribute.
* `fromView` - This mode skips reflecting the value of the property back to the HTML attribute, but does receive updates when changed through `setAttribute`.
In addition to setting the `mode`, you can also supply a custom `ValueConverter` by setting the `converter` property of the attribute configuration. The converter must implement the following interface:
```ts
interface ValueConverter {
toView(value: any): string;
fromView(value: string): any;
}
```
Here's how it works:
* When the DOM attribute value changes, the converter's `fromView` method will be called, allowing custom code to coerce the value to the proper type expected by the property.
* When the property value changes, the converter's `fromView` method will also be called, ensuring that the type is correct. After this, the `mode` will be determined. If the mode is set to `reflect` then the converter's `toView` method will be called to allow the type to be formatted before writing to the attribute using `setAttribute`.
:::important
When the `mode` is set to `boolean`, a built-in `booleanConverter` is automatically used to ensure type correctness so that the manual configuration of the converter is not needed in this common scenario.
:::
**Example: An Attribute in Reflect Mode with No Special Conversion**
```ts
import { FASTElement, customElement, attr } from '@microsoft/fast-element';
@customElement('name-tag')
export class NameTag extends FASTElement {
@attr greeting: string = 'Hello';
}
```
**Example: An Attribute in Boolean Mode with Boolean Conversion**
```ts
import { FASTElement, customElement, attr } from '@microsoft/fast-element';
@customElement('my-checkbox')
export class MyCheckbox extends FASTElement {
@attr({ mode: 'boolean' }) disabled: boolean = false;
}
```
**Example: An Attribute in Reflect Mode with Custom Conversion**
```ts
import { FASTElement, customElement, attr, ValueConverter } from '@microsoft/fast-element';
const numberConverter: ValueConverter = {
toView(value: any): string {
// convert numbers to strings
},
fromView(value: string): any {
// convert strings to numbers
}
};
@customElement('my-counter')
export class MyCounter extends FASTElement {
@attr({ converter: numberConverter }) count: number = 0;
}
```
## The element lifecycle
All Web Components support a series of lifecycle events that you can tap into to execute custom code at specific points in time. `FASTElement` implements several of these callbacks automatically in order to enable features of its templating engine (described in [declaring templates](./declaring-templates.md)). However, you can override them to provide your own code. Here's an example of how you would execute custom code when your element is inserted into the DOM.
**Example: Tapping into the Custom Element Lifecycle**
```ts
import { FASTElement, customElement, attr } from '@microsoft/fast-element';
@customElement('name-tag')
export class NameTag extends FASTElement {
@attr greeting: string = 'Hello';
greetingChanged() {
this.shadowRoot!.innerHTML = this.greeting;
}
connectedCallback() {
super.connectedCallback();
console.log('name-tag is now connected to the DOM');
}
}
```
The full list of available lifecycle callbacks is:
| Callback | Description |
| ------------- |-------------|
| constructor | Runs when the element is created or upgraded. `FASTElement` will attach the shadow DOM at this time. |
| connectedCallback | Runs when the element is inserted into the DOM. On first connect, `FASTElement` hydrates the HTML template, connects template bindings, and adds the styles. |
| disconnectedCallback | Runs when the element is removed from the DOM. `FASTElement` will remove template bindings and clean up resources at this time. |
| attributeChangedCallback(attrName, oldVal, newVal) | Runs any time one of the element's custom attributes changes. `FASTElement` uses this to sync the attribute with its property. When the property updates, a render update is also queued, if there was a template dependency. |
| adoptedCallback | Runs if the element was moved from its current `document` into a new `document` via a call to the `adoptNode(...)` API. |
## Working without decorators
The examples above and those throughout our documentation leverage TypeScript, and in particular, the decorators feature of the language. Decorators are an upcoming feature planned for a future version of JavaScript, but their design is not yet finished. While the syntax for decorator usage is not likely to change in the final version of the feature, some of our community members may feel uncomfortable using this feature at this stage. Additionally, since decorators are transpiled into code that uses helper functions (both in TypeScript and Babel) the compiled output will be larger than the equivalent non-decorator code.
While there are size implications of using decorators prior to full language support, they do present the most declarative and readable form of the API, and we recommend their use for the average project. To strike a balance between declarative readability and size, we recommend that TypeScript be used in combination with the `"importHelpers": true` compiler option. When this option is set, instead of generating helper functions for decorators into every file, TypeScript will import a set of shared helpers published in the `tslib` package.
For those that require the smallest possible builds, FAST Elements can be completely defined in Vanilla JS, without using decorators, by leveraging a static `definition` field on your class. The `definition` field only needs to present the same configuration as the `@customElement` decorator. Here's an example that shows the use of the `definition` field along with a manual call to `define` the element:
```js
import { FASTElement, html, css } from '@microsoft/fast-element';
const template = html`...`;
const styles = css`...`;
const converter = { ... };
export class MyElement extends FASTElement {
static definition = {
name: 'my-element',
template,
styles,
attributes: [
'value', // same attr/prop
{ attribute: 'some-attr', property: 'someAttr' }, // different attr/prop
{ property: 'count', converter } // derive attr; add converter
]
};
value = '';
someAttr = '';
count = 0;
}
FASTElement.define(MyElement);
```
:::note
The `definition` can also be separated from the class and passed into the `define` call directly if desired. Here's what that would look like: `FASTElement.define(MyElement, myDefinition);`
:::

Просмотреть файл

@ -1,253 +0,0 @@
---
id: leveraging-css
title: Leveraging CSS
sidebar_label: Leveraging CSS
custom_edit_url: https://github.com/microsoft/fast/edit/master/packages/web-components/fast-element/docs/guide/leveraging-css.md
description: Similar to HTML, FASTElement provides a css tagged template helper to allow creating and re-using CSS.
---
## Basic styles
The final piece of our component story is CSS. Similar to HTML, `FASTElement` provides a `css` tagged template helper to allow creating and re-using CSS. Let's add some CSS for our `name-tag` component.
**Example: Adding CSS to a `FASTElement`**
```ts
import { html, css, customElement, attr, FASTElement } from "@microsoft/fast-element";
const template = html<NameTag>`
<div class="header">
<slot name="avatar"></slot>
<h3>${x => x.greeting.toUpperCase()}</h3>
<h4>my name is</h4>
</div>
<div class="body">
<slot></slot>
</div>
<div class="footer"></div>
`;
const styles = css`
:host {
display: inline-block;
contain: content;
color: white;
background: var(--fill-color);
border-radius: var(--border-radius);
min-width: 325px;
text-align: center;
box-shadow: 0 0 calc(var(--depth) * 1px) rgba(0,0,0,.5);
}
:host([hidden]) {
display: none;
}
.header {
margin: 16px 0;
position: relative;
}
h3 {
font-weight: bold;
font-family: 'Source Sans Pro';
letter-spacing: 4px;
font-size: 32px;
margin: 0;
padding: 0;
}
h4 {
font-family: sans-serif;
font-size: 18px;
margin: 0;
padding: 0;
}
.body {
background: white;
color: black;
padding: 32px 8px;
font-size: 42px;
font-family: cursive;
}
.footer {
height: 16px;
background: var(--fill-color);
border-radius: 0 0 var(--border-radius) var(--border-radius);
}
`;
@customElement({
name: 'name-tag',
template,
styles
})
export class NameTag extends FASTElement {
@attr greeting: string = 'Hello';
}
```
Using the `css` helper, we're able to create `ElementStyles`. We configure this with the element through the `styles` option of the decorator. Internally, `FASTElement` will leverage [Constructable Stylesheet Objects](https://wicg.github.io/construct-stylesheets/) and `ShadowRoot#adoptedStyleSheets` to efficiently re-use CSS across components. This means that even if we have 1k instances of our `name-tag` component, they will all share a single instance of the associated styles, allowing for reduced memory allocation and improved performance. Because the styles are associated with the `ShadowRoot`, they will also be encapsulated. This ensures that your styles don't affect other elements and other element styles don't affect your element.
:::note
We've used [CSS Custom Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/--*) throughout our CSS as well as [CSS Calc](https://developer.mozilla.org/en-US/docs/Web/CSS/calc) in order to enable our component to be styled in basic ways by consumers. Additionally, consider adding [CSS Shadow Parts](https://developer.mozilla.org/en-US/docs/Web/CSS/::part) to your template, to enable even more powerful customization.
:::
## Composing styles
One of the nice features of `ElementStyles` is that it can be composed with other styles. Imagine that we had a CSS normalize that we wanted to use in our `name-tag` component. We could compose that into our styles like this:
**Example: Composing CSS Registries**
```ts
import { normalize } from './normalize';
const styles = css`
${normalize}
:host {
display: inline-block;
contain: content;
color: white;
background: var(--fill-color);
border-radius: var(--border-radius);
min-width: 325px;
text-align: center;
box-shadow: 0 0 calc(var(--depth) * 1px) rgba(0,0,0,.5);
}
...
`;
```
Rather than simply concatenating CSS strings, the `css` helper understands that `normalize` is `ElementStyles` and is able to re-use the same Constructable StyleSheet instance as any other component that uses `normalize`.
:::note
You can also pass a CSS `string` or a [CSSStyleSheet](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet) instance directly to the element definition, or even a mixed array of `string`, `CSSStyleSheet`, or `ElementStyles`.
:::
### Partial CSS
There are times when you may want to create reusable blocks of *partial* CSS, where the abstraction is not valid CSS in and of itself, such as groups of CSS properties or a complex value. To do that, you can use the `cssPartial` tagged template literal:
```ts
import { css, cssPartial } from "@microsoft/fast-element";
const partial = cssPartial`color: red;`;
const styles = css`:host{ ${partial} }`;
```
`cssPartial` can also compose all structures that `css` can compose, providing even greater flexibility.
## CSSDirective
The `CSSDirective` allows binding behavior to an element via `ElementStyles`. To create a `CSSDirective`, import and extend `CSSDirective` from `@microsoft/fast-element`:
```ts
import { CSSDirective } from "@microsoft/fast-element"
class RandomWidth extends CSSDirective {}
```
A CSS directive has two key methods that you can leverage to add dynamic behavior via CSS:
### createCSS
`CSSDirective` has a `createCSS()` method that returns a string to be interpolated into an `ElementStyles`:
```ts
class RandomWidth extends CSSDirective {
createCSS() {
return "width: var(--random-width);"
}
}
```
### createBehavior
The `createBehavior()` method can be used to create a `Behavior` that is bound to the element using the `CSSDirective`:
```ts
class RandomWidth extends CSSDirective {
private property = "--random-width";
createCSS() {
return `width: var(${this.property});`
}
createBehavior() {
return {
bind(el) {
el.style.setProperty(this.property, Math.random() * 100)
}
unbind(el) {
el.style.removeProperty(this.property);
}
}
}
}
```
### Usage in ElementStyles
The `CSSDirective` can then be used in an `ElementStyles`, where the CSS string from `createCSS()` will be interpolated into the stylesheet, and the behavior returned from `createBehavior()` will get bound to the element using the stylesheet:
```ts
const styles = css`:host {${new RandomWidth()}}`;
```
## Shadow DOM styling
You may have noticed the `:host` selector we used in our `name-tag` styles. This selector allows us to apply styles directly to our custom element. Here are a few things to consider always configuring for your host element:
* **display** - By default, the `display` property of a custom element is `inline`, so consider whether you want your element's default display behavior to be different.
* **contain** - If your element's painting is contained within its bounds, consider setting the CSS `contain` property to `content`. The right containment model can positively affect your element's performance. [See the MDN docs](https://developer.mozilla.org/en-US/docs/web/css/contain) for more information on the various values of `contain` and what they do.
* **hidden** - In addition to a default `display` style, add support for `hidden` so that your default `display` does not override this state. This can be done with `:host([hidden]) { display: none }`.
## Slotted content
In addition to providing host styles, you can also provide default styles for content that gets slotted. For example, if we wanted to style all `img` elements that were slotted into our `name-tag`, we could do it like this:
**Example: Slotted Styles**
```ts
const styles = css`
...
::slotted(img) {
border-radius: 50%;
height: 64px;
width: 64px;
box-shadow: 0 0 calc(var(--depth) / 2px) rgba(0,0,0,.5);
position: absolute;
left: 16px;
top: -4px;
}
...
`;
```
:::note
Both slotted and host styles can be overridden by the element user. Think of these as the *default* styles that you are providing, so that your elements look and function correctly out-of-the-box.
:::
## Styles and the element lifecycle
It is during the `connectedCallback` phase of the Custom Element lifecycle that `FASTElement` adds the element's styles. The styles are only added the first time the element is connected.
In most cases, the styles that `FASTElement` renders are determined by the `styles` property of the Custom Element's configuration. However, you can also implement a method on your Custom Element class named `resolveStyles()` that returns an `ElementStyles` instance. If this method is present, it will be called during `connectedCallback` to obtain the styles to use. This allows the element author to dynamically select completely different styles based on the state of the element at the time of connection.
In addition to dynamic style selection during the `connectedCallback`, the `$fastController` property of `FASTElement` enables dynamically changing the styles at any time through setting the controller's `styles` property to any valid styles.
### Hiding undefined elements
Custom Elements that have not been [upgraded](https://developers.google.com/web/fundamentals/web-components/customelements#upgrades) and don't have styles attached can still be rendered by the browser but they likely do not look how they are supposed to. To avoid a *flash of un-styled content* (FOUC), visually hide Custom Elements if they have not been *defined*:
```CSS
:not(:defined) {
visibility: hidden;
}
```
:::important
The consuming application must apply this, as the components themselves do not.
:::

Просмотреть файл

@ -1,13 +0,0 @@
---
id: next-steps
title: Next Steps
sidebar_label: Next Steps
custom_edit_url: https://github.com/microsoft/fast/edit/master/packages/web-components/fast-element/docs/guide/next-steps.md
description: Now that you're familiar with the robust and powerful features of FASTElement, you're ready to build your own components and apps.
---
We've seen how to use `FASTElement` to declaratively build Web Components. In addition to the basics of element and attribute definition, `FASTElement` also provides a way to declare templates capable of high-performance rendering, and efficient, incremental batched updates. Finally, CSS can easily be associated with an element in a way that leverages core platform optimizations for performance and low memory allocation.
Now that you're familiar with the robust and powerful features of `FASTElement`, you're ready to build your own components and apps.
For a quick reference, check out [our Cheat Sheet](../resources/cheat-sheet.md).

Просмотреть файл

@ -1,213 +0,0 @@
---
id: observables-and-state
title: Observables and State
sidebar_label: Observables and State
custom_edit_url: https://github.com/microsoft/fast/edit/master/packages/web-components/fast-element/docs/guide/observables-and-state.md
description: To enable binding tracking and change notification, properties must be decorated with either @attr or @observable.
---
## Reactivity
The arrow function bindings and directives used in templates allow the `fast-element` templating engine to intelligently react by only updating the parts of the DOM that actually change, with no need for a virtual DOM, VDOM diffing, or DOM reconciliation algorithms. This approach enables top-tier initial render time, industry-leading incremental DOM updates, and ultra-low memory allocation.
When a binding is used within a template, the underlying engine uses a technique to capture which properties are accessed in that expression. With the list of properties captured, it then subscribes to changes in their values. Any time a value changes, a task is scheduled on the DOM update queue. When the queue is processed, all updates run as a batch, updating precisely the aspects of the DOM that have changed.
## Observables
To enable binding tracking and change notification, properties must be decorated with either `@attr` or `@observable`. Use `@attr` for primitive properties (string, bool, number) that are intended to be surfaced on your element as HTML attributes. Use `@observable` for all other properties. In addition to observing properties, the templating system can also observe arrays. The `repeat` directive is able to efficiently respond to array change records, updating the DOM based on changes in the collection.
These decorators are a means of meta-programming the properties on your class, such that they include all the implementation needed to support state tracking, observation, and reactivity. You can access any property within your template, but if it hasn't been decorated with one of these two decorators, its value will not update after the initial render.
:::important
The `@attr` decorator can only be used in a `FASTElement` but the `@observable` decorator can be used in any class.
:::
:::important
Properties with only a getter, that function as a computed property over other observables, should not be decorated with `@attr` or `@observable`. However, they may need to be decorated with `@volatile`, depending on the internal logic.
:::
## Observable Features
### Access tracking
When `@attr` and `@observable` decorated properties are accessed during template rendering, they are tracked, allowing the engine to deeply understand the relationship between your model and view. These decorators serves to meta-program the property for you, injecting code to enable the observation system. However, if you do not like this approach, for `@observable`, you can always implement notification manually. Here's what that would look like:
**Example: Manual Observer Implementation**
```ts
import { Observable } from '@microsoft/fast-element';
export class Person {
private _firstName: string;
private _lastName: string;
get firstName() {
Observable.track(this, 'firstName');
return this._firstName;
}
set firstName(value: string) {
this._firstName = value;
Observable.notify(this, 'firstName');
}
get lastName() {
Observable.track(this, 'lastName');
return this._lastName;
}
set lastName(value: string) {
this._lastName = value;
Observable.notify(this, 'lastName');
}
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
```
Notice that the `fullName` property does not need any special code, since it's computing based on properties that are already observable. There is one special exception to this: if you have a computed property with branching code paths, such as ternary operators, if/else conditions, etc, then you must tell the observation system that your computed property has *volatile* dependencies. In other words, which properties need to be observed may change from invocation to invocation based on which code path executes.
Here's how you would do that with a decorator:
```ts
import { observable, volatile } from '@microsoft/fast-element';
export class MyClass {
@observable someBoolean = false;
@observable valueA = 0;
@observable valueB = 42;
@volatile
get computedValue() {
return this.someBoolean ? this.valueA : this.valueB;
}
}
```
Here's how you would do it without a decorator:
```ts
import { Observable, observable } from '@microsoft/fast-element';
export class MyClass {
@observable someBoolean = false;
@observable valueA = 0;
@observable valueB = 42;
get computedValue() {
Observable.trackVolatile();
return this.someBoolean ? this.valueA : this.valueB;
}
}
```
### Internal observation
On the class where the `@attr` or `@observable` is defined, you can optionally implement a *propertyName*Changed method to easily respond to changes in your own state.
**Example: Property Change Callbacks**
```ts
import { observable } from '@microsoft/fast-element';
export class Person {
@observable name: string;
nameChanged(oldValue: string, newValue: string) {
}
}
```
### External observation
Decorated properties can be subscribed to, to receive notification of changes in the property value. The templating engine uses this, but you can also directly subscribe. Here's how you would subscribe to changes in the `name` property of a `Person` class:
**Example: Subscribing to an Observable**
```ts
import { Observable } from '@microsoft/fast-element';
const person = new Person();
const notifier = Observable.getNotifier(person);
const handler = {
handleChange(source: any, propertyName: string) {
// respond to the change here
// source will be the person instance
// propertyName will be "name"
}
};
notifier.subscribe(handler, 'firstName')
notifier.unsubscribe(handler, 'lastName');
```
## Observing Arrays
So far, we've only seen how to observe properties on objects, but it's also possible to observe arrays for changes. Given an instance of an array, it can be observed like this:
**Example: Observing an Array**
```ts
const arr = [];
const notifier = Observable.getNotifier(arr);
const handler = {
handleChange(source: any, splices: Splice[]) {
// respond to the change here
// source will be the array instance
// splices will be an array of change records
// describing the mutations in the array in
// terms of splice operations
}
};
notifier.subscribe(handler);
```
There are a couple of important details to note with array observation:
* The `fast-element` library's ability to observe arrays is opt-in, in order that the functionality remain tree-shakeable. If you use a `repeat` directive anywhere in your code, you will be automatically opted in. However, if you wish to use the above APIs and are not using `repeat`, you will need to enable array observation by importing and calling the `enableArrayObservation()` function.
* The observation system cannot track changes made directly through an index update. e.g. `arr[3] = 'new value';`. This is due to a limitation in JavaScript. To work around this, update arrays with the equivalent `splice` code e.g. `arr.splice(3, 1, 'new value');`
* If the array is a property of an object, you will often want to observe both the property and the array. Observing the property will allow you to detect when the array instance is completely replaced on the object, while observing the array will allow you to detect changes in the array instance itself. When the property changes, be sure to unsubscribe to the old array and set up a subscription to the new array instance.
* Observing an array only notifies on changes to the array itself. It does not notify on changes to properties on objects held within the array. Separate observers would need to be set up for those individual properties. These could be set up and torn down in response to changes in the array though.
## Observing Volatile Properties
In addition to watching properties and arrays, you can also watch volatile properties.
**Example: Subscribing to a Volatile Property**
```ts
import { Observable, defaultExecutionContext } from '@microsoft/fast-element';
const myObject = new MyClass();
const handler = {
handleChange(source: any) {
// respond to the change here
// the source is the volatile binding itself
}
};
const bindingObserver = Observable.binding(myObject.computedValue, handler);
bindingObserver.observe(myObject, defaultExecutionContext);
// Call this to dismantle the observer
bindingObserver.disconnect();
```
### Records
To inspect which observable objects and properties were accessed from a `BindingObserver`, you can get the observation records from `BindingObserver.records()` after observing the binding.
**Example: Getting observation records**
```ts
const binding = (x: MyClass) => x.someBoolean ? x.valueA : x.valueB;
const bindingObserver = Observable.binding(binding);
const value = bindingObserver.observe({}, defaultExecutionContext);
for (const record of bindingObserver.records()) {
// Do something with the binding's observable dependencies
console.log(record.propertySource, record.propertyName)
}
```

Просмотреть файл

@ -1,576 +0,0 @@
---
id: using-directives
title: Using Directives
sidebar_label: Using Directives
custom_edit_url: https://github.com/microsoft/fast/edit/master/packages/web-components/fast-element/docs/guide/using-directives.md
description: In addition to declaring dynamic parts of templates with expressions, you also have access to several powerful directives, which aid in common scenarios.
---
In addition to declaring dynamic parts of templates with expressions, you also have access to several powerful *directives*, which aid in common scenarios.
## Structural directives
Structural directives change the shape of the DOM itself by adding and removing nodes based on the state of your element.
### The `when` directive
The `when` directive enables you to conditionally render blocks of HTML. When you provide an expression to `when` it will render the child template into the DOM when the expression evaluates to `true` and remove the child template when it evaluates to `false` (or if it is never `true`, the rendering will be skipped entirely).
**Example: Conditional Rendering**
```ts
import { FASTElement, customElement, observable, html, when } from '@microsoft/fast-element';
const template = html<MyApp>`
<h1>My App</h1>
${when(x => !x.ready, html<MyApp>`
Loading...
`)}
`;
@customElement({
name: 'my-app',
template
})
export class MyApp extends FASTElement {
@observable ready: boolean = false;
@observable data: any = null;
connectedCallback() {
super.connectedCallback();
this.loadData();
}
async loadData() {
const response = await fetch('some/resource');
const data = await response.json();
this.data = data;
this.ready = true;
}
}
```
:::note
The `@observable` decorator creates a property that the template system can watch for changes. It is similar to `@attr`, but the property is not surfaced as an HTML attribute on the element itself. While `@attr` can only be used in a `FASTElement`, `@observable` can be used in any class.
:::
In addition to providing a template to conditionally render, you can also provide an expression that evaluates to a template. This enables you to dynamically change what you are conditionally rendering.
**Example: Conditional Rendering with Dynamic Template**
```ts
import { FASTElement, customElement, observable, html, when } from '@microsoft/fast-element';
const template = html<MyApp>`
<h1>My App</h1>
${when(x => x.ready, x => x.dataTemplate)}
`;
```
### The `repeat` directive
To render a list of data, use the `repeat` directive, providing the list to render and a template to use in rendering each item.
**Example: List Rendering**
```ts
import { FASTElement, customElement, observable, html, repeat } from '@microsoft/fast-element';
const template = html<FriendList>`
<h1>Friends</h1>
<form @submit=${x => x.addFriend()}>
<input type="text" :value=${x => x.name} @input=${(x, c) => x.handleNameInput(c.event)}>
<button type="submit">Add Friend</button>
</form>
<ul>
${repeat(x => x.friends, html<string>`
<li>${x => x}</li>
`)}
</ul>
`;
@customElement({
name: 'friend-list',
template
})
export class FriendList extends FASTElement {
@observable friends: string[] = [];
@observable name: string = '';
addFriend() {
if (!this.name) {
return;
}
this.friends.push(this.name);
this.name = '';
}
handleNameInput(event: Event) {
this.name = (event.target! as HTMLInputElement).value;
}
}
```
Similar to event handlers, within a `repeat` block you have access to a special context object. Here is a list of the properties that are available on the context:
* `event` - The event object when inside an event handler.
* `parent` - The parent view-model when inside a `repeat` block.
* `parentContext` - The parent `ExecutionContext` when inside a `repeat` block. This is useful when repeats are nested and the inner-most repeat needs access to the root view-model.
* `index` - The index of the current item when inside a `repeat` block (opt-in).
* `length` - The length of the array when inside a `repeat` block (opt-in).
* `isEven` - True if the index of the current item is even when inside a `repeat` block (opt-in).
* `isOdd` - True if the index of the current item is odd when inside a `repeat` block (opt-in).
* `isFirst` - True if the current item is first in the array inside a `repeat` block (opt-in).
* `isInMiddle` - True if the current item is somewhere in the middle of the array inside a `repeat` block (opt-in).
* `isLast` - True if the current item is last in the array inside a `repeat` block (opt-in).
Some context properties are opt-in because they are more costly to update. So, for performance reasons, they are not available by default. To opt into the positioning properties, pass options to the repeat directive, with the setting `positioning: true`. For example, here's how we would use the `index` in our friends template from above:
**Example: List Rendering with Item Index**
```html
<ul>
${repeat(x => x.friends, html<string>`
<li>${(x, c) => c.index} ${x => x}</li>
`, { positioning: true })}
</ul>
```
Whether or not a repeat directive re-uses item views can be controlled with the `recycle` option setting. When `recycle: true`, which is the default value, the repeat directive may reuse views rather than create new ones from the template. When `recycle: false`
previously used views are always discarded and each item will always be assigned a new view. Recyling previously used views may improve performance in some situations but may also be "dirty" from the previously displayed item.
**Example: List Rendering without view recycling**
```html
<ul>
${repeat(x => x.friends, html<string>`
<li>${(x, c) => c.index} ${x => x}</li>
`, { positioning: true, recycle: false })}
</ul>
```
In addition to providing a template to render the items with, you can also provide an expression that evaluates to a template. This enables you to dynamically change what you are using to render the items. Each item will still be rendered with the same template, but you can use techniques from "Composing Templates" below to render a different template depending on the item itself.
### Composing templates
The `ViewTemplate` returned from the `html` tag helper has special handling when it is used inside of another template. This is done so that you can create templates and compose them into other templates.
**Example: Composing Templates**
```ts
import { FASTElement, customElement, observable, html, repeat, when } from '@microsoft/fast-element';
interface Named {
name: string;
}
class Person {
@observable name: string;
constructor(name: string) {
this.name = name;
}
}
const nameTemplate = html<Named>`
<span class="name">${x => x.name}</span>
`;
const template = html<FriendList>`
<h1>Friends</h1>
<form @submit=${x => x.addFriend()}>
<input type="text" :value=${x => x.name} @input=${(x, c) => x.handleNameInput(c.event)}>
${when(x => x.name, html`
<div>Next Name: ${nameTemplate}</div>
`)}
<div class="button-bar">
<button type="submit">Add Friend</button>
</div>
</form>
<ul>
${repeat(x => x.friends, html`
<li>${nameTemplate}</li>
`)}
</ul>
`;
@customElement({
name: 'friend-list',
template
})
export class FriendList extends FASTElement {
@observable friends: Person[] = [];
@observable name: string = '';
addFriend() {
if (!this.name) {
return;
}
this.friends.push(new Person(this.name));
this.name = '';
}
handleNameInput(event: Event) {
this.name = (event.target! as HTMLInputElement).value;
}
}
```
In the above example, we create an independent `nameTemplate` and then use it in two different places. First inside of a `when` template and then later inside of a `repeat` template.
But content composition is actually more powerful than that because you aren't limited to *static composition* of templates. You can also provide any expression that returns a template. As a result, when the `@observable` dependencies of the expression change, you can dynamically change which template is selected for rendering. If you don't want to render anything, you can also handle that by returning `null` or `undefined`. Here are a few examples of what you can do with content composition:
**Example: Dynamic Composition**
```ts
const defaultTemplate = html`...`;
const templatesByType = {
foo: html`...`,
bar: html`...`,
baz: html`...`
};
const template = html<MyElement>`
<div>${x => x.selectTemplate()}</div>
`;
@customElement({
name: 'my-element',
template
})
export class MyElement extends FASTElement {
@observable data;
selectTemplate() {
return templatesByType[this.data.type] || defaultTemplate;
}
}
```
**Example: Override Templates**
```ts
const myCustomTemplate = html`...`
@customElement({
name: 'my-derived-element',
template
})
export class MyDerivedElement extends MyElement {
selectTemplate() {
return myCustomTemplate;
}
}
```
**Example: Complex Conditional**
```ts
const dataTemplate = html`...`;
const loadingTemplate = html`...`;
const template = html<MyElement>`
<div>
${x => {
if (x.ready) {
return dataTemplate;
}
// Any logic can go here to determine which template to use.
// Which template to use will be re-evaluated whenever @observable
// properties from this method implementation change.
return loadingTemplate;
}}
</div>
`;
@customElement({
name: 'my-element',
template
})
export class MyElement extends FASTElement {
@observable ready: boolean = false;
@observable data: any = null;
connectedCallback() {
super.connectedCallback();
this.loadData();
}
async loadData() {
const response = await fetch('some/resource');
const data = await response.json();
this.data = data;
this.ready = true;
}
}
```
**Example: Per Item List Types**
```ts
const defaultTemplate = html`...`;
const templatesByType = {
foo: html`...`,
bar: html`...`,
baz: html`...`
};
const template = html<MyElement>`
<ul>
${repeat(x => x.items, html`
<li>
${(x, c) => c.parent.selectTemplate(x)}
</li>
`)}
</ul>
`;
@customElement({
name: 'my-element',
template
})
export class MyElement extends FASTElement {
@observable items: any[] = [];
selectTemplate(item) {
return templatesByType[item.type] || defaultTemplate;
}
}
```
**Example: Custom Rendering Override**
```ts
const defaultTemplate = html`...`;
const template = html<MyElement>`
<div>${x => x.selectTemplate()}</div>
`;
@customElement({
name: 'my-element',
template
})
export class MyElement extends FASTElement {
selectTemplate() {
return defaultTemplate;
}
}
export class MyCustomTemplate implements SyntheticViewTemplate {
create(): SyntheticView {
// construct your own implementation of SyntheticView
return customView;
}
}
const customTemplate = new MyCustomTemplate();
@customElement({
name: 'my-derived-element',
template
})
export class MyDerivedElement extends MyElement {
selectTemplate() {
return customTemplate;
}
}
```
:::important
When composing templates, extract the composed template to an external variable. If you define the template inline, within your method, property, or expression, then each time that is invoked, a new instance of the template will be created, rather than reusing the template. This will result in an unnecessary performance cost.
:::
**Example: The `when` Directive**
Now that we've explained how content composition works, you may find it interesting to know that `when` is actually just *syntax sugar* on top of the core composition system. Let's look at the implementation of `when` itself to see how it works:
```ts
export function when(condition, templateOrTemplateExpression) {
const getTemplate = typeof templateOrTemplateExpression === "function"
? templateOrTemplateExpression
: () => templateOrTemplateExpression;
return (source, context) =>
condition(source, context)
? getTemplate(source, context)
: null;
}
```
As you can see, all that `when` does is compose a new function that checks your condition. If it's `true,` it invokes your template provider function; if `false`, it returns `null`, indicating nothing should be rendered.
## Referential directives
Referential directives allow you to easily get references to DOM nodes in various scenarios.
### The `ref` directive
Sometimes you need a direct reference to a single DOM node from your template. This might be because you want to control the playback of a `video` element, use the drawing context of a `canvas` element, or pass an element to a 3rd party library. Whatever the reason, you can get a reference to the DOM node by using the `ref` directive.
**Example: Referencing an Element**
```ts
import { FASTElement, customElement, attr, html, ref } from '@microsoft/fast-element';
const template = html<MP4Player>`
<video ${ref('video')}>
<source src=${x => x.src} type="video/mp4">
</video>
`;
@customElement({
name: 'mp4-player',
template
})
export class MP4Player extends FASTElement {
@attr src: string;
video: HTMLVideoElement;
connectedCallback() {
super.connectedCallback();
this.video.play();
}
}
```
Place the `ref` directive on the element you want to reference and provide it with a property name to assign the reference to. Once the `connectedCallback` lifecycle event runs, your property will be set to the reference, ready for use.
:::tip
If you provide a type for your HTML template, TypeScript will type check the property name you provide to ensure that it actually exists on your element.
:::
### The `children` directive
Besides using `ref` to reference a single DOM node, you can use `children` to get references to all child nodes of a particular element.
**Example: Referencing Child Nodes**
```ts
import { FASTElement, customElement, html, children, repeat } from '@microsoft/fast-element';
const template = html<FriendList>`
<ul ${children('listItems')}>
${repeat(x => x.friends, html<string>`
<li>${x => x}</li>
`)}
</ul>
`;
@customElement({
name: 'friend-list',
template
})
export class FriendList extends FASTElement {
@observable listItems: Node[];
@observable friends: string[] = [];
connectedCallback() {
super.connectedCallback();
console.log(this.listItems);
}
}
```
In the example above, the `listItems` property will be populated with all child nodes of the `ul` element. If `listItems` is decorated with `@observable` then it will be updated dynamically as the child nodes change. Like any observable, you can optionally implement a *propertyName*Changed method to be notified when the nodes change. Additionally, you can provide an `options` object to the `children` directive to specify a customized configuration for the underlying [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver).
:::important
Like `ref`, the child nodes are not available until the `connectedCallback` lifecycle event.
:::
You can also provide a `filter` function to control which child nodes are synchronized to your property. As a convenience, we provide an `elements` filter that lets you optionally specify a selector. Taking the above example, if we wanted to ensure that our `listItems` array only included `li` elements (and not any text nodes or other potential child nodes), we could author our template like this:
**Example: Filtering Child Nodes**
```ts
import { FASTElement, customElement, html, children, repeat, elements } from '@microsoft/fast-element';
const template = html<FriendList>`
<ul ${children({ property: 'listItems', filter: elements('li') })}>
${repeat(x => x.friends, html<string>`
<li>${x => x}</li>
`)}
</ul>
`;
```
If using the `subtree` option for `children` then a `selector` is *required* in place of a `filter`. This enables more efficient collection of the desired nodes in the presence of a potential large node quantity throughout the subtree.
### The `slotted` directive
Sometimes you may want references to all nodes that are assigned to a particular slot. To accomplish this, use the `slotted` directive. (For more on slots, see [Working with Shadow DOM](./working-with-shadow-dom.md).)
```ts
import { FASTElement, customElement, html, slotted } from '@microsoft/fast-element';
const template = html<MyElement>`
<div>
<slot ${slotted('slottedNodes')}></slot>
</div>
`;
@customElement({
name: 'my-element',
template
})
export class MyElement extends FASTElement {
@observable slottedNodes: Node[];
slottedNodesChanged() {
// respond to changes in slotted node
}
}
```
Similar to the `children` directive, the `slotted` directive will populate the `slottedNodes` property with nodes assigned to the slot. If `slottedNodes` is decorated with `@observable` then it will be updated dynamically as the assigned nodes change. Like any observable, you can optionally implement a *propertyName*Changed method to be notified when the nodes change. Additionally, you can provide an `options` object to the `slotted` directive to specify a customized configuration for the underlying [assignedNodes() API call](https://developer.mozilla.org/en-US/docs/Web/API/HTMLSlotElement/assignedNodes) or specify a `filter`.
:::tip
It's best to leverage a change handler for slotted nodes rather than assuming that the nodes will be present in the `connectedCallback`.
:::
## Host directives
So far, our bindings and directives have only affected elements within the Shadow DOM of the component. However, sometimes you want to affect the host element itself, based on property state. For example, a progress component might want to write various `aria` attributes to the host, based on the progress state. In order to facilitate scenarios like this, you can use a `template` element as the root of your template, and it will represent the host element. Any attribute or directive you place on the `template` element will be applied to the host itself.
**Example: Host Directive Template**
```ts
const template = html<MyProgress>`
<template (Represents my-progress element)
role="progressbar"
$aria-valuenow={x => x.value}
$aria-valuemin={x => x.min}
$aria-valuemax={x => x.max}>
(template targeted at Shadow DOM here)
</template>
`;
```
**Example: DOM with Host Directive Output**
```html
<my-progress
min="0" (from user)
max="100" (from user)
value="50" (from user)
role="progressbar" (from host directive)
aria-valuenow="50" (from host directive)
aria-valuemin="0" (from host directive)
aria-valuemax="100" (from host directive)>
</my-progress>
```
:::tip
Using the `children` directive on the `template` element will provide you with references to all Light DOM child nodes of your custom element, regardless of if or where they are slotted.
:::

Просмотреть файл

@ -1,296 +0,0 @@
---
id: working-with-shadow-dom
title: Working with Shadow DOM
sidebar_label: Working with Shadow DOM
custom_edit_url: https://github.com/microsoft/fast/edit/master/packages/web-components/fast-element/docs/guide/working-with-shadow-dom.md
description: See how our custom elements can be composed together with standard HTML or other custom elements.
---
So far we've looked at how to define elements, how to define attributes on those elements, and how to control element rendering through declarative templates. However, we haven't yet seen how our custom elements can be composed together with standard HTML or other custom elements.
## The default slot
To enable composition, `FASTElement` leverages the Shadow DOM standard. Previously, we've seen how `FASTElement` automatically attaches a `ShadowRoot`, and when your element declares a template, it renders that template into the Shadow DOM. To enable element composition, all we need to do is make use of the standard `<slot>` element within our template.
Let's return to our original `name-tag` element example and see how we can use a `slot` to compose the person's name.
**Example: Using Slots in a `FASTElement`**
```ts
import { FASTElement, customElement, attr, html } from '@microsoft/fast-element';
const template = html<NameTag>`
<div class="header">
<h3>${x => x.greeting.toUpperCase()}</h3>
<h4>my name is</h4>
</div>
<div class="body">
<slot></slot>
</div>
<div class="footer"></div>
`;
@customElement({
name: 'name-tag',
template
})
export class NameTag extends FASTElement {
@attr greeting: string = 'Hello';
}
```
Inside the body `div`, we've placed a `slot` element. This is referred to as the "default slot" for the component because, by default, all content placed between the element's opening and closing tags will be *rendered* at this location.
To make this clear, let's look at how the `name-tag` element would be used with content and then see how the browser would composite the final rendered output.
**Example: Using `name-tag` with a Default Slot**
```html
<name-tag>John Doe<name-tag>
```
**Example: Rendered Output for `name-tag` with a Default Slot**
```html
<name-tag>
#shadow-root
<div class="header">
<h3>HELLO</h3>
<h4>my name is</h4>
</div>
<div class="body">
<slot>John Doe</slot>
</div>
<div class="footer"></div>
#shadow-root
John Doe
</name-tag>
```
The text "John Doe" exists in the "Light DOM", but it gets *projected* into the location of the `slot` within the "Shadow DOM".
:::note
If you find the terms "Light DOM" and "Shadow DOM" unintuitive, you're not alone. Another way to think of "Light DOM" is as the "Semantic DOM". It represents your semantic content model, without any concern for rendering. Another way to think of "Shadow DOM" is as the "Render DOM". It represents how your element is rendered, independent of content, or semantics.
:::
With slots at our disposal, we now unlock the full compositional model of HTML for use in our own elements. However, there's even more that slots can do.
## Named slots
In the example above, we use a single `slot` element to render *all* content placed between the start and end tags of the `name-tag`. However, we're not limited to only having a default slot. We can also have *named slots* that declare other locations to which we can render content. To demonstrate this, let's add a named slot to our `name-tag`'s template where we can display the person's avatar.
**Example: `name-tag` with a Named Slot**
```ts
import { FASTElement, customElement, attr, html } from '@microsoft/fast-element';
const template = html<NameTag>`
<div class="header">
<slot name="avatar"></slot>
<h3>${x => x.greeting.toUpperCase()}</h3>
<h4>my name is</h4>
</div>
<div class="body">
<slot></slot>
</div>
<div class="footer"></div>
`;
@customElement({
name: 'name-tag',
template
})
export class NameTag extends FASTElement {
@attr greeting: string = 'Hello';
}
```
**Example: Using `name-tag` with a Named Slot**
```html
<name-tag>
John Doe
<img slot="avatar" src="...">
</name-tag>
```
**Example: Rendered Output for `name-tag` with a Named Slot**
```html
<name-tag>
#shadow-root
<div class="header">
<slot name="avatar">
<img slot="avatar" src="...">
</slot>
<h3>HELLO</h3>
<h4>my name is</h4>
</div>
<div class="body">
<slot>John Doe</slot>
</div>
<div class="footer"></div>
#shadow-root
John Doe
<img slot="avatar" src="...">
</name-tag>
```
If an element declares named slots, its content can then leverage the `slot` *attribute* to indicate where it wants to be slotted. Anything without a `slot` attribute will be projected to the default slot. Anything with a `slot` attribute will be projected into its requested slot.
Here are a couple of quick notes on slots:
* You can have any number of content nodes project into the same slot.
* You can only place `slot` attributes on the direct content of the containing element.
```html
<name-tag>
<div> <!--Projected to default slot-->
<img slot="avatar"> <!--Slot Ignored!-->
</div>
<img slot="avatar"> <!--Projected to "avatar" slot-->
</name-tag>
```
* If you have direct content elements in the Light DOM for which there is no corresponding Shadow DOM slot, it will not be rendered.
* Ordering is maintained when projecting to slots. So, if you have two elements projecting into the same slot, they will render in the slot in the same order as they appeared in the Light DOM.
* A `slot` element can also have a `slot` attribute if the slot element is the direct child of another custom element used in your template. In this case, it means that whatever content would be projected into that slot gets re-projected into the slot of the containing element.
```html
<div class="uber-name-tag-template">
...
<name-tag>
<slot name="uber-avatar" slot="avatar">
<!--uber-name-tag's "uber-avatar" content gets projected into name-tag's "avatar" slot-->
</slot>
<slot>
<!--uber-name-tag's default content gets projected into name-tag's default slot-->
</slot>
</name-tag>
...
</div>
```
* You do not need to provide content for every declared slot. In the above example, just because the `name-tag` has an "avatar" slot does not mean we must provide content for that slot. If no content is provided for a slot, then nothing will be rendered at that location, unless the slot declared fallback content...
## Fallback content
There are several scenarios for using slots in your elements. So far, we've been showing how to use slots for content projection. However, another major use case is to enable various parts of your element's rendering to be replaced by the software using your element. To enable this, you can provide *fallback content* for any slot. This content will render if the element consumer provides no content for that slot, but if they do, their own content will override the fallback content.
**Example: Fallback Slot Content**
```html
<div class="my-slider-template">
<slot name="thumb">
<span class="thumb"></span>
</slot>
</div>
```
In the example above, the author of the `my-slider` custom element provides default HTML for the slider's "thumb", ensuring that the element will always render and function properly. However, this design leaves open the option to the component's consumer, to replace the thumb with their own HTML by simply providing HTML and assigning the proper slot name.
## Slot APIs
In addition to the declarative means of using slots described so far, the browser offers a number of slot-specific APIs you can use directly in JavaScript code. Below is a summary of what is available to you.
| API | Description |
| ------------- |-------------|
| `slotchange` | By adding an event listener for the `slotchange` event on a `slot` element, you can receive notifications any time the slotted nodes of a particular slot change. |
| `assignedNodes()` | The `slot` element provides an `assignedNodes()` method that can be called to get a list of all nodes that a particular slot currently renders. You can pass an options object with `{ flatten: true }` if you wish to also see fallback content nodes. |
| `assignedSlot` | The `assignedSlot` property is present on any element that has been projected to a slot so that you can determine where it is projected. |
:::tip
Remember that you can use the templating system's event support to respond to `slotchange` events with `<slot @slotchange=${...}></slot>`. You can also obtain a reference to any slot with the `ref` directive, making it easy to call APIs like `assignedNodes()` or manually add/remove event listeners.
:::
## Events
Events originating from within the Shadow DOM appear as if they originated from the custom element itself. In order for an event to propagate from within the Shadow DOM, it must be dispatched with the `composed: true` option. The following is a list of built-in events that compose:
* `blur`, `focus`, `focusin`, `focusout`
* `click`, `dblclick`, `mousedown`, `mouseenter`, `mousemove`, etc.
* `wheel`
* `beforeinput`, `input`
* `keydown`, `keyup`
* `compositionstart`, `compositionupdate`, `compositionend`
* `dragstart`, `drag`, `dragend`, `drop`, etc.
Here are some events which do not compose and are only visible from within the Shadow DOM itself:
* `mouseenter`, `mouseleave`
* `load`, `unload`, `abort`, `error`
* `select`
* `slotchange`
To get the fully composed event path from an event object, invoke the `composedPath()` method on the event itself. This will return an array of targets representing the path through which the event bubbled. If your custom element uses `closed` Shadow DOM mode, targets within the Shadow DOM will not be present in the composed path, and it will appear as if the custom element itself was the first target.
### Custom events
In various scenarios, it may be appropriate for a custom element to publish its own element-specific events. To do this, you can use the `$emit` helper on `FASTElement`. It's a convenience method that creates an instance of `CustomEvent` and uses the `dispatchEvent` API on `FASTElement` with the `bubbles: true` and `composed: true` options. It also ensures that the event is only emitted if the custom element is fully connected to the DOM. Here's an example:
**Example: Custom Event Dispatch**
```ts
customElement('my-input')
export class MyInput extends FASTElement {
@attr value: string = '';
valueChanged() {
this.$emit('change', this.value);
}
}
```
:::tip
When emitting custom events, ensure that your event name is always lower-case, so that your Web Components stay compatible with various front-end frameworks that attach events through DOM binding patterns (the DOM is case insensitive).
:::
## Shadow DOM configuration
In all the examples we've seen so far `FASTElement` automatically creates a Shadow Root for your element and attaches it in `open` mode. However, if desired, you can specify `closed` mode or make the element render into the Light DOM instead. These choices can be made by using the `shadowOptions` setting with your `@customElement` decorator.
**Example: Shadow DOM in Closed Mode**
```ts
@customElement({
name: 'name-tag',
template,
shadowOptions: { mode: 'closed' }
})
export class NameTag extends FASTElement {
@attr greeting: string = 'Hello';
}
```
:::tip
Avoid using `closed` mode since it affects event propagation and makes custom elements less inspectable.
:::
**Example: Render to Light DOM**
```ts
@customElement({
name: 'name-tag',
template,
shadowOptions: null
})
export class NameTag extends FASTElement {
@attr greeting: string = 'Hello';
}
```
:::important
If you choose to render to the Light DOM, you will not be able to compose the content, use slots, or leverage encapsulated styles. Light DOM rendering is not recommended for reusable components. It may have some limited use as the root component of a small app.
:::
In addition to the Shadow DOM mode, `shadowOptions` exposes all the options that can be set through the standard `attachShadow` API. This means that you can also use it to specify new options such as `delegatesFocus: true`. You only need to specify options that are different from the defaults mentioned above.
## Shadow DOM and the element lifecycle
It is during the constructor that `FASTElement` attaches the Shadow DOM for an element. The `shadowRoot` is then available directly as a property on your Custom Element, assuming that the element uses `open` mode.

Просмотреть файл

@ -32,7 +32,7 @@ export class DefaultLinkHandler implements LinkHandler {
}
public disconnect(): void {
window.removeEventListener("click", this.handler);
window.removeEventListener("click", this.handler, true);
}
private getEventInfo(event: MouseEvent): AnchorEventInfo {

Просмотреть файл

@ -29,7 +29,7 @@ npm install --save @microsoft/fast-ssr @microsoft/fast-element
`@microsoft/fast-ssr` requires a minimal DOM implementation to function. Install the shim by importing it. Doing so will ensure availability of certain DOM globals like `HTMLElement`, `Document`, etc.
```js
import "@microsoft/fast-ssr/install-dom-shim";
import "@microsoft/fast-ssr/install-dom-shim.js";
```
Alternatively, a full DOM implementation such as [`jsdom`](https://github.com/jsdom/jsdom) or [`happy-dom`](https://github.com/capricorn86/happy-dom) can be used.
@ -57,7 +57,7 @@ const { templateRenderer } = fastSSR();
### Define Custom Elements
Ensure that the custom elements used in the template you are rendering are defined in the `customElements` registry. This example defines a component directly, but you can also import any components being used:
```js
import { customElement, html, FASTElement } from "@microsoft/fast-element":
import { attr, customElement, html, FASTElement } from "@microsoft/fast-element";
@customElement({name: "my-element", template: html`<h1>${x => x.message}</h1>`})
class MyElement extends FASTElement {

Просмотреть файл

@ -148,7 +148,7 @@ module.exports = {
// TODO: Uncomment to begin displaying the doc versions labels (lines 160-167)
versions: {
current: {
label: "2.0.0",
label: "2.x",
},
"1.0.0": {
label: "1.0.0",

Просмотреть файл

@ -6,28 +6,44 @@ module.exports = {
},
{
type: "category",
label: "Building Components",
label: "Getting Started",
link: {
type: "generated-index",
},
items: [
"fast-element/getting-started",
"fast-element/defining-elements",
"fast-element/declaring-templates",
"fast-element/using-directives",
"fast-element/observables-and-state",
"fast-element/working-with-shadow-dom",
"fast-element/leveraging-css",
"fast-element/next-steps",
{
type: "doc",
id: "getting-started/quick-start",
},
{
type: "doc",
id: "getting-started/html-templates",
},
{
type: "doc",
id: "getting-started/html-directives",
},
{
type: "doc",
id: "getting-started/css-templates",
},
{
type: "doc",
id: "getting-started/fast-element",
},
],
},
{
type: "category",
label: "Apps and Experiences",
label: "Advanced Concepts",
link: {
type: "generated-index",
},
items: ["apps-and-experiences/dependency-injection"],
items: [
"advanced/working-with-custom-elements",
"advanced/component-libraries",
"advanced/dependency-injection",
],
},
{
type: "category",
@ -47,6 +63,14 @@ module.exports = {
},
],
},
{
type: "doc",
id: "integrations",
},
{
type: "doc",
id: "migration-guide",
},
{
type: "category",
label: "Community Contribution",
@ -86,7 +110,6 @@ module.exports = {
"resources/browser-support",
"resources/acknowledgements",
"resources/glossary",
"resources/cheat-sheet",
"resources/faq",
],
},

Просмотреть файл

@ -0,0 +1,9 @@
---
id: component-libraries
title: Component Libraries
sidebar_label: Component Libraries
keywords:
- component libraries
---
// TODO

Просмотреть файл

@ -0,0 +1,11 @@
---
id: dependency-injection
title: Dependency Injection
sidebar_label: Dependency Injection
custom_edit_url: https://github.com/microsoft/fast/edit/master/sites/website/src/docs/apps-and-experiences/dependency-injection.md
description: FAST introduces the concept of a dependency injection container.
keywords:
- dependency injection container
---
// TODO

Просмотреть файл

@ -0,0 +1,200 @@
---
id: working-with-custom-elements
title: Working with Custom Elements
sidebar_label: Working with Custom Elements
keywords:
- shadow
- DOM
- fouc
- flash of unstyled content
- web components
---
A good starting resource for understanding Web Components are the [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/API/Web_Components).
## FOUC & Hiding undefined elements
Custom Elements that have not been upgraded and don't have styles attached can still be rendered by the browser but they likely do not look how they are supposed to. To avoid a [flash of un-styled content (FOUC)](https://webkit.org/blog/66/the-fouc-problem/), visually hide Custom Elements if they have not been defined:
```css
:not(:defined) {
visibility: hidden;
}
```
## Updating attributes on the host element
Sometimes you want to affect the host element itself, based on property state. For example, a progress component might want to write various `aria` attributes to the host, based on the progress state. In order to facilitate scenarios like this, you can use a `template` element as the root of your template, and it will represent the host element. Any attribute or directive you place on the `template` element will be applied to the host itself. If you do not need to affect the host element you do not need to use the `template` element.
**Example: Host Directive Template**
```ts
const template = html<MyProgress>`
<template (Represents my-progress element)
role="progressbar"
$aria-valuenow={x => x.value}
$aria-valuemin={x => x.min}
$aria-valuemax={x => x.max}>
(template targeted at Shadow DOM here)
</template>
`;
```
**Example: DOM with Host Directive Output**
```html
<my-progress
min="0" (from user)
max="100" (from user)
value="50" (from user)
role="progressbar" (from host directive)
aria-valuenow="50" (from host directive)
aria-valuemin="0" (from host directive)
aria-valuemax="100" (from host directive)>
</my-progress>
```
## Adding and Removing Styles in FASTElement
`FASTElement` has the ability to add and remove styles which may be useful for dynamic updates.
**Example:**
```ts
import { attr, css, FASTElement } from '@microsoft/fast-element';
class MyComponent extends FASTElement {
private dynamicCSS = css`
:host {
color: red;
}
`;
attr({ mode: 'boolean' })
dynamicStyle!: boolean;
dynamicStyleChanged = (oldValue: boolean, newValue: boolean) => {
if (newValue) {
this.$fastController.addStyles(this.dynamicCSS);
} else {
this.$fastController.removeStyles(this.dynamicCSS);
}
}
}
```
```html
<!-- turn styles on -->
<my-component dynamicstyle></my-component>
<!-- turn styles off -->
<my-component></my-component>
```
## Adding and Removing Styles via `css` tag templates
A similar method of adding and removing styles as seen in `FASTElement` can also be done via behaviors which can allow `css` tag templates to update based on some external factors. The `HostBehavior` and `HostController` utilities can be used to create these behaviors.
`HostBehavior` provides access to a `HostController` in the `connectedCallback` method that can add or remove styles with methods `addStyles()` and `removeStyles()`.
Here is an example using [matchMedia()](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia) to change behaviors when a media query string is matched.
```ts
import { css, HostBehavior, HostController } from "@microsoft/fast-element";
/**
* A behavior to add or remove a stylesheet from an element based on a media query. The behavior ensures that
* styles are applied while the a query matches the environment and that styles are not applied if the query does
* not match the environment.
*
* @public
*/
export class MatchMediaStyleSheetBehavior extends HostBehavior {
public readonly styles: ElementStyles;
/**
* The behavior needs to operate on element instances but elements might share a behavior instance.
* To ensure proper attachment / detachment per instance, we construct a listener for
* each bind invocation and cache the listeners by element reference.
*/
private listenerCache = new WeakMap<HostController, MediaQueryListListener>();
public readonly query: MediaQueryList;
/**
* Binds the behavior to the element.
* @param controller - The host controller orchestrating this behavior.
*/
connectedCallback(controller: HostController) {
const { query } = this;
let listener = this.listenerCache.get(controller);
if (!listener) {
listener = this.constructListener(controller);
this.listenerCache.set(controller, listener);
}
// Invoke immediately to add if the query currently matches
listener.bind(query)();
query.addEventListener('change', listener);
}
/**
* Unbinds the behavior from the element.
* @param controller - The host controller orchestrating this behavior.
*/
disconnectedCallback(controller: HostController) {
const listener = this.listenerCache.get(controller);
if (listener) {
this.query.removeEventListener('change', listener);
}
}
constructor(query: MediaQueryList, styles: ElementStyles) {
this.query = query;
this.styles = styles;
}
public static with(query: MediaQueryList) {
return (styles: ElementStyles) => {
return new MatchMediaStyleSheetBehavior(query, styles);
};
}
protected constructListener(controller: HostController): MediaQueryListListener {
let attached = false;
const styles = this.styles;
return function listener(this: { matches: boolean }) {
const { matches } = this;
if (matches && !attached) {
controller.addStyles(styles);
attached = matches;
} else if (!matches && attached) {
controller.removeStyles(styles);
attached = matches;
}
};
}
public removedCallback(controller: HostController<any>): void {
controller.removeStyles(this.styles);
}
}
const darkModeStylesheetBehavior = MatchMediaStyleSheetBehavior.with(
window.matchMedia('(prefers-color-scheme: dark)'),
);
export const styles = css`
:host {
border-color: black;
}
`.withBehaviors(
darkModeStylesheetBehavior(css`
:host {
border-color: white;
}
`),
);
```

Просмотреть файл

@ -1,188 +0,0 @@
---
id: dependency-injection
title: Dependency Injection
sidebar_label: Dependency Injection
custom_edit_url: https://github.com/microsoft/fast/edit/master/sites/website/src/docs/apps-and-experiences/dependency-injection.md
description: FAST introduces the concept of a dependency injection container.
keywords:
- dependency injection container
---
An important principle of object oriented programming (OOP) is to favor a compositional approach to systems over an inheritance-based approach. In such systems, complex problems are broken down into small, single-purpose objects that collaborate with one another. However, this approach is not a panacea, and introduces its own set of challenges:
- How do we instantiate a set of collaborating objects, particularly when there is a complex arrangement of dependencies between the objects?
- How do we avoid tight coupling between an object and the implementation details of its dependencies?
- How do we manage memory and control the lifetimes of objects in such a system?
- How does our user interface, often controlled by a different engine, gain access to our composed system capabilities?
To address these challenges, FAST introduces the concept of a *dependency injection container*. A DI Container is a sub-system with the responsibilities of understanding dependency relationships, constructing objects with their dependencies, delivering dependencies to components, and managing lifetimes.
## Creating a DI Container
DI Containers can exist in a hierarchy, allowing child containers to override the dependencies of the parent, but typically there is one root container in which all the system's services are registered. In a typical FAST application, you will want this container to be associated with the `document.body` so that all UI component children can gain access to its capabilities. Here's how you would create that root container:
```ts
import { DI } from "@microsoft/fast-foundation";
const container = DI.getOrCreateDOMContainer();
```
You'll want to create and configure your root container as early as possible in your application lifecycle, typically in your application's entry point module. If you are using FAST's Design System features or its components, we've integrated them with DI, so that you can configure everything with a unified API. Instead of calling `DI.getOrCreateDOMContainer()` you can simply import the Design System Provider function and use that. Here's some code that you may have seen in other parts of our documentation, that does just that:
```ts
provideFASTDesignSystem()
.register(
fastButton()
);
```
The `register` method of the `DesignSystem` actually delegates directly to the DI container. As a result, you can register your Web Components and all your application dependencies in the same place, following the same patterns.
### Example Scenario
Once you have a container, you can use it to register and retrieve system dependencies. This is best demonstrated with a typical scenario. Imagine that we have a `NewAccountScreen` web component in our app. Users navigate to this screen to create a new account. This screen is dependent on an `AccountService`, which is able to create accounts and login new users. The `AccountService` itself is dependent on a `UserSession` and an `HTTPClient`. The `HTTPClient` is dependent on a `ServiceBaseURL` string. Let's set this up, working from the inside out.
## Creating DI Keys
The FAST DI can handle any kind of dependency, including primitive values like strings. Simply register a `ServiceBaseURL` with the container and any HTTP service that needs it can simply request it by key. This allows for a centralized configuration, making it easy to swap out in different environments. Here's how you define a strongly typed *key* that symbolizes an interface to a dependency.
```ts
import { DI } from "@microsoft/fast-foundation";
export const ServiceBaseURL = DI.createInterface<string>();
```
## Registering Dependencies
Once an interface key is defined, you'll want to register a concrete value with the root container. That can be done like so:
```ts
container.register(
Registration.instance(ServiceBaseURL, "https://www.fast.design/")
);
```
Or through the design system like this:
```ts
provideFASTDesignSystem()
.register(
Registration.instance(ServiceBaseURL, "https://www.fast.design/")
);
```
The container (and the Design System) has a `register` method that takes a variable number of `Registry` instances. The `Registration` object is a helper for creating registrations with various behaviors. Here is a summary of what's available on the `Registration` object:
- `instance` - Configures an existing object instance. Every request with the key will return this exact instance.
- `singleton` - Configures a class that is instantiated when the Key is first requested. All successive requests with the same key will return the cached instance.
- `transient` - Configures a class that is instantiated for each request with the Key. This means that each requestor gets a new instance.
- `callback` - Configures a function callback. Every time the Key is requested, the callback will be run, allowing custom code to dynamically return values.
- `cachedCallback` - Configures a function callback. The first time the Key is requested, the callback will be run to return a value. Successive calls return the cached value without invoking the callback again.
- `aliasTo` - Configures a Key to act as an alias to another Key.
:::note
Component functions like `fastButton()` actually return an instance of `Registry` that is responsible for obtaining contextual information from the DI container and using it to register the Web Component with FAST. You can follow this same pattern not only with your own components but with any configurable, shared dependency.
:::
## Constructor Injection
The above code defines a key for the `ServiceBaseURL` and configures a value for the container, but how do we get that value to the `HTTPClient`? Since `HTTPClient` is a plain class (as opposed to a web component), we'll leverage constructor injection. To do so, we declare that our constructor is dependent on `ServiceBaseURL` as follows:
```ts
export class HTTPClient {
constructor(@ServiceBaseURL serviceBaseURL: string) {}
public get<T>(url: string): Promise<T> { ... }
}
```
Notice that in TypeScript, we can use the key as a decorator for the constructor parameter. This tells the DI container that when it creates an instance of `HTTPClient` it should first resolve `ServiceBaseURL` since it will need to provide that as the first parameter when constructing `HTTPClient`.
## DI Key and Inject Patterns
Our `ServiceBaseURL` represented a simple string. So, how do we handle something more complex like `HTTPClient`? After all, we want that to be injected into the `AccountService`. A common pattern is to create an interface for `HTTPClient` and a key with the same name. TypeScript allows these to be named the same, which works to our advantage here.
```ts
export interface HTTPClient {
get<T>(url: string): Promise<T>;
}
export const HTTPClient = DI.createInterface<HTTPClient>();
export class DefaultHTTPClient implements HTTPClient {
constructor(@ServiceBaseURL serviceBaseURL: string) {}
public get<T>(url: string): Promise<T> { ... }
}
```
We could follow the same pattern for the `UserSession` as well, but let's look at a different approach. Sometimes, you may find that having the extra interface/implementation with interface key abstraction is an over-complication for your use case. You don't have to create a custom key if you just want to inject a concrete class. The `AccountService` could declare its dependencies using the generic `inject` decorator like so:
```ts
export class AccountService {
constructor(
@HttpClient http: HttpClient,
@inject(UserSession) session: UserSession
) {}
}
```
Better yet, if you are using the `tsconfig.json` setting `"emitDecoratorMetadata": true` then you can even do this:
```ts
export class AccountService {
constructor(
@HttpClient http: HttpClient,
@inject() session: UserSession
) {}
}
```
## Other DI Registration Approaches
Above, we saw that `ServiceBaseURL` had to be explicitly registered with the container. Otherwise, how else would we know what string to resolve? However, explicit registration with the container is not always needed.
### Auto-registration
In the case of `UserSession` above, the container will use `UserSession` directly as the key. However, since we have not explicitly registered `UserSession` in the container, it will attempt to use the key itself as the registration. As a result, it will instantiate `UserSession` and then register the instance. Another way of thinking of this is that auto-registered classes are treated as singletons by default.
### Default Registration
We've seen how we can explicitly register dependencies with the container and also how classes can be auto-registered. A third approach is to have the key itself define a default registration. If defined, this registration will be used if no other registration for the same key is configured with the container. We could set the `AccountService` up to work this way if we desired. Here's what that would look like:
```ts
export interface AccountService {
...
}
class DefaultAccountService implements AccountService {
constructor(
@HttpClient http: HttpClient,
@inject() session: UserSession
) {}
}
export const AccountService = DI.createInterface<AccountService>(
x => x.singleton(DefaultAccountService)
);
```
## Injecting into Web Components
Ultimately, our `NewAccountScreen` web component needs our `AccountService`. Unfortunately, web components must have parameterless constructors. To complicate matters further, the constructor is usually called by the web browser's runtime itself while parsing HTML, so our DI container is completely blocked from the process.
To address this, the FAST DI supports property injection on web components. Here's how we would declare the dependency:
```ts
export class NewAccountScreen extends FASTElement {
@AccountService accountService!: AccountService;
}
```
With the property defined as above, the `accountService` property will be available to access from the `connectedCallback` lifecycle hook forward.
Alternatively, like with constructor injection, you can also use the `inject` decorator directly with concrete types. Here's what that looks like:
```ts
export class NewAccountScreen extends FASTElement {
@inject(MyService) accountService!: MyService;
}
```

Просмотреть файл

@ -0,0 +1,119 @@
---
id: css-templates
title: CSS Templates
sidebar_label: CSS Templates
keywords:
- css
- template
- web components
---
# CSS Templates
The `@microsoft/fast-element` package offers a named export `css` which is a [tag template literal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals). It can be used to create CSS snippets which will become your web components CSS. These styles are [adoptedStylesheets](https://developer.mozilla.org/en-US/docs/Web/API/Document/adoptedStyleSheets) and associated with the `ShadowRoot`, they therefore do not affect styling in the rest of the document. To share styles between a document and web components, we suggest using [CSS properties](https://developer.mozilla.org/en-US/docs/Web/CSS/--*).
**Example:**
```ts
import { FASTElement, attr, css, html } from '@microsoft/fast-element';
const template = html`
<span>${x => x.greeting.toUpperCase()}</span>
`;
export const styles = css`
:host {
color: red;
background: var(--background-color, green);
}
`;
export class NameTag extends FASTElement {
@attr
greeting: string = 'hello';
}
NameTag.define({
name: 'name-tag',
template,
styles
});
```
HTML file:
```html
<style>
body {
--background-color: orange;
}
</style>
<name-tag></name-tag>
```
Using the `css` helper, we're able to create `ElementStyles`. We configure this with the element through the `styles` option of the decorator. Internally, `FASTElement` will leverage [Constructable Stylesheet Objects](https://wicg.github.io/construct-stylesheets/) and `ShadowRoot#adoptedStyleSheets` to efficiently re-use CSS across components. This means that even if we have 1k instances of our `name-tag` component, they will all share a single instance of the associated styles, allowing for reduced memory allocation and improved performance. Because the styles are associated with the `ShadowRoot`, they will also be encapsulated. This ensures that your styles don't affect other elements and other element styles don't affect your element.
## Composing styles
One of the nice features of `ElementStyles` is that it can be composed with other styles. Imagine that we had a CSS normalize that we wanted to use in our `name-tag` component. We could compose that into our styles like this:
**Example: Composing CSS Registries**
```ts
import { normalize } from './normalize';
const styles = css`
${normalize}
:host {
display: inline-block;
contain: content;
color: white;
background: var(--fill-color);
border-radius: var(--border-radius);
min-width: 325px;
text-align: center;
box-shadow: 0 0 calc(var(--depth) * 1px) rgba(0,0,0,.5);
}
...
`;
```
Rather than simply concatenating CSS strings, the `css` helper understands that `normalize` is `ElementStyles` and is able to re-use the same Constructable StyleSheet instance as any other component that uses `normalize`.
:::note
You can also pass a CSS `string` or a [CSSStyleSheet](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet) instance directly to the element definition, or even a mixed array of `string`, `CSSStyleSheet`, or `ElementStyles`.
:::
:::tip
Styles can be added or removed using `withBehaviors()` to the `css` tag template, or inside the `FASTElement`. check out the [advanced documentation](/docs/advanced/working-with-custom-elements.md) for details on these approaches.
:::
## Adding external styles
Styles can be added as an array, this can be useful for sharing styles between components and for bringing in styles as a string.
**Example:**
```ts
const sharedStyles = `
h2 {
font-family: sans-serif;
}
`;
NameTag.define({
name: 'name-tag',
template,
styles: [
css`
:host {
color: red;
background: var(--background-color, green);
}
`,
sharedStyles
]
})
```
:::tip
You may notice that we have used [`:host`](https://developer.mozilla.org/en-US/docs/Web/CSS/:host), this is part of standard CSS pseudo classes. Pseudo elements that may be useful for styling your custom web components include [`::slotted`](https://developer.mozilla.org/en-US/docs/Web/CSS/::slotted) and [`::part`](https://developer.mozilla.org/en-US/docs/Web/CSS/::part).
:::

Просмотреть файл

@ -0,0 +1,310 @@
---
id: fast-element
title: FASTElement
sidebar_label: FASTElement
keywords:
- FASTElement
- web components
---
# FASTElement
The `FASTElement` class can be extended from for your custom component logic.
## Attribute Bindings
Attributes are defined using the `@attr` decorator.
**Example:**
```ts
import { FASTElement, attr } from '@microsoft/fast-element';
export class MyElement extends FASTElement {
@attr
foo: string;
}
```
HTML file:
```html
<my-element foo="Hello"></my-element>
```
An `@attr` can take a configuration with the following options:
| Property | Description | Values | Default |
|-|-|-|-|
| attribute | The attribute name that is reflected in the DOM, this can be specified in cases where a different string is preferred. | `string` | The class property converted to lowercase |
| mode | If the attribute is a boolean and the mode is set to "boolean" this allows `FASTElement` to add/remove the attribute from the element in the same way that [native boolean attributes on elements work](https://developer.mozilla.org/en-US/docs/Glossary/Boolean/HTML). The "fromView" behavior only updates the property value based on changes in the DOM, but does not reflect property changes back. | `"reflect" \| "boolean" \| "fromView"` | "reflect" |
| converter | This allows the value of the attribute to be converted when moving to and from the HTML template. | [See ValueConverter Interface](#converters) | |
Example with a custom attribute name and boolean mode:
```ts
import { FASTElement, attr } from '@microsoft/fast-element';
export class MyElement extends FASTElement {
@attr({
attribute: "foo-bar",
mode: "boolean"
})
foo: boolean;
}
```
HTML file:
```html
<my-element foo-bar></my-element>
```
:::tip
As a handy feature, attribute names are automatically converted to a lower-case version in HTML, so declaring `fooBar` as an `@attr` in `FASTElement` will in HTML convert to `foobar`. We include the configuration option of `attribute` to allow re-naming, and one of the most common use cases is adding dashes, so you can have `foo-bar` as in the example above.
:::
:::important
When the `mode` is set to `boolean`, a built-in `booleanConverter` is automatically used to ensure type correctness so that the manual configuration of the converter is not needed in this common scenario.
:::
### Converters
In addition to setting the `mode`, you can also supply a custom `ValueConverter` by setting the `converter` property of the attribute configuration. The converter must implement the following interface:
```ts
interface ValueConverter {
toView(value: any): string;
fromView(value: string): any;
}
```
Here's how it works:
* When the DOM attribute value changes, the converter's `fromView` method will be called, allowing custom code to coerce the value to the proper type expected by the property.
* When the property value changes, the converter's `fromView` method will also be called, ensuring that the type is correct. After this, the `mode` will be determined. If the mode is set to `reflect` then the converter's `toView` method will be called to allow the type to be formatted before writing to the attribute using `setAttribute`.
**Example: An Attribute in Reflect Mode with Custom Conversion**
```ts
import { FASTElement, attr, ValueConverter } from '@microsoft/fast-element';
const numberConverter: ValueConverter = {
toView(value: any): string {
// convert numbers to strings
},
fromView(value: string): any {
// convert strings to numbers
}
};
export class MyCounter extends FASTElement {
@attr({ converter: numberConverter }) count: number = 0;
}
MyCounter.define({
name: 'my-counter'
});
```
A few commonly used converters are available as well:
- [booleanConverter](/docs/api/fast-element.booleanconverter)
- [nullableBooleanConverter](/docs/api/fast-element.nullablebooleanconverter)
- [nullableNumberConverter](/docs/api/fast-element.nullablenumberconverter)
## Observables
While `@attr` is used for primitive properties (string, boolean, and number), the `@observable` decorator is for all other properties. In addition to observing properties, the templating system can also observe arrays.
These decorators are a means of meta-programming the properties on your class, such that they include all the implementation needed to support state tracking, observation, and reactivity. You can access any property within your template, but if it hasn't been decorated with one of these two decorators, its value will not update after the initial render.
:::important
Properties with only a getter, that function as a computed property over other observables, should not be decorated with `@attr` or `@observable`. However, they may need to be decorated with `@volatile`, depending on the internal logic.
:::
```ts
import { FASTElement, observable } from '@microsoft/fast-element';
export class MyComponent extends FASTElement {
@observable
someBoolean = false;
@observable
valueA = 0;
@observable
valueB = 42;
}
```
A common use case for `@observable` is with slotted elements.
**Example: Track changes to elements being added/removed to a slot**
```ts
import { FASTElement, observable } from '@microsoft/fast-element';
class MyComponent extends FASTElement {
@observable
public slottedItems: HTMLElement[];
protected itemCount: number;
public slottedItemsChanged(oldValue: HTMLElement[], newValue: HTMLElement[]): void {
if (this.$fastController.isConnected) {
this.itemCount = newValue.length;
}
}
}
```
### Manually tracking observables
When `@attr` and `@observable` decorated properties are accessed during template rendering, they are tracked, allowing the engine to deeply understand the relationship between your model and view. These decorators serves to meta-program the property for you, injecting code to enable the observation system. However, if you do not like this approach, for `@observable`, you can always implement notification manually. This is especially useful if you need to do some additional logic inside a `getter` and `setter`. Here's what that would look like:
**Example: Manual Observer Implementation**
```ts
import { Observable } from '@microsoft/fast-element';
export class Person {
private _name: string;
get name() {
Observable.track(this, 'name');
return this._name;
}
set name(value: string) {
this._name = value;
Observable.notify(this, 'name');
}
}
```
## Emitting Events
In various scenarios, it may be appropriate for a custom element to publish its own element-specific events. To do this, you can use the `$emit` helper on `FASTElement`. It's a convenience method that creates an instance of `CustomEvent` and uses the `dispatchEvent` API on `FASTElement` with the `bubbles: true` and `composed: true` options. It also ensures that the event is only emitted if the custom element is fully connected to the DOM.
**Example: Custom Event Dispatch**
```ts
const template = html`
<input @change="${x => x.valueChanged()}" />
`;
export class MyInput extends FASTElement {
@attr
value: string = '';
valueChanged() {
this.$emit('change', this.value);
}
}
```
:::tip
When emitting custom events, ensure that your event name is always lower-case, so that your Web Components stay compatible with various front-end frameworks that attach events through DOM binding patterns (the DOM is case insensitive).
:::
## Defining
`FASTElement` has a `define` method, this is the means by which a custom web component is registered with the browser.
**Example:**
```ts
import { FASTElement } from '@microsoft/fast-element';
export class MyElement extends FASTElement {}
MyElement.define({
name: 'my-element'
});
```
:::important
Defining a web component creates [side effects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#import_a_module_for_its_side_effects_only). This is important to note as [tree shaking](https://developer.mozilla.org/en-US/docs/Glossary/Tree_shaking) may cause web components to be removed during transpile even if they are imported. Ensure that your build system accounts for this and does not tree shake out your web components.
:::
This configuration can take various options:
| Property | Description | Values | Default | Required |
|-|-|-|-|-|
| name | The [name of the custom element](https://developer.mozilla.org/en-US/docs/Web/API/Web_Components/Using_custom_elements#name). | | | Yes |
| template | The template to render for the custom element. Use the `html` tag template literal to create this template. | | | |
| styles | The styles to associate with the custom element. Use the `css` tag template literal to create this template. | | | |
| shadowOptions | Options controlling the creation of the custom element's shadow DOM. Provide null to render to the associated template to the light DOM instead. Example: `{ delegatesFocus: true }`, see the [ShadowRoot API](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot) for details. | | Defaults to an open shadow root. | |
| elementOptions | [Options](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define#options) controlling how the custom element is defined with the platform. | | | |
| registry | The registry to register this component in by default. | | If not provided, defaults to the global registry. | |
A typical configuration will at least include `name`, `template`, and `styles`.
**Example:**
```ts
import { attr, css, FASTElement, html } from "@microsoft/fast-element";
const template = html`<span>Hello ${x => x.name}!</span>`
const styles = css`
:host {
border: 1px solid blue;
}
`;
class HelloWorld extends FASTElement {
@attr
name: string;
}
HelloWorld.define({
name: "hello-world",
template,
styles,
});
```
**Example: Defining with a custom registry**
```ts
export const FooRegistry = Object.freeze({
prefix: 'foo',
registry: customElements,
});
HelloWorld.compose({
name: `${FooRegistry.prefix}-tab`,
template,
styles,
}).define(FooRegistry);
```
## Lifecycle
All Web Components support a series of lifecycle events that you can tap into to execute custom code at specific points in time. `FASTElement` implements several of these callbacks automatically in order to enable features of its templating engine. However, you can override them to provide your own code. Here's an example of how you would execute custom code when your element is inserted into the DOM.
**Example: Tapping into the Custom Element Lifecycle**
```ts
import { FASTElement, attr } from '@microsoft/fast-element';
export class NameTag extends FASTElement {
@attr
greeting: string = 'Hello';
greetingChanged() {
this.shadowRoot!.innerHTML = this.greeting;
}
connectedCallback() {
super.connectedCallback();
console.log('name-tag is now connected to the DOM');
}
}
```
The full list of available lifecycle callbacks is:
| Callback | Description |
| ------------- |-------------|
| constructor | Runs when the element is created or upgraded. `FASTElement` will attach the shadow DOM at this time. |
| connectedCallback | Runs when the element is inserted into the DOM. On first connect, `FASTElement` hydrates the HTML template, connects template bindings, and adds the styles. |
| disconnectedCallback | Runs when the element is removed from the DOM. `FASTElement` will remove template bindings and clean up resources at this time. |
| `<attribute>Changed(oldVal, newVal)` | Runs any time one of the element's custom attributes changes. `FASTElement` uses this to sync the attribute with its property. When the property updates, a render update is also queued, if there was a template dependency. The naming convention is to add "Changed" to the end of the attribute name, and that is the method that will get called. |
| adoptedCallback | Runs if the element was moved from its current `document` into a new `document` via a call to the `adoptNode(...)` API. |

Просмотреть файл

@ -0,0 +1,315 @@
---
id: html-directives
title: HTML Directives
sidebar_label: HTML Directives
keywords:
- ref
- slotted
- children
- when
- repeat
- directives
- web components
---
# HTML Directives
FAST provides directives to aide in solving some common scenarios.
## ref
Sometimes you need a direct reference to a single DOM node from your template. This might be because you need the rendered dimensions of the node, you want to control the playback of a `video` element, use the drawing context of a `canvas` element, or pass an element to a 3rd party library. Whatever the reason, you can get a reference to the DOM node by using the `ref` directive.
**Example: Referencing an Element**
```ts
import { FASTElement, attr, html, ref } from '@microsoft/fast-element';
const template = html<MP4Player>`
<video ${ref('video')}>
<source src=${x => x.src} type="video/mp4">
</video>
`;
export class MP4Player extends FASTElement {
@attr
src: string;
video: HTMLVideoElement;
connectedCallback() {
super.connectedCallback();
this.video.play();
}
}
MP4Player.define({
name: "mp4-player",
template
});
```
Place the `ref` directive on the element you want to reference and provide it with a property name to assign the reference to. Once the `connectedCallback` lifecycle event runs, your property will be set to the reference, ready for use.
:::tip
If you provide a type for your HTML template, TypeScript will type check the property name you provide to ensure that it actually exists on your element.
:::
## slotted
Sometimes you may want references to all nodes that are assigned to a particular slot. To accomplish this, use the `slotted` directive. (For more on slots, see [Working with Shadow DOM](/docs/advanced/working-with-custom-elements.md).)
```ts
import { FASTElement, html, slotted } from '@microsoft/fast-element';
const template = html<MyElement>`
<div>
<slot ${slotted('slottedNodes')}></slot>
</div>
`;
export class MyElement extends FASTElement {
@observable
slottedNodes: Node[];
slottedNodesChanged() {
// respond to changes in slotted node
}
}
MyElement.define({
name: 'my-element',
template
});
```
Similar to the `children` directive, the `slotted` directive will populate the `slottedNodes` property with nodes assigned to the slot. If `slottedNodes` is decorated with `@observable` then it will be updated dynamically as the assigned nodes change. Like any observable, you can optionally implement a *propertyName*Changed method to be notified when the nodes change. Additionally, you can provide an `options` object to the `slotted` directive to specify a customized configuration for the underlying [assignedNodes() API call](https://developer.mozilla.org/en-US/docs/Web/API/HTMLSlotElement/assignedNodes) or specify a `filter`.
:::tip
It's best to leverage a change handler for slotted nodes rather than assuming that the nodes will be present in the `connectedCallback`.
:::
## children
Besides using `ref` to reference a single DOM node, you can use `children` to get references to all child nodes of a particular element.
**Example: Referencing Child Nodes**
```ts
import { FASTElement, html, children, repeat } from '@microsoft/fast-element';
const template = html<FriendList>`
<ul ${children('listItems')}>
${repeat(x => x.friends, html<string>`
<li>${x => x}</li>
`)}
</ul>
`;
export class FriendList extends FASTElement {
@observable
listItems: Node[];
@observable
friends: string[] = [];
connectedCallback() {
super.connectedCallback();
console.log(this.listItems);
}
}
FriendList.define({
name: 'friend-list',
template
});
```
In the example above, the `listItems` property will be populated with all child nodes of the `ul` element. If `listItems` is decorated with `@observable` then it will be updated dynamically as the child nodes change. Like any observable, you can optionally implement a *propertyName*Changed method to be notified when the nodes change. Additionally, you can provide an `options` object to the `children` directive to specify a customized configuration for the underlying [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver).
:::important
Like `ref`, the child nodes are not available until the `connectedCallback` lifecycle event.
:::
:::tip
Using the `children` directive on the `template` element will provide you with references to all Light DOM child nodes of your custom element, regardless of if or where they are slotted.
:::
You can also provide a `filter` function to control which child nodes are synchronized to your property. As a convenience, we provide an `elements` filter that lets you optionally specify a selector. Taking the above example, if we wanted to ensure that our `listItems` array only included `li` elements (and not any text nodes or other potential child nodes), we could author our template like this:
**Example: HTML Template with Filtering Child Nodes**
```ts
const template = html<FriendList>`
<ul ${children({ property: 'listItems', filter: elements('li') })}>
${repeat(x => x.friends, html<string>`
<li>${x => x}</li>
`)}
</ul>
`;
```
If using the `subtree` option for `children` then a `selector` is *required* in place of a `filter`. This enables more efficient collection of the desired nodes in the presence of a potential large node quantity throughout the subtree.
## when
:::warning
Use sparingly, this will have impacts on performance. If you find yourself using this directive a lot in a single component, consider creating multiple components instead.
:::
The `when` directive enables you to conditionally render blocks of HTML. When you provide an expression to `when` it will render the child template into the DOM when the expression evaluates to `true` and remove the child template when it evaluates to `false` (or if it is never `true`, the rendering will be skipped entirely).
**Example: Conditional Rendering**
```ts
import { FASTElement, observable, html, when } from '@microsoft/fast-element';
const template = html<MyApp>`
<h1>My App</h1>
${when(x => !x.ready, html<MyApp>`
Loading...
`)}
`;
export class MyApp extends FASTElement {
@observable
ready: boolean = false;
@observable
data: any = null;
connectedCallback() {
super.connectedCallback();
this.loadData();
}
async loadData() {
const response = await fetch('some/resource');
const data = await response.json();
this.data = data;
this.ready = true;
}
}
MyApp.define({
name: 'my-app',
template
});
```
:::note
The `@observable` decorator creates a property that the template system can watch for changes. It is similar to `@attr`, but the property is not surfaced as an HTML attribute on the element itself.
:::
In addition to providing a template to conditionally render, you can also provide an expression that evaluates to a template. This enables you to dynamically change what you are conditionally rendering.
**Example: HTML Template with Conditional Rendering and Dynamic Template**
```ts
const template = html<MyApp>`
<h1>My App</h1>
${when(x => x.ready, x => x.dataTemplate)}
`;
```
## repeat
:::warning
Use sparingly, this will have impacts on performance. Instead, use slots and compose your component using multiple nested elements, slotted elements may provide more performant and more maintainable solutions. See the [FASTElement documentation](./fast-element.md) for details.
:::
To render a list of data, use the `repeat` directive, providing the list to render and a template to use in rendering each item.
**Example: List Rendering**
```ts
import { FASTElement, observable, html, repeat } from '@microsoft/fast-element';
const template = html<FriendList>`
<h1>Friends</h1>
<form @submit="${x => x.addFriend()}>"
<input type="text" :value="${x => x.name}" @input="${(x, c) => x.handleNameInput(c.event)}">
<button type="submit">Add Friend</button>
</form>
<ul>
${repeat(x => x.friends, html<string>`
<li>${x => x}</li>
`)}
</ul>
`;
export class FriendList extends FASTElement {
@observable
friends: string[] = [];
@observable
name: string = '';
addFriend() {
if (!this.name) {
return;
}
this.friends.push(this.name);
this.name = '';
}
handleNameInput(event: Event) {
this.name = (event.target! as HTMLInputElement).value;
}
}
FriendList.define({
name: 'friend-list',
template
})
```
Similar to event handlers, within a `repeat` block you have access to a special context object. Here is a list of the properties that are available on the context:
* `event` - The event object when inside an event handler.
* `parent` - The parent view-model when inside a `repeat` block.
* `parentContext` - The parent `ExecutionContext` when inside a `repeat` block. This is useful when repeats are nested and the inner-most repeat needs access to the root view-model.
* `index` - The index of the current item when inside a `repeat` block (opt-in).
* `length` - The length of the array when inside a `repeat` block (opt-in).
* `isEven` - True if the index of the current item is even when inside a `repeat` block (opt-in).
* `isOdd` - True if the index of the current item is odd when inside a `repeat` block (opt-in).
* `isFirst` - True if the current item is first in the array inside a `repeat` block (opt-in).
* `isInMiddle` - True if the current item is somewhere in the middle of the array inside a `repeat` block (opt-in).
* `isLast` - True if the current item is last in the array inside a `repeat` block (opt-in).
Some context properties are opt-in because they are more costly to update. So, for performance reasons, they are not available by default. To opt into the positioning properties, pass options to the repeat directive, with the setting `positioning: true`. For example, here's how we would use the `index` in our friends template from above:
**Example: HTMLTemplate with List Rendering and Item Index**
```ts
const template = html<FriendList>`
<ul>
${repeat(x => x.friends, html<string>`
<li>${(x, c) => c.index} ${x => x}</li>
`, { positioning: true })}
</ul>
`;
```
Whether or not a repeat directive re-uses item views can be controlled with the `recycle` option setting. When `recycle: true`, which is the default value, the repeat directive may reuse views rather than create new ones from the template. When `recycle: false`
previously used views are always discarded and each item will always be assigned a new view. Recyling previously used views may improve performance in some situations but may also be "dirty" from the previously displayed item.
**Example: HTML Template with List Rendering and without view recycling**
```ts
const template = html<FriendList>`
<ul>
${repeat(
x => x.friends,
html<string>`<li>${(x, c) => c.index} ${x => x}</li>`,
{ positioning: true, recycle: false }
)}
</ul>
`;
```
In addition to providing a template to render the items with, you can also provide an expression that evaluates to a template. This enables you to dynamically change what you are using to render the items.

Просмотреть файл

@ -0,0 +1,114 @@
---
id: html-templates
title: HTML Templates
sidebar_label: HTML Templates
keywords:
- html
- template
- web components
---
# HTML Templates
The `@microsoft/fast-element` package offers a named export `html` which is a [tag template literal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals). It can be used to create HTML snippets which will become your web components shadow DOM.
**Example:**
```typescript
import { html } from "@microsoft/fast-element";
export const template = html`
<template>Hello world</template>
`;
```
## Binding
When working with the `html` template, bindings allow more complex behavior than simply passing attributes. These bindings are dynamic and are denoted by the [arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions). By default attributes are assumed to be strings. We typically denote `x` for the element, and `c` for the context.
**Example:**
```ts
import { FASTElement, attr, html } from '@microsoft/fast-element';
const template = html<NameTag>`
<h3>${x => x.greeting.toUpperCase()}</h3>
`;
export class NameTag extends FASTElement {
@attr
greeting: string = 'hello';
}
NameTag.define({
name: 'name-tag',
template
});
```
When the greeting attribute is updated, so will the template.
### Booleans
Boolean bindings use the `?` symbol, use these for Boolean attributes.
**Example:**
```typescript
import { html } from "@microsoft/fast-element";
export const template = html`
<button
?disabled="${(x) => x.disabled}"
>
Button
</button>
`;
```
### Events
Events bindings use the `@` symbol. All Element events are available see the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/Element#events) for details.
**Example:**
```typescript
import { html } from "@microsoft/fast-element";
export const template = html`
<button
@click="${(x, c) => x.clickHandler(c.event)}"
>
Button
</button>
`;
```
:::important
After your event handler is executed, `preventDefault()` will be called on the event object by default. You can return `true` from your handler to opt-out of this behavior.
:::
### Properties
Property bindings use the `:` symbol.
**Example:**
```typescript
import { html } from "@microsoft/fast-element";
export const template = html`
<input
:value="${(x) => x.value}"
/>
`;
```
Some complex use cases include binding to a custom property, updating that property and observing it. To learn more about observing properties, check out the [FASTElement](./fast-element.md) document.
## Typed Templates
Your templates can be typed to the data model that they are rendering over. In TypeScript, we provide the type as part of the tag: `html<NameTag>`.
```ts
import { html } from '@microsoft/fast-element';
const template = html<NameTag>`
<div>${x => x.greeting}</div>
`;
```

Просмотреть файл

@ -0,0 +1,78 @@
---
id: quick-start
title: Quick Start
sidebar_label: Quick Start
keywords:
- quick start
- web components
---
# Quick Start
## Install the package
```bash
npm install --save @microsoft/fast-element
```
## Create a web component
A web component created using FAST is comprised of 3 parts, the HTML template, the CSS styles, and the component logic. Web components can be as simple as a button, or as complex as a full page interactive experience.
To start, let's create a simple web component that combines all the necessary parts:
```typescript
import { attr, css, FASTElement, html } from "@microsoft/fast-element";
/**
* Create an HTML template using the html tag template literal,
* this contains interpolated text content from a passed attribute
*/
const template = html`<span>Hello ${x => x.name}!</span>`
/**
* Create CSS styles using the css tag template literal
*/
const styles = css`
:host {
border: 1px solid blue;
}
span {
color: red;
}
`;
/**
* Define your component logic by creating a class that extends
* the FASTElement, note the addition of the attr decorator,
* this creates an attribute on your component which can be passed.
*/
class HelloWorld extends FASTElement {
@attr
name: string;
}
/**
* Define your custom web component for the browser, as soon as the file
* containing this logic is imported, the element "hello-world" will be
* defined in the DOM with it's html, styles, logic, and tag name.
*/
HelloWorld.define({
name: "hello-world",
template,
styles,
});
```
## Add it to your project
Now that the "hello-world" custom web component has been defined, it can be included in your HTML like so:
```html
<script type="module" src="path/to/hello-world.js"></script>
<hello-world name="Earth"></hello-world>
```
It's as simple as that!
Continue reading through the docs to understand all of the potential ways to use `@microsoft/fast-element` for your website or application.

Просмотреть файл

@ -0,0 +1,272 @@
---
id: integrations
title: Integrations
sidebar_label: Integrations
keywords:
- integrations
- angular
- asp.net
- aurelia 2
- ember
- react
- rollup
- svelte
- vite
- vue
- webpack
- web components
---
# Integrations
If your project includes one of the popular frameworks below, we've included some helpful tips.
Natively supported, no additional configuration needed:
- ASP.NET
- Ember
- Svelte
- Vite
- Vue
- Webpack
## Angular
[Angular](https://angular.io/) works well with web components,
To work with typings within Angular, check out their documentation on [custom typings](https://angular.io/guide/elements#typings-for-custom-elements).
## Aurelia 2
FAST works flawlessly with Aurelia 2, with full integration into the binding engine and component model.
### Enabling two-way bindings
Aurelia knows by default how to listen for changes in native elements. Now we need to teach it how to listen for changes in FAST elements. You can do so by [extending its templating syntax](https://docs.aurelia.io/examples/integration/ms-fast).
You can either use a [wrapper](https://www.npmjs.com/package/aurelia-fast-adapter) developed by the community or teach Aurelia manually:
### Import and register `aurelia-fast-adapter`
Start by installing the adapter
```ts
npm install aurelia-fast-adapter
```
and then simply register it from your `src/main.ts`:
```ts
// src/main.ts
import { FASTAdapter } from 'aurelia-fast-adapter';
Aurelia
.register(FASTAdapter) // add this line
// other registrations...
.start();
```
If you use FAST in its default configuration that's all you need to do. But if you changed the prefix of your components to something else, you can customize the adapter as such:
```ts
// src/main.ts
import { FASTAdapter } from 'aurelia-fast-adapter';
Aurelia
.register(FASTAdapter.customize({withPrefix: 'my-custom-prefix'}) // customized with prefix
.start();
```
Also, in case you have local components that require two-way binding, you can adjust the adapter before to register it as such:
```ts
// src/main.ts
import { FASTAdapter } from 'aurelia-fast-adapter';
// this line will tell the adapter that it must use two-way binding on the <my-custom-prefix-date-field> component and use this two-way binding on the `value` property. It's possible to add several properties at once if necessary
FASTAdapter.tags['DATE-FIELD'] = ['value'];
Aurelia
.register(FASTAdapter.customize({withPrefix: 'my-custom-prefix'})
.start();
```
Congratulations! You're now set up to use FAST and Aurelia 2!
### Manually teach Aurelia 2 about two-way binding:
If the example doesn't seem obvious, the following prerequisite reads are recommended:
* [extending Aurelia templating syntax](https://docs.aurelia.io/app-basics/extending-templating-syntax)
The following is a code example of how to teach Aurelia to work seamlessly with Microsoft FAST.
```typescript
import { AppTask, IContainer, IAttrMapper, NodeObserverLocator } from 'aurelia';
Aurelia.register(AppTask.beforeCreate(IContainer, container => {
const attrMapper = container.get(IAttrMapper);
const nodeObserverLocator = container.get(NodeObserverLocator);
attrMapper.useTwoWay((el, property) => {
switch (el.tagName) {
case 'FAST-SLIDER':
case 'FAST-TEXT-FIELD':
case 'FAST-TEXT-AREA':
return property === 'value';
case 'FAST-CHECKBOX':
case 'FAST-RADIO':
case 'FAST-RADIO-GROUP':
case 'FAST-SWITCH':
return property === 'checked';
case 'FAST-TABS':
return property === 'activeid';
default:
return false;
}
});
// Teach Aurelia what events to use to observe properties of elements.
// Because FAST components all use a single change event to notify,
// we can use a single common object
const valuePropertyConfig = { events: ['input', 'change'] };
nodeObserverLocator.useConfig({
'FAST-CHECKBOX': {
checked: valuePropertyConfig
},
'FAST-RADIO': {
checked: valuePropertyConfig
},
'FAST-RADIO-GROUP': {
value: valuePropertyConfig
},
'FAST-SLIDER': {
value: valuePropertyConfig
},
'FAST-SWITCH': {
checked: valuePropertyConfig
},
'FAST-TABS': {
activeid: valuePropertyConfig
},
'FAST-TEXT-FIELD': {
value: valuePropertyConfig
},
'FAST-TEXT-AREA': {
value: valuePropertyConfig
}
});
}))
```
## React
See the [React documentation](https://react.dev/reference/react-dom/components#custom-html-elements) on including custom elements.
## Rollup
In your `rollup.config.js` file in the root of your project folder can look something like this:
```js
import transformTaggedTemplate from 'rollup-plugin-transform-tagged-template';
import typescript from '@rollup/plugin-typescript';
import resolve from '@rollup/plugin-node-resolve';
import cleaner from 'rollup-plugin-cleaner';
import copy from 'rollup-plugin-copy';
import serve from 'rollup-plugin-serve';
import { terser } from 'rollup-plugin-terser';
export default {
input: 'src/main.ts',
output: {
file: 'dist/bundle.js',
name: 'bundle',
format: 'umd',
sourcemap: true
},
plugins: [
transformTaggedTemplate({
tagsToProcess: ['html','css'],
parserOptions: {
sourceType: "module",
plugins: [
"typescript",
[
"decorators",
{ decoratorsBeforeExport: true }
]
]
},
transformer(data) {
data = data.replace(/\s([{}()>~+=^$:!;])\s/gm, '$1');
data = data.replace(/([",[]])\s+/gm, '$1');
data = data.replace(/\s{2,}/gm, ' ');
return data.trim();
}
}),
typescript(),
resolve(),
cleaner({
targets: [
'dist'
]
}),
copy({
targets: [
{ src: 'index.html', dest: 'dist' },
]
}),
serve({
open: true,
contentBase: 'dist'
}),
terser(),
]
};
```
Let's go over a few of the plugins we would suggest using:
- `transformTaggedTemplate` minifies content within tagged templates (e.g. html and css)
- `typescript` allows us to write our source files in TypeScript.
- `resolve` allows us to import modules installed from npm, like `@microsoft/fast-element`.
## TypeScript
Here's an example starter `taconfig.json` that you can use:
```json
{
"compilerOptions": {
"pretty": true,
"target": "ES2015",
"module": "ES2015",
"moduleResolution": "node",
"importHelpers": true,
"experimentalDecorators": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"noEmitOnError": true,
"strict": true,
"outDir": "dist/build",
"rootDir": "src",
"lib": [
"dom",
"esnext"
]
},
"include": [
"src"
]
}
```
Note the inclusion of `"dom"` and `"experimentalDecorators"`.
You can learn more about `tsconfig.json` options in [the official TypeScript documentation](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html).
:::note
Do not set `useDefineForClassFields` to `true` in your `tsconfig.json` if you are using decorators (e.g. `ExperimentalDecorators`). These two features conflict at present. This will be resolved in future versions of TypeScript and FAST.
:::

Просмотреть файл

@ -8,7 +8,7 @@ keywords:
- web components
---
# Welcome to the <i>FAST</i> documentation!
# Introduction
## What is FAST?
@ -20,79 +20,14 @@ FAST is a collection of technologies built on Web Components and modern Web Stan
### How does FAST leverage Web Components?
`@microsoft/fast-element` provides a thin layer of opinion on top of Web Components, lifting the level of abstraction just enough to make it easier and faster to build components by providing:
- Attribute/property syncing
- Rich Model-View-ViewModel (MVVM) support
- Efficient template rendering/update
- Style composition
- Elements refs, template directives, and much more.
The entire `@microsoft/fast-element` library, without tree-shaking, is around 10kb minified and GZipped. It was designed for tree-shaking from the beginning, so any feature you don't use when building a component will be removed during build, allowing for highly efficient optimized payloads as small as 4.5k.
One of FAST's driving principles is "strive to adopt open, web standards-based approaches as much as possible." To that end, FAST is built directly on the W3C Web Component standards mentioned above, and does not create its own component model. This allows components built with FAST to function the same as built-in, native HTML elements. You do not need a framework to use FAST components, but you can use them in combination with any framework or library of your choice.
### How can FAST help me?
To understand how FAST can help you, let's take a look at the **FAST tech stack from top to bottom**.
At the top of the stack, FAST provides a set of Web Components: **`@microsoft/fast-components`**. This library includes a common set of components found in many websites and apps.
<div markdown="1" style={{ display:"flex", justifyContent:"center", marginBottom:"20px" }}>
```mermaid
flowchart
subgraph FAST [ ]
direction TB
FastComponents("@microsoft/fast-components\n(Component library that implements FAST Frame Design System)")
end
style FAST fill:transparent,stroke:none
style FastComponents fill:#f4f4f4,stroke:#fb356d,stroke-width:3px,color:#333
```
</div>
Example components include button, card, modal, menu, tab, tree-view, and more. `@microsoft/fast-components` provides an industry-focused design system, which we call "FAST Frame". If you're looking to integrate FAST components into an existing site or app, or if you need more control over the theme of the components, this is the option you'll want to start with.
**What if you're not just looking for a set of components to use, but you also need to implement a custom *design system*?**
This is where the second level of the stack comes into play. **`@microsoft/fast-foundation`** provides foundational building blocks that can be assembled to create new design systems and component libraries.
<div markdown="1" style={{ display:"flex", justifyContent:"center", marginBottom:"20px" }}>
```mermaid
flowchart
subgraph FAST [ ]
direction TB
FastComponents("@microsoft/fast-components\n(Component library that implements FAST Frame Design System)")
FastFoundation("@microsoft/fast-foundation\n(Building blocks for custom design systems/component libraries)")
FastComponents -.-> FastFoundation
end
style FAST fill:transparent,stroke:none
style FastComponents fill:#f4f4f4,stroke:#f4f4f4,color:#333
style FastFoundation fill:#f4f4f4,stroke:#fb356d,stroke-width:3px,color:#333
```
</div>
For example, if you wanted to implement Google's Material Design, you could do that using `@microsoft/fast-foundation`; you could also implement something like Twitter Bootstrap. `@microsoft/fast-components` assemble the building blocks of `@microsoft/fast-foundation` to create its component sets.
**What types of building blocks are we talking about though?**
Perhaps the most valuable feature of the foundation is that it provides base component behaviors and templates for the standard components. So, if you want to implement a tree-view in your design system, for example, you would use the foundation base component behavior and template, but combine it with your own styles.
The foundation components implement the state management, accessibility, keyboard navigation, and extensibility/composition model so you don't have to write that code. Additionally, foundation provides facilities for dynamic style behaviors, CSS property management, algorithmic color, RTL, high contrast, and more. You don't have to write any of that. Just assemble the building blocks and add your styles to create your own component library, expressing your own design system. So far we've talked about using existing components and creating new design systems and component libraries from existing pieces. But FAST enables you to create completely new Web Components as well.
Enter **`@microsoft/fast-element`**, the lowest level part of the FAST tech stack. This is a lightweight library for building performant, memory-efficient, standards-compliant Web Components.
<div markdown="1" style={{ display:"flex", justifyContent:"center", marginBottom:"20px" }}>
```mermaid
flowchart
subgraph FAST [ ]
direction TB
FastComponents("@microsoft/fast-components\n(Component library that implements FAST Frame Design System)")
FastFoundation("@microsoft/fast-foundation\n(Building blocks for custom design systems/component libraries)")
FastElement("@microsoft/fast-element\n(Lightweight library for building custom Web Components)")
FastComponents -.-> FastFoundation -.-> FastElement
end
style FAST fill:transparent,stroke:none
style FastComponents fill:#f4f4f4,stroke:#f4f4f4,color:#333
style FastFoundation fill:#f4f4f4,stroke:#f4f4f4,color:#333
style FastElement fill:#f4f4f4,stroke:#fb356d,stroke-width:3px,color:#333
```
</div>
`@microsoft/fast-element` provides a thin layer of opinion on top of Web Components, lifting the level of abstraction just enough to make it easier and faster to build components. `@microsoft/fast-element` helps by providing attribute/property syncing, rich Model-View-ViewModel (MVVM), efficient template rendering/update, style composition, and much more. The entire `@microsoft/fast-element` library, *without* tree-shaking, is around 10kb minified and GZipped. It was designed for tree-shaking from the beginning, so any feature you don't use when building a component will be removed during build, allowing for highly efficient optimized payloads as small as 4.5k.

Просмотреть файл

@ -0,0 +1,11 @@
---
id: migration-guide
title: Migration Guide
sidebar_label: Migration Guide
keywords:
- migrate
- migration
- web components
---
// TODO

Просмотреть файл

@ -8,7 +8,7 @@ keywords:
- browser support
---
The following browsers have native support for the Web Components features used by `fast-element`:
The following browsers have native support for the Web Components features used by `@microsoft/fast-element`:
* Microsoft Edge 79+
* Mozilla Firefox 63+
@ -21,5 +21,3 @@ The following browsers have native support for the Web Components features used
* Chrome for Android 81+
* Firefox for Android 68+
* Samsung Internet 6.2+x
For `fast-foundation` we will support the last 4 versions of all major browsers. This approach allows us to take advantage of improvements and new features being added to the web platform.

Просмотреть файл

@ -1,485 +0,0 @@
---
id: cheat-sheet
title: Cheat Sheet
sidebar_label: Cheat Sheet
custom_edit_url: https://github.com/microsoft/fast/edit/master/sites/website/src/docs/resources/cheat-sheet.md
description: A quick reference guide to the documentation.
keywords:
- cheat sheet
---
# Cheat Sheet
## Packages
### [@microsoft/fast-element](../fast-element/getting-started.md)
**A lightweight library for building performant, memory-efficient, standards-compliant Web Components.**
* Provides a thin layer of opinion on top of [Web Components](./why-web-components.md), lifting the level of abstraction just enough to make it easier and faster to [build components](#building-components).
* Use this library when you want to create new custom web components.
To install the `fast-element` library, use either `npm` or `yarn`:
```shell
npm install --save @microsoft/fast-element
```
```shell
yarn add @microsoft/fast-element
```
Within your JavaScript or TypeScript code, you can then import library APIs like this:
```ts
import { FASTElement } from "@microsoft/fast-element";
```
---
## [Building components](../fast-element/getting-started.md)
There are two main approaches to building a component:
- The first approach is for simple declarations of non-shared components.
- The second approach is for components designed to be published in [shareable libraries](#design-system).
### Setup
**Example**
To define a custom element:
```ts
import { FASTElement, customElement } from "@microsoft/fast-element";
@customElement("name-tag") // custom element being created
export class NameTag extends FASTElement {
...
}
```
With this in place, you can now use your `<name-tag>` element anywhere in HTML with the following markup:
```html
<name-tag></name-tag>
```
---
### [Attributes](../fast-element/defining-elements.md#customizing-attributes)
To add attributes to your HTML element, create properties decorated by the `@attr` decorator.
All attributes defined this way will be automatically registered with the platform so that they can be updated through the browser's native `setAttribute` API as well as the property.
You can optionally add a method with the naming convention *propertyName*Changed to your class, and this method will be called whenever your property changes, whether it changes through the property or the attribute API.
**Example**
```ts
import { FASTElement, customElement, attr } from "@microsoft/fast-element";
@customElement("name-tag")
export class NameTag extends FASTElement {
@attr greeting: string = "Hello";
// optional method
greetingChanged() {
...
}
}
```
**Example: To use a Web Component with Attributes**
```html
<name-tag greeting="Hola"></name-tag>
```
---
#### [Customizing attributes](../fast-element/defining-elements.md#customizing-attributes)
There are three modes available through the `mode` property of the attribute configuration:
| Mode | Guidance |
| :-- | :-- |
| reflect | The default mode that is used if none is specified. |
| boolean | This mode causes your attribute to function using the HTML boolean attribute behavior. |
| fromView | This mode skips reflecting the value of the property back to the HTML attribute. |
---
In addition to setting the mode, you can also supply a custom `ValueConverter` by setting the `converter` property of the attribute configuration.
The converter must implement the following interface:
```ts
interface ValueConverter {
toView(value: any): string;
fromView(value: string): any;
}
```
**Example: An Attribute in `Reflect` Mode with No Special Conversion**
```ts
import { FASTElement, customElement, attr } from '@microsoft/fast-element';
@customElement('name-tag')
export class NameTag extends FASTElement {
@attr greeting: string = 'Hello';
}
```
**Example: An Attribute in `Boolean` Mode with Boolean Conversion**
```ts
import { FASTElement, customElement, attr } from '@microsoft/fast-element';
@customElement('my-checkbox')
export class MyCheckbox extends FASTElement {
@attr({ mode: 'boolean' }) disabled: boolean = false;
}
```
**Example: An Attribute in `Reflect` Mode with Custom Conversion**
```ts
import { FASTElement, customElement, attr, ValueConverter } from '@microsoft/fast-element';
const numberConverter: ValueConverter = {
toView(value: number): string {
return String(value);
},
fromView(value: string): number {
return Number(value);
}
};
@customElement("my-counter")
export class MyCounter extends FASTElement {
@attr({
mode: "reflect",
converter: numberConverter
})
count: number = 0;
}
```
---
### [Templates](../fast-element/declaring-templates.md)
To create an HTML template for an element, import and use the `html` tagged template helper and pass the template to the `@customElement` decorator.
**Example**
```ts
import { FASTElement, customElement, attr, html } from "@microsoft/fast-element";
const template = html<NameTag>`
<div class="header">
<h3>${x => x.greeting.toUpperCase()}</h3>
<h4>my name is</h4>
</div>
<div class="body">TODO: Name Here</div>
<div class="footer"></div>
`;
@customElement({
name: "name-tag",
template
})
export class NameTag extends FASTElement {
...
}
```
---
### [Observables](../fast-element/observables-and-state.md#observable-features)
To enable binding tracking and change notification, properties must be decorated with either `@attr` or `@observable`.
Use `@attr` for primitive properties (string, bool, number) that are intended to be surfaced on your element as HTML [attributes](#attributes). Use `@observable` for all other property types on an HTMLElement and all observable properties on plain classes.
**Example**
```ts
import { Observable } from "@microsoft/fast-element";
export class Person {
@observable firstName = "";
@observable lastName = "";
get fullName() {
return `${this.firstName} ${this.LastName}`;
}
}
```
---
### [Bindings](../fast-element/declaring-templates.md#understanding-bindings)
(`x` refers to the custom-element class instance in the examples below.)
| Binding Type | Example | Notes |
| :--- | :--- | :--- |
| Content | `<p>${x => x.greeting} friend!</p>` | Creates a binding to interpolate text or child templates into element content. |
| HTML Attribute | `<a href=${x => x.aboutLink}></a>` | Creates a binding that uses the setAttribute API. Attribute bindings also support interpolation with text and other bindings. |
| HTML Boolean Attribute | `<input ?disabled=${x => x.isDisabled}>` | Creates a binding that adds or removes the attribute based on truthy/falsey values. |
| JS Property | `<input :value=${x => x.name}>` | Creates a binding that sets a JavaScript property on the element. |
| Event Handler | `<button @click=${x => x.handleClick()}>Button</button>` | Registers an event handler using addEventListener. The listener is automatically removed when the template is unbound. <br /><br />After your event handler is executed, preventDefault() will be called on the event object by default. You can return true from your handler to opt-out of this behavior.|
| HTML Element Reference | `<button ${ref("myButton")}>Button</button>` | Captures a reference to the element and assigns it to the named property on the element instance. |
| Slotted Node Capture | `<slot ${slotted("defaultSlotNodes")}></slot>` | Watches the slot for changes and synchronizes those to an array, assigned to the named property on the element instance. |
| Child Node Capture | `<div ${children("divChildren")}></div>` | Watches the element's children or changes and synchronizes those to an array, assigned to the named property on the element instance. |
---
### [Directives](../fast-element/using-directives.md)
Use the [`when`](../fast-element/using-directives.md#the-when-directive) directive to conditionally render blocks of HTML.
**Example**
```ts
import { FASTElement, customElement, observable, html, when } from "@microsoft/fast-element";
const template = html<MyApp>`
...
${when(x => !x.ready, html<MyApp>`
Loading...
`)}
`;
@customElement({
name: "my-app",
template
})
export class MyApp extends FASTElement {
@observable ready: boolean = false;
...
}
```
---
Use the [`repeat`](../fast-element/using-directives.md#the-repeat-directive) directive to render a list of data.
**Example**
```ts
import { FASTElement, customElement, observable, html, repeat } from "@microsoft/fast-element";
const template = html<FriendList>`
...
${repeat(x => x.friends, html<string>`
<li>${x => x}</li>
`)}
`;
@customElement({
name: "friend-list",
template
})
export class FriendList extends FASTElement {
@observable friends: Person[] = [];
...
}
```
---
Properties available on the context object within a `repeat` block:
| Property | Definition |
| :-- | :-- |
| event | The event object when inside an event handler. |
| parent | The parent view-model when inside a repeat block. |
| parentContext | The parent ExecutionContext when inside a repeat block. This is useful when repeats are nested and the inner-most repeat needs access to the root view-model. |
`Opt-in` properties that are not available by default:
| Opt-in Properties | Definition |
| :-- | :-- |
| index | The index of the current item when inside a repeat block. |
| length | The length of the array when inside a repeat block. |
| isEven | True if the index of the current item is even when inside a repeat block. |
| isOdd | True if the index of the current item is odd when inside a repeat block. |
| isFirst | True if the current item is first in the array inside a repeat block. |
| isInMiddle | True if the current item is somewhere in the middle of the array inside a repeat block. |
| isLast | True if the current item is last in the array inside a repeat block. |
To opt into the positioning properties, pass options to the `repeat` directive, with the setting `positioning: true`.
**Example: Rendering a list with Item Index**
```ts
<ul>
${repeat(x => x.friends, html<string>`
<li>${(x, c) => c.index} ${x => x}</li>
`, { positioning: true })}
</ul>
```
---
### [Styles](../fast-element/leveraging-css.md#basic-styles)
`FASTElement` provides a css tagged template helper that allows for the creation of `ElementStyles`.
**Example: Adding CSS to a `FASTElement`**
```ts
import { FASTElement, customElement } from "@microsoft/fast-element";
import { css, customElement, FASTElement } from "@microsoft/fast-element";
import { disabledOpacity } from "../design-tokens";
const styles = css`
:host([disabled]) {
opacity: ${disabledOpacity};
}
`;
@customElement({
styles
})
export class MyElement extends FASTElement {}
```
---
#### [Composing styles](../fast-element/leveraging-css.md#composing-styles)
`ElementStyles` can be composed with other styles.
**Example**
```ts
import { normalize } from "./normalize";
const styles = css`
${normalize}
:host {
...
}
`;
```
---
#### [Partial CSS](../fast-element/leveraging-css.md#partial-css)
Use the `cssPartial` tagged template literal to create reusable blocks of partial CSS.
**Example**
```ts
import { css, cssPartial } from "@microsoft/fast-element";
const partial = cssPartial`color: red;`;
const styles = css`:host{ ${partial} }`;
```
---
### [CSSDirective](../fast-element/leveraging-css.md#cssdirective)
To create a `CSSDirective`, import and extend `CSSDirective` from `@microsoft/fast-element`.
**Example**
```ts
import { CSSDirective } from "@microsoft/fast-element"
class RandomWidth extends CSSDirective {}
```
---
#### [createCSS method](../fast-element/leveraging-css.md#createcss)
`CSSDirective` has a `createCSS()` method that returns a string to be interpolated into an `ElementStyles`.
**Example**
```ts
class RandomWidth extends CSSDirective {
createCSS() {
return "width: var(--random-width);"
}
}
```
---
#### [createBehavior method](../fast-element/leveraging-css.md#createbehavior)
The `createBehavior()` method can be used to create a `Behavior` that is bound to the element using the `CSSDirective`.
**Example**
```ts
class RandomWidth extends CSSDirective {
private property = "20px";
createCSS() {
return `width: ${this.property};`
}
createBehavior() {
return {
bind(el) {
el.style.setProperty(this.property, Math.floor(Math.random() * 100) + "px")
}
unbind(el) {
el.style.removeProperty(this.property);
}
}
}
}
```
---
## [Contributing to FAST](../community/join.md)
**Connect with us**:
- Join our [Discord](https://discord.gg/FcSNfg4) server.
- Report bugs, request features through [Github](https://github.com/microsoft/fast/issues/new/choose).
**Unsure of what to work on?**
- Here are [good first issues](https://github.com/microsoft/fast/labels/community:good-first-issue).
---
### [Contributor guide](../community/contributor-guide.md/)
- [Machine setup](../community/contributor-guide.md#machine-setup)
- [Cloning the repository](../community/contributor-guide.md#cloning-the-repository)
- [Installing & building](../community/contributor-guide.md#installing-and-building)
- [Testing](../community/contributor-guide.md/#testing)
- [Submitting a pull request](../community/contributor-guide.md/#submitting-a-pull-request)
- [Merging a pull request](../community/contributor-guide.md#merging-a-pull-request)
---
### [Branch Guide](../community/branch-guide.md)
When contributing to the FAST repository, please follow the standards defined in this guide.
---
### [Contributing to documentation](../community/writing-documentation.md)
```shell
cd sites/website
yarn start
```
[Docusaurus](https://docusaurus.io/) will open in a browser window at `localhost:3000`.

Просмотреть файл

@ -30,10 +30,3 @@ Great question! Check out [why you might choose Web Components](./why-web-compon
### Are Web Components "done"?
The work on Web Component standards, like the rest of the web, is ongoing. New APIs continue to be designed and released. Some recent APIs include Form Associated Custom Element APIs and CSS Shadow Parts. The W3C is currently working on standards for things like Constructible Style Sheets, Declarative Shadow DOM, Scoped Element Registries, Custom Pseudo Selectors, and more.
### Why does FAST have components that are already available in HTML?
Various members of our community have wondered why FAST has components that seem to mirror native elements. Examples include `fast-anchor`, `fast-button`, and `fast-divider`. There are several reasons for this:
* CSS Encapsulation - By using Shadow DOM, FAST is able to provide a set of styles for these elements and guarantee that they will not conflict with your site or app. Your site styles will not break FAST and FAST will not break your site.
* CSS Behaviors - Custom elements enable FAST to dynamically add/remove styles based on runtime conditions, such as toggling high contrast mode. They also enable components to hook into the *design system* and respond to design changes over time.
* Enhanced Anatomies - The FAST team refers to the DOM structure of a component as its "anatomy". This is an important detail of [a component spec](https://github.com/microsoft/fast/tree/master/specs). Our research as part of [OpenUI](https://open-ui.org/) has revealed common anatomies across many design systems and component libraries that are not reflected by a single standard HTML element. We leverage this research to design the structure of our FAST components so that they are built in a way that meets real-world needs, particularly regarding composition with other components. Some basic components, such as `anchor`, benefit from an expanded anatomy, based on industry usage. Through custom elements, we are able to implement this anatomy without inflicting an HTML authoring burden on our community.

Просмотреть файл

@ -7,14 +7,6 @@ description: A glossary of terms.
keywords:
- glossary of terms
---
#### Design System
A Design System can generally be thought of as a collection of resources for interactive media that promotes brand alignment. While that definition is intentionally broad, in UI development Design Systems generally manifest as component libraries surrounded by usage guidance and design principles.
#### Design Token
A Design Token is a semantic, named variable used to describe a Design System. They often describe design concepts like typography, color, sizes, UI spacing, etc. FAST encourages checking out [the Design Tokens Community Group](https://github.com/design-tokens/community-group#design-tokens) for more information on Design Tokens themselves.
#### ES2015
The official name of the JavaScript specification released in 2015. The committee governing EcmaScript, tc39, releases a spec each year. The designation ES2015+ refers to any version of the language from 2015 onward.

Просмотреть файл

@ -32,6 +32,7 @@ Web Components are *literally*, the standard component model of the web, written
By choosing web components, you enable your customers to choose their preferred application framework or library, rather than forcing them to use any particular JavaScript framework. Building your ecosystem or plugin model around one framework often means the exclusion of an entire set of customers who build on another.
### You might choose Web Components if you want to include community members who are not proficient with JavaScript.
Most of the popular JavaScript frameworks require strong JavaScript skills and knowledge and often at least an intermediate proficiency for use. Web Components can be used without any knowledge of JavaScript whatsoever. Building around Web Components opens up opportunities for a broader range of customers with more diverse backgrounds.
### Who is using Web Components?

Просмотреть файл

@ -4,8 +4,6 @@ const { exec } = require("child_process");
const fs = require("fs-extra");
const { getPackageJsonDir } = require("../../../build/get-package-json");
const fastElement = getPackageJsonDir("@microsoft/fast-element"); // path.dirname(require.resolve("@microsoft/fast-element/package.json"));
// sites/website
const projectRoot = path.resolve(__dirname, "../");
const root = path.resolve(projectRoot, "../../");
@ -73,19 +71,7 @@ async function safeWrite(dest, content) {
}
}
async function moveMarkdownFiles(src, docsFolderDest) {
const files = findFiles(src, ".md");
for (const source of files) {
const filename = path.basename(source);
const dest = path.join(__dirname, "../docs", docsFolderDest, filename);
await safeCopy(source, dest);
}
}
async function copyArticleMarkdown() {
await moveMarkdownFiles(path.resolve(fastElement, "docs/guide"), "fast-element");
const mergeDocs = [
{
src: path.resolve(root, "CODE_OF_CONDUCT.md"),
@ -157,7 +143,7 @@ async function copyArticleMarkdown() {
src: path.resolve(
getPackageJsonDir("@microsoft/fast-element"),
"./docs/ACKNOWLEDGEMENTS.md"
), // require.resolve("@microsoft/fast-element/docs/ACKNOWLEDGEMENTS.md"),
),
dest: path.resolve(outputDir, "resources/acknowledgements.md"),
metadata: {
id: "acknowledgements",
@ -170,23 +156,6 @@ async function copyArticleMarkdown() {
keywords: ["acknowlegements"],
},
},
{
src: path.resolve(
getPackageJsonDir("@microsoft/fast-element"),
"./README.md"
),
dest: path.resolve(outputDir, "fast-element/getting-started.md"),
metadata: {
id: "getting-started",
title: "Getting Started with FAST Element",
sidebar_label: "Getting Started",
custom_edit_url:
"https://github.com/microsoft/fast/edit/master/packages/web-components/fast-element/README.md",
description:
"The fast-element library is a lightweight means to easily build performant, memory-efficient, standards-compliant Web Components.",
keywords: ["fast-element", "web components"],
},
},
];
for (const file of mergeDocs) {

Просмотреть файл

@ -126,7 +126,7 @@ If you are finding that your changes are either breaking changes or require mult
### Merging a pull request
If you are merging a pull request, be sure to use the pull request title as the commit title. The title should follow the [conventional commit guidelines](https://www.conventionalcommits.org/). It is recommended that if you are merging in pull requests regularly that you add a browser extension that will auto-correct the title for you. A few that should do this are [Refined GitHub](https://github.com/sindresorhus/refined-github) and [Squashed Merge Message](https://github.com/zachwhaley/squashed-merge-message).
If you are merging a pull request, be sure to use the pull request title as the commit title. The title should follow the [conventional commit guidelines](https://www.conventionalcommits.org/).
### Documenting breaking changes

Просмотреть файл

@ -173,7 +173,7 @@ Properties can also be set directly on an HTML element. To do so, prepend the pr
:::warning
Avoid scenarios that require you to directly set HTML, especially when the content is coming from an external source. If you must do this, you should always sanitize the HTML.
The best way to accomplish HTML sanitization is to configure [a trusted types policy](https://w3c.github.io/webappsec-trusted-types/dist/spec/) with FASTElement's template engine. FASTElement ensures that all HTML strings pass through the configured policy. Also, by leveraging the platform's trusted types capabilities, you get native enforcement of the policy through CSP headers. Here's an example of how to configure a custom policy to sanitize HTML:
The best way to accomplish HTML sanitization is to configure [a trusted types policy](https://w3c.github.io/trusted-types/dist/spec/) with FASTElement's template engine. FASTElement ensures that all HTML strings pass through the configured policy. Also, by leveraging the platform's trusted types capabilities, you get native enforcement of the policy through CSP headers. Here's an example of how to configure a custom policy to sanitize HTML:
```ts
import { DOM } from '@microsoft/fast-element';

Просмотреть файл

@ -551,9 +551,9 @@ So far, our bindings and directives have only affected elements within the Shado
const template = html<MyProgress>`
<template (Represents my-progress element)
role="progressbar"
$aria-valuenow={x => x.value}
$aria-valuemin={x => x.min}
$aria-valuemax={x => x.max}>
aria-valuenow=${x => x.value}
aria-valuemin=${x => x.min}
aria-valuemax=${x => x.max}>
(template targeted at Shadow DOM here)
</template>
`;

Просмотреть файл

@ -60,7 +60,7 @@ In the root of your project folder, you will see a `tsconfig.js` file. Replace
"forceConsistentCasingInFileNames": true,
"useDefineForClassFields": false
},
"include": ["src/*/.ts"]
"include": ["src/**/*.ts"]
}
```