Skip to content

Commit

Permalink
feat(core): adding isEmpty, hasSlotted w/ default slot (#2604)
Browse files Browse the repository at this point in the history
* feat(core): adding isEmpty, hasSlotted w/ default slot

* chore: adding changeset
  • Loading branch information
brianferry authored Dec 4, 2023
1 parent 3d7ce5a commit ac0c376
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 23 deletions.
6 changes: 6 additions & 0 deletions .changeset/cold-cars-relate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@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)
`SlotController`: `hasSlotted` method now returns default slot if no slot name is provided. (#2603)
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) =>
(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;

#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

0 comments on commit ac0c376

Please sign in to comment.