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
@@ -0,0 +1,141 @@
import { describe, expect, it } from 'vitest';
import { defineComponent, nextTick, ref } from 'vue';
import { mount } from '@vue/test-utils';
import { useTemplateRefsList } from '.';
describe(useTemplateRefsList, () => {
it('collects elements rendered with v-for', async () => {
const Component = defineComponent({
setup() {
const items = ref([1, 2, 3]);
const { refs, set } = useTemplateRefsList<HTMLDivElement>();
return { items, refs, set };
},
template: `<div v-for="item in items" :key="item" :ref="set">{{ item }}</div>`,
});
const wrapper = mount(Component);
await nextTick();
expect(wrapper.vm.refs).toHaveLength(3);
expect(wrapper.vm.refs[0]).toBeInstanceOf(HTMLDivElement);
expect(wrapper.vm.refs[1]).toBeInstanceOf(HTMLDivElement);
expect(wrapper.vm.refs[2]).toBeInstanceOf(HTMLDivElement);
});
it('updates refs when items are added', async () => {
const Component = defineComponent({
setup() {
const items = ref([1, 2]);
const { refs, set } = useTemplateRefsList<HTMLDivElement>();
return { items, refs, set };
},
template: `<div v-for="item in items" :key="item" :ref="set">{{ item }}</div>`,
});
const wrapper = mount(Component);
await nextTick();
expect(wrapper.vm.refs).toHaveLength(2);
wrapper.vm.items.push(3);
await nextTick();
expect(wrapper.vm.refs).toHaveLength(3);
});
it('updates refs when items are removed', async () => {
const Component = defineComponent({
setup() {
const items = ref([1, 2, 3]);
const { refs, set } = useTemplateRefsList<HTMLDivElement>();
return { items, refs, set };
},
template: `<div v-for="item in items" :key="item" :ref="set">{{ item }}</div>`,
});
const wrapper = mount(Component);
await nextTick();
expect(wrapper.vm.refs).toHaveLength(3);
wrapper.vm.items.splice(0, 1);
await nextTick();
expect(wrapper.vm.refs).toHaveLength(2);
});
it('returns empty array when no elements are rendered', async () => {
const Component = defineComponent({
setup() {
const items = ref<number[]>([]);
const { refs, set } = useTemplateRefsList<HTMLDivElement>();
return { items, refs, set };
},
template: `<div><span v-for="item in items" :key="item" :ref="set">{{ item }}</span></div>`,
});
const wrapper = mount(Component);
await nextTick();
expect(wrapper.vm.refs).toHaveLength(0);
});
it('unwraps component instances to their root elements', async () => {
const Child = defineComponent({
template: `<span class="child">child</span>`,
});
const Parent = defineComponent({
components: { Child },
setup() {
const items = ref([1, 2]);
const { refs, set } = useTemplateRefsList<HTMLSpanElement>();
return { items, refs, set };
},
template: `<div><Child v-for="item in items" :key="item" :ref="set" /></div>`,
});
const wrapper = mount(Parent);
await nextTick();
expect(wrapper.vm.refs).toHaveLength(2);
expect(wrapper.vm.refs[0]).toBeInstanceOf(HTMLSpanElement);
expect(wrapper.vm.refs[0]!.classList.contains('child')).toBeTruthy();
});
it('preserves element order matching v-for order', async () => {
const Component = defineComponent({
setup() {
const items = ref(['a', 'b', 'c']);
const { refs, set } = useTemplateRefsList<HTMLDivElement>();
return { items, refs, set };
},
template: `<div v-for="item in items" :key="item" :ref="set" :data-item="item">{{ item }}</div>`,
});
const wrapper = mount(Component);
await nextTick();
expect(wrapper.vm.refs[0]!.dataset.item).toBe('a');
expect(wrapper.vm.refs[1]!.dataset.item).toBe('b');
expect(wrapper.vm.refs[2]!.dataset.item).toBe('c');
});
it('handles complete list replacement', async () => {
const Component = defineComponent({
setup() {
const items = ref([1, 2, 3]);
const { refs, set } = useTemplateRefsList<HTMLDivElement>();
return { items, refs, set };
},
template: `<div v-for="item in items" :key="item" :ref="set" :data-item="item">{{ item }}</div>`,
});
const wrapper = mount(Component);
await nextTick();
expect(wrapper.vm.refs).toHaveLength(3);
wrapper.vm.items = [4, 5];
await nextTick();
expect(wrapper.vm.refs).toHaveLength(2);
expect(wrapper.vm.refs[0]!.dataset.item).toBe('4');
expect(wrapper.vm.refs[1]!.dataset.item).toBe('5');
});
});
@@ -0,0 +1,72 @@
import { onBeforeUpdate, onMounted, onUpdated, readonly, shallowRef } from 'vue';
import type { DeepReadonly, ShallowRef } from 'vue';
import type { MaybeElement } from '../unrefElement';
import { unrefElement } from '../unrefElement';
export interface UseTemplateRefsListReturn<El extends Element> {
/** Reactive readonly array of collected template refs */
refs: DeepReadonly<ShallowRef<El[]>>;
/** Ref setter function — bind via `:ref="set"` in templates */
set: (el: MaybeElement) => void;
}
/**
* @name useTemplateRefsList
* @category Component
* @description Collects a dynamic list of template refs for use with `v-for`.
* Automatically clears the list before each component update and repopulates it
* with fresh element references. Handles both plain DOM elements and Vue component
* instances (unwraps `$el`).
*
* Uses a non-reactive buffer internally to collect refs during the render cycle,
* then flushes to a `shallowRef` in `onMounted`/`onUpdated` to avoid triggering
* recursive update loops.
*
* @returns {UseTemplateRefsListReturn<El>} An object with a reactive `refs` array and a `set` function
*
* @example
* const { refs, set } = useTemplateRefsList<HTMLDivElement>();
* // Template: <div v-for="item in items" :key="item.id" :ref="set" />
* // refs.value contains all rendered div elements
*
* @since 0.0.14
*/
export function useTemplateRefsList<El extends Element = Element>(): UseTemplateRefsListReturn<El> {
const refs = shallowRef<El[]>([]);
let buffer: El[] = [];
const set = (el: MaybeElement) => {
const plain = unrefElement(el);
if (plain)
buffer.push(plain as unknown as El);
};
const flush = () => {
buffer.sort(documentPositionComparator);
refs.value = buffer;
};
onBeforeUpdate(() => {
buffer = [];
});
onMounted(flush);
onUpdated(flush);
return {
refs: readonly(refs) as DeepReadonly<ShallowRef<El[]>>,
set,
};
}
function documentPositionComparator(a: Element, b: Element): number {
if (a === b) return 0;
const position = a.compareDocumentPosition(b);
if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1;
if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1;
return 0;
}