fix: add popover core, use in html and improve factory (#204)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Wesley Luyten
2025-11-21 15:16:20 -06:00
committed by GitHub
co-authored by Copilot
parent 072b0dece7
commit f3b1b19917
16 changed files with 755 additions and 742 deletions
+16 -4
View File
@@ -17,12 +17,24 @@ export function namedNodeMapToObject(namedNodeMap: NamedNodeMap): Record<string,
* @param element - The element to set attributes on.
* @param attributes - The attributes to set.
*/
export function setAttributes(element: HTMLElement, attributes: Record<string, string>): void {
export function setAttributes(element: HTMLElement, attributes: Record<string, any>): void {
for (const [key, value] of Object.entries(attributes)) {
if (typeof value === 'boolean') {
element.toggleAttribute(key, value);
if (key === 'style' && typeof value === 'object') {
for (const [styleKey, styleValue] of Object.entries(value)) {
if (typeof styleValue === 'string') {
element.style.setProperty(styleKey, styleValue);
} else if (styleValue == null) {
element.style.removeProperty(styleKey);
}
}
} else {
element.setAttribute(key, value);
if (typeof value === 'boolean') {
element.toggleAttribute(key, value);
} else if (value === undefined) {
element.removeAttribute(key);
} else {
element.setAttribute(key, value);
}
}
}
}
+2
View File
@@ -1,5 +1,7 @@
export * from './shared/console';
export * from './shared/crypto';
export * from './shared/memoize';
export * from './shared/state';
export * from './shared/string';
export * from './shared/time';
export * from './shared/unit';
+71
View File
@@ -0,0 +1,71 @@
// Adapted from https://github.com/caiogondim/fast-memoize.js - MIT License
type MemoizedFunction<T extends (...args: any[]) => any> = (
...args: Parameters<T>
) => ReturnType<T>;
interface MemoizedId {
$m: number;
}
interface MemoizedWithId {
$m?: number;
}
function isNode(x: unknown): boolean {
if (typeof globalThis === 'undefined') return false;
const NodeConstructor = (globalThis as { Node?: new () => unknown }).Node;
return NodeConstructor !== undefined && x instanceof NodeConstructor;
}
export function memoize<T extends (...args: any[]) => any>(
func: T,
): MemoizedFunction<T> {
const cache: Record<string, ReturnType<T>> = {};
return function (this: unknown, ...args: Parameters<T>): ReturnType<T> {
const argsWithFuncIds = args.map((x) => {
if (isPlainObject(x) || Array.isArray(x)) {
const obj: Record<string, unknown> = {};
for (const key in x) {
obj[key] = memoizedIdFunc((x as Record<string, unknown>)[key]);
}
return obj;
}
return memoizedIdFunc(x);
});
const cacheKey = JSON.stringify(argsWithFuncIds);
const cachedValue = cache[cacheKey];
if (cachedValue !== undefined) {
return cachedValue;
}
const computedValue = func.apply(this, args);
cache[cacheKey] = computedValue;
return computedValue;
};
}
let id = 0;
function memoizedIdFunc(x: unknown): unknown {
if (typeof x === 'function' || isNode(x)) {
const funcOrNode = x as MemoizedWithId;
if (!funcOrNode.$m) funcOrNode.$m = ++id;
return { $m: funcOrNode.$m } as MemoizedId;
}
return x;
}
/**
* Check if this is a plain object.
* @param {object} obj - The object to inspect.
* @return {boolean}
*/
function isPlainObject(obj: unknown): obj is Record<string, unknown> {
if (typeof obj !== 'object' || obj === null) return false;
let proto = obj;
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}
return Object.getPrototypeOf(obj) === proto;
}
+11
View File
@@ -0,0 +1,11 @@
/**
* Converts a string to camel case.
*
* @param str - The string to convert.
* @returns The camel case string.
*/
export function toCamelCase(str: string): string {
return str
.toLowerCase()
.replace(/[-_]([a-z])/g, (_$0, $1) => $1.toUpperCase());
}