
* web/admin: Fix issues surrounding dual select state management. * web: Fix nested path. * web: Use PatternFly variable.
143 lines
5.1 KiB
TypeScript
143 lines
5.1 KiB
TypeScript
import {
|
|
ConstructorWithMixin,
|
|
LitElementConstructor,
|
|
createMixin,
|
|
} from "@goauthentik/elements/types";
|
|
import { CustomEventDetail, isCustomEvent } from "@goauthentik/elements/utils/customEvents";
|
|
|
|
export interface CustomEventEmitterMixin<EventType extends string = string> {
|
|
dispatchCustomEvent<D extends CustomEventDetail>(
|
|
eventType: EventType,
|
|
detail?: D,
|
|
eventInit?: EventInit,
|
|
): void;
|
|
}
|
|
|
|
export function CustomEmitterElement<
|
|
EventType extends string = string,
|
|
T extends LitElementConstructor = LitElementConstructor,
|
|
>(SuperClass: T) {
|
|
abstract class CustomEventEmmiter
|
|
extends SuperClass
|
|
implements CustomEventEmitterMixin<EventType>
|
|
{
|
|
public dispatchCustomEvent<D extends CustomEventDetail>(
|
|
eventType: string,
|
|
detail: D = {} as D,
|
|
eventInit: EventInit = {},
|
|
) {
|
|
let normalizedDetail: CustomEventDetail;
|
|
|
|
if (detail && typeof detail === "object" && !Array.isArray(detail)) {
|
|
// TODO: Is this destructuring still necessary to shallow copy the object?
|
|
normalizedDetail = { ...detail };
|
|
} else {
|
|
normalizedDetail = detail;
|
|
}
|
|
|
|
this.dispatchEvent(
|
|
new CustomEvent(eventType, {
|
|
composed: true,
|
|
bubbles: true,
|
|
...eventInit,
|
|
detail: normalizedDetail,
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
return CustomEventEmmiter as unknown as ConstructorWithMixin<
|
|
T,
|
|
CustomEventEmitterMixin<EventType>
|
|
>;
|
|
}
|
|
|
|
type CustomEventListener<D = unknown> = (ev: CustomEvent<D>) => void;
|
|
type EventMap<D = unknown> = WeakMap<CustomEventListener<D>, CustomEventListener<D>>;
|
|
|
|
export interface CustomEventTarget<EventType extends string = string> {
|
|
addCustomListener<D = unknown>(eventType: EventType, handler: CustomEventListener<D>): void;
|
|
removeCustomListener<D = unknown>(eventType: EventType, handler: CustomEventListener<D>): void;
|
|
}
|
|
|
|
/**
|
|
* A mixin that enables Lit Elements to handle custom events in a more straightforward manner.
|
|
*
|
|
* @todo Can we lean on the native `EventTarget` class for this?
|
|
* @category Mixin
|
|
*/
|
|
export const CustomListenerElement = createMixin<CustomEventTarget>(({ SuperClass }) => {
|
|
return class ListenerElementHandler extends SuperClass implements CustomEventTarget {
|
|
#listenHandlers = new Map<string, EventMap>();
|
|
|
|
#getListener<D = unknown>(
|
|
eventType: string,
|
|
handler: CustomEventListener<D>,
|
|
): CustomEventListener<D> | undefined {
|
|
const internalMap = this.#listenHandlers.get(eventType) as EventMap<D> | undefined;
|
|
|
|
return internalMap?.get(handler);
|
|
}
|
|
|
|
// For every event NAME, we create a WeakMap that pairs the event handler given to us by the
|
|
// class that uses this method to the custom, wrapped handler we create to manage the types
|
|
// and handlings. If the wrapped handler disappears due to garbage collection, no harm done;
|
|
// meanwhile, this allows us to remove it from the event listeners if it's still around
|
|
// using the original handler's identity as the key.
|
|
//
|
|
#addListener<D = unknown>(
|
|
eventType: string,
|
|
handler: CustomEventListener<D>,
|
|
internalHandler: CustomEventListener<D>,
|
|
) {
|
|
let internalMap = this.#listenHandlers.get(eventType) as EventMap<D> | undefined;
|
|
|
|
if (!internalMap) {
|
|
internalMap = new WeakMap();
|
|
|
|
this.#listenHandlers.set(eventType, internalMap as EventMap);
|
|
}
|
|
|
|
internalMap.set(handler, internalHandler);
|
|
}
|
|
|
|
#removeListener<D = unknown>(eventType: string, listener: CustomEventListener<D>) {
|
|
const internalMap = this.#listenHandlers.get(eventType) as EventMap<D> | undefined;
|
|
|
|
if (internalMap) {
|
|
internalMap.delete(listener);
|
|
}
|
|
}
|
|
|
|
addCustomListener<D = unknown>(eventType: string, listener: CustomEventListener<D>) {
|
|
const internalHandler = (event: Event) => {
|
|
if (!isCustomEvent<D>(event)) {
|
|
console.error(
|
|
`Received a standard event for custom event ${eventType}; event will not be handled.`,
|
|
);
|
|
|
|
return null;
|
|
}
|
|
|
|
return listener(event);
|
|
};
|
|
|
|
this.#addListener(eventType, listener, internalHandler);
|
|
this.addEventListener(eventType, internalHandler);
|
|
}
|
|
|
|
removeCustomListener<D = unknown>(eventType: string, listener: CustomEventListener<D>) {
|
|
const realHandler = this.#getListener(eventType, listener);
|
|
|
|
if (realHandler) {
|
|
this.removeEventListener(
|
|
eventType,
|
|
realHandler as EventListenerOrEventListenerObject,
|
|
);
|
|
}
|
|
|
|
this.#removeListener<D>(eventType, listener);
|
|
}
|
|
};
|
|
});
|