watch-selector
Version:
Runs a function when a selector is added to dom
156 lines (126 loc) • 4.7 kB
text/typescript
// Utility functions for wrapping generators with timing controls
import type { ElementFn, TypedGeneratorContext } from '../types.ts';
// Debounce a generator function
export function debounceGenerator<El extends HTMLElement>(
generatorFn: (ctx: TypedGeneratorContext<El>) => Generator<ElementFn<El>, void, unknown>,
delay: number
): (ctx: TypedGeneratorContext<El>) => Generator<ElementFn<El>, void, unknown> {
const timers = new WeakMap<HTMLElement, number>();
return function* (ctx: TypedGeneratorContext<El>) {
// We need to get the current element to debounce per-element
const generator = generatorFn(ctx);
let result = generator.next();
while (!result.done) {
const originalFn = result.value;
// Wrap the element function with debouncing
const debouncedFn: ElementFn<El> = (element: El) => {
// Clear existing timer for this element
const existingTimer = timers.get(element);
if (existingTimer) {
clearTimeout(existingTimer);
}
// Set new timer
const timer = setTimeout(() => {
originalFn(element);
timers.delete(element);
}, delay);
timers.set(element, timer as unknown as number);
};
yield debouncedFn;
result = generator.next();
}
};
}
// Throttle a generator function
export function throttleGenerator<El extends HTMLElement>(
generatorFn: (ctx: TypedGeneratorContext<El>) => Generator<ElementFn<El>, void, unknown>,
delay: number
): (ctx: TypedGeneratorContext<El>) => Generator<ElementFn<El>, void, unknown> {
const lastCalls = new WeakMap<HTMLElement, number>();
return function* (ctx: TypedGeneratorContext<El>) {
const generator = generatorFn(ctx);
let result = generator.next();
while (!result.done) {
const originalFn = result.value;
// Wrap the element function with throttling
const throttledFn: ElementFn<El> = (element: El) => {
const now = Date.now();
const lastCall = lastCalls.get(element) || 0;
if (now - lastCall >= delay) {
originalFn(element);
lastCalls.set(element, now);
}
};
yield throttledFn;
result = generator.next();
}
};
}
// Run a generator only once per element
export function onceGenerator<El extends HTMLElement>(
generatorFn: (ctx: TypedGeneratorContext<El>) => Generator<ElementFn<El>, void, unknown>
): (ctx: TypedGeneratorContext<El>) => Generator<ElementFn<El>, void, unknown> {
const executed = new WeakSet<HTMLElement>();
return function* (ctx: TypedGeneratorContext<El>) {
const generator = generatorFn(ctx);
let result = generator.next();
while (!result.done) {
const originalFn = result.value;
// Wrap the element function to run only once
const onceFn: ElementFn<El> = (element: El) => {
if (!executed.has(element)) {
originalFn(element);
executed.add(element);
}
};
yield onceFn;
result = generator.next();
}
};
}
// Delay a generator function
export function delayGenerator<El extends HTMLElement>(
generatorFn: () => Generator<ElementFn<El>, void, unknown>,
delay: number
): () => Generator<ElementFn<El>, void, unknown> {
return function* () {
const generator = generatorFn();
let result = generator.next();
while (!result.done) {
const originalFn = result.value;
// Wrap the element function with delay
const delayedFn: ElementFn<El> = (element: El) => {
setTimeout(() => originalFn(element), delay);
};
yield delayedFn;
result = generator.next();
}
};
}
// Batch multiple element function calls
export function batchGenerator<El extends HTMLElement>(
generatorFn: () => Generator<ElementFn<El>, void, unknown>,
batchSize: number = 10
): () => Generator<ElementFn<El>, void, unknown> {
return function* () {
const generator = generatorFn();
let result = generator.next();
while (!result.done) {
const originalFn = result.value;
// Collect elements to batch process
const elementBatch: El[] = [];
const batchedFn: ElementFn<El> = (element: El) => {
elementBatch.push(element);
if (elementBatch.length >= batchSize) {
// Process batch
const batch = elementBatch.splice(0, batchSize);
requestAnimationFrame(() => {
batch.forEach(el => originalFn(el));
});
}
};
yield batchedFn;
result = generator.next();
}
};
}