Skip to content

Commit

Permalink
Merge pull request #3000 from jd239/feat/slot-changes-after-inital-re…
Browse files Browse the repository at this point in the history
…nder

[ic-footer]: Slot updates after initial render
  • Loading branch information
GCHQ-Developer-847 authored Jan 14, 2025
2 parents 79454bb + 52926b7 commit 305fe69
Show file tree
Hide file tree
Showing 22 changed files with 521 additions and 157 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@ import {
State,
h,
Method,
forceUpdate,
Watch,
} from "@stencil/core";
import {
onComponentRequiredPropUndefined,
isSlotUsed,
getThemeFromContext,
removeDisabledFalse,
checkSlotInChildMutations,
renderDynamicChildSlots,
} from "../../utils/helpers";
import {
IcTheme,
Expand Down Expand Up @@ -128,7 +127,9 @@ export class Card {
);
this.updateTheme();

this.hostMutationObserver = new MutationObserver(this.hostMutationCallback);
this.hostMutationObserver = new MutationObserver((mutationList) =>
renderDynamicChildSlots(mutationList, "image", this)
);
this.hostMutationObserver.observe(this.el, {
childList: true,
});
Expand Down Expand Up @@ -159,18 +160,6 @@ export class Card {
}
}

private hostMutationCallback = (mutationList: MutationRecord[]): void => {
if (
mutationList.some(({ type, addedNodes, removedNodes }) =>
type === "childList"
? checkSlotInChildMutations(addedNodes, removedNodes, "image")
: false
)
) {
forceUpdate(this);
}
};

private parentFocussed = (): void => {
this.isFocussed = true;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@ import {
Watch,
State,
Listen,
forceUpdate,
Method,
} from "@stencil/core";
import { IcSizes, IcThemeForegroundNoDefault } from "../../utils/types";
import {
isSlotUsed,
onComponentRequiredPropUndefined,
checkSlotInChildMutations,
removeDisabledFalse,
renderDynamicChildSlots,
} from "../../utils/helpers";
import arrowDropdown from "../../assets/arrow-dropdown.svg";

Expand Down Expand Up @@ -162,7 +161,9 @@ export class TreeItem {
"Tree item"
);

this.hostMutationObserver = new MutationObserver(this.hostMutationCallback);
this.hostMutationObserver = new MutationObserver((mutationList) =>
renderDynamicChildSlots(mutationList, "icon", this)
);
this.hostMutationObserver.observe(this.el, {
childList: true,
});
Expand Down Expand Up @@ -339,18 +340,6 @@ export class TreeItem {
return !!this.routerSlot;
}

private hostMutationCallback = (mutationList: MutationRecord[]): void => {
if (
mutationList.some(({ type, addedNodes, removedNodes }) =>
type === "childList"
? checkSlotInChildMutations(addedNodes, removedNodes, "icon")
: false
)
) {
forceUpdate(this);
}
};

private handleDisplayTooltip = (display: boolean) => {
const typographyEl: HTMLIcTypographyElement =
this.el.shadowRoot.querySelector(".tree-item-label");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ import {
Watch,
State,
Listen,
forceUpdate,
} from "@stencil/core";
import { IcSizes, IcThemeForegroundNoDefault } from "../../utils/types";
import {
isPropDefined,
isSlotUsed,
checkSlotInChildMutations,
renderDynamicChildSlots,
} from "../../utils/helpers";

let treeViewIds = 0;
Expand Down Expand Up @@ -100,7 +99,9 @@ export class TreeView {

this.addSlotChangeListener();

this.hostMutationObserver = new MutationObserver(this.hostMutationCallback);
this.hostMutationObserver = new MutationObserver((mutationList) =>
renderDynamicChildSlots(mutationList, "icon", this)
);
this.hostMutationObserver.observe(this.el, {
childList: true,
});
Expand Down Expand Up @@ -268,18 +269,6 @@ export class TreeView {
}
};

private hostMutationCallback = (mutationList: MutationRecord[]): void => {
if (
mutationList.some(({ type, addedNodes, removedNodes }) =>
type === "childList"
? checkSlotInChildMutations(addedNodes, removedNodes, "icon")
: false
)
) {
forceUpdate(this);
}
};

private isHeadingDefined = () =>
isPropDefined(this.heading) && this.heading !== null;

Expand Down
30 changes: 27 additions & 3 deletions packages/canary-web-components/src/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
IcThemeForeground,
IcThemeForegroundEnum,
} from "./types"; // Using @ukic/web-components/dist/types/utils/types does not work so duplicated constants into canary package
import { EventEmitter } from "@stencil/core";
import { EventEmitter, forceUpdate } from "@stencil/core";
import { IcDataTableDataType } from "../interface";

const DARK_MODE_THRESHOLD = 133.3505;
Expand Down Expand Up @@ -650,10 +650,14 @@ export const capitalize = (text: string): string => {
export const checkSlotInChildMutations = (
addedNodes: NodeList,
removedNodes: NodeList,
slotName: string
slotName: string | string[]
): boolean => {
const hasSlot = (nodeList: NodeList) =>
Array.from(nodeList).some((node) => (node as Element).slot === slotName);
Array.from(nodeList).some((node) =>
Array.isArray(slotName)
? slotName.some((name) => (node as Element).slot === name)
: (node as Element).slot === slotName
);
return hasSlot(addedNodes) || hasSlot(removedNodes);
};

Expand All @@ -679,3 +683,23 @@ export const addDataToPosition = (
}
return newData;
};

export const hasDynamicChildSlots = (
mutationList: MutationRecord[],
slotNames: string | string[]
) =>
mutationList.some(({ type, addedNodes, removedNodes }) =>
type === "childList"
? checkSlotInChildMutations(addedNodes, removedNodes, slotNames)
: false
);

export const renderDynamicChildSlots = (
mutationList: MutationRecord[],
slotNames: string | string[],
ref: any
): void => {
if (hasDynamicChildSlots(mutationList, slotNames)) {
forceUpdate(ref);
}
};
19 changes: 4 additions & 15 deletions packages/web-components/src/components/ic-alert/ic-alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ import {
Listen,
Prop,
h,
forceUpdate,
} from "@stencil/core";
import closeIcon from "../../assets/close-icon.svg";
import { isSlotUsed, checkSlotInChildMutations } from "../../utils/helpers";
import { isSlotUsed, renderDynamicChildSlots } from "../../utils/helpers";
import { IcThemeForegroundEnum, IcStatusVariants } from "../../utils/types";
import { VARIANT_ICONS } from "../../utils/constants";

Expand Down Expand Up @@ -85,7 +84,9 @@ export class Alert {
componentDidLoad(): void {
this.alertTitleShouldWrap();

this.hostMutationObserver = new MutationObserver(this.hostMutationCallback);
this.hostMutationObserver = new MutationObserver((mutationList) =>
renderDynamicChildSlots(mutationList, "action", this)
);
this.hostMutationObserver.observe(this.el, {
childList: true,
});
Expand All @@ -107,18 +108,6 @@ export class Alert {
if (titleHeight > 24) this.alertTitleWrap = true;
}

private hostMutationCallback = (mutationList: MutationRecord[]): void => {
if (
mutationList.some(({ type, addedNodes, removedNodes }) =>
type === "childList"
? checkSlotInChildMutations(addedNodes, removedNodes, "action")
: false
)
) {
forceUpdate(this);
}
};

render() {
const {
variant,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`ic-alert component should test rendering an action after initial render 1`] = `
<ic-alert class="dark" heading="Test heading" role="alert">
<mock:shadow-root>
<div class="container container-neutral">
<div class="alert-icon-container">
<div class="divider divider-neutral"></div>
<span class="alert-icon icon-neutral svg-container">
svg
</span>
</div>
<div class="alert-content">
<div class="alert-message">
<ic-typography class="alert-title" variant="subtitle-large">
<p>
Test heading
</p>
</ic-typography>
<slot name="message">
<ic-typography variant="body"></ic-typography>
</slot>
</div>
<div class="alert-action-container">
<slot name="action"></slot>
</div>
</div>
<div class="dismiss-icon-container"></div>
</div>
</mock:shadow-root>
<button slot="action"></button>
</ic-alert>
`;
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
import { newSpecPage } from "@stencil/core/testing";
import { Alert } from "../../ic-alert";
import {
mockHasDynamicChildSlots,
mockMutationObserverImplementation,
MockMutationRecord,
mockRenderDynamicChildSlots,
} from "../../../../testspec.setup";

describe("ic-alert component", () => {
afterAll(() => {
jest.restoreAllMocks();
});

afterEach(() => {
jest.clearAllMocks();
});

it("should render with a heading when supplied", async () => {
const page = await newSpecPage({
components: [Alert],
Expand Down Expand Up @@ -323,15 +337,40 @@ describe("ic-alert component", () => {
html: `<ic-alert heading="Test heading"></ic-alert>`,
});

const component = page.rootInstance;
const host = page.root;

const observerInstance =
mockMutationObserverImplementation.mock.results[0].value;

const action = document.createElement("button");
action.setAttribute("slot", "action");

page.rootInstance.hostMutationCallback([
observerInstance.observe(host, { childList: true });

host.appendChild(action);

const mockMutationRecord: MockMutationRecord[] = [
{
type: "childList",
addedNodes: [action],
removedNodes: [],
target: host,
},
]);
];

observerInstance.trigger(mockMutationRecord);

await page.waitForChanges();

expect(mockRenderDynamicChildSlots).toHaveBeenCalledTimes(1);
expect(mockRenderDynamicChildSlots).toHaveBeenCalledWith(
mockMutationRecord,
"action",
component
);

expect(mockHasDynamicChildSlots).toHaveBeenCalledTimes(1);

expect(page.root).toMatchSnapshot();
});
});
Loading

0 comments on commit 305fe69

Please sign in to comment.