From 5abfdc089047c12133cb262f3a11fd8131d81565 Mon Sep 17 00:00:00 2001 From: skirtle <65301168+skirtles-code@users.noreply.github.com> Date: Sun, 17 May 2026 09:24:05 +0100 Subject: [PATCH 1/5] Add support for `with-meta` --- packages/vue-vnode-utils/package.json | 11 +- .../src/__tests__/with-meta.spec.ts | 273 ++++++++++ packages/vue-vnode-utils/src/iterators.ts | 2 +- .../vue-vnode-utils/src/with-meta/index.ts | 30 ++ .../src/with-meta/iterators.ts | 177 ++++++ packages/vue-vnode-utils/tsconfig.app.json | 5 +- packages/vue-vnode-utils/tsconfig.node.json | 1 + packages/vue-vnode-utils/tsdown.config.ts | 154 ++++++ packages/vue-vnode-utils/vite.config.mts | 7 + pnpm-lock.yaml | 502 ++++++++++++++++++ 10 files changed, 1159 insertions(+), 3 deletions(-) create mode 100644 packages/vue-vnode-utils/src/__tests__/with-meta.spec.ts create mode 100644 packages/vue-vnode-utils/src/with-meta/index.ts create mode 100644 packages/vue-vnode-utils/src/with-meta/iterators.ts create mode 100644 packages/vue-vnode-utils/tsdown.config.ts diff --git a/packages/vue-vnode-utils/package.json b/packages/vue-vnode-utils/package.json index 1d02948..5723060 100644 --- a/packages/vue-vnode-utils/package.json +++ b/packages/vue-vnode-utils/package.json @@ -25,6 +25,11 @@ "import": "./dist/vue-vnode-utils.esm-bundler.js", "require": "./dist/vue-vnode-utils.cjs" }, + "./with-meta": { + "types": "./dist/with-meta/vue-vnode-utils.d.ts", + "import": "./dist/with-meta/vue-vnode-utils.esm-bundler.js", + "require": "./dist/with-meta/vue-vnode-utils.cjs" + }, "./dist/*": "./dist/*", "./package.json": "./package.json" }, @@ -48,6 +53,7 @@ "npm-run-all2": "catalog:", "publint": "catalog:", "rimraf": "catalog:", + "tsdown": "^0.22.0", "typescript": "catalog:", "vite": "catalog:", "vite-plugin-dts": "catalog:", @@ -66,6 +72,9 @@ "build:dev": "vite build --mode development", "build:neutral": "vite build --mode neutral", "build:prod": "vite build --mode production", - "build": "run-s clean:dist build:* type-check lint:package" + "vite:build": "run-s clean:dist build:* type-check lint:package", + "tsdown": "tsdown", + "tsdown:build": "run-s clean:dist build:copy tsdown type-check lint:package", + "build": "run-s tsdown:build" } } diff --git a/packages/vue-vnode-utils/src/__tests__/with-meta.spec.ts b/packages/vue-vnode-utils/src/__tests__/with-meta.spec.ts new file mode 100644 index 0000000..86461c3 --- /dev/null +++ b/packages/vue-vnode-utils/src/__tests__/with-meta.spec.ts @@ -0,0 +1,273 @@ +import { describe, expect, it } from 'vitest' +import { COMPONENTS_AND_ELEMENTS, eachChild, everyChild, findChild, getText, isText, reduceChildren, someChild } from '../with-meta' +import { h, isVNode } from 'vue' + +function getAllKeys(obj: object) { + const keys: string[] = [] + + for (const key in obj) { + keys.push(key) + } + + return keys +} + +describe('someChild', () => { + it('someChild - 39f9', () => { + const startNodes = [h('div'), 'Some text', h('p')] + + let count = 0 + + let out = someChild(startNodes, (node, meta) => { + expect(isVNode(node)).toBe(true) + + let metaProperties = getAllKeys(meta) + expect(metaProperties.includes('index')).toBe(true) + expect(metaProperties.includes('length')).toBe(true) + + expect(meta.index).toBe(count) + expect(meta.length).toBe(3) + + // `length` property should still be enumerable + metaProperties = getAllKeys(meta) + expect(metaProperties.includes('index')).toBe(true) + expect(metaProperties.includes('length')).toBe(true) + + count++ + return false + }) + + expect(out).toBe(false) + expect(count).toBe(3) + + count = 0 + + out = someChild(startNodes, (node, meta) => { + expect(isVNode(node)).toBe(true) + expect(meta.index).toBe(count) + expect(meta.length).toBe(2) + count++ + return false + }, COMPONENTS_AND_ELEMENTS) + + expect(out).toBe(false) + expect(count).toBe(2) + + count = 0 + + out = someChild(startNodes, (node, meta) => { + expect(isVNode(node)).toBe(true) + expect(meta.index).toBe(count) + expect(meta.length).toBe(3) + count++ + return count === 2 + }) + + expect(out).toBe(true) + expect(count).toBe(2) + }) +}) + +describe('everyChild', () => { + it('everyChild - 1d78', () => { + const startNodes = [h('div'), 'Some text', h('p')] + + let count = 0 + + let out = everyChild(startNodes, (node, meta) => { + expect(isVNode(node)).toBe(true) + + let metaProperties = getAllKeys(meta) + expect(metaProperties.includes('index')).toBe(true) + expect(metaProperties.includes('length')).toBe(true) + + expect(meta.index).toBe(count) + expect(meta.length).toBe(3) + + // `length` property should still be enumerable + metaProperties = getAllKeys(meta) + expect(metaProperties.includes('index')).toBe(true) + expect(metaProperties.includes('length')).toBe(true) + + count++ + return true + }) + + expect(out).toBe(true) + expect(count).toBe(3) + + count = 0 + + out = everyChild(startNodes, (node, meta) => { + expect(isVNode(node)).toBe(true) + expect(meta.index).toBe(count) + expect(meta.length).toBe(2) + count++ + return true + }, COMPONENTS_AND_ELEMENTS) + + expect(out).toBe(true) + expect(count).toBe(2) + + count = 0 + + out = everyChild(startNodes, (node, meta) => { + expect(isVNode(node)).toBe(true) + expect(meta.index).toBe(count) + expect(meta.length).toBe(3) + count++ + return count !== 2 + }) + + expect(out).toBe(false) + expect(count).toBe(2) + }) +}) + +describe('eachChild', () => { + it('eachChild - 2558', () => { + const startNodes = [h('div'), 'Some text', h('p')] + + let count = 0 + + let out = eachChild(startNodes, (node, meta) => { + expect(isVNode(node)).toBe(true) + + let metaProperties = getAllKeys(meta) + expect(metaProperties.includes('index')).toBe(true) + expect(metaProperties.includes('length')).toBe(true) + + expect(meta.index).toBe(count) + expect(meta.length).toBe(3) + + // `length` property should still be enumerable + metaProperties = getAllKeys(meta) + expect(metaProperties.includes('index')).toBe(true) + expect(metaProperties.includes('length')).toBe(true) + + count++ + return !(count % 2) + }) + + expect(out).toBeUndefined() + expect(count).toBe(3) + + count = 0 + + out = eachChild(startNodes, (node, meta) => { + expect(isVNode(node)).toBe(true) + expect(meta.index).toBe(count) + expect(meta.length).toBe(2) + count++ + return count % 2 + }, COMPONENTS_AND_ELEMENTS) + + expect(out).toBeUndefined() + expect(count).toBe(2) + }) +}) + +describe('findChild', () => { + it('findChild - abc2', () => { + const startNodes = [h('div'), 'Some text', h('p')] + + let count = 0 + + let out = findChild(startNodes, (node, meta) => { + expect(isVNode(node)).toBe(true) + + let metaProperties = getAllKeys(meta) + expect(metaProperties.includes('index')).toBe(true) + expect(metaProperties.includes('length')).toBe(true) + + expect(meta.index).toBe(count) + expect(meta.length).toBe(3) + + // `length` property should still be enumerable + metaProperties = getAllKeys(meta) + expect(metaProperties.includes('index')).toBe(true) + expect(metaProperties.includes('length')).toBe(true) + + count++ + return false + }) + + expect(out).toBeUndefined() + expect(count).toBe(3) + + count = 0 + + out = findChild(startNodes, (node, meta) => { + expect(isVNode(node)).toBe(true) + expect(meta.index).toBe(count) + expect(meta.length).toBe(2) + count++ + return false + }, COMPONENTS_AND_ELEMENTS) + + expect(out).toBeUndefined() + expect(count).toBe(2) + + count = 0 + + out = findChild(startNodes, (node, meta) => { + expect(isVNode(node)).toBe(true) + expect(meta.index).toBe(count) + expect(meta.length).toBe(3) + count++ + return count === 2 + }) + + expect(isText(out)).toBe(true) + expect(count).toBe(2) + }) +}) + +describe('reduceChildren', () => { + it('reduceChildren - 0ddb', () => { + const startNodes = ['1', '2', '3'] + + let count = 0 + + let out = reduceChildren(startNodes, (acc, node, meta) => { + expect(acc).toBe([0, 1, 3][count]) + expect(isVNode(node)).toBe(true) + expect(getText(node)).toBe(String(count + 1)) + + let metaProperties = getAllKeys(meta) + expect(metaProperties.includes('index')).toBe(true) + expect(metaProperties.includes('length')).toBe(true) + + expect(meta.index).toBe(count) + expect(meta.length).toBe(3) + + // `length` property should still be enumerable + metaProperties = getAllKeys(meta) + expect(metaProperties.includes('index')).toBe(true) + expect(metaProperties.includes('length')).toBe(true) + + count++ + return acc + count + }, 0) + + expect(out).toBe(6) + expect(count).toBe(3) + + count = 0 + + out = reduceChildren([h('div'), 'Some text', h('p')], (acc, node, meta) => { + expect(acc).toBe([0, 1][count]) + expect(isVNode(node)).toBe(true) + expect(isText(node)).toBe(false) + + expect(meta.index).toBe(count) + expect(meta.length).toBe(2) + + count++ + return acc + count + }, 0, COMPONENTS_AND_ELEMENTS) + + expect(out).toBe(3) + expect(count).toBe(2) + }) +}) diff --git a/packages/vue-vnode-utils/src/iterators.ts b/packages/vue-vnode-utils/src/iterators.ts index 492c596..7a372a4 100644 --- a/packages/vue-vnode-utils/src/iterators.ts +++ b/packages/vue-vnode-utils/src/iterators.ts @@ -20,7 +20,7 @@ const warn = (method: string, msg: string) => { console.warn(`[${method}] ${msg}`) } -const checkArguments = (method: string, passed: unknown[], expected: string[]) => { +export const checkArguments = (method: string, passed: unknown[], expected: string[]) => { for (let index = 0; index < passed.length; ++index) { const t = typeOf(passed[index]) const expect = expected[index] diff --git a/packages/vue-vnode-utils/src/with-meta/index.ts b/packages/vue-vnode-utils/src/with-meta/index.ts new file mode 100644 index 0000000..5dce39b --- /dev/null +++ b/packages/vue-vnode-utils/src/with-meta/index.ts @@ -0,0 +1,30 @@ +export { + ALL_VNODES, + COMPONENTS_AND_ELEMENTS, + extractSingleChild, + getText, + getType, + isComment, + isComponent, + isElement, + isEmpty, + isFragment, + isFunctionalComponent, + isStatefulComponent, + isStatic, + isText, + type IterationOptions, + SKIP_COMMENTS +} from '@skirtle/vue-vnode-utils' + +export { + addProps, + betweenChildren, + eachChild, + findChild, + everyChild, + type IterationMeta, + reduceChildren, + replaceChildren, + someChild +} from './iterators' diff --git a/packages/vue-vnode-utils/src/with-meta/iterators.ts b/packages/vue-vnode-utils/src/with-meta/iterators.ts new file mode 100644 index 0000000..3e87d02 --- /dev/null +++ b/packages/vue-vnode-utils/src/with-meta/iterators.ts @@ -0,0 +1,177 @@ +import type { VNode, VNodeArrayChildren } from 'vue' + +import type { IterationOptions } from '@skirtle/vue-vnode-utils' +import { + addProps as addPropsRaw, + ALL_VNODES, + betweenChildren as betweenChildrenRaw, + COMPONENTS_AND_ELEMENTS, + eachChild as eachChildRaw, + everyChild as everyChildRaw, + findChild as findChildRaw, + reduceChildren as reduceChildrenRaw, + replaceChildren as replaceChildrenRaw, + SKIP_COMMENTS, + someChild as someChildRaw +} from '@skirtle/vue-vnode-utils' + +// TODO: Should probably move `checkArguments` elsewhere +import { checkArguments } from '../iterators' + +type AnyFunction = (...args: never) => unknown + +function checkFunction(method: string, callback: AnyFunction) { + checkArguments(method, [callback], ['function']) +} + +export type IterationMeta = { + readonly index: number + readonly length: number +} + +function count(children: VNodeArrayChildren, options: IterationOptions) { + let count = 0 + eachChildRaw(children, () => ++count, options) + return count +} + +function setPropertyValue(obj: object, key: string, value: unknown): any { + return Object.defineProperty(obj, key, { + value, + enumerable: true + }) +} + +function createMetaFactory(children: VNodeArrayChildren, options: IterationOptions): () => IterationMeta { + let index = -1 + + const baseMeta = { + get length() { + const length = count(children, options) + setPropertyValue(baseMeta, 'length', length) + return length + } + } + + return () => { + return setPropertyValue(Object.create(baseMeta), 'index', ++index) + } +} + +function withMeta( + iterator: (children: VNodeArrayChildren, callback: (vnode: VNode) => CallbackReturn, options: IterationOptions) => IteratorReturn, + children: VNodeArrayChildren, + callback: (vnode: VNode, meta: IterationMeta) => CallbackReturn, + options: IterationOptions +): IteratorReturn { + const metaFactory = createMetaFactory(children, options) + + return iterator(children, (vnode: VNode) => { + return callback(vnode, metaFactory()) + }, options) +} + +export const addProps = ( + children: VNodeArrayChildren, + callback: (vnode: VNode, meta: IterationMeta) => (Record | null | void), + options: IterationOptions = COMPONENTS_AND_ELEMENTS +): VNodeArrayChildren => { + if (__DEV__) { + checkFunction('addProps', callback) + } + + return withMeta(addPropsRaw, children, callback, options) +} + +export const replaceChildren = ( + children: VNodeArrayChildren, + callback: (vnode: VNode, meta: IterationMeta) => (VNode | VNodeArrayChildren | string | number | void), + options: IterationOptions = SKIP_COMMENTS +): VNodeArrayChildren => { + if (__DEV__) { + checkFunction('replaceChildren', callback) + } + + return withMeta(replaceChildrenRaw, children, callback, options) +} + +export const betweenChildren = ( + children: VNodeArrayChildren, + callback: (previousVNode: VNode, nextVNode: VNode, meta: IterationMeta) => (VNode | VNodeArrayChildren | string | number | void), + options: IterationOptions = SKIP_COMMENTS +): VNodeArrayChildren => { + if (__DEV__) { + checkFunction('betweenChildren', callback) + } + + const metaFactory = createMetaFactory(children, options) + + return betweenChildrenRaw(children, (previousVNode: VNode, nextVNode: VNode) => { + return callback(previousVNode, nextVNode, metaFactory()) + }, options) +} + +export const someChild = ( + children: VNodeArrayChildren, + callback: (vnode: VNode, meta: IterationMeta) => unknown, + options: IterationOptions = ALL_VNODES +): boolean => { + if (__DEV__) { + checkFunction('someChild', callback) + } + + return withMeta(someChildRaw, children, callback, options) +} + +export const everyChild = ( + children: VNodeArrayChildren, + callback: (vnode: VNode, meta: IterationMeta) => unknown, + options: IterationOptions = ALL_VNODES +): boolean => { + if (__DEV__) { + checkFunction('everyChild', callback) + } + + return withMeta(everyChildRaw, children, callback, options) +} + +export const eachChild = ( + children: VNodeArrayChildren, + callback: (vnode: VNode, meta: IterationMeta) => void, + options: IterationOptions = ALL_VNODES +): void => { + if (__DEV__) { + checkFunction('eachChild', callback) + } + + return withMeta(eachChildRaw, children, callback, options) +} + +export const findChild = ( + children: VNodeArrayChildren, + callback: (vnode: VNode, meta: IterationMeta) => unknown, + options: IterationOptions = ALL_VNODES +): (VNode | undefined) => { + if (__DEV__) { + checkFunction('findChild', callback) + } + + return withMeta(findChildRaw, children, callback, options) +} + +export const reduceChildren = ( + children: VNodeArrayChildren, + callback: (previousValue: R, vnode: VNode, meta: IterationMeta) => R, + initialValue: R, + options: IterationOptions = ALL_VNODES +): R => { + if (__DEV__) { + checkFunction('reduceChildren', callback) + } + + const metaFactory = createMetaFactory(children, options) + + return reduceChildrenRaw(children, (previousValue: R, vnode: VNode) => { + return callback(previousValue, vnode, metaFactory()) + }, initialValue, options) +} diff --git a/packages/vue-vnode-utils/tsconfig.app.json b/packages/vue-vnode-utils/tsconfig.app.json index 02a220a..0e7edcc 100644 --- a/packages/vue-vnode-utils/tsconfig.app.json +++ b/packages/vue-vnode-utils/tsconfig.app.json @@ -3,6 +3,9 @@ "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], "exclude": ["src/**/__tests__/*"], "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo" + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "paths": { + "@skirtle/vue-vnode-utils": ["./src/index"] + } } } diff --git a/packages/vue-vnode-utils/tsconfig.node.json b/packages/vue-vnode-utils/tsconfig.node.json index 859ba6f..3cf02e1 100644 --- a/packages/vue-vnode-utils/tsconfig.node.json +++ b/packages/vue-vnode-utils/tsconfig.node.json @@ -1,6 +1,7 @@ { "extends": "@tsconfig/node24/tsconfig.json", "include": [ + "tsdown.config.*", "vite.config.*", "vitest.config.*", "cypress.config.*", diff --git a/packages/vue-vnode-utils/tsdown.config.ts b/packages/vue-vnode-utils/tsdown.config.ts new file mode 100644 index 0000000..e45ee9a --- /dev/null +++ b/packages/vue-vnode-utils/tsdown.config.ts @@ -0,0 +1,154 @@ +import { defineConfig } from 'tsdown' +import { fileURLToPath, URL } from 'node:url' + +const base = defineConfig({ + entry: { + 'vue-vnode-utils': 'src/index.ts' + }, + target: 'es2019', + platform: 'neutral', + dts: false, + globalName: 'VueVNodeUtils' +}) + +const suffix = (suffix: string) => { + return { + outputOptions: { + entryFileNames: `[name].${suffix}`, + globals: { + vue: 'Vue' + } + } + } +} + +const cjs = defineConfig({ + ...base, + format: ['cjs'], + define: { + __DEV__: `!(process.env.NODE_ENV === 'production')` + } +}) + +const esmBundler = defineConfig({ + ...cjs, + format: ['esm'], + dts: { tsconfig: './tsconfig.app.json' }, + outExtensions: () => ({ js: '.esm-bundler.js', dts: '.d.ts' }) +}) + +const esmBrowserDev = defineConfig({ + ...base, + format: ['esm'], + define: { + __DEV__: 'true' + }, + ...suffix('esm-browser.dev.js') +}) + +const iifeDev = defineConfig({ + ...esmBrowserDev, + format: ['iife'], + ...suffix('global.dev.js') +}) + +const esmBrowserProd = defineConfig({ + ...base, + format: ['esm'], + minify: true, + define: { + __DEV__: 'false' + }, + ...suffix('esm-browser.prod.js') +}) + +const iifeProd = defineConfig({ + ...esmBrowserProd, + format: ['iife'], + ...suffix('global.prod.js') +}) + +const withMetaBase = defineConfig({ + ...base, + outDir: 'dist/with-meta', + entry: { + 'vue-vnode-utils': 'src/with-meta/index.ts' + }, + alias: { + '@skirtle/vue-vnode-utils': fileURLToPath(new URL('./src', import.meta.url)) + } +}) + +const withMetaCjs = defineConfig({ + ...withMetaBase, + format: ['cjs'], + define: { + __DEV__: `!(process.env.NODE_ENV === 'production')` + }, + alias: {}, + deps: { + neverBundle: [ + 'vue', + '@skirtle/vue-vnode-utils' + ] + } +}) + +const withMetaEsmBundler = defineConfig({ + ...withMetaCjs, + format: ['esm'], + dts: { tsconfig: './tsconfig.app.json' }, + outExtensions: () => ({ js: '.esm-bundler.js', dts: '.d.ts' }), + deps: { + neverBundle: [ + 'vue', + '@skirtle/vue-vnode-utils' + ] + } +}) + +const withMetaEsmBrowserDev = defineConfig({ + ...withMetaBase, + format: ['esm'], + define: { + __DEV__: 'true' + }, + ...suffix('esm-browser.dev.js') +}) + +const withMetaIifeDev = defineConfig({ + ...withMetaEsmBrowserDev, + format: ['iife'], + ...suffix('global.dev.js') +}) + +const withMetaEsmBrowserProd = defineConfig({ + ...withMetaBase, + format: ['esm'], + minify: true, + define: { + __DEV__: 'false' + }, + ...suffix('esm-browser.prod.js') +}) + +const withMetaIifeProd = defineConfig({ + ...withMetaEsmBrowserProd, + format: ['iife'], + ...suffix('global.prod.js') +}) + +export default [ + cjs, + esmBundler, + esmBrowserDev, + iifeDev, + esmBrowserProd, + iifeProd, + withMetaCjs, + withMetaEsmBundler, + withMetaEsmBrowserDev, + withMetaIifeDev, + withMetaEsmBrowserProd, + withMetaIifeProd +] diff --git a/packages/vue-vnode-utils/vite.config.mts b/packages/vue-vnode-utils/vite.config.mts index ccd4d6a..791094a 100644 --- a/packages/vue-vnode-utils/vite.config.mts +++ b/packages/vue-vnode-utils/vite.config.mts @@ -34,6 +34,13 @@ export default defineConfig(({ mode }): UserConfig => { dtsPlugin ], + resolve: { + alias: { + // Alias so `with-meta` can import from the root package + '@skirtle/vue-vnode-utils': fileURLToPath(new URL('./src/index.ts', import.meta.url)) + } + }, + build: { target: 'es2019', emptyOutDir: false, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 75cc9e5..3463210 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -214,6 +214,9 @@ importers: rimraf: specifier: 'catalog:' version: 6.1.3 + tsdown: + specifier: ^0.22.0 + version: 0.22.0(publint@0.3.19)(typescript@5.9.3)(vue-tsc@3.2.8) typescript: specifier: 'catalog:' version: 5.9.3 @@ -323,23 +326,49 @@ packages: '@asamuzakjp/nwsapi@2.3.9': resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + '@babel/generator@8.0.0-rc.5': + resolution: {integrity: sha512-nFZPWz3FHIS7y6rMIVoa/WBwjdutfIaRJIBQjzn+t3RnecZoRNlGmGcyR2wb0T/IgSd50Kz/6dG8/LvMCRunjg==} + engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@8.0.0-rc.5': + resolution: {integrity: sha512-sN7R8rBvDurfaziNfDEIjIntlazmlkCDGO4SNl2RJ3wRCn+QxspLV7hzYAE8WWVd2joVuT8sUxeePdLp2idI1A==} + engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-validator-identifier@7.28.5': resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@8.0.0-rc.5': + resolution: {integrity: sha512-ehJDxHvtbZ85RtX/L2fi0h9AGsBNqB5Euv1EB8RMAvGYvD+2X+QbpzzOpbklnNXO+WSZJNOaetw2BBj27xsWVg==} + engines: {node: ^22.18.0 || >=24.11.0} + '@babel/parser@7.29.3': resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==} engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@8.0.0-rc.4': + resolution: {integrity: sha512-0S/1yefMa15N4i2v3t8Fw9pgMHhf2gF6Lc1UEXI96Ls6FNAjqvHHZouZ2ZS/deqLhbMFtmfVeFac6iTsvFbLwA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + '@babel/parser@8.0.0-rc.5': + resolution: {integrity: sha512-/Mfg83rK3+jsRbl4Vbd0jqxc6M1A1/WNFtgrowRM1unEsD3XcNnrBdMM0JWakd0/RN9lseQKwPduW1TiEwKOlQ==} + engines: {node: ^22.18.0 || >=24.11.0} + hasBin: true + '@babel/types@7.29.0': resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@babel/types@8.0.0-rc.5': + resolution: {integrity: sha512-JeSVu/m8x/zpp4CLjYHVNXuhEyOkhPXuxM8YOXjh6L4LlvQNKuUNOTo5KdBuKAcTDHw8DquToTaEkhsBqPXOaA==} + engines: {node: ^22.18.0 || >=24.11.0} + '@bcoe/v8-coverage@1.0.2': resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} @@ -403,6 +432,15 @@ packages: search-insights: optional: true + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} @@ -787,6 +825,9 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -810,6 +851,12 @@ packages: '@microsoft/tsdoc@0.16.0': resolution: {integrity: sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==} + '@napi-rs/wasm-runtime@1.1.4': + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -825,6 +872,9 @@ packages: '@one-ini/wasm@0.1.1': resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + '@oxc-project/types@0.130.0': + resolution: {integrity: sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -833,9 +883,110 @@ packages: resolution: {integrity: sha512-HDVTWq3H0uTXiU0eeSQntcVUTPP3GamzeXI41+x7uU9J65JgWQh3qWZHblR1i0npXfFtF+mxBiU2nJH8znxWnQ==} engines: {node: '>=18'} + '@quansync/fs@1.0.0': + resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} + + '@rolldown/binding-android-arm64@1.0.1': + resolution: {integrity: sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.1': + resolution: {integrity: sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.1': + resolution: {integrity: sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.1': + resolution: {integrity: sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.1': + resolution: {integrity: sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.1': + resolution: {integrity: sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.1': + resolution: {integrity: sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.0.1': + resolution: {integrity: sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.0.1': + resolution: {integrity: sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.1': + resolution: {integrity: sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.1': + resolution: {integrity: sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.1': + resolution: {integrity: sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.1': + resolution: {integrity: sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.1': + resolution: {integrity: sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.1': + resolution: {integrity: sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@rolldown/pluginutils@1.0.0-rc.13': resolution: {integrity: sha512-3ngTAv6F/Py35BsYbeeLeecvhMKdsKm4AoOETVhAA+Qc8nrA2I0kF7oa93mE9qnIurngOSpMnQ0x2nQY2FPviA==} + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + '@rollup/plugin-replace@6.0.3': resolution: {integrity: sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA==} engines: {node: '>=14.0.0'} @@ -1058,6 +1209,9 @@ packages: '@tsconfig/node24@24.0.4': resolution: {integrity: sha512-2A933l5P5oCbv6qSxHs7ckKwobs8BDAe9SJ/Xr2Hy+nDlwmLE1GhFh/g/vXGRZWgxBg9nX/5piDtHR9Dkw/XuA==} + '@tybys/wasm-util@0.10.2': + resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + '@types/argparse@1.0.38': resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==} @@ -1079,6 +1233,9 @@ packages: '@types/jsdom@27.0.0': resolution: {integrity: sha512-NZyFl/PViwKzdEkQg96gtnB8wm+1ljhdDay9ahn4hgb+SfVtPCbm3TlmDUFXTA+MGN3CijicnMhG18SI5H3rFw==} + '@types/jsesc@2.5.1': + resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -1450,6 +1607,10 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} + ansis@4.3.0: + resolution: {integrity: sha512-44mvgtPvohuU/70DdY5Oz2AIrLJ9k6/5x4KmoSvPwO+5Moijo0+N9D0fKbbYZQWP1hNm5CpOf+E01jhxG/r8xg==} + engines: {node: '>=14'} + argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -1460,6 +1621,10 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + ast-kit@3.0.0-beta.1: + resolution: {integrity: sha512-trmleAnZ2PxN/loHWVhhx1qeOHSRXq4TDsBBxq3GqeJitfk3+jTQ+v/C1km/KYq9M7wKqCewMh+/NAvVH7m+bw==} + engines: {node: '>=20.19.0'} + ast-v8-to-istanbul@1.0.0: resolution: {integrity: sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==} @@ -1476,6 +1641,9 @@ packages: birpc@2.9.0: resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} + birpc@4.0.0: + resolution: {integrity: sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -1493,6 +1661,10 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + cac@7.0.0: + resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} + engines: {node: '>=20.19.0'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -1617,6 +1789,9 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -1628,6 +1803,15 @@ packages: resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} engines: {node: '>=0.3.1'} + dts-resolver@3.0.0: + resolution: {integrity: sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q==} + engines: {node: ^22.18.0 || >=24.0.0} + peerDependencies: + oxc-resolver: '>=11.0.0' + peerDependenciesMeta: + oxc-resolver: + optional: true + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -1648,6 +1832,10 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + empathic@2.0.1: + resolution: {integrity: sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==} + engines: {node: '>=14'} + entities@6.0.1: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} @@ -1850,6 +2038,10 @@ packages: resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} engines: {node: '>=18'} + get-tsconfig@5.0.0-beta.5: + resolution: {integrity: sha512-/6gFNr0N04nob252sTQxyFLi3eKFRqIg1I87YcqAMT1i6SQrSF6KujUEQrtrjMV0H/eejTCltLdDSTEMzHbnsQ==} + engines: {node: '>=20.20.0'} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1899,6 +2091,9 @@ packages: hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + hookable@6.1.1: + resolution: {integrity: sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==} + html-encoding-sniffer@6.0.0: resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -1933,6 +2128,10 @@ packages: resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} engines: {node: '>=8'} + import-without-cache@0.4.0: + resolution: {integrity: sha512-NkJQA7oZ4YHQhd2+H3BoRFKF3d/XNsiKpHZCQEMH9pDX27hQQLsTyOocyRgaIVtf8gHX3Nt3LPkR4e5EdtPAGQ==} + engines: {node: ^22.18.0 || >=24.0.0} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -2038,6 +2237,11 @@ packages: canvas: optional: true + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -2350,6 +2554,9 @@ packages: quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + quansync@1.0.0: + resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -2384,6 +2591,9 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve@1.22.12: resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} engines: {node: '>= 0.4'} @@ -2405,6 +2615,30 @@ packages: engines: {node: 20 || >=22} hasBin: true + rolldown-plugin-dts@0.25.1: + resolution: {integrity: sha512-zK82aC/8z1iVW+g0bCnlQZq04Y5bNeL/RcRwTYBwsnU6wH0N+6vpIFkN7JC0kYRS5qKA+pxQyfIPvXJ6Q5xSpQ==} + engines: {node: ^22.18.0 || >=24.0.0} + peerDependencies: + '@ts-macro/tsc': ^0.3.6 + '@typescript/native-preview': '>=7.0.0-dev.20260325.1' + rolldown: ^1.0.0 + typescript: ^5.0.0 || ^6.0.0 + vue-tsc: ~3.2.0 + peerDependenciesMeta: + '@ts-macro/tsc': + optional: true + '@typescript/native-preview': + optional: true + typescript: + optional: true + vue-tsc: + optional: true + + rolldown@1.0.1: + resolution: {integrity: sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + rollup@4.60.3: resolution: {integrity: sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -2590,6 +2824,10 @@ packages: resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} engines: {node: '>=20'} + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -2599,6 +2837,43 @@ packages: peerDependencies: typescript: '>=4.8.4' + tsdown@0.22.0: + resolution: {integrity: sha512-FgW0hHb27nGQA/+F3d5+U9wKXkfilk9DVkc5+7x/ZqF03g+Hoz/eeApT32jqxATt9eRoR+1jxk7MUMON+O4CXw==} + engines: {node: ^22.18.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@arethetypeswrong/core': ^0.18.1 + '@tsdown/css': 0.22.0 + '@tsdown/exe': 0.22.0 + '@vitejs/devtools': '*' + publint: ^0.3.8 + tsx: '*' + typescript: ^5.0.0 || ^6.0.0 + unplugin-unused: ^0.5.0 + unrun: '*' + peerDependenciesMeta: + '@arethetypeswrong/core': + optional: true + '@tsdown/css': + optional: true + '@tsdown/exe': + optional: true + '@vitejs/devtools': + optional: true + publint: + optional: true + tsx: + optional: true + typescript: + optional: true + unplugin-unused: + optional: true + unrun: + optional: true + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -2618,6 +2893,9 @@ packages: ufo@1.6.4: resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} + unconfig-core@7.5.0: + resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==} + undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} @@ -3054,19 +3332,45 @@ snapshots: '@asamuzakjp/nwsapi@2.3.9': {} + '@babel/generator@8.0.0-rc.5': + dependencies: + '@babel/parser': 8.0.0-rc.5 + '@babel/types': 8.0.0-rc.5 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + '@types/jsesc': 2.5.1 + jsesc: 3.1.0 + '@babel/helper-string-parser@7.27.1': {} + '@babel/helper-string-parser@8.0.0-rc.5': {} + '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-identifier@8.0.0-rc.5': {} + '@babel/parser@7.29.3': dependencies: '@babel/types': 7.29.0 + '@babel/parser@8.0.0-rc.4': + dependencies: + '@babel/types': 8.0.0-rc.5 + + '@babel/parser@8.0.0-rc.5': + dependencies: + '@babel/types': 8.0.0-rc.5 + '@babel/types@7.29.0': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@babel/types@8.0.0-rc.5': + dependencies: + '@babel/helper-string-parser': 8.0.0-rc.5 + '@babel/helper-validator-identifier': 8.0.0-rc.5 + '@bcoe/v8-coverage@1.0.2': {} '@csstools/color-helpers@6.0.2': {} @@ -3117,6 +3421,22 @@ snapshots: transitivePeerDependencies: - '@algolia/client-search' + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild/aix-ppc64@0.21.5': optional: true @@ -3353,6 +3673,11 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/sourcemap-codec@1.5.5': {} @@ -3397,6 +3722,13 @@ snapshots: '@microsoft/tsdoc@0.16.0': {} + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.2 + optional: true + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3411,13 +3743,70 @@ snapshots: '@one-ini/wasm@0.1.1': {} + '@oxc-project/types@0.130.0': {} + '@pkgjs/parseargs@0.11.0': optional: true '@publint/pack@0.1.4': {} + '@quansync/fs@1.0.0': + dependencies: + quansync: 1.0.0 + + '@rolldown/binding-android-arm64@1.0.1': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.1': + optional: true + + '@rolldown/binding-darwin-x64@1.0.1': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.1': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.1': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.1': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.1': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.1': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.1': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.1': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.1': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.1': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.1': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.1': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.1': + optional: true + '@rolldown/pluginutils@1.0.0-rc.13': {} + '@rolldown/pluginutils@1.0.1': {} + '@rollup/plugin-replace@6.0.3(rollup@4.60.3)': dependencies: '@rollup/pluginutils': 5.3.0(rollup@4.60.3) @@ -3601,6 +3990,11 @@ snapshots: '@tsconfig/node24@24.0.4': {} + '@tybys/wasm-util@0.10.2': + dependencies: + tslib: 2.8.1 + optional: true + '@types/argparse@1.0.38': {} '@types/chai@5.2.3': @@ -3624,6 +4018,8 @@ snapshots: '@types/tough-cookie': 4.0.5 parse5: 7.3.0 + '@types/jsesc@2.5.1': {} + '@types/json-schema@7.0.15': {} '@types/linkify-it@5.0.0': {} @@ -4053,6 +4449,8 @@ snapshots: ansi-styles@6.2.3: {} + ansis@4.3.0: {} + argparse@1.0.10: dependencies: sprintf-js: 1.0.3 @@ -4061,6 +4459,12 @@ snapshots: assertion-error@2.0.1: {} + ast-kit@3.0.0-beta.1: + dependencies: + '@babel/parser': 8.0.0-rc.4 + estree-walker: 3.0.3 + pathe: 2.0.3 + ast-v8-to-istanbul@1.0.0: dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -4077,6 +4481,8 @@ snapshots: birpc@2.9.0: {} + birpc@4.0.0: {} + boolbase@1.0.0: {} brace-expansion@1.1.14: @@ -4096,6 +4502,8 @@ snapshots: dependencies: fill-range: 7.1.1 + cac@7.0.0: {} + callsites@3.1.0: {} ccount@2.0.1: {} @@ -4208,6 +4616,8 @@ snapshots: deep-is@0.1.4: {} + defu@6.1.7: {} + dequal@2.0.3: {} devlop@1.1.0: @@ -4216,6 +4626,8 @@ snapshots: diff@8.0.4: {} + dts-resolver@3.0.0: {} + eastasianwidth@0.2.0: {} editorconfig@1.0.7: @@ -4233,6 +4645,8 @@ snapshots: emoji-regex@9.2.2: {} + empathic@2.0.1: {} + entities@6.0.1: {} entities@7.0.1: {} @@ -4483,6 +4897,10 @@ snapshots: get-east-asian-width@1.5.0: {} + get-tsconfig@5.0.0-beta.5: + dependencies: + resolve-pkg-maps: 1.0.0 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -4547,6 +4965,8 @@ snapshots: hookable@5.5.3: {} + hookable@6.1.1: {} + html-encoding-sniffer@6.0.0: dependencies: '@exodus/bytes': 1.15.0 @@ -4582,6 +5002,8 @@ snapshots: import-lazy@4.0.0: {} + import-without-cache@0.4.0: {} + imurmurhash@0.1.4: {} inflight@1.0.6: @@ -4690,6 +5112,8 @@ snapshots: - supports-color - utf-8-validate + jsesc@3.1.0: {} + json-buffer@3.0.1: {} json-parse-even-better-errors@4.0.0: {} @@ -5009,6 +5433,8 @@ snapshots: quansync@0.2.11: {} + quansync@1.0.0: {} + queue-microtask@1.2.3: {} read-package-json-fast@4.0.0: @@ -5049,6 +5475,8 @@ snapshots: resolve-from@4.0.0: {} + resolve-pkg-maps@1.0.0: {} + resolve@1.22.12: dependencies: es-errors: 1.3.0 @@ -5070,6 +5498,44 @@ snapshots: glob: 13.0.6 package-json-from-dist: 1.0.1 + rolldown-plugin-dts@0.25.1(rolldown@1.0.1)(typescript@5.9.3)(vue-tsc@3.2.8): + dependencies: + '@babel/generator': 8.0.0-rc.5 + '@babel/helper-validator-identifier': 8.0.0-rc.5 + '@babel/parser': 8.0.0-rc.4 + ast-kit: 3.0.0-beta.1 + birpc: 4.0.0 + dts-resolver: 3.0.0 + get-tsconfig: 5.0.0-beta.5 + obug: 2.1.1 + rolldown: 1.0.1 + optionalDependencies: + typescript: 5.9.3 + vue-tsc: 3.2.8(typescript@5.9.3) + transitivePeerDependencies: + - oxc-resolver + + rolldown@1.0.1: + dependencies: + '@oxc-project/types': 0.130.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.1 + '@rolldown/binding-darwin-arm64': 1.0.1 + '@rolldown/binding-darwin-x64': 1.0.1 + '@rolldown/binding-freebsd-x64': 1.0.1 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.1 + '@rolldown/binding-linux-arm64-gnu': 1.0.1 + '@rolldown/binding-linux-arm64-musl': 1.0.1 + '@rolldown/binding-linux-ppc64-gnu': 1.0.1 + '@rolldown/binding-linux-s390x-gnu': 1.0.1 + '@rolldown/binding-linux-x64-gnu': 1.0.1 + '@rolldown/binding-linux-x64-musl': 1.0.1 + '@rolldown/binding-openharmony-arm64': 1.0.1 + '@rolldown/binding-wasm32-wasi': 1.0.1 + '@rolldown/binding-win32-arm64-msvc': 1.0.1 + '@rolldown/binding-win32-x64-msvc': 1.0.1 + rollup@4.60.3: dependencies: '@types/estree': 1.0.8 @@ -5266,12 +5732,43 @@ snapshots: dependencies: punycode: 2.3.1 + tree-kill@1.2.2: {} + trim-lines@3.0.1: {} ts-api-utils@2.5.0(typescript@5.9.3): dependencies: typescript: 5.9.3 + tsdown@0.22.0(publint@0.3.19)(typescript@5.9.3)(vue-tsc@3.2.8): + dependencies: + ansis: 4.3.0 + cac: 7.0.0 + defu: 6.1.7 + empathic: 2.0.1 + hookable: 6.1.1 + import-without-cache: 0.4.0 + obug: 2.1.1 + picomatch: 4.0.4 + rolldown: 1.0.1 + rolldown-plugin-dts: 0.25.1(rolldown@1.0.1)(typescript@5.9.3)(vue-tsc@3.2.8) + semver: 7.7.4 + tinyexec: 1.1.2 + tinyglobby: 0.2.16 + tree-kill: 1.2.2 + unconfig-core: 7.5.0 + optionalDependencies: + publint: 0.3.19 + typescript: 5.9.3 + transitivePeerDependencies: + - '@ts-macro/tsc' + - '@typescript/native-preview' + - oxc-resolver + - vue-tsc + + tslib@2.8.1: + optional: true + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -5291,6 +5788,11 @@ snapshots: ufo@1.6.4: {} + unconfig-core@7.5.0: + dependencies: + '@quansync/fs': 1.0.0 + quansync: 1.0.0 + undici-types@7.16.0: {} unist-util-is@6.0.1: From 474ab46320efb824d5394237fe56dc9f6b236553 Mon Sep 17 00:00:00 2001 From: skirtle <65301168+skirtles-code@users.noreply.github.com> Date: Tue, 19 May 2026 18:14:55 +0100 Subject: [PATCH 2/5] More tests for `with-meta` --- .../src/__tests__/with-meta.spec.ts | 230 +++++++++++++++++- 1 file changed, 228 insertions(+), 2 deletions(-) diff --git a/packages/vue-vnode-utils/src/__tests__/with-meta.spec.ts b/packages/vue-vnode-utils/src/__tests__/with-meta.spec.ts index 86461c3..87c0eda 100644 --- a/packages/vue-vnode-utils/src/__tests__/with-meta.spec.ts +++ b/packages/vue-vnode-utils/src/__tests__/with-meta.spec.ts @@ -1,6 +1,20 @@ import { describe, expect, it } from 'vitest' -import { COMPONENTS_AND_ELEMENTS, eachChild, everyChild, findChild, getText, isText, reduceChildren, someChild } from '../with-meta' -import { h, isVNode } from 'vue' +import { h, isVNode, type VNode } from 'vue' +import { + addProps, + ALL_VNODES, + betweenChildren, + COMPONENTS_AND_ELEMENTS, + eachChild, + everyChild, + findChild, + getText, + isElement, + isText, + reduceChildren, + replaceChildren, + someChild +} from '../with-meta' function getAllKeys(obj: object) { const keys: string[] = [] @@ -271,3 +285,215 @@ describe('reduceChildren', () => { expect(count).toBe(2) }) }) + +describe('addProps', () => { + it('addProps - 5ad7', () => { + const startNodes = [h('div'), 'Some text', h('p')] + + let count = 0 + + const nodes = addProps(startNodes, (node, meta) => { + expect(isVNode(node)).toBe(true) + expect(isElement(node)).toBe(true) + + let metaProperties = getAllKeys(meta) + expect(metaProperties.includes('index')).toBe(true) + expect(metaProperties.includes('length')).toBe(true) + + expect(meta.index).toBe(count) + expect(meta.length).toBe(2) + + // `length` property should still be enumerable + metaProperties = getAllKeys(meta) + expect(metaProperties.includes('index')).toBe(true) + expect(metaProperties.includes('length')).toBe(true) + + count++ + + return { + class: 'red' + } + }) + + expect(Array.isArray(nodes)).toBe(true) + expect(nodes).toHaveLength(3) + expect(count).toBe(2) + + const node = nodes[0] as VNode + + expect(isElement(node)).toBe(true) + expect(node.type).toBe('div') + expect(node.props?.class).toBe('red') + + count = 0 + + const unchanged = addProps(startNodes, () => { + ++count + }, { component: true }) + + expect(count).toBe(0) + expect(unchanged).toBe(startNodes) + }) +}) + +describe('replaceChildren', () => { + it('replaceChildren - 9c3c', () => { + const startNodes = [h('div'), 'Some text', h('p')] + + let count = 0 + + let nodes = replaceChildren(startNodes, (node, meta) => { + expect(isVNode(node)).toBe(true) + + let metaProperties = getAllKeys(meta) + expect(metaProperties.includes('index')).toBe(true) + expect(metaProperties.includes('length')).toBe(true) + + expect(meta.index).toBe(count) + expect(meta.length).toBe(3) + + // `length` property should still be enumerable + metaProperties = getAllKeys(meta) + expect(metaProperties.includes('index')).toBe(true) + expect(metaProperties.includes('length')).toBe(true) + + count++ + + if (count === 2) { + return [] + } + }) + + expect(Array.isArray(nodes)).toBe(true) + expect(nodes).toHaveLength(2) + expect(count).toBe(3) + + let node = nodes[0] as VNode + expect(isElement(node)).toBe(true) + expect(node.type).toBe('div') + + node = nodes[1] as VNode + expect(isElement(node)).toBe(true) + expect(node.type).toBe('p') + + count = 0 + + nodes = replaceChildren(startNodes, (node, meta) => { + expect(isVNode(node)).toBe(true) + expect(isElement(node)).toBe(true) + + let metaProperties = getAllKeys(meta) + expect(metaProperties.includes('index')).toBe(true) + expect(metaProperties.includes('length')).toBe(true) + + expect(meta.index).toBe(count) + expect(meta.length).toBe(2) + + // `length` property should still be enumerable + metaProperties = getAllKeys(meta) + expect(metaProperties.includes('index')).toBe(true) + expect(metaProperties.includes('length')).toBe(true) + + count++ + + if (count === 2) { + return [] + } + }, COMPONENTS_AND_ELEMENTS) + + expect(Array.isArray(nodes)).toBe(true) + expect(nodes).toHaveLength(2) + expect(count).toBe(2) + + node = nodes[0] as VNode + expect(isElement(node)).toBe(true) + expect(node.type).toBe('div') + + node = nodes[1] as VNode + expect(isText(node)).toBe(true) + expect(getText(node)).toBe('Some text') + + count = 0 + + nodes = replaceChildren([true, false, null], () => { + ++count + }) + + expect(count).toBe(0) + expect(Array.isArray(nodes)).toBe(true) + expect(nodes).toHaveLength(3) + }) +}) + +describe('betweenChildren', () => { + it('betweenChildren - 719e', () => { + const startNodes = [h('div'), 'Some text', h('p')] + + let count = 0 + + let nodes = betweenChildren(startNodes, (prev, next, meta) => { + expect(isVNode(prev)).toBe(true) + expect(isVNode(next)).toBe(true) + + let metaProperties = getAllKeys(meta) + expect(metaProperties.includes('index')).toBe(true) + expect(metaProperties.includes('length')).toBe(true) + + expect(meta.index).toBe(count) + expect(meta.length).toBe(3) + + // `length` property should still be enumerable + metaProperties = getAllKeys(meta) + expect(metaProperties.includes('index')).toBe(true) + expect(metaProperties.includes('length')).toBe(true) + + count++ + + return String(count) + }) + + expect(Array.isArray(nodes)).toBe(true) + expect(nodes).toHaveLength(5) + expect(count).toBe(2) + + let node = nodes[0] as VNode + expect(isElement(node)).toBe(true) + expect(node.type).toBe('div') + + node = nodes[1] as VNode + expect(isText(node)).toBe(true) + expect(getText(node)).toBe('1') + + node = nodes[2] as VNode + expect(isText(node)).toBe(true) + expect(getText(node)).toBe('Some text') + + node = nodes[3] as VNode + expect(isText(node)).toBe(true) + expect(getText(node)).toBe('2') + + node = nodes[4] as VNode + expect(isElement(node)).toBe(true) + expect(node.type).toBe('p') + + count = 0 + + const comments = [true, false, null] + + nodes = betweenChildren(comments, () => { + ++count + }) + + expect(count).toBe(0) + expect(Array.isArray(nodes)).toBe(true) + expect(nodes).toHaveLength(3) + + nodes = betweenChildren(comments, () => { + ++count + }, ALL_VNODES) + + expect(count).toBe(2) + expect(Array.isArray(nodes)).toBe(true) + expect(nodes).toHaveLength(3) + }) +}) From c8317151215444dd40c8d7e32125760a60b9c42a Mon Sep 17 00:00:00 2001 From: skirtle <65301168+skirtles-code@users.noreply.github.com> Date: Sun, 24 May 2026 16:33:39 +0100 Subject: [PATCH 3/5] Use `countChildren` --- packages/vue-vnode-utils/src/with-meta/index.ts | 1 + packages/vue-vnode-utils/src/with-meta/iterators.ts | 9 ++------- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/vue-vnode-utils/src/with-meta/index.ts b/packages/vue-vnode-utils/src/with-meta/index.ts index 5dce39b..e8ede77 100644 --- a/packages/vue-vnode-utils/src/with-meta/index.ts +++ b/packages/vue-vnode-utils/src/with-meta/index.ts @@ -1,6 +1,7 @@ export { ALL_VNODES, COMPONENTS_AND_ELEMENTS, + countChildren, extractSingleChild, getText, getType, diff --git a/packages/vue-vnode-utils/src/with-meta/iterators.ts b/packages/vue-vnode-utils/src/with-meta/iterators.ts index 3e87d02..8b1e09e 100644 --- a/packages/vue-vnode-utils/src/with-meta/iterators.ts +++ b/packages/vue-vnode-utils/src/with-meta/iterators.ts @@ -6,6 +6,7 @@ import { ALL_VNODES, betweenChildren as betweenChildrenRaw, COMPONENTS_AND_ELEMENTS, + countChildren, eachChild as eachChildRaw, everyChild as everyChildRaw, findChild as findChildRaw, @@ -29,12 +30,6 @@ export type IterationMeta = { readonly length: number } -function count(children: VNodeArrayChildren, options: IterationOptions) { - let count = 0 - eachChildRaw(children, () => ++count, options) - return count -} - function setPropertyValue(obj: object, key: string, value: unknown): any { return Object.defineProperty(obj, key, { value, @@ -47,7 +42,7 @@ function createMetaFactory(children: VNodeArrayChildren, options: IterationOptio const baseMeta = { get length() { - const length = count(children, options) + const length = countChildren(children, options) setPropertyValue(baseMeta, 'length', length) return length } From 42b4e4c1ddf324eaa228d4686d3f5cb5e9182579 Mon Sep 17 00:00:00 2001 From: skirtle <65301168+skirtles-code@users.noreply.github.com> Date: Tue, 26 May 2026 22:05:51 +0100 Subject: [PATCH 4/5] Documentation for `with-meta` --- packages/docs/.vitepress/config.mts | 3 + packages/docs/src/api.md | 1 + packages/docs/src/guide/iterators.md | 16 ++++ packages/docs/src/guide/other-helpers.md | 2 + packages/docs/src/guide/with-meta.md | 97 ++++++++++++++++++++++++ 5 files changed, 119 insertions(+) create mode 100644 packages/docs/src/guide/with-meta.md diff --git a/packages/docs/.vitepress/config.mts b/packages/docs/.vitepress/config.mts index 19e8b98..afd65a2 100644 --- a/packages/docs/.vitepress/config.mts +++ b/packages/docs/.vitepress/config.mts @@ -136,6 +136,9 @@ export default ({ mode }: { mode: string }) => defineConfigWithTheme({ }, { text: 'Other helpers', link: '/guide/other-helpers.html' + }, { + text: 'With meta', + link: '/guide/with-meta.html' } ] }, { diff --git a/packages/docs/src/api.md b/packages/docs/src/api.md index 28a0ff9..ee45428 100644 --- a/packages/docs/src/api.md +++ b/packages/docs/src/api.md @@ -80,6 +80,7 @@ The [`options`](#iterationoptions) object can be used to decide which node types ### See also * [Guide - Other helpers](/guide/other-helpers.html) +* [Guide - With meta](/guide/with-meta.html) ## eachChild() diff --git a/packages/docs/src/guide/iterators.md b/packages/docs/src/guide/iterators.md index 745ac3f..e892af6 100644 --- a/packages/docs/src/guide/iterators.md +++ b/packages/docs/src/guide/iterators.md @@ -91,3 +91,19 @@ For the specific case of counting children, the dedicated [`countChildren()`](./ While these examples need to display the count, a more common scenario involves only needing to know whether the count is 0. The [`isEmpty()`](./other-helpers#checking-for-an-empty-slot) helper can be used in that case. It is worth noting that the count here is just a count of the VNodes. It is not necessarily an accurate count of the number of `
  • ` elements. If any of the children had been a component it would have added 1 to the count, even though a component wouldn't necessarily render exactly one `
  • ` element. + +The iteration index and total node count aren't passed to the callback by default. It's relatively easy to implement that yourself: + +```js +const children = slots.default?.() ?? [] +const count = countChildren(children) +let index = -1 + +eachChild(children, (vnode) => { + index++ + + // ... +}) +``` + +But if you'd like the index and length to be passed to the callback you can use the [`with-meta`](./with-meta) iterators instead. diff --git a/packages/docs/src/guide/other-helpers.md b/packages/docs/src/guide/other-helpers.md index 04854cf..6a45fef 100644 --- a/packages/docs/src/guide/other-helpers.md +++ b/packages/docs/src/guide/other-helpers.md @@ -25,6 +25,8 @@ function ChildComponent(_, { slots }) { See it on the SFC Playground: [Composition API](https://play.vuejs.org/#eNp9VGtv0zAU/StXAamtaJPykJDCxoBp4v0Qm8QHgiBLnNara0e20xVV/e8c22nWdBvaPjS+55577vU93kSv6zpeNSxKoyNTaF5bMsw29ctM8mWttKUNaZYXlq8YbanSakkD4Add/BM39ge38wtlc9EC4qR3Gl8Z4DNZKGksccuWho472uHPTBLKWLa2KQ2+s3IwJjNX1ylZ3aDquBd/qxmTHaLKhbkFeSMgsM+RyV+jGwku8loIiBiO6PglbVx6pTQNbySSqoLUUQiT/4pdKvIcqzsFMf6PkjA7TA0fgNUitwxfREeXjbVK0qtC8GJxnEVt7SzyYaJzR5jjwIGTgA6ZAjOcXGOIE+um2OI7elpNIBmMXi2XQW3H6wmA4VUL8dL3whjYJvTkxkZb9LEL3BK937ofOXje8ZL1BPvMRPCd0KQ/CEQO++lBonEUVmqyzGusjJJYSj961PcBNJfuLiOLXpkF11awBOs4WUlVskljufCgLJpbW5s0SRpZL2ZxoZbJffikhK7Dw5iZ5eRSq2vDdFyyFfRk0e7CofRwvyG188v8bqNsqFCNtKdzLkrN5JjOP77/9vv06+fPZ18uzruc+2R6B7G1pypZlTfCUtVIWAgX5UlPFQpJJu3w9xjVjFDW0LZd37DYRVsc1+jDaM0zncQwwskJ/fy1h3VqAeypHu4YDuR7cxE8bRstaT4clHwFC3pr0833H88TKFN6uPE/tn9G3r+usCt54vCNAHxXbEQpySZ4xPkYl4A7MPavYCYujJt+u7GPCIVCx5dKl0yn9Lhek1GCl/RgOp2+cKFlrmdcYg1rhKf1uj1cYztLO0/pyXR3WOdlyeWsg6FyBiVthbxYzDQ0lyk9qJ67P590R+WiKPYqBzqakpPjKWFVT9mrF8Lo1BpcScVnB67AVtdcMP21dkvQdwdeFHX9wZ+5t6odMHLmrFjccX5l1sE43zTDzq/g8C5mIZrZED47/4LXYi+4VGUjgP5P8DvDFLDESgbYG0wMsvdwXu177xN0f2HO1pZJs2uqe2w9PovgDLfr97V+I/dp/Kzz7PYf9mdPuQ==) | [Options API](https://play.vuejs.org/#eNp9Vftv0zAQ/ldOAamdaJPykJDCYMA08X6ITeIHgiCLncara0f2pSuq+r9zfiRry4Y2qfXdd9/d+e5zN8mrtk1XHU/y5NhWRrT4olBi2WqD8FFY/C6wudBYSqiNXsIozfas6ZUdFQqgUHztYxivy04ibJy10kSkuEKbBwPsczrTdhLiAViJ5fioBxqOnVH9CUAgXxLNj/4MsAHka8xh9I2z0QRso69zQNPxyHmAemM4VwOuLqW9A/hadvyAr0f9DF/82cW6zyXHRrObDl3cKyn7TgossNYGxpVWFn0boGvARtjU9zR03HeZOgZ47lP3nlhBSFwo+j/OhnHRgcJaWSKnE8DxZYeoFbyspKgWz4skllQk3g1w7hKUZHDgLKBDpKTxTK9pPlN0A4r4gR5WU+qFGH0bQoWpDLyegDCijhDfyo6bbnkTenR3DdvhYm8pevcq/LSI561gfK9gH5lJ0Rea7V8EeQ772YMkkyTs+nRZtrTLWpEOwtiig5obJlskL+1CGJQ8I8VMV0ozPu1QSA8qkgaxtXmWdapdzFNa/uwufMaorkNjyu1yemn0teUmZXxF9RRJP3Cq9FB4VGoU6gYa2EaBEikpcnBUulN42gjJDFcTOP/w7uuv0y+fPp19vjgfYu4qk4j+EXbdqQoFDcqTnvYKH/+aUDYrNVrYxpUOG1/F5DRG76bWPNNJShI5OYEfXlQR66ol4F7V457hoPyjoL/4UDTjERMr0m18IYbzb88TKHO4v/Fftr+PovRDyhOH7yTB+2RHkIPqgkZ+UioaAs3A4h/JbVpZd/txYx8AJQodX2rDuMnhYbsGq6VgcG82mz3zz0Rp5kLRGrbknrXraFzTdjJscng0641tyZhQ8wFGmQuqJGYoq8XcUM0sh3v1U/fng27JXFXVTuZABzNw5XhKkqqn3MsX3NQpWhpJLeYHqnBPupDcfGndEuyrg14Uff3e29zbFS+YYhpeLW6xX9l1EM5Xw2nnV6TwwYdUNMfgPjv/TK/FjnOpWScJ/R/nN063QEusVYC9phujsndwvtp3XifU/YU9WyNXtm+qf3zjz1NCynC7flfrN+U+Tp8Mmt3+Bfk4a4M=) +If you need the child count inside the callback of an iterator then you might prefer to use [`with-meta`](./with-meta) instead, which will pass the count to the iteration callback. + If you need more fine-grained control over the counting you can use [`eachChild()` or `reduceChildren()`](./iterators) instead. ## Extracting a single child diff --git a/packages/docs/src/guide/with-meta.md b/packages/docs/src/guide/with-meta.md new file mode 100644 index 0000000..b18d1db --- /dev/null +++ b/packages/docs/src/guide/with-meta.md @@ -0,0 +1,97 @@ +--- +# Updated automatically in production builds: see the VitePress config. +packageVersions: + vue: 3.5.34 + '@skirtle/vue-vnode-utils': 0.3.0 +--- +# With meta + +There are alternative versions of the iterator functions, with added support for accessing extra metadata for the current node. + +Currently, this metadata just includes the iteration index and length. + +To use this metadata you'll need to import the functions from `@skirtle/vue-vnode-utils/with-meta` rather than `@skirtle/vue-vnode-utils`. + +## Accessing meta from callbacks + +The meta is passed in an object as the final argument of the callback for each iterator. + +The `length` property is a number and should be equivalent to calling [`countChildren()`](/other-helpers.html#counting-children). It will use the same iteration options as the iterator, so if you're skipping nodes then those nodes won't be included in the `length`. For most iterators, the `length` is the number of times the callback will be called during the iteration. + +The `index` is zero-based, counting up to `length - 1`. + +The `length` is calculated lazily, so if it isn't accessed the nodes won't be counted. + +[`betweenChildren()`](./inserting-new-nodes) behaves a little differently. The `index` will still start at zero, but it will only count up to `length - 2`. The `length` will still reflect the number of nodes, like it would for the other iterators, but that won't match the number of calls to the callback. + +## Example + +```js +import { h } from 'vue' +import { addProps } from '@skirtle/vue-vnode-utils/with-meta' + +export default function AddStripes(_, { slots }) { + const children = addProps(slots.default(), (vnode, { index, length }) => { + return { + class: [ + index % 2 ? 'odd' : 'even', + { + first: index === 0, + last: index === length - 1 + } + ] + } + }) + + return h('div', children) +} +``` + +See it on the SFC Playground: [Composition API](https://play.vuejs.org/#eNqFVFtr2zAU/isHw4gDid3u8uI1u9KHDbaVdW/zGG504qiVJSPJaUrIf98n2bm0tJ0JROf26Vy+o03ysW2zVcdJkZy5uZWtJ8e+a9+VWjatsZ42ZHkxoblp2s6zoC0trGlohKDR3umjEJce0ewGa5YfVNm1g2ep50Y7D6BOe5oF1PTNeKeVnhsH7e6aNB3T7B19q/wya6p1ejIZzlKnESFbVarjCZ2ejMdAOcv77JE3BIC1qvIMiehMakDSatoYwWpWJjG+TMjftQxRd80VW8jAhngSTtUap1Mce4RDLVGGRsgVEBfGBgCS+IUCBv/wbTakabsd3HP491BHfYHiLN+nmkySvpnTpmrRMqMxk02IKQcD4AuKmqD74G6k9YpzDGK60qht2nmpXH4r/XLasK+Ce5ksvW9dkeedbm/qDP3Nn4wU0vlD+ENzxq6ZXllz69hmglfIsUxCNihyi+zvDRy57/mzfJwzG6qEuLCmdXv7/2uKROJ1BBC8qDrladHpuZdGH7Ew/TsBvlPGA3zcN22g31IqYVmDa7vr0+iHkiJcOp5QGu8OEFILXk9Isa496oikHEZgsSdW7yTgq8q5gn7vZOqD6QW9pPc0MkKMqKARr1iPJgenfXz4FtI6XwyBs9mMTo48iXDDPeuQ1pROD14D5Yj+9Icob7Ei4X/IeZmOQMgRtnroBszwwxC9Q5sWsn5AwLCWUrH90YZG3ydipZS5/Rp13mIld/r5kuc3vX5RKXcwXLt1T80Ly+DSistkb/OVrRnLGcznl995jfPeiAXuFLyfMf5kZxQ4Y3Tv9qnTAnkf+cV0v0QKSl3/cudrz9rtqgoVxIZF/zIBET8/U/sh3VfZ66NdcP5OYQ/mLixC1laiD8FB4NICr1a7fht7XuosDn3HUWVsgTEJWAnvkK2lnnrTPggJRLgXUVtmvTeDa731qprf1BbvnShIyXrp7ziUH8EfTybQ84nYKzy4j0cm238hNxSj) | [Options API](https://play.vuejs.org/#eNqFVNtu2zAM/RXCwBAH8CXd5SVLunVDHzZgW7HubR4Gx2JsNbJkSHLqIsi/j/ItTtF2gYGIInl4O9TBu6qqaF+jt/RWJtO8speJ5GWltIUrxm4tXaGBrVYlzKL4dBXdmVkiE4lNa8twm9bCwiGRAJkiAInSmmV3ARMsJx8D5wrAUpv688FGo621HCQHU0u7hHedfJw6ugi1RTbic4ulOUGNYN9SW0Rl2viLoD9z6duCm6hFD+BiMZ9PAySSvlU89oIEgq5EapEkgBWXFBn2YakYinXitTiJB/ahQhJlXW5Qk0yBSFy4U9rQ6YKOHcKpF61MN4zvCXGrtAMATp8rp7d3v8MBJBzbDMk8JvsOajIQuljFk1S9wOvmGJZpRdNSkmbctifpFRRg7F/ifTQ7rq3AmNgQ7iVVF9aWCxPfc1uEJdrUmSdeYW1llnFcy2qXUxfL+FlPxo09uT9WR2jKcKPVvUEdMdxTjok3zICyP+Ma5d6z8gAFHHtCEiKRcFSkjN1oVZlR//+anuLwtpaZ5UpOSOv/DQjfCGUJvGdZpqSxkBVcMI0S1mN4v7Wjklo4fx6A38Z2EFwybAIQKHNLdcxhffks/UVqaIF+DzKR3DnDK3gNH2CmGJvBEma4RzmjtRiMRn/323JtaIU6x/V6DYuJJQBFONP2aYVwcbLqSQfwZ7omtDPuv8+58GdEyVkwdoPUZEdDtIbatOX5IwK6/eUC9Y/KNfqciKkQ6v5re2d1jX3C5FNgtuvut6kwJ8WdaTpq3mgkLu0x8UadTXWOtJ5OfX37HRs6j0pa4VqQ9QvKn2iUIM4o2Zl9qiWjvCd2bbpfWgpymf8y141FaYaqXAXDw+WsiYifX6j9lO6b6O1kF4x9ELQHmXGLEFUp61zowCjokl6xqnnf9jyRUTv0gaNC6SWNiZEW6CXSOZehVdUjF0eEM49cI8pRTVzrtJs02+WaXjx6eQXPC/uArvwW/OlkHD2f8d2IGp/29I7/AH7AK5M=) + +## Accessing the `with-meta` iterators + +Metadata isn't included in the default iterators, to avoid the added overhead. You'll need to use alternative versions of the iterators to get the metadata. + +### With a bundler + +If you're using a bundler then you just need to change `@skirtle/vue-vnode-utils` to `@skirtle/vue-vnode-utils/with-meta` in your imports to get access to the metadata: + +```js +import { addProps } from '@skirtle/vue-vnode-utils/with-meta' +``` + +All exports from `@skirtle/vue-vnode-utils` are re-exported by `@skirtle/vue-vnode-utils/with-meta`, even those that don't include meta. You can also mix `@skirtle/vue-vnode-utils` and `@skirtle/vue-vnode-utils/with-meta` and functions will be tree-shaken across the two packages. + +### CDN - global build + +To use meta with a global build, add `with-meta` to the path: + +```html-vue + +``` + +The global variable is called `VueVNodeUtils`, the same as with the normal build. You wouldn't load both in the same project. + +### CDN - ES module build + +Similar to the global build, you need to add `with-meta` to the path: + +```html-vue + + +``` + +The `with-meta` module includes a copy of everything it needs, you don't need to include `@skirtle/vue-vnode-utils` in the import map. + +The package name is usually arbitrary, though using `@skirtle/vue-vnode-utils/with-meta` is recommended unless you have a good reason to use an alternative. From ee46d1415564e038c13df5e533554fc50d43a05c Mon Sep 17 00:00:00 2001 From: skirtle <65301168+skirtles-code@users.noreply.github.com> Date: Tue, 26 May 2026 22:12:17 +0100 Subject: [PATCH 5/5] Fix broken docs link --- packages/docs/src/guide/with-meta.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/docs/src/guide/with-meta.md b/packages/docs/src/guide/with-meta.md index b18d1db..aaff2bc 100644 --- a/packages/docs/src/guide/with-meta.md +++ b/packages/docs/src/guide/with-meta.md @@ -16,7 +16,7 @@ To use this metadata you'll need to import the functions from `@skirtle/vue-vnod The meta is passed in an object as the final argument of the callback for each iterator. -The `length` property is a number and should be equivalent to calling [`countChildren()`](/other-helpers.html#counting-children). It will use the same iteration options as the iterator, so if you're skipping nodes then those nodes won't be included in the `length`. For most iterators, the `length` is the number of times the callback will be called during the iteration. +The `length` property is a number and should be equivalent to calling [`countChildren()`](./other-helpers#counting-children). It will use the same iteration options as the iterator, so if you're skipping nodes then those nodes won't be included in the `length`. For most iterators, the `length` is the number of times the callback will be called during the iteration. The `index` is zero-based, counting up to `length - 1`.