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

feat(core): adding isEmpty, hasSlotted w/ default slot #2604

Merged
merged 7 commits into from
Dec 4, 2023
Merged
8 changes: 8 additions & 0 deletions .changeset/cold-cars-relate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@patternfly/pfe-core": minor
---

`SlotController`:

- Add `isEmpty` method to check if a slot is empty. If no slot name is provided it will check the default slot. (#2603)
- `hasSlotted` method now returns default slot if no slot name is provided. (#2603)
bennypowers marked this conversation as resolved.
Show resolved Hide resolved
59 changes: 36 additions & 23 deletions core/pfe-core/controllers/slot-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,17 @@ function isObjectConfigSpread(config: ([SlotsConfig] | (string | null)[])): conf
* for the default slot, look for direct children not assigned to a slot
*/
const isSlot =
<T extends Element = Element>(n: string | typeof SlotController.anonymous) =>
<T extends Element = Element>(n: string | typeof SlotController.default) =>
brianferry marked this conversation as resolved.
Show resolved Hide resolved
(child: Element): child is T =>
n === SlotController.anonymous ? !child.hasAttribute('slot')
n === SlotController.default ? !child.hasAttribute('slot')
: child.getAttribute('slot') === n;

export class SlotController implements ReactiveController {
public static anonymous = Symbol('anonymous slot');
public static default = Symbol('default slot');
/** @deprecated use `default` */
public static anonymous = this.default;
bennypowers marked this conversation as resolved.
Show resolved Hide resolved

#nodes = new Map<string | typeof SlotController.anonymous, Slot>();
#nodes = new Map<string | typeof SlotController.default, Slot>();

#logger: Logger;

Expand Down Expand Up @@ -105,22 +107,6 @@ export class SlotController implements ReactiveController {
this.#mo.disconnect();
}

/**
* Returns a boolean statement of whether or not any of those slots exists in the light DOM.
*
* @param {String|Array} name The slot name.
* @example this.hasSlotted("header");
*/
hasSlotted(...names: string[]): boolean {
if (!names.length) {
this.#logger.warn(`Please provide at least one slot name for which to search.`);
return false;
} else {
return names.some(x =>
this.#nodes.get(x)?.hasContent ?? false);
}
}

/**
* Given a slot name or slot names, returns elements assigned to the requested slots as an array.
* If no value is provided, it returns all children not assigned to a slot (without a slot attribute).
Expand All @@ -142,13 +128,40 @@ export class SlotController implements ReactiveController {
*/
getSlotted<T extends Element = Element>(...slotNames: string[]): T[] {
if (!slotNames.length) {
return (this.#nodes.get(SlotController.anonymous)?.elements ?? []) as T[];
return (this.#nodes.get(SlotController.default)?.elements ?? []) as T[];
} else {
return slotNames.flatMap(slotName =>
this.#nodes.get(slotName)?.elements ?? []) as T[];
}
}

/**
* Returns a boolean statement of whether or not any of those slots exists in the light DOM.
*
* @param names The slot names to check.
* @example this.hasSlotted('header');
*/
hasSlotted(...names: (string | null | undefined)[]): boolean {
const { anonymous } = SlotController;
const slotNames = Array.from(names, x => x == null ? anonymous : x);
if (!slotNames.length) {
slotNames.push(anonymous);
}
return slotNames.some(x => this.#nodes.get(x)?.hasContent ?? false);
}

/**
* Whether or not all the requested slots are empty.
*
* @param slots The slot name. If no value is provided, it returns the default slot.
* @example this.isEmpty('header', 'footer');
* @example this.isEmpty();
* @returns {Boolean}
*/
isEmpty(...names: (string | null | undefined)[]): boolean {
return !this.hasSlotted(...names);
}

#onSlotChange = (event: Event & { target: HTMLSlotElement }) => {
const slotName = event.target.name;
this.#initSlot(slotName);
Expand All @@ -168,13 +181,13 @@ export class SlotController implements ReactiveController {
this.host.requestUpdate();
};

#getChildrenForSlot<T extends Element = Element>(name: string | typeof SlotController.anonymous): T[] {
#getChildrenForSlot<T extends Element = Element>(name: string | typeof SlotController.default): T[] {
const children = Array.from(this.host.children) as T[];
return children.filter(isSlot(name));
}

#initSlot = (slotName: string | null) => {
const name = slotName || SlotController.anonymous;
const name = slotName || SlotController.default;
const elements = this.#nodes.get(name)?.slot?.assignedElements?.() ?? this.#getChildrenForSlot(name);
const selector = slotName ? `slot[name="${slotName}"]` : 'slot:not([name])';
const slot = this.host.shadowRoot?.querySelector?.<HTMLSlotElement>(selector) ?? null;
Expand Down