Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Add whitespaceFilter, an ElementsFilter that removes whitespace-only text nodes from slotted and children node collections.",
"packageName": "@microsoft/fast-element",
"email": "144495202+AKnassa@users.noreply.github.com",
"dependentChangeType": "none"
}
6 changes: 3 additions & 3 deletions packages/fast-element/SIZES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ Bundle sizes for `@microsoft/fast-element` exports.

| Export | Minified | Gzip | Brotli |
|--------|----------|------|--------|
| CDN Rollup Bundle | 78.61 KB | 23.40 KB | 20.77 KB |
| CDN Rollup Bundle | 78.73 KB | 23.44 KB | 20.80 KB |
| FASTElement (@microsoft/fast-element/fast-element.js) | 23.11 KB | 7.12 KB | 6.41 KB |
| Updates (@microsoft/fast-element/updates.js) | 473 B | 335 B | 290 B |
| Observable (@microsoft/fast-element/observable.js) | 6.75 KB | 2.51 KB | 2.23 KB |
| observable (@microsoft/fast-element/observable.js) | 6.79 KB | 2.52 KB | 2.24 KB |
| attr (@microsoft/fast-element/attr.js) | 477 B | 287 B | 244 B |
| children (@microsoft/fast-element/children.js) | 4.87 KB | 1.88 KB | 1.66 KB |
| children (@microsoft/fast-element/children.js) | 4.87 KB | 1.88 KB | 1.65 KB |
| ref (@microsoft/fast-element/ref.js) | 3.84 KB | 1.54 KB | 1.34 KB |
| slotted (@microsoft/fast-element/slotted.js) | 4.66 KB | 1.81 KB | 1.59 KB |
| volatile (@microsoft/fast-element/volatile.js) | 6.84 KB | 2.54 KB | 2.26 KB |
Expand All @@ -19,7 +19,7 @@ Bundle sizes for `@microsoft/fast-element` exports.
| repeat (@microsoft/fast-element/repeat.js) | 31.80 KB | 10.00 KB | 9.03 KB |
| css (@microsoft/fast-element/css.js) | 2.43 KB | 1.00 KB | 911 B |
| enableHydration (@microsoft/fast-element/hydration.js) | 46.53 KB | 13.91 KB | 12.49 KB |
| declarativeTemplate (@microsoft/fast-element/declarative.js) | 62.15 KB | 19.46 KB | 17.42 KB |
| declarativeTemplate (@microsoft/fast-element/declarative.js) | 62.15 KB | 19.46 KB | 17.41 KB |
| ArrayObserver (@microsoft/fast-element/arrays.js) | 12.55 KB | 4.46 KB | 4.03 KB |
| observerMap (@microsoft/fast-element/observer-map.js) | 21.96 KB | 7.73 KB | 6.97 KB |
| attributeMap (@microsoft/fast-element/attribute-map.js) | 15.31 KB | 5.41 KB | 4.88 KB |
3 changes: 3 additions & 0 deletions packages/fast-element/docs/api-report.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1280,4 +1280,7 @@ export function watch(object: any, subscriber: Subscriber | ((subject: any, args
// @public
export function when<TSource = any, TReturn = any, TParent = any>(condition: Expression<TSource, TReturn, TParent> | boolean, templateOrTemplateBinding: SyntheticViewTemplate<TSource, TParent> | Expression<TSource, SyntheticViewTemplate<TSource, TParent>, TParent>, elseTemplateOrTemplateBinding?: SyntheticViewTemplate<TSource, TParent> | Expression<TSource, SyntheticViewTemplate<TSource, TParent>, TParent>): CaptureType<TSource, TParent>;

// @public
export const whitespaceFilter: ElementsFilter;

```
1 change: 1 addition & 0 deletions packages/fast-element/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ export {
elements,
type NodeBehaviorOptions,
NodeObservationDirective,
whitespaceFilter,
} from "./templating/node-observation.js";
export { RefDirective, ref } from "./templating/ref.js";
export { RenderBehavior, RenderDirective, render } from "./templating/render.js";
Expand Down
42 changes: 41 additions & 1 deletion packages/fast-element/src/templating/children.pw.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ test.describe("The children", () => {
typeof ChildrenDirective
>;
const behavior = directive.createBehavior();
return behavior === behavior;
return behavior === directive;
});

expect(isSame).toBe(true);
Expand Down Expand Up @@ -143,6 +143,46 @@ test.describe("The children", () => {
expect(result.allMatch).toBe(true);
});

test("gathers child nodes with the whitespace filter", async ({ page }) => {
const result = await page.evaluate(async () => {
// @ts-expect-error: Client module.
const { ChildrenDirective, Observable, Fake, whitespaceFilter } =
await import("/main.js");

class Model {
nodes: any;
reference: any;
}

Observable.defineProperty(Model.prototype, "nodes");

const host = document.createElement("div");
host.appendChild(document.createTextNode("\n "));
host.appendChild(document.createElement("foo-bar"));
host.appendChild(document.createTextNode("hello"));
host.appendChild(document.createTextNode(""));

const nodeId = "r";
const targets = { [nodeId]: host };

const behavior = new ChildrenDirective({
property: "nodes",
filter: whitespaceFilter,
});
behavior.targetNodeId = nodeId;
const model = new Model();
const controller = Fake.viewController(targets, behavior);

controller.bind(model);

return model.nodes.map((x: Node) =>
x.nodeType === 1 ? (x as Element).tagName : x.nodeValue,
);
});

expect(result).toEqual(["FOO-BAR", "hello"]);
});

test("updates child nodes when they change", async ({ page }) => {
const result = await page.evaluate(async () => {
// @ts-expect-error: Client module.
Expand Down
13 changes: 13 additions & 0 deletions packages/fast-element/src/templating/node-observation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,19 @@ export const elements = (selector?: string): ElementsFilter =>
? value => value.nodeType === 1 && (value as HTMLElement).matches(selector)
: selectElements;

/**
* Filters out text nodes that contain only whitespace, such as the indentation
* between elements in markup. Note that a node containing only non-breaking
* spaces is considered whitespace and is filtered out as well.
* @public
* @example
* ```ts
* html`<slot ${slotted({ property: "nodes", filter: whitespaceFilter })}></slot>`
* ```
*/
export const whitespaceFilter: ElementsFilter = value =>
value.nodeType !== 3 || !!value.nodeValue?.trim().length;

/**
* A base class for node observation.
* @public
Expand Down
58 changes: 58 additions & 0 deletions packages/fast-element/src/templating/slotted.pw.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,64 @@ test.describe("The slotted", () => {
expect(result).toBe(true);
});

test("gathers nodes from a slot with the whitespace filter", async ({ page }) => {
await page.goto("/");

const result = await page.evaluate(async () => {
// @ts-expect-error: Client module.
const { SlottedDirective, Observable, Fake, Updates, whitespaceFilter } =
await import("/main.js");

class Model {
nodes;
reference;
}
Observable.defineProperty(Model.prototype, "nodes");

const host = document.createElement("div");
const slot = document.createElement("slot");
const shadowRoot = host.attachShadow({ mode: "open" });
const nodeId = "r";
const targets = { [nodeId]: slot };
shadowRoot.appendChild(slot);

host.appendChild(document.createTextNode("\n "));
host.appendChild(document.createElement("foo-bar"));
host.appendChild(document.createTextNode("hello"));
host.appendChild(document.createTextNode("\n"));

const behavior = new SlottedDirective({
property: "nodes",
filter: whitespaceFilter,
});
behavior.targetNodeId = nodeId;
const model = new Model();
const controller = {
source: model,
targets,
context: Fake.executionContext(),
isBound: false,
onUnbind() {},
};

behavior.bind(controller);

const describe = nodes =>
nodes.map(x => (x.nodeType === 1 ? x.tagName : x.nodeValue));
const bound = describe(model.nodes);

host.appendChild(document.createTextNode(" \t"));
host.appendChild(document.createElement("foo-bar"));

await Updates.next();

return { bound, updated: describe(model.nodes) };
});

expect(result.bound).toEqual(["FOO-BAR", "hello"]);
expect(result.updated).toEqual(["FOO-BAR", "hello", "FOO-BAR"]);
});

test("updates when slotted nodes change", async ({ page }) => {
await page.goto("/");

Expand Down
2 changes: 1 addition & 1 deletion packages/fast-element/test/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export { Compiler } from "../src/templating/compiler.js";
export { HTMLBindingDirective } from "../src/templating/html-binding-directive.js";
export { HTMLDirective, htmlDirective } from "../src/templating/html-directive.js";
export { Markup, nextId, Parser } from "../src/templating/markup.js";
export { elements } from "../src/templating/node-observation.js";
export { elements, whitespaceFilter } from "../src/templating/node-observation.js";
export {
NodeTemplate,
RenderBehavior,
Expand Down
11 changes: 11 additions & 0 deletions sites/website/src/docs/3.x/api/fast-element.md
Original file line number Diff line number Diff line change
Expand Up @@ -1741,6 +1741,17 @@ The default UpdateQueue.
Bridges between ViewBehaviors and HostBehaviors, enabling a host to control ViewBehaviors.


</td></tr>
<tr><td>

[whitespaceFilter](../fast-element.whitespacefilter/)


</td><td>

Filters out text nodes that contain only whitespace, such as the indentation between elements in markup. Note that a node containing only non-breaking spaces is considered whitespace and is filtered out as well.


</td></tr>
</tbody></table>

Expand Down
31 changes: 31 additions & 0 deletions sites/website/src/docs/3.x/api/fast-element.whitespacefilter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
id: "fast-element.whitespacefilter"
title: "whitespaceFilter variable"
layout: 3x-api
eleventyNavigation:
key: "api3xfast-element.whitespacefilter"
parent: "api3xfast-element"
title: "whitespaceFilter variable"
navigationOptions:
activeKey: "api3xfast-element.whitespacefilter"
---
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[@microsoft/fast-element](../fast-element/index.html) &gt; [whitespaceFilter](../fast-element.whitespacefilter/index.html)

## whitespaceFilter variable

Filters out text nodes that contain only whitespace, such as the indentation between elements in markup. Note that a node containing only non-breaking spaces is considered whitespace and is filtered out as well.

**Signature:**

```typescript
whitespaceFilter: ElementsFilter
```

## Example


```ts
html`<slot ${slotted({ property: "nodes", filter: whitespaceFilter })}></slot>`
```
15 changes: 15 additions & 0 deletions sites/website/src/docs/3.x/getting-started/html-directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,21 @@ const template = html<SlottedExample>`
`;
```

Because a `<slot>` is assigned text nodes as well as elements, the indentation between elements arrives as whitespace-only text nodes. Use `whitespaceFilter` to drop those while keeping elements and meaningful text:

```ts
import { html, slotted, whitespaceFilter } from "@microsoft/fast-element";

const template = html<SlottedExample>`
<template>
<slot ${slotted({
property: "slottedNodes",
filter: whitespaceFilter
})}></slot>
</template>
`;
```

#### Flattening

The configuration object also accepts a `flatten` option. By default, `slotted()` references the nodes directly assigned to the slot. Setting `flatten: true` references the slot's flattened assigned nodes instead, resolving any nested slots to their distributed content and falling back to the slot's default content when nothing is assigned. The option is passed through to the underlying [`assignedNodes()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLSlotElement/assignedNodes) call.
Expand Down
6 changes: 3 additions & 3 deletions sites/website/src/docs/3.x/resources/export-sizes.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ Bundle sizes for `@microsoft/fast-element` exports.

| Export | Minified | Gzip | Brotli |
|--------|----------|------|--------|
| CDN Rollup Bundle | 78.61 KB | 23.40 KB | 20.77 KB |
| CDN Rollup Bundle | 78.73 KB | 23.44 KB | 20.80 KB |
| FASTElement (@microsoft/fast-element/fast-element.js) | 23.11 KB | 7.12 KB | 6.41 KB |
| Updates (@microsoft/fast-element/updates.js) | 473 B | 335 B | 290 B |
| Observable (@microsoft/fast-element/observable.js) | 6.75 KB | 2.51 KB | 2.23 KB |
| observable (@microsoft/fast-element/observable.js) | 6.79 KB | 2.52 KB | 2.24 KB |
| attr (@microsoft/fast-element/attr.js) | 477 B | 287 B | 244 B |
| children (@microsoft/fast-element/children.js) | 4.87 KB | 1.88 KB | 1.66 KB |
| children (@microsoft/fast-element/children.js) | 4.87 KB | 1.88 KB | 1.65 KB |
| ref (@microsoft/fast-element/ref.js) | 3.84 KB | 1.54 KB | 1.34 KB |
| slotted (@microsoft/fast-element/slotted.js) | 4.66 KB | 1.81 KB | 1.59 KB |
| volatile (@microsoft/fast-element/volatile.js) | 6.84 KB | 2.54 KB | 2.26 KB |
Expand All @@ -34,7 +34,7 @@ Bundle sizes for `@microsoft/fast-element` exports.
| repeat (@microsoft/fast-element/repeat.js) | 31.80 KB | 10.00 KB | 9.03 KB |
| css (@microsoft/fast-element/css.js) | 2.43 KB | 1.00 KB | 911 B |
| enableHydration (@microsoft/fast-element/hydration.js) | 46.53 KB | 13.91 KB | 12.49 KB |
| declarativeTemplate (@microsoft/fast-element/declarative.js) | 62.15 KB | 19.46 KB | 17.42 KB |
| declarativeTemplate (@microsoft/fast-element/declarative.js) | 62.15 KB | 19.46 KB | 17.41 KB |
| ArrayObserver (@microsoft/fast-element/arrays.js) | 12.55 KB | 4.46 KB | 4.03 KB |
| observerMap (@microsoft/fast-element/observer-map.js) | 21.96 KB | 7.73 KB | 6.97 KB |
| attributeMap (@microsoft/fast-element/attribute-map.js) | 15.31 KB | 5.41 KB | 4.88 KB |