diff --git a/.gitignore b/.gitignore index 6fdac783e..b4def37c0 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ CLAUDE.md # Local Netlify folder .netlify + +# Generated by "npm run link:package" to make the symlinked vueless package self-describing +src/package.json diff --git a/.storybook/theme/theme.css b/.storybook/theme/theme.css index 90f1bca50..747c176b4 100644 --- a/.storybook/theme/theme.css +++ b/.storybook/theme/theme.css @@ -35,4 +35,3 @@ --rounded-md: 0.375rem; --rounded-lg: 0.5rem; } - diff --git a/.storybook/themes/theme.css b/.storybook/themes/theme.css deleted file mode 100644 index 6e9212202..000000000 --- a/.storybook/themes/theme.css +++ /dev/null @@ -1,33 +0,0 @@ -/* -------------------- Color Palettes -------------------- */ - -:root { - /* Neutral (Gray) Colors */ - --neutral-50: #f9fafb; - --neutral-100: #f3f4f6; - --neutral-200: #e5e7eb; - --neutral-300: #d1d5db; - --neutral-400: #9ca3af; - --neutral-500: #6b7280; - --neutral-600: #4b5563; - --neutral-700: #374151; - --neutral-800: #1f2937; - --neutral-900: #111827; - --neutral-950: #030712; - - /* Primary (Green) Colors */ - --primary-50: #f0fdf4; - --primary-100: #dcfce7; - --primary-200: #bbf7d0; - --primary-300: #86efac; - --primary-400: #4ade80; - --primary-500: #22c55e; - --primary-600: #16a34a; - --primary-700: #15803d; - --primary-800: #166534; - --primary-900: #14532d; - --primary-950: #052e16; - - /* White */ - --white: #ffffff; -} - diff --git a/.storybook/themes/themeConfig.ts b/.storybook/themes/themeConfig.ts deleted file mode 100644 index 910f2b505..000000000 --- a/.storybook/themes/themeConfig.ts +++ /dev/null @@ -1,34 +0,0 @@ -export const themeConfig = { - colors: { - gray: { - 50: "#f9fafb", - 100: "#f3f4f6", - 200: "#e5e7eb", - 300: "#d1d5db", - 400: "#9ca3af", - 500: "#6b7280", - 600: "#4b5563", - 700: "#374151", - 800: "#1f2937", - 900: "#111827", - 950: "#030712", - }, - white: "#ffffff", - }, - borderRadius: { - sm: 4, - md: 8, - lg: 12, - }, - fonts: { - fontBase: '"Roboto", sans-serif', - fontCode: "monospace", - }, - brand: { - brandTitle: "Vueless UI", - brandUrl: "https://vueless.com", - brandTarget: "_blank", - brandImageLight: "./images/logo-light.svg", - brandImageDark: "./images/logo-dark.svg", - }, -}; diff --git a/.storybook/vite.config.ts b/.storybook/vite.config.ts index c669587f5..9794e98ea 100644 --- a/.storybook/vite.config.ts +++ b/.storybook/vite.config.ts @@ -7,8 +7,10 @@ const __dirname = dirname(__filename); // Plugins import Vue from "@vitejs/plugin-vue"; -import { Vueless, TailwindCSS } from "vueless/plugin-vite.js"; -import { INTERNAL_ENV } from "vueless/constants.js"; +/* Imported via a relative path (not the `vueless` alias) so Storybook's config + loader can resolve them while the symlinked `vueless` package is bundled. */ +import { Vueless, TailwindCSS } from "../src/plugin-vite.js"; +import { INTERNAL_ENV } from "../src/constants.js"; export default defineConfig({ plugins: [Vue(), TailwindCSS(), Vueless({ env: INTERNAL_ENV })], diff --git a/README.md b/README.md index 1109e1285..b2462b7de 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ A UI library with Open Architecture for Vue.js 3 and Nuxt.js 3 / 4, powered by [ ### Key features -- 🧩 65+ crafted UI components (including range date picker, multi-select, and nested table) +- 🧩 74 crafted UI components (including range date picker, multi-select, and nested table) - ✨ Open Architecture lets you customize, copy, extend, and create your own components - 📕 Built-in Storybook support - đŸĒŠ Theme Builder for runtime theme customization diff --git a/eslint.config.ts b/eslint.config.ts index 5d8ca5824..95ec4c496 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -80,6 +80,13 @@ export default defineConfigWithVueTs( files: ["src/**/tests/*"], ...pluginVitest.configs.recommended, }, + { + name: "vueless-type-tests", + files: ["src/**/tests/*.test-d.ts"], + rules: { + "vitest/expect-expect": ["error", { assertFunctionNames: ["expect", "assert", "expectTypeOf"] }], + }, + }, { name: "eslint", files: ["eslint.config.*"], diff --git a/package-lock.json b/package-lock.json index d967dd27a..b8628772a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vueless", - "version": "1.4.11", + "version": "1.4.12-beta.20", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vueless", - "version": "1.4.11", + "version": "1.4.12-beta.20", "license": "MIT", "dependencies": { "@tailwindcss/postcss": "^4.2.4", @@ -14,7 +14,7 @@ "@types/lodash-es": "^4.17.12", "cva": "^1.0.0-beta.4", "lodash-es": "^4.18.1", - "rolldown": "1.0.0-rc.17", + "rolldown": "^1.1.5", "svgo": "^4.0.1", "tailwind-merge": "^3.5.0", "tailwindcss": "^4.2.4", @@ -396,20 +396,30 @@ } }, "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/core/node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", "license": "MIT", "optional": true, "dependencies": { - "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", "license": "MIT", "optional": true, "dependencies": { @@ -427,9 +437,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", - "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", "cpu": [ "ppc64" ], @@ -443,9 +453,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", - "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", "cpu": [ "arm" ], @@ -459,9 +469,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", - "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", "cpu": [ "arm64" ], @@ -475,9 +485,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", - "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", "cpu": [ "x64" ], @@ -491,9 +501,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", - "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", "cpu": [ "arm64" ], @@ -507,9 +517,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", - "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", "cpu": [ "x64" ], @@ -523,9 +533,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", - "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", "cpu": [ "arm64" ], @@ -539,9 +549,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", - "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", "cpu": [ "x64" ], @@ -555,9 +565,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", - "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", "cpu": [ "arm" ], @@ -571,9 +581,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", - "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", "cpu": [ "arm64" ], @@ -587,9 +597,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", - "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", "cpu": [ "ia32" ], @@ -603,9 +613,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", - "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", "cpu": [ "loong64" ], @@ -619,9 +629,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", - "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", "cpu": [ "mips64el" ], @@ -635,9 +645,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", - "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", "cpu": [ "ppc64" ], @@ -651,9 +661,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", - "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", "cpu": [ "riscv64" ], @@ -667,9 +677,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", - "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", "cpu": [ "s390x" ], @@ -683,9 +693,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", - "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", "cpu": [ "x64" ], @@ -699,9 +709,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", - "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", "cpu": [ "arm64" ], @@ -715,9 +725,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", - "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", "cpu": [ "x64" ], @@ -731,9 +741,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", - "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", "cpu": [ "arm64" ], @@ -747,9 +757,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", - "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", "cpu": [ "x64" ], @@ -763,9 +773,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", - "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", "cpu": [ "arm64" ], @@ -779,9 +789,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", - "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", "cpu": [ "x64" ], @@ -795,9 +805,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", - "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", "cpu": [ "arm64" ], @@ -811,9 +821,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", - "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", "cpu": [ "ia32" ], @@ -827,9 +837,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", - "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", "cpu": [ "x64" ], @@ -1485,9 +1495,6 @@ "dependencies": { "@tybys/wasm-util": "^0.10.3" }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=23.5.0" - }, "funding": { "type": "github", "url": "https://github.com/sponsors/Brooooooklyn" @@ -2078,6 +2085,7 @@ "version": "0.127.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" @@ -2482,9 +2490,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", "cpu": [ "arm64" ], @@ -2498,9 +2506,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", "cpu": [ "arm64" ], @@ -2514,9 +2522,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz", - "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", + "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", "cpu": [ "x64" ], @@ -2530,9 +2538,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz", - "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", "cpu": [ "x64" ], @@ -2546,9 +2554,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz", - "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", + "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", "cpu": [ "arm" ], @@ -2562,9 +2570,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", + "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", "cpu": [ "arm64" ], @@ -2578,9 +2586,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz", - "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", "cpu": [ "arm64" ], @@ -2594,9 +2602,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", "cpu": [ "ppc64" ], @@ -2610,9 +2618,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", "cpu": [ "s390x" ], @@ -2626,9 +2634,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", "cpu": [ "x64" ], @@ -2642,9 +2650,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz", - "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", "cpu": [ "x64" ], @@ -2658,9 +2666,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", "cpu": [ "arm64" ], @@ -2674,27 +2682,27 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz", - "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", "cpu": [ "wasm32" ], "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" }, "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz", - "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", "cpu": [ "arm64" ], @@ -2708,9 +2716,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz", - "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", + "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", "cpu": [ "x64" ], @@ -4590,16 +4598,16 @@ "license": "ISC" }, "node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/braces": { @@ -5595,9 +5603,9 @@ "license": "MIT" }, "node_modules/editorconfig/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", "dev": true, "license": "MIT", "dependencies": { @@ -5707,9 +5715,9 @@ } }, "node_modules/esbuild": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", - "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "devOptional": true, "hasInstallScript": true, "license": "MIT", @@ -5720,32 +5728,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.0", - "@esbuild/android-arm": "0.28.0", - "@esbuild/android-arm64": "0.28.0", - "@esbuild/android-x64": "0.28.0", - "@esbuild/darwin-arm64": "0.28.0", - "@esbuild/darwin-x64": "0.28.0", - "@esbuild/freebsd-arm64": "0.28.0", - "@esbuild/freebsd-x64": "0.28.0", - "@esbuild/linux-arm": "0.28.0", - "@esbuild/linux-arm64": "0.28.0", - "@esbuild/linux-ia32": "0.28.0", - "@esbuild/linux-loong64": "0.28.0", - "@esbuild/linux-mips64el": "0.28.0", - "@esbuild/linux-ppc64": "0.28.0", - "@esbuild/linux-riscv64": "0.28.0", - "@esbuild/linux-s390x": "0.28.0", - "@esbuild/linux-x64": "0.28.0", - "@esbuild/netbsd-arm64": "0.28.0", - "@esbuild/netbsd-x64": "0.28.0", - "@esbuild/openbsd-arm64": "0.28.0", - "@esbuild/openbsd-x64": "0.28.0", - "@esbuild/openharmony-arm64": "0.28.0", - "@esbuild/sunos-x64": "0.28.0", - "@esbuild/win32-arm64": "0.28.0", - "@esbuild/win32-ia32": "0.28.0", - "@esbuild/win32-x64": "0.28.0" + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" } }, "node_modules/escape-string-regexp": { @@ -6511,9 +6519,9 @@ "license": "MIT" }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", "dev": true, "license": "MIT", "dependencies": { @@ -7138,14 +7146,11 @@ } }, "node_modules/js-cookie": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", - "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.8.tgz", + "integrity": "sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } + "license": "MIT" }, "node_modules/js-stringify": { "version": "1.0.2", @@ -7162,10 +7167,20 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -7974,9 +7989,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "funding": [ { "type": "github", @@ -8578,9 +8593,9 @@ } }, "node_modules/postcss": { - "version": "8.5.12", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", - "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", + "version": "8.5.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", + "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", "funding": [ { "type": "opencollective", @@ -8597,7 +8612,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -9094,9 +9109,9 @@ } }, "node_modules/release-it": { - "version": "20.0.1", - "resolved": "https://registry.npmjs.org/release-it/-/release-it-20.0.1.tgz", - "integrity": "sha512-3ob1P1aV+3+ZOoR7qgobfYyMlQbpitzOK09iKTtQ145vFi4rWxlRTgHwtVl8kokCvqiF/cJPxRlfcmZmF5aDJA==", + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/release-it/-/release-it-20.2.1.tgz", + "integrity": "sha512-xd0mqTGduwQlEzVBKOJcoVZxDrRLzjmFv3W8tWwkPIPDYtwYBWYjULiSk6VhfuGKocfz7GmptAqViKMQV4Zzbw==", "dev": true, "funding": [ { @@ -9129,7 +9144,7 @@ "proxy-agent": "7.0.0", "semver": "7.7.4", "tinyglobby": "0.2.15", - "undici": "7.24.5", + "undici": "7.28.0", "url-join": "5.0.0", "wildcard-match": "5.1.4", "yargs-parser": "22.0.0" @@ -9189,16 +9204,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/release-it/node_modules/undici": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.5.tgz", - "integrity": "sha512-3IWdCpjgxp15CbJnsi/Y9TCDE7HWVN19j1hmzVhoAkY/+CJx449tVxT5wZc1Gwg8J+P0LWvzlBzxYRnHJ+1i7Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.18.1" - } - }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -9270,13 +9275,13 @@ } }, "node_modules/rolldown": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz", - "integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.127.0", - "@rolldown/pluginutils": "1.0.0-rc.17" + "@oxc-project/types": "=0.139.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { "rolldown": "bin/cli.mjs" @@ -9285,27 +9290,36 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.17", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", - "@rolldown/binding-darwin-x64": "1.0.0-rc.17", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" + "@rolldown/binding-android-arm64": "1.1.5", + "@rolldown/binding-darwin-arm64": "1.1.5", + "@rolldown/binding-darwin-x64": "1.1.5", + "@rolldown/binding-freebsd-x64": "1.1.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", + "@rolldown/binding-linux-arm64-gnu": "1.1.5", + "@rolldown/binding-linux-arm64-musl": "1.1.5", + "@rolldown/binding-linux-ppc64-gnu": "1.1.5", + "@rolldown/binding-linux-s390x-gnu": "1.1.5", + "@rolldown/binding-linux-x64-gnu": "1.1.5", + "@rolldown/binding-linux-x64-musl": "1.1.5", + "@rolldown/binding-openharmony-arm64": "1.1.5", + "@rolldown/binding-wasm32-wasi": "1.1.5", + "@rolldown/binding-win32-arm64-msvc": "1.1.5", + "@rolldown/binding-win32-x64-msvc": "1.1.5" + } + }, + "node_modules/rolldown/node_modules/@oxc-project/types": { + "version": "0.139.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, "node_modules/rolldown/node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz", - "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "license": "MIT" }, "node_modules/run-applescript": { @@ -9824,9 +9838,9 @@ } }, "node_modules/svgo": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz", - "integrity": "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.2.tgz", + "integrity": "sha512-ekx94z1rRc5LDi6oSUaeRnYhd0UOJxdtQCL2rF8xpWxD3TPAsISWOrxezqGovqS38GRZOdpDfvQe3ts6F7nsng==", "license": "MIT", "dependencies": { "commander": "^11.1.0", @@ -9965,9 +9979,9 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -10233,9 +10247,9 @@ } }, "node_modules/undici": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", - "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", "dev": true, "license": "MIT", "engines": { @@ -10433,16 +10447,16 @@ } }, "node_modules/vite": { - "version": "8.0.10", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz", - "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==", + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.4.tgz", + "integrity": "sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==", "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.10", - "rolldown": "1.0.0-rc.17", - "tinyglobby": "^0.2.16" + "picomatch": "^4.0.5", + "postcss": "^8.5.16", + "rolldown": "~1.1.4", + "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" @@ -10458,7 +10472,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.0", + "@vitejs/devtools": "^0.3.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -10510,9 +10524,9 @@ } }, "node_modules/vite/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "license": "MIT", "engines": { "node": ">=12" diff --git a/package.json b/package.json index e112d77f6..e8a13da35 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vueless", - "version": "1.4.11", + "version": "1.4.12-beta.20", "description": "Vue Styleless UI Component Library, powered by Tailwind CSS.", "author": "Johnny Grid (https://vueless.com)", "homepage": "https://vueless.com", @@ -37,7 +37,7 @@ "@types/lodash-es": "^4.17.12", "cva": "^1.0.0-beta.4", "lodash-es": "^4.18.1", - "rolldown": "1.0.0-rc.17", + "rolldown": "^1.1.5", "svgo": "^4.0.1", "tailwind-merge": "^3.5.0", "tailwindcss": "^4.2.4", diff --git a/src/composables/useUI.ts b/src/composables/useUI.ts index 18a1d5cd6..03d9beff1 100644 --- a/src/composables/useUI.ts +++ b/src/composables/useUI.ts @@ -228,6 +228,16 @@ export function useUI(defaultConfig: T, mutatedProps?: MutatedProps, topLevel const keysAttrs: KeysAttrs = {}; const attrsRefs: Record> = {}; + /** + * Structural signature per key. The watcher below fires on any config/prop/class + * change and would otherwise re-mint every key's attrs object — even keys whose + * classes are identical — invalidating downstream reactive readers (e.g. every + * body row of a table on an unrelated sticky toggle). We skip the `.value` write + * when the new object is structurally equal to the last one, so the ref keeps its + * identity and dependents are not re-rendered. + */ + const attrsSignatures: Record = {}; + for (const key in config.value) { if (isSystemKey(key)) continue; @@ -266,12 +276,20 @@ export function useUI(defaultConfig: T, mutatedProps?: MutatedProps, topLevel /* Delete value key to prevent v-model overwrite. */ delete commonAttrs.value; - attrsRefs[key].value = { + const nextValue: KeyAttrs = { ...commonAttrs, class: cx([...data.extendsClasses, classes, commonAttrs.class]), config: data.mergedNestedConfig, ...data.mergedDefaults, }; + + /* Keep the previous ref identity when nothing changed — see attrsSignatures. */ + const signature = JSON.stringify(nextValue); + + if (attrsSignatures[key] === signature) continue; + + attrsSignatures[key] = signature; + attrsRefs[key].value = nextValue; } }, { immediate: true }, diff --git a/src/index.d.ts b/src/index.d.ts index ef9896889..4f9fa1fd9 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -108,6 +108,134 @@ export type { LocaleInstance, LocaleMessages, } from "./types"; +/* component props */ +export type { Props as UAccordionProps } from "./ui.container-accordion/types"; +export type { Props as UAccordionItemProps } from "./ui.container-accordion-item/types"; +export type { Props as UAlertProps } from "./ui.text-alert/types"; +export type { Props as UAvatarProps } from "./ui.image-avatar/types"; +export type { Props as UAvatarGroupProps } from "./ui.image-avatar-group/types"; +export type { Props as UBadgeProps } from "./ui.text-badge/types"; +export type { Props as UBoilerplateProps } from "./ui.boilerplate/types"; +export type { Props as UBreadcrumbsProps } from "./ui.navigation-breadcrumbs/types"; +export type { Props as UButtonProps } from "./ui.button/types"; +export type { Props as UCalendarProps } from "./ui.form-calendar/types"; +export type { Props as UCardProps } from "./ui.container-card/types"; +export type { Props as UCheckboxProps } from "./ui.form-checkbox/types"; +export type { Props as UCheckboxGroupProps } from "./ui.form-checkbox-group/types"; +export type { Props as UCheckboxMultiStateProps } from "./ui.form-checkbox-multi-state/types"; +export type { Props as UChipProps } from "./ui.other-chip/types"; +export type { Props as UColProps } from "./ui.container-col/types"; +export type { Props as UCollapsibleProps } from "./ui.container-collapsible/types"; +export type { Props as UColorToggleProps } from "./ui.form-color-toggle/types"; +export type { Props as UDataListProps } from "./ui.data-list/types"; +export type { Props as UDatePickerProps } from "./ui.form-date-picker/types"; +export type { Props as UDatePickerRangeProps } from "./ui.form-date-picker-range/types"; +export type { Props as UDividerProps } from "./ui.container-divider/types"; +export type { Props as UDotProps } from "./ui.other-dot/types"; +export type { Props as UDrawerProps } from "./ui.container-drawer/types"; +export type { Props as UDropdownProps } from "./ui.dropdown/types"; +export type { Props as UDropdownBadgeProps } from "./ui.dropdown-badge/types"; +export type { Props as UDropdownButtonProps } from "./ui.dropdown-button/types"; +export type { Props as UDropdownLinkProps } from "./ui.dropdown-link/types"; +export type { Props as UEmptyProps } from "./ui.container-empty/types"; +export type { Props as UFileProps } from "./ui.text-file/types"; +export type { Props as UFilesProps } from "./ui.text-files/types"; +export type { Props as UGridProps } from "./ui.container-grid/types"; +export type { Props as UGroupProps } from "./ui.container-group/types"; +export type { Props as UGroupsProps } from "./ui.container-groups/types"; +export type { Props as UHeaderProps } from "./ui.text-header/types"; +export type { Props as UIconProps } from "./ui.image-icon/types"; +export type { Props as UInputProps } from "./ui.form-input/types"; +export type { Props as UInputCounterProps } from "./ui.form-input-counter/types"; +export type { Props as UInputFileProps } from "./ui.form-input-file/types"; +export type { Props as UInputNumberProps } from "./ui.form-input-number/types"; +export type { Props as UInputPasswordProps } from "./ui.form-input-password/types"; +export type { Props as UInputRatingProps } from "./ui.form-input-rating/types"; +export type { Props as UInputSearchProps } from "./ui.form-input-search/types"; +export type { Props as UKeyProps } from "./ui.text-key/types"; +export type { Props as ULabelProps } from "./ui.form-label/types"; +export type { Props as ULinkProps } from "./ui.button-link/types"; +export type { Props as UListboxProps } from "./ui.form-listbox/types"; +export type { Props as ULoaderProps } from "./ui.loader/types"; +export type { Props as ULoaderOverlayProps } from "./ui.loader-overlay/types"; +export type { Props as ULoaderProgressProps } from "./ui.loader-progress/types"; +export type { Props as UModalProps } from "./ui.container-modal/types"; +export type { Props as UModalConfirmProps } from "./ui.container-modal-confirm/types"; +export type { Props as UNotifyProps } from "./ui.text-notify/types"; +export type { Props as UNumberProps } from "./ui.text-number/types"; +export type { Props as UPageProps } from "./ui.container-page/types"; +export type { Props as UPaginationProps } from "./ui.navigation-pagination/types"; +export type { Props as UPlaceholderProps } from "./ui.container-placeholder/types"; +export type { Props as UProgressProps } from "./ui.navigation-progress/types"; +export type { Props as URadioProps } from "./ui.form-radio/types"; +export type { Props as URadioGroupProps } from "./ui.form-radio-group/types"; +export type { Props as URowProps } from "./ui.container-row/types"; +export type { Props as USelectProps } from "./ui.form-select/types"; +export type { Props as USkeletonProps } from "./ui.skeleton/types"; +export type { Props as USkeletonChoiceProps } from "./ui.skeleton-choice/types"; +export type { Props as USkeletonInputProps } from "./ui.skeleton-input/types"; +export type { Props as USkeletonTextProps } from "./ui.skeleton-text/types"; +export type { Props as USplitterProps } from "./ui.container-splitter/types"; +export type { Props as USwitchProps } from "./ui.form-switch/types"; +export type { Props as UTabProps } from "./ui.navigation-tab/types"; +export type { Props as UTableProps } from "./ui.data-table/types"; +export type { Props as UTabsProps } from "./ui.navigation-tabs/types"; +export type { Props as UTextProps } from "./ui.text-block/types"; +export type { Props as UTextareaProps } from "./ui.form-textarea/types"; +export type { Props as UThemeColorToggleProps } from "./ui.other-theme-color-toggle/types"; +export type { Props as UToggleProps } from "./ui.button-toggle/types"; +/* component data types */ +export type { + Row, + BaseRow, + TableRow, + FlatRow, + RowId, + RowData, + Cell, + CellObject, + Column, + ColumnObject, + TableColumn, + DateDivider, + BaseDateDivider, + SlotRow, + SelectedSlotRow, + UTableSlots, + UTableStaticSlots, + UTableDynamicSlots, +} from "./ui.data-table/types"; +export type { + Option, + BaseOption, + ListboxOption, + SelectedValue, + SlotOption, + UListboxSlots, +} from "./ui.form-listbox/types"; +export type { + SelectOption, + SelectedSlotOption, + SelectedOptionsBinding, + USelectSlots, +} from "./ui.form-select/types"; +export type { UDropdownSlots } from "./ui.dropdown/types"; +export type { UDropdownBadgeSlots } from "./ui.dropdown-badge/types"; +export type { UDropdownButtonSlots } from "./ui.dropdown-button/types"; +export type { UDropdownLinkSlots } from "./ui.dropdown-link/types"; +export type { UTabsOption, BaseTabOption, UTabsSlots } from "./ui.navigation-tabs/types"; +export type { UBreadcrumb, BaseBreadcrumb } from "./ui.navigation-breadcrumbs/types"; +export type { UToggleOption, BaseToggleOption, UToggleSlots } from "./ui.button-toggle/types"; +export type { UAccordionOption, BaseAccordionOption } from "./ui.container-accordion/types"; +export type { + DataListItem, + BaseDataListItem, + SlotItem, + UDataListSlots, +} from "./ui.data-list/types"; +export type { UCheckboxOption, BaseCheckboxOption } from "./ui.form-checkbox/types"; +export type { ChipItem, BaseChipItem } from "./ui.image-avatar/types"; +export type { AvatarItem, BaseAvatarItem } from "./ui.image-avatar-group/types"; /* Export enums directly (not as types) */ export { ColorMode } from "./types"; diff --git a/src/index.ts b/src/index.ts index 258eb139c..645a5344a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -114,6 +114,134 @@ export type { LocaleInstance, LocaleMessages, } from "./types"; +/* component props */ +export type { Props as UAccordionProps } from "./ui.container-accordion/types"; +export type { Props as UAccordionItemProps } from "./ui.container-accordion-item/types"; +export type { Props as UAlertProps } from "./ui.text-alert/types"; +export type { Props as UAvatarProps } from "./ui.image-avatar/types"; +export type { Props as UAvatarGroupProps } from "./ui.image-avatar-group/types"; +export type { Props as UBadgeProps } from "./ui.text-badge/types"; +export type { Props as UBoilerplateProps } from "./ui.boilerplate/types"; +export type { Props as UBreadcrumbsProps } from "./ui.navigation-breadcrumbs/types"; +export type { Props as UButtonProps } from "./ui.button/types"; +export type { Props as UCalendarProps } from "./ui.form-calendar/types"; +export type { Props as UCardProps } from "./ui.container-card/types"; +export type { Props as UCheckboxProps } from "./ui.form-checkbox/types"; +export type { Props as UCheckboxGroupProps } from "./ui.form-checkbox-group/types"; +export type { Props as UCheckboxMultiStateProps } from "./ui.form-checkbox-multi-state/types"; +export type { Props as UChipProps } from "./ui.other-chip/types"; +export type { Props as UColProps } from "./ui.container-col/types"; +export type { Props as UCollapsibleProps } from "./ui.container-collapsible/types"; +export type { Props as UColorToggleProps } from "./ui.form-color-toggle/types"; +export type { Props as UDataListProps } from "./ui.data-list/types"; +export type { Props as UDatePickerProps } from "./ui.form-date-picker/types"; +export type { Props as UDatePickerRangeProps } from "./ui.form-date-picker-range/types"; +export type { Props as UDividerProps } from "./ui.container-divider/types"; +export type { Props as UDotProps } from "./ui.other-dot/types"; +export type { Props as UDrawerProps } from "./ui.container-drawer/types"; +export type { Props as UDropdownProps } from "./ui.dropdown/types"; +export type { Props as UDropdownBadgeProps } from "./ui.dropdown-badge/types"; +export type { Props as UDropdownButtonProps } from "./ui.dropdown-button/types"; +export type { Props as UDropdownLinkProps } from "./ui.dropdown-link/types"; +export type { Props as UEmptyProps } from "./ui.container-empty/types"; +export type { Props as UFileProps } from "./ui.text-file/types"; +export type { Props as UFilesProps } from "./ui.text-files/types"; +export type { Props as UGridProps } from "./ui.container-grid/types"; +export type { Props as UGroupProps } from "./ui.container-group/types"; +export type { Props as UGroupsProps } from "./ui.container-groups/types"; +export type { Props as UHeaderProps } from "./ui.text-header/types"; +export type { Props as UIconProps } from "./ui.image-icon/types"; +export type { Props as UInputProps } from "./ui.form-input/types"; +export type { Props as UInputCounterProps } from "./ui.form-input-counter/types"; +export type { Props as UInputFileProps } from "./ui.form-input-file/types"; +export type { Props as UInputNumberProps } from "./ui.form-input-number/types"; +export type { Props as UInputPasswordProps } from "./ui.form-input-password/types"; +export type { Props as UInputRatingProps } from "./ui.form-input-rating/types"; +export type { Props as UInputSearchProps } from "./ui.form-input-search/types"; +export type { Props as UKeyProps } from "./ui.text-key/types"; +export type { Props as ULabelProps } from "./ui.form-label/types"; +export type { Props as ULinkProps } from "./ui.button-link/types"; +export type { Props as UListboxProps } from "./ui.form-listbox/types"; +export type { Props as ULoaderProps } from "./ui.loader/types"; +export type { Props as ULoaderOverlayProps } from "./ui.loader-overlay/types"; +export type { Props as ULoaderProgressProps } from "./ui.loader-progress/types"; +export type { Props as UModalProps } from "./ui.container-modal/types"; +export type { Props as UModalConfirmProps } from "./ui.container-modal-confirm/types"; +export type { Props as UNotifyProps } from "./ui.text-notify/types"; +export type { Props as UNumberProps } from "./ui.text-number/types"; +export type { Props as UPageProps } from "./ui.container-page/types"; +export type { Props as UPaginationProps } from "./ui.navigation-pagination/types"; +export type { Props as UPlaceholderProps } from "./ui.container-placeholder/types"; +export type { Props as UProgressProps } from "./ui.navigation-progress/types"; +export type { Props as URadioProps } from "./ui.form-radio/types"; +export type { Props as URadioGroupProps } from "./ui.form-radio-group/types"; +export type { Props as URowProps } from "./ui.container-row/types"; +export type { Props as USelectProps } from "./ui.form-select/types"; +export type { Props as USkeletonProps } from "./ui.skeleton/types"; +export type { Props as USkeletonChoiceProps } from "./ui.skeleton-choice/types"; +export type { Props as USkeletonInputProps } from "./ui.skeleton-input/types"; +export type { Props as USkeletonTextProps } from "./ui.skeleton-text/types"; +export type { Props as USplitterProps } from "./ui.container-splitter/types"; +export type { Props as USwitchProps } from "./ui.form-switch/types"; +export type { Props as UTabProps } from "./ui.navigation-tab/types"; +export type { Props as UTableProps } from "./ui.data-table/types"; +export type { Props as UTabsProps } from "./ui.navigation-tabs/types"; +export type { Props as UTextProps } from "./ui.text-block/types"; +export type { Props as UTextareaProps } from "./ui.form-textarea/types"; +export type { Props as UThemeColorToggleProps } from "./ui.other-theme-color-toggle/types"; +export type { Props as UToggleProps } from "./ui.button-toggle/types"; +/* component data types */ +export type { + Row, + BaseRow, + TableRow, + FlatRow, + RowId, + RowData, + Cell, + CellObject, + Column, + ColumnObject, + TableColumn, + DateDivider, + BaseDateDivider, + SlotRow, + SelectedSlotRow, + UTableSlots, + UTableStaticSlots, + UTableDynamicSlots, +} from "./ui.data-table/types"; +export type { + Option, + BaseOption, + ListboxOption, + SelectedValue, + SlotOption, + UListboxSlots, +} from "./ui.form-listbox/types"; +export type { + SelectOption, + SelectedSlotOption, + SelectedOptionsBinding, + USelectSlots, +} from "./ui.form-select/types"; +export type { UDropdownSlots } from "./ui.dropdown/types"; +export type { UDropdownBadgeSlots } from "./ui.dropdown-badge/types"; +export type { UDropdownButtonSlots } from "./ui.dropdown-button/types"; +export type { UDropdownLinkSlots } from "./ui.dropdown-link/types"; +export type { UTabsOption, BaseTabOption, UTabsSlots } from "./ui.navigation-tabs/types"; +export type { UBreadcrumb, BaseBreadcrumb } from "./ui.navigation-breadcrumbs/types"; +export type { UToggleOption, BaseToggleOption, UToggleSlots } from "./ui.button-toggle/types"; +export type { UAccordionOption, BaseAccordionOption } from "./ui.container-accordion/types"; +export type { + DataListItem, + BaseDataListItem, + SlotItem, + UDataListSlots, +} from "./ui.data-list/types"; +export type { UCheckboxOption, BaseCheckboxOption } from "./ui.form-checkbox/types"; +export type { ChipItem, BaseChipItem } from "./ui.image-avatar/types"; +export type { AvatarItem, BaseAvatarItem } from "./ui.image-avatar-group/types"; /* Export enums directly (not as types) */ export { ColorMode } from "./types"; diff --git a/src/types.ts b/src/types.ts index a6bb10514..37a16c3f9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -390,9 +390,11 @@ export interface CVACompoundVariants { export type MutatedProps = ComputedRef; +export type GetDataTest = (suffix?: string) => string | null; + export type UseUI = { config: Ref>; - getDataTest: (suffix?: string) => string | null; + getDataTest: GetDataTest; } & KeysAttrs; export interface ConfigDerivedData { diff --git a/src/ui.button-link/config.ts b/src/ui.button-link/config.ts index d686983af..7f333135f 100644 --- a/src/ui.button-link/config.ts +++ b/src/ui.button-link/config.ts @@ -5,7 +5,7 @@ export default /*tw*/ { text-{color} decoration-{color} underline-offset-4 hover:text-{color}-lifted hover:decoration-{color}-lifted active:text-{color}-accented active:decoration-{color}-accented - outline-solid outline-transparent + outline-solid outline-medium outline-transparent focus-visible:outline-medium focus-visible:rounded-small focus-visible:outline-offset-4 focus-visible:outline-{color} `, diff --git a/src/ui.button-toggle/UToggle.vue b/src/ui.button-toggle/UToggle.vue index 55b9492a9..6004b5b50 100644 --- a/src/ui.button-toggle/UToggle.vue +++ b/src/ui.button-toggle/UToggle.vue @@ -1,4 +1,4 @@ - diff --git a/src/ui.button-toggle/types.ts b/src/ui.button-toggle/types.ts index 044790158..8dc8e7723 100644 --- a/src/ui.button-toggle/types.ts +++ b/src/ui.button-toggle/types.ts @@ -1,21 +1,33 @@ import defaultConfig from "./config"; -import type { ComponentConfig } from "../types"; +import type { ComponentConfig, UnknownObject } from "../types"; export type Config = typeof defaultConfig; -export interface UToggleOption { +export interface BaseToggleOption { value: string | number | boolean; label?: string; disabled?: boolean; icon?: string; leftIcon?: string; rightIcon?: string; - onClick?: (option: Omit) => void; - [key: string]: unknown; + onClick?: (option: Omit) => void; } -export interface Props { +/* Any object shape is a valid option, only reserved option keys are type checked. */ +export type UToggleOption = BaseToggleOption & (object | UnknownObject); + +/** + * Slots receive each option verbatim — `UToggle` iterates `props.options` without + * filtering, flattening or synthesising entries, so the caller's shape is guaranteed. + */ +export interface UToggleSlots { + left?: (props: { option: TItem; index: number; iconName?: string }) => unknown; + option?: (props: { option: TItem; index: number; label: string; iconName?: string }) => unknown; + right?: (props: { option: TItem; index: number; iconName?: string }) => unknown; +} + +export interface Props { /** * Selected value. */ @@ -24,7 +36,7 @@ export interface Props { /** * Toggle item options. */ - options?: UToggleOption[]; + options?: TItem[]; /** * Toggle size. diff --git a/src/ui.button/config.ts b/src/ui.button/config.ts index d9dda8c07..0fd8c4710 100644 --- a/src/ui.button/config.ts +++ b/src/ui.button/config.ts @@ -2,7 +2,7 @@ export default /*tw*/ { button: { base: ` flex items-center justify-center font-medium leading-snug! whitespace-nowrap - border border-solid outline-solid outline-transparent transition cursor-pointer + border border-solid outline-solid outline-medium outline-transparent transition cursor-pointer focus-visible:outline-medium focus-visible:outline-offset-2 focus-visible:outline-{color} disabled:cursor-not-allowed disabled:outline-0 disabled:outline-offset-0 `, diff --git a/src/ui.container-accordion/types.ts b/src/ui.container-accordion/types.ts index ca9ce928d..3ccb0b4e1 100644 --- a/src/ui.container-accordion/types.ts +++ b/src/ui.container-accordion/types.ts @@ -1,15 +1,18 @@ import defaultConfig from "./config"; -import type { ComponentConfig } from "../types"; +import type { ComponentConfig, UnknownObject } from "../types"; export type Config = typeof defaultConfig; -export interface UAccordionOption { +export interface BaseAccordionOption { value: string; title: string; description?: string; opened?: boolean; } +/* Any object shape is a valid option, only reserved option keys are type checked. */ +export type UAccordionOption = BaseAccordionOption & (object | UnknownObject); + export type SetAccordionSelectedItem = (value: string, opened: boolean) => void; export interface Props { diff --git a/src/ui.container-collapsible/config.ts b/src/ui.container-collapsible/config.ts index 751ac21da..f50c8c2f5 100644 --- a/src/ui.container-collapsible/config.ts +++ b/src/ui.container-collapsible/config.ts @@ -1,7 +1,7 @@ export default /*tw*/ { wrapper: { base: ` - relative inline-block h-max rounded-medium transition cursor-pointer outline-solid outline-transparent + relative inline-block h-max rounded-medium transition cursor-pointer outline-solid outline-medium outline-transparent focus-visible:outline-medium focus-visible:outline-offset-2 focus-visible:outline-primary`, variants: { disabled: { diff --git a/src/ui.container-drawer/tests/UDrawer.test.ts b/src/ui.container-drawer/tests/UDrawer.test.ts index 3e82c3559..f64401faa 100644 --- a/src/ui.container-drawer/tests/UDrawer.test.ts +++ b/src/ui.container-drawer/tests/UDrawer.test.ts @@ -9,11 +9,6 @@ import type { Props } from "../types"; describe("UDrawer", () => { const modelValue = true; - // Wait for an async component to load - function sleep(ms: number = 0) { - return new Promise((resolve) => setTimeout(resolve, ms)); - } - // Props tests describe("Props", () => { it("ModelValue – renders when true", () => { @@ -150,10 +145,10 @@ describe("UDrawer", () => { expect(innerWrapper.attributes("class")).toContain(expectedClass); }); - it("CloseOnOverlay – closes drawer when overlay is clicked", () => { + it("CloseOnOverlay – closes drawer when overlay is clicked", async () => { const closeOnOverlay = [true, false]; - closeOnOverlay.forEach(async (value) => { + for (const value of closeOnOverlay) { const component = mount(UDrawer, { props: { modelValue, @@ -167,18 +162,17 @@ describe("UDrawer", () => { await innerWrapper.trigger("mousedown"); await innerWrapper.trigger("click"); - await sleep(1000); - const drawer = component.find('[vl-key="drawer"]'); + const closeRequests = component.emitted("update:modelValue"); - expect(drawer.exists()).toBe(!value); - }); + expect(value ? closeRequests?.at(-1) : closeRequests).toEqual(value ? [false] : undefined); + } }); - it("CloseOnEsc – closes drawer when escape key is pressed", () => { + it("CloseOnEsc – closes drawer when escape key is pressed", async () => { const closeOnEsc = [true, false]; - closeOnEsc.forEach(async (value) => { + for (const value of closeOnEsc) { const component = mount(UDrawer, { props: { modelValue, @@ -189,12 +183,11 @@ describe("UDrawer", () => { const wrapper = component.find("[vl-key='wrapper']"); await wrapper.trigger("keydown", { key: "Escape" }); - await sleep(1000); - const drawer = component.find('[vl-key="drawer"]'); + const closeRequests = component.emitted("update:modelValue"); - expect(drawer.exists()).toBe(!value); - }); + expect(value ? closeRequests?.at(-1) : closeRequests).toEqual(value ? [false] : undefined); + } }); it("DataTest – applies the correct data-test attribute", () => { diff --git a/src/ui.container-modal/tests/UModal.test.ts b/src/ui.container-modal/tests/UModal.test.ts index bb7ad2b02..60e577bf2 100644 --- a/src/ui.container-modal/tests/UModal.test.ts +++ b/src/ui.container-modal/tests/UModal.test.ts @@ -34,11 +34,6 @@ const mountWithRouter = (component: unknown, options: UnknownObject) => { describe("UModal", () => { const modelValue = true; - // Wait for an async component to load - function sleep(ms: number = 0) { - return new Promise((resolve) => setTimeout(resolve, ms)); - } - // Props tests describe("Props", () => { // ModelValue prop @@ -189,10 +184,10 @@ describe("UModal", () => { }); // CloseOnOverlay prop - it("renders with closeOnOverlay prop", () => { + it("renders with closeOnOverlay prop", async () => { const closeOnOverlay = [true, false]; - closeOnOverlay.forEach(async (value) => { + for (const value of closeOnOverlay) { const component = mount(UModal, { props: { modelValue, @@ -206,19 +201,18 @@ describe("UModal", () => { await innerWrapper.trigger("mousedown"); await innerWrapper.trigger("click"); - await sleep(1000); - const modal = component.find('[vl-key="modal"]'); + const closeRequests = component.emitted("update:modelValue"); - expect(modal.exists()).toBe(!value); - }); + expect(value ? closeRequests?.at(-1) : closeRequests).toEqual(value ? [false] : undefined); + } }); // CloseOnEsc prop - it("renders with closeOnEsc prop", () => { + it("renders with closeOnEsc prop", async () => { const closeOnEsc = [true, false]; - closeOnEsc.forEach(async (value) => { + for (const value of closeOnEsc) { const component = mount(UModal, { props: { modelValue, @@ -229,12 +223,11 @@ describe("UModal", () => { const wrapper = component.find("[vl-key='wrapper']"); await wrapper.trigger("keydown", { key: "Escape" }); - await sleep(1000); - const modal = component.find('[vl-key="modal"]'); + const closeRequests = component.emitted("update:modelValue"); - expect(modal.exists()).toBe(!value); - }); + expect(value ? closeRequests?.at(-1) : closeRequests).toEqual(value ? [false] : undefined); + } }); // Inner prop diff --git a/src/ui.data-list/UDataList.vue b/src/ui.data-list/UDataList.vue index d55536dea..2c1072e02 100644 --- a/src/ui.data-list/UDataList.vue +++ b/src/ui.data-list/UDataList.vue @@ -1,4 +1,4 @@ - diff --git a/src/ui.data-list/types.ts b/src/ui.data-list/types.ts index ff1e41ff6..c555aa8aa 100644 --- a/src/ui.data-list/types.ts +++ b/src/ui.data-list/types.ts @@ -2,7 +2,7 @@ import defaultConfig from "./config"; import DraggableContext from "vuedraggable"; -import type { ComponentConfig, UnknownType } from "../types"; +import type { ComponentConfig, UnknownObject } from "../types"; export type Config = typeof defaultConfig; @@ -11,18 +11,44 @@ export interface DragMoveEvent extends DragEvent { relatedContext: typeof DraggableContext | null; } -export interface DataListItem { +export interface BaseDataListItem { crossed?: boolean; actions?: boolean; children?: DataListItem[]; - [key: string]: UnknownType | DataListItem[]; } -export interface Props { +/* Any object shape is a valid item, only reserved item keys are type checked. */ +export type DataListItem = BaseDataListItem & (object | UnknownObject); + +/* Drops `DataListItem`'s index signature so slot items only expose statically known keys. */ +type KnownKeys = { + [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K]; +}; + +/** + * Item shape exposed to slots: the caller's item plus the reserved item keys. + * + * `TItem`'s own keys are optional. The component renders itself for nested children + * (`:list="element.children"`), and `BaseDataListItem.children` is `DataListItem[]`, not `TItem[]` + * — so a child need not carry the parent's members. `nestedKey` can open a nested list from a + * different source entirely. `BaseDataListItem`'s keys are optional already, so nothing is + * promised that can be absent. + */ +export type SlotItem = Partial> & + BaseDataListItem; + +export interface UDataListSlots { + empty?: (props: { emptyTitle: string; emptyDescription: string }) => unknown; + drag?: (props: { item: SlotItem; iconName: string }) => unknown; + label?: (props: { item: SlotItem; crossed: boolean }) => unknown; + actions?: (props: { item: SlotItem }) => unknown; +} + +export interface Props { /** * Data item options. */ - list?: DataListItem[]; + list?: TItem[]; /** * Group name. @@ -49,12 +75,33 @@ export interface Props { */ animationDuration?: number; + /** + * Sortable JS fallback (instead of native HTML5 DnD). + */ + forceFallback?: boolean; + + /** + * Append the fallback drag clone to document.body. + */ + fallbackOnBody?: boolean; + + /** + * CSS class for the Sortable fallback drag clone (forceFallback mode). + */ + fallbackClass?: string; + /** * Disable empty state for nested elements if empty (internal props). * @ignore */ hideEmptyStateForNesting?: boolean; + /** + * Item key whose truthy value renders an empty nested drop-zone, + * so a deeper level can be created by drag even without children. + */ + nestedKey?: string; + /** * Component config object. */ diff --git a/src/ui.data-table/UTable.vue b/src/ui.data-table/UTable.vue index 47b76cc55..b1ff2ac70 100644 --- a/src/ui.data-table/UTable.vue +++ b/src/ui.data-table/UTable.vue @@ -1,4 +1,4 @@ - - +
- + - +
- + - +
@@ -1293,8 +1443,13 @@ function renderRowTemplate(row: FlatRow, rowIndex: number): VNode[] { @binding {number} index --> @@ -1305,7 +1460,7 @@ function renderRowTemplate(row: FlatRow, rowIndex: number): VNode[] { - + - + + + + + + + + + + + + + diff --git a/src/ui.data-table/UTableRow.vue b/src/ui.data-table/UTableRow.vue index 94b249399..0f8396fe0 100644 --- a/src/ui.data-table/UTableRow.vue +++ b/src/ui.data-table/UTableRow.vue @@ -400,6 +400,7 @@ function renderNestedFirstCell(value: Cell, key: string, cellIndex: number): VNo function renderTableCell(value: Cell, key: string, index: number): VNode { const keyStr = String(key); + const column = props.columns.find((col) => col.key === key) ?? props.columns[index]; const nestedCellNode = isNestedFirstCell(index) ? renderNestedFirstCell(value, key, index) @@ -414,12 +415,12 @@ function renderTableCell(value: Cell, key: string, index: number): VNode { ...props.attrs.bodyCellBaseAttrs.value, class: cx([ props.attrs.bodyCellBaseAttrs.value.class, - props.columns[index].tdClass, + column.tdClass, getCellClasses(props.row, keyStr), - getStickyColumnClass(props.columns[index]), + getStickyColumnClass(column), getSearchMatchCellClass(keyStr), ]), - style: getStickyColumnStyle(props.columns[index]), + style: getStickyColumnStyle(column), "data-cell-key": key, "data-test": getDataTest(`${key}-cell`), }, diff --git a/src/ui.data-table/config.ts b/src/ui.data-table/config.ts index b62af9afe..237ae2f36 100644 --- a/src/ui.data-table/config.ts +++ b/src/ui.data-table/config.ts @@ -13,7 +13,11 @@ export default /*tw*/ { }, compoundVariants: [ { stickedHeader: true, actionsHeader: true, class: "rounded-none" }, - { stickedHeader: true, actionsHeader: false, class: "border-muted bg-default" }, + { + stickedHeader: true, + actionsHeader: false, + class: "border-muted bg-default", + }, ], }, stickyHeaderCell: "{>headerCellBase} flex-none whitespace-nowrap", @@ -41,7 +45,7 @@ export default /*tw*/ { base: "min-w-full border-none text-medium w-auto table-auto border-separate border-spacing-0", variants: { virtualScroll: { - true: "table-layout-fixed w-full", + true: "table-fixed w-full", }, }, }, @@ -111,6 +115,8 @@ export default /*tw*/ { }, bodyEmptyState: "{UEmpty} my-8", bodyEmptyStateCell: "", + skeletonCell: "{USkeleton} h-4 rounded-small", + skeletonCheckbox: "{USkeleton} size-4 rounded-small", footer: { base: "group/footer border-t border-solid border-muted", variants: { @@ -147,6 +153,9 @@ export default /*tw*/ { stickyHeader: false, stickyFooter: false, loading: false, + skeletonLoading: false, + skeletonRows: 5, + skeletonWidths: ["w-11/12", "w-4/5", "w-3/4", "w-2/3", "w-1/2"], textEllipsis: false, search: "", searchMatch: -1, diff --git a/src/ui.data-table/storybook/stories.ts b/src/ui.data-table/storybook/stories.ts index 02edb679d..50fc37133 100644 --- a/src/ui.data-table/storybook/stories.ts +++ b/src/ui.data-table/storybook/stories.ts @@ -12,7 +12,7 @@ import { getRandomId } from "../../utils/helper"; import defaultConfig from "../config"; -import UTable from "../UTable.vue"; +import UTableComponent from "../UTable.vue"; import UButton from "../../ui.button/UButton.vue"; import ULink from "../../ui.button-link/ULink.vue"; import UNumber from "../../ui.text-number/UNumber.vue"; @@ -27,6 +27,9 @@ import tooltip from "../../v.tooltip/vTooltip"; import type { Row, Props, ColumnObject } from "../types"; import { StickySide } from "../types"; +/* Generic SFCs are typed as a function, which drops the compiler-generated `__name`. */ +const UTable = UTableComponent as typeof UTableComponent & { __name: string }; + interface UTableArgs extends Props { slotTemplate?: string; enum: "size"; @@ -182,6 +185,36 @@ Loading.parameters = { }, }; +export const SkeletonLoading = DefaultTemplate.bind({}); +SkeletonLoading.args = { + rows: [], + skeletonLoading: true, +}; +SkeletonLoading.parameters = { + docs: { + description: { + story: + "Use `skeletonLoading` while the first request is pending. The table keeps its structure " + + "and renders cell skeletons instead of the empty state.", + }, + }, +}; + +export const SkeletonLoadingConfigured = DefaultTemplate.bind({}); +SkeletonLoadingConfigured.args = { + rows: [], + skeletonLoading: true, + skeletonRows: 3, + skeletonWidths: ["w-11/12", "w-2/3", "w-1/2"], +}; +SkeletonLoadingConfigured.parameters = { + docs: { + description: { + story: "Configure skeleton row count and cell widths for a single table with props.", + }, + }, +}; + export const EmptyCellLabel = DefaultTemplate.bind({}); EmptyCellLabel.args = { emptyCellLabel: "NO DATA", diff --git a/src/ui.data-table/tests/UTable.test-d.ts b/src/ui.data-table/tests/UTable.test-d.ts new file mode 100644 index 000000000..0d11800e0 --- /dev/null +++ b/src/ui.data-table/tests/UTable.test-d.ts @@ -0,0 +1,179 @@ +import { describe, it, expectTypeOf } from "vitest"; + +import type { + Props, + RowId, + SelectedSlotRow, + SlotRow, + TableRow, + UTableDynamicSlots, + UTableSlots, +} from "../types"; + +interface UserRow { + id: number; + name: string; + qty: number; +} + +type CellSlot = NonNullable["cell-name"]>; +type CellProps = Parameters[0]; + +/* The caller's own row members must all stay optional — see `SlotRow`. */ +type RequiredKeys = { + [K in keyof T]-?: object extends Pick ? never : K; +}[keyof T]; + +describe("UTable types", () => { + it("infers the caller's row shape in a cell slot", () => { + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + }); + + it("does not expose undeclared row properties to a cell slot", () => { + /* An index signature would make any key legal, so `row.nonExistent` must not resolve. */ + type HasIndexSignature = string extends keyof CellProps["row"] ? true : false; + + expectTypeOf().toEqualTypeOf(); + expectTypeOf().not.toEqualTypeOf(); + }); + + it("keeps the row flattening metadata available", () => { + expectTypeOf().toHaveProperty("nestedLevel"); + expectTypeOf["nestedLevel"]>().toEqualTypeOf(); + }); + + it("types the static slots", () => { + type SelectedRows = Parameters["header-actions"]>>[0]; + + expectTypeOf().toEqualTypeOf[]>(); + + type Expand = NonNullable["expand"]>; + + expectTypeOf[0]["expanded"]>().toEqualTypeOf(); + }); + + it("does not promise the caller's members on selected rows", () => { + /* Select-all flattens nested children into the selection, and those need not carry + * `TRow`'s members — so the slot must not type them as guaranteed. */ + expectTypeOf["name"]>().toEqualTypeOf(); + expectTypeOf["qty"]>().toEqualTypeOf(); + + /* A nested child, as `getFlatRows` actually produces it, must be assignable. */ + expectTypeOf<{ + id: number; + nestedLevel: number; + parentRowId: number; + }>().toExtend>(); + + /* Flattening metadata stays guaranteed. */ + expectTypeOf["nestedLevel"]>().toEqualTypeOf(); + }); + + it("does not promise the caller's members on any row slot", () => { + /* `getFlatRows` recurses into `row.row`, and a nested child is typed `TableRow`, not `TRow`. + * Every row slot receives those children verbatim; `nested-row` receives nothing else. */ + type Expand = Parameters["expand"]>>[0]; + type NestedRow = Parameters["nested-row"]>>[0]; + + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + + /* The real nested-child shape recorded at these slots must be assignable to all of them. */ + type NestedChild = { + id: number; + label: string; + nestedLevel: number; + parentRowId: number; + }; + + expectTypeOf().toExtend(); + expectTypeOf().toExtend(); + expectTypeOf().toExtend(); + + /* Flattening metadata is always set by `getFlatRows`, so it stays guaranteed. */ + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toExtend(); + expectTypeOf().not.toExtend(); + }); + + it("resolves Props with no type argument", () => { + expectTypeOf().toEqualTypeOf>(); + expectTypeOf["rows"]>().toEqualTypeOf(); + }); + + it("narrows the dynamic slot keys to the known column keys", () => { + type Narrowed = keyof UTableDynamicSlots; + + expectTypeOf<"cell-name">().toExtend(); + expectTypeOf<"header-qty">().toExtend(); + expectTypeOf<"cell-typo">().not.toExtend(); + expectTypeOf<"header-typo">().not.toExtend(); + }); + + it("accepts every dynamic slot key when the columns are not statically known", () => { + /* Columns from a `ref`, an API, or a plain `string[]` widen `TCol` to `string`. Erroring on + * a valid slot there would break existing callers, so the keys must stay open. */ + type Loose = keyof UTableDynamicSlots; + + expectTypeOf<"cell-name">().toExtend(); + expectTypeOf<"cell-anything-at-all">().toExtend(); + expectTypeOf<"header-whatever">().toExtend(); + + expectTypeOf>().toEqualTypeOf< + UTableDynamicSlots + >(); + }); + + it("accepts every dynamic slot key when the columns are empty", () => { + /* An empty `columns` array infers `TCol` as `never`, which means "not statically known", + * not "no keys allowed". Without the `[TCol]` tuple guard the mapped type collapses to `{}` + * and every slot is rejected — the "columns load async, start empty" pattern breaks. */ + type Empty = keyof UTableDynamicSlots; + + /* `toEqualTypeOf`, not `toExtend`: the bug made `Empty` collapse to `never`, and `never` + * satisfies every `toExtend` assertion vacuously. */ + expectTypeOf().toEqualTypeOf<`cell-${string}` | `header-${string}`>(); + expectTypeOf().not.toEqualTypeOf(); + + expectTypeOf>().toEqualTypeOf< + UTableDynamicSlots + >(); + }); + + it("keeps the row shape intact when the keys are narrowed", () => { + type NarrowCell = Parameters["cell-name"]>>[0]; + + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + }); + + it("keeps the slot payloads identical however the keys resolve", () => { + /* Narrowing must only change which keys exist, never what a slot receives. */ + type WideCell = Parameters["cell-name"]>>[0]; + type NarrowCell = Parameters["cell-name"]>>[0]; + type EmptyCell = Parameters["cell-name"]>>[0]; + + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + + type WideHeader = Parameters["header-name"]>>[0]; + type EmptyHeader = Parameters["header-name"]>>[0]; + + expectTypeOf().toEqualTypeOf(); + + /* No row slot may promise the caller's members, whatever `TCol` is. Only the flattening + * metadata stays required; `name`/`qty` must never appear among the required keys. */ + expectTypeOf>().toEqualTypeOf>(); + expectTypeOf>().toEqualTypeOf>(); + + type CallerKeys = Extract>, "name" | "qty">; + + expectTypeOf().toEqualTypeOf(); + }); +}); diff --git a/src/ui.data-table/tests/UTable.test.ts b/src/ui.data-table/tests/UTable.test.ts index f64aa32a5..29cfc1847 100644 --- a/src/ui.data-table/tests/UTable.test.ts +++ b/src/ui.data-table/tests/UTable.test.ts @@ -7,6 +7,7 @@ import UTableRow from "../UTableRow.vue"; import UEmpty from "../../ui.container-empty/UEmpty.vue"; import ULoaderProgress from "../../ui.loader-progress/ULoaderProgress.vue"; import UDivider from "../../ui.container-divider/UDivider.vue"; +import USkeleton from "../../ui.skeleton/USkeleton.vue"; import { LoaderProgressSymbol, createLoaderProgress, @@ -77,7 +78,7 @@ describe("UTable.vue", () => { function mountUTable(props: Props, options = {}) { return mount(UTable, { - props, + props: { ...props }, global: getGlobalOptions(), ...options, }); @@ -149,6 +150,29 @@ describe("UTable.vue", () => { expect(emptyComponent.exists()).toBe(true); }); + it("Skeleton loading – renders skeleton body instead of empty state", () => { + const component = mountUTable(getDefaultProps({ rows: [], skeletonLoading: true })); + + expect(component.findComponent(UEmpty).exists()).toBe(false); + expect(component.findAllComponents(USkeleton).length).toBeGreaterThan(0); + expect(component.find("table").exists()).toBe(true); + }); + + it("Skeleton loading – applies skeleton rows and widths from props", () => { + const component = mountUTable( + getDefaultProps({ + rows: [], + skeletonLoading: true, + skeletonRows: 2, + skeletonWidths: ["w-1/3"], + }), + ); + + expect(component.findAll("tbody tr")).toHaveLength(2); + expect(component.findAllComponents(USkeleton)).toHaveLength(defaultColumns.length * 2); + expect(component.findComponent(USkeleton).classes()).toContain("w-1/3"); + }); + it("Selectable – renders select all checkbox when selectable is true", () => { const component = mountUTable(getDefaultProps({ selectable: true })); @@ -221,6 +245,20 @@ describe("UTable.vue", () => { expect(loader.props("loading")).toBe(false); }); + it("Skeleton loading – hides progress loader while skeleton is shown", () => { + const component = mountUTable( + getDefaultProps({ + rows: [], + loading: true, + skeletonLoading: true, + }), + ); + + const loader = component.findComponent(ULoaderProgress); + + expect(loader.props("loading")).toBe(false); + }); + it("Data Test – applies correct data-test attributes", () => { const dataTest = "test-table"; @@ -644,7 +682,14 @@ describe("UTable.vue", () => { name: "Parent Row", email: "parent@example.com", role: "Admin", - row: [{ id: "1-1", name: "Child", email: "child@example.com", role: "User" }], + row: [ + { + id: "1-1", + name: "Child", + email: "child@example.com", + role: "User", + }, + ], }, ]; @@ -710,7 +755,14 @@ describe("UTable.vue", () => { name: "Parent Row", email: "parent@example.com", role: "Admin", - row: [{ id: "1-1", name: "Child", email: "child@example.com", role: "User" }], + row: [ + { + id: "1-1", + name: "Child", + email: "child@example.com", + role: "User", + }, + ], }, ]; @@ -792,7 +844,14 @@ describe("UTable.vue", () => { name: "Parent Row", email: "parent@example.com", role: "Admin", - row: [{ id: "1-1", name: "Child", email: "child@example.com", role: "User" }], + row: [ + { + id: "1-1", + name: "Child", + email: "child@example.com", + role: "User", + }, + ], }; const component = mountUTable(getDefaultProps({ rows: [expandableRow] })); @@ -813,7 +872,14 @@ describe("UTable.vue", () => { name: "Parent Row", email: "parent@example.com", role: "Admin", - row: [{ id: "1-1", name: "Child", email: "child@example.com", role: "User" }], + row: [ + { + id: "1-1", + name: "Child", + email: "child@example.com", + role: "User", + }, + ], }; const component = mountUTable( @@ -879,7 +945,12 @@ describe("UTable.vue", () => { email: "child1@example.com", role: "User", row: [ - { id: "1-1-1", name: "Grandchild", email: "grandchild@example.com", role: "Guest" }, + { + id: "1-1-1", + name: "Grandchild", + email: "grandchild@example.com", + role: "Guest", + }, ], }, ], @@ -902,7 +973,14 @@ describe("UTable.vue", () => { name: "Parent Row", email: "parent@example.com", role: "Admin", - row: [{ id: "1-1", name: "Child", email: "child@example.com", role: "User" }], + row: [ + { + id: "1-1", + name: "Child", + email: "child@example.com", + role: "User", + }, + ], }; const component = mountUTable(getDefaultProps({ rows: [expandableRow] })); diff --git a/src/ui.data-table/tests/UTableEmptyColumnsTypes.vue b/src/ui.data-table/tests/UTableEmptyColumnsTypes.vue new file mode 100644 index 000000000..708e2985a --- /dev/null +++ b/src/ui.data-table/tests/UTableEmptyColumnsTypes.vue @@ -0,0 +1,27 @@ + + + + diff --git a/src/ui.data-table/tests/UTableTypes.vue b/src/ui.data-table/tests/UTableTypes.vue new file mode 100644 index 000000000..f75149e06 --- /dev/null +++ b/src/ui.data-table/tests/UTableTypes.vue @@ -0,0 +1,193 @@ + + + + diff --git a/src/ui.data-table/types.ts b/src/ui.data-table/types.ts index 9e5c5a51f..dda15c897 100644 --- a/src/ui.data-table/types.ts +++ b/src/ui.data-table/types.ts @@ -1,5 +1,4 @@ import defaultConfig from "./config"; - import type { Ref } from "vue"; import type { ComponentConfig, UnknownObject } from "../types"; import type { Config as UDividerConfig } from "../ui.container-divider/types"; @@ -26,20 +25,30 @@ export interface RowData { [key: string]: Cell; } -export interface DateDivider { +export interface BaseDateDivider { date: Date | string; label?: string; config?: ComponentConfig; } -export interface Row { +/* Any object shape is a valid divider, only reserved divider keys are type checked. */ +export type DateDivider = BaseDateDivider & (object | UnknownObject); + +export interface BaseRow { id: RowId; rowDate?: string | Date; - row?: Row | Row[]; + row?: TableRow | TableRow[]; class?: string | ((row: Row) => string); +} + +export interface Row extends BaseRow { + row?: Row | Row[]; [key: string]: unknown; } +/* Any object shape is a valid row, only reserved row keys are type checked. */ +export type TableRow = BaseRow & (object | UnknownObject); + export interface FlatRow extends Row { parentRowId?: RowId; nestedLevel: number; @@ -59,23 +68,30 @@ export interface ColumnObject { thClass?: string; } -export type Column = ColumnObject | string; +/* Any object shape is a valid column, only reserved column keys are type checked. */ +export type TableColumn = ColumnObject & (object | UnknownObject); + +export type Column = TableColumn | string; + +/* A column entry that keeps its key literal, so slot keys can be narrowed at the call site. */ +export type KeyedColumn = TCol | (TableColumn & { key: TCol }); -export interface Props { +export interface Props { /** * Table columns (headers). + * `readonly` so `as const` column lists keep their literal keys and narrow the cell slot names. */ - columns: Column[]; + columns: readonly KeyedColumn[]; /** * Table rows data. */ - rows: Row[]; + rows: TRow[]; /** * Selected rows. */ - selectedRows?: Row[]; + selectedRows?: TRow[]; /** * Selected rows id. @@ -117,6 +133,21 @@ export interface Props { */ loading?: boolean; + /** + * Show skeleton body for the first table load instead of the regular loader. + */ + skeletonLoading?: boolean; + + /** + * Number of skeleton rows shown during initial loading. + */ + skeletonRows?: number; + + /** + * Skeleton cell width classes used to vary the loading placeholder layout. + */ + skeletonWidths?: string[]; + /** * Enable virtual scrolling for large datasets. */ @@ -163,6 +194,72 @@ export interface Props { dataTest?: string | null; } +/* Drops `Row`'s `[key: string]: unknown` so slot rows only expose statically known keys. */ +type KnownKeys = { + [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K]; +}; + +/** + * Row shape exposed to slots: the caller's row plus the flattening metadata. + * + * `TRow`'s own keys are optional. Rows are flattened through `getFlatRows`, which recurses into + * `row.row` — and `BaseRow.row` is `TableRow | TableRow[]`, not `TRow`, so a nested child need not + * carry the parent's members. Every row slot (`cell-*`, `expand`, `nested-row`) receives those + * children verbatim; `nested-row` receives nothing else. `BaseRow`/`FlatRow` metadata + * (`id`, `nestedLevel`, â€Ļ) is always set by the flattener, so it stays guaranteed. + */ +export type SlotRow = Partial> & + KnownKeys; + +/* Selected rows carry the same guarantee: select-all flattens nested children into the selection. */ +export type SelectedSlotRow = SlotRow; + +export interface UTableStaticSlots { + "header-counter"?: (props: { total: number }) => unknown; + "header-actions"?: (props: { selectedRows: SelectedSlotRow[] }) => unknown; + "before-header"?: (props: { colsCount: number; classes?: string }) => unknown; + "after-last-row"?: (props: { colsCount: number; classes?: string }) => unknown; + "before-first-row"?: () => unknown; + "empty-state"?: () => unknown; + footer?: (props: { colsCount: number }) => unknown; + expand?: (props: { index: number; row: SlotRow; expanded: boolean }) => unknown; + "nested-row"?: (props: { index: number; row: SlotRow; nestedLevel: number }) => unknown; +} + +/** + * Slot key suffix. Narrows to the literal column keys only when they are statically known — + * `columns` from a `ref`, an API, or a plain `string[]` widens `TCol` back to `string`, and an + * empty `columns` array infers `TCol` as `never`. Both mean "not statically known", so every + * `cell-*` / `header-*` slot must keep compiling. + * + * The `[TCol]` tuple wrapper is required: a naked `never` distributes to `never`, which would + * silently collapse the whole mapped type and reject every slot. + */ +type SlotKeySuffix = [TCol] extends [never] + ? string + : string extends TCol + ? string + : TCol; + +export type UTableDynamicSlots = { + [K in `cell-${SlotKeySuffix}`]?: (props: { + value: Cell; + row: SlotRow; + index: number; + cellIndex: number; + }) => unknown; +} & { + [K in `header-${SlotKeySuffix}`]?: (props: { + column: ColumnObject; + index: number; + }) => unknown; +}; + +export type UTableSlots< + TRow extends TableRow = TableRow, + TCol extends string = string, +> = UTableStaticSlots & UTableDynamicSlots; + export interface UTableRowAttrs { bodyCellContentAttrs: Ref; bodyCellCheckboxAttrs: Ref; diff --git a/src/ui.data-table/utilTable.ts b/src/ui.data-table/utilTable.ts index 79323a178..ebf40f1c1 100644 --- a/src/ui.data-table/utilTable.ts +++ b/src/ui.data-table/utilTable.ts @@ -1,6 +1,6 @@ import type { Column, ColumnObject, FlatRow, Row, RowData, RowId } from "./types"; -export function normalizeColumns(columns: Column[]): ColumnObject[] { +export function normalizeColumns(columns: readonly Column[]): ColumnObject[] { return columns.map((column) => typeof column === "string" ? { label: column, key: column } : column, ); diff --git a/src/ui.dropdown-badge/UDropdownBadge.vue b/src/ui.dropdown-badge/UDropdownBadge.vue index 76b942b9b..9e97514a3 100644 --- a/src/ui.dropdown-badge/UDropdownBadge.vue +++ b/src/ui.dropdown-badge/UDropdownBadge.vue @@ -1,4 +1,4 @@ - diff --git a/src/ui.dropdown-badge/types.ts b/src/ui.dropdown-badge/types.ts index c24255e07..29c529b3e 100644 --- a/src/ui.dropdown-badge/types.ts +++ b/src/ui.dropdown-badge/types.ts @@ -1,11 +1,25 @@ import defaultConfig from "./config"; -import type { Option } from "../ui.form-listbox/types"; +import type { ListboxOption, SlotOption } from "../ui.form-listbox/types"; import type { ComponentConfig, UnknownObject } from "../types"; export type Config = typeof defaultConfig; -export interface Props { +/* Option slots are forwarded through `UDropdown` to `UListbox`, inheriting its group flattening — + a flattened member need not carry the parent's members, so `SlotOption` is `Partial`. */ +export interface UDropdownBadgeSlots { + /* `label` is `UDropdown`'s `displayLabel`, a join of `option[labelKey]` lookups — `undefined` + when the label cannot be resolved. */ + default?: (props: { label: string | undefined; opened: boolean }) => unknown; + left?: (props: { opened: boolean }) => unknown; + toggle?: (props: { opened: boolean }) => unknown; + "before-option"?: (props: { option: SlotOption; index: number }) => unknown; + option?: (props: { option: SlotOption; index: number }) => unknown; + "after-option"?: (props: { option: SlotOption; index: number }) => unknown; + empty?: () => unknown; +} + +export interface Props { /** * Selected badge. */ @@ -24,7 +38,7 @@ export interface Props { /** * Options list. */ - options?: Option[]; + options?: TOption[]; /** * Label key in the item object of options. diff --git a/src/ui.dropdown-button/UDropdownButton.vue b/src/ui.dropdown-button/UDropdownButton.vue index 139b9d656..b4a2898b6 100644 --- a/src/ui.dropdown-button/UDropdownButton.vue +++ b/src/ui.dropdown-button/UDropdownButton.vue @@ -1,4 +1,4 @@ - diff --git a/src/ui.dropdown-button/types.ts b/src/ui.dropdown-button/types.ts index 75119fe5f..625debf69 100644 --- a/src/ui.dropdown-button/types.ts +++ b/src/ui.dropdown-button/types.ts @@ -1,11 +1,25 @@ import defaultConfig from "./config"; -import type { Option } from "../ui.form-listbox/types"; +import type { ListboxOption, SlotOption } from "../ui.form-listbox/types"; import type { ComponentConfig, UnknownObject } from "../types"; export type Config = typeof defaultConfig; -export interface Props { +/* Option slots are forwarded through `UDropdown` to `UListbox`, inheriting its group flattening — + a flattened member need not carry the parent's members, so `SlotOption` is `Partial`. */ +export interface UDropdownButtonSlots { + /* `label` is `UDropdown`'s `displayLabel`, a join of `option[labelKey]` lookups — `undefined` + when the label cannot be resolved. */ + default?: (props: { label: string | undefined; opened: boolean }) => unknown; + left?: (props: { opened: boolean }) => unknown; + toggle?: (props: { opened: boolean }) => unknown; + "before-option"?: (props: { option: SlotOption; index: number }) => unknown; + option?: (props: { option: SlotOption; index: number }) => unknown; + "after-option"?: (props: { option: SlotOption; index: number }) => unknown; + empty?: () => unknown; +} + +export interface Props { /** * Selected button. */ @@ -24,7 +38,7 @@ export interface Props { /** * Options list. */ - options?: Option[]; + options?: TOption[]; /** * Label key in the item object of options. diff --git a/src/ui.dropdown-link/UDropdownLink.vue b/src/ui.dropdown-link/UDropdownLink.vue index 79c4fdba3..6bc8dcd6a 100644 --- a/src/ui.dropdown-link/UDropdownLink.vue +++ b/src/ui.dropdown-link/UDropdownLink.vue @@ -1,4 +1,4 @@ - diff --git a/src/ui.dropdown-link/types.ts b/src/ui.dropdown-link/types.ts index c91bdba91..a1d3f30a0 100644 --- a/src/ui.dropdown-link/types.ts +++ b/src/ui.dropdown-link/types.ts @@ -1,11 +1,25 @@ import defaultConfig from "./config"; -import type { Option } from "../ui.form-listbox/types"; +import type { ListboxOption, SlotOption } from "../ui.form-listbox/types"; import type { ComponentConfig, UnknownObject } from "../types"; export type Config = typeof defaultConfig; -export interface Props { +/* Option slots are forwarded through `UDropdown` to `UListbox`, inheriting its group flattening — + a flattened member need not carry the parent's members, so `SlotOption` is `Partial`. */ +export interface UDropdownLinkSlots { + /* `label` is `UDropdown`'s `displayLabel`, a join of `option[labelKey]` lookups — `undefined` + when the label cannot be resolved. */ + default?: (props: { label: string | undefined; opened: boolean }) => unknown; + left?: (props: { opened: boolean }) => unknown; + toggle?: (props: { opened: boolean }) => unknown; + "before-option"?: (props: { option: SlotOption; index: number }) => unknown; + option?: (props: { option: SlotOption; index: number }) => unknown; + "after-option"?: (props: { option: SlotOption; index: number }) => unknown; + empty?: () => unknown; +} + +export interface Props { /** * Selected link. */ @@ -24,7 +38,7 @@ export interface Props { /** * Options list. */ - options?: Option[]; + options?: TOption[]; /** * Label key in the item object of options. diff --git a/src/ui.dropdown/UDropdown.vue b/src/ui.dropdown/UDropdown.vue index 9a96912aa..92a368c9a 100644 --- a/src/ui.dropdown/UDropdown.vue +++ b/src/ui.dropdown/UDropdown.vue @@ -1,4 +1,4 @@ - diff --git a/src/ui.dropdown/types.ts b/src/ui.dropdown/types.ts index fe880301b..3c22db329 100644 --- a/src/ui.dropdown/types.ts +++ b/src/ui.dropdown/types.ts @@ -1,11 +1,35 @@ import defaultConfig from "./config"; -import type { Option } from "../ui.form-listbox/types"; +import type { ListboxOption, SlotOption } from "../ui.form-listbox/types"; import type { ComponentConfig, UnknownObject } from "../types"; export type Config = typeof defaultConfig; -export interface Props { +/** + * The option slots are forwarded verbatim to `UListbox`, so they inherit its flattening: with + * `groupValueKey` set, `filterGroups` concats nested group members into the list the slots iterate + * and those members need not carry the parent's members. Hence `SlotOption`, which is `Partial`. + * + * `selectedOptions` is different — it is a `filter`ed subset of `props.options` (`UDropdown.vue:91`) + * with no synthetic entry, so it keeps the full `TOption` guarantee. + */ +export interface UDropdownSlots { + default?: (props: { + /* `displayLabel` joins `option[labelKey]` lookups, which yield `undefined` for an option + without that key — so it is only `string` when the label is resolvable. */ + opened: boolean; + displayLabel: string | undefined; + fullLabel: string; + selectedOptions: TOption[]; + }) => unknown; + dropdown?: (props: { opened: boolean; contentClasses: string | undefined }) => unknown; + "before-option"?: (props: { option: SlotOption; index: number }) => unknown; + option?: (props: { option: SlotOption; index: number }) => unknown; + "after-option"?: (props: { option: SlotOption; index: number }) => unknown; + empty?: () => unknown; +} + +export interface Props { /** * Selected dropdown value. */ @@ -24,7 +48,7 @@ export interface Props { /** * Options list. */ - options?: Option[]; + options?: TOption[]; /** * Label key in the item object of options. diff --git a/src/ui.form-calendar/config.ts b/src/ui.form-calendar/config.ts index 51adcc383..2d6360dd6 100644 --- a/src/ui.form-calendar/config.ts +++ b/src/ui.form-calendar/config.ts @@ -56,7 +56,7 @@ export default /*tw*/ { timepicker: "mt-2 pl-1 pt-3 text-medium flex items-stretch justify-between gap-2 border-t border-muted", timepickerLabel: "w-full self-center", timepickerInputWrapper: ` - flex items-center rounded-medium border border-solid border-default outline-solid outline-transparent + flex items-center rounded-medium border border-solid border-default outline-solid outline-small outline-transparent hover:focus-within:border-primary focus-within:border-primary focus-within:outline focus-within:outline-small focus-within:outline-primary `, diff --git a/src/ui.form-checkbox-group/UCheckboxGroup.vue b/src/ui.form-checkbox-group/UCheckboxGroup.vue index e8aab63ab..9521d43f1 100644 --- a/src/ui.form-checkbox-group/UCheckboxGroup.vue +++ b/src/ui.form-checkbox-group/UCheckboxGroup.vue @@ -1,5 +1,5 @@