first commit

This commit is contained in:
douboer@gmail.com
2026-03-03 13:23:14 +08:00
commit 3b7c1d558a
161 changed files with 28120 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
import { isFunction } from "../helpers";
/**
* Debouncing functions
*
* https://www.freecodecamp.org/news/javascript-debounce-example
*
* https://programmingwithmosh.com/javascript/javascript/throttle-and-debounce-patterns/
*/
const DEBOUNCE_TIME = 0;
export function bounce(fn: TimerHandler, ...args: unknown[]): number {
return setTimeout(fn, DEBOUNCE_TIME, ...args);
}
/**
* Delay the execution of the function until a pause happens
* @param fn The function to execute
* @returns A function that limits the intermediate calls to `fn`
*/
export function debounce(fn: TimerHandler) {
let flag: number;
return (...args: unknown[]) => {
if (!isFunction(fn)) return;
clearTimeout(flag);
flag = bounce(fn, ...args);
};
}

View File

@@ -0,0 +1,42 @@
import type { IDisposable } from "../types";
/**
* Disposables
*
* https://blog.hediet.de/post/the_disposable_pattern_in_typescript
*
* https://github.com/xtermjs/xterm.js/blob/master/src/common/Lifecycle.ts
*/
export default class Disposable implements IDisposable {
// private store for states
#disposables: IDisposable[];
public isDisposed: boolean;
constructor() {
this.isDisposed = false;
this.#disposables = [];
}
/**
* Registers a disposable object
* @param d The disposable to register
*/
public register<T extends IDisposable>(d: T): void {
if (this.isDisposed) {
d?.dispose();
} else {
this.#disposables.push(d);
}
}
/**
* Disposes the object, triggering the `dispose` method on all registered disposables
*/
public dispose(): void {
if (this.isDisposed) return;
this.isDisposed = true;
this.#disposables.forEach((d) => d?.dispose());
}
}

View File

@@ -0,0 +1,13 @@
export class XError extends Error {
constructor(message: string) {
message = "[x] " + message;
super(message);
this.name = "XTerminalError";
}
}
export const TARGET_INVALID_ERR =
"mount: A parent HTMLElement (target) is required";
export const TARGET_NOT_CONNECTED_ERR =
"'mount' was called on an HTMLElement (target) that is not attached to DOM.";

View File

@@ -0,0 +1,70 @@
import { isFunction } from "../helpers";
import type { IDisposable } from "../types";
/**
* Reactivity
* => https://github.com/henryhale/reactivity
*/
/**
* Effect - callback triggered when a reactive value changes
*/
export type IEffect = () => void;
/**
* Reactive value -> all effects are disposable
*/
export interface IReactive<T> extends IDisposable {
value: T;
}
/**
* Global observer
*/
let observer: IEffect | null;
/**
* Function that creates a disposable reactive object from a primitive value
* @param value The primitive value (initial)
* @returns Reactive object
*/
export function ref<T>(value: T): IReactive<T> {
const observers = new Set<IEffect>();
let disposed = false;
return {
get value() {
// allow subscriptons only when the object is not disposed
if (!disposed && isFunction(observer)) {
observers.add(observer);
}
return value;
},
set value(newValue) {
value = newValue;
// alert subscribers only when the object is not disposed
if (!disposed) {
observers.forEach((o) => o.call(undefined));
}
},
dispose() {
if (disposed) return;
disposed = true;
// remove all subscriptions
observers.clear();
}
};
}
/**
* Function that opens a subscription to reactive values
* @param fn The subscription (function) to be made
*/
export function createEffect(fn: IEffect): void {
if (!isFunction(fn)) return;
observer = fn;
try {
fn.call(undefined);
} finally {
observer = null;
}
}