feat(monorepo): migrate vue packages and apply oxlint refactors

This commit is contained in:
2026-03-07 18:07:22 +07:00
parent abd6605db3
commit 41d5e18f6b
286 changed files with 10295 additions and 5028 deletions
+22
View File
@@ -0,0 +1,22 @@
import { getCurrentInstance } from 'vue';
import type { ComponentInternalInstance } from 'vue';
/**
* @name getLifeCycleTarger
* @category Utils
* @description Function to get the target instance of the lifecycle hook
*
* @param {ComponentInternalInstance} target The target instance of the lifecycle hook
* @returns {ComponentInternalInstance | null} Instance of the lifecycle hook or null
*
* @example
* const target = getLifeCycleTarger();
*
* @example
* const target = getLifeCycleTarger(instance);
*
* @since 0.0.1
*/
export function getLifeCycleTarger(target?: ComponentInternalInstance) {
return target || getCurrentInstance();
}
+13
View File
@@ -0,0 +1,13 @@
/**
* @name VueToolsError
* @category Error
* @description VueToolsError is a custom error class that represents an error in Vue Tools
*
* @since 0.0.1
*/
export class VueToolsError extends Error {
constructor(message: string) {
super(message);
this.name = 'VueToolsError';
}
}
+171
View File
@@ -0,0 +1,171 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { ref } from 'vue';
import { bypassFilter, debounceFilter, throttleFilter, pausableFilter } from './filters';
describe(bypassFilter, () => {
it('invokes callback immediately', () => {
const fn = vi.fn();
bypassFilter(fn);
expect(fn).toHaveBeenCalledOnce();
});
});
describe(debounceFilter, () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it('delays invocation by the specified ms', () => {
const filter = debounceFilter(100);
const fn = vi.fn();
filter(fn);
expect(fn).not.toHaveBeenCalled();
vi.advanceTimersByTime(50);
expect(fn).not.toHaveBeenCalled();
vi.advanceTimersByTime(50);
expect(fn).toHaveBeenCalledOnce();
});
it('resets timer on repeated calls', () => {
const filter = debounceFilter(100);
const fn = vi.fn();
filter(fn);
vi.advanceTimersByTime(80);
filter(fn);
vi.advanceTimersByTime(80);
expect(fn).not.toHaveBeenCalled();
vi.advanceTimersByTime(20);
expect(fn).toHaveBeenCalledOnce();
});
it('supports reactive ms via ref', () => {
const delay = ref(100);
const filter = debounceFilter(delay);
const fn = vi.fn();
filter(fn);
vi.advanceTimersByTime(100);
expect(fn).toHaveBeenCalledOnce();
delay.value = 200;
filter(fn);
vi.advanceTimersByTime(100);
expect(fn).toHaveBeenCalledOnce(); // still 1
vi.advanceTimersByTime(100);
expect(fn).toHaveBeenCalledTimes(2);
});
});
describe(throttleFilter, () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it('invokes immediately on first call (leading)', () => {
const filter = throttleFilter(100);
const fn = vi.fn();
filter(fn);
expect(fn).toHaveBeenCalledOnce();
});
it('throttles subsequent calls', () => {
const filter = throttleFilter(100);
const fn1 = vi.fn();
const fn2 = vi.fn();
filter(fn1);
expect(fn1).toHaveBeenCalledOnce();
// Within throttle window
filter(fn2);
expect(fn2).not.toHaveBeenCalled();
// After throttle window, trailing fires
vi.advanceTimersByTime(100);
expect(fn2).toHaveBeenCalledOnce();
});
it('does not invoke trailing when trailing=false', () => {
const filter = throttleFilter(100, false, true);
const fn1 = vi.fn();
const fn2 = vi.fn();
filter(fn1);
expect(fn1).toHaveBeenCalledOnce();
filter(fn2);
vi.advanceTimersByTime(200);
expect(fn2).not.toHaveBeenCalled();
});
it('does not invoke leading when leading=false', () => {
const filter = throttleFilter(100, true, false);
const fn = vi.fn();
filter(fn);
expect(fn).not.toHaveBeenCalled();
vi.advanceTimersByTime(100);
expect(fn).toHaveBeenCalledOnce();
});
});
describe(pausableFilter, () => {
it('invokes immediately when active', () => {
const { filter, isActive } = pausableFilter();
const fn = vi.fn();
expect(isActive.value).toBeTruthy();
filter(fn);
expect(fn).toHaveBeenCalledOnce();
});
it('queues invocations when paused', () => {
const { filter, pause } = pausableFilter();
const fn = vi.fn();
pause();
filter(fn);
expect(fn).not.toHaveBeenCalled();
});
it('replays queued invocations on resume', () => {
const { filter, pause, resume } = pausableFilter();
const fn1 = vi.fn();
const fn2 = vi.fn();
pause();
filter(fn1);
filter(fn2);
expect(fn1).not.toHaveBeenCalled();
expect(fn2).not.toHaveBeenCalled();
resume();
expect(fn1).toHaveBeenCalledOnce();
expect(fn2).toHaveBeenCalledOnce();
});
it('isActive reflects the paused state', () => {
const { isActive, pause, resume } = pausableFilter();
expect(isActive.value).toBeTruthy();
pause();
expect(isActive.value).toBeFalsy();
resume();
expect(isActive.value).toBeTruthy();
});
});
+138
View File
@@ -0,0 +1,138 @@
import { ref, toValue } from 'vue';
import type { MaybeRefOrGetter, Ref } from 'vue';
export type EventFilter = (invoke: () => void) => void;
export interface ConfigurableEventFilter {
/**
* Event filter for controlling how frequently writes propagate
*
* @example debounceFilter(500) — debounce writes by 500ms
* @example throttleFilter(1000) — throttle writes to once per 1000ms
*/
eventFilter?: EventFilter;
}
/**
* A no-op filter that invokes the callback immediately
*/
export const bypassFilter: EventFilter = (invoke) => {
invoke();
};
/**
* Create a debounce event filter
*
* @param ms Delay in milliseconds (can be reactive)
* @returns EventFilter
*/
export function debounceFilter(ms: MaybeRefOrGetter<number>): EventFilter {
let timer: ReturnType<typeof setTimeout> | undefined;
const filter: EventFilter = (invoke) => {
if (timer !== undefined)
clearTimeout(timer);
timer = setTimeout(() => {
timer = undefined;
invoke();
}, toValue(ms));
};
return filter;
}
/**
* Create a throttle event filter
*
* @param ms Interval in milliseconds (can be reactive)
* @param trailing Whether to invoke on trailing edge (default: true)
* @param leading Whether to invoke on leading edge (default: true)
* @returns EventFilter
*/
export function throttleFilter(
ms: MaybeRefOrGetter<number>,
trailing = true,
leading = true,
): EventFilter {
let lastExec = 0;
let timer: ReturnType<typeof setTimeout> | undefined;
let lastInvoke: (() => void) | undefined;
let isLeading = true;
const clear = () => {
if (timer !== undefined) {
clearTimeout(timer);
timer = undefined;
}
};
const filter: EventFilter = (invoke) => {
const duration = toValue(ms);
const elapsed = Date.now() - lastExec;
lastInvoke = invoke;
if (elapsed >= duration && (leading || !isLeading)) {
lastExec = Date.now();
isLeading = false;
invoke();
clear();
return;
}
isLeading = false;
if (trailing) {
clear();
timer = setTimeout(() => {
lastExec = Date.now();
isLeading = true;
timer = undefined;
lastInvoke?.();
}, Math.max(0, duration - elapsed));
}
};
return filter;
}
export interface PausableEventFilterReturn {
filter: EventFilter;
isActive: Ref<boolean>;
pause: () => void;
resume: () => void;
}
/**
* Create a pausable event filter
*
* When paused, invocations are queued and replayed on resume.
*
* @returns PausableEventFilterReturn
*/
export function pausableFilter(): PausableEventFilterReturn {
const isActive = ref(true);
let pendingInvocations: Array<() => void> = [];
const filter: EventFilter = (invoke) => {
if (isActive.value) {
invoke();
}
else {
pendingInvocations.push(invoke);
}
};
return {
filter,
isActive: isActive as Ref<boolean>,
pause: () => { isActive.value = false; },
resume: () => {
isActive.value = true;
const pending = pendingInvocations;
pendingInvocations = [];
for (const fn of pending) fn();
},
};
}
+3
View File
@@ -0,0 +1,3 @@
export * from './components';
export * from './error';
export * from './filters';