Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clone-replace for alfa-dom Nodes #1523

Merged
merged 24 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
aea426c
Add node cloning functions with replace capability
rcj-siteimprove Dec 1, 2023
0253c6f
Add changeset
rcj-siteimprove Dec 1, 2023
e1b2906
Clone attributes
rcj-siteimprove Dec 4, 2023
55d195c
Clean up
rcj-siteimprove Dec 4, 2023
6299b92
Merge branch 'main' into node-cloning
rcj-siteimprove Dec 4, 2023
1ce9c2d
Extract API
github-actions[bot] Dec 4, 2023
e6bc527
Update clone functions to accept `Iterable<Element>`
rcj-siteimprove Dec 6, 2023
07dc6c9
Add interface for gathering replace predicate and elements to replace
rcj-siteimprove Dec 6, 2023
e958464
Use `Selective` and currify functions
rcj-siteimprove Dec 6, 2023
7bb415a
Fix shadow and content not being cloned and add tests for that
rcj-siteimprove Dec 6, 2023
0c0f376
Merge remote-tracking branch 'refs/remotes/origin/node-cloning' into …
rcj-siteimprove Dec 6, 2023
619df86
Extract API
github-actions[bot] Dec 7, 2023
8f385d4
Update cloning functions to discard `extraData` and add doc strings
rcj-siteimprove Dec 7, 2023
8c64e79
Update doc strings
rcj-siteimprove Dec 7, 2023
5b30d0e
Add remark about externalId to doc strings
rcj-siteimprove Dec 7, 2023
9e292b9
Extract API
github-actions[bot] Dec 7, 2023
a4352ec
Merge branch 'main' into node-cloning
rcj-siteimprove Dec 7, 2023
392632e
Update dependencies
rcj-siteimprove Dec 7, 2023
987de4d
Merge remote-tracking branch 'refs/remotes/origin/node-cloning' into …
rcj-siteimprove Dec 7, 2023
e1fbdf3
Extract shadow cloning and simplify tests
rcj-siteimprove Dec 7, 2023
cec6b70
Merge branch 'main' into node-cloning
rcj-siteimprove Dec 7, 2023
1280281
Extract API
github-actions[bot] Dec 7, 2023
adc173a
Clean up
rcj-siteimprove Dec 7, 2023
e8a2344
Merge remote-tracking branch 'refs/remotes/origin/node-cloning' into …
rcj-siteimprove Dec 7, 2023
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
7 changes: 5 additions & 2 deletions docs/review/api/alfa-dom.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,7 @@ export namespace Node {
const fullTree: Traversal;
const composedNested: Traversal;
export function clone(node: Fragment, options?: ElementReplacementOptions, device?: Device): Fragment;
export function clone(node: Shadow, options?: ElementReplacementOptions, device?: Device): Shadow;
export function clone(node: Node, options?: ElementReplacementOptions, device?: Device): Node;
// @internal (undocumented)
export function cloneNode(node: Node, options?: ElementReplacementOptions, device?: Device): Trampoline<Node>;
Expand Down Expand Up @@ -983,11 +984,11 @@ export class Shadow extends Node<"shadow"> {
// (undocumented)
get mode(): Shadow.Mode;
// (undocumented)
static of(children: Iterable<Node>, style?: Iterable<Sheet>, mode?: Shadow.Mode, externalId?: string, extraData?: any): Shadow;
static of(children: Iterable_2<Node>, style?: Iterable_2<Sheet>, mode?: Shadow.Mode, externalId?: string, extraData?: any): Shadow;
// (undocumented)
parent(options?: Node.Traversal): Option<Node>;
// (undocumented)
get style(): Iterable<Sheet>;
get style(): Iterable_2<Sheet>;
// (undocumented)
toJSON(): Shadow.JSON;
// (undocumented)
Expand All @@ -996,6 +997,8 @@ export class Shadow extends Node<"shadow"> {

// @public (undocumented)
export namespace Shadow {
// @internal (undocumented)
export function cloneShadow(options: Node.ElementReplacementOptions, device?: Device): (shadow: Shadow) => Trampoline<Shadow>;
// @internal (undocumented)
export function fromShadow(json: JSON, device?: Device): Trampoline<Shadow>;
// (undocumented)
Expand Down
16 changes: 16 additions & 0 deletions packages/alfa-dom/src/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
Document,
Element,
Fragment,
Shadow,
Slot,
Text,
Type,
Expand Down Expand Up @@ -480,6 +481,20 @@ export namespace Node {
device?: Device,
): Fragment;

/**
* Creates a new `Shadow` instance with the same value as the original and deeply referentially non-equal.
* Optionally replaces child elements based on a predicate.
*
* @remarks
* The clone will have the same `externalId` as the original.
* The clone will *not* get `extraData` from the original, instead it will be `undefined`.
*/
export function clone(
node: Shadow,
options?: ElementReplacementOptions,
device?: Device,
): Shadow;

/**
* Creates a new `Node` instance with the same value as the original and deeply referentially non-equal.
* Optionally replaces child elements based on a predicate.
Expand Down Expand Up @@ -521,6 +536,7 @@ export namespace Node {
.if(Document.isDocument, Document.cloneDocument(options, device))
.if(Type.isType, Type.cloneType)
.if(Fragment.isFragment, Fragment.cloneFragment(options, device))
.if(Shadow.isShadow, Shadow.cloneShadow(options, device))
.else(() => {
throw new Error(`Unexpected node of type: ${node.type}`);
})
Expand Down
20 changes: 7 additions & 13 deletions packages/alfa-dom/src/node/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,9 @@ export namespace Element {
element.namespace,
element.prefix,
element.name,
element.attributes.map((attribute) => Attribute.clone(attribute)),
element.attributes.map((attribute) =>
Attribute.clone(attribute, options, device),
),
Iterable.flatten(children),
element.style.map((block) => {
return Block.of(
Expand All @@ -488,21 +490,13 @@ export namespace Element {

if (element.shadow.isSome()) {
const shadow = element.shadow.get();
clonedElement._attachShadow(
Shadow.of(
shadow
.children()
.map((child) => Node.clone(child, options, device)),
shadow.style,
shadow.mode,
shadow.externalId,
shadow.extraData,
),
);
clonedElement._attachShadow(Shadow.clone(shadow, options, device));
}

if (element.content.isSome()) {
clonedElement._attachContent(Document.clone(element.content.get()));
clonedElement._attachContent(
Document.clone(element.content.get(), options, device),
);
}

return clonedElement;
Expand Down
25 changes: 25 additions & 0 deletions packages/alfa-dom/src/node/shadow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { None, Option } from "@siteimprove/alfa-option";
import { Trampoline } from "@siteimprove/alfa-trampoline";

import { Device } from "@siteimprove/alfa-device";
import { Iterable } from "@siteimprove/alfa-iterable";
import { Node } from "../node";
import { Sheet } from "../style/sheet";
import { Element } from "./element";
Expand Down Expand Up @@ -156,6 +157,30 @@ export namespace Shadow {
Shadow.of(children, json.style.map(Sheet.from), json.mode as Mode),
);
}

/**
* @internal
*/
export function cloneShadow(
options: Node.ElementReplacementOptions,
device?: Device,
): (shadow: Shadow) => Trampoline<Shadow> {
return (shadow) =>
Trampoline.traverse(shadow.children(), (child) => {
if (Element.isElement(child) && options.predicate(child)) {
return Trampoline.done(Array.from(options.newElements));
}

return Node.cloneNode(child, options, device).map((node) => [node]);
}).map((children) => {
return Shadow.of(
Iterable.flatten(children),
shadow.style,
shadow.mode,
shadow.externalId,
);
});
}
}

function indent(input: string): string {
Expand Down
6 changes: 2 additions & 4 deletions packages/alfa-dom/test/node.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,8 @@ test(`Node.clone() creates new instance with same value`, (t) => {
});

test(`Node.clone() clones shadow`, (t) => {
const div = <div>hello</div>;
const shadow = h.shadow([<div>foo</div>]);

div._attachShadow(shadow);
const div = <div>{shadow}</div>;

const clonedDiv = Node.clone(div);

Expand All @@ -101,8 +99,8 @@ test(`Node.clone() clones shadow`, (t) => {
});

test(`Node.clone() clones content`, (t) => {
const div = <div>hello</div>;
const content = h.document([<div>foo</div>]);
const div = <div>{content}</div>;

div._attachContent(content);

Expand Down