Skip to content

Commit 0b3dffa

Browse files
committed
fix(core): remove need for static decorator for ssr slot hints
1 parent 49c3202 commit 0b3dffa

File tree

4 files changed

+105
-42
lines changed

4 files changed

+105
-42
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import type { ReactiveElement } from 'lit';
2+
import {
3+
type SlotControllerArgs,
4+
type SlotControllerPublicAPI,
5+
} from './slot-controller.js';
6+
7+
export class SlotController implements SlotControllerPublicAPI {
8+
public static default = Symbol('default slot') satisfies symbol as symbol;
9+
10+
/** @deprecated use `default` */
11+
public static anonymous: symbol = this.default;
12+
13+
static property = 'ssrHintHasSlotted' as const;
14+
15+
static attribute = 'ssr-hint-has-slotted' as const;
16+
17+
static anonymousAttribute = 'ssr-hint-has-slotted-anonymous' as const;
18+
19+
constructor(public host: ReactiveElement, ..._: SlotControllerArgs) {
20+
host.addController(this);
21+
}
22+
23+
hostConnected?(): Promise<void>;
24+
25+
private fromAttribute(slots: string | null) {
26+
return (slots ?? '')
27+
.split(/[, ]/)
28+
.map(x => x.trim());
29+
}
30+
31+
getSlotted<T extends Element = Element>(..._: string[]): T[] {
32+
return [];
33+
}
34+
35+
hasSlotted(...names: (string | null)[]): boolean {
36+
const attr = this.host.getAttribute(SlotController.attribute);
37+
const anon = this.host.hasAttribute(SlotController.anonymousAttribute);
38+
const hints = new Set(this.fromAttribute(attr));
39+
return names.every(x => x === null ? anon : hints.has(x));
40+
}
41+
42+
isEmpty(...names: (string | null)[]): boolean {
43+
return !this.hasSlotted(...names);
44+
}
45+
}

core/pfe-core/controllers/slot-controller.ts

+55-14
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ interface NamedSlot extends AnonymousSlot {
1313

1414
export type Slot = NamedSlot | AnonymousSlot;
1515

16+
export type SlotName = string | null;
17+
1618
export interface SlotsConfig {
17-
slots: (string | null)[];
19+
slots: SlotName[];
1820
/**
1921
* Object mapping new slot name keys to deprecated slot name values
2022
* @example `pf-modal--header` is deprecated in favour of `header`
@@ -30,11 +32,9 @@ export interface SlotsConfig {
3032
deprecations?: Record<string, string>;
3133
}
3234

33-
export type SlotControllerArgs = [SlotsConfig] | (string | null)[];
35+
export type SlotControllerArgs = [SlotsConfig] | SlotName[];
3436

35-
function isObjectSpread(
36-
config: ([SlotsConfig] | (string | null)[]),
37-
): config is [SlotsConfig] {
37+
export function isObjectSpread(config: SlotControllerArgs): config is [SlotsConfig] {
3838
return config.length === 1 && typeof config[0] === 'object' && config[0] !== null;
3939
}
4040

@@ -49,14 +49,61 @@ const isSlot =
4949
n === SlotController.default ? !child.hasAttribute('slot')
5050
: child.getAttribute('slot') === n;
5151

52-
export class SlotController implements ReactiveController {
52+
export declare class SlotControllerPublicAPI implements ReactiveController {
53+
static default: symbol;
54+
55+
public host: ReactiveElement;
56+
57+
constructor(host: ReactiveElement, ...args: SlotControllerArgs);
58+
59+
hostConnected?(): Promise<void>;
60+
61+
hostDisconnected?(): void;
62+
63+
hostUpdated?(): void;
64+
65+
/**
66+
* Given a slot name or slot names, returns elements assigned to the requested slots as an array.
67+
* If no value is provided, it returns all children not assigned to a slot (without a slot attribute).
68+
* @param slotNames slots to query
69+
* @example Get header-slotted elements
70+
* ```js
71+
* this.getSlotted('header')
72+
* ```
73+
* @example Get header- and footer-slotted elements
74+
* ```js
75+
* this.getSlotted('header', 'footer')
76+
* ```
77+
* @example Get default-slotted elements
78+
* ```js
79+
* this.getSlotted();
80+
* ```
81+
*/
82+
getSlotted<T extends Element = Element>(...slotNames: string[]): T[];
83+
84+
/**
85+
* Returns a boolean statement of whether or not any of those slots exists in the light DOM.
86+
* @param names The slot names to check.
87+
* @example this.hasSlotted('header');
88+
*/
89+
hasSlotted(...names: (string | null | undefined)[]): boolean;
90+
91+
/**
92+
* Whether or not all the requested slots are empty.
93+
* @param names The slot names to query. If no value is provided, it returns the default slot.
94+
* @example this.isEmpty('header', 'footer');
95+
* @example this.isEmpty();
96+
* @returns
97+
*/
98+
isEmpty(...names: (string | null | undefined)[]): boolean;
99+
}
100+
101+
export class SlotController implements SlotControllerPublicAPI {
53102
public static default = Symbol('default slot') satisfies symbol as symbol;
54103

55104
/** @deprecated use `default` */
56105
public static anonymous: symbol = this.default;
57106

58-
private static singletons = new WeakMap<ReactiveElement, SlotController>();
59-
60107
#nodes = new Map<string | typeof SlotController.default, Slot>();
61108

62109
#slotMapInitialized = false;
@@ -70,14 +117,8 @@ export class SlotController implements ReactiveController {
70117
#mo = new MutationObserver(this.#initSlotMap.bind(this));
71118

72119
constructor(public host: ReactiveElement, ...args: SlotControllerArgs) {
73-
const singleton = SlotController.singletons.get(host);
74-
if (singleton) {
75-
singleton.#initialize(...args);
76-
return singleton;
77-
}
78120
this.#initialize(...args);
79121
host.addController(this);
80-
SlotController.singletons.set(host, this);
81122
if (!this.#slotNames.length) {
82123
this.#slotNames = [null];
83124
}

core/pfe-core/decorators/slots.ts

-27
This file was deleted.

core/pfe-core/package.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@
3232
"./controllers/property-observer-controller.js": "./controllers/property-observer-controller.js",
3333
"./controllers/roving-tabindex-controller.js": "./controllers/roving-tabindex-controller.js",
3434
"./controllers/scroll-spy-controller.js": "./controllers/scroll-spy-controller.js",
35-
"./controllers/slot-controller.js": "./controllers/slot-controller.js",
35+
"./controllers/slot-controller.js": {
36+
"node": "./controllers/slot-controller-server.js",
37+
"import": "./controllers/slot-controller.js",
38+
"default": "./controllers/slot-controller.js"
39+
},
3640
"./controllers/style-controller.js": "./controllers/style-controller.js",
3741
"./controllers/timestamp-controller.js": "./controllers/timestamp-controller.js",
3842
"./controllers/tabs-controller.js": "./controllers/tabs-controller.js",

0 commit comments

Comments
 (0)