mirror of
https://github.com/jaandrle/deka-dom-el
synced 2025-07-01 12:22:15 +02:00
🔤 🐛 ⚡ v0.9.1-alpha (#30)
* :tap: removed on.attributeChanged and static observedAttributes * ⚡ import optimalization * ⚡ scope.signal * 🔤 🐛 * ⚡ 🐛 registerReactivity and types * 🔤 * ⚡ * 🔤 * 🐛 Node in enviroment * ⚡ todos * ⚡ * ⚡ 🔤 * ⚡ lint * ⚡ memo * 🔤 🐛 memo * ⚡ 🔤 todomvc * 🐛 types * 🔤 p08 signal factory * 🔤 ⚡ types * ⚡ 🔤 lint * 🔤 * 🔤 * 🔤 * 🔤 * 📺
This commit is contained in:
80
dist/esm-with-signals.d.ts
vendored
80
dist/esm-with-signals.d.ts
vendored
@ -18,6 +18,7 @@ export type Actions<V> = Record<string | SymbolOnclear, Action<V>>;
|
||||
export type OnListenerOptions = Pick<AddEventListenerOptions, "signal"> & {
|
||||
first_time?: boolean;
|
||||
};
|
||||
export type SElement = Node | Element | DocumentFragment | ddeHTMLElement | ddeSVGElement | ddeDocumentFragment;
|
||||
export interface signal {
|
||||
_: Symbol;
|
||||
/**
|
||||
@ -63,7 +64,7 @@ export interface signal {
|
||||
* S.el(listS, list=> list.map(li=> el("li", li)));
|
||||
* ```
|
||||
* */
|
||||
el<S extends any>(signal: Signal<S, any>, el: (v: S) => Element | Element[] | DocumentFragment): DocumentFragment;
|
||||
el<S extends any>(signal: Signal<S, any>, el: (v: S) => SElement | SElement[]): DocumentFragment;
|
||||
observedAttributes(custom_element: HTMLElement): Record<string, Signal<string, {}>>;
|
||||
}
|
||||
export const signal: signal;
|
||||
@ -80,10 +81,11 @@ export type CustomElementTagNameMap = {
|
||||
export type SupportedElement = HTMLElementTagNameMap[keyof HTMLElementTagNameMap] | SVGElementTagNameMap[keyof SVGElementTagNameMap] | MathMLElementTagNameMap[keyof MathMLElementTagNameMap] | CustomElementTagNameMap[keyof CustomElementTagNameMap];
|
||||
declare global {
|
||||
type ddeComponentAttributes = Record<any, any> | undefined;
|
||||
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node> = (element: El) => any;
|
||||
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node> = (element: El, ...rest: any) => any;
|
||||
type ddeString = string | Signal<string, {}>;
|
||||
type ddeStringable = ddeString | number | Signal<number, {}>;
|
||||
}
|
||||
export type Host<EL extends SupportedElement> = (...addons: ddeElementAddon<EL>[]) => EL;
|
||||
export type PascalCase = `${Capitalize<string>}${string}`;
|
||||
export type AttrsModified = {
|
||||
/**
|
||||
@ -145,6 +147,7 @@ export namespace el {
|
||||
host?: "this" | "parentElement";
|
||||
}, is_open?: boolean): Comment;
|
||||
}
|
||||
export function chainableAppend<EL extends SupportedElement>(el: EL): EL | ddeHTMLElement;
|
||||
export function el<A extends ddeComponentAttributes, EL extends SupportedElement | ddeDocumentFragment>(component: (attr: A, ...rest: any[]) => EL, attrs?: NoInfer<A>, ...addons: ddeElementAddon<EL>[]): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] ? EL : (EL extends ddeDocumentFragment ? EL : ddeHTMLElement);
|
||||
export function el<A extends {
|
||||
textContent: ddeStringable;
|
||||
@ -157,7 +160,6 @@ export function elNS(namespace: "http://www.w3.org/1998/Math/MathML"): <TAG exte
|
||||
[key in keyof EL]: EL[key] | Signal<EL[key], {}> | string | number | boolean;
|
||||
}>, ...addons: ddeElementAddon<NoInfer<EL>>[]) => ddeMathMLElement;
|
||||
export function elNS(namespace: string): (tag_name: string, attrs?: string | ddeStringable | Record<string, any>, ...addons: ddeElementAddon<SupportedElement>[]) => SupportedElement;
|
||||
export function chainableAppend<EL extends SupportedElement>(el: EL): EL;
|
||||
/** Simulate slots for ddeComponents */
|
||||
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(root: EL): EL;
|
||||
/**
|
||||
@ -166,9 +168,9 @@ export function simulateSlots<EL extends SupportedElement | DocumentFragment>(ro
|
||||
* @param body Body of the custom element
|
||||
* */
|
||||
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(el: HTMLElement, body: EL): EL;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, element: SupportedElement): (data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, host: Host<SupportedElement>): (data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?: EventInit): (element: SupportedElement, data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, element: SupportedElement | (() => SupportedElement)): (data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
|
||||
export interface On {
|
||||
/** Listens to the DOM event. See {@link Document.addEventListener} */
|
||||
<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE;
|
||||
@ -186,7 +188,7 @@ export interface On {
|
||||
export const on: On;
|
||||
export type Scope = {
|
||||
scope: Node | Function | Object;
|
||||
host: ddeElementAddon<any>;
|
||||
host: Host<SupportedElement>;
|
||||
custom_element: false | HTMLElement;
|
||||
prevent: boolean;
|
||||
};
|
||||
@ -202,7 +204,11 @@ export const scope: {
|
||||
* It can be also used to register Addon(s) (functions to be called when component is initized)
|
||||
* — `scope.host(on.connected(console.log))`.
|
||||
* */
|
||||
host: (...addons: ddeElementAddon<SupportedElement>[]) => HTMLElement;
|
||||
host: Host<SupportedElement>;
|
||||
/**
|
||||
* Creates/gets an AbortController that triggers when the element disconnects
|
||||
* */
|
||||
signal: AbortSignal;
|
||||
state: Scope[];
|
||||
/** Adds new child scope. All attributes are inherited by default. */
|
||||
push(scope?: Partial<Scope>): ReturnType<Array<Scope>["push"]>;
|
||||
@ -214,7 +220,6 @@ export const scope: {
|
||||
export function customElementRender<EL extends HTMLElement, P extends any = Record<string, string | Signal<string, {}>>>(target: ShadowRoot | EL, render: (props: P) => SupportedElement | DocumentFragment, props?: P | ((el: EL) => P)): EL;
|
||||
export function customElementWithDDE<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||
export function lifecyclesToEvents<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||
export function observedAttributes(custom_element: HTMLElement): Record<string, string>;
|
||||
/**
|
||||
* This is used primarly for server side rendering. To be sure that all async operations
|
||||
* are finished before the page is sent to the client.
|
||||
@ -235,6 +240,65 @@ export function observedAttributes(custom_element: HTMLElement): Record<string,
|
||||
* ```
|
||||
* */
|
||||
export function queue(promise?: Promise<unknown>): Promise<unknown>;
|
||||
/**
|
||||
* Memoization utility for caching DOM elements to improve performance.
|
||||
* Used to prevent unnecessary recreation of elements when rendering lists or complex components.
|
||||
*
|
||||
* @param key - Unique identifier for the element (usually an ID or unique value)
|
||||
* @param generator - Function that creates the element
|
||||
* @returns The cached element if the key exists, otherwise the result of the generator function
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Within S.el for list rendering
|
||||
* S.el(itemsSignal, (items, memo) =>
|
||||
* el("ul").append(
|
||||
* ...items.map(item =>
|
||||
* memo(item.id, () => el(ItemComponent, item))
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
export function memo<T>(key: string | number | object, generator: (key: any) => T): T;
|
||||
/**
|
||||
* Memo namespace containing utility functions for memoization.
|
||||
*/
|
||||
export namespace memo {
|
||||
/**
|
||||
* Checks if an object is a memo scope.
|
||||
* @param obj - The object to check
|
||||
* @returns True if the object is a memo scope
|
||||
*/
|
||||
export function isScope(obj: any): boolean;
|
||||
/**
|
||||
* Creates a memoized function with optional cleanup support.
|
||||
*
|
||||
* @param fun - The function to memoize
|
||||
* @param options - Configuration options
|
||||
* @param options.signal - AbortSignal for cleanup
|
||||
* @param options.onlyLast - When true, only keeps the cache from the most recent call
|
||||
* @returns A memoized version of the function with a .clear() method
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const renderItems = memo.scope(function(items) {
|
||||
* return items.map(item =>
|
||||
* memo(item.id, () => el("div", item.name))
|
||||
* );
|
||||
* }, {
|
||||
* signal: controller.signal,
|
||||
* onlyLast: true
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function scope<F extends Function>(fun: F, options?: {
|
||||
signal?: AbortSignal;
|
||||
onlyLast?: boolean;
|
||||
}): F & {
|
||||
clear: () => void;
|
||||
};
|
||||
}
|
||||
/* TypeScript MEH */
|
||||
declare global {
|
||||
type ddeAppend<el> = (...nodes: (Node | string)[]) => el;
|
||||
|
510
dist/esm-with-signals.js
vendored
510
dist/esm-with-signals.js
vendored
@ -32,8 +32,8 @@ function onAbort(signal2, listener) {
|
||||
};
|
||||
}
|
||||
function observedAttributes(instance, observedAttribute2) {
|
||||
const { observedAttributes: observedAttributes3 = [] } = instance.constructor;
|
||||
return observedAttributes3.reduce(function(out, name) {
|
||||
const { observedAttributes: observedAttributes2 = [] } = instance.constructor;
|
||||
return observedAttributes2.reduce(function(out, name) {
|
||||
out[kebabToCamel(name)] = observedAttribute2(instance, name);
|
||||
return out;
|
||||
}, {});
|
||||
@ -91,6 +91,7 @@ var enviroment = {
|
||||
setDeleteAttr,
|
||||
ssr: "",
|
||||
D: globalThis.document,
|
||||
N: globalThis.Node,
|
||||
F: globalThis.DocumentFragment,
|
||||
H: globalThis.HTMLElement,
|
||||
S: globalThis.SVGElement,
|
||||
@ -111,6 +112,215 @@ var evc = "dde:connected";
|
||||
var evd = "dde:disconnected";
|
||||
var eva = "dde:attributeChanged";
|
||||
|
||||
// src/events-observer.js
|
||||
var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, {
|
||||
get() {
|
||||
return () => {
|
||||
};
|
||||
}
|
||||
});
|
||||
function connectionsChangesObserverConstructor() {
|
||||
const store = /* @__PURE__ */ new Map();
|
||||
let is_observing = false;
|
||||
const observerListener = (stop2) => function(mutations) {
|
||||
for (const mutation of mutations) {
|
||||
if (mutation.type !== "childList") continue;
|
||||
if (observerAdded(mutation.addedNodes, true)) {
|
||||
stop2();
|
||||
continue;
|
||||
}
|
||||
if (observerRemoved(mutation.removedNodes, true))
|
||||
stop2();
|
||||
}
|
||||
};
|
||||
const observer = new enviroment.M(observerListener(stop));
|
||||
return {
|
||||
/**
|
||||
* Creates an observer for a specific element
|
||||
* @param {Element} element - Element to observe
|
||||
* @returns {Function} Cleanup function
|
||||
*/
|
||||
observe(element) {
|
||||
const o = new enviroment.M(observerListener(() => {
|
||||
}));
|
||||
o.observe(element, { childList: true, subtree: true });
|
||||
return () => o.disconnect();
|
||||
},
|
||||
/**
|
||||
* Register a connection listener for an element
|
||||
* @param {Element} element - Element to watch
|
||||
* @param {Function} listener - Callback for connection event
|
||||
*/
|
||||
onConnected(element, listener) {
|
||||
start();
|
||||
const listeners = getElementStore(element);
|
||||
if (listeners.connected.has(listener)) return;
|
||||
listeners.connected.add(listener);
|
||||
listeners.length_c += 1;
|
||||
},
|
||||
/**
|
||||
* Unregister a connection listener
|
||||
* @param {Element} element - Element being watched
|
||||
* @param {Function} listener - Callback to remove
|
||||
*/
|
||||
offConnected(element, listener) {
|
||||
if (!store.has(element)) return;
|
||||
const ls = store.get(element);
|
||||
if (!ls.connected.has(listener)) return;
|
||||
ls.connected.delete(listener);
|
||||
ls.length_c -= 1;
|
||||
cleanWhenOff(element, ls);
|
||||
},
|
||||
/**
|
||||
* Register a disconnection listener for an element
|
||||
* @param {Element} element - Element to watch
|
||||
* @param {Function} listener - Callback for disconnection event
|
||||
*/
|
||||
onDisconnected(element, listener) {
|
||||
start();
|
||||
const listeners = getElementStore(element);
|
||||
if (listeners.disconnected.has(listener)) return;
|
||||
listeners.disconnected.add(listener);
|
||||
listeners.length_d += 1;
|
||||
},
|
||||
/**
|
||||
* Unregister a disconnection listener
|
||||
* @param {Element} element - Element being watched
|
||||
* @param {Function} listener - Callback to remove
|
||||
*/
|
||||
offDisconnected(element, listener) {
|
||||
if (!store.has(element)) return;
|
||||
const ls = store.get(element);
|
||||
ls.disconnected.delete(listener);
|
||||
ls.length_d -= 1;
|
||||
cleanWhenOff(element, ls);
|
||||
}
|
||||
};
|
||||
function cleanWhenOff(element, ls) {
|
||||
if (ls.length_c || ls.length_d)
|
||||
return;
|
||||
store.delete(element);
|
||||
stop();
|
||||
}
|
||||
function getElementStore(element) {
|
||||
if (store.has(element)) return store.get(element);
|
||||
const out = {
|
||||
connected: /* @__PURE__ */ new WeakSet(),
|
||||
length_c: 0,
|
||||
disconnected: /* @__PURE__ */ new WeakSet(),
|
||||
length_d: 0
|
||||
};
|
||||
store.set(element, out);
|
||||
return out;
|
||||
}
|
||||
function start() {
|
||||
if (is_observing) return;
|
||||
is_observing = true;
|
||||
observer.observe(enviroment.D.body, { childList: true, subtree: true });
|
||||
}
|
||||
function stop() {
|
||||
if (!is_observing || store.size) return;
|
||||
is_observing = false;
|
||||
observer.disconnect();
|
||||
}
|
||||
function requestIdle() {
|
||||
return new Promise(function(resolve) {
|
||||
(requestIdleCallback || requestAnimationFrame)(resolve);
|
||||
});
|
||||
}
|
||||
async function collectChildren(element) {
|
||||
if (store.size > 30)
|
||||
await requestIdle();
|
||||
const out = [];
|
||||
if (!isInstance(element, enviroment.N)) return out;
|
||||
for (const el of store.keys()) {
|
||||
if (el === element || !isInstance(el, enviroment.N)) continue;
|
||||
if (element.contains(el))
|
||||
out.push(el);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function observerAdded(addedNodes, is_root) {
|
||||
let out = false;
|
||||
for (const element of addedNodes) {
|
||||
if (is_root) collectChildren(element).then(observerAdded);
|
||||
if (!store.has(element)) continue;
|
||||
const ls = store.get(element);
|
||||
if (!ls.length_c) continue;
|
||||
element.dispatchEvent(new Event(evc));
|
||||
ls.connected = /* @__PURE__ */ new WeakSet();
|
||||
ls.length_c = 0;
|
||||
if (!ls.length_d) store.delete(element);
|
||||
out = true;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function observerRemoved(removedNodes, is_root) {
|
||||
let out = false;
|
||||
for (const element of removedNodes) {
|
||||
if (is_root) collectChildren(element).then(observerRemoved);
|
||||
if (!store.has(element)) continue;
|
||||
const ls = store.get(element);
|
||||
if (!ls.length_d) continue;
|
||||
(globalThis.queueMicrotask || setTimeout)(dispatchRemove(element));
|
||||
out = true;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function dispatchRemove(element) {
|
||||
return () => {
|
||||
if (element.isConnected) return;
|
||||
element.dispatchEvent(new Event(evd));
|
||||
store.delete(element);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// src/events.js
|
||||
function dispatchEvent(name, options, host) {
|
||||
if (typeof options === "function") {
|
||||
host = options;
|
||||
options = null;
|
||||
}
|
||||
if (!options) options = {};
|
||||
return function dispatch(element, ...d) {
|
||||
if (host) {
|
||||
d.unshift(element);
|
||||
element = typeof host === "function" ? host() : host;
|
||||
}
|
||||
const event = d.length ? new CustomEvent(name, oAssign({ detail: d[0] }, options)) : new Event(name, options);
|
||||
return element.dispatchEvent(event);
|
||||
};
|
||||
}
|
||||
function on(event, listener, options) {
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(event, listener, options);
|
||||
return element;
|
||||
};
|
||||
}
|
||||
var lifeOptions = (obj) => oAssign({}, typeof obj === "object" ? obj : null, { once: true });
|
||||
on.connected = function(listener, options) {
|
||||
options = lifeOptions(options);
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(evc, listener, options);
|
||||
if (element[keyLTE]) return element;
|
||||
if (element.isConnected) return element.dispatchEvent(new Event(evc)), element;
|
||||
const c = onAbort(options.signal, () => c_ch_o.offConnected(element, listener));
|
||||
if (c) c_ch_o.onConnected(element, listener);
|
||||
return element;
|
||||
};
|
||||
};
|
||||
on.disconnected = function(listener, options) {
|
||||
options = lifeOptions(options);
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(evd, listener, options);
|
||||
if (element[keyLTE]) return element;
|
||||
const c = onAbort(options.signal, () => c_ch_o.offDisconnected(element, listener));
|
||||
if (c) c_ch_o.onDisconnected(element, listener);
|
||||
return element;
|
||||
};
|
||||
};
|
||||
|
||||
// src/dom.js
|
||||
function queue(promise) {
|
||||
return enviroment.q(promise);
|
||||
@ -122,6 +332,7 @@ var scopes = [{
|
||||
host: (c) => c ? c(enviroment.D.body) : enviroment.D.body,
|
||||
prevent: true
|
||||
}];
|
||||
var store_abort = /* @__PURE__ */ new WeakMap();
|
||||
var scope = {
|
||||
/**
|
||||
* Gets the current scope
|
||||
@ -138,6 +349,17 @@ var scope = {
|
||||
return this.current.host;
|
||||
},
|
||||
/**
|
||||
* Creates/gets an AbortController that triggers when the element disconnects
|
||||
* */
|
||||
get signal() {
|
||||
const { host } = this;
|
||||
if (store_abort.has(host)) return store_abort.get(host);
|
||||
const a = new AbortController();
|
||||
store_abort.set(host, a);
|
||||
host(on.disconnected(() => a.abort()));
|
||||
return a.signal;
|
||||
},
|
||||
/**
|
||||
* Prevents default behavior in the current scope
|
||||
* @returns {Object} Current scope context
|
||||
*/
|
||||
@ -380,172 +602,8 @@ function setDelete(obj, key, val) {
|
||||
return Reflect.deleteProperty(obj, key);
|
||||
}
|
||||
|
||||
// src/events-observer.js
|
||||
var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, {
|
||||
get() {
|
||||
return () => {
|
||||
};
|
||||
}
|
||||
});
|
||||
function connectionsChangesObserverConstructor() {
|
||||
const store = /* @__PURE__ */ new Map();
|
||||
let is_observing = false;
|
||||
const observerListener = (stop2) => function(mutations) {
|
||||
for (const mutation of mutations) {
|
||||
if (mutation.type !== "childList") continue;
|
||||
if (observerAdded(mutation.addedNodes, true)) {
|
||||
stop2();
|
||||
continue;
|
||||
}
|
||||
if (observerRemoved(mutation.removedNodes, true))
|
||||
stop2();
|
||||
}
|
||||
};
|
||||
const observer = new enviroment.M(observerListener(stop));
|
||||
return {
|
||||
/**
|
||||
* Creates an observer for a specific element
|
||||
* @param {Element} element - Element to observe
|
||||
* @returns {Function} Cleanup function
|
||||
*/
|
||||
observe(element) {
|
||||
const o = new enviroment.M(observerListener(() => {
|
||||
}));
|
||||
o.observe(element, { childList: true, subtree: true });
|
||||
return () => o.disconnect();
|
||||
},
|
||||
/**
|
||||
* Register a connection listener for an element
|
||||
* @param {Element} element - Element to watch
|
||||
* @param {Function} listener - Callback for connection event
|
||||
*/
|
||||
onConnected(element, listener) {
|
||||
start();
|
||||
const listeners = getElementStore(element);
|
||||
if (listeners.connected.has(listener)) return;
|
||||
listeners.connected.add(listener);
|
||||
listeners.length_c += 1;
|
||||
},
|
||||
/**
|
||||
* Unregister a connection listener
|
||||
* @param {Element} element - Element being watched
|
||||
* @param {Function} listener - Callback to remove
|
||||
*/
|
||||
offConnected(element, listener) {
|
||||
if (!store.has(element)) return;
|
||||
const ls = store.get(element);
|
||||
if (!ls.connected.has(listener)) return;
|
||||
ls.connected.delete(listener);
|
||||
ls.length_c -= 1;
|
||||
cleanWhenOff(element, ls);
|
||||
},
|
||||
/**
|
||||
* Register a disconnection listener for an element
|
||||
* @param {Element} element - Element to watch
|
||||
* @param {Function} listener - Callback for disconnection event
|
||||
*/
|
||||
onDisconnected(element, listener) {
|
||||
start();
|
||||
const listeners = getElementStore(element);
|
||||
if (listeners.disconnected.has(listener)) return;
|
||||
listeners.disconnected.add(listener);
|
||||
listeners.length_d += 1;
|
||||
},
|
||||
/**
|
||||
* Unregister a disconnection listener
|
||||
* @param {Element} element - Element being watched
|
||||
* @param {Function} listener - Callback to remove
|
||||
*/
|
||||
offDisconnected(element, listener) {
|
||||
if (!store.has(element)) return;
|
||||
const ls = store.get(element);
|
||||
ls.disconnected.delete(listener);
|
||||
ls.length_d -= 1;
|
||||
cleanWhenOff(element, ls);
|
||||
}
|
||||
};
|
||||
function cleanWhenOff(element, ls) {
|
||||
if (ls.length_c || ls.length_d)
|
||||
return;
|
||||
store.delete(element);
|
||||
stop();
|
||||
}
|
||||
function getElementStore(element) {
|
||||
if (store.has(element)) return store.get(element);
|
||||
const out = {
|
||||
connected: /* @__PURE__ */ new WeakSet(),
|
||||
length_c: 0,
|
||||
disconnected: /* @__PURE__ */ new WeakSet(),
|
||||
length_d: 0
|
||||
};
|
||||
store.set(element, out);
|
||||
return out;
|
||||
}
|
||||
function start() {
|
||||
if (is_observing) return;
|
||||
is_observing = true;
|
||||
observer.observe(enviroment.D.body, { childList: true, subtree: true });
|
||||
}
|
||||
function stop() {
|
||||
if (!is_observing || store.size) return;
|
||||
is_observing = false;
|
||||
observer.disconnect();
|
||||
}
|
||||
function requestIdle() {
|
||||
return new Promise(function(resolve) {
|
||||
(requestIdleCallback || requestAnimationFrame)(resolve);
|
||||
});
|
||||
}
|
||||
async function collectChildren(element) {
|
||||
if (store.size > 30)
|
||||
await requestIdle();
|
||||
const out = [];
|
||||
if (!isInstance(element, Node)) return out;
|
||||
for (const el of store.keys()) {
|
||||
if (el === element || !isInstance(el, Node)) continue;
|
||||
if (element.contains(el))
|
||||
out.push(el);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function observerAdded(addedNodes, is_root) {
|
||||
let out = false;
|
||||
for (const element of addedNodes) {
|
||||
if (is_root) collectChildren(element).then(observerAdded);
|
||||
if (!store.has(element)) continue;
|
||||
const ls = store.get(element);
|
||||
if (!ls.length_c) continue;
|
||||
element.dispatchEvent(new Event(evc));
|
||||
ls.connected = /* @__PURE__ */ new WeakSet();
|
||||
ls.length_c = 0;
|
||||
if (!ls.length_d) store.delete(element);
|
||||
out = true;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function observerRemoved(removedNodes, is_root) {
|
||||
let out = false;
|
||||
for (const element of removedNodes) {
|
||||
if (is_root) collectChildren(element).then(observerRemoved);
|
||||
if (!store.has(element)) continue;
|
||||
const ls = store.get(element);
|
||||
if (!ls.length_d) continue;
|
||||
(globalThis.queueMicrotask || setTimeout)(dispatchRemove(element));
|
||||
out = true;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function dispatchRemove(element) {
|
||||
return () => {
|
||||
if (element.isConnected) return;
|
||||
element.dispatchEvent(new Event(evd));
|
||||
store.delete(element);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// src/customElement.js
|
||||
function customElementRender(target, render, props = observedAttributes2) {
|
||||
function customElementRender(target, render, props = {}) {
|
||||
const custom_element = target.host || target;
|
||||
scope.push({
|
||||
scope: custom_element,
|
||||
@ -586,81 +644,40 @@ function wrapMethod(obj, method, apply) {
|
||||
obj[method] = new Proxy(obj[method] || (() => {
|
||||
}), { apply });
|
||||
}
|
||||
function observedAttributes2(instance) {
|
||||
return observedAttributes(instance, (i, n) => i.getAttribute(n));
|
||||
}
|
||||
|
||||
// src/events.js
|
||||
function dispatchEvent(name, options, host) {
|
||||
if (typeof options === "function") {
|
||||
host = options;
|
||||
options = null;
|
||||
}
|
||||
if (!options) options = {};
|
||||
return function dispatch(element, ...d) {
|
||||
if (host) {
|
||||
d.unshift(element);
|
||||
element = typeof host === "function" ? host() : host;
|
||||
}
|
||||
const event = d.length ? new CustomEvent(name, oAssign({ detail: d[0] }, options)) : new Event(name, options);
|
||||
return element.dispatchEvent(event);
|
||||
};
|
||||
// src/memo.js
|
||||
var memoMark = "__dde_memo";
|
||||
var memo_scope = [];
|
||||
function memo(key, generator) {
|
||||
if (!memo_scope.length) return generator(key);
|
||||
const k = typeof key === "object" ? JSON.stringify(key) : key;
|
||||
const [{ cache, after }] = memo_scope;
|
||||
return after(k, hasOwn(cache, k) ? cache[k] : generator(key));
|
||||
}
|
||||
function on(event, listener, options) {
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(event, listener, options);
|
||||
return element;
|
||||
};
|
||||
}
|
||||
var lifeOptions = (obj) => oAssign({}, typeof obj === "object" ? obj : null, { once: true });
|
||||
on.connected = function(listener, options) {
|
||||
options = lifeOptions(options);
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(evc, listener, options);
|
||||
if (element[keyLTE]) return element;
|
||||
if (element.isConnected) return element.dispatchEvent(new Event(evc)), element;
|
||||
const c = onAbort(options.signal, () => c_ch_o.offConnected(element, listener));
|
||||
if (c) c_ch_o.onConnected(element, listener);
|
||||
return element;
|
||||
};
|
||||
memo.isScope = function(obj) {
|
||||
return obj[memoMark];
|
||||
};
|
||||
on.disconnected = function(listener, options) {
|
||||
options = lifeOptions(options);
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(evd, listener, options);
|
||||
if (element[keyLTE]) return element;
|
||||
const c = onAbort(options.signal, () => c_ch_o.offDisconnected(element, listener));
|
||||
if (c) c_ch_o.onDisconnected(element, listener);
|
||||
return element;
|
||||
};
|
||||
};
|
||||
var store_abort = /* @__PURE__ */ new WeakMap();
|
||||
on.disconnectedAsAbort = function(host) {
|
||||
if (store_abort.has(host)) return store_abort.get(host);
|
||||
const a = new AbortController();
|
||||
store_abort.set(host, a);
|
||||
host(on.disconnected(() => a.abort()));
|
||||
return a.signal;
|
||||
};
|
||||
var els_attribute_store = /* @__PURE__ */ new WeakSet();
|
||||
on.attributeChanged = function(listener, options) {
|
||||
if (typeof options !== "object")
|
||||
options = {};
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(eva, listener, options);
|
||||
if (element[keyLTE] || els_attribute_store.has(element))
|
||||
return element;
|
||||
if (!enviroment.M) return element;
|
||||
const observer = new enviroment.M(function(mutations) {
|
||||
for (const { attributeName, target } of mutations)
|
||||
target.dispatchEvent(
|
||||
new CustomEvent(eva, { detail: [attributeName, target.getAttribute(attributeName)] })
|
||||
);
|
||||
memo.scope = function memoScope(fun, { signal: signal2, onlyLast } = {}) {
|
||||
let cache = oCreate();
|
||||
function memoScope2(...args) {
|
||||
if (signal2 && signal2.aborted)
|
||||
return fun.apply(this, args);
|
||||
let cache_local = onlyLast ? cache : oCreate();
|
||||
memo_scope.unshift({
|
||||
cache,
|
||||
after(key, val) {
|
||||
return cache_local[key] = val;
|
||||
}
|
||||
});
|
||||
const c = onAbort(options.signal, () => observer.disconnect());
|
||||
if (c) observer.observe(element, { attributes: true });
|
||||
return element;
|
||||
};
|
||||
const out = fun.apply(this, args);
|
||||
memo_scope.shift();
|
||||
cache = cache_local;
|
||||
return out;
|
||||
}
|
||||
memoScope2[memoMark] = true;
|
||||
memoScope2.clear = () => cache = oCreate();
|
||||
if (signal2) signal2.addEventListener("abort", memoScope2.clear);
|
||||
return memoScope2;
|
||||
};
|
||||
|
||||
// src/signals-lib/helpers.js
|
||||
@ -776,25 +793,18 @@ signal.clear = function(...signals2) {
|
||||
}
|
||||
};
|
||||
var key_reactive = "__dde_reactive";
|
||||
function cache(store = oCreate()) {
|
||||
return (key, fun) => hasOwn(store, key) ? store[key] : store[key] = fun();
|
||||
}
|
||||
signal.el = function(s, map) {
|
||||
map = memo.isScope(map) ? map : memo.scope(map, { onlyLast: true });
|
||||
const mark_start = createElement.mark({ type: "reactive", source: new Defined().compact }, true);
|
||||
const mark_end = mark_start.end;
|
||||
const out = enviroment.D.createDocumentFragment();
|
||||
out.append(mark_start, mark_end);
|
||||
const { current } = scope;
|
||||
let cache_shared = oCreate();
|
||||
const reRenderReactiveElement = (v) => {
|
||||
if (!mark_start.parentNode || !mark_end.parentNode)
|
||||
return removeSignalListener(s, reRenderReactiveElement);
|
||||
const memo = cache(cache_shared);
|
||||
cache_shared = oCreate();
|
||||
scope.push(current);
|
||||
let els = map(v, function useCache(key, fun) {
|
||||
return cache_shared[key] = memo(key, fun);
|
||||
});
|
||||
let els = map(v);
|
||||
scope.pop();
|
||||
if (!Array.isArray(els))
|
||||
els = [els];
|
||||
@ -814,7 +824,7 @@ signal.el = function(s, map) {
|
||||
current.host(on.disconnected(
|
||||
() => (
|
||||
/*! Clears cached elements for reactive element `S.el` */
|
||||
cache_shared = {}
|
||||
map.clear()
|
||||
)
|
||||
));
|
||||
return out;
|
||||
@ -846,7 +856,7 @@ var key_attributes = "__dde_attributes";
|
||||
signal.observedAttributes = function(element) {
|
||||
const store = element[key_attributes] = {};
|
||||
const attrs = observedAttributes(element, observedAttribute(store));
|
||||
on.attributeChanged(function attributeChangeToSignal({ detail }) {
|
||||
on(eva, function attributeChangeToSignal({ detail }) {
|
||||
/*! This maps attributes to signals (`S.observedAttributes`).
|
||||
Investigate `__dde_attributes` key of the element. */
|
||||
const [name, value] = detail;
|
||||
@ -997,7 +1007,7 @@ export {
|
||||
elementAttribute,
|
||||
isSignal,
|
||||
lifecyclesToEvents,
|
||||
observedAttributes2 as observedAttributes,
|
||||
memo,
|
||||
on,
|
||||
queue,
|
||||
registerReactivity,
|
||||
|
80
dist/esm-with-signals.min.d.ts
vendored
80
dist/esm-with-signals.min.d.ts
vendored
@ -18,6 +18,7 @@ export type Actions<V> = Record<string | SymbolOnclear, Action<V>>;
|
||||
export type OnListenerOptions = Pick<AddEventListenerOptions, "signal"> & {
|
||||
first_time?: boolean;
|
||||
};
|
||||
export type SElement = Node | Element | DocumentFragment | ddeHTMLElement | ddeSVGElement | ddeDocumentFragment;
|
||||
export interface signal {
|
||||
_: Symbol;
|
||||
/**
|
||||
@ -63,7 +64,7 @@ export interface signal {
|
||||
* S.el(listS, list=> list.map(li=> el("li", li)));
|
||||
* ```
|
||||
* */
|
||||
el<S extends any>(signal: Signal<S, any>, el: (v: S) => Element | Element[] | DocumentFragment): DocumentFragment;
|
||||
el<S extends any>(signal: Signal<S, any>, el: (v: S) => SElement | SElement[]): DocumentFragment;
|
||||
observedAttributes(custom_element: HTMLElement): Record<string, Signal<string, {}>>;
|
||||
}
|
||||
export const signal: signal;
|
||||
@ -80,10 +81,11 @@ export type CustomElementTagNameMap = {
|
||||
export type SupportedElement = HTMLElementTagNameMap[keyof HTMLElementTagNameMap] | SVGElementTagNameMap[keyof SVGElementTagNameMap] | MathMLElementTagNameMap[keyof MathMLElementTagNameMap] | CustomElementTagNameMap[keyof CustomElementTagNameMap];
|
||||
declare global {
|
||||
type ddeComponentAttributes = Record<any, any> | undefined;
|
||||
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node> = (element: El) => any;
|
||||
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node> = (element: El, ...rest: any) => any;
|
||||
type ddeString = string | Signal<string, {}>;
|
||||
type ddeStringable = ddeString | number | Signal<number, {}>;
|
||||
}
|
||||
export type Host<EL extends SupportedElement> = (...addons: ddeElementAddon<EL>[]) => EL;
|
||||
export type PascalCase = `${Capitalize<string>}${string}`;
|
||||
export type AttrsModified = {
|
||||
/**
|
||||
@ -145,6 +147,7 @@ export namespace el {
|
||||
host?: "this" | "parentElement";
|
||||
}, is_open?: boolean): Comment;
|
||||
}
|
||||
export function chainableAppend<EL extends SupportedElement>(el: EL): EL | ddeHTMLElement;
|
||||
export function el<A extends ddeComponentAttributes, EL extends SupportedElement | ddeDocumentFragment>(component: (attr: A, ...rest: any[]) => EL, attrs?: NoInfer<A>, ...addons: ddeElementAddon<EL>[]): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] ? EL : (EL extends ddeDocumentFragment ? EL : ddeHTMLElement);
|
||||
export function el<A extends {
|
||||
textContent: ddeStringable;
|
||||
@ -157,7 +160,6 @@ export function elNS(namespace: "http://www.w3.org/1998/Math/MathML"): <TAG exte
|
||||
[key in keyof EL]: EL[key] | Signal<EL[key], {}> | string | number | boolean;
|
||||
}>, ...addons: ddeElementAddon<NoInfer<EL>>[]) => ddeMathMLElement;
|
||||
export function elNS(namespace: string): (tag_name: string, attrs?: string | ddeStringable | Record<string, any>, ...addons: ddeElementAddon<SupportedElement>[]) => SupportedElement;
|
||||
export function chainableAppend<EL extends SupportedElement>(el: EL): EL;
|
||||
/** Simulate slots for ddeComponents */
|
||||
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(root: EL): EL;
|
||||
/**
|
||||
@ -166,9 +168,9 @@ export function simulateSlots<EL extends SupportedElement | DocumentFragment>(ro
|
||||
* @param body Body of the custom element
|
||||
* */
|
||||
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(el: HTMLElement, body: EL): EL;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, element: SupportedElement): (data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, host: Host<SupportedElement>): (data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?: EventInit): (element: SupportedElement, data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, element: SupportedElement | (() => SupportedElement)): (data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
|
||||
export interface On {
|
||||
/** Listens to the DOM event. See {@link Document.addEventListener} */
|
||||
<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE;
|
||||
@ -186,7 +188,7 @@ export interface On {
|
||||
export const on: On;
|
||||
export type Scope = {
|
||||
scope: Node | Function | Object;
|
||||
host: ddeElementAddon<any>;
|
||||
host: Host<SupportedElement>;
|
||||
custom_element: false | HTMLElement;
|
||||
prevent: boolean;
|
||||
};
|
||||
@ -202,7 +204,11 @@ export const scope: {
|
||||
* It can be also used to register Addon(s) (functions to be called when component is initized)
|
||||
* — `scope.host(on.connected(console.log))`.
|
||||
* */
|
||||
host: (...addons: ddeElementAddon<SupportedElement>[]) => HTMLElement;
|
||||
host: Host<SupportedElement>;
|
||||
/**
|
||||
* Creates/gets an AbortController that triggers when the element disconnects
|
||||
* */
|
||||
signal: AbortSignal;
|
||||
state: Scope[];
|
||||
/** Adds new child scope. All attributes are inherited by default. */
|
||||
push(scope?: Partial<Scope>): ReturnType<Array<Scope>["push"]>;
|
||||
@ -214,7 +220,6 @@ export const scope: {
|
||||
export function customElementRender<EL extends HTMLElement, P extends any = Record<string, string | Signal<string, {}>>>(target: ShadowRoot | EL, render: (props: P) => SupportedElement | DocumentFragment, props?: P | ((el: EL) => P)): EL;
|
||||
export function customElementWithDDE<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||
export function lifecyclesToEvents<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||
export function observedAttributes(custom_element: HTMLElement): Record<string, string>;
|
||||
/**
|
||||
* This is used primarly for server side rendering. To be sure that all async operations
|
||||
* are finished before the page is sent to the client.
|
||||
@ -235,6 +240,65 @@ export function observedAttributes(custom_element: HTMLElement): Record<string,
|
||||
* ```
|
||||
* */
|
||||
export function queue(promise?: Promise<unknown>): Promise<unknown>;
|
||||
/**
|
||||
* Memoization utility for caching DOM elements to improve performance.
|
||||
* Used to prevent unnecessary recreation of elements when rendering lists or complex components.
|
||||
*
|
||||
* @param key - Unique identifier for the element (usually an ID or unique value)
|
||||
* @param generator - Function that creates the element
|
||||
* @returns The cached element if the key exists, otherwise the result of the generator function
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Within S.el for list rendering
|
||||
* S.el(itemsSignal, (items, memo) =>
|
||||
* el("ul").append(
|
||||
* ...items.map(item =>
|
||||
* memo(item.id, () => el(ItemComponent, item))
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
export function memo<T>(key: string | number | object, generator: (key: any) => T): T;
|
||||
/**
|
||||
* Memo namespace containing utility functions for memoization.
|
||||
*/
|
||||
export namespace memo {
|
||||
/**
|
||||
* Checks if an object is a memo scope.
|
||||
* @param obj - The object to check
|
||||
* @returns True if the object is a memo scope
|
||||
*/
|
||||
export function isScope(obj: any): boolean;
|
||||
/**
|
||||
* Creates a memoized function with optional cleanup support.
|
||||
*
|
||||
* @param fun - The function to memoize
|
||||
* @param options - Configuration options
|
||||
* @param options.signal - AbortSignal for cleanup
|
||||
* @param options.onlyLast - When true, only keeps the cache from the most recent call
|
||||
* @returns A memoized version of the function with a .clear() method
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const renderItems = memo.scope(function(items) {
|
||||
* return items.map(item =>
|
||||
* memo(item.id, () => el("div", item.name))
|
||||
* );
|
||||
* }, {
|
||||
* signal: controller.signal,
|
||||
* onlyLast: true
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function scope<F extends Function>(fun: F, options?: {
|
||||
signal?: AbortSignal;
|
||||
onlyLast?: boolean;
|
||||
}): F & {
|
||||
clear: () => void;
|
||||
};
|
||||
}
|
||||
/* TypeScript MEH */
|
||||
declare global {
|
||||
type ddeAppend<el> = (...nodes: (Node | string)[]) => el;
|
||||
|
8
dist/esm-with-signals.min.js
vendored
8
dist/esm-with-signals.min.js
vendored
File diff suppressed because one or more lines are too long
80
dist/esm.d.ts
vendored
80
dist/esm.d.ts
vendored
@ -18,6 +18,7 @@ export type Actions<V> = Record<string | SymbolOnclear, Action<V>>;
|
||||
export type OnListenerOptions = Pick<AddEventListenerOptions, "signal"> & {
|
||||
first_time?: boolean;
|
||||
};
|
||||
export type SElement = Node | Element | DocumentFragment | ddeHTMLElement | ddeSVGElement | ddeDocumentFragment;
|
||||
export interface signal {
|
||||
_: Symbol;
|
||||
/**
|
||||
@ -63,7 +64,7 @@ export interface signal {
|
||||
* S.el(listS, list=> list.map(li=> el("li", li)));
|
||||
* ```
|
||||
* */
|
||||
el<S extends any>(signal: Signal<S, any>, el: (v: S) => Element | Element[] | DocumentFragment): DocumentFragment;
|
||||
el<S extends any>(signal: Signal<S, any>, el: (v: S) => SElement | SElement[]): DocumentFragment;
|
||||
observedAttributes(custom_element: HTMLElement): Record<string, Signal<string, {}>>;
|
||||
}
|
||||
declare const signal: signal;
|
||||
@ -79,10 +80,11 @@ export type CustomElementTagNameMap = {
|
||||
export type SupportedElement = HTMLElementTagNameMap[keyof HTMLElementTagNameMap] | SVGElementTagNameMap[keyof SVGElementTagNameMap] | MathMLElementTagNameMap[keyof MathMLElementTagNameMap] | CustomElementTagNameMap[keyof CustomElementTagNameMap];
|
||||
declare global {
|
||||
type ddeComponentAttributes = Record<any, any> | undefined;
|
||||
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node> = (element: El) => any;
|
||||
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node> = (element: El, ...rest: any) => any;
|
||||
type ddeString = string | Signal<string, {}>;
|
||||
type ddeStringable = ddeString | number | Signal<number, {}>;
|
||||
}
|
||||
export type Host<EL extends SupportedElement> = (...addons: ddeElementAddon<EL>[]) => EL;
|
||||
export type PascalCase = `${Capitalize<string>}${string}`;
|
||||
export type AttrsModified = {
|
||||
/**
|
||||
@ -144,6 +146,7 @@ export namespace el {
|
||||
host?: "this" | "parentElement";
|
||||
}, is_open?: boolean): Comment;
|
||||
}
|
||||
export function chainableAppend<EL extends SupportedElement>(el: EL): EL | ddeHTMLElement;
|
||||
export function el<A extends ddeComponentAttributes, EL extends SupportedElement | ddeDocumentFragment>(component: (attr: A, ...rest: any[]) => EL, attrs?: NoInfer<A>, ...addons: ddeElementAddon<EL>[]): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] ? EL : (EL extends ddeDocumentFragment ? EL : ddeHTMLElement);
|
||||
export function el<A extends {
|
||||
textContent: ddeStringable;
|
||||
@ -156,7 +159,6 @@ export function elNS(namespace: "http://www.w3.org/1998/Math/MathML"): <TAG exte
|
||||
[key in keyof EL]: EL[key] | Signal<EL[key], {}> | string | number | boolean;
|
||||
}>, ...addons: ddeElementAddon<NoInfer<EL>>[]) => ddeMathMLElement;
|
||||
export function elNS(namespace: string): (tag_name: string, attrs?: string | ddeStringable | Record<string, any>, ...addons: ddeElementAddon<SupportedElement>[]) => SupportedElement;
|
||||
export function chainableAppend<EL extends SupportedElement>(el: EL): EL;
|
||||
/** Simulate slots for ddeComponents */
|
||||
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(root: EL): EL;
|
||||
/**
|
||||
@ -165,9 +167,9 @@ export function simulateSlots<EL extends SupportedElement | DocumentFragment>(ro
|
||||
* @param body Body of the custom element
|
||||
* */
|
||||
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(el: HTMLElement, body: EL): EL;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, element: SupportedElement): (data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, host: Host<SupportedElement>): (data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?: EventInit): (element: SupportedElement, data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, element: SupportedElement | (() => SupportedElement)): (data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
|
||||
export interface On {
|
||||
/** Listens to the DOM event. See {@link Document.addEventListener} */
|
||||
<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE;
|
||||
@ -185,7 +187,7 @@ export interface On {
|
||||
export const on: On;
|
||||
export type Scope = {
|
||||
scope: Node | Function | Object;
|
||||
host: ddeElementAddon<any>;
|
||||
host: Host<SupportedElement>;
|
||||
custom_element: false | HTMLElement;
|
||||
prevent: boolean;
|
||||
};
|
||||
@ -201,7 +203,11 @@ export const scope: {
|
||||
* It can be also used to register Addon(s) (functions to be called when component is initized)
|
||||
* — `scope.host(on.connected(console.log))`.
|
||||
* */
|
||||
host: (...addons: ddeElementAddon<SupportedElement>[]) => HTMLElement;
|
||||
host: Host<SupportedElement>;
|
||||
/**
|
||||
* Creates/gets an AbortController that triggers when the element disconnects
|
||||
* */
|
||||
signal: AbortSignal;
|
||||
state: Scope[];
|
||||
/** Adds new child scope. All attributes are inherited by default. */
|
||||
push(scope?: Partial<Scope>): ReturnType<Array<Scope>["push"]>;
|
||||
@ -213,7 +219,6 @@ export const scope: {
|
||||
export function customElementRender<EL extends HTMLElement, P extends any = Record<string, string | Signal<string, {}>>>(target: ShadowRoot | EL, render: (props: P) => SupportedElement | DocumentFragment, props?: P | ((el: EL) => P)): EL;
|
||||
export function customElementWithDDE<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||
export function lifecyclesToEvents<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||
export function observedAttributes(custom_element: HTMLElement): Record<string, string>;
|
||||
/**
|
||||
* This is used primarly for server side rendering. To be sure that all async operations
|
||||
* are finished before the page is sent to the client.
|
||||
@ -234,6 +239,65 @@ export function observedAttributes(custom_element: HTMLElement): Record<string,
|
||||
* ```
|
||||
* */
|
||||
export function queue(promise?: Promise<unknown>): Promise<unknown>;
|
||||
/**
|
||||
* Memoization utility for caching DOM elements to improve performance.
|
||||
* Used to prevent unnecessary recreation of elements when rendering lists or complex components.
|
||||
*
|
||||
* @param key - Unique identifier for the element (usually an ID or unique value)
|
||||
* @param generator - Function that creates the element
|
||||
* @returns The cached element if the key exists, otherwise the result of the generator function
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Within S.el for list rendering
|
||||
* S.el(itemsSignal, (items, memo) =>
|
||||
* el("ul").append(
|
||||
* ...items.map(item =>
|
||||
* memo(item.id, () => el(ItemComponent, item))
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
export function memo<T>(key: string | number | object, generator: (key: any) => T): T;
|
||||
/**
|
||||
* Memo namespace containing utility functions for memoization.
|
||||
*/
|
||||
export namespace memo {
|
||||
/**
|
||||
* Checks if an object is a memo scope.
|
||||
* @param obj - The object to check
|
||||
* @returns True if the object is a memo scope
|
||||
*/
|
||||
export function isScope(obj: any): boolean;
|
||||
/**
|
||||
* Creates a memoized function with optional cleanup support.
|
||||
*
|
||||
* @param fun - The function to memoize
|
||||
* @param options - Configuration options
|
||||
* @param options.signal - AbortSignal for cleanup
|
||||
* @param options.onlyLast - When true, only keeps the cache from the most recent call
|
||||
* @returns A memoized version of the function with a .clear() method
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const renderItems = memo.scope(function(items) {
|
||||
* return items.map(item =>
|
||||
* memo(item.id, () => el("div", item.name))
|
||||
* );
|
||||
* }, {
|
||||
* signal: controller.signal,
|
||||
* onlyLast: true
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function scope<F extends Function>(fun: F, options?: {
|
||||
signal?: AbortSignal;
|
||||
onlyLast?: boolean;
|
||||
}): F & {
|
||||
clear: () => void;
|
||||
};
|
||||
}
|
||||
/* TypeScript MEH */
|
||||
declare global {
|
||||
type ddeAppend<el> = (...nodes: (Node | string)[]) => el;
|
||||
|
505
dist/esm.js
vendored
505
dist/esm.js
vendored
@ -1,4 +1,5 @@
|
||||
// src/helpers.js
|
||||
var hasOwn = (...a) => Object.prototype.hasOwnProperty.call(...a);
|
||||
function isUndef(value) {
|
||||
return typeof value === "undefined";
|
||||
}
|
||||
@ -8,6 +9,9 @@ function isInstance(obj, cls) {
|
||||
function isProtoFrom(obj, cls) {
|
||||
return Object.prototype.isPrototypeOf.call(cls, obj);
|
||||
}
|
||||
function oCreate(proto = null, p = {}) {
|
||||
return Object.create(proto, p);
|
||||
}
|
||||
function oAssign(...o) {
|
||||
return Object.assign(...o);
|
||||
}
|
||||
@ -21,16 +25,6 @@ function onAbort(signal, listener) {
|
||||
signal.removeEventListener("abort", listener);
|
||||
};
|
||||
}
|
||||
function observedAttributes(instance, observedAttribute) {
|
||||
const { observedAttributes: observedAttributes3 = [] } = instance.constructor;
|
||||
return observedAttributes3.reduce(function(out, name) {
|
||||
out[kebabToCamel(name)] = observedAttribute(instance, name);
|
||||
return out;
|
||||
}, {});
|
||||
}
|
||||
function kebabToCamel(name) {
|
||||
return name.replace(/-./g, (x) => x[1].toUpperCase());
|
||||
}
|
||||
|
||||
// src/signals-lib/common.js
|
||||
var signals_global = {
|
||||
@ -68,6 +62,7 @@ var enviroment = {
|
||||
setDeleteAttr,
|
||||
ssr: "",
|
||||
D: globalThis.document,
|
||||
N: globalThis.Node,
|
||||
F: globalThis.DocumentFragment,
|
||||
H: globalThis.HTMLElement,
|
||||
S: globalThis.SVGElement,
|
||||
@ -88,6 +83,215 @@ var evc = "dde:connected";
|
||||
var evd = "dde:disconnected";
|
||||
var eva = "dde:attributeChanged";
|
||||
|
||||
// src/events-observer.js
|
||||
var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, {
|
||||
get() {
|
||||
return () => {
|
||||
};
|
||||
}
|
||||
});
|
||||
function connectionsChangesObserverConstructor() {
|
||||
const store = /* @__PURE__ */ new Map();
|
||||
let is_observing = false;
|
||||
const observerListener = (stop2) => function(mutations) {
|
||||
for (const mutation of mutations) {
|
||||
if (mutation.type !== "childList") continue;
|
||||
if (observerAdded(mutation.addedNodes, true)) {
|
||||
stop2();
|
||||
continue;
|
||||
}
|
||||
if (observerRemoved(mutation.removedNodes, true))
|
||||
stop2();
|
||||
}
|
||||
};
|
||||
const observer = new enviroment.M(observerListener(stop));
|
||||
return {
|
||||
/**
|
||||
* Creates an observer for a specific element
|
||||
* @param {Element} element - Element to observe
|
||||
* @returns {Function} Cleanup function
|
||||
*/
|
||||
observe(element) {
|
||||
const o = new enviroment.M(observerListener(() => {
|
||||
}));
|
||||
o.observe(element, { childList: true, subtree: true });
|
||||
return () => o.disconnect();
|
||||
},
|
||||
/**
|
||||
* Register a connection listener for an element
|
||||
* @param {Element} element - Element to watch
|
||||
* @param {Function} listener - Callback for connection event
|
||||
*/
|
||||
onConnected(element, listener) {
|
||||
start();
|
||||
const listeners = getElementStore(element);
|
||||
if (listeners.connected.has(listener)) return;
|
||||
listeners.connected.add(listener);
|
||||
listeners.length_c += 1;
|
||||
},
|
||||
/**
|
||||
* Unregister a connection listener
|
||||
* @param {Element} element - Element being watched
|
||||
* @param {Function} listener - Callback to remove
|
||||
*/
|
||||
offConnected(element, listener) {
|
||||
if (!store.has(element)) return;
|
||||
const ls = store.get(element);
|
||||
if (!ls.connected.has(listener)) return;
|
||||
ls.connected.delete(listener);
|
||||
ls.length_c -= 1;
|
||||
cleanWhenOff(element, ls);
|
||||
},
|
||||
/**
|
||||
* Register a disconnection listener for an element
|
||||
* @param {Element} element - Element to watch
|
||||
* @param {Function} listener - Callback for disconnection event
|
||||
*/
|
||||
onDisconnected(element, listener) {
|
||||
start();
|
||||
const listeners = getElementStore(element);
|
||||
if (listeners.disconnected.has(listener)) return;
|
||||
listeners.disconnected.add(listener);
|
||||
listeners.length_d += 1;
|
||||
},
|
||||
/**
|
||||
* Unregister a disconnection listener
|
||||
* @param {Element} element - Element being watched
|
||||
* @param {Function} listener - Callback to remove
|
||||
*/
|
||||
offDisconnected(element, listener) {
|
||||
if (!store.has(element)) return;
|
||||
const ls = store.get(element);
|
||||
ls.disconnected.delete(listener);
|
||||
ls.length_d -= 1;
|
||||
cleanWhenOff(element, ls);
|
||||
}
|
||||
};
|
||||
function cleanWhenOff(element, ls) {
|
||||
if (ls.length_c || ls.length_d)
|
||||
return;
|
||||
store.delete(element);
|
||||
stop();
|
||||
}
|
||||
function getElementStore(element) {
|
||||
if (store.has(element)) return store.get(element);
|
||||
const out = {
|
||||
connected: /* @__PURE__ */ new WeakSet(),
|
||||
length_c: 0,
|
||||
disconnected: /* @__PURE__ */ new WeakSet(),
|
||||
length_d: 0
|
||||
};
|
||||
store.set(element, out);
|
||||
return out;
|
||||
}
|
||||
function start() {
|
||||
if (is_observing) return;
|
||||
is_observing = true;
|
||||
observer.observe(enviroment.D.body, { childList: true, subtree: true });
|
||||
}
|
||||
function stop() {
|
||||
if (!is_observing || store.size) return;
|
||||
is_observing = false;
|
||||
observer.disconnect();
|
||||
}
|
||||
function requestIdle() {
|
||||
return new Promise(function(resolve) {
|
||||
(requestIdleCallback || requestAnimationFrame)(resolve);
|
||||
});
|
||||
}
|
||||
async function collectChildren(element) {
|
||||
if (store.size > 30)
|
||||
await requestIdle();
|
||||
const out = [];
|
||||
if (!isInstance(element, enviroment.N)) return out;
|
||||
for (const el of store.keys()) {
|
||||
if (el === element || !isInstance(el, enviroment.N)) continue;
|
||||
if (element.contains(el))
|
||||
out.push(el);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function observerAdded(addedNodes, is_root) {
|
||||
let out = false;
|
||||
for (const element of addedNodes) {
|
||||
if (is_root) collectChildren(element).then(observerAdded);
|
||||
if (!store.has(element)) continue;
|
||||
const ls = store.get(element);
|
||||
if (!ls.length_c) continue;
|
||||
element.dispatchEvent(new Event(evc));
|
||||
ls.connected = /* @__PURE__ */ new WeakSet();
|
||||
ls.length_c = 0;
|
||||
if (!ls.length_d) store.delete(element);
|
||||
out = true;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function observerRemoved(removedNodes, is_root) {
|
||||
let out = false;
|
||||
for (const element of removedNodes) {
|
||||
if (is_root) collectChildren(element).then(observerRemoved);
|
||||
if (!store.has(element)) continue;
|
||||
const ls = store.get(element);
|
||||
if (!ls.length_d) continue;
|
||||
(globalThis.queueMicrotask || setTimeout)(dispatchRemove(element));
|
||||
out = true;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function dispatchRemove(element) {
|
||||
return () => {
|
||||
if (element.isConnected) return;
|
||||
element.dispatchEvent(new Event(evd));
|
||||
store.delete(element);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// src/events.js
|
||||
function dispatchEvent(name, options, host) {
|
||||
if (typeof options === "function") {
|
||||
host = options;
|
||||
options = null;
|
||||
}
|
||||
if (!options) options = {};
|
||||
return function dispatch(element, ...d) {
|
||||
if (host) {
|
||||
d.unshift(element);
|
||||
element = typeof host === "function" ? host() : host;
|
||||
}
|
||||
const event = d.length ? new CustomEvent(name, oAssign({ detail: d[0] }, options)) : new Event(name, options);
|
||||
return element.dispatchEvent(event);
|
||||
};
|
||||
}
|
||||
function on(event, listener, options) {
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(event, listener, options);
|
||||
return element;
|
||||
};
|
||||
}
|
||||
var lifeOptions = (obj) => oAssign({}, typeof obj === "object" ? obj : null, { once: true });
|
||||
on.connected = function(listener, options) {
|
||||
options = lifeOptions(options);
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(evc, listener, options);
|
||||
if (element[keyLTE]) return element;
|
||||
if (element.isConnected) return element.dispatchEvent(new Event(evc)), element;
|
||||
const c = onAbort(options.signal, () => c_ch_o.offConnected(element, listener));
|
||||
if (c) c_ch_o.onConnected(element, listener);
|
||||
return element;
|
||||
};
|
||||
};
|
||||
on.disconnected = function(listener, options) {
|
||||
options = lifeOptions(options);
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(evd, listener, options);
|
||||
if (element[keyLTE]) return element;
|
||||
const c = onAbort(options.signal, () => c_ch_o.offDisconnected(element, listener));
|
||||
if (c) c_ch_o.onDisconnected(element, listener);
|
||||
return element;
|
||||
};
|
||||
};
|
||||
|
||||
// src/dom.js
|
||||
function queue(promise) {
|
||||
return enviroment.q(promise);
|
||||
@ -99,6 +303,7 @@ var scopes = [{
|
||||
host: (c) => c ? c(enviroment.D.body) : enviroment.D.body,
|
||||
prevent: true
|
||||
}];
|
||||
var store_abort = /* @__PURE__ */ new WeakMap();
|
||||
var scope = {
|
||||
/**
|
||||
* Gets the current scope
|
||||
@ -115,6 +320,17 @@ var scope = {
|
||||
return this.current.host;
|
||||
},
|
||||
/**
|
||||
* Creates/gets an AbortController that triggers when the element disconnects
|
||||
* */
|
||||
get signal() {
|
||||
const { host } = this;
|
||||
if (store_abort.has(host)) return store_abort.get(host);
|
||||
const a = new AbortController();
|
||||
store_abort.set(host, a);
|
||||
host(on.disconnected(() => a.abort()));
|
||||
return a.signal;
|
||||
},
|
||||
/**
|
||||
* Prevents default behavior in the current scope
|
||||
* @returns {Object} Current scope context
|
||||
*/
|
||||
@ -357,172 +573,8 @@ function setDelete(obj, key, val) {
|
||||
return Reflect.deleteProperty(obj, key);
|
||||
}
|
||||
|
||||
// src/events-observer.js
|
||||
var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, {
|
||||
get() {
|
||||
return () => {
|
||||
};
|
||||
}
|
||||
});
|
||||
function connectionsChangesObserverConstructor() {
|
||||
const store = /* @__PURE__ */ new Map();
|
||||
let is_observing = false;
|
||||
const observerListener = (stop2) => function(mutations) {
|
||||
for (const mutation of mutations) {
|
||||
if (mutation.type !== "childList") continue;
|
||||
if (observerAdded(mutation.addedNodes, true)) {
|
||||
stop2();
|
||||
continue;
|
||||
}
|
||||
if (observerRemoved(mutation.removedNodes, true))
|
||||
stop2();
|
||||
}
|
||||
};
|
||||
const observer = new enviroment.M(observerListener(stop));
|
||||
return {
|
||||
/**
|
||||
* Creates an observer for a specific element
|
||||
* @param {Element} element - Element to observe
|
||||
* @returns {Function} Cleanup function
|
||||
*/
|
||||
observe(element) {
|
||||
const o = new enviroment.M(observerListener(() => {
|
||||
}));
|
||||
o.observe(element, { childList: true, subtree: true });
|
||||
return () => o.disconnect();
|
||||
},
|
||||
/**
|
||||
* Register a connection listener for an element
|
||||
* @param {Element} element - Element to watch
|
||||
* @param {Function} listener - Callback for connection event
|
||||
*/
|
||||
onConnected(element, listener) {
|
||||
start();
|
||||
const listeners = getElementStore(element);
|
||||
if (listeners.connected.has(listener)) return;
|
||||
listeners.connected.add(listener);
|
||||
listeners.length_c += 1;
|
||||
},
|
||||
/**
|
||||
* Unregister a connection listener
|
||||
* @param {Element} element - Element being watched
|
||||
* @param {Function} listener - Callback to remove
|
||||
*/
|
||||
offConnected(element, listener) {
|
||||
if (!store.has(element)) return;
|
||||
const ls = store.get(element);
|
||||
if (!ls.connected.has(listener)) return;
|
||||
ls.connected.delete(listener);
|
||||
ls.length_c -= 1;
|
||||
cleanWhenOff(element, ls);
|
||||
},
|
||||
/**
|
||||
* Register a disconnection listener for an element
|
||||
* @param {Element} element - Element to watch
|
||||
* @param {Function} listener - Callback for disconnection event
|
||||
*/
|
||||
onDisconnected(element, listener) {
|
||||
start();
|
||||
const listeners = getElementStore(element);
|
||||
if (listeners.disconnected.has(listener)) return;
|
||||
listeners.disconnected.add(listener);
|
||||
listeners.length_d += 1;
|
||||
},
|
||||
/**
|
||||
* Unregister a disconnection listener
|
||||
* @param {Element} element - Element being watched
|
||||
* @param {Function} listener - Callback to remove
|
||||
*/
|
||||
offDisconnected(element, listener) {
|
||||
if (!store.has(element)) return;
|
||||
const ls = store.get(element);
|
||||
ls.disconnected.delete(listener);
|
||||
ls.length_d -= 1;
|
||||
cleanWhenOff(element, ls);
|
||||
}
|
||||
};
|
||||
function cleanWhenOff(element, ls) {
|
||||
if (ls.length_c || ls.length_d)
|
||||
return;
|
||||
store.delete(element);
|
||||
stop();
|
||||
}
|
||||
function getElementStore(element) {
|
||||
if (store.has(element)) return store.get(element);
|
||||
const out = {
|
||||
connected: /* @__PURE__ */ new WeakSet(),
|
||||
length_c: 0,
|
||||
disconnected: /* @__PURE__ */ new WeakSet(),
|
||||
length_d: 0
|
||||
};
|
||||
store.set(element, out);
|
||||
return out;
|
||||
}
|
||||
function start() {
|
||||
if (is_observing) return;
|
||||
is_observing = true;
|
||||
observer.observe(enviroment.D.body, { childList: true, subtree: true });
|
||||
}
|
||||
function stop() {
|
||||
if (!is_observing || store.size) return;
|
||||
is_observing = false;
|
||||
observer.disconnect();
|
||||
}
|
||||
function requestIdle() {
|
||||
return new Promise(function(resolve) {
|
||||
(requestIdleCallback || requestAnimationFrame)(resolve);
|
||||
});
|
||||
}
|
||||
async function collectChildren(element) {
|
||||
if (store.size > 30)
|
||||
await requestIdle();
|
||||
const out = [];
|
||||
if (!isInstance(element, Node)) return out;
|
||||
for (const el of store.keys()) {
|
||||
if (el === element || !isInstance(el, Node)) continue;
|
||||
if (element.contains(el))
|
||||
out.push(el);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function observerAdded(addedNodes, is_root) {
|
||||
let out = false;
|
||||
for (const element of addedNodes) {
|
||||
if (is_root) collectChildren(element).then(observerAdded);
|
||||
if (!store.has(element)) continue;
|
||||
const ls = store.get(element);
|
||||
if (!ls.length_c) continue;
|
||||
element.dispatchEvent(new Event(evc));
|
||||
ls.connected = /* @__PURE__ */ new WeakSet();
|
||||
ls.length_c = 0;
|
||||
if (!ls.length_d) store.delete(element);
|
||||
out = true;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function observerRemoved(removedNodes, is_root) {
|
||||
let out = false;
|
||||
for (const element of removedNodes) {
|
||||
if (is_root) collectChildren(element).then(observerRemoved);
|
||||
if (!store.has(element)) continue;
|
||||
const ls = store.get(element);
|
||||
if (!ls.length_d) continue;
|
||||
(globalThis.queueMicrotask || setTimeout)(dispatchRemove(element));
|
||||
out = true;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function dispatchRemove(element) {
|
||||
return () => {
|
||||
if (element.isConnected) return;
|
||||
element.dispatchEvent(new Event(evd));
|
||||
store.delete(element);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// src/customElement.js
|
||||
function customElementRender(target, render, props = observedAttributes2) {
|
||||
function customElementRender(target, render, props = {}) {
|
||||
const custom_element = target.host || target;
|
||||
scope.push({
|
||||
scope: custom_element,
|
||||
@ -563,81 +615,40 @@ function wrapMethod(obj, method, apply) {
|
||||
obj[method] = new Proxy(obj[method] || (() => {
|
||||
}), { apply });
|
||||
}
|
||||
function observedAttributes2(instance) {
|
||||
return observedAttributes(instance, (i, n) => i.getAttribute(n));
|
||||
}
|
||||
|
||||
// src/events.js
|
||||
function dispatchEvent(name, options, host) {
|
||||
if (typeof options === "function") {
|
||||
host = options;
|
||||
options = null;
|
||||
}
|
||||
if (!options) options = {};
|
||||
return function dispatch(element, ...d) {
|
||||
if (host) {
|
||||
d.unshift(element);
|
||||
element = typeof host === "function" ? host() : host;
|
||||
}
|
||||
const event = d.length ? new CustomEvent(name, oAssign({ detail: d[0] }, options)) : new Event(name, options);
|
||||
return element.dispatchEvent(event);
|
||||
};
|
||||
// src/memo.js
|
||||
var memoMark = "__dde_memo";
|
||||
var memo_scope = [];
|
||||
function memo(key, generator) {
|
||||
if (!memo_scope.length) return generator(key);
|
||||
const k = typeof key === "object" ? JSON.stringify(key) : key;
|
||||
const [{ cache, after }] = memo_scope;
|
||||
return after(k, hasOwn(cache, k) ? cache[k] : generator(key));
|
||||
}
|
||||
function on(event, listener, options) {
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(event, listener, options);
|
||||
return element;
|
||||
};
|
||||
}
|
||||
var lifeOptions = (obj) => oAssign({}, typeof obj === "object" ? obj : null, { once: true });
|
||||
on.connected = function(listener, options) {
|
||||
options = lifeOptions(options);
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(evc, listener, options);
|
||||
if (element[keyLTE]) return element;
|
||||
if (element.isConnected) return element.dispatchEvent(new Event(evc)), element;
|
||||
const c = onAbort(options.signal, () => c_ch_o.offConnected(element, listener));
|
||||
if (c) c_ch_o.onConnected(element, listener);
|
||||
return element;
|
||||
};
|
||||
memo.isScope = function(obj) {
|
||||
return obj[memoMark];
|
||||
};
|
||||
on.disconnected = function(listener, options) {
|
||||
options = lifeOptions(options);
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(evd, listener, options);
|
||||
if (element[keyLTE]) return element;
|
||||
const c = onAbort(options.signal, () => c_ch_o.offDisconnected(element, listener));
|
||||
if (c) c_ch_o.onDisconnected(element, listener);
|
||||
return element;
|
||||
};
|
||||
};
|
||||
var store_abort = /* @__PURE__ */ new WeakMap();
|
||||
on.disconnectedAsAbort = function(host) {
|
||||
if (store_abort.has(host)) return store_abort.get(host);
|
||||
const a = new AbortController();
|
||||
store_abort.set(host, a);
|
||||
host(on.disconnected(() => a.abort()));
|
||||
return a.signal;
|
||||
};
|
||||
var els_attribute_store = /* @__PURE__ */ new WeakSet();
|
||||
on.attributeChanged = function(listener, options) {
|
||||
if (typeof options !== "object")
|
||||
options = {};
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(eva, listener, options);
|
||||
if (element[keyLTE] || els_attribute_store.has(element))
|
||||
return element;
|
||||
if (!enviroment.M) return element;
|
||||
const observer = new enviroment.M(function(mutations) {
|
||||
for (const { attributeName, target } of mutations)
|
||||
target.dispatchEvent(
|
||||
new CustomEvent(eva, { detail: [attributeName, target.getAttribute(attributeName)] })
|
||||
);
|
||||
memo.scope = function memoScope(fun, { signal, onlyLast } = {}) {
|
||||
let cache = oCreate();
|
||||
function memoScope2(...args) {
|
||||
if (signal && signal.aborted)
|
||||
return fun.apply(this, args);
|
||||
let cache_local = onlyLast ? cache : oCreate();
|
||||
memo_scope.unshift({
|
||||
cache,
|
||||
after(key, val) {
|
||||
return cache_local[key] = val;
|
||||
}
|
||||
});
|
||||
const c = onAbort(options.signal, () => observer.disconnect());
|
||||
if (c) observer.observe(element, { attributes: true });
|
||||
return element;
|
||||
};
|
||||
const out = fun.apply(this, args);
|
||||
memo_scope.shift();
|
||||
cache = cache_local;
|
||||
return out;
|
||||
}
|
||||
memoScope2[memoMark] = true;
|
||||
memoScope2.clear = () => cache = oCreate();
|
||||
if (signal) signal.addEventListener("abort", memoScope2.clear);
|
||||
return memoScope2;
|
||||
};
|
||||
export {
|
||||
assign,
|
||||
@ -653,7 +664,7 @@ export {
|
||||
createElementNS as elNS,
|
||||
elementAttribute,
|
||||
lifecyclesToEvents,
|
||||
observedAttributes2 as observedAttributes,
|
||||
memo,
|
||||
on,
|
||||
queue,
|
||||
registerReactivity,
|
||||
|
80
dist/esm.min.d.ts
vendored
80
dist/esm.min.d.ts
vendored
@ -18,6 +18,7 @@ export type Actions<V> = Record<string | SymbolOnclear, Action<V>>;
|
||||
export type OnListenerOptions = Pick<AddEventListenerOptions, "signal"> & {
|
||||
first_time?: boolean;
|
||||
};
|
||||
export type SElement = Node | Element | DocumentFragment | ddeHTMLElement | ddeSVGElement | ddeDocumentFragment;
|
||||
export interface signal {
|
||||
_: Symbol;
|
||||
/**
|
||||
@ -63,7 +64,7 @@ export interface signal {
|
||||
* S.el(listS, list=> list.map(li=> el("li", li)));
|
||||
* ```
|
||||
* */
|
||||
el<S extends any>(signal: Signal<S, any>, el: (v: S) => Element | Element[] | DocumentFragment): DocumentFragment;
|
||||
el<S extends any>(signal: Signal<S, any>, el: (v: S) => SElement | SElement[]): DocumentFragment;
|
||||
observedAttributes(custom_element: HTMLElement): Record<string, Signal<string, {}>>;
|
||||
}
|
||||
declare const signal: signal;
|
||||
@ -79,10 +80,11 @@ export type CustomElementTagNameMap = {
|
||||
export type SupportedElement = HTMLElementTagNameMap[keyof HTMLElementTagNameMap] | SVGElementTagNameMap[keyof SVGElementTagNameMap] | MathMLElementTagNameMap[keyof MathMLElementTagNameMap] | CustomElementTagNameMap[keyof CustomElementTagNameMap];
|
||||
declare global {
|
||||
type ddeComponentAttributes = Record<any, any> | undefined;
|
||||
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node> = (element: El) => any;
|
||||
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node> = (element: El, ...rest: any) => any;
|
||||
type ddeString = string | Signal<string, {}>;
|
||||
type ddeStringable = ddeString | number | Signal<number, {}>;
|
||||
}
|
||||
export type Host<EL extends SupportedElement> = (...addons: ddeElementAddon<EL>[]) => EL;
|
||||
export type PascalCase = `${Capitalize<string>}${string}`;
|
||||
export type AttrsModified = {
|
||||
/**
|
||||
@ -144,6 +146,7 @@ export namespace el {
|
||||
host?: "this" | "parentElement";
|
||||
}, is_open?: boolean): Comment;
|
||||
}
|
||||
export function chainableAppend<EL extends SupportedElement>(el: EL): EL | ddeHTMLElement;
|
||||
export function el<A extends ddeComponentAttributes, EL extends SupportedElement | ddeDocumentFragment>(component: (attr: A, ...rest: any[]) => EL, attrs?: NoInfer<A>, ...addons: ddeElementAddon<EL>[]): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] ? EL : (EL extends ddeDocumentFragment ? EL : ddeHTMLElement);
|
||||
export function el<A extends {
|
||||
textContent: ddeStringable;
|
||||
@ -156,7 +159,6 @@ export function elNS(namespace: "http://www.w3.org/1998/Math/MathML"): <TAG exte
|
||||
[key in keyof EL]: EL[key] | Signal<EL[key], {}> | string | number | boolean;
|
||||
}>, ...addons: ddeElementAddon<NoInfer<EL>>[]) => ddeMathMLElement;
|
||||
export function elNS(namespace: string): (tag_name: string, attrs?: string | ddeStringable | Record<string, any>, ...addons: ddeElementAddon<SupportedElement>[]) => SupportedElement;
|
||||
export function chainableAppend<EL extends SupportedElement>(el: EL): EL;
|
||||
/** Simulate slots for ddeComponents */
|
||||
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(root: EL): EL;
|
||||
/**
|
||||
@ -165,9 +167,9 @@ export function simulateSlots<EL extends SupportedElement | DocumentFragment>(ro
|
||||
* @param body Body of the custom element
|
||||
* */
|
||||
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(el: HTMLElement, body: EL): EL;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, element: SupportedElement): (data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, host: Host<SupportedElement>): (data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?: EventInit): (element: SupportedElement, data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, element: SupportedElement | (() => SupportedElement)): (data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
|
||||
export interface On {
|
||||
/** Listens to the DOM event. See {@link Document.addEventListener} */
|
||||
<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE;
|
||||
@ -185,7 +187,7 @@ export interface On {
|
||||
export const on: On;
|
||||
export type Scope = {
|
||||
scope: Node | Function | Object;
|
||||
host: ddeElementAddon<any>;
|
||||
host: Host<SupportedElement>;
|
||||
custom_element: false | HTMLElement;
|
||||
prevent: boolean;
|
||||
};
|
||||
@ -201,7 +203,11 @@ export const scope: {
|
||||
* It can be also used to register Addon(s) (functions to be called when component is initized)
|
||||
* — `scope.host(on.connected(console.log))`.
|
||||
* */
|
||||
host: (...addons: ddeElementAddon<SupportedElement>[]) => HTMLElement;
|
||||
host: Host<SupportedElement>;
|
||||
/**
|
||||
* Creates/gets an AbortController that triggers when the element disconnects
|
||||
* */
|
||||
signal: AbortSignal;
|
||||
state: Scope[];
|
||||
/** Adds new child scope. All attributes are inherited by default. */
|
||||
push(scope?: Partial<Scope>): ReturnType<Array<Scope>["push"]>;
|
||||
@ -213,7 +219,6 @@ export const scope: {
|
||||
export function customElementRender<EL extends HTMLElement, P extends any = Record<string, string | Signal<string, {}>>>(target: ShadowRoot | EL, render: (props: P) => SupportedElement | DocumentFragment, props?: P | ((el: EL) => P)): EL;
|
||||
export function customElementWithDDE<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||
export function lifecyclesToEvents<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||
export function observedAttributes(custom_element: HTMLElement): Record<string, string>;
|
||||
/**
|
||||
* This is used primarly for server side rendering. To be sure that all async operations
|
||||
* are finished before the page is sent to the client.
|
||||
@ -234,6 +239,65 @@ export function observedAttributes(custom_element: HTMLElement): Record<string,
|
||||
* ```
|
||||
* */
|
||||
export function queue(promise?: Promise<unknown>): Promise<unknown>;
|
||||
/**
|
||||
* Memoization utility for caching DOM elements to improve performance.
|
||||
* Used to prevent unnecessary recreation of elements when rendering lists or complex components.
|
||||
*
|
||||
* @param key - Unique identifier for the element (usually an ID or unique value)
|
||||
* @param generator - Function that creates the element
|
||||
* @returns The cached element if the key exists, otherwise the result of the generator function
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Within S.el for list rendering
|
||||
* S.el(itemsSignal, (items, memo) =>
|
||||
* el("ul").append(
|
||||
* ...items.map(item =>
|
||||
* memo(item.id, () => el(ItemComponent, item))
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
export function memo<T>(key: string | number | object, generator: (key: any) => T): T;
|
||||
/**
|
||||
* Memo namespace containing utility functions for memoization.
|
||||
*/
|
||||
export namespace memo {
|
||||
/**
|
||||
* Checks if an object is a memo scope.
|
||||
* @param obj - The object to check
|
||||
* @returns True if the object is a memo scope
|
||||
*/
|
||||
export function isScope(obj: any): boolean;
|
||||
/**
|
||||
* Creates a memoized function with optional cleanup support.
|
||||
*
|
||||
* @param fun - The function to memoize
|
||||
* @param options - Configuration options
|
||||
* @param options.signal - AbortSignal for cleanup
|
||||
* @param options.onlyLast - When true, only keeps the cache from the most recent call
|
||||
* @returns A memoized version of the function with a .clear() method
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const renderItems = memo.scope(function(items) {
|
||||
* return items.map(item =>
|
||||
* memo(item.id, () => el("div", item.name))
|
||||
* );
|
||||
* }, {
|
||||
* signal: controller.signal,
|
||||
* onlyLast: true
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function scope<F extends Function>(fun: F, options?: {
|
||||
signal?: AbortSignal;
|
||||
onlyLast?: boolean;
|
||||
}): F & {
|
||||
clear: () => void;
|
||||
};
|
||||
}
|
||||
/* TypeScript MEH */
|
||||
declare global {
|
||||
type ddeAppend<el> = (...nodes: (Node | string)[]) => el;
|
||||
|
2
dist/esm.min.js
vendored
2
dist/esm.min.js
vendored
File diff suppressed because one or more lines are too long
80
dist/iife-with-signals.d.ts
vendored
80
dist/iife-with-signals.d.ts
vendored
@ -18,6 +18,7 @@ export type Actions<V> = Record<string | SymbolOnclear, Action<V>>;
|
||||
export type OnListenerOptions = Pick<AddEventListenerOptions, "signal"> & {
|
||||
first_time?: boolean;
|
||||
};
|
||||
export type SElement = Node | Element | DocumentFragment | ddeHTMLElement | ddeSVGElement | ddeDocumentFragment;
|
||||
export interface signal {
|
||||
_: Symbol;
|
||||
/**
|
||||
@ -63,7 +64,7 @@ export interface signal {
|
||||
* S.el(listS, list=> list.map(li=> el("li", li)));
|
||||
* ```
|
||||
* */
|
||||
el<S extends any>(signal: Signal<S, any>, el: (v: S) => Element | Element[] | DocumentFragment): DocumentFragment;
|
||||
el<S extends any>(signal: Signal<S, any>, el: (v: S) => SElement | SElement[]): DocumentFragment;
|
||||
observedAttributes(custom_element: HTMLElement): Record<string, Signal<string, {}>>;
|
||||
}
|
||||
export const signal: signal;
|
||||
@ -80,10 +81,11 @@ export type CustomElementTagNameMap = {
|
||||
export type SupportedElement = HTMLElementTagNameMap[keyof HTMLElementTagNameMap] | SVGElementTagNameMap[keyof SVGElementTagNameMap] | MathMLElementTagNameMap[keyof MathMLElementTagNameMap] | CustomElementTagNameMap[keyof CustomElementTagNameMap];
|
||||
declare global {
|
||||
type ddeComponentAttributes = Record<any, any> | undefined;
|
||||
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node> = (element: El) => any;
|
||||
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node> = (element: El, ...rest: any) => any;
|
||||
type ddeString = string | Signal<string, {}>;
|
||||
type ddeStringable = ddeString | number | Signal<number, {}>;
|
||||
}
|
||||
export type Host<EL extends SupportedElement> = (...addons: ddeElementAddon<EL>[]) => EL;
|
||||
export type PascalCase = `${Capitalize<string>}${string}`;
|
||||
export type AttrsModified = {
|
||||
/**
|
||||
@ -145,6 +147,7 @@ export namespace el {
|
||||
host?: "this" | "parentElement";
|
||||
}, is_open?: boolean): Comment;
|
||||
}
|
||||
export function chainableAppend<EL extends SupportedElement>(el: EL): EL | ddeHTMLElement;
|
||||
export function el<A extends ddeComponentAttributes, EL extends SupportedElement | ddeDocumentFragment>(component: (attr: A, ...rest: any[]) => EL, attrs?: NoInfer<A>, ...addons: ddeElementAddon<EL>[]): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] ? EL : (EL extends ddeDocumentFragment ? EL : ddeHTMLElement);
|
||||
export function el<A extends {
|
||||
textContent: ddeStringable;
|
||||
@ -157,7 +160,6 @@ export function elNS(namespace: "http://www.w3.org/1998/Math/MathML"): <TAG exte
|
||||
[key in keyof EL]: EL[key] | Signal<EL[key], {}> | string | number | boolean;
|
||||
}>, ...addons: ddeElementAddon<NoInfer<EL>>[]) => ddeMathMLElement;
|
||||
export function elNS(namespace: string): (tag_name: string, attrs?: string | ddeStringable | Record<string, any>, ...addons: ddeElementAddon<SupportedElement>[]) => SupportedElement;
|
||||
export function chainableAppend<EL extends SupportedElement>(el: EL): EL;
|
||||
/** Simulate slots for ddeComponents */
|
||||
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(root: EL): EL;
|
||||
/**
|
||||
@ -166,9 +168,9 @@ export function simulateSlots<EL extends SupportedElement | DocumentFragment>(ro
|
||||
* @param body Body of the custom element
|
||||
* */
|
||||
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(el: HTMLElement, body: EL): EL;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, element: SupportedElement): (data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, host: Host<SupportedElement>): (data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?: EventInit): (element: SupportedElement, data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, element: SupportedElement | (() => SupportedElement)): (data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
|
||||
export interface On {
|
||||
/** Listens to the DOM event. See {@link Document.addEventListener} */
|
||||
<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE;
|
||||
@ -186,7 +188,7 @@ export interface On {
|
||||
export const on: On;
|
||||
export type Scope = {
|
||||
scope: Node | Function | Object;
|
||||
host: ddeElementAddon<any>;
|
||||
host: Host<SupportedElement>;
|
||||
custom_element: false | HTMLElement;
|
||||
prevent: boolean;
|
||||
};
|
||||
@ -202,7 +204,11 @@ export const scope: {
|
||||
* It can be also used to register Addon(s) (functions to be called when component is initized)
|
||||
* — `scope.host(on.connected(console.log))`.
|
||||
* */
|
||||
host: (...addons: ddeElementAddon<SupportedElement>[]) => HTMLElement;
|
||||
host: Host<SupportedElement>;
|
||||
/**
|
||||
* Creates/gets an AbortController that triggers when the element disconnects
|
||||
* */
|
||||
signal: AbortSignal;
|
||||
state: Scope[];
|
||||
/** Adds new child scope. All attributes are inherited by default. */
|
||||
push(scope?: Partial<Scope>): ReturnType<Array<Scope>["push"]>;
|
||||
@ -214,7 +220,6 @@ export const scope: {
|
||||
export function customElementRender<EL extends HTMLElement, P extends any = Record<string, string | Signal<string, {}>>>(target: ShadowRoot | EL, render: (props: P) => SupportedElement | DocumentFragment, props?: P | ((el: EL) => P)): EL;
|
||||
export function customElementWithDDE<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||
export function lifecyclesToEvents<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||
export function observedAttributes(custom_element: HTMLElement): Record<string, string>;
|
||||
/**
|
||||
* This is used primarly for server side rendering. To be sure that all async operations
|
||||
* are finished before the page is sent to the client.
|
||||
@ -235,6 +240,65 @@ export function observedAttributes(custom_element: HTMLElement): Record<string,
|
||||
* ```
|
||||
* */
|
||||
export function queue(promise?: Promise<unknown>): Promise<unknown>;
|
||||
/**
|
||||
* Memoization utility for caching DOM elements to improve performance.
|
||||
* Used to prevent unnecessary recreation of elements when rendering lists or complex components.
|
||||
*
|
||||
* @param key - Unique identifier for the element (usually an ID or unique value)
|
||||
* @param generator - Function that creates the element
|
||||
* @returns The cached element if the key exists, otherwise the result of the generator function
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Within S.el for list rendering
|
||||
* S.el(itemsSignal, (items, memo) =>
|
||||
* el("ul").append(
|
||||
* ...items.map(item =>
|
||||
* memo(item.id, () => el(ItemComponent, item))
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
export function memo<T>(key: string | number | object, generator: (key: any) => T): T;
|
||||
/**
|
||||
* Memo namespace containing utility functions for memoization.
|
||||
*/
|
||||
export namespace memo {
|
||||
/**
|
||||
* Checks if an object is a memo scope.
|
||||
* @param obj - The object to check
|
||||
* @returns True if the object is a memo scope
|
||||
*/
|
||||
export function isScope(obj: any): boolean;
|
||||
/**
|
||||
* Creates a memoized function with optional cleanup support.
|
||||
*
|
||||
* @param fun - The function to memoize
|
||||
* @param options - Configuration options
|
||||
* @param options.signal - AbortSignal for cleanup
|
||||
* @param options.onlyLast - When true, only keeps the cache from the most recent call
|
||||
* @returns A memoized version of the function with a .clear() method
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const renderItems = memo.scope(function(items) {
|
||||
* return items.map(item =>
|
||||
* memo(item.id, () => el("div", item.name))
|
||||
* );
|
||||
* }, {
|
||||
* signal: controller.signal,
|
||||
* onlyLast: true
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function scope<F extends Function>(fun: F, options?: {
|
||||
signal?: AbortSignal;
|
||||
onlyLast?: boolean;
|
||||
}): F & {
|
||||
clear: () => void;
|
||||
};
|
||||
}
|
||||
/* TypeScript MEH */
|
||||
declare global {
|
||||
type ddeAppend<el> = (...nodes: (Node | string)[]) => el;
|
||||
|
510
dist/iife-with-signals.js
vendored
510
dist/iife-with-signals.js
vendored
@ -35,7 +35,7 @@ var DDE = (() => {
|
||||
elementAttribute: () => elementAttribute,
|
||||
isSignal: () => isSignal,
|
||||
lifecyclesToEvents: () => lifecyclesToEvents,
|
||||
observedAttributes: () => observedAttributes2,
|
||||
memo: () => memo,
|
||||
on: () => on,
|
||||
queue: () => queue,
|
||||
registerReactivity: () => registerReactivity,
|
||||
@ -78,8 +78,8 @@ var DDE = (() => {
|
||||
};
|
||||
}
|
||||
function observedAttributes(instance, observedAttribute2) {
|
||||
const { observedAttributes: observedAttributes3 = [] } = instance.constructor;
|
||||
return observedAttributes3.reduce(function(out, name) {
|
||||
const { observedAttributes: observedAttributes2 = [] } = instance.constructor;
|
||||
return observedAttributes2.reduce(function(out, name) {
|
||||
out[kebabToCamel(name)] = observedAttribute2(instance, name);
|
||||
return out;
|
||||
}, {});
|
||||
@ -137,6 +137,7 @@ var DDE = (() => {
|
||||
setDeleteAttr,
|
||||
ssr: "",
|
||||
D: globalThis.document,
|
||||
N: globalThis.Node,
|
||||
F: globalThis.DocumentFragment,
|
||||
H: globalThis.HTMLElement,
|
||||
S: globalThis.SVGElement,
|
||||
@ -157,6 +158,215 @@ var DDE = (() => {
|
||||
var evd = "dde:disconnected";
|
||||
var eva = "dde:attributeChanged";
|
||||
|
||||
// src/events-observer.js
|
||||
var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, {
|
||||
get() {
|
||||
return () => {
|
||||
};
|
||||
}
|
||||
});
|
||||
function connectionsChangesObserverConstructor() {
|
||||
const store = /* @__PURE__ */ new Map();
|
||||
let is_observing = false;
|
||||
const observerListener = (stop2) => function(mutations) {
|
||||
for (const mutation of mutations) {
|
||||
if (mutation.type !== "childList") continue;
|
||||
if (observerAdded(mutation.addedNodes, true)) {
|
||||
stop2();
|
||||
continue;
|
||||
}
|
||||
if (observerRemoved(mutation.removedNodes, true))
|
||||
stop2();
|
||||
}
|
||||
};
|
||||
const observer = new enviroment.M(observerListener(stop));
|
||||
return {
|
||||
/**
|
||||
* Creates an observer for a specific element
|
||||
* @param {Element} element - Element to observe
|
||||
* @returns {Function} Cleanup function
|
||||
*/
|
||||
observe(element) {
|
||||
const o = new enviroment.M(observerListener(() => {
|
||||
}));
|
||||
o.observe(element, { childList: true, subtree: true });
|
||||
return () => o.disconnect();
|
||||
},
|
||||
/**
|
||||
* Register a connection listener for an element
|
||||
* @param {Element} element - Element to watch
|
||||
* @param {Function} listener - Callback for connection event
|
||||
*/
|
||||
onConnected(element, listener) {
|
||||
start();
|
||||
const listeners = getElementStore(element);
|
||||
if (listeners.connected.has(listener)) return;
|
||||
listeners.connected.add(listener);
|
||||
listeners.length_c += 1;
|
||||
},
|
||||
/**
|
||||
* Unregister a connection listener
|
||||
* @param {Element} element - Element being watched
|
||||
* @param {Function} listener - Callback to remove
|
||||
*/
|
||||
offConnected(element, listener) {
|
||||
if (!store.has(element)) return;
|
||||
const ls = store.get(element);
|
||||
if (!ls.connected.has(listener)) return;
|
||||
ls.connected.delete(listener);
|
||||
ls.length_c -= 1;
|
||||
cleanWhenOff(element, ls);
|
||||
},
|
||||
/**
|
||||
* Register a disconnection listener for an element
|
||||
* @param {Element} element - Element to watch
|
||||
* @param {Function} listener - Callback for disconnection event
|
||||
*/
|
||||
onDisconnected(element, listener) {
|
||||
start();
|
||||
const listeners = getElementStore(element);
|
||||
if (listeners.disconnected.has(listener)) return;
|
||||
listeners.disconnected.add(listener);
|
||||
listeners.length_d += 1;
|
||||
},
|
||||
/**
|
||||
* Unregister a disconnection listener
|
||||
* @param {Element} element - Element being watched
|
||||
* @param {Function} listener - Callback to remove
|
||||
*/
|
||||
offDisconnected(element, listener) {
|
||||
if (!store.has(element)) return;
|
||||
const ls = store.get(element);
|
||||
ls.disconnected.delete(listener);
|
||||
ls.length_d -= 1;
|
||||
cleanWhenOff(element, ls);
|
||||
}
|
||||
};
|
||||
function cleanWhenOff(element, ls) {
|
||||
if (ls.length_c || ls.length_d)
|
||||
return;
|
||||
store.delete(element);
|
||||
stop();
|
||||
}
|
||||
function getElementStore(element) {
|
||||
if (store.has(element)) return store.get(element);
|
||||
const out = {
|
||||
connected: /* @__PURE__ */ new WeakSet(),
|
||||
length_c: 0,
|
||||
disconnected: /* @__PURE__ */ new WeakSet(),
|
||||
length_d: 0
|
||||
};
|
||||
store.set(element, out);
|
||||
return out;
|
||||
}
|
||||
function start() {
|
||||
if (is_observing) return;
|
||||
is_observing = true;
|
||||
observer.observe(enviroment.D.body, { childList: true, subtree: true });
|
||||
}
|
||||
function stop() {
|
||||
if (!is_observing || store.size) return;
|
||||
is_observing = false;
|
||||
observer.disconnect();
|
||||
}
|
||||
function requestIdle() {
|
||||
return new Promise(function(resolve) {
|
||||
(requestIdleCallback || requestAnimationFrame)(resolve);
|
||||
});
|
||||
}
|
||||
async function collectChildren(element) {
|
||||
if (store.size > 30)
|
||||
await requestIdle();
|
||||
const out = [];
|
||||
if (!isInstance(element, enviroment.N)) return out;
|
||||
for (const el of store.keys()) {
|
||||
if (el === element || !isInstance(el, enviroment.N)) continue;
|
||||
if (element.contains(el))
|
||||
out.push(el);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function observerAdded(addedNodes, is_root) {
|
||||
let out = false;
|
||||
for (const element of addedNodes) {
|
||||
if (is_root) collectChildren(element).then(observerAdded);
|
||||
if (!store.has(element)) continue;
|
||||
const ls = store.get(element);
|
||||
if (!ls.length_c) continue;
|
||||
element.dispatchEvent(new Event(evc));
|
||||
ls.connected = /* @__PURE__ */ new WeakSet();
|
||||
ls.length_c = 0;
|
||||
if (!ls.length_d) store.delete(element);
|
||||
out = true;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function observerRemoved(removedNodes, is_root) {
|
||||
let out = false;
|
||||
for (const element of removedNodes) {
|
||||
if (is_root) collectChildren(element).then(observerRemoved);
|
||||
if (!store.has(element)) continue;
|
||||
const ls = store.get(element);
|
||||
if (!ls.length_d) continue;
|
||||
(globalThis.queueMicrotask || setTimeout)(dispatchRemove(element));
|
||||
out = true;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function dispatchRemove(element) {
|
||||
return () => {
|
||||
if (element.isConnected) return;
|
||||
element.dispatchEvent(new Event(evd));
|
||||
store.delete(element);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// src/events.js
|
||||
function dispatchEvent(name, options, host) {
|
||||
if (typeof options === "function") {
|
||||
host = options;
|
||||
options = null;
|
||||
}
|
||||
if (!options) options = {};
|
||||
return function dispatch(element, ...d) {
|
||||
if (host) {
|
||||
d.unshift(element);
|
||||
element = typeof host === "function" ? host() : host;
|
||||
}
|
||||
const event = d.length ? new CustomEvent(name, oAssign({ detail: d[0] }, options)) : new Event(name, options);
|
||||
return element.dispatchEvent(event);
|
||||
};
|
||||
}
|
||||
function on(event, listener, options) {
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(event, listener, options);
|
||||
return element;
|
||||
};
|
||||
}
|
||||
var lifeOptions = (obj) => oAssign({}, typeof obj === "object" ? obj : null, { once: true });
|
||||
on.connected = function(listener, options) {
|
||||
options = lifeOptions(options);
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(evc, listener, options);
|
||||
if (element[keyLTE]) return element;
|
||||
if (element.isConnected) return element.dispatchEvent(new Event(evc)), element;
|
||||
const c = onAbort(options.signal, () => c_ch_o.offConnected(element, listener));
|
||||
if (c) c_ch_o.onConnected(element, listener);
|
||||
return element;
|
||||
};
|
||||
};
|
||||
on.disconnected = function(listener, options) {
|
||||
options = lifeOptions(options);
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(evd, listener, options);
|
||||
if (element[keyLTE]) return element;
|
||||
const c = onAbort(options.signal, () => c_ch_o.offDisconnected(element, listener));
|
||||
if (c) c_ch_o.onDisconnected(element, listener);
|
||||
return element;
|
||||
};
|
||||
};
|
||||
|
||||
// src/dom.js
|
||||
function queue(promise) {
|
||||
return enviroment.q(promise);
|
||||
@ -168,6 +378,7 @@ var DDE = (() => {
|
||||
host: (c) => c ? c(enviroment.D.body) : enviroment.D.body,
|
||||
prevent: true
|
||||
}];
|
||||
var store_abort = /* @__PURE__ */ new WeakMap();
|
||||
var scope = {
|
||||
/**
|
||||
* Gets the current scope
|
||||
@ -184,6 +395,17 @@ var DDE = (() => {
|
||||
return this.current.host;
|
||||
},
|
||||
/**
|
||||
* Creates/gets an AbortController that triggers when the element disconnects
|
||||
* */
|
||||
get signal() {
|
||||
const { host } = this;
|
||||
if (store_abort.has(host)) return store_abort.get(host);
|
||||
const a = new AbortController();
|
||||
store_abort.set(host, a);
|
||||
host(on.disconnected(() => a.abort()));
|
||||
return a.signal;
|
||||
},
|
||||
/**
|
||||
* Prevents default behavior in the current scope
|
||||
* @returns {Object} Current scope context
|
||||
*/
|
||||
@ -426,172 +648,8 @@ var DDE = (() => {
|
||||
return Reflect.deleteProperty(obj, key);
|
||||
}
|
||||
|
||||
// src/events-observer.js
|
||||
var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, {
|
||||
get() {
|
||||
return () => {
|
||||
};
|
||||
}
|
||||
});
|
||||
function connectionsChangesObserverConstructor() {
|
||||
const store = /* @__PURE__ */ new Map();
|
||||
let is_observing = false;
|
||||
const observerListener = (stop2) => function(mutations) {
|
||||
for (const mutation of mutations) {
|
||||
if (mutation.type !== "childList") continue;
|
||||
if (observerAdded(mutation.addedNodes, true)) {
|
||||
stop2();
|
||||
continue;
|
||||
}
|
||||
if (observerRemoved(mutation.removedNodes, true))
|
||||
stop2();
|
||||
}
|
||||
};
|
||||
const observer = new enviroment.M(observerListener(stop));
|
||||
return {
|
||||
/**
|
||||
* Creates an observer for a specific element
|
||||
* @param {Element} element - Element to observe
|
||||
* @returns {Function} Cleanup function
|
||||
*/
|
||||
observe(element) {
|
||||
const o = new enviroment.M(observerListener(() => {
|
||||
}));
|
||||
o.observe(element, { childList: true, subtree: true });
|
||||
return () => o.disconnect();
|
||||
},
|
||||
/**
|
||||
* Register a connection listener for an element
|
||||
* @param {Element} element - Element to watch
|
||||
* @param {Function} listener - Callback for connection event
|
||||
*/
|
||||
onConnected(element, listener) {
|
||||
start();
|
||||
const listeners = getElementStore(element);
|
||||
if (listeners.connected.has(listener)) return;
|
||||
listeners.connected.add(listener);
|
||||
listeners.length_c += 1;
|
||||
},
|
||||
/**
|
||||
* Unregister a connection listener
|
||||
* @param {Element} element - Element being watched
|
||||
* @param {Function} listener - Callback to remove
|
||||
*/
|
||||
offConnected(element, listener) {
|
||||
if (!store.has(element)) return;
|
||||
const ls = store.get(element);
|
||||
if (!ls.connected.has(listener)) return;
|
||||
ls.connected.delete(listener);
|
||||
ls.length_c -= 1;
|
||||
cleanWhenOff(element, ls);
|
||||
},
|
||||
/**
|
||||
* Register a disconnection listener for an element
|
||||
* @param {Element} element - Element to watch
|
||||
* @param {Function} listener - Callback for disconnection event
|
||||
*/
|
||||
onDisconnected(element, listener) {
|
||||
start();
|
||||
const listeners = getElementStore(element);
|
||||
if (listeners.disconnected.has(listener)) return;
|
||||
listeners.disconnected.add(listener);
|
||||
listeners.length_d += 1;
|
||||
},
|
||||
/**
|
||||
* Unregister a disconnection listener
|
||||
* @param {Element} element - Element being watched
|
||||
* @param {Function} listener - Callback to remove
|
||||
*/
|
||||
offDisconnected(element, listener) {
|
||||
if (!store.has(element)) return;
|
||||
const ls = store.get(element);
|
||||
ls.disconnected.delete(listener);
|
||||
ls.length_d -= 1;
|
||||
cleanWhenOff(element, ls);
|
||||
}
|
||||
};
|
||||
function cleanWhenOff(element, ls) {
|
||||
if (ls.length_c || ls.length_d)
|
||||
return;
|
||||
store.delete(element);
|
||||
stop();
|
||||
}
|
||||
function getElementStore(element) {
|
||||
if (store.has(element)) return store.get(element);
|
||||
const out = {
|
||||
connected: /* @__PURE__ */ new WeakSet(),
|
||||
length_c: 0,
|
||||
disconnected: /* @__PURE__ */ new WeakSet(),
|
||||
length_d: 0
|
||||
};
|
||||
store.set(element, out);
|
||||
return out;
|
||||
}
|
||||
function start() {
|
||||
if (is_observing) return;
|
||||
is_observing = true;
|
||||
observer.observe(enviroment.D.body, { childList: true, subtree: true });
|
||||
}
|
||||
function stop() {
|
||||
if (!is_observing || store.size) return;
|
||||
is_observing = false;
|
||||
observer.disconnect();
|
||||
}
|
||||
function requestIdle() {
|
||||
return new Promise(function(resolve) {
|
||||
(requestIdleCallback || requestAnimationFrame)(resolve);
|
||||
});
|
||||
}
|
||||
async function collectChildren(element) {
|
||||
if (store.size > 30)
|
||||
await requestIdle();
|
||||
const out = [];
|
||||
if (!isInstance(element, Node)) return out;
|
||||
for (const el of store.keys()) {
|
||||
if (el === element || !isInstance(el, Node)) continue;
|
||||
if (element.contains(el))
|
||||
out.push(el);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function observerAdded(addedNodes, is_root) {
|
||||
let out = false;
|
||||
for (const element of addedNodes) {
|
||||
if (is_root) collectChildren(element).then(observerAdded);
|
||||
if (!store.has(element)) continue;
|
||||
const ls = store.get(element);
|
||||
if (!ls.length_c) continue;
|
||||
element.dispatchEvent(new Event(evc));
|
||||
ls.connected = /* @__PURE__ */ new WeakSet();
|
||||
ls.length_c = 0;
|
||||
if (!ls.length_d) store.delete(element);
|
||||
out = true;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function observerRemoved(removedNodes, is_root) {
|
||||
let out = false;
|
||||
for (const element of removedNodes) {
|
||||
if (is_root) collectChildren(element).then(observerRemoved);
|
||||
if (!store.has(element)) continue;
|
||||
const ls = store.get(element);
|
||||
if (!ls.length_d) continue;
|
||||
(globalThis.queueMicrotask || setTimeout)(dispatchRemove(element));
|
||||
out = true;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function dispatchRemove(element) {
|
||||
return () => {
|
||||
if (element.isConnected) return;
|
||||
element.dispatchEvent(new Event(evd));
|
||||
store.delete(element);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// src/customElement.js
|
||||
function customElementRender(target, render, props = observedAttributes2) {
|
||||
function customElementRender(target, render, props = {}) {
|
||||
const custom_element = target.host || target;
|
||||
scope.push({
|
||||
scope: custom_element,
|
||||
@ -632,81 +690,40 @@ var DDE = (() => {
|
||||
obj[method] = new Proxy(obj[method] || (() => {
|
||||
}), { apply });
|
||||
}
|
||||
function observedAttributes2(instance) {
|
||||
return observedAttributes(instance, (i, n) => i.getAttribute(n));
|
||||
}
|
||||
|
||||
// src/events.js
|
||||
function dispatchEvent(name, options, host) {
|
||||
if (typeof options === "function") {
|
||||
host = options;
|
||||
options = null;
|
||||
}
|
||||
if (!options) options = {};
|
||||
return function dispatch(element, ...d) {
|
||||
if (host) {
|
||||
d.unshift(element);
|
||||
element = typeof host === "function" ? host() : host;
|
||||
}
|
||||
const event = d.length ? new CustomEvent(name, oAssign({ detail: d[0] }, options)) : new Event(name, options);
|
||||
return element.dispatchEvent(event);
|
||||
};
|
||||
// src/memo.js
|
||||
var memoMark = "__dde_memo";
|
||||
var memo_scope = [];
|
||||
function memo(key, generator) {
|
||||
if (!memo_scope.length) return generator(key);
|
||||
const k = typeof key === "object" ? JSON.stringify(key) : key;
|
||||
const [{ cache, after }] = memo_scope;
|
||||
return after(k, hasOwn(cache, k) ? cache[k] : generator(key));
|
||||
}
|
||||
function on(event, listener, options) {
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(event, listener, options);
|
||||
return element;
|
||||
};
|
||||
}
|
||||
var lifeOptions = (obj) => oAssign({}, typeof obj === "object" ? obj : null, { once: true });
|
||||
on.connected = function(listener, options) {
|
||||
options = lifeOptions(options);
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(evc, listener, options);
|
||||
if (element[keyLTE]) return element;
|
||||
if (element.isConnected) return element.dispatchEvent(new Event(evc)), element;
|
||||
const c = onAbort(options.signal, () => c_ch_o.offConnected(element, listener));
|
||||
if (c) c_ch_o.onConnected(element, listener);
|
||||
return element;
|
||||
};
|
||||
memo.isScope = function(obj) {
|
||||
return obj[memoMark];
|
||||
};
|
||||
on.disconnected = function(listener, options) {
|
||||
options = lifeOptions(options);
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(evd, listener, options);
|
||||
if (element[keyLTE]) return element;
|
||||
const c = onAbort(options.signal, () => c_ch_o.offDisconnected(element, listener));
|
||||
if (c) c_ch_o.onDisconnected(element, listener);
|
||||
return element;
|
||||
};
|
||||
};
|
||||
var store_abort = /* @__PURE__ */ new WeakMap();
|
||||
on.disconnectedAsAbort = function(host) {
|
||||
if (store_abort.has(host)) return store_abort.get(host);
|
||||
const a = new AbortController();
|
||||
store_abort.set(host, a);
|
||||
host(on.disconnected(() => a.abort()));
|
||||
return a.signal;
|
||||
};
|
||||
var els_attribute_store = /* @__PURE__ */ new WeakSet();
|
||||
on.attributeChanged = function(listener, options) {
|
||||
if (typeof options !== "object")
|
||||
options = {};
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(eva, listener, options);
|
||||
if (element[keyLTE] || els_attribute_store.has(element))
|
||||
return element;
|
||||
if (!enviroment.M) return element;
|
||||
const observer = new enviroment.M(function(mutations) {
|
||||
for (const { attributeName, target } of mutations)
|
||||
target.dispatchEvent(
|
||||
new CustomEvent(eva, { detail: [attributeName, target.getAttribute(attributeName)] })
|
||||
);
|
||||
memo.scope = function memoScope(fun, { signal: signal2, onlyLast } = {}) {
|
||||
let cache = oCreate();
|
||||
function memoScope2(...args) {
|
||||
if (signal2 && signal2.aborted)
|
||||
return fun.apply(this, args);
|
||||
let cache_local = onlyLast ? cache : oCreate();
|
||||
memo_scope.unshift({
|
||||
cache,
|
||||
after(key, val) {
|
||||
return cache_local[key] = val;
|
||||
}
|
||||
});
|
||||
const c = onAbort(options.signal, () => observer.disconnect());
|
||||
if (c) observer.observe(element, { attributes: true });
|
||||
return element;
|
||||
};
|
||||
const out = fun.apply(this, args);
|
||||
memo_scope.shift();
|
||||
cache = cache_local;
|
||||
return out;
|
||||
}
|
||||
memoScope2[memoMark] = true;
|
||||
memoScope2.clear = () => cache = oCreate();
|
||||
if (signal2) signal2.addEventListener("abort", memoScope2.clear);
|
||||
return memoScope2;
|
||||
};
|
||||
|
||||
// src/signals-lib/helpers.js
|
||||
@ -822,25 +839,18 @@ var DDE = (() => {
|
||||
}
|
||||
};
|
||||
var key_reactive = "__dde_reactive";
|
||||
function cache(store = oCreate()) {
|
||||
return (key, fun) => hasOwn(store, key) ? store[key] : store[key] = fun();
|
||||
}
|
||||
signal.el = function(s, map) {
|
||||
map = memo.isScope(map) ? map : memo.scope(map, { onlyLast: true });
|
||||
const mark_start = createElement.mark({ type: "reactive", source: new Defined().compact }, true);
|
||||
const mark_end = mark_start.end;
|
||||
const out = enviroment.D.createDocumentFragment();
|
||||
out.append(mark_start, mark_end);
|
||||
const { current } = scope;
|
||||
let cache_shared = oCreate();
|
||||
const reRenderReactiveElement = (v) => {
|
||||
if (!mark_start.parentNode || !mark_end.parentNode)
|
||||
return removeSignalListener(s, reRenderReactiveElement);
|
||||
const memo = cache(cache_shared);
|
||||
cache_shared = oCreate();
|
||||
scope.push(current);
|
||||
let els = map(v, function useCache(key, fun) {
|
||||
return cache_shared[key] = memo(key, fun);
|
||||
});
|
||||
let els = map(v);
|
||||
scope.pop();
|
||||
if (!Array.isArray(els))
|
||||
els = [els];
|
||||
@ -860,7 +870,7 @@ var DDE = (() => {
|
||||
current.host(on.disconnected(
|
||||
() => (
|
||||
/*! Clears cached elements for reactive element `S.el` */
|
||||
cache_shared = {}
|
||||
map.clear()
|
||||
)
|
||||
));
|
||||
return out;
|
||||
@ -892,7 +902,7 @@ var DDE = (() => {
|
||||
signal.observedAttributes = function(element) {
|
||||
const store = element[key_attributes] = {};
|
||||
const attrs = observedAttributes(element, observedAttribute(store));
|
||||
on.attributeChanged(function attributeChangeToSignal({ detail }) {
|
||||
on(eva, function attributeChangeToSignal({ detail }) {
|
||||
/*! This maps attributes to signals (`S.observedAttributes`).
|
||||
Investigate `__dde_attributes` key of the element. */
|
||||
const [name, value] = detail;
|
||||
|
80
dist/iife-with-signals.min.d.ts
vendored
80
dist/iife-with-signals.min.d.ts
vendored
@ -18,6 +18,7 @@ export type Actions<V> = Record<string | SymbolOnclear, Action<V>>;
|
||||
export type OnListenerOptions = Pick<AddEventListenerOptions, "signal"> & {
|
||||
first_time?: boolean;
|
||||
};
|
||||
export type SElement = Node | Element | DocumentFragment | ddeHTMLElement | ddeSVGElement | ddeDocumentFragment;
|
||||
export interface signal {
|
||||
_: Symbol;
|
||||
/**
|
||||
@ -63,7 +64,7 @@ export interface signal {
|
||||
* S.el(listS, list=> list.map(li=> el("li", li)));
|
||||
* ```
|
||||
* */
|
||||
el<S extends any>(signal: Signal<S, any>, el: (v: S) => Element | Element[] | DocumentFragment): DocumentFragment;
|
||||
el<S extends any>(signal: Signal<S, any>, el: (v: S) => SElement | SElement[]): DocumentFragment;
|
||||
observedAttributes(custom_element: HTMLElement): Record<string, Signal<string, {}>>;
|
||||
}
|
||||
export const signal: signal;
|
||||
@ -80,10 +81,11 @@ export type CustomElementTagNameMap = {
|
||||
export type SupportedElement = HTMLElementTagNameMap[keyof HTMLElementTagNameMap] | SVGElementTagNameMap[keyof SVGElementTagNameMap] | MathMLElementTagNameMap[keyof MathMLElementTagNameMap] | CustomElementTagNameMap[keyof CustomElementTagNameMap];
|
||||
declare global {
|
||||
type ddeComponentAttributes = Record<any, any> | undefined;
|
||||
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node> = (element: El) => any;
|
||||
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node> = (element: El, ...rest: any) => any;
|
||||
type ddeString = string | Signal<string, {}>;
|
||||
type ddeStringable = ddeString | number | Signal<number, {}>;
|
||||
}
|
||||
export type Host<EL extends SupportedElement> = (...addons: ddeElementAddon<EL>[]) => EL;
|
||||
export type PascalCase = `${Capitalize<string>}${string}`;
|
||||
export type AttrsModified = {
|
||||
/**
|
||||
@ -145,6 +147,7 @@ export namespace el {
|
||||
host?: "this" | "parentElement";
|
||||
}, is_open?: boolean): Comment;
|
||||
}
|
||||
export function chainableAppend<EL extends SupportedElement>(el: EL): EL | ddeHTMLElement;
|
||||
export function el<A extends ddeComponentAttributes, EL extends SupportedElement | ddeDocumentFragment>(component: (attr: A, ...rest: any[]) => EL, attrs?: NoInfer<A>, ...addons: ddeElementAddon<EL>[]): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] ? EL : (EL extends ddeDocumentFragment ? EL : ddeHTMLElement);
|
||||
export function el<A extends {
|
||||
textContent: ddeStringable;
|
||||
@ -157,7 +160,6 @@ export function elNS(namespace: "http://www.w3.org/1998/Math/MathML"): <TAG exte
|
||||
[key in keyof EL]: EL[key] | Signal<EL[key], {}> | string | number | boolean;
|
||||
}>, ...addons: ddeElementAddon<NoInfer<EL>>[]) => ddeMathMLElement;
|
||||
export function elNS(namespace: string): (tag_name: string, attrs?: string | ddeStringable | Record<string, any>, ...addons: ddeElementAddon<SupportedElement>[]) => SupportedElement;
|
||||
export function chainableAppend<EL extends SupportedElement>(el: EL): EL;
|
||||
/** Simulate slots for ddeComponents */
|
||||
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(root: EL): EL;
|
||||
/**
|
||||
@ -166,9 +168,9 @@ export function simulateSlots<EL extends SupportedElement | DocumentFragment>(ro
|
||||
* @param body Body of the custom element
|
||||
* */
|
||||
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(el: HTMLElement, body: EL): EL;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, element: SupportedElement): (data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, host: Host<SupportedElement>): (data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?: EventInit): (element: SupportedElement, data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, element: SupportedElement | (() => SupportedElement)): (data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
|
||||
export interface On {
|
||||
/** Listens to the DOM event. See {@link Document.addEventListener} */
|
||||
<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE;
|
||||
@ -186,7 +188,7 @@ export interface On {
|
||||
export const on: On;
|
||||
export type Scope = {
|
||||
scope: Node | Function | Object;
|
||||
host: ddeElementAddon<any>;
|
||||
host: Host<SupportedElement>;
|
||||
custom_element: false | HTMLElement;
|
||||
prevent: boolean;
|
||||
};
|
||||
@ -202,7 +204,11 @@ export const scope: {
|
||||
* It can be also used to register Addon(s) (functions to be called when component is initized)
|
||||
* — `scope.host(on.connected(console.log))`.
|
||||
* */
|
||||
host: (...addons: ddeElementAddon<SupportedElement>[]) => HTMLElement;
|
||||
host: Host<SupportedElement>;
|
||||
/**
|
||||
* Creates/gets an AbortController that triggers when the element disconnects
|
||||
* */
|
||||
signal: AbortSignal;
|
||||
state: Scope[];
|
||||
/** Adds new child scope. All attributes are inherited by default. */
|
||||
push(scope?: Partial<Scope>): ReturnType<Array<Scope>["push"]>;
|
||||
@ -214,7 +220,6 @@ export const scope: {
|
||||
export function customElementRender<EL extends HTMLElement, P extends any = Record<string, string | Signal<string, {}>>>(target: ShadowRoot | EL, render: (props: P) => SupportedElement | DocumentFragment, props?: P | ((el: EL) => P)): EL;
|
||||
export function customElementWithDDE<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||
export function lifecyclesToEvents<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||
export function observedAttributes(custom_element: HTMLElement): Record<string, string>;
|
||||
/**
|
||||
* This is used primarly for server side rendering. To be sure that all async operations
|
||||
* are finished before the page is sent to the client.
|
||||
@ -235,6 +240,65 @@ export function observedAttributes(custom_element: HTMLElement): Record<string,
|
||||
* ```
|
||||
* */
|
||||
export function queue(promise?: Promise<unknown>): Promise<unknown>;
|
||||
/**
|
||||
* Memoization utility for caching DOM elements to improve performance.
|
||||
* Used to prevent unnecessary recreation of elements when rendering lists or complex components.
|
||||
*
|
||||
* @param key - Unique identifier for the element (usually an ID or unique value)
|
||||
* @param generator - Function that creates the element
|
||||
* @returns The cached element if the key exists, otherwise the result of the generator function
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Within S.el for list rendering
|
||||
* S.el(itemsSignal, (items, memo) =>
|
||||
* el("ul").append(
|
||||
* ...items.map(item =>
|
||||
* memo(item.id, () => el(ItemComponent, item))
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
export function memo<T>(key: string | number | object, generator: (key: any) => T): T;
|
||||
/**
|
||||
* Memo namespace containing utility functions for memoization.
|
||||
*/
|
||||
export namespace memo {
|
||||
/**
|
||||
* Checks if an object is a memo scope.
|
||||
* @param obj - The object to check
|
||||
* @returns True if the object is a memo scope
|
||||
*/
|
||||
export function isScope(obj: any): boolean;
|
||||
/**
|
||||
* Creates a memoized function with optional cleanup support.
|
||||
*
|
||||
* @param fun - The function to memoize
|
||||
* @param options - Configuration options
|
||||
* @param options.signal - AbortSignal for cleanup
|
||||
* @param options.onlyLast - When true, only keeps the cache from the most recent call
|
||||
* @returns A memoized version of the function with a .clear() method
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const renderItems = memo.scope(function(items) {
|
||||
* return items.map(item =>
|
||||
* memo(item.id, () => el("div", item.name))
|
||||
* );
|
||||
* }, {
|
||||
* signal: controller.signal,
|
||||
* onlyLast: true
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function scope<F extends Function>(fun: F, options?: {
|
||||
signal?: AbortSignal;
|
||||
onlyLast?: boolean;
|
||||
}): F & {
|
||||
clear: () => void;
|
||||
};
|
||||
}
|
||||
/* TypeScript MEH */
|
||||
declare global {
|
||||
type ddeAppend<el> = (...nodes: (Node | string)[]) => el;
|
||||
|
8
dist/iife-with-signals.min.js
vendored
8
dist/iife-with-signals.min.js
vendored
File diff suppressed because one or more lines are too long
80
dist/iife.d.ts
vendored
80
dist/iife.d.ts
vendored
@ -18,6 +18,7 @@ export type Actions<V> = Record<string | SymbolOnclear, Action<V>>;
|
||||
export type OnListenerOptions = Pick<AddEventListenerOptions, "signal"> & {
|
||||
first_time?: boolean;
|
||||
};
|
||||
export type SElement = Node | Element | DocumentFragment | ddeHTMLElement | ddeSVGElement | ddeDocumentFragment;
|
||||
export interface signal {
|
||||
_: Symbol;
|
||||
/**
|
||||
@ -63,7 +64,7 @@ export interface signal {
|
||||
* S.el(listS, list=> list.map(li=> el("li", li)));
|
||||
* ```
|
||||
* */
|
||||
el<S extends any>(signal: Signal<S, any>, el: (v: S) => Element | Element[] | DocumentFragment): DocumentFragment;
|
||||
el<S extends any>(signal: Signal<S, any>, el: (v: S) => SElement | SElement[]): DocumentFragment;
|
||||
observedAttributes(custom_element: HTMLElement): Record<string, Signal<string, {}>>;
|
||||
}
|
||||
declare const signal: signal;
|
||||
@ -79,10 +80,11 @@ export type CustomElementTagNameMap = {
|
||||
export type SupportedElement = HTMLElementTagNameMap[keyof HTMLElementTagNameMap] | SVGElementTagNameMap[keyof SVGElementTagNameMap] | MathMLElementTagNameMap[keyof MathMLElementTagNameMap] | CustomElementTagNameMap[keyof CustomElementTagNameMap];
|
||||
declare global {
|
||||
type ddeComponentAttributes = Record<any, any> | undefined;
|
||||
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node> = (element: El) => any;
|
||||
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node> = (element: El, ...rest: any) => any;
|
||||
type ddeString = string | Signal<string, {}>;
|
||||
type ddeStringable = ddeString | number | Signal<number, {}>;
|
||||
}
|
||||
export type Host<EL extends SupportedElement> = (...addons: ddeElementAddon<EL>[]) => EL;
|
||||
export type PascalCase = `${Capitalize<string>}${string}`;
|
||||
export type AttrsModified = {
|
||||
/**
|
||||
@ -144,6 +146,7 @@ export namespace el {
|
||||
host?: "this" | "parentElement";
|
||||
}, is_open?: boolean): Comment;
|
||||
}
|
||||
export function chainableAppend<EL extends SupportedElement>(el: EL): EL | ddeHTMLElement;
|
||||
export function el<A extends ddeComponentAttributes, EL extends SupportedElement | ddeDocumentFragment>(component: (attr: A, ...rest: any[]) => EL, attrs?: NoInfer<A>, ...addons: ddeElementAddon<EL>[]): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] ? EL : (EL extends ddeDocumentFragment ? EL : ddeHTMLElement);
|
||||
export function el<A extends {
|
||||
textContent: ddeStringable;
|
||||
@ -156,7 +159,6 @@ export function elNS(namespace: "http://www.w3.org/1998/Math/MathML"): <TAG exte
|
||||
[key in keyof EL]: EL[key] | Signal<EL[key], {}> | string | number | boolean;
|
||||
}>, ...addons: ddeElementAddon<NoInfer<EL>>[]) => ddeMathMLElement;
|
||||
export function elNS(namespace: string): (tag_name: string, attrs?: string | ddeStringable | Record<string, any>, ...addons: ddeElementAddon<SupportedElement>[]) => SupportedElement;
|
||||
export function chainableAppend<EL extends SupportedElement>(el: EL): EL;
|
||||
/** Simulate slots for ddeComponents */
|
||||
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(root: EL): EL;
|
||||
/**
|
||||
@ -165,9 +167,9 @@ export function simulateSlots<EL extends SupportedElement | DocumentFragment>(ro
|
||||
* @param body Body of the custom element
|
||||
* */
|
||||
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(el: HTMLElement, body: EL): EL;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, element: SupportedElement): (data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, host: Host<SupportedElement>): (data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?: EventInit): (element: SupportedElement, data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, element: SupportedElement | (() => SupportedElement)): (data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
|
||||
export interface On {
|
||||
/** Listens to the DOM event. See {@link Document.addEventListener} */
|
||||
<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE;
|
||||
@ -185,7 +187,7 @@ export interface On {
|
||||
export const on: On;
|
||||
export type Scope = {
|
||||
scope: Node | Function | Object;
|
||||
host: ddeElementAddon<any>;
|
||||
host: Host<SupportedElement>;
|
||||
custom_element: false | HTMLElement;
|
||||
prevent: boolean;
|
||||
};
|
||||
@ -201,7 +203,11 @@ export const scope: {
|
||||
* It can be also used to register Addon(s) (functions to be called when component is initized)
|
||||
* — `scope.host(on.connected(console.log))`.
|
||||
* */
|
||||
host: (...addons: ddeElementAddon<SupportedElement>[]) => HTMLElement;
|
||||
host: Host<SupportedElement>;
|
||||
/**
|
||||
* Creates/gets an AbortController that triggers when the element disconnects
|
||||
* */
|
||||
signal: AbortSignal;
|
||||
state: Scope[];
|
||||
/** Adds new child scope. All attributes are inherited by default. */
|
||||
push(scope?: Partial<Scope>): ReturnType<Array<Scope>["push"]>;
|
||||
@ -213,7 +219,6 @@ export const scope: {
|
||||
export function customElementRender<EL extends HTMLElement, P extends any = Record<string, string | Signal<string, {}>>>(target: ShadowRoot | EL, render: (props: P) => SupportedElement | DocumentFragment, props?: P | ((el: EL) => P)): EL;
|
||||
export function customElementWithDDE<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||
export function lifecyclesToEvents<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||
export function observedAttributes(custom_element: HTMLElement): Record<string, string>;
|
||||
/**
|
||||
* This is used primarly for server side rendering. To be sure that all async operations
|
||||
* are finished before the page is sent to the client.
|
||||
@ -234,6 +239,65 @@ export function observedAttributes(custom_element: HTMLElement): Record<string,
|
||||
* ```
|
||||
* */
|
||||
export function queue(promise?: Promise<unknown>): Promise<unknown>;
|
||||
/**
|
||||
* Memoization utility for caching DOM elements to improve performance.
|
||||
* Used to prevent unnecessary recreation of elements when rendering lists or complex components.
|
||||
*
|
||||
* @param key - Unique identifier for the element (usually an ID or unique value)
|
||||
* @param generator - Function that creates the element
|
||||
* @returns The cached element if the key exists, otherwise the result of the generator function
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Within S.el for list rendering
|
||||
* S.el(itemsSignal, (items, memo) =>
|
||||
* el("ul").append(
|
||||
* ...items.map(item =>
|
||||
* memo(item.id, () => el(ItemComponent, item))
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
export function memo<T>(key: string | number | object, generator: (key: any) => T): T;
|
||||
/**
|
||||
* Memo namespace containing utility functions for memoization.
|
||||
*/
|
||||
export namespace memo {
|
||||
/**
|
||||
* Checks if an object is a memo scope.
|
||||
* @param obj - The object to check
|
||||
* @returns True if the object is a memo scope
|
||||
*/
|
||||
export function isScope(obj: any): boolean;
|
||||
/**
|
||||
* Creates a memoized function with optional cleanup support.
|
||||
*
|
||||
* @param fun - The function to memoize
|
||||
* @param options - Configuration options
|
||||
* @param options.signal - AbortSignal for cleanup
|
||||
* @param options.onlyLast - When true, only keeps the cache from the most recent call
|
||||
* @returns A memoized version of the function with a .clear() method
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const renderItems = memo.scope(function(items) {
|
||||
* return items.map(item =>
|
||||
* memo(item.id, () => el("div", item.name))
|
||||
* );
|
||||
* }, {
|
||||
* signal: controller.signal,
|
||||
* onlyLast: true
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function scope<F extends Function>(fun: F, options?: {
|
||||
signal?: AbortSignal;
|
||||
onlyLast?: boolean;
|
||||
}): F & {
|
||||
clear: () => void;
|
||||
};
|
||||
}
|
||||
/* TypeScript MEH */
|
||||
declare global {
|
||||
type ddeAppend<el> = (...nodes: (Node | string)[]) => el;
|
||||
|
505
dist/iife.js
vendored
505
dist/iife.js
vendored
@ -33,7 +33,7 @@ var DDE = (() => {
|
||||
elNS: () => createElementNS,
|
||||
elementAttribute: () => elementAttribute,
|
||||
lifecyclesToEvents: () => lifecyclesToEvents,
|
||||
observedAttributes: () => observedAttributes2,
|
||||
memo: () => memo,
|
||||
on: () => on,
|
||||
queue: () => queue,
|
||||
registerReactivity: () => registerReactivity,
|
||||
@ -42,6 +42,7 @@ var DDE = (() => {
|
||||
});
|
||||
|
||||
// src/helpers.js
|
||||
var hasOwn = (...a) => Object.prototype.hasOwnProperty.call(...a);
|
||||
function isUndef(value) {
|
||||
return typeof value === "undefined";
|
||||
}
|
||||
@ -51,6 +52,9 @@ var DDE = (() => {
|
||||
function isProtoFrom(obj, cls) {
|
||||
return Object.prototype.isPrototypeOf.call(cls, obj);
|
||||
}
|
||||
function oCreate(proto = null, p = {}) {
|
||||
return Object.create(proto, p);
|
||||
}
|
||||
function oAssign(...o) {
|
||||
return Object.assign(...o);
|
||||
}
|
||||
@ -64,16 +68,6 @@ var DDE = (() => {
|
||||
signal.removeEventListener("abort", listener);
|
||||
};
|
||||
}
|
||||
function observedAttributes(instance, observedAttribute) {
|
||||
const { observedAttributes: observedAttributes3 = [] } = instance.constructor;
|
||||
return observedAttributes3.reduce(function(out, name) {
|
||||
out[kebabToCamel(name)] = observedAttribute(instance, name);
|
||||
return out;
|
||||
}, {});
|
||||
}
|
||||
function kebabToCamel(name) {
|
||||
return name.replace(/-./g, (x) => x[1].toUpperCase());
|
||||
}
|
||||
|
||||
// src/signals-lib/common.js
|
||||
var signals_global = {
|
||||
@ -111,6 +105,7 @@ var DDE = (() => {
|
||||
setDeleteAttr,
|
||||
ssr: "",
|
||||
D: globalThis.document,
|
||||
N: globalThis.Node,
|
||||
F: globalThis.DocumentFragment,
|
||||
H: globalThis.HTMLElement,
|
||||
S: globalThis.SVGElement,
|
||||
@ -131,6 +126,215 @@ var DDE = (() => {
|
||||
var evd = "dde:disconnected";
|
||||
var eva = "dde:attributeChanged";
|
||||
|
||||
// src/events-observer.js
|
||||
var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, {
|
||||
get() {
|
||||
return () => {
|
||||
};
|
||||
}
|
||||
});
|
||||
function connectionsChangesObserverConstructor() {
|
||||
const store = /* @__PURE__ */ new Map();
|
||||
let is_observing = false;
|
||||
const observerListener = (stop2) => function(mutations) {
|
||||
for (const mutation of mutations) {
|
||||
if (mutation.type !== "childList") continue;
|
||||
if (observerAdded(mutation.addedNodes, true)) {
|
||||
stop2();
|
||||
continue;
|
||||
}
|
||||
if (observerRemoved(mutation.removedNodes, true))
|
||||
stop2();
|
||||
}
|
||||
};
|
||||
const observer = new enviroment.M(observerListener(stop));
|
||||
return {
|
||||
/**
|
||||
* Creates an observer for a specific element
|
||||
* @param {Element} element - Element to observe
|
||||
* @returns {Function} Cleanup function
|
||||
*/
|
||||
observe(element) {
|
||||
const o = new enviroment.M(observerListener(() => {
|
||||
}));
|
||||
o.observe(element, { childList: true, subtree: true });
|
||||
return () => o.disconnect();
|
||||
},
|
||||
/**
|
||||
* Register a connection listener for an element
|
||||
* @param {Element} element - Element to watch
|
||||
* @param {Function} listener - Callback for connection event
|
||||
*/
|
||||
onConnected(element, listener) {
|
||||
start();
|
||||
const listeners = getElementStore(element);
|
||||
if (listeners.connected.has(listener)) return;
|
||||
listeners.connected.add(listener);
|
||||
listeners.length_c += 1;
|
||||
},
|
||||
/**
|
||||
* Unregister a connection listener
|
||||
* @param {Element} element - Element being watched
|
||||
* @param {Function} listener - Callback to remove
|
||||
*/
|
||||
offConnected(element, listener) {
|
||||
if (!store.has(element)) return;
|
||||
const ls = store.get(element);
|
||||
if (!ls.connected.has(listener)) return;
|
||||
ls.connected.delete(listener);
|
||||
ls.length_c -= 1;
|
||||
cleanWhenOff(element, ls);
|
||||
},
|
||||
/**
|
||||
* Register a disconnection listener for an element
|
||||
* @param {Element} element - Element to watch
|
||||
* @param {Function} listener - Callback for disconnection event
|
||||
*/
|
||||
onDisconnected(element, listener) {
|
||||
start();
|
||||
const listeners = getElementStore(element);
|
||||
if (listeners.disconnected.has(listener)) return;
|
||||
listeners.disconnected.add(listener);
|
||||
listeners.length_d += 1;
|
||||
},
|
||||
/**
|
||||
* Unregister a disconnection listener
|
||||
* @param {Element} element - Element being watched
|
||||
* @param {Function} listener - Callback to remove
|
||||
*/
|
||||
offDisconnected(element, listener) {
|
||||
if (!store.has(element)) return;
|
||||
const ls = store.get(element);
|
||||
ls.disconnected.delete(listener);
|
||||
ls.length_d -= 1;
|
||||
cleanWhenOff(element, ls);
|
||||
}
|
||||
};
|
||||
function cleanWhenOff(element, ls) {
|
||||
if (ls.length_c || ls.length_d)
|
||||
return;
|
||||
store.delete(element);
|
||||
stop();
|
||||
}
|
||||
function getElementStore(element) {
|
||||
if (store.has(element)) return store.get(element);
|
||||
const out = {
|
||||
connected: /* @__PURE__ */ new WeakSet(),
|
||||
length_c: 0,
|
||||
disconnected: /* @__PURE__ */ new WeakSet(),
|
||||
length_d: 0
|
||||
};
|
||||
store.set(element, out);
|
||||
return out;
|
||||
}
|
||||
function start() {
|
||||
if (is_observing) return;
|
||||
is_observing = true;
|
||||
observer.observe(enviroment.D.body, { childList: true, subtree: true });
|
||||
}
|
||||
function stop() {
|
||||
if (!is_observing || store.size) return;
|
||||
is_observing = false;
|
||||
observer.disconnect();
|
||||
}
|
||||
function requestIdle() {
|
||||
return new Promise(function(resolve) {
|
||||
(requestIdleCallback || requestAnimationFrame)(resolve);
|
||||
});
|
||||
}
|
||||
async function collectChildren(element) {
|
||||
if (store.size > 30)
|
||||
await requestIdle();
|
||||
const out = [];
|
||||
if (!isInstance(element, enviroment.N)) return out;
|
||||
for (const el of store.keys()) {
|
||||
if (el === element || !isInstance(el, enviroment.N)) continue;
|
||||
if (element.contains(el))
|
||||
out.push(el);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function observerAdded(addedNodes, is_root) {
|
||||
let out = false;
|
||||
for (const element of addedNodes) {
|
||||
if (is_root) collectChildren(element).then(observerAdded);
|
||||
if (!store.has(element)) continue;
|
||||
const ls = store.get(element);
|
||||
if (!ls.length_c) continue;
|
||||
element.dispatchEvent(new Event(evc));
|
||||
ls.connected = /* @__PURE__ */ new WeakSet();
|
||||
ls.length_c = 0;
|
||||
if (!ls.length_d) store.delete(element);
|
||||
out = true;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function observerRemoved(removedNodes, is_root) {
|
||||
let out = false;
|
||||
for (const element of removedNodes) {
|
||||
if (is_root) collectChildren(element).then(observerRemoved);
|
||||
if (!store.has(element)) continue;
|
||||
const ls = store.get(element);
|
||||
if (!ls.length_d) continue;
|
||||
(globalThis.queueMicrotask || setTimeout)(dispatchRemove(element));
|
||||
out = true;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function dispatchRemove(element) {
|
||||
return () => {
|
||||
if (element.isConnected) return;
|
||||
element.dispatchEvent(new Event(evd));
|
||||
store.delete(element);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// src/events.js
|
||||
function dispatchEvent(name, options, host) {
|
||||
if (typeof options === "function") {
|
||||
host = options;
|
||||
options = null;
|
||||
}
|
||||
if (!options) options = {};
|
||||
return function dispatch(element, ...d) {
|
||||
if (host) {
|
||||
d.unshift(element);
|
||||
element = typeof host === "function" ? host() : host;
|
||||
}
|
||||
const event = d.length ? new CustomEvent(name, oAssign({ detail: d[0] }, options)) : new Event(name, options);
|
||||
return element.dispatchEvent(event);
|
||||
};
|
||||
}
|
||||
function on(event, listener, options) {
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(event, listener, options);
|
||||
return element;
|
||||
};
|
||||
}
|
||||
var lifeOptions = (obj) => oAssign({}, typeof obj === "object" ? obj : null, { once: true });
|
||||
on.connected = function(listener, options) {
|
||||
options = lifeOptions(options);
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(evc, listener, options);
|
||||
if (element[keyLTE]) return element;
|
||||
if (element.isConnected) return element.dispatchEvent(new Event(evc)), element;
|
||||
const c = onAbort(options.signal, () => c_ch_o.offConnected(element, listener));
|
||||
if (c) c_ch_o.onConnected(element, listener);
|
||||
return element;
|
||||
};
|
||||
};
|
||||
on.disconnected = function(listener, options) {
|
||||
options = lifeOptions(options);
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(evd, listener, options);
|
||||
if (element[keyLTE]) return element;
|
||||
const c = onAbort(options.signal, () => c_ch_o.offDisconnected(element, listener));
|
||||
if (c) c_ch_o.onDisconnected(element, listener);
|
||||
return element;
|
||||
};
|
||||
};
|
||||
|
||||
// src/dom.js
|
||||
function queue(promise) {
|
||||
return enviroment.q(promise);
|
||||
@ -142,6 +346,7 @@ var DDE = (() => {
|
||||
host: (c) => c ? c(enviroment.D.body) : enviroment.D.body,
|
||||
prevent: true
|
||||
}];
|
||||
var store_abort = /* @__PURE__ */ new WeakMap();
|
||||
var scope = {
|
||||
/**
|
||||
* Gets the current scope
|
||||
@ -158,6 +363,17 @@ var DDE = (() => {
|
||||
return this.current.host;
|
||||
},
|
||||
/**
|
||||
* Creates/gets an AbortController that triggers when the element disconnects
|
||||
* */
|
||||
get signal() {
|
||||
const { host } = this;
|
||||
if (store_abort.has(host)) return store_abort.get(host);
|
||||
const a = new AbortController();
|
||||
store_abort.set(host, a);
|
||||
host(on.disconnected(() => a.abort()));
|
||||
return a.signal;
|
||||
},
|
||||
/**
|
||||
* Prevents default behavior in the current scope
|
||||
* @returns {Object} Current scope context
|
||||
*/
|
||||
@ -400,172 +616,8 @@ var DDE = (() => {
|
||||
return Reflect.deleteProperty(obj, key);
|
||||
}
|
||||
|
||||
// src/events-observer.js
|
||||
var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, {
|
||||
get() {
|
||||
return () => {
|
||||
};
|
||||
}
|
||||
});
|
||||
function connectionsChangesObserverConstructor() {
|
||||
const store = /* @__PURE__ */ new Map();
|
||||
let is_observing = false;
|
||||
const observerListener = (stop2) => function(mutations) {
|
||||
for (const mutation of mutations) {
|
||||
if (mutation.type !== "childList") continue;
|
||||
if (observerAdded(mutation.addedNodes, true)) {
|
||||
stop2();
|
||||
continue;
|
||||
}
|
||||
if (observerRemoved(mutation.removedNodes, true))
|
||||
stop2();
|
||||
}
|
||||
};
|
||||
const observer = new enviroment.M(observerListener(stop));
|
||||
return {
|
||||
/**
|
||||
* Creates an observer for a specific element
|
||||
* @param {Element} element - Element to observe
|
||||
* @returns {Function} Cleanup function
|
||||
*/
|
||||
observe(element) {
|
||||
const o = new enviroment.M(observerListener(() => {
|
||||
}));
|
||||
o.observe(element, { childList: true, subtree: true });
|
||||
return () => o.disconnect();
|
||||
},
|
||||
/**
|
||||
* Register a connection listener for an element
|
||||
* @param {Element} element - Element to watch
|
||||
* @param {Function} listener - Callback for connection event
|
||||
*/
|
||||
onConnected(element, listener) {
|
||||
start();
|
||||
const listeners = getElementStore(element);
|
||||
if (listeners.connected.has(listener)) return;
|
||||
listeners.connected.add(listener);
|
||||
listeners.length_c += 1;
|
||||
},
|
||||
/**
|
||||
* Unregister a connection listener
|
||||
* @param {Element} element - Element being watched
|
||||
* @param {Function} listener - Callback to remove
|
||||
*/
|
||||
offConnected(element, listener) {
|
||||
if (!store.has(element)) return;
|
||||
const ls = store.get(element);
|
||||
if (!ls.connected.has(listener)) return;
|
||||
ls.connected.delete(listener);
|
||||
ls.length_c -= 1;
|
||||
cleanWhenOff(element, ls);
|
||||
},
|
||||
/**
|
||||
* Register a disconnection listener for an element
|
||||
* @param {Element} element - Element to watch
|
||||
* @param {Function} listener - Callback for disconnection event
|
||||
*/
|
||||
onDisconnected(element, listener) {
|
||||
start();
|
||||
const listeners = getElementStore(element);
|
||||
if (listeners.disconnected.has(listener)) return;
|
||||
listeners.disconnected.add(listener);
|
||||
listeners.length_d += 1;
|
||||
},
|
||||
/**
|
||||
* Unregister a disconnection listener
|
||||
* @param {Element} element - Element being watched
|
||||
* @param {Function} listener - Callback to remove
|
||||
*/
|
||||
offDisconnected(element, listener) {
|
||||
if (!store.has(element)) return;
|
||||
const ls = store.get(element);
|
||||
ls.disconnected.delete(listener);
|
||||
ls.length_d -= 1;
|
||||
cleanWhenOff(element, ls);
|
||||
}
|
||||
};
|
||||
function cleanWhenOff(element, ls) {
|
||||
if (ls.length_c || ls.length_d)
|
||||
return;
|
||||
store.delete(element);
|
||||
stop();
|
||||
}
|
||||
function getElementStore(element) {
|
||||
if (store.has(element)) return store.get(element);
|
||||
const out = {
|
||||
connected: /* @__PURE__ */ new WeakSet(),
|
||||
length_c: 0,
|
||||
disconnected: /* @__PURE__ */ new WeakSet(),
|
||||
length_d: 0
|
||||
};
|
||||
store.set(element, out);
|
||||
return out;
|
||||
}
|
||||
function start() {
|
||||
if (is_observing) return;
|
||||
is_observing = true;
|
||||
observer.observe(enviroment.D.body, { childList: true, subtree: true });
|
||||
}
|
||||
function stop() {
|
||||
if (!is_observing || store.size) return;
|
||||
is_observing = false;
|
||||
observer.disconnect();
|
||||
}
|
||||
function requestIdle() {
|
||||
return new Promise(function(resolve) {
|
||||
(requestIdleCallback || requestAnimationFrame)(resolve);
|
||||
});
|
||||
}
|
||||
async function collectChildren(element) {
|
||||
if (store.size > 30)
|
||||
await requestIdle();
|
||||
const out = [];
|
||||
if (!isInstance(element, Node)) return out;
|
||||
for (const el of store.keys()) {
|
||||
if (el === element || !isInstance(el, Node)) continue;
|
||||
if (element.contains(el))
|
||||
out.push(el);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function observerAdded(addedNodes, is_root) {
|
||||
let out = false;
|
||||
for (const element of addedNodes) {
|
||||
if (is_root) collectChildren(element).then(observerAdded);
|
||||
if (!store.has(element)) continue;
|
||||
const ls = store.get(element);
|
||||
if (!ls.length_c) continue;
|
||||
element.dispatchEvent(new Event(evc));
|
||||
ls.connected = /* @__PURE__ */ new WeakSet();
|
||||
ls.length_c = 0;
|
||||
if (!ls.length_d) store.delete(element);
|
||||
out = true;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function observerRemoved(removedNodes, is_root) {
|
||||
let out = false;
|
||||
for (const element of removedNodes) {
|
||||
if (is_root) collectChildren(element).then(observerRemoved);
|
||||
if (!store.has(element)) continue;
|
||||
const ls = store.get(element);
|
||||
if (!ls.length_d) continue;
|
||||
(globalThis.queueMicrotask || setTimeout)(dispatchRemove(element));
|
||||
out = true;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function dispatchRemove(element) {
|
||||
return () => {
|
||||
if (element.isConnected) return;
|
||||
element.dispatchEvent(new Event(evd));
|
||||
store.delete(element);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// src/customElement.js
|
||||
function customElementRender(target, render, props = observedAttributes2) {
|
||||
function customElementRender(target, render, props = {}) {
|
||||
const custom_element = target.host || target;
|
||||
scope.push({
|
||||
scope: custom_element,
|
||||
@ -606,81 +658,40 @@ var DDE = (() => {
|
||||
obj[method] = new Proxy(obj[method] || (() => {
|
||||
}), { apply });
|
||||
}
|
||||
function observedAttributes2(instance) {
|
||||
return observedAttributes(instance, (i, n) => i.getAttribute(n));
|
||||
}
|
||||
|
||||
// src/events.js
|
||||
function dispatchEvent(name, options, host) {
|
||||
if (typeof options === "function") {
|
||||
host = options;
|
||||
options = null;
|
||||
}
|
||||
if (!options) options = {};
|
||||
return function dispatch(element, ...d) {
|
||||
if (host) {
|
||||
d.unshift(element);
|
||||
element = typeof host === "function" ? host() : host;
|
||||
}
|
||||
const event = d.length ? new CustomEvent(name, oAssign({ detail: d[0] }, options)) : new Event(name, options);
|
||||
return element.dispatchEvent(event);
|
||||
};
|
||||
// src/memo.js
|
||||
var memoMark = "__dde_memo";
|
||||
var memo_scope = [];
|
||||
function memo(key, generator) {
|
||||
if (!memo_scope.length) return generator(key);
|
||||
const k = typeof key === "object" ? JSON.stringify(key) : key;
|
||||
const [{ cache, after }] = memo_scope;
|
||||
return after(k, hasOwn(cache, k) ? cache[k] : generator(key));
|
||||
}
|
||||
function on(event, listener, options) {
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(event, listener, options);
|
||||
return element;
|
||||
};
|
||||
}
|
||||
var lifeOptions = (obj) => oAssign({}, typeof obj === "object" ? obj : null, { once: true });
|
||||
on.connected = function(listener, options) {
|
||||
options = lifeOptions(options);
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(evc, listener, options);
|
||||
if (element[keyLTE]) return element;
|
||||
if (element.isConnected) return element.dispatchEvent(new Event(evc)), element;
|
||||
const c = onAbort(options.signal, () => c_ch_o.offConnected(element, listener));
|
||||
if (c) c_ch_o.onConnected(element, listener);
|
||||
return element;
|
||||
};
|
||||
memo.isScope = function(obj) {
|
||||
return obj[memoMark];
|
||||
};
|
||||
on.disconnected = function(listener, options) {
|
||||
options = lifeOptions(options);
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(evd, listener, options);
|
||||
if (element[keyLTE]) return element;
|
||||
const c = onAbort(options.signal, () => c_ch_o.offDisconnected(element, listener));
|
||||
if (c) c_ch_o.onDisconnected(element, listener);
|
||||
return element;
|
||||
};
|
||||
};
|
||||
var store_abort = /* @__PURE__ */ new WeakMap();
|
||||
on.disconnectedAsAbort = function(host) {
|
||||
if (store_abort.has(host)) return store_abort.get(host);
|
||||
const a = new AbortController();
|
||||
store_abort.set(host, a);
|
||||
host(on.disconnected(() => a.abort()));
|
||||
return a.signal;
|
||||
};
|
||||
var els_attribute_store = /* @__PURE__ */ new WeakSet();
|
||||
on.attributeChanged = function(listener, options) {
|
||||
if (typeof options !== "object")
|
||||
options = {};
|
||||
return function registerElement(element) {
|
||||
element.addEventListener(eva, listener, options);
|
||||
if (element[keyLTE] || els_attribute_store.has(element))
|
||||
return element;
|
||||
if (!enviroment.M) return element;
|
||||
const observer = new enviroment.M(function(mutations) {
|
||||
for (const { attributeName, target } of mutations)
|
||||
target.dispatchEvent(
|
||||
new CustomEvent(eva, { detail: [attributeName, target.getAttribute(attributeName)] })
|
||||
);
|
||||
memo.scope = function memoScope(fun, { signal, onlyLast } = {}) {
|
||||
let cache = oCreate();
|
||||
function memoScope2(...args) {
|
||||
if (signal && signal.aborted)
|
||||
return fun.apply(this, args);
|
||||
let cache_local = onlyLast ? cache : oCreate();
|
||||
memo_scope.unshift({
|
||||
cache,
|
||||
after(key, val) {
|
||||
return cache_local[key] = val;
|
||||
}
|
||||
});
|
||||
const c = onAbort(options.signal, () => observer.disconnect());
|
||||
if (c) observer.observe(element, { attributes: true });
|
||||
return element;
|
||||
};
|
||||
const out = fun.apply(this, args);
|
||||
memo_scope.shift();
|
||||
cache = cache_local;
|
||||
return out;
|
||||
}
|
||||
memoScope2[memoMark] = true;
|
||||
memoScope2.clear = () => cache = oCreate();
|
||||
if (signal) signal.addEventListener("abort", memoScope2.clear);
|
||||
return memoScope2;
|
||||
};
|
||||
return __toCommonJS(index_exports);
|
||||
})();
|
||||
|
80
dist/iife.min.d.ts
vendored
80
dist/iife.min.d.ts
vendored
@ -18,6 +18,7 @@ export type Actions<V> = Record<string | SymbolOnclear, Action<V>>;
|
||||
export type OnListenerOptions = Pick<AddEventListenerOptions, "signal"> & {
|
||||
first_time?: boolean;
|
||||
};
|
||||
export type SElement = Node | Element | DocumentFragment | ddeHTMLElement | ddeSVGElement | ddeDocumentFragment;
|
||||
export interface signal {
|
||||
_: Symbol;
|
||||
/**
|
||||
@ -63,7 +64,7 @@ export interface signal {
|
||||
* S.el(listS, list=> list.map(li=> el("li", li)));
|
||||
* ```
|
||||
* */
|
||||
el<S extends any>(signal: Signal<S, any>, el: (v: S) => Element | Element[] | DocumentFragment): DocumentFragment;
|
||||
el<S extends any>(signal: Signal<S, any>, el: (v: S) => SElement | SElement[]): DocumentFragment;
|
||||
observedAttributes(custom_element: HTMLElement): Record<string, Signal<string, {}>>;
|
||||
}
|
||||
declare const signal: signal;
|
||||
@ -79,10 +80,11 @@ export type CustomElementTagNameMap = {
|
||||
export type SupportedElement = HTMLElementTagNameMap[keyof HTMLElementTagNameMap] | SVGElementTagNameMap[keyof SVGElementTagNameMap] | MathMLElementTagNameMap[keyof MathMLElementTagNameMap] | CustomElementTagNameMap[keyof CustomElementTagNameMap];
|
||||
declare global {
|
||||
type ddeComponentAttributes = Record<any, any> | undefined;
|
||||
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node> = (element: El) => any;
|
||||
type ddeElementAddon<El extends SupportedElement | DocumentFragment | Node> = (element: El, ...rest: any) => any;
|
||||
type ddeString = string | Signal<string, {}>;
|
||||
type ddeStringable = ddeString | number | Signal<number, {}>;
|
||||
}
|
||||
export type Host<EL extends SupportedElement> = (...addons: ddeElementAddon<EL>[]) => EL;
|
||||
export type PascalCase = `${Capitalize<string>}${string}`;
|
||||
export type AttrsModified = {
|
||||
/**
|
||||
@ -144,6 +146,7 @@ export namespace el {
|
||||
host?: "this" | "parentElement";
|
||||
}, is_open?: boolean): Comment;
|
||||
}
|
||||
export function chainableAppend<EL extends SupportedElement>(el: EL): EL | ddeHTMLElement;
|
||||
export function el<A extends ddeComponentAttributes, EL extends SupportedElement | ddeDocumentFragment>(component: (attr: A, ...rest: any[]) => EL, attrs?: NoInfer<A>, ...addons: ddeElementAddon<EL>[]): EL extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] ? EL : (EL extends ddeDocumentFragment ? EL : ddeHTMLElement);
|
||||
export function el<A extends {
|
||||
textContent: ddeStringable;
|
||||
@ -156,7 +159,6 @@ export function elNS(namespace: "http://www.w3.org/1998/Math/MathML"): <TAG exte
|
||||
[key in keyof EL]: EL[key] | Signal<EL[key], {}> | string | number | boolean;
|
||||
}>, ...addons: ddeElementAddon<NoInfer<EL>>[]) => ddeMathMLElement;
|
||||
export function elNS(namespace: string): (tag_name: string, attrs?: string | ddeStringable | Record<string, any>, ...addons: ddeElementAddon<SupportedElement>[]) => SupportedElement;
|
||||
export function chainableAppend<EL extends SupportedElement>(el: EL): EL;
|
||||
/** Simulate slots for ddeComponents */
|
||||
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(root: EL): EL;
|
||||
/**
|
||||
@ -165,9 +167,9 @@ export function simulateSlots<EL extends SupportedElement | DocumentFragment>(ro
|
||||
* @param body Body of the custom element
|
||||
* */
|
||||
export function simulateSlots<EL extends SupportedElement | DocumentFragment>(el: HTMLElement, body: EL): EL;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, element: SupportedElement): (data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, host: Host<SupportedElement>): (data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?: EventInit): (element: SupportedElement, data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, element: SupportedElement | (() => SupportedElement)): (data?: any) => void;
|
||||
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
|
||||
export interface On {
|
||||
/** Listens to the DOM event. See {@link Document.addEventListener} */
|
||||
<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE;
|
||||
@ -185,7 +187,7 @@ export interface On {
|
||||
export const on: On;
|
||||
export type Scope = {
|
||||
scope: Node | Function | Object;
|
||||
host: ddeElementAddon<any>;
|
||||
host: Host<SupportedElement>;
|
||||
custom_element: false | HTMLElement;
|
||||
prevent: boolean;
|
||||
};
|
||||
@ -201,7 +203,11 @@ export const scope: {
|
||||
* It can be also used to register Addon(s) (functions to be called when component is initized)
|
||||
* — `scope.host(on.connected(console.log))`.
|
||||
* */
|
||||
host: (...addons: ddeElementAddon<SupportedElement>[]) => HTMLElement;
|
||||
host: Host<SupportedElement>;
|
||||
/**
|
||||
* Creates/gets an AbortController that triggers when the element disconnects
|
||||
* */
|
||||
signal: AbortSignal;
|
||||
state: Scope[];
|
||||
/** Adds new child scope. All attributes are inherited by default. */
|
||||
push(scope?: Partial<Scope>): ReturnType<Array<Scope>["push"]>;
|
||||
@ -213,7 +219,6 @@ export const scope: {
|
||||
export function customElementRender<EL extends HTMLElement, P extends any = Record<string, string | Signal<string, {}>>>(target: ShadowRoot | EL, render: (props: P) => SupportedElement | DocumentFragment, props?: P | ((el: EL) => P)): EL;
|
||||
export function customElementWithDDE<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||
export function lifecyclesToEvents<EL extends (new () => HTMLElement)>(custom_element: EL): EL;
|
||||
export function observedAttributes(custom_element: HTMLElement): Record<string, string>;
|
||||
/**
|
||||
* This is used primarly for server side rendering. To be sure that all async operations
|
||||
* are finished before the page is sent to the client.
|
||||
@ -234,6 +239,65 @@ export function observedAttributes(custom_element: HTMLElement): Record<string,
|
||||
* ```
|
||||
* */
|
||||
export function queue(promise?: Promise<unknown>): Promise<unknown>;
|
||||
/**
|
||||
* Memoization utility for caching DOM elements to improve performance.
|
||||
* Used to prevent unnecessary recreation of elements when rendering lists or complex components.
|
||||
*
|
||||
* @param key - Unique identifier for the element (usually an ID or unique value)
|
||||
* @param generator - Function that creates the element
|
||||
* @returns The cached element if the key exists, otherwise the result of the generator function
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Within S.el for list rendering
|
||||
* S.el(itemsSignal, (items, memo) =>
|
||||
* el("ul").append(
|
||||
* ...items.map(item =>
|
||||
* memo(item.id, () => el(ItemComponent, item))
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
export function memo<T>(key: string | number | object, generator: (key: any) => T): T;
|
||||
/**
|
||||
* Memo namespace containing utility functions for memoization.
|
||||
*/
|
||||
export namespace memo {
|
||||
/**
|
||||
* Checks if an object is a memo scope.
|
||||
* @param obj - The object to check
|
||||
* @returns True if the object is a memo scope
|
||||
*/
|
||||
export function isScope(obj: any): boolean;
|
||||
/**
|
||||
* Creates a memoized function with optional cleanup support.
|
||||
*
|
||||
* @param fun - The function to memoize
|
||||
* @param options - Configuration options
|
||||
* @param options.signal - AbortSignal for cleanup
|
||||
* @param options.onlyLast - When true, only keeps the cache from the most recent call
|
||||
* @returns A memoized version of the function with a .clear() method
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const renderItems = memo.scope(function(items) {
|
||||
* return items.map(item =>
|
||||
* memo(item.id, () => el("div", item.name))
|
||||
* );
|
||||
* }, {
|
||||
* signal: controller.signal,
|
||||
* onlyLast: true
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function scope<F extends Function>(fun: F, options?: {
|
||||
signal?: AbortSignal;
|
||||
onlyLast?: boolean;
|
||||
}): F & {
|
||||
clear: () => void;
|
||||
};
|
||||
}
|
||||
/* TypeScript MEH */
|
||||
declare global {
|
||||
type ddeAppend<el> = (...nodes: (Node | string)[]) => el;
|
||||
|
2
dist/iife.min.js
vendored
2
dist/iife.min.js
vendored
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user