web/admin: Dual select state management, custom event dispatching. (#14490)
* web/admin: Fix issues surrounding dual select state management. * web: Fix nested path. * web: Use PatternFly variable.
This commit is contained in:
@ -1,18 +1,28 @@
|
||||
import { createMixin } from "@goauthentik/elements/types";
|
||||
import {
|
||||
ConstructorWithMixin,
|
||||
LitElementConstructor,
|
||||
createMixin,
|
||||
} from "@goauthentik/elements/types";
|
||||
import { CustomEventDetail, isCustomEvent } from "@goauthentik/elements/utils/customEvents";
|
||||
|
||||
export interface EmmiterElementHandler {
|
||||
dispatchCustomEvent<T>(
|
||||
eventName: string,
|
||||
detail?: T extends CustomEvent<infer U> ? U : T,
|
||||
export interface CustomEventEmitterMixin<EventType extends string = string> {
|
||||
dispatchCustomEvent<D extends CustomEventDetail>(
|
||||
eventType: EventType,
|
||||
detail?: D,
|
||||
eventInit?: EventInit,
|
||||
): void;
|
||||
}
|
||||
|
||||
export const CustomEmitterElement = createMixin<EmmiterElementHandler>(({ SuperClass }) => {
|
||||
return class EmmiterElementHandler extends SuperClass {
|
||||
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>(
|
||||
eventName: string,
|
||||
eventType: string,
|
||||
detail: D = {} as D,
|
||||
eventInit: EventInit = {},
|
||||
) {
|
||||
@ -26,7 +36,7 @@ export const CustomEmitterElement = createMixin<EmmiterElementHandler>(({ SuperC
|
||||
}
|
||||
|
||||
this.dispatchEvent(
|
||||
new CustomEvent(eventName, {
|
||||
new CustomEvent(eventType, {
|
||||
composed: true,
|
||||
bubbles: true,
|
||||
...eventInit,
|
||||
@ -34,34 +44,20 @@ export const CustomEmitterElement = createMixin<EmmiterElementHandler>(({ SuperC
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mixin that enables Lit Elements to handle custom events in a more straightforward manner.
|
||||
*
|
||||
*/
|
||||
|
||||
// This is a neat trick: this static "class" is just a namespace for these unique symbols. Because
|
||||
// of all the constraints on them, they're legal field names in Typescript objects! Which means that
|
||||
// we can use them as identifiers for internal references in a Typescript class with absolutely no
|
||||
// risk that a future user who wants a name like 'addHandler' or 'removeHandler' will override any
|
||||
// of those, either in this mixin or in any class that this is mixed into, past or present along the
|
||||
// chain of inheritance.
|
||||
|
||||
class HK {
|
||||
public static readonly listenHandlers: unique symbol = Symbol();
|
||||
public static readonly addHandler: unique symbol = Symbol();
|
||||
public static readonly removeHandler: unique symbol = Symbol();
|
||||
public static readonly getHandler: unique symbol = Symbol();
|
||||
return CustomEventEmmiter as unknown as ConstructorWithMixin<
|
||||
T,
|
||||
CustomEventEmitterMixin<EventType>
|
||||
>;
|
||||
}
|
||||
|
||||
type EventHandler = (ev: CustomEvent) => void;
|
||||
type EventMap = WeakMap<EventHandler, EventHandler>;
|
||||
type CustomEventListener<D = unknown> = (ev: CustomEvent<D>) => void;
|
||||
type EventMap<D = unknown> = WeakMap<CustomEventListener<D>, CustomEventListener<D>>;
|
||||
|
||||
export interface CustomEventTarget {
|
||||
addCustomListener(eventName: string, handler: EventHandler): void;
|
||||
removeCustomListener(eventName: string, handler: EventHandler): void;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -72,11 +68,15 @@ export interface CustomEventTarget {
|
||||
*/
|
||||
export const CustomListenerElement = createMixin<CustomEventTarget>(({ SuperClass }) => {
|
||||
return class ListenerElementHandler extends SuperClass implements CustomEventTarget {
|
||||
private [HK.listenHandlers] = new Map<string, EventMap>();
|
||||
#listenHandlers = new Map<string, EventMap>();
|
||||
|
||||
private [HK.getHandler](eventName: string, handler: EventHandler) {
|
||||
const internalMap = this[HK.listenHandlers].get(eventName);
|
||||
return internalMap ? internalMap.get(handler) : undefined;
|
||||
#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
|
||||
@ -85,50 +85,58 @@ export const CustomListenerElement = createMixin<CustomEventTarget>(({ SuperClas
|
||||
// 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.
|
||||
//
|
||||
private [HK.addHandler](
|
||||
eventName: string,
|
||||
handler: EventHandler,
|
||||
internalHandler: EventHandler,
|
||||
#addListener<D = unknown>(
|
||||
eventType: string,
|
||||
handler: CustomEventListener<D>,
|
||||
internalHandler: CustomEventListener<D>,
|
||||
) {
|
||||
if (!this[HK.listenHandlers].has(eventName)) {
|
||||
this[HK.listenHandlers].set(eventName, new WeakMap());
|
||||
let internalMap = this.#listenHandlers.get(eventType) as EventMap<D> | undefined;
|
||||
|
||||
if (!internalMap) {
|
||||
internalMap = new WeakMap();
|
||||
|
||||
this.#listenHandlers.set(eventType, internalMap as EventMap);
|
||||
}
|
||||
const internalMap = this[HK.listenHandlers].get(eventName);
|
||||
|
||||
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.set(handler, internalHandler);
|
||||
internalMap.delete(listener);
|
||||
}
|
||||
}
|
||||
|
||||
private [HK.removeHandler](eventName: string, handler: EventHandler) {
|
||||
const internalMap = this[HK.listenHandlers].get(eventName);
|
||||
if (internalMap) {
|
||||
internalMap.delete(handler);
|
||||
}
|
||||
}
|
||||
|
||||
addCustomListener(eventName: string, handler: EventHandler) {
|
||||
addCustomListener<D = unknown>(eventType: string, listener: CustomEventListener<D>) {
|
||||
const internalHandler = (event: Event) => {
|
||||
if (!isCustomEvent(event)) {
|
||||
if (!isCustomEvent<D>(event)) {
|
||||
console.error(
|
||||
`Received a standard event for custom event ${eventName}; event will not be handled.`,
|
||||
`Received a standard event for custom event ${eventType}; event will not be handled.`,
|
||||
);
|
||||
return;
|
||||
|
||||
return null;
|
||||
}
|
||||
handler(event);
|
||||
|
||||
return listener(event);
|
||||
};
|
||||
this[HK.addHandler](eventName, handler, internalHandler);
|
||||
this.addEventListener(eventName, internalHandler);
|
||||
|
||||
this.#addListener(eventType, listener, internalHandler);
|
||||
this.addEventListener(eventType, internalHandler);
|
||||
}
|
||||
|
||||
removeCustomListener(eventName: string, handler: EventHandler) {
|
||||
const realHandler = this[HK.getHandler](eventName, handler);
|
||||
removeCustomListener<D = unknown>(eventType: string, listener: CustomEventListener<D>) {
|
||||
const realHandler = this.#getListener(eventType, listener);
|
||||
|
||||
if (realHandler) {
|
||||
this.removeEventListener(
|
||||
eventName,
|
||||
eventType,
|
||||
realHandler as EventListenerOrEventListenerObject,
|
||||
);
|
||||
}
|
||||
this[HK.removeHandler](eventName, handler);
|
||||
|
||||
this.#removeListener<D>(eventType, listener);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user