@laserware/hoverboard
Version:
Better context menus for Electron.
132 lines (100 loc) • 3.14 kB
text/typescript
import type { MenuItemConstructorOptions } from "electron";
import type { ContextMenuItemType } from "../renderer/types.js";
import type { SubmenuMenuItemElement } from "./SubmenuMenuItemElement.js";
export type BooleanAttribute = "true" | "false" | null;
export interface ContextMenuItemAttributes {
id: string;
visible: BooleanAttribute;
}
export type PropertyOptions = {
attribute?: string;
type: typeof Boolean | typeof Number | typeof String;
fallback?: boolean | number | string;
};
export function property(options: PropertyOptions) {
// biome-ignore lint/complexity/useArrowFunction: Need to use a function here.
return function (target: HTMLElement, propertyKey: string) {
const attributeName = options.attribute ?? propertyKey;
Object.defineProperty(target, propertyKey, {
get() {
const value = this.getAttribute(attributeName);
if (value === null) {
return options.fallback ?? undefined;
}
switch (options.type) {
case Boolean:
return value === "true";
case Number: {
const numericValue = Number(value);
if (
Number.isNaN(numericValue) &&
typeof options.fallback === "number"
) {
return options.fallback;
} else {
return numericValue;
}
}
default:
return value;
}
},
set(value: boolean | number | string | null | undefined) {
if (value === null || value === undefined) {
this.removeAttribute(attributeName);
} else {
this.setAttribute(attributeName, String(value));
}
},
});
};
}
const idGenerator = (() => {
let value = 1;
return {
next: () => `menu-item-${(value++).toString()}`,
};
})();
export class ContextMenuItemElement<
Attrs extends Record<string, any> = ContextMenuItemAttributes,
> extends HTMLElement {
public readonly type: ContextMenuItemType;
constructor(type: ContextMenuItemType) {
super();
this.type = type;
}
({ type: String })
public id!: string;
({ type: Boolean })
public visible: boolean | undefined;
public submenu: SubmenuMenuItemElement | null = null;
public toTemplate(): MenuItemConstructorOptions {
const template: MenuItemConstructorOptions = {};
const id = this.getAttribute("id");
if (id !== null) {
template.id = this.id;
}
if (this.type !== undefined) {
template.type = this.type;
}
if (this.visible !== undefined) {
template.visible = this.visible;
}
return template;
}
public connectedCallback(): void {
const id = this.getAttribute("id");
if (id === null) {
this.setAttribute("id", idGenerator.next());
}
}
public getAttribute(name: keyof Attrs) {
return super.getAttribute(name as string);
}
public setAttribute(name: keyof Attrs, value: string) {
super.setAttribute(name as string, value);
}
public removeAttribute(name: keyof Attrs) {
super.removeAttribute(name as string);
}
}