Compare commits

...
36 Commits
Author SHA1 Message Date
Clintchiz bca9549f3a finish CSS delivery and generated type remediation
Quality / quality (ubuntu-latest) (push) Failing after 12m54s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-10 00:17:25 +05:30
Clintchiz 232d8e6734 complete performance and reliability follow-ups 2026-08-09 23:04:36 +05:30
Clintchiz 8f19a5eb2b complete UI CSS ownership remediation 2026-08-09 21:42:30 +05:30
Clintchiz 937ccb9e8e stabilize cache ignore integration test 2026-08-09 21:11:01 +05:30
Clintchiz 6859b5c4ff localize legacy UI component styles 2026-08-09 21:07:10 +05:30
Clintchiz 3f5ec8696f ignore dev caches in example tool configs 2026-08-09 20:22:55 +05:30
Clintchiz 9fd80b7cef ignore per-process caches across repository tools 2026-08-09 19:48:54 +05:30
Clintchiz 7d87200e31 fix layout SSR translations and remediation follow-ups 2026-08-09 18:49:16 +05:30
Clintchiz 51286d0fa3 complete framework remediation validation 2026-08-09 14:39:22 +05:30
Clintchiz 905cbce0c1 remove hazardous legacy scripts and gate repository writes 2026-08-09 14:24:35 +05:30
Clintchiz 09ebd44b6c fix compiler authoring traps and showcase generation order 2026-08-09 14:22:12 +05:30
Clintchiz 2e5c0cc953 feat(ui): build remaining component scaffolds 2026-08-09 14:18:43 +05:30
Clintchiz 1660044ed2 perf(csr): load component controllers on demand 2026-08-09 14:10:15 +05:30
Clintchiz 324928cfca refactor(ui): finish style-only component migration 2026-08-09 14:01:01 +05:30
Clintchiz 5c2c6a0a8f refactor(ui): replace Tailwind utilities with local styles 2026-08-09 13:58:13 +05:30
Clintchiz ff62918dbb refactor(ui): remove superseded component scaffolds 2026-08-09 13:49:55 +05:30
Clintchiz 54a93c1d14 refactor(ui): localize component styles 2026-08-09 13:47:01 +05:30
Clintchiz b6bd5cfcb7 refactor(ui): localize progress and loading styles 2026-08-09 13:42:09 +05:30
Clintchiz fdf6282aed perf(compiler): share client function state closures 2026-08-09 13:38:55 +05:30
Clintchiz ca8aabf640 fix(compiler): avoid deferred helper name collisions 2026-08-09 13:34:17 +05:30
Clintchiz a90d3683d3 fix(dev-server): normalize hot reload module paths 2026-08-09 13:32:00 +05:30
Clintchiz d85be6c456 test(perf): tighten reactive runtime size budget 2026-08-09 13:25:16 +05:30
Clintchiz 8ef6233ef3 fix(compiler): reject malformed structured props 2026-08-09 13:24:18 +05:30
Clintchiz 91e8b2ab05 test(dev-server): guard server-rendered translations 2026-08-09 13:22:18 +05:30
Clintchiz b2e83bc941 feat(compiler): commit deferred client state writes 2026-08-09 13:21:34 +05:30
Clintchiz 3e77a621f5 test(dev-server): guard nested component slot rendering 2026-08-09 13:18:32 +05:30
Clintchiz a1f671ed5d feat(framework): make component props reactive 2026-08-09 13:17:36 +05:30
Clintchiz 7c584c1d2e feat(csr): diagnose missing rendered theme tokens 2026-08-09 13:07:54 +05:30
Clintchiz 709a38feb4 feat(csr): diagnose missing output binding functions 2026-08-09 13:06:56 +05:30
Clintchiz 35cd28aad4 revert(compiler): preserve inline structured component props 2026-08-09 13:00:18 +05:30
Clintchiz de8d792e37 fix(compiler): diagnose inline object component props 2026-08-09 12:58:02 +05:30
Clintchiz 64bb0e366a perf(compiler): deduplicate client peer state synchronization 2026-08-09 12:57:37 +05:30
Clintchiz e4502f2437 fix(dev-server): isolate generated cache per process 2026-08-09 12:56:11 +05:30
Clintchiz 504d065003 feat(csr): add stripped dev output diagnostics 2026-08-09 11:25:48 +05:30
Clintchiz f09341fcfa test(scripts): make UI generator newline check deterministic 2026-08-09 11:24:20 +05:30
Clintchiz 535ad5af6d chore(scripts): remove destructive UI catalog generator 2026-08-09 11:23:59 +05:30
197 changed files with 10891 additions and 14560 deletions
+1
View File
@@ -1,6 +1,7 @@
node_modules/
dist/
.wrnexus/
.wrnexus-*/
*.log
*.db
*.db-shm
+2
View File
@@ -1,6 +1,8 @@
node_modules/
dist/
**/dist/
**/.wrnexus/**
**/.wrnexus-*/**
.publish/
**/.wirefw/
coverage/
+231 -4
View File
@@ -109,12 +109,101 @@
"name": "inter-app-api-showcase",
"devDependencies": {
"@eslint/js": "^9.0.0",
"@types/bun": "latest",
"@types/bun": "^1.3.14",
"@wrnexus/cli": "0.8.6",
"eslint": "^9.0.0",
"prettier": "latest",
"prettier": "^3.9.6",
"typescript": "^5.5.0",
"typescript-eslint": "latest",
"typescript-eslint": "^8.65.0",
},
},
"examples/inter-app-api-showcase/apps/admin": {
"name": "admin",
"version": "0.1.0",
"dependencies": {
"@wrnexus/ai": "0.8.6",
"@wrnexus/auth": "0.8.6",
"@wrnexus/authz": "0.8.6",
"@wrnexus/captcha": "0.8.6",
"@wrnexus/core": "0.8.6",
"@wrnexus/csr": "0.8.6",
"@wrnexus/db": "0.8.6",
"@wrnexus/dev-server": "0.8.6",
"@wrnexus/encryption": "0.8.6",
"@wrnexus/helpers": "0.8.6",
"@wrnexus/i18n": "0.8.6",
"@wrnexus/image": "0.8.6",
"@wrnexus/jwt": "0.8.6",
"@wrnexus/observability": "0.8.6",
"@wrnexus/realtime": "0.8.6",
"@wrnexus/security": "0.8.6",
"@wrnexus/store": "0.8.6",
"@wrnexus/styles": "0.8.6",
"@wrnexus/tracking": "0.8.6",
"@wrnexus/ui": "0.8.6",
"@wrnexus/uploader": "0.8.6",
"@wrnexus/validation": "0.8.6",
},
"devDependencies": {
"@eslint/js": "^9.0.0",
"@iconify-json/lucide": "^1.2.118",
"@iconify/tailwind4": "^1.2.3",
"@tailwindcss/cli": "^4.0.0",
"@types/bun": "^1.3.14",
"@wrnexus/cli": "0.8.6",
"eslint": "^9.0.0",
"prettier": "^3.9.6",
"tailwindcss": "^4.0.0",
"typescript": "^5.5.0",
"typescript-eslint": "^8.65.0",
},
},
"examples/inter-app-api-showcase/apps/web": {
"name": "web",
"version": "0.1.0",
"dependencies": {
"@wrnexus/ai": "0.8.6",
"@wrnexus/auth": "0.8.6",
"@wrnexus/authz": "0.8.6",
"@wrnexus/captcha": "0.8.6",
"@wrnexus/core": "0.8.6",
"@wrnexus/csr": "0.8.6",
"@wrnexus/db": "0.8.6",
"@wrnexus/dev-server": "0.8.6",
"@wrnexus/encryption": "0.8.6",
"@wrnexus/helpers": "0.8.6",
"@wrnexus/i18n": "0.8.6",
"@wrnexus/image": "0.8.6",
"@wrnexus/jwt": "0.8.6",
"@wrnexus/observability": "0.8.6",
"@wrnexus/realtime": "0.8.6",
"@wrnexus/security": "0.8.6",
"@wrnexus/store": "0.8.6",
"@wrnexus/styles": "0.8.6",
"@wrnexus/tracking": "0.8.6",
"@wrnexus/ui": "0.8.6",
"@wrnexus/uploader": "0.8.6",
"@wrnexus/validation": "0.8.6",
},
"devDependencies": {
"@eslint/js": "^9.0.0",
"@iconify-json/lucide": "^1.2.118",
"@iconify/tailwind4": "^1.2.3",
"@tailwindcss/cli": "^4.0.0",
"@types/bun": "^1.3.14",
"@wrnexus/cli": "0.8.6",
"eslint": "^9.0.0",
"prettier": "^3.9.6",
"tailwindcss": "^4.0.0",
"typescript": "^5.5.0",
"typescript-eslint": "^8.65.0",
},
},
"examples/inter-app-api-showcase/packages/shared": {
"name": "@app/shared",
"version": "0.0.0",
"dependencies": {
"@wrnexus/pubsub": "0.8.6",
},
},
"packages/ai": {
@@ -563,7 +652,7 @@
},
"services/managed-captcha": {
"name": "@wrnexus/managed-captcha-service",
"version": "0.8.0",
"version": "0.8.6",
"dependencies": {
"@wrnexus/captcha": "workspace:*",
},
@@ -580,6 +669,8 @@
"packages": {
"@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="],
"@app/shared": ["@app/shared@workspace:examples/inter-app-api-showcase/packages/shared"],
"@cyberalien/svg-utils": ["@cyberalien/svg-utils@1.2.19", "", { "dependencies": { "@iconify/types": "^2.0.0" } }, "sha512-paDJoDu+LhuH5Ma1q0BDqBpg6d16Xk22cZZrlg/s/J9oB8KgnnpU/snaEETJu2G1pwYcWQINUz6WUqu0Wm9k6A=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
@@ -950,6 +1041,8 @@
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
"admin": ["admin@workspace:examples/inter-app-api-showcase/apps/admin"],
"ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="],
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
@@ -1286,6 +1379,8 @@
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"web": ["web@workspace:examples/inter-app-api-showcase/apps/web"],
"whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
@@ -1318,6 +1413,12 @@
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
"admin/@eslint/js": ["@eslint/js@9.39.5", "", {}, "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A=="],
"admin/eslint": ["eslint@9.39.5", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.6", "@eslint/js": "9.39.5", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw=="],
"admin/typescript-eslint": ["typescript-eslint@8.66.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.66.0", "@typescript-eslint/parser": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0", "@typescript-eslint/utils": "8.66.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-QlEbBPz/RuJ1XUHj29nm3t0F/O/cSlEnntozqPOYHnnTGAXFamnMBu5i9Vn6vhUPHGAjR+Vl+5J8vPN/BMUrJw=="],
"csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="],
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
@@ -1338,8 +1439,38 @@
"tsyringe/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="],
"web/@eslint/js": ["@eslint/js@9.39.5", "", {}, "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A=="],
"web/eslint": ["eslint@9.39.5", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.6", "@eslint/js": "9.39.5", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw=="],
"web/typescript-eslint": ["typescript-eslint@8.66.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.66.0", "@typescript-eslint/parser": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0", "@typescript-eslint/utils": "8.66.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-QlEbBPz/RuJ1XUHj29nm3t0F/O/cSlEnntozqPOYHnnTGAXFamnMBu5i9Vn6vhUPHGAjR+Vl+5J8vPN/BMUrJw=="],
"@eslint/eslintrc/espree/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
"admin/eslint/@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="],
"admin/eslint/@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="],
"admin/eslint/@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
"admin/eslint/@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
"admin/eslint/eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
"admin/eslint/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
"admin/eslint/espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
"admin/eslint/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
"admin/typescript-eslint/@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.66.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.66.0", "@typescript-eslint/type-utils": "8.66.0", "@typescript-eslint/utils": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.66.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A=="],
"admin/typescript-eslint/@typescript-eslint/parser": ["@typescript-eslint/parser@8.66.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g=="],
"admin/typescript-eslint/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.66.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.66.0", "@typescript-eslint/tsconfig-utils": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg=="],
"admin/typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.66.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA=="],
"csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="],
"inter-app-api-showcase/eslint/@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="],
@@ -1366,6 +1497,58 @@
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.66.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA=="],
"web/eslint/@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="],
"web/eslint/@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="],
"web/eslint/@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
"web/eslint/@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
"web/eslint/eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
"web/eslint/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
"web/eslint/espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
"web/eslint/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
"web/typescript-eslint/@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.66.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.66.0", "@typescript-eslint/type-utils": "8.66.0", "@typescript-eslint/utils": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.66.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A=="],
"web/typescript-eslint/@typescript-eslint/parser": ["@typescript-eslint/parser@8.66.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g=="],
"web/typescript-eslint/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.66.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.66.0", "@typescript-eslint/tsconfig-utils": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg=="],
"web/typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.66.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA=="],
"admin/eslint/@eslint/config-array/@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
"admin/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0" } }, "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g=="],
"admin/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0", "@typescript-eslint/utils": "8.66.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-LG2dWfjZQQp0ADtAu/EWJVayefGL2UEZ3CDeI44D9v3rXB/WYUqE/jpO28KrEKul5AySrmI+Zh1v6v+xW2U9+g=="],
"admin/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
"admin/typescript-eslint/@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
"admin/typescript-eslint/@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0" } }, "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g=="],
"admin/typescript-eslint/@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"admin/typescript-eslint/@typescript-eslint/parser/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
"admin/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.66.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.66.0", "@typescript-eslint/types": "^8.66.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g=="],
"admin/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.66.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g=="],
"admin/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"admin/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
"admin/typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0" } }, "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g=="],
"admin/typescript-eslint/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"inter-app-api-showcase/eslint/@eslint/config-array/@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0" } }, "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g=="],
@@ -1394,6 +1577,42 @@
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"web/eslint/@eslint/config-array/@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
"web/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0" } }, "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g=="],
"web/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0", "@typescript-eslint/utils": "8.66.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-LG2dWfjZQQp0ADtAu/EWJVayefGL2UEZ3CDeI44D9v3rXB/WYUqE/jpO28KrEKul5AySrmI+Zh1v6v+xW2U9+g=="],
"web/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
"web/typescript-eslint/@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
"web/typescript-eslint/@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0" } }, "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g=="],
"web/typescript-eslint/@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"web/typescript-eslint/@typescript-eslint/parser/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
"web/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.66.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.66.0", "@typescript-eslint/types": "^8.66.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g=="],
"web/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.66.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g=="],
"web/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"web/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
"web/typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0" } }, "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g=="],
"web/typescript-eslint/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"admin/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"admin/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"admin/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"admin/typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
@@ -1401,5 +1620,13 @@
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
"web/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"web/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"web/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"web/typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
}
}
+120 -72
View File
@@ -1,7 +1,29 @@
# Framework remediation plan
**Status: completed.** The
repository has 102 maintained UI components, zero
undeclared non-native output emitters, zero components matching the scaffold
definition, and local styles on every component. The core reactive runtime is
48,124 minified bytes; component controllers ship separately at 24,027 bytes.
The §4.1 ownership migration is complete: `ui.css` contains global and
cross-cutting primitives only. Shared component foundations are carried by
three internal style-only components and render on demand; the former global
component families and standalone `wire-next` generation class are absent.
Final validation passed the production, package, example, service, editor,
showcase-generation, typecheck, lint, formatting, public-API, visual-contract,
security, and runtime-size gates. The only skipped test is the explicitly
environment-dependent live PostgreSQL/MySQL test.
Production CSS delivery now links the selected palette/accent sheet (6,632
decoded bytes for dark/violet) instead of the complete 74,764-byte theme
matrix. The compatibility `theme.css` endpoint remains available but is not
linked by generated pages. Generated application declarations are committed
and checked for drift by `check:production`.
Written 2026-08-09, after the 0.8.6 release. Every number here was measured
against the tree at commit `7a2b5865`, not estimated. Where a cause is not yet
against the completed tree, not estimated. Where a cause is not yet
proven the item says so and makes proving it step one — nothing in this document
should be implemented on the strength of a guess.
@@ -155,7 +177,7 @@ This section is the real work. These are capability gaps, not defects, and they
are what currently limits what anyone can build. **§2.1 is the highest-value
item in this entire document.**
### 2.1 The framework fails silently, everywhere
### 2.1 Development diagnostics — FIXED
**Issue.** Nothing in §1 produced a single diagnostic at any layer. That is not
five unlucky bugs; it is a systemic property. The failure mode is always
@@ -195,10 +217,10 @@ bun test packages/csr/test/diagnostics.test.ts
bun run build && grep -c "WRN-DEV-" examples/basic-app/dist/reactive.js # expect 0
```
Add a runtime size assertion so the production bundle does not grow: it is
69,947 bytes minified today.
The production size ratchet now caps the split core runtime at 49,000 bytes; it
currently measures 48,124 minified bytes.
### 2.2 Deferred state writes are silently dropped
### 2.2 Deferred state writes — FIXED
**Issue.** State written after a client function returns — in a `setTimeout`, a
promise callback, an observer, a `pointermove` — is discarded. The peer-function
@@ -219,9 +241,9 @@ model limitation being paid for in bundle size.
interacts with re-render batching.
**How to test.** A test where a client function writes state inside a
`setTimeout` and the DOM reflects it after the timer. It must fail today.
`setTimeout` and the DOM reflects it after the timer. This failed before the fix.
### 2.3 Props and slot content render once
### 2.3 Reactive props and slot content — FIXED
**Issue.** Component props and slot content do not track page state. The
controlled-component pattern does not work at all: passing `value={pageState}`
@@ -243,9 +265,9 @@ quietly doesn't.
**How to test.** A page with `state n`, a component bound to `value={n}`, and a
button incrementing `n`; the component's rendered output must follow. Must fail
today.
before the fix.
### 2.4 A component tag inside another component's slot is dropped
### 2.4 Nested component slots — FIXED
**Issue.** Nesting a component tag in another component's slot drops the tag and
leaves its children orphaned.
@@ -255,9 +277,9 @@ it is fixed, emit the §2.1 diagnostic — a dropped component must never be
silent.
**How to test.** Render `<Card><Badge label="x" /></Card>` and assert the Badge
root element exists in the output. Must fail today.
root element exists in the output. This failed before the fix.
### 2.5 Object props must be hand-hoisted
### 2.5 Structured object props — FIXED
**Issue.** An object or array prop written inline in an attribute is read as
interpolation. Three encodings were tried this session and all failed: bare
@@ -274,17 +296,19 @@ outcomes for a plain authoring mistake.
**How to test.** A compiler test asserting the specific diagnostic for
`items={{ a: 1 }}`, and one asserting the hoisted form still compiles.
### 2.6 i18n text is not server-rendered
### 2.6 i18n text is server-rendered — FIXED
**Issue.** Translated text ships as empty spans and is filled after hydration.
Navigation is blank until the runtime loads.
**Original issue.** Translated text shipped as empty spans and was filled after
hydration. Navigation was blank until the runtime loaded.
**Why it matters.** This is an SSR-first framework failing to server-render
text. It is an SEO problem, a layout-shift problem, and it undercuts the
framework's central claim.
**Change.** Resolve the active locale during SSR and emit the translated text
into the HTML, keeping the client path for locale switches.
**Change.** Done. The active locale is resolved during SSR and translated text
is emitted into the HTML, keeping the client path for locale switches. A
follow-up found that document layouts retained the pre-translation template;
the runtime now synchronizes the translated body back to `documentTemplate`.
**How to test.**
@@ -293,13 +317,13 @@ curl -s http://localhost:3520/ | grep -c "Navigation" # expect > 0
```
Assert against the raw server response with JavaScript disabled — not the
hydrated DOM, which already looks correct today.
hydrated DOM.
---
## 3. Smaller but real
### 3.1 Twenty-two outputs still have no emitter
### 3.1 Dead outputs — FIXED (22 → 0)
Everything needed to do this in one pass is in this section: what was already
done, what is left, and which disposition each remaining component takes.
@@ -331,9 +355,9 @@ all ten rewired components. **`Map` was not verified** — its three outputs wer
converted by the same mechanical change and the build passes, but no Map was on
the probe page. Put one on a page and confirm before treating it as done.
#### 3.1.2 What is left — 22 outputs, 9 components
#### 3.1.2 Original remaining work — resolved
**Issue.** These are not miswired. All nine are **pure scaffolds**: roughly 25
**Original issue.** These were not miswired. All nine were **pure scaffolds**: roughly 25
lines each, zero state, zero functions, no style block, no event handlers. They
are markup shells that declare outputs. "Add an emitter" is not the work — there
is nothing to emit from.
@@ -393,9 +417,7 @@ self-contained. `TreeView` is medium — recursive rendering plus expand state.
**How to test.**
- The ratchet in `packages/ui/test/ui.test.ts` is pinned at 22 and only ever
moves down. Lower the ceiling **in the same commit** that fixes or removes a
component — never in a separate one, or the ratchet stops meaning anything.
- The ratchet in `packages/ui/test/ui.test.ts` is now pinned at **0**.
- For a removal: `bun run check:public-api` flags the dropped export, and the
migration entry is required before `release:prepare` will pass.
- For a build: the component renders, its behaviour works in a browser, its
@@ -403,16 +425,16 @@ self-contained. `TreeView` is medium — recursive rendering plus expand state.
output from a page and confirm it arrives — §1.1 and §1.2 are both cases where
reading the source said it worked and the browser said otherwise.
### 3.2 Twenty-eight components are still scaffolds
### 3.2 Component scaffolds — FIXED (28 → 0)
**Issue.** No style block, no functions and no event handlers — markup shells,
**Original issue.** No style block, no functions and no event handlers — markup shells,
the same shape as the Table scaffold that was removed and the LayoutSplitter
scaffold that was rebuilt. A scaffold in a published library is a promise the
library does not keep.
**Evidence.** Counted as components with no `style {}`, no `function` and no
`@handler`. An earlier figure of 23 in the audit was measured with a looser rule;
28 is the number:
28 was the number:
> AdvancedDatePicker, AdvancedRangeSlider, AuthSplitLayout, avatar, Blockquote,
> button, Chart, Clipboard, Confetti, CopyMarkup, DataMap, DragAndDrop,
@@ -425,10 +447,10 @@ AdvancedRangeSlider, Chart, Confetti, CopyMarkup, FileUpload, Toast,
ToastNotifications, TreeView. Do §3.1 first and this list drops to 19 without
any extra work. Do not plan the two items separately.
Separately, **58 components still carry the `wire-next` scaffold class** in
their markup, including many that are otherwise finished. That class is a
generation artefact rather than a design, and it is what ties them to `ui.css`
instead of their own styles (§4.1).
Separately, 58 components carried the standalone `wire-next` scaffold class in
the original audit; **zero retain it now**. Component-specific BEM identifiers
such as `wire-next--alert` remain locally owned implementation details, but no
component is bound to a global stylesheet through the generation artefact.
**Change.** Rebuild them under §0.1, or remove them. Note that several are
primitives where a scaffold is nearly the right answer — `skeleton`, `spinner`,
@@ -446,12 +468,15 @@ Track the count down the same way as §4.1:
bun run build && gzip -c examples/basic-app/dist/ui.css | wc -c
```
### 3.3 Seven components still use Tailwind utilities
### 3.3 Tailwind component migrations — FIXED (7 → 0)
**Issue.** `List` (23 utility classes), `InputNumber` (22), `Marquee` (15),
**Original issue.** `List` (23 utility classes), `InputNumber` (22), `Marquee` (15),
`TextLink` (14), `Map` (12), `SearchBox` (8), `Timeline` (8) still style
themselves with utility classes in markup.
All seven now use local `wire-*` BEM styles. `InputNumber` styles are emitted as
component-scoped CSS and therefore do not appear in the global `ui.css` file.
**Change.** Migrate to `wire-*` BEM classes in the component's own `style {}`
block, per §0.1. Note this changes the rendered class list, so application CSS
selecting on those utilities stops matching — it needs a migration note, exactly
@@ -466,7 +491,7 @@ bun run generate:ui-visual # only after reviewing
Compare screenshots before and after; the visual result should be unchanged.
### 3.4 `List` only fires `select` for items with an href
### 3.4 `List` selection without an href — FIXED
**Issue.** The `@click` handler sits on the `item.href` branch only, so an item
without a link is not selectable. Pre-existing, and surprising.
@@ -481,28 +506,37 @@ whichever behaviour was chosen.
## 4. Delivery and the dev loop
### 4.1 `ui.css` ships whole to every page
### 4.1 Global `ui.css` migration — FIXED
**Issue.** `ui.css` is **175,848 bytes, 26,350 gzipped**, served on every page.
Only **42 of 108** components have a local `style {}` block, so 66 still depend
on it.
**Original issue.** `ui.css` was **175,848 bytes, 26,350 gzipped**, served on
every page. Only **42 of 108** components had a local `style {}` block.
**Change.** Migrate the remaining 66 under §0.1. This is mechanical, high-value,
and shrinks `ui.css` toward the global-only baseline it should be. Do it in
groups, one commit per group, regenerating the visual contract each time.
**Result.** The maintained library now has **102 of 102** components with local
styles. Built `ui.css` is **15,927 bytes / 3,762 gzipped**. The `.wire-next`,
`.wire-btn`, `.wire-dropdown`, `.wire-alert`, `.wire-card`, `.wire-footer`,
`.wire-metric-card`, and `.wire-segmented-group` global families are gone.
Reusable field, select, color, and size foundations live in three internal
style-only components; the renderer emits and deduplicates them only when a
component that needs them renders. An example-app SSR guard verifies that
on-demand path, while the UI suite prevents the global families or standalone
`wire-next` class from returning.
**Change.** Done in groups with the visual contract regenerated after each
migration.
**How to test.** Track the number down, every time:
```bash
ls packages/ui/components/*.wrn | wc -l # 108
grep -l '^ style {' packages/ui/components/*.wrn | wc -l # 42 today
bun run build && gzip -c examples/basic-app/dist/ui.css | wc -c # 26350 today
ls packages/ui/components/*.wrn | wc -l # 102
grep -lE '^\s*style\s*\{' packages/ui/components/*.wrn | wc -l # 102
bun run build
bun -e "const b=await Bun.file('examples/basic-app/dist/ui.css').arrayBuffer(); console.log(Bun.gzipSync(new Uint8Array(b)).length)" # 3762
```
Add a ratchet test asserting the gzipped size only ever decreases, so this
cannot silently regress.
### 4.2 Dev server: `packages/ui` edits do not take effect
### 4.2 Dev-server package UI hot reload — FIXED
**Issue.** Editing a component under `packages/ui` does not hot-reload. Every UI
change this session needed a full server restart, which is the single biggest
@@ -529,9 +563,9 @@ confirming it.
`packages/ui/components/*.wrn`, reload the page **without restarting**, and see
the change. That is the whole acceptance criterion.
### 4.3 Running tests wipes the cache of a live dev server
### 4.3 Per-process development caches — FIXED
**Issue.** Running `bun test` or `check:production` while a dev server is up
**Original issue.** Running `bun test` or `check:production` while a dev server was up
empties `.wrnexus`, and every client module then 404s until a restart. This
produced several confusing "the Sidebar is broken" symptoms this session that
were not component bugs at all.
@@ -541,7 +575,12 @@ were not component bugs at all.
`WRNEXUS_PRESERVE_CACHE=1`. Any dev server a test spawns against the repo root
therefore deletes the running server's cache.
**Change.** Two options, both cheap:
**Change.** The first option was implemented: each process uses
`.wrnexus-<pid>`, normal server shutdown removes its directory, and
`.wrnexus-*/` is excluded by Git, ESLint, TypeScript, and Prettier so an
interrupted process cannot pollute a commit or a repository-wide tool run.
The considered options were:
- Give each dev-server instance its own cache directory (keyed by port or pid),
so instances cannot collide; or
@@ -553,23 +592,22 @@ Prefer the first — it fixes the class rather than one caller. Either way,
owns.
**How to test.** Start the dev server, load a page, run `bun test packages` to
completion, reload the page without restarting. It must still work. This fails
reliably today.
completion, reload the page without restarting. It must still work.
### 4.4 Generated artefacts have an order dependency
### 4.4 Generated artefact ordering — FIXED
**Issue.** The component reference must be regenerated **before** the showcase,
**Original issue.** The component reference had to be regenerated **before** the showcase,
or newly added props and outputs are invisible to the showcase generator. This
is undocumented and cost real time.
**Change.** Make the showcase generator depend on the reference explicitly, or
fail loudly when the reference is older than the component sources.
**Change.** Done. `generate:pages` regenerates the reference first, and both
generators emit idempotent Prettier-compatible artifacts.
**How to test.** Add a prop to a component, run the showcase generator without
regenerating the reference, and assert it errors rather than silently emitting a
stale page.
### 4.5 Two authoring traps worth compiler errors
### 4.5 Compiler diagnostics for authoring traps — FIXED
**Issue.** Block comments are not allowed inside `props {}` — line comments
only — and `state page` collides with the `page` keyword. Both currently fail in
@@ -593,9 +631,9 @@ the real number.
"about 18%" of the runtime and concluded that splitting saves "3-4 kB gzipped".
Both figures were wrong, and the conclusion that followed from them was wrong.
#### The runtime today
#### Runtime before remediation
`reactive.js` is **70,101 bytes minified, 21,736 gzipped**. Removing each
`reactive.js` was **70,101 bytes minified, 21,736 gzipped**. Removing each
subsystem and re-minifying gives its true cost:
| Subsystem | Minified | Share |
@@ -619,7 +657,11 @@ fetch, which are framework features) total **23,722 minified / 6,660 gzipped —
15,076 gzipped: the expression engine, the scope and reactivity core, and loop
diffing.
#### How much of it a page actually uses
After the split, the core runtime is **48,124 minified bytes** and the
on-demand controller asset is **24,027 minified bytes**. `/` requests no
controller asset.
#### How much of it a page actually used before the split
Measured against the example app by checking which controller markers appear in
the served HTML:
@@ -650,9 +692,9 @@ bun run scripts/lib/measure-runtime-size.ts # core must stay under budget
Plus a browser check on `/`: zero controller chunks requested.
#### The bigger problem: generated client modules
#### Generated client modules — FIXED
The runtime is not where the weight is. On `/navigation`:
Before remediation, `/navigation` measured:
- **490,212 bytes decoded** across 11 client modules, **21,026 transferred**
a 23:1 compression ratio.
@@ -661,6 +703,9 @@ The runtime is not where the weight is. On `/navigation`:
- One line appears **162 times**: `brand = context.state.brand; topLinks =
context.state.topLinks; ...` — the full state-restore prologue.
After hoisting and closure deduplication, the page is **101,253 decoded bytes**;
the largest module is **51,946 bytes** with **2.4% duplicated lines**.
Gzip hides this on the wire, but **parse and compile cost scales with decoded
bytes, not transferred bytes**. Half a megabyte of JavaScript is parsed to run
one page.
@@ -720,16 +765,18 @@ bun run build
Add the ratio itself as a signal: any module compressing better than about 10:1
is duplicating itself and should fail the check. Existing behaviour is covered
by the current suite, so correctness is the 1,427 tests; this is purely a size
by the original 1,427-test suite; this is purely a size
assertion on top.
### 4.7 `generate-ui-complete-catalog.mjs` is broken and destructive
### 4.7 Destructive generator — REMOVED
**Issue.** The script fails partway through with
**Original issue.** The script failed partway through with
`TypeError: factories[entry.category] is not a function`
(`scripts/generate-ui-complete-catalog.mjs:156`) — but not before it has already
started writing. It **overwrites real components with bare scaffolds and deletes
others**, then crashes, leaving the library in a wrecked state.
started writing. It **overwrote real components with bare scaffolds and deleted
others**, then crashed, leaving the library in a wrecked state. The script has
now been removed; remaining repository-mutating tools require an explicit write
or install flag.
**Evidence.** Running it on 2026-08-09 rewrote Accordion, alert, Badge,
AvatarGroup, ToggleCount and LayoutSplitter down to ~15-line stubs, deleted 24
@@ -754,10 +801,11 @@ a case-sensitive filesystem the same script produces duplicate files instead.
Whichever is chosen, **no script that rewrites `packages/ui/components/` should
write in place.** Generate to a staging directory, validate, then move.
**Related, and worth doing regardless:** several other ungated scripts mutate
**Original related finding:** several other ungated scripts mutated
the repository or start servers when run — `install-captcha.mjs`, the
`validate-*.mjs` and `verify-*.mjs` families, and `benchmark-framework.mjs`. All
of them fail today. They should either be repaired and gated, or removed. A
`validate-*.mjs` and `verify-*.mjs` families, and `benchmark-framework.mjs`.
Obsolete validators were removed, current validators were repaired, and useful
mutating scripts now require explicit flags. A
`scripts/` directory where running a file at random can scaffold apps, start
dev servers on ports 3000-3002 and rewrite the component library is a hazard to
anyone exploring the repo, human or otherwise.
@@ -772,7 +820,7 @@ Add a check that runs each generator in `--check` mode and fails if it would
modify tracked files, the way `check:workspace` and `check:public-api` already
do.
### 4.8 Filename casing is not consistent with the git index
### 4.8 Filename casing — FIXED
**Issue.** Four components were tracked in git under lowercase names
(`card.wrn`, `container.wrn`, `divider.wrn`, `grid.wrn`) while existing on disk
@@ -797,10 +845,10 @@ must return the capitalised names. Better, set `git config core.ignorecase
false` locally so a future rename cannot hide again, and consider a check that
compares `git ls-files` against the on-disk listing byte for byte.
## 5. Order of work
## 5. Historical order of work
Ranked by return, not by size. The first item changes the cost of every item
below it, which is why it is first.
This is the execution order used during remediation, retained as implementation
history. Every row is complete.
| # | Item | Why now |
| --- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -810,7 +858,7 @@ below it, which is why it is first.
| 3 | §2.6 server-rendered i18n | Contained work; the core SSR claim currently fails on text. |
| 4 | §4.2 + §4.3 dev loop | Cheap, and it compounds across every task below. |
| 5 | §4.6 de-duplicate generated client modules | 490 kB decoded parsed per page, 90% of it duplicated. Codegen-only, no API impact. |
| 6 | §4.1 finish the CSS migration | 66 components, mechanical, takes ~26 kB off every page. |
| 6 | §4.1 finish the CSS migration | Localized component ownership and reduced the always-served sheet by ~160 kB. |
| 7 | §4.6 split controllers out of the core runtime | 6.6 kB gzipped a typical page never executes. |
| 8 | §3.1 Group A: supersede the 5 duplicate scaffolds | 15 of the 22 dead outputs, and 5 fewer components to maintain. Needs a migration entry, not new code. |
| 9 | §3.2 scaffolds that need only their styles | Larger and cheaper than it looks; folds into item 6. |
+4
View File
@@ -1316,6 +1316,7 @@
"createOutputProxy",
"createServerProxy",
"getActionRuntime",
"getComponentControllerRuntime",
"getNavRuntime",
"getReactiveRuntime",
"getRealtimeRuntime",
@@ -2603,6 +2604,7 @@
"StylesMode",
"THEME_COOKIE",
"THEME_CSS_HREF",
"THEME_CSS_PREFIX",
"THEME_JS_HREF",
"THEME_PALETTES",
"THEME_PALETTE_NAMES",
@@ -2613,6 +2615,7 @@
"ThemeSemanticColor",
"ThemeToken",
"ThemeTokens",
"activeThemeCssHref",
"auditCssPerformance",
"auditWireTokens",
"bundleCss",
@@ -2628,6 +2631,7 @@
"loadEnv",
"loadRawConfig",
"normalizeStyleSources",
"renderActiveThemeCss",
"renderFontHead",
"renderProductionFontHead",
"renderStyles",
+77 -80
View File
@@ -2,114 +2,111 @@
"schemaVersion": 1,
"releaseLine": "0.8",
"artifacts": {
"packages/ui/components/Accordion.wrn": "2b2e756a2197052bbcaf7499d01ec4b68b53a3acaf4524b7ca6424b71ba2e9a3",
"packages/ui/components/AdvancedDatePicker.wrn": "b9b5b08e9464544837a5400bbf9a2cb25724fac037fe196a219b42e5841ad672",
"packages/ui/components/AdvancedRangeSlider.wrn": "e78b5cf825503d0cd306c6e490d71317ee3db1844882d13b500df1f7f1c408e1",
"packages/ui/components/AdvancedSelect.wrn": "29d43f8019a3e0e98af7cf12440f41523665535a39a973441b4faea78e67114a",
"packages/ui/components/Accordion.wrn": "285df0aa50733618afb1b82aa125a67030d3af4bd8b9de52bc2e6366cea871de",
"packages/ui/components/AdvancedSelect.wrn": "e8666638204f9eb044ee6f163d6af432b119bf7f39d9df20243cfae471d20bcd",
"packages/ui/components/AnnouncementBar.wrn": "903bfd9421a7350c923ace3315cedc5763fe3e6b7b54b660a0f26281d46a8754",
"packages/ui/components/AuthForm.wrn": "496d240090269e69679e01cda3b071c012ec4e925a7c01ae883d25e359e381e1",
"packages/ui/components/AuthSplitLayout.wrn": "72b82495dd14d40a2d594ccdcf3adc925d1567c155750fb1072347b8845aab7b",
"packages/ui/components/AvatarGroup.wrn": "66f3d211b7658ca5193c750d0ccb3e392185038d8960d3206f6e8a249e10214e",
"packages/ui/components/AuthForm.wrn": "ec8796a67320afa2fb2070edba194382d1ab0a00cbd574f936a502da819d00fa",
"packages/ui/components/AuthSplitLayout.wrn": "675c6ac9b07e0e9c5d6572f30028dda2391f9c6599b45945eb626df056a5b987",
"packages/ui/components/AvatarGroup.wrn": "85dd6c64f8064d0980bf4157963bf366410c2ba391fd65df86a54b1a336f43a4",
"packages/ui/components/BackToTop.wrn": "b12c56ec8b7aabad68cbdc47f4b3237e2c21a61398f0aa51f9add6229b7357cd",
"packages/ui/components/Badge.wrn": "e96cdcf182ff9834509464bb2fa4c0b041748d3f7671aa46ab489bae6f3d98ae",
"packages/ui/components/Blockquote.wrn": "6016dd4450de7cbf5c3cb413599baa2e502321e8eedef54439a0df6d0aae8bcb",
"packages/ui/components/Breadcrumb.wrn": "14732e91468f5793ee5da8599f8e40d84ec51c93d660fb0c0056b956ec707850",
"packages/ui/components/ButtonGroup.wrn": "0d97fbeed531d1d8ef4b59531923b47b34100d0de71d7bf524b02c668920b3d8",
"packages/ui/components/Blockquote.wrn": "6f91cee9538c11858aaeb98ef1a098a814a6e319e3777e59cecd2de078b3beaf",
"packages/ui/components/Breadcrumb.wrn": "5ab85d273ce72e4d5b87907975bb33f0249a34f2f87f01dc0812e827f775eed9",
"packages/ui/components/ButtonGroup.wrn": "0bb23d927e03926cc303498bc4ac1ab098343782082ac1fceeb2402702945842",
"packages/ui/components/CTASection.wrn": "2d2241d4f1018cfd2f8ea1ddd5fa8c8fcb8d1bbb2d4e4498cb9f39ff47a3801c",
"packages/ui/components/Card.wrn": "457bc546e0b227692ac96ec8f413823463d3cfe9194b6f4847c21a5beee5e8a4",
"packages/ui/components/Chart.wrn": "841def6b024720bd24898975a1434a1cb39c89e1272d42db431d35d27c98c553",
"packages/ui/components/ChatBubble.wrn": "0da2bc86830ac712481559bd2fe7214d26f25101f2e8c2e4629e25c004388b52",
"packages/ui/components/Checkbox.wrn": "45e6046e3ad4acb1c1805b51fd5bf0b95b346d3d53973ededc5e43ea2fd91f70",
"packages/ui/components/Clipboard.wrn": "bb8651c7e44517db827603d0c0e546b6891093d7e6589c0b6e45d91b6187591b",
"packages/ui/components/Collapse.wrn": "c537239831c6938f3c39be0ca3ae8ba928c2490e21cfe5853c10f7221a4678ca",
"packages/ui/components/ColorPicker.wrn": "6940b7ae48f1358008b8c3ae6dc7da31f96a1aefadeef18252568b2cda0b5cbf",
"packages/ui/components/Card.wrn": "ba1b577ae4ffa9bd93c1e6a650ce90745df3f78cd6ff9ee85530e32dab56860a",
"packages/ui/components/Carousel.wrn": "0d39e17a623fb623022f48e79ea1ab9f268e2eb359c491609c137f711a1a3ffc",
"packages/ui/components/Chart.wrn": "d2cedd0e02818d0908708c2d07a495c629fad13fa021a2e035d5023dc5c367de",
"packages/ui/components/ChatBubble.wrn": "c46b9aef38f54e6243663046fe1b18ba6f9223cbe9b479d9335f96f217d401cc",
"packages/ui/components/Clipboard.wrn": "03fcfb9786008fd7fb0a7719c3279711253afc5bbb9b0262b805dd553c44da8a",
"packages/ui/components/Collapse.wrn": "8869ff4a39541b7e0d538e2b63b9a3dcdcfcd4055fa362519c7e73f164f1be9d",
"packages/ui/components/ColorPicker.wrn": "02f5744c3eb984de4b24de26fbfd3f69021633d4b0b33aceeba84df32f6a7284",
"packages/ui/components/Columns.wrn": "e01496aa53025444cc51671efcce40d6d24d86775b2cbb1ee0def1862bb6315d",
"packages/ui/components/Combobox.wrn": "62a7d861b0e536ceb088cea3552c59acbafaea5184101e3651dc8f4976b99dbc",
"packages/ui/components/Confetti.wrn": "22d687beefd5047055f65e806b4e39f81e18887532ec18d439ac364a044b230a",
"packages/ui/components/Combobox.wrn": "140b038735a5902eb901386281a5a01e16fd1c345560a48ff2b143ebaebe1f3d",
"packages/ui/components/Confetti.wrn": "b9aece64735c721c24356122e52ca84602d9f646c7d8be1e0b80d9de3163a501",
"packages/ui/components/Container.wrn": "4a0acbb896f4c11714e3925d912b10ef9aeedfa904b4ad8561aa2cbd9a0fdca0",
"packages/ui/components/ContextMenu.wrn": "2e011e9cb1c09a3a2344ed3fa29dcc74331cd39f838535d8aee4b249dceef0ce",
"packages/ui/components/CopyMarkup.wrn": "8d57a7d72e02c126f855a181fc36e1b969edb90f683dce6b6ffe546d49d2b29b",
"packages/ui/components/ContextMenu.wrn": "33fb0f60268b12411d66e3031dc0557cb231149e7a8d83340b4b453a6e37cb09",
"packages/ui/components/CustomScrollbar.wrn": "fedb2ab06cc2cf9499c130590945ca6971a3edfb60407b6658ef7756e4be9823",
"packages/ui/components/DataMap.wrn": "65552d73ecd1a148427dbb10d611f9352ca36bd490f595ab1e1a47e173445ba1",
"packages/ui/components/DataTable.wrn": "5a76e50e8559c724aaf01ec954958ac640c99758d44f1eef8e6eb1cb6dc4f862",
"packages/ui/components/DatePicker.wrn": "18b07e59c27c2720bf960e9c04ad70d9e422ddcf0817fede74e71899811bb387",
"packages/ui/components/DeviceFrame.wrn": "a881cf2cc1f859b43cc3c7a1e1ca0fb40cea7a9e530ab5bdcb985aa9592d11c0",
"packages/ui/components/DataMap.wrn": "6058f92be7fb95e9eedcc714cad1ad43fa70e40c9dd50fd8bba5aae9935953a7",
"packages/ui/components/DataTable.wrn": "edb935b091750ee3d1aff354007eabd4998fc7e5584d50a6c726c6657765d800",
"packages/ui/components/DatePicker.wrn": "2c46f92612528cf60aa0b7e8aaae9826094acb5518d3982c2db65aca6d01e103",
"packages/ui/components/DeviceFrame.wrn": "d918d213e6e0b74d690911ab3b3f440e9b62fa019fd9e8140c5a56c5c0a0843d",
"packages/ui/components/Divider.wrn": "f5cc449611022b3d3eedf982ae48f6b1a289db7855cc9b6ea0248dcda807ffb3",
"packages/ui/components/DragAndDrop.wrn": "9a403ce9ed20d36211d8916911871875c9c7d0952a7de1a21a433eaa0b3c3324",
"packages/ui/components/Drawer.wrn": "04a77a69edfad6793b5ab60981a719f11061984c087d0d61663566e322649fef",
"packages/ui/components/Dropdown.wrn": "c61b46ae6e54f6aacac9f4ed422f6c659deea9a637fd944645acd37bf270b01d",
"packages/ui/components/FeatureCard.wrn": "a6afd6d4080917106b2c504ea3836b92ac3c18e85f1aafd74ffe49592782b5ec",
"packages/ui/components/FeatureGrid.wrn": "9e3c0440d08c6982861d3e732eeb20f1023413a58243844dfb9d7033bac13d2b",
"packages/ui/components/FeatureIconCard.wrn": "afe56213543dd78932b060fa19c4e645e558b211b380c42b6d4b0d18e9d543df",
"packages/ui/components/FileInput.wrn": "8b63811deb90a03763620bedf0d5d3c7d05a20b8ae34292eaface9b756b32ecd",
"packages/ui/components/FileUpload.wrn": "3346d8978e134c3a1e6bc742201a3fbb0cffb89da40da6fcd856a2adbb971393",
"packages/ui/components/FileUploadProgress.wrn": "83bcc8558c70fbe0e0670642f65998abd222c235f1031be46bd7c65e6b5c5154",
"packages/ui/components/Footer.wrn": "d9075d2206af75a1ec4317d7974d47311889400699260e990d672780f43f40bf",
"packages/ui/components/DragAndDrop.wrn": "31ffe9d41fc655a38257c841a520c947d3ce2338e174b5e89e32fe9ccf23b9b3",
"packages/ui/components/Drawer.wrn": "e6e2778e515361a3475439ab91b6efec3895efab2a4dfdcfb535713f656cc7ed",
"packages/ui/components/Dropdown.wrn": "627319de0f555f0f07f80e19a59f9fd52e7f74af7cf2cf9ff09e114678ad9eb8",
"packages/ui/components/FeatureCard.wrn": "2a1bfb01cec8a9892d3ff066e48c276fa7c548cb59c15bcd9b4bbf85450a07a2",
"packages/ui/components/FeatureGrid.wrn": "9bd3e9becb5d7359841ed21d607131c58da20f2e8e87e25a9fb48f6b1a2b17e5",
"packages/ui/components/FeatureIconCard.wrn": "620f66a36ca5c8ad8f2ea09e94dc4471e4146189c42149e9d31db58501281639",
"packages/ui/components/FileInput.wrn": "cc4b8f95f11d91ead9a5796e202b2264eeea24aa1c3b1718327a6ea1dd28d785",
"packages/ui/components/FileUploadProgress.wrn": "c4b4174025a1de75c35a3497e0afd0b8acc645ad5f85c3ed7296ea295bb05749",
"packages/ui/components/Footer.wrn": "71790c99e4b15a62fdaf4b7a65d7016ca427408b62a2b1c7695ef1b39ba29440",
"packages/ui/components/Grid.wrn": "d089d8657298b4d33993ba4bf7b581f816c65db513d8e48cf1fdd18da19e05a8",
"packages/ui/components/Hero.wrn": "e4e39dcd59c273192c7e4136f6583279e20b0956bcba4cfbc3f6e01a1fade152",
"packages/ui/components/HeroActions.wrn": "c5720efe2ce7f6c114eb2ea4ccc3e8102a1099078ba23fd13770eddeb6aec640",
"packages/ui/components/HeroActions.wrn": "77f69085db73b946c00aa684d50fb5ae474684e440e81edbccfd8ef5dc357e33",
"packages/ui/components/Image.wrn": "8113441bf90ab0d1d8618d477125ad339021f39e853db89d656c72c492e7a20b",
"packages/ui/components/Input.wrn": "9ad394eed60f1b05bbd2ccd50dc2de7b78245c52c87afd46055d772c38b7b0c9",
"packages/ui/components/InputGroup.wrn": "0e23ea541a60e893c9d9e6f95113911ee459468d8d6105bf03aeb55395359123",
"packages/ui/components/InputNumber.wrn": "26936385e9f01ec888a1df61719991aa7a2782305328b02c939d3c2c94e3e8e0",
"packages/ui/components/InputGroup.wrn": "95333e7cb883c52958e3d6112899f337b00825e7678a2323baed1724983a7238",
"packages/ui/components/InputNumber.wrn": "0ec020084f3e556d438f8264d61f836cfab4951c52d4df60e4944fb734fb38a9",
"packages/ui/components/Kbd.wrn": "5afaabbb15902bd8fb82cb4155cf6cbfff55600927b54d564d27e2dd8b752386",
"packages/ui/components/LayoutSplitter.wrn": "6bd6812e364420ed5d3439862116c58d0b011b298679005795bdccdbb7455066",
"packages/ui/components/LegendIndicator.wrn": "6a7607b27a17196f899132eb952203a1073e4077365a94935f485e700cbac665",
"packages/ui/components/LegendIndicator.wrn": "11f13413242e106daf347638ee2b2313fd20b05fc7001f03c0f4c00a558dfec0",
"packages/ui/components/Link.wrn": "b986abcc66bc0b1c6296f3ff4e560812e35d89cc680d5251d7a3883e68c1d053",
"packages/ui/components/List.wrn": "8cf4505ff6a079feb3b77822d7ed6977a70393dd1e6267f94e75234d3ac215d6",
"packages/ui/components/ListGroup.wrn": "37e2ec61ceab20ed020a834da03208c493702b41abcf586816e29f2e50d8ee11",
"packages/ui/components/Map.wrn": "990656db93e1acd73a301a4addaf353afa14430805366d2e5f124b831044afbe",
"packages/ui/components/MarketingSectionHeader.wrn": "7d6ffcc01a9331474475ea57ca58598be757abd9428b66bdfaf6f3603c5b145b",
"packages/ui/components/Marquee.wrn": "3395235807aea43ac10c4d562706e510dac2a32786b4a5d5d73927989ede68df",
"packages/ui/components/List.wrn": "9bfa9613f388e5b126fc27d505e8165d7aa4a058e72132b755585464e116490e",
"packages/ui/components/ListGroup.wrn": "885020eea4dbdfc90b5a659e48a339cba7a205ddb539e30d7f1aa57244d728a5",
"packages/ui/components/Map.wrn": "9023a7a188480b24222a3910d26f6926918470369a1ab610f841c31a6120b9aa",
"packages/ui/components/MarketingSectionHeader.wrn": "6f89a3b298ea299db686ddf903f92c4fe5d321719213943b16be82ba36c23302",
"packages/ui/components/Marquee.wrn": "2a88c8bd8378469add26b0d937adccac4aa9f2c2005393010298e8e4fa89693e",
"packages/ui/components/MegaMenu.wrn": "8a9a9b348ca1c5e182914ff55d981a91f47a49c52ef5e5efd8e4cb6e56361a43",
"packages/ui/components/MetricCard.wrn": "6451182739298691908f68258c0250cce2a78b0dc27c97115579ece30d7d9f92",
"packages/ui/components/MetricGrid.wrn": "6018a98c10628ed240ed236ed916c0ff60994d192c3aadafd10bc876be0f9364",
"packages/ui/components/Modal.wrn": "59d4ae9d6dd2700692d9edecfe53ee868e9f864363a4885961d1813cb2bee51f",
"packages/ui/components/Nav.wrn": "7dc456b879e9248bde267b986cdcb138b8f3127d98b51e27bc9708129953f5e2",
"packages/ui/components/Navbar.wrn": "7989d25819312014c0a0855370b80b925d53ff735d682fdb61e9677a9dacb7f3",
"packages/ui/components/PageHeader.wrn": "0761235a4924eec09877be40b292d27b70db22d210be7d8e989493165c20df86",
"packages/ui/components/Navbar.wrn": "70640f9ff6e615c70ea9a5e996058bad45ebf0e97b02c9d827228a8f475f72e2",
"packages/ui/components/PageHeader.wrn": "59264dfb26a132fd3eb265c4374f9fcae781f30dd94a087c57e6e8256717cf14",
"packages/ui/components/Pagination.wrn": "9ceb74745b159150b015bbbb1974c68e14a7d4b92bd03491cb23b8855aa07983",
"packages/ui/components/PinInput.wrn": "5196f584de8d548a5dfa03688c3c95da3c948cead2662b05e927386ccea74299",
"packages/ui/components/Popover.wrn": "167f6c476cf3114ac5062ecf7739bddd9b5a81b1d209396a3675067dad1577b2",
"packages/ui/components/PortalDashboard.wrn": "037d4300b59c7d60543abc7d4aba5c738efc143e9b61abc48aad0ddfcfe6845b",
"packages/ui/components/PreferenceSwitcher.wrn": "2cc186d4dcb6580b4b152e3a265d9ed5ad210d3fdc76330b9092db21894465f0",
"packages/ui/components/PinInput.wrn": "2b656cf832c02fa9c28e2fe78bf1147ff05b17a9d8e744c33e1a63bad7c7ebb3",
"packages/ui/components/Popover.wrn": "f2996f00fad3e35fba2d48e70af17f592854eb73b9e8c6a83526aa78c4e41293",
"packages/ui/components/PortalDashboard.wrn": "79b54868f150a551cede0731cc5390a45ce22375f1929f2d537ce67a987bde50",
"packages/ui/components/PreferenceSwitcher.wrn": "241060fb24a16e795c53981506d273fe9110cbb250c73c85928c788c75d05ded",
"packages/ui/components/PublicPageShell.wrn": "369b74f65e27227311a2ed73e9ca56c8b60908100d6840a798e8ba5f2c8b1040",
"packages/ui/components/Radio.wrn": "0b2d38a5aee3e859280e4590fa1d509d789cc2239082054872fa342817735f1b",
"packages/ui/components/RangeSlider.wrn": "21eb7a5df0ee2cb5e78f628eb920265998eb9f1a4933d4e5c57db0323fd66b41",
"packages/ui/components/Rating.wrn": "a2d74dc748fc7d98892684c760518b87fa65411b1102e3611252b87245b3ffcb",
"packages/ui/components/RangeSlider.wrn": "eff9db1616d424f9c803185b4efe011942866acad3328179438a200210d97710",
"packages/ui/components/Rating.wrn": "41bbce2eb7fb6ad08a3aacde97d03af6e346824962148e7655743028c59185d3",
"packages/ui/components/Scrollspy.wrn": "76d1c17580c8d460f1222ce97585bdcac9779141f2f30d5460c84ff9f75413d8",
"packages/ui/components/SearchBox.wrn": "4a07358530ffb197d80d816b6ee4335a20812b6bee1e66a845c4075305bee6f7",
"packages/ui/components/SearchBox.wrn": "717f7647fabd762f8ed60a7c3306eac3ddb83c0da292a53c76d91886c2b8f94f",
"packages/ui/components/Section.wrn": "33eb8a82a2102229c2b1f64cec497e9e8e70b67adab8dbe396be860fccbb699c",
"packages/ui/components/SectionHeader.wrn": "8eecf2ea7a93139512260e3d46ca2a1afeb341b8bd46b7c30baa6e2c75e25d10",
"packages/ui/components/Select.wrn": "7f046bb11b7c2470dae26d91a4b2261c66a40ae054d0e04c69b6748912d1e204",
"packages/ui/components/Sidebar.wrn": "03f1bbce75ca6d50ed940e62df39c539610e89eadd2064fe24abc7d5470f98bf",
"packages/ui/components/SplitHero.wrn": "70e843565ff869bcf413b11b6d9830b2e2bd229863a00904596f71e2dff0e76d",
"packages/ui/components/StatsBar.wrn": "c7d1df3895f25b6d3a2e1012a18b97592c7f33e65418b880d486f20c2d490686",
"packages/ui/components/Stepper.wrn": "df9093e4222ef3250963a8c6fbfec74477242735d23eabe896c29731c54fc14d",
"packages/ui/components/StrongPassword.wrn": "c7c5ef26ece6170dd7db9882f0dc98cb2e4607f1e5d43eb3fd39e5d15bbd3a2e",
"packages/ui/components/StyledIcon.wrn": "4a4504e357dee9dd0418edbe85fc90b824ccdb3752123a2125da95b77bf0b336",
"packages/ui/components/Switch.wrn": "ccb74599fab72b0d68b09a7f1f90b7732cb2f9cbed67a84219e897575ce30c28",
"packages/ui/components/StrongPassword.wrn": "0df060bf01ace77ad0158df489588df75fd5d426dc04f000abacc2b3ddd93fc5",
"packages/ui/components/StyledIcon.wrn": "bf9b41e9c402c4886feddda504c3f12348bbbe49b9003de69d807db4bf52d1b1",
"packages/ui/components/Tabs.wrn": "e38336d28876caa4e97da32a569749e3803b030284a85ce9f35fa788e51cc5b0",
"packages/ui/components/TextLink.wrn": "30782039293eb36d63b7b3a4f32a71a47177a3a68c7e90184be7cf7b4385eb19",
"packages/ui/components/Textarea.wrn": "ddf0b4f124b2cf0c0ab3d820d3ac0085f7c20466e977231949be264cd0cee8cf",
"packages/ui/components/TimePicker.wrn": "2e8e7a90f6b6069a07e7ffd2725ba1e1031e84d55a4f1254025befbb314fa695",
"packages/ui/components/Timeline.wrn": "aec123bf69f6bfa41c3564e4eac18b2a245214dc6905ebd0bf5b69f7c2736ae6",
"packages/ui/components/Toast.wrn": "f37c584d1c1a66401deb53d915c8ee0c70aaf7baa1d3c297fa74c5bdb914dd1d",
"packages/ui/components/ToastNotifications.wrn": "33ff76b2a0a129ff896baea8979b4be97f7ec4471a92a2da970403a5c46e8b03",
"packages/ui/components/Toaster.wrn": "7481ecddde9ed5bf1f448d45a78da7ee0009f84963814bf681735db4e5ab75f9",
"packages/ui/components/ToggleCount.wrn": "17710fb37fc6953911acec4e80cd95c1202836e9b958ae4ce34c9a9639edae95",
"packages/ui/components/TogglePassword.wrn": "405a85cbfa3d0ff0185b52d2805fba88497a5f25501f01b2438ed3a28591a38d",
"packages/ui/components/Tooltip.wrn": "e6c8c14a75062d04a2e64245df1a40e67785240573c73bae4b0b32e76ef822bc",
"packages/ui/components/TreeView.wrn": "1791a10135595b17b5c746af9e0476e8520d516ba46d64c79dc4efe0af2b500d",
"packages/ui/components/TextLink.wrn": "6e18906337cd3d30c2a0122593d249bde8f7cedc697b90ae388e2bd0e8eff3e1",
"packages/ui/components/TimePicker.wrn": "d2fb7cd156bb2abb997a9e85d27c36b80f4ba81515b523a125e793363568475c",
"packages/ui/components/Timeline.wrn": "686a37ebc62732842846d004491336970db94945bca942f226097d863e159d1c",
"packages/ui/components/Toaster.wrn": "288b2b370f65bf823e8d2a6f8c8cf66972332b8174303d33ebe8546d5e1d52c4",
"packages/ui/components/ToggleCount.wrn": "285abf0ed5c7156c5a6f404af0c2519f181b591975515e845cbc3c21349c5cfd",
"packages/ui/components/TogglePassword.wrn": "94cd3579bb48e53283c82400bff58ea0f2ca92646b87385502e3b474ba787a96",
"packages/ui/components/TreeView.wrn": "c354fce9c5e81fed6eec4089dfd2a7f6bb84eb189c94efc88e00251efdc12c0b",
"packages/ui/components/Typography.wrn": "1b4d1b91dee0ff522d27c566becc3bc49bd9ec979ae6334e6a15fca5d88eccac",
"packages/ui/components/WysiwygEditor.wrn": "636e60b9f9be7a5807cca7ad20e0b0f370ee1ec6c1ecba5d0a577726cc86bc98",
"packages/ui/components/alert.wrn": "3f4c4c90c2b5a00a13d0fa1e7e48e4898869dd27a50ff412a51d7bcc87a0baac",
"packages/ui/components/avatar.wrn": "571b70790aff28a5b7d6adbf0196e77ece1ce153a95b384148cb2bb5f74ab2e9",
"packages/ui/components/button.wrn": "647af3918142fd9151e46555345532980633cc3677ec26d0940c253ea789bc31",
"packages/ui/components/carousel.wrn": "37bdde82de59e90cf02c9fed0ee11f5ddd2979246932701dad9412c4fd358a6a",
"packages/ui/components/progress.wrn": "ba6f4dfcc00f04f34e9533be675bb4a6200c3cdcd7d03fc597377e48520fdec7",
"packages/ui/components/skeleton.wrn": "ceec8af147b8be08155500e378393ac7c72d819da61b62191c076c80e943d5a9",
"packages/ui/components/spinner.wrn": "abc4ee3ede2e019289257e9009eee012c880a85839cc220fac6dbe1b780caf52",
"packages/ui/ui.css": "3138b86f269b6f7e67922ef2c56918444d562b11f4ae6f1fab98b63805cd78dc"
"packages/ui/components/WysiwygEditor.wrn": "231da25f4064b065adb2783252d430aa336585e008547a010a41174a41284524",
"packages/ui/components/alert.wrn": "6dde2cee818a66e1d80df6b93e9f971d5389f95387961593350b0c297e3ed367",
"packages/ui/components/avatar.wrn": "ac2239cc746ec35b81a34ec6e27f4ee74ca04dc6058bad5853aa66640f2a9426",
"packages/ui/components/badge.wrn": "9e1e884e9e2e9d881348c5121f53f4c85b91355cbfa49eeb2bff43f5ab38fab5",
"packages/ui/components/button.wrn": "da1660c671daf2feffe1024203ac1a8bc947beb07691bef595c1552c664b9cde",
"packages/ui/components/checkbox.wrn": "a5fb8410558696b3c8e63d5cd8dd49b44d51ed35b809cb2b189501e2e7b323e2",
"packages/ui/components/input.wrn": "184066a837982ef3fe44e518730d0cccb7dd6772d5af58116c0774b71cb894e2",
"packages/ui/components/progress.wrn": "61b01ba575d35691e3dfebfcb03ccd1c6b79f1df1c0d41857ec3b01944081a5d",
"packages/ui/components/radio.wrn": "cc65395aad6c0bac690c18cb7c36b1d2784e4c3284be3d3cb277b745f2f29bbc",
"packages/ui/components/select.wrn": "d58b454958edccce99f96c642eb67d5a0e64e2b97e58e742d445fe45164a7660",
"packages/ui/components/skeleton.wrn": "dda95a2c59c6060ab2b9f916eb046fbe74a68b398a051afc0bde41808e059c3a",
"packages/ui/components/spinner.wrn": "ffbd28ec6fc017fc94c67778f8966eab2eefa3b47f3f27f064c77dbeb65d84c1",
"packages/ui/components/switch.wrn": "917219d77b2089584bc444fe94f2cb0670f800d73f69ae70f0368bfece16be0c",
"packages/ui/components/textarea.wrn": "602ef2c8a081476f790408119b96ec2b51fb18abe76a43ee2b955dcb60debd17",
"packages/ui/components/tooltip.wrn": "40028dfbb17f6725c76c2e091af9c30ea4db6aa4dea919d38f53874ea70315d1",
"packages/ui/styles/ComponentBaseStyles.wrn": "566724a7bcc8f7caa067d5415177bf982ebae7b579a34052ce6f2092cbade4d7",
"packages/ui/styles/FieldStyles.wrn": "460fc8cbb0228151927fd6a61caceae0a148fdbc3009c13ba511ab0a4010b42d",
"packages/ui/styles/SelectStyles.wrn": "2c45e9c04678aaff76632e908b3c061410be0d6999a54a9203a39a3817d698f2",
"packages/ui/ui.css": "b7e2ae796ffbb738451364f63fce13022a81d81207f7e49c7f6220d24c36acd9"
}
}
+168 -87
View File
@@ -1,6 +1,6 @@
"use strict";
// Generated by scripts/build-editor-compiler.mjs. Do not edit directly.
// WRN editor compiler source hash: 451706f47a7734fd5808cdeeb5786e1b97a183afec98db99fc041369eb16a205
// WRN editor compiler source hash: 196a514b18fb6e31fdd8f4ca667a76b0b62cab547af33725ba78564d70bbc01b
// WRN editor compiler generator hash: c71e7fe4258c97b73b384ff14b321f0cf0b30cc2ed0322f5f84b04e757159b18
// Generated with TypeScript: 5.9.3
const __nodeRequire = require;
@@ -558,7 +558,7 @@ function browserModuleRequired(ast) {
const functions = ast.runtimeFunctions.filter((fn) => ["legacy", "client", "shared"].includes(fn.runtime));
return functions.length > 0 || selectedBrowserImports(ast, functions).length > 0;
}
function functionEntry(ast, fn, availableFunctions) {
function _functionEntry(ast, fn, availableFunctions) {
const parameterNames = new Set(fn.parameters.map((parameter) => parameter.name));
/*
* Names the function body declares for itself.
@@ -608,32 +608,42 @@ function functionEntry(ast, fn, availableFunctions) {
const syncStateFromContext = stateNames
.map((name) => `${name} = context.state.${name};`)
.join(" ");
const peerAliases = functionAliases
.map((name) => {
const call = `context.functions[${JSON.stringify(name)}](...__wrnexusPeerArgs)`;
if (!stateNames.length) {
return `const ${name} = (...__wrnexusPeerArgs) => ${call};`;
}
return `const ${name} = (...__wrnexusPeerArgs) => {
${syncStateToContext}
let __wrnexusPeerResult;
const peerAliases = !stateNames.length
? functionAliases
.map((name) => `const ${name} = (...__wrnexusPeerArgs) => context.functions[${JSON.stringify(name)}](...__wrnexusPeerArgs);`)
.join("\n")
: `const __wrnexusFlush = () => { ${syncStateToContext} };
const __wrnexusRestore = () => { ${syncStateFromContext} };
const __wrnexusPeer = (name, args) => {
__wrnexusFlush();
let result;
try {
__wrnexusPeerResult = ${call};
} catch (__wrnexusPeerError) {
${syncStateFromContext}
throw __wrnexusPeerError;
result = context.functions[name](...args);
} catch (error) {
__wrnexusRestore();
throw error;
}
if (__wrnexusPeerResult && typeof __wrnexusPeerResult.then === "function") {
return Promise.resolve(__wrnexusPeerResult).finally(() => { ${syncStateFromContext} });
if (result && typeof result.then === "function") {
return Promise.resolve(result).finally(__wrnexusRestore);
}
${syncStateFromContext}
return __wrnexusPeerResult;
};`;
})
.join("\n");
__wrnexusRestore();
return result;
};
${functionAliases.map((name) => `const ${name} = (...__wrnexusPeerArgs) => __wrnexusPeer(${JSON.stringify(name)}, __wrnexusPeerArgs);`).join("\n")}`;
const copyBack = stateNames
.map((name) => `if (!Object.is(${name}, __wrnexusInitialState[${JSON.stringify(name)}])) context.state.${name} = ${name};`)
.join("\n");
const commitBinding = stateNames.length
? `const __wrnexusCommit = () => { ${copyBack} };
${!parameterNames.has("commit") && !declaredLocals.has("commit") && !functionAliases.includes("commit") ? "const commit = __wrnexusCommit;" : ""}
${!parameterNames.has("setTimeout") &&
!declaredLocals.has("setTimeout") &&
!functionAliases.includes("setTimeout")
? `const setTimeout = (callback, delay, ...args) => globalThis.setTimeout(() => {
try { return callback(...args); } finally { __wrnexusCommit(); }
}, delay);`
: ""}`
: "";
const body = (0, syntax_1.eraseFunctionTypes)(fn.body);
const runtimeBindings = [
!parameterNames.has("output") ? "const output = context.output;" : "",
@@ -647,12 +657,13 @@ function functionEntry(ast, fn, availableFunctions) {
${initialStateSnapshot}
${stateAliases}
${propAliases}
${commitBinding}
${peerAliases}
${runtimeBindings}
try {
${body}
} finally {
${copyBack}
${stateNames.length ? "__wrnexusCommit();" : ""}
}
}`;
}
@@ -663,22 +674,74 @@ function generateBrowserModule(ast) {
const selectedImports = selectedBrowserImports(ast, functions);
const imports = selectedImports.map((entry) => entry.code).join("\n");
const importedBindings = [...new Set(selectedImports.flatMap((entry) => entry.bindings))];
const sharedState = state.filter((name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name));
const sharedProps = ast.props
.map((entry) => entry.name)
.filter((name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name) && !sharedState.includes(name));
const callableAliases = functionNames.filter((name) => safeIdentifier(name) &&
!RUNTIME_BINDINGS.has(name) &&
!sharedState.includes(name) &&
!sharedProps.includes(name));
const sharedCommit = sharedState.map((name) => `context.state.${name} = ${name};`).join(" ");
const sharedRestore = [
...sharedState.map((name) => `${name} = context.state.${name};`),
...sharedProps.map((name) => `${name} = context.props.${name};`),
].join(" ");
const hasAuthoredCommit = callableAliases.includes("commit");
const hasAuthoredSetTimeout = callableAliases.includes("setTimeout");
const implementations = functions
.map((fn) => {
const parameters = fn.parameters.map((parameter) => parameter.name).join(", ");
return `${JSON.stringify(fn.name)}: ${fn.async ? "async " : ""}function(${parameters}) {
try { ${(0, syntax_1.eraseFunctionTypes)(fn.body)} } finally { __wrnexusCommit(); }
}`;
})
.join(",\n");
return `// generated WRNexusJS browser module for ${ast.name}
${imports}
export const __wrnexusClientFunctions = {
${functions.map((fn) => ` ${functionEntry(ast, fn, functionNames)}`).join(",\n")}
${functions
.map((fn) => ` ${JSON.stringify(fn.name)}: (context, ...args) => __wrnexusBindings(context)[${JSON.stringify(fn.name)}](...args)`)
.join(",\n")}
};
export const __wrnexusClientState = ${JSON.stringify(state)};
export const __wrnexusOutputs = ${JSON.stringify(ast.outputs)};
export const __wrnexusImportedBindings = { ${importedBindings.join(", ")} };
export function bindClientScope(context) {
function __wrnexusCreateClientFunctions(context) {
${sharedState.length ? `let { ${sharedState.join(", ")} } = context.state;` : ""}
${sharedProps.length ? `let { ${sharedProps.join(", ")} } = context.props;` : ""}
const output = context.output;
const server = context.server;
const props = context.props;
const refs = context.refs;
const __wrnexusCommit = () => { ${sharedCommit} };
const __wrnexusRestore = () => { ${sharedRestore} };
${!hasAuthoredCommit ? "const commit = __wrnexusCommit;" : ""}
${!hasAuthoredSetTimeout
? `const setTimeout = (callback, delay, ...args) => globalThis.setTimeout(() => {
try { return callback(...args); } finally { __wrnexusCommit(); }
}, delay);`
: ""}
const implementations = {
${implementations}
};
${callableAliases.map((name) => `const ${name} = (...args) => implementations[${JSON.stringify(name)}](...args);`).join("\n ")}
const functions = {};
const scopedContext = { ...context, functions };
for (const [name, handler] of Object.entries(__wrnexusClientFunctions)) {
functions[name] = (...args) => handler(scopedContext, ...args);
for (const name of Object.keys(implementations)) {
functions[name] = (...args) => {
__wrnexusRestore();
return implementations[name](...args);
};
}
return functions;
}
function __wrnexusBindings(context) {
return context.__wrnexusBoundFunctions ||
(context.__wrnexusBoundFunctions = __wrnexusCreateClientFunctions(context));
}
export function bindClientScope(context) {
return __wrnexusBindings(context);
}
`;
}
@@ -1282,13 +1345,12 @@ function renderNestedComponentInvocation(node, ctx) {
? `\${__wireProp(${ctx.resolveExpr(wholeExpression)})}`
: compileAttrValue(attr.value, ctx);
const rendered = ` ${attr.name}="${compiledValue}"`;
if (wholeExpression ||
!attr.value.includes("{") ||
!exprRefsState(attr.value, ctx.stateNames)) {
if (!attr.value.includes("{") ||
(!exprRefsState(attr.value, ctx.stateNames) && !exprRefsState(attr.value, ctx.propNames))) {
return rendered;
}
const marker = attrEscape(JSON.stringify([attr.name, attr.value]));
return rendered + ` data-wrn-bind-${bindIndex++}="${escLit(marker)}"`;
return rendered + ` data-wrn-prop-bind-${bindIndex++}="${escLit(marker)}"`;
})
.join("");
const loops = loopVarsOf(node);
@@ -1392,7 +1454,12 @@ function __wrnexusEscapeHtml(value: unknown): string {
return String(value).replace(/[&<>"']/g, (ch) => __wrnexusHtmlEscapes[ch] ?? ch);
}
function __wrnexusEvalData(data: unknown, body: string, helpers = "", ctx: any): unknown {
type __WrnexusContext = import("@wrnexus/core").Context & {
__wrnexusCallApi?: (path: string, method: string) => Promise<unknown>;
localStorage?: unknown;
};
function __wrnexusEvalData(data: unknown, body: string, helpers = "", ctx: __WrnexusContext): unknown {
const adapters = {
cookies: ctx.cookies,
session: ctx.session,
@@ -1423,7 +1490,7 @@ function __wrnexusPropAttr(
);
}
async function __wrnexusCallApi(path: string, method: string, ctx: any): Promise<unknown> {
async function __wrnexusCallApi(path: string, method: string, ctx: __WrnexusContext): Promise<unknown> {
if (typeof ctx.__wrnexusCallApi === "function") {
return await ctx.__wrnexusCallApi(path, method);
}
@@ -1438,7 +1505,7 @@ async function __wrnexusCallApi(path: string, method: string, ctx: any): Promise
return type.includes("application/json") ? await res.json() : await res.text();
}
async function __wrnexusRenderSsrBindings(html: string, ctx: any): Promise<string> {
async function __wrnexusRenderSsrBindings(html: string, ctx: __WrnexusContext): Promise<string> {
for (const binding of __wrnexusSsrBindings) {
const data = await __wrnexusCallApi(binding.path, binding.method, ctx);
const value = __wrnexusEvalData(data, binding.body, binding.helpers, ctx);
@@ -1871,14 +1938,14 @@ function generate(ast) {
out.push(ssrRuntimeSource());
out.push(`const __wrnexusSsrBindings = ${JSON.stringify(ssrBindings, null, 2)};`);
const decls = loopConsts.length > 0 ? loopConsts.join("\n") + "\n" : "";
out.push(`export default async function ${ast.name}(ctx: any) {
out.push(`export default async function ${ast.name}(ctx: __WrnexusContext) {
${storeDeclarations}
${serverLoadAliases}
${decls}
const __state: ${stateType} = ${dynamicStateInitializer};
${ssrStateAliases}
${ssrComputedAliases}
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]]));
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)]));
const __scopeValue = Object.entries(__hydrationState)
.map(([key, value]) => {
let encoded: string;
@@ -1904,13 +1971,13 @@ function generate(ast) {
}`);
}
else {
out.push(`export default ${storeBindings.length > 0 ? "async " : ""}function ${ast.name}(ctx: any) {
out.push(`export default ${storeBindings.length > 0 ? "async " : ""}function ${ast.name}(ctx: import("@wrnexus/core").Context) {
${storeDeclarations}
${serverLoadAliases}
const __state: ${stateType} = ${dynamicStateInitializer};
${ssrStateAliases}
${ssrComputedAliases}
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]]));
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)]));
const __scopeValue = Object.entries(__hydrationState)
.map(([key, value]) => {
let encoded: string;
@@ -1934,14 +2001,14 @@ function generate(ast) {
}`);
}
if (staticShellBody !== undefined) {
out.push(`export async function __wrnexusBuildStaticShell(ctx: any = {}) {
out.push(`export async function __wrnexusBuildStaticShell(ctx: import("@wrnexus/core").Context = {} as import("@wrnexus/core").Context) {
${storeDeclarations}
${serverLoadAliases}
${loopConsts.length > 0 ? loopConsts.join("\n") : ""}
const __state: ${stateType} = ${dynamicStateInitializer};
${ssrStateAliases}
${ssrComputedAliases}
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]]));
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)]));
const __scopeValue = Object.entries(__hydrationState)
.map(([key, value]) => {
let encoded: string;
@@ -1991,7 +2058,7 @@ function generate(ast) {
})
.join("\n");
const visible = exposed.filter((entry) => entry.name);
return `export async function ${exportName}(ctx: any) {
return `export async function ${exportName}(ctx: import("@wrnexus/core").Context) {
${exposed
.filter((entry) => !entry.name)
.map((entry) => entry.body)
@@ -2014,7 +2081,7 @@ ${visible.length
out.push(`export async function ${action.name}(${action.args.join(", ")}) {${action.body}}`);
continue;
}
out.push(`export async function ${action.name}(input: any, ctx: any) {
out.push(`export async function ${action.name}(input: InferSchema<typeof ${action.schema}>, ctx: import("@wrnexus/core").Context) {
const invalidate = (...tags: string[]) => {
const bucket = (ctx.locals.__wrnexusInvalidatedTags ??= []);
bucket.push(...tags.flat());
@@ -2036,7 +2103,7 @@ ${ast.actions
ast.apis.forEach((api, index) => {
const name = `__wrnexusApi_${api.method}_${index}`;
out.push(`// ${api.method} ${apiRoutePath(api.path)}
const ${name} = async (ctx: any) => {${api.body}};`);
const ${name} = async (ctx: import("@wrnexus/core").Context) => {${api.body}};`);
});
const entries = ast.apis.map((api, index) => ` ${JSON.stringify(`${api.method} ${apiRoutePath(api.path)}`)}: __wrnexusApi_${api.method}_${index},`);
out.push(`export const __wrnexusApi = {\n${entries.join("\n")}\n};`);
@@ -2052,7 +2119,7 @@ const ${name} = async (ctx: any) => {${api.body}};`);
if (ast.realtimes.length > 0) {
const handlers = ast.realtimes.flatMap((rt) => rt.handlers.map((h) => {
const params = ["ws", ...h.args].join(", ");
return ` ${h.event}(${params}: any) {${h.body}},`;
return ` ${h.event}(${params}: Event) {${h.body}},`;
}));
out.push(`export const websocket = {\n${handlers.join("\n")}\n};`);
}
@@ -2178,7 +2245,9 @@ function exprRefsState(expr, stateNames) {
return false;
}
function exprRefsComponentReactiveValue(expr, ctx) {
return exprRefsState(expr, ctx.stateNames) || exprRefsState(expr, ctx.functionNames);
return (exprRefsState(expr, ctx.stateNames) ||
exprRefsState(expr, ctx.propNames) ||
exprRefsState(expr, ctx.functionNames));
}
function viewHasEvents(nodes) {
return nodes.some((node) => {
@@ -2242,6 +2311,9 @@ function compileText(raw, ctx) {
// list renderer fills it per item; it has no server-side value.
out += escLit(`{${expr}}`);
}
else if (expr === "content") {
out += `\${__wireRaw(${ctx.resolveExpr(expr)})}`;
}
else if (exprRefsComponentReactiveValue(expr, ctx)) {
// State interpolation: bake the initial value AND keep it reactive via a
// data-text span, so no-JS clients see the real value and hydration
@@ -2251,9 +2323,6 @@ function compileText(raw, ctx) {
`\${__wireHtml(${ctx.resolveExpr(expr)})}` +
escLit(`</span>`);
}
else if (expr === "content") {
out += `\${__wireRaw(${ctx.resolveExpr(expr)})}`;
}
else {
out += `\${__wireHtml(${ctx.resolveExpr(expr)})}`;
}
@@ -2593,6 +2662,7 @@ function generateComponent(ast) {
};
const ctx = {
stateNames,
propNames: new Set(effectiveProps.map((entry) => entry.name)),
functionNames: new Set(ast.runtimeFunctions.filter((fn) => fn.runtime !== "server").map((fn) => fn.name)),
resolveExpr,
eventNames: publicOutputNames(ast),
@@ -2610,12 +2680,12 @@ function generateComponent(ast) {
.join("");
const styles = ast.styles.map((body) => body.trim()).filter(Boolean);
const styleTag = escLit(localStyleTag(ast, styles));
// A component needs a reactive scope only when it has state or event handlers.
// Prop-driven text/attributes are baked server-side, so static components ship
// no JavaScript at all.
// Props remain server-rendered and also become signals so a parent can drive
// a mounted child after hydration.
const behavior = componentBehavior(ast);
const needsScope = ast.runtime !== "server" &&
(browserStates.length > 0 ||
(effectiveProps.length > 0 ||
browserStates.length > 0 ||
ast.computed.length > 0 ||
viewHasEvents(ast.view) ||
behavior !== null);
@@ -2632,7 +2702,7 @@ function generateComponent(ast) {
if (prop.required) {
decls.push(` if (__p[${JSON.stringify(prop.name)}] === undefined) throw new TypeError(${JSON.stringify(`${ast.name} requires prop '${prop.name}' (${prop.valueType ?? "unknown"})`)});`);
}
decls.push(` const ${nameRefs.get(prop.name)}: ${prop.valueType ?? "any"} = __coerce(__p[${JSON.stringify(prop.name)}], (${resolveExpr(prop.default)}), ${JSON.stringify((0, types_ts_1.runtimeTypeOf)(prop.valueType))});`);
decls.push(` const ${nameRefs.get(prop.name)}: ${prop.valueType ?? "unknown"} = __coerce(__p[${JSON.stringify(prop.name)}], (${resolveExpr(prop.default)}), ${JSON.stringify((0, types_ts_1.runtimeTypeOf)(prop.valueType))}, ${JSON.stringify(prop.name)}) as ${prop.valueType ?? "unknown"};`);
}
if (!effectiveProps.some((prop) => prop.name === "attrs")) {
decls.push(` const __attrs = __restProps(__p, new Set(${JSON.stringify(effectiveProps.map((prop) => prop.name))}));`);
@@ -2697,7 +2767,7 @@ function generateComponent(ast) {
.map((output) => ` ${JSON.stringify(output.name)}(${output.payload ? `${output.payload.name}${output.payload.optional ? "?" : ""}: ${output.payload.valueType}` : ""}): void;`)
.join("\n")}\n}`);
}
out.push(`function __coerce(v: any, def: any, declared: string = "unknown"): any {
out.push(`function __coerce(v: unknown, def: unknown, declared: string = "unknown", propName: string = "prop"): unknown {
if (v === undefined || v === null) {
return def;
}
@@ -2722,14 +2792,14 @@ function generateComponent(ast) {
if (typeof v === "string") {
try {
const parsed = JSON.parse(v);
return Array.isArray(parsed) ? parsed : def;
if (!Array.isArray(parsed)) throw new TypeError("Expected an array prop '" + propName + "'");
return parsed;
} catch {
if (declared === "array") throw new TypeError("Expected an array prop");
return def;
throw new TypeError("Expected an array prop '" + propName + "'");
}
}
return def;
throw new TypeError("Expected an array prop '" + propName + "'");
}
if (declared === "object" || (def !== null && typeof def === "object")) {
@@ -2745,23 +2815,19 @@ function generateComponent(ast) {
try {
const parsed = JSON.parse(v);
return (
parsed !== null &&
typeof parsed === "object" &&
!Array.isArray(parsed)
)
? parsed
: def;
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
throw new TypeError("Expected an object prop '" + propName + "'");
}
return parsed;
} catch {
if (declared === "object") throw new TypeError("Expected an object prop");
return def;
throw new TypeError("Expected an object prop '" + propName + "'");
}
}
return def;
throw new TypeError("Expected an object prop '" + propName + "'");
}
if (declared === "bigint") return BigInt(v);
if (declared === "bigint") return BigInt(v as string | number | bigint | boolean);
if (declared === "function" && typeof v !== "function") {
throw new TypeError("Expected a function prop");
}
@@ -2769,15 +2835,15 @@ function generateComponent(ast) {
}
function __restProps(
props: Record<string, any>,
props: Record<string, unknown>,
declared: Set<string>,
): Record<string, any> {
): Record<string, unknown> {
return Object.fromEntries(
Object.entries(props).filter(([name]) => !declared.has(name)),
);
}
function __wireHtml(v: any): string {
function __wireHtml(v: unknown): string {
return String(v == null ? "" : v).replace(
/[&<>]/g,
(c) =>
@@ -2789,7 +2855,7 @@ function __wireHtml(v: any): string {
);
}
function __wireAttr(v: any): string {
function __wireAttr(v: unknown): string {
return String(v == null ? "" : v).replace(
/[&<>"]/g,
(c) =>
@@ -2803,7 +2869,7 @@ function __wireAttr(v: any): string {
);
}
function __wireBooleanAttr(name: string, value: any): string {
function __wireBooleanAttr(name: string, value: unknown): string {
return value === true ||
value === "true" ||
value === "" ||
@@ -2814,7 +2880,7 @@ function __wireBooleanAttr(name: string, value: any): string {
: "";
}
function __wireSpreadAttrs(value: any): string {
function __wireSpreadAttrs(value: unknown): string {
if (value === null || typeof value !== "object" || Array.isArray(value)) return "";
const booleanAttributes = new Set(${JSON.stringify([...HTML_BOOLEAN_ATTRIBUTES])});
@@ -2828,10 +2894,12 @@ function __wireSpreadAttrs(value: any): string {
lowerName === "style" ||
lowerName === "slot" ||
lowerName === "data-component" ||
// Internal markers must not leak through a spread -- except the
// parent's output handlers, whose whole job is to ride from the mount
// onto the view root so the mounting scope can bind them there.
(lowerName.startsWith("data-wrn") && !lowerName.startsWith("data-wrn-out-"))
// Internal markers must not leak through a spread. Parent-owned output
// and prop bindings ride from the mount onto the
// rendered child root so the mounting scope can bind them there.
(lowerName.startsWith("data-wrn") &&
!lowerName.startsWith("data-wrn-out-") &&
!lowerName.startsWith("data-wrn-prop-bind-"))
) {
continue;
}
@@ -2848,7 +2916,7 @@ function __wireSpreadAttrs(value: any): string {
return attributes.join("");
}
function __wireProp(v: any): string {
function __wireProp(v: unknown): string {
const value =
v !== null && typeof v === "object"
? JSON.stringify(v)
@@ -2857,16 +2925,16 @@ function __wireProp(v: any): string {
return __wireAttr(value);
}
function __wireRaw(v: any): string {
function __wireRaw(v: unknown): string {
return String(v == null ? "" : v);
}`);
if (hasServerEach) {
out.push(`function __wrnexusEncodeLoopLocals(value: Record<string, any>): string {
out.push(`function __wrnexusEncodeLoopLocals(value: Record<string, unknown>): string {
return __WrnexusBuffer.from(JSON.stringify(value), "utf8").toString("base64");
}`);
}
if (needsScope) {
out.push(`function __wrnexusSerializeScopeValue(value: any): string {
out.push(`function __wrnexusSerializeScopeValue(value: unknown): string {
if (value === undefined) {
return "undefined";
}
@@ -2900,7 +2968,7 @@ function __wireRaw(v: any): string {
}
}
function __wrnexusScopeDecl(obj: Record<string, any>): string {
function __wrnexusScopeDecl(obj: Record<string, unknown>): string {
return Object.keys(obj)
.map(
(key) =>
@@ -2918,7 +2986,7 @@ function __wireRaw(v: any): string {
}`);
}
const serverFunctionSource = serverFunctions ? `${serverFunctions}\n` : "";
out.push(`export function render(props: ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, any>"} = {} as ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, any>"}): string {\n` +
out.push(`export function render(props: ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, unknown>"} = {} as ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, unknown>"}): string {\n` +
` const __p = props || {};\n` +
(decls.length > 0 ? decls.join("\n") + "\n" : "") +
serverFunctionSource +
@@ -5634,7 +5702,12 @@ function parse(source) {
// props { name: Type = <default>; @event name = function }
lx.next();
expect("lbrace");
while (lx.peek().type !== "rbrace") {
while (true) {
if (lx.startsWithBlockComment()) {
throw new ParseError("Block comments are not allowed inside props {}; use // line comments instead", "WRN-PROPS-BLOCK-COMMENT");
}
if (lx.peek().type === "rbrace")
break;
const t = lx.peek();
if (t.type === "eof")
throw new ParseError("Unexpected end of input inside props");
@@ -6013,6 +6086,9 @@ function parse(source) {
}
expect("rbrace");
const declaredStates = new Set(states.map((state) => state.name));
if (declaredStates.has("page")) {
throw new ParseError("State name 'page' collides with the WRN 'page' keyword; choose another state name", "WRN-STATE-RESERVED-NAME");
}
for (const watcher of watches) {
if (!declaredStates.has(watcher.state)) {
throw new ParseError(`Cannot watch undeclared state '${watcher.state}'`);
@@ -6543,6 +6619,11 @@ class Lexer {
break;
}
}
/** True when the next non-trivia characters open a block comment. */
startsWithBlockComment() {
this.skipTrivia();
return this.src[this.pos] === "/" && this.src[this.pos + 1] === "*";
}
/** Read and consume the next structural token. */
next() {
this.skipTrivia();
+143 -80
View File
@@ -1,4 +1,4 @@
// WRN editor extension source hash: 6165420f37af57e441086efeb6b25528f72e77a39abb2c76ca5bc8748dd90c2f
// WRN editor extension source hash: 47188196a996fce0b8f1e2a4302e563767451d9b56df885abc60e9c925653b21
// WRN editor extension generator hash: 456d1d614e44e5fb1f19b784176c09cf2ade9b64ef73a17934c2698150b62728
"use strict";
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
@@ -23202,7 +23202,7 @@ ${(0, codegen_ts_1.generate)(ast)}`,
const functions = ast.runtimeFunctions.filter((fn) => ["legacy", "client", "shared"].includes(fn.runtime));
return functions.length > 0 || selectedBrowserImports(ast, functions).length > 0;
}
function functionEntry(ast, fn, availableFunctions) {
function _functionEntry(ast, fn, availableFunctions) {
const parameterNames = new Set(fn.parameters.map((parameter) => parameter.name));
const declaredLocals = new Set;
for (const match of fn.body.matchAll(/\b(?:var|let|const)\s+([A-Za-z_$][\w$]*)|\bfunction\s+([A-Za-z_$][\w$]*)/g)) {
@@ -23220,30 +23220,33 @@ ${(0, codegen_ts_1.generate)(ast)}`,
const propAliases = propNames.length ? `const { ${propNames.join(", ")} } = context.props;` : "";
const syncStateToContext = stateNames.map((name) => `context.state.${name} = ${name};`).join(" ");
const syncStateFromContext = stateNames.map((name) => `${name} = context.state.${name};`).join(" ");
const peerAliases = functionAliases.map((name) => {
const call = `context.functions[${JSON.stringify(name)}](...__wrnexusPeerArgs)`;
if (!stateNames.length) {
return `const ${name} = (...__wrnexusPeerArgs) => ${call};`;
}
return `const ${name} = (...__wrnexusPeerArgs) => {
${syncStateToContext}
let __wrnexusPeerResult;
const peerAliases = !stateNames.length ? functionAliases.map((name) => `const ${name} = (...__wrnexusPeerArgs) => context.functions[${JSON.stringify(name)}](...__wrnexusPeerArgs);`).join(`
`) : `const __wrnexusFlush = () => { ${syncStateToContext} };
const __wrnexusRestore = () => { ${syncStateFromContext} };
const __wrnexusPeer = (name, args) => {
__wrnexusFlush();
let result;
try {
__wrnexusPeerResult = ${call};
} catch (__wrnexusPeerError) {
${syncStateFromContext}
throw __wrnexusPeerError;
result = context.functions[name](...args);
} catch (error) {
__wrnexusRestore();
throw error;
}
if (__wrnexusPeerResult && typeof __wrnexusPeerResult.then === "function") {
return Promise.resolve(__wrnexusPeerResult).finally(() => { ${syncStateFromContext} });
if (result && typeof result.then === "function") {
return Promise.resolve(result).finally(__wrnexusRestore);
}
${syncStateFromContext}
return __wrnexusPeerResult;
};`;
}).join(`
`);
__wrnexusRestore();
return result;
};
${functionAliases.map((name) => `const ${name} = (...__wrnexusPeerArgs) => __wrnexusPeer(${JSON.stringify(name)}, __wrnexusPeerArgs);`).join(`
`)}`;
const copyBack = stateNames.map((name) => `if (!Object.is(${name}, __wrnexusInitialState[${JSON.stringify(name)}])) context.state.${name} = ${name};`).join(`
`);
const commitBinding = stateNames.length ? `const __wrnexusCommit = () => { ${copyBack} };
${!parameterNames.has("commit") && !declaredLocals.has("commit") && !functionAliases.includes("commit") ? "const commit = __wrnexusCommit;" : ""}
${!parameterNames.has("setTimeout") && !declaredLocals.has("setTimeout") && !functionAliases.includes("setTimeout") ? `const setTimeout = (callback, delay, ...args) => globalThis.setTimeout(() => {
try { return callback(...args); } finally { __wrnexusCommit(); }
}, delay);` : ""}` : "";
const body = (0, syntax_1.eraseFunctionTypes)(fn.body);
const runtimeBindings = [
!parameterNames.has("output") ? "const output = context.output;" : "",
@@ -23256,12 +23259,13 @@ ${(0, codegen_ts_1.generate)(ast)}`,
${initialStateSnapshot}
${stateAliases}
${propAliases}
${commitBinding}
${peerAliases}
${runtimeBindings}
try {
${body}
} finally {
${copyBack}
${stateNames.length ? "__wrnexusCommit();" : ""}
}
}`;
}
@@ -23273,23 +23277,66 @@ ${(0, codegen_ts_1.generate)(ast)}`,
const imports = selectedImports.map((entry) => entry.code).join(`
`);
const importedBindings = [...new Set(selectedImports.flatMap((entry) => entry.bindings))];
const sharedState = state.filter((name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name));
const sharedProps = ast.props.map((entry) => entry.name).filter((name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name) && !sharedState.includes(name));
const callableAliases = functionNames.filter((name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name) && !sharedState.includes(name) && !sharedProps.includes(name));
const sharedCommit = sharedState.map((name) => `context.state.${name} = ${name};`).join(" ");
const sharedRestore = [
...sharedState.map((name) => `${name} = context.state.${name};`),
...sharedProps.map((name) => `${name} = context.props.${name};`)
].join(" ");
const hasAuthoredCommit = callableAliases.includes("commit");
const hasAuthoredSetTimeout = callableAliases.includes("setTimeout");
const implementations = functions.map((fn) => {
const parameters = fn.parameters.map((parameter) => parameter.name).join(", ");
return `${JSON.stringify(fn.name)}: ${fn.async ? "async " : ""}function(${parameters}) {
try { ${(0, syntax_1.eraseFunctionTypes)(fn.body)} } finally { __wrnexusCommit(); }
}`;
}).join(`,
`);
return `// generated WRNexusJS browser module for ${ast.name}
${imports}
export const __wrnexusClientFunctions = {
${functions.map((fn) => ` ${functionEntry(ast, fn, functionNames)}`).join(`,
${functions.map((fn) => ` ${JSON.stringify(fn.name)}: (context, ...args) => __wrnexusBindings(context)[${JSON.stringify(fn.name)}](...args)`).join(`,
`)}
};
export const __wrnexusClientState = ${JSON.stringify(state)};
export const __wrnexusOutputs = ${JSON.stringify(ast.outputs)};
export const __wrnexusImportedBindings = { ${importedBindings.join(", ")} };
export function bindClientScope(context) {
function __wrnexusCreateClientFunctions(context) {
${sharedState.length ? `let { ${sharedState.join(", ")} } = context.state;` : ""}
${sharedProps.length ? `let { ${sharedProps.join(", ")} } = context.props;` : ""}
const output = context.output;
const server = context.server;
const props = context.props;
const refs = context.refs;
const __wrnexusCommit = () => { ${sharedCommit} };
const __wrnexusRestore = () => { ${sharedRestore} };
${!hasAuthoredCommit ? "const commit = __wrnexusCommit;" : ""}
${!hasAuthoredSetTimeout ? `const setTimeout = (callback, delay, ...args) => globalThis.setTimeout(() => {
try { return callback(...args); } finally { __wrnexusCommit(); }
}, delay);` : ""}
const implementations = {
${implementations}
};
${callableAliases.map((name) => `const ${name} = (...args) => implementations[${JSON.stringify(name)}](...args);`).join(`
`)}
const functions = {};
const scopedContext = { ...context, functions };
for (const [name, handler] of Object.entries(__wrnexusClientFunctions)) {
functions[name] = (...args) => handler(scopedContext, ...args);
for (const name of Object.keys(implementations)) {
functions[name] = (...args) => {
__wrnexusRestore();
return implementations[name](...args);
};
}
return functions;
}
function __wrnexusBindings(context) {
return context.__wrnexusBoundFunctions ||
(context.__wrnexusBoundFunctions = __wrnexusCreateClientFunctions(context));
}
export function bindClientScope(context) {
return __wrnexusBindings(context);
}
`;
}
},
@@ -23719,11 +23766,11 @@ export function bindClientScope(context) {
const wholeExpression = wholeAttributeExpression(attr.value);
const compiledValue = wholeExpression ? `\${__wireProp(${ctx.resolveExpr(wholeExpression)})}` : compileAttrValue(attr.value, ctx);
const rendered = ` ${attr.name}="${compiledValue}"`;
if (wholeExpression || !attr.value.includes("{") || !exprRefsState(attr.value, ctx.stateNames)) {
if (!attr.value.includes("{") || !exprRefsState(attr.value, ctx.stateNames) && !exprRefsState(attr.value, ctx.propNames)) {
return rendered;
}
const marker = attrEscape(JSON.stringify([attr.name, attr.value]));
return rendered + ` data-wrn-bind-${bindIndex++}="${escLit(marker)}"`;
return rendered + ` data-wrn-prop-bind-${bindIndex++}="${escLit(marker)}"`;
}).join("");
const loops = loopVarsOf(node);
const childCtx = loops.length > 0 ? {
@@ -23816,7 +23863,12 @@ function __wrnexusEscapeHtml(value: unknown): string {
return String(value).replace(/[&<>"']/g, (ch) => __wrnexusHtmlEscapes[ch] ?? ch);
}
function __wrnexusEvalData(data: unknown, body: string, helpers = "", ctx: any): unknown {
type __WrnexusContext = import("@wrnexus/core").Context & {
__wrnexusCallApi?: (path: string, method: string) => Promise<unknown>;
localStorage?: unknown;
};
function __wrnexusEvalData(data: unknown, body: string, helpers = "", ctx: __WrnexusContext): unknown {
const adapters = {
cookies: ctx.cookies,
session: ctx.session,
@@ -23847,7 +23899,7 @@ function __wrnexusPropAttr(
);
}
async function __wrnexusCallApi(path: string, method: string, ctx: any): Promise<unknown> {
async function __wrnexusCallApi(path: string, method: string, ctx: __WrnexusContext): Promise<unknown> {
if (typeof ctx.__wrnexusCallApi === "function") {
return await ctx.__wrnexusCallApi(path, method);
}
@@ -23862,7 +23914,7 @@ async function __wrnexusCallApi(path: string, method: string, ctx: any): Promise
return type.includes("application/json") ? await res.json() : await res.text();
}
async function __wrnexusRenderSsrBindings(html: string, ctx: any): Promise<string> {
async function __wrnexusRenderSsrBindings(html: string, ctx: __WrnexusContext): Promise<string> {
for (const binding of __wrnexusSsrBindings) {
const data = await __wrnexusCallApi(binding.path, binding.method, ctx);
const value = __wrnexusEvalData(data, binding.body, binding.helpers, ctx);
@@ -24253,14 +24305,14 @@ ${helpers}`);
const decls = loopConsts.length > 0 ? loopConsts.join(`
`) + `
` : "";
out.push(`export default async function ${ast.name}(ctx: any) {
out.push(`export default async function ${ast.name}(ctx: __WrnexusContext) {
${storeDeclarations}
${serverLoadAliases}
${decls}
const __state: ${stateType} = ${dynamicStateInitializer};
${ssrStateAliases}
${ssrComputedAliases}
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]]));
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)]));
const __scopeValue = Object.entries(__hydrationState)
.map(([key, value]) => {
let encoded: string;
@@ -24285,13 +24337,13 @@ ${helpers}`);
return await __wrnexusRenderSsrBindings(html, ctx);
}`);
} else {
out.push(`export default ${storeBindings.length > 0 ? "async " : ""}function ${ast.name}(ctx: any) {
out.push(`export default ${storeBindings.length > 0 ? "async " : ""}function ${ast.name}(ctx: import("@wrnexus/core").Context) {
${storeDeclarations}
${serverLoadAliases}
const __state: ${stateType} = ${dynamicStateInitializer};
${ssrStateAliases}
${ssrComputedAliases}
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]]));
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)]));
const __scopeValue = Object.entries(__hydrationState)
.map(([key, value]) => {
let encoded: string;
@@ -24315,7 +24367,7 @@ ${helpers}`);
}`);
}
if (staticShellBody !== undefined) {
out.push(`export async function __wrnexusBuildStaticShell(ctx: any = {}) {
out.push(`export async function __wrnexusBuildStaticShell(ctx: import("@wrnexus/core").Context = {} as import("@wrnexus/core").Context) {
${storeDeclarations}
${serverLoadAliases}
${loopConsts.length > 0 ? loopConsts.join(`
@@ -24323,7 +24375,7 @@ ${helpers}`);
const __state: ${stateType} = ${dynamicStateInitializer};
${ssrStateAliases}
${ssrComputedAliases}
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]]));
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)]));
const __scopeValue = Object.entries(__hydrationState)
.map(([key, value]) => {
let encoded: string;
@@ -24370,7 +24422,7 @@ ${helpers}`);
}).join(`
`);
const visible = exposed.filter((entry) => entry.name);
return `export async function ${exportName}(ctx: any) {
return `export async function ${exportName}(ctx: import("@wrnexus/core").Context) {
${exposed.filter((entry) => !entry.name).map((entry) => entry.body).join(`
`)}
${declarations}
@@ -24389,7 +24441,7 @@ ${visible.length ? ` const __values = await Promise.all([${visible.map((entry)
out.push(`export async function ${action.name}(${action.args.join(", ")}) {${action.body}}`);
continue;
}
out.push(`export async function ${action.name}(input: any, ctx: any) {
out.push(`export async function ${action.name}(input: InferSchema<typeof ${action.schema}>, ctx: import("@wrnexus/core").Context) {
const invalidate = (...tags: string[]) => {
const bucket = (ctx.locals.__wrnexusInvalidatedTags ??= []);
bucket.push(...tags.flat());
@@ -24407,7 +24459,7 @@ ${ast.actions.map((action) => ` ${action.name}: createActionClient<${action.sch
ast.apis.forEach((api, index) => {
const name = `__wrnexusApi_${api.method}_${index}`;
out.push(`// ${api.method} ${apiRoutePath(api.path)}
const ${name} = async (ctx: any) => {${api.body}};`);
const ${name} = async (ctx: import("@wrnexus/core").Context) => {${api.body}};`);
});
const entries = ast.apis.map((api, index) => ` ${JSON.stringify(`${api.method} ${apiRoutePath(api.path)}`)}: __wrnexusApi_${api.method}_${index},`);
out.push(`export const __wrnexusApi = {
@@ -24425,7 +24477,7 @@ ${entries.join(`
if (ast.realtimes.length > 0) {
const handlers = ast.realtimes.flatMap((rt) => rt.handlers.map((h) => {
const params = ["ws", ...h.args].join(", ");
return ` ${h.event}(${params}: any) {${h.body}},`;
return ` ${h.event}(${params}: Event) {${h.body}},`;
}));
out.push(`export const websocket = {
${handlers.join(`
@@ -24547,7 +24599,7 @@ ${handlers.join(`
return false;
}
function exprRefsComponentReactiveValue(expr, ctx) {
return exprRefsState(expr, ctx.stateNames) || exprRefsState(expr, ctx.functionNames);
return exprRefsState(expr, ctx.stateNames) || exprRefsState(expr, ctx.propNames) || exprRefsState(expr, ctx.functionNames);
}
function viewHasEvents(nodes) {
return nodes.some((node) => {
@@ -24599,10 +24651,10 @@ ${handlers.join(`
out += escLit(`<span data-t="${attrEscape(expr.slice(2).trim())}"></span>`);
} else if (ctx.loopVars && exprRefsState(expr, ctx.loopVars)) {
out += escLit(`{${expr}}`);
} else if (exprRefsComponentReactiveValue(expr, ctx)) {
out += escLit(`<span data-text="${attrEscape(expr)}">`) + `\${__wireHtml(${ctx.resolveExpr(expr)})}` + escLit(`</span>`);
} else if (expr === "content") {
out += `\${__wireRaw(${ctx.resolveExpr(expr)})}`;
} else if (exprRefsComponentReactiveValue(expr, ctx)) {
out += escLit(`<span data-text="${attrEscape(expr)}">`) + `\${__wireHtml(${ctx.resolveExpr(expr)})}` + escLit(`</span>`);
} else {
out += `\${__wireHtml(${ctx.resolveExpr(expr)})}`;
}
@@ -24855,6 +24907,7 @@ ${handlers.join(`
};
const ctx = {
stateNames,
propNames: new Set(effectiveProps.map((entry) => entry.name)),
functionNames: new Set(ast.runtimeFunctions.filter((fn) => fn.runtime !== "server").map((fn) => fn.name)),
resolveExpr,
eventNames: publicOutputNames(ast)
@@ -24867,7 +24920,7 @@ ${handlers.join(`
const styles = ast.styles.map((body) => body.trim()).filter(Boolean);
const styleTag = escLit(localStyleTag(ast, styles));
const behavior = componentBehavior(ast);
const needsScope = ast.runtime !== "server" && (browserStates.length > 0 || ast.computed.length > 0 || viewHasEvents(ast.view) || behavior !== null);
const needsScope = ast.runtime !== "server" && (effectiveProps.length > 0 || browserStates.length > 0 || ast.computed.length > 0 || viewHasEvents(ast.view) || behavior !== null);
if (hasServerEach || needsScope) {
out.push(`import { Buffer as __WrnexusBuffer } from "node:buffer";`);
}
@@ -24881,7 +24934,7 @@ ${handlers.join(`
if (prop.required) {
decls.push(` if (__p[${JSON.stringify(prop.name)}] === undefined) throw new TypeError(${JSON.stringify(`${ast.name} requires prop '${prop.name}' (${prop.valueType ?? "unknown"})`)});`);
}
decls.push(` const ${nameRefs.get(prop.name)}: ${prop.valueType ?? "any"} = __coerce(__p[${JSON.stringify(prop.name)}], (${resolveExpr(prop.default)}), ${JSON.stringify((0, types_ts_1.runtimeTypeOf)(prop.valueType))});`);
decls.push(` const ${nameRefs.get(prop.name)}: ${prop.valueType ?? "unknown"} = __coerce(__p[${JSON.stringify(prop.name)}], (${resolveExpr(prop.default)}), ${JSON.stringify((0, types_ts_1.runtimeTypeOf)(prop.valueType))}, ${JSON.stringify(prop.name)}) as ${prop.valueType ?? "unknown"};`);
}
if (!effectiveProps.some((prop) => prop.name === "attrs")) {
decls.push(` const __attrs = __restProps(__p, new Set(${JSON.stringify(effectiveProps.map((prop) => prop.name))}));`);
@@ -24941,7 +24994,7 @@ ${ast.outputs.map((output) => ` ${JSON.stringify(output.name)}(${output.payload
`)}
}`);
}
out.push(`function __coerce(v: any, def: any, declared: string = "unknown"): any {
out.push(`function __coerce(v: unknown, def: unknown, declared: string = "unknown", propName: string = "prop"): unknown {
if (v === undefined || v === null) {
return def;
}
@@ -24966,14 +25019,14 @@ ${ast.outputs.map((output) => ` ${JSON.stringify(output.name)}(${output.payload
if (typeof v === "string") {
try {
const parsed = JSON.parse(v);
return Array.isArray(parsed) ? parsed : def;
if (!Array.isArray(parsed)) throw new TypeError("Expected an array prop '" + propName + "'");
return parsed;
} catch {
if (declared === "array") throw new TypeError("Expected an array prop");
return def;
throw new TypeError("Expected an array prop '" + propName + "'");
}
}
return def;
throw new TypeError("Expected an array prop '" + propName + "'");
}
if (declared === "object" || (def !== null && typeof def === "object")) {
@@ -24989,23 +25042,19 @@ ${ast.outputs.map((output) => ` ${JSON.stringify(output.name)}(${output.payload
try {
const parsed = JSON.parse(v);
return (
parsed !== null &&
typeof parsed === "object" &&
!Array.isArray(parsed)
)
? parsed
: def;
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
throw new TypeError("Expected an object prop '" + propName + "'");
}
return parsed;
} catch {
if (declared === "object") throw new TypeError("Expected an object prop");
return def;
throw new TypeError("Expected an object prop '" + propName + "'");
}
}
return def;
throw new TypeError("Expected an object prop '" + propName + "'");
}
if (declared === "bigint") return BigInt(v);
if (declared === "bigint") return BigInt(v as string | number | bigint | boolean);
if (declared === "function" && typeof v !== "function") {
throw new TypeError("Expected a function prop");
}
@@ -25013,15 +25062,15 @@ ${ast.outputs.map((output) => ` ${JSON.stringify(output.name)}(${output.payload
}
function __restProps(
props: Record<string, any>,
props: Record<string, unknown>,
declared: Set<string>,
): Record<string, any> {
): Record<string, unknown> {
return Object.fromEntries(
Object.entries(props).filter(([name]) => !declared.has(name)),
);
}
function __wireHtml(v: any): string {
function __wireHtml(v: unknown): string {
return String(v == null ? "" : v).replace(
/[&<>]/g,
(c) =>
@@ -25033,7 +25082,7 @@ function __wireHtml(v: any): string {
);
}
function __wireAttr(v: any): string {
function __wireAttr(v: unknown): string {
return String(v == null ? "" : v).replace(
/[&<>"]/g,
(c) =>
@@ -25047,7 +25096,7 @@ function __wireAttr(v: any): string {
);
}
function __wireBooleanAttr(name: string, value: any): string {
function __wireBooleanAttr(name: string, value: unknown): string {
return value === true ||
value === "true" ||
value === "" ||
@@ -25058,7 +25107,7 @@ function __wireBooleanAttr(name: string, value: any): string {
: "";
}
function __wireSpreadAttrs(value: any): string {
function __wireSpreadAttrs(value: unknown): string {
if (value === null || typeof value !== "object" || Array.isArray(value)) return "";
const booleanAttributes = new Set(${JSON.stringify([...HTML_BOOLEAN_ATTRIBUTES])});
@@ -25072,10 +25121,12 @@ function __wireSpreadAttrs(value: any): string {
lowerName === "style" ||
lowerName === "slot" ||
lowerName === "data-component" ||
// Internal markers must not leak through a spread -- except the
// parent's output handlers, whose whole job is to ride from the mount
// onto the view root so the mounting scope can bind them there.
(lowerName.startsWith("data-wrn") && !lowerName.startsWith("data-wrn-out-"))
// Internal markers must not leak through a spread. Parent-owned output
// and prop bindings ride from the mount onto the
// rendered child root so the mounting scope can bind them there.
(lowerName.startsWith("data-wrn") &&
!lowerName.startsWith("data-wrn-out-") &&
!lowerName.startsWith("data-wrn-prop-bind-"))
) {
continue;
}
@@ -25092,7 +25143,7 @@ function __wireSpreadAttrs(value: any): string {
return attributes.join("");
}
function __wireProp(v: any): string {
function __wireProp(v: unknown): string {
const value =
v !== null && typeof v === "object"
? JSON.stringify(v)
@@ -25101,16 +25152,16 @@ function __wireProp(v: any): string {
return __wireAttr(value);
}
function __wireRaw(v: any): string {
function __wireRaw(v: unknown): string {
return String(v == null ? "" : v);
}`);
if (hasServerEach) {
out.push(`function __wrnexusEncodeLoopLocals(value: Record<string, any>): string {
out.push(`function __wrnexusEncodeLoopLocals(value: Record<string, unknown>): string {
return __WrnexusBuffer.from(JSON.stringify(value), "utf8").toString("base64");
}`);
}
if (needsScope) {
out.push(`function __wrnexusSerializeScopeValue(value: any): string {
out.push(`function __wrnexusSerializeScopeValue(value: unknown): string {
if (value === undefined) {
return "undefined";
}
@@ -25144,7 +25195,7 @@ function __wireRaw(v: any): string {
}
}
function __wrnexusScopeDecl(obj: Record<string, any>): string {
function __wrnexusScopeDecl(obj: Record<string, unknown>): string {
return Object.keys(obj)
.map(
(key) =>
@@ -25163,7 +25214,7 @@ function __wireRaw(v: any): string {
}
const serverFunctionSource = serverFunctions ? `${serverFunctions}
` : "";
out.push(`export function render(props: ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, any>"} = {} as ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, any>"}): string {
out.push(`export function render(props: ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, unknown>"} = {} as ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, unknown>"}): string {
` + ` const __p = props || {};
` + (decls.length > 0 ? decls.join(`
`) + `
@@ -27916,7 +27967,12 @@ ${serverFunctions}
case "props": {
lx.next();
expect("lbrace");
while (lx.peek().type !== "rbrace") {
while (true) {
if (lx.startsWithBlockComment()) {
throw new ParseError("Block comments are not allowed inside props {}; use // line comments instead", "WRN-PROPS-BLOCK-COMMENT");
}
if (lx.peek().type === "rbrace")
break;
const t = lx.peek();
if (t.type === "eof")
throw new ParseError("Unexpected end of input inside props");
@@ -28275,6 +28331,9 @@ ${serverFunctions}
}
expect("rbrace");
const declaredStates = new Set(states.map((state) => state.name));
if (declaredStates.has("page")) {
throw new ParseError("State name 'page' collides with the WRN 'page' keyword; choose another state name", "WRN-STATE-RESERVED-NAME");
}
for (const watcher of watches) {
if (!declaredStates.has(watcher.state)) {
throw new ParseError(`Cannot watch undeclared state '${watcher.state}'`);
@@ -28748,6 +28807,10 @@ ${serverFunctions}
break;
}
}
startsWithBlockComment() {
this.skipTrivia();
return this.src[this.pos] === "/" && this.src[this.pos + 1] === "*";
}
next() {
this.skipTrivia();
const { src } = this;
+14 -2
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env node
// WRN editor language server source hash: 65a5536efbc7c9fee15040559bb48afe78c4686fc5e7341ffc941cc7113c8556
// WRN editor language server source hash: 4c7e59ebeb512e23d95f4169dd80da2b951ae39c31d31919994368b8e90f5cec
// WRN editor language server generator hash: f593a44aaf05495b789ce7a3086bee1eebb951b884d41c0e017bbcfe5f547e72
// @bun @bun-cjs
(function(exports, require, module, __filename, __dirname) {var __create = Object.create;
@@ -168786,6 +168786,10 @@ class Lexer {
break;
}
}
startsWithBlockComment() {
this.skipTrivia();
return this.src[this.pos] === "/" && this.src[this.pos + 1] === "*";
}
next() {
this.skipTrivia();
const { src } = this;
@@ -170521,7 +170525,12 @@ function parse(source) {
case "props": {
lx.next();
expect("lbrace");
while (lx.peek().type !== "rbrace") {
while (true) {
if (lx.startsWithBlockComment()) {
throw new ParseError("Block comments are not allowed inside props {}; use // line comments instead", "WRN-PROPS-BLOCK-COMMENT");
}
if (lx.peek().type === "rbrace")
break;
const t = lx.peek();
if (t.type === "eof")
throw new ParseError("Unexpected end of input inside props");
@@ -170880,6 +170889,9 @@ function parse(source) {
}
expect("rbrace");
const declaredStates = new Set(states.map((state) => state.name));
if (declaredStates.has("page")) {
throw new ParseError("State name 'page' collides with the WRN 'page' keyword; choose another state name", "WRN-STATE-RESERVED-NAME");
}
for (const watcher of watches) {
if (!declaredStates.has(watcher.state)) {
throw new ParseError(`Cannot watch undeclared state '${watcher.state}'`);
+1
View File
@@ -13,6 +13,7 @@ export default tseslint.config(
"**/dist/**",
".publish/**",
"**/.wrnexus/**",
"**/.wrnexus-*/**",
"coverage/**",
"bun.lock",
"bun.lockb",
+2
View File
@@ -1,6 +1,8 @@
node_modules/
dist/
.wrnexus/
.wrnexus-*/
**/.wrnexus-*/
.wirefw/
**/.wirefw/
**/*.gen.ts
+29 -1
View File
@@ -60,10 +60,27 @@ describe("full app", () => {
});
afterAll(() => app?.close());
test("home page responds with HTML", async () => {
test("home page server-renders translated page and layout text", async () => {
const res = await app.fetch("/");
expect(res.status).toBe(200);
expect(res.headers.get("content-type")).toContain("text/html");
const html = await res.text();
expect(html).toContain('<span data-t="nav.home">Home</span>');
expect(html).toContain('<span data-t="home.title">Hello from WrNexus</span>');
expect(html).not.toContain('<span data-t="nav.home"></span>');
});
test("home page ships only its active theme stylesheet", async () => {
const response = await app.fetch("/");
const html = await response.text();
const href = html.match(/data-wrnexus-theme href="([^"]+)"/)?.[1];
expect(href).toContain("/__wrnexus/theme/dark/");
expect(html).not.toContain('href="/__wrnexus/theme.css');
const cssResponse = await app.fetch(String(href));
expect(cssResponse.status).toBe(200);
const css = await cssResponse.text();
expect(css.length).toBeLessThan(10_000);
expect(css).not.toContain("[data-theme=");
});
test("GET /api/hello returns a translated greeting", async () => {
@@ -94,6 +111,17 @@ describe("full app", () => {
expect(html).not.toContain("Card title");
});
test("shared UI foundations render on demand instead of shipping in ui.css", async () => {
const fieldResponse = await app.fetch("/modal");
expect(fieldResponse.status).toBe(200);
const fieldHtml = await fieldResponse.text();
expect(fieldHtml).toContain('data-wrnexus-style-owner="FieldStyles"');
expect(fieldHtml).toContain('data-wrnexus-style-owner="ComponentBaseStyles"');
const selectResponse = await app.fetch("/test");
expect(selectResponse.status).toBe(200);
expect(await selectResponse.text()).toContain('data-wrnexus-style-owner="SelectStyles"');
});
test("platform showcase uses the public layout and styled reactive primitives", async () => {
const res = await app.fetch("/platform-showcase");
expect(res.status).toBe(200);
+4 -7
View File
@@ -1,3 +1,7 @@
import Input from "@wrnexus/ui/components/input.wrn"
import Modal from "@wrnexus/ui/components/Modal.wrn"
import Toaster from "@wrnexus/ui/components/Toaster.wrn"
// Modal demo page. Route: /modal
//
// Five real-world use cases for the shared @wrnexus/ui Modal component — each
@@ -9,13 +13,6 @@
// this page. Any client expression can raise one with the runtime global —
// toast("..."), toast.success(...), toast.error(...) — so pages never
// hand-roll a status banner and never reach for a blocking alert().
//
// No import here on purpose: component tags resolve by directory scan, and
// @wrnexus/ui's index.ts intentionally has no runtime exports (registry.ts
// holds the server-only filesystem helpers instead) — an explicit
// `import { Modal } from "@wrnexus/ui"` fails to bundle since there's no
// matching export. The WRN-IMPORT-IMPLICIT warning this triggers is safe to
// ignore for component tags.
page ModalPage {
state noteText = "Remember to ship the release notes"
+3
View File
@@ -1,3 +1,6 @@
import AdvancedSelect from "@wrnexus/ui/components/AdvancedSelect.wrn"
import ComboBox from "@wrnexus/ui/components/Combobox.wrn"
// Component event test page. Route: /test
//
// Events bubble from the exact ComboBox or AdvancedSelect that emitted them.
+5 -4
View File
@@ -13,15 +13,14 @@ declare namespace WRNexusGenerated {
: never;
type RealtimeMessage<T> = T extends import("@wrnexus/core").RoomDefinition<any, infer M> ? M : unknown;
type QueuePayload<T> = T extends import("@wrnexus/queue").JobDefinition<infer I> ? I : unknown;
type RouteName = "about" | "async.data" | "chat" | "client.only" | "dashboard" | "hello" | "index" | "language.tools" | "login" | "modal" | "partial.static" | "platform.showcase" | "reactive" | "server.actions" | "test" | "ui";
type ApiRoute = "/api/echo" | "/api/graphql-example" | "/api/hello" | "/api/login" | "/api/logout" | "/api/me" | "/api/typed-user" | "/api/users/csr" | "/api/users/ssr" | "/api/webhooks/payment";
type RouteName = "about" | "async.data" | "chat" | "client.only" | "dashboard" | "hello" | "index" | "language.tools" | "layout" | "login" | "modal" | "navigation" | "partial.static" | "platform.showcase" | "reactive" | "server.actions" | "table" | "test" | "ui";
type ApiRoute = "/api/accounts" | "/api/echo" | "/api/graphql-example" | "/api/hello" | "/api/invite" | "/api/login" | "/api/logout" | "/api/me" | "/api/typed-user" | "/api/users/csr" | "/api/users/ssr" | "/api/webhooks/payment";
type RealtimeRoute = "/realtime/chat" | "/realtime/hello";
type EnvironmentKey = "APP_LABEL" | "DATABASE_URL" | "DEMO_SHARED" | "HOST" | "NODE_ENV" | "PORT" | "SESSION_SECRET" | "UAT_ONLY";
type TranslationKey = "api.greeting" | "home.intro" | "home.title" | "nav.about" | "nav.chat" | "nav.dashboard" | "nav.home" | "nav.ui";
type TranslationKey = "api.greeting" | "home.intro" | "home.title" | "nav.about" | "nav.chat" | "nav.dashboard" | "nav.home" | "nav.layout" | "nav.navigation" | "nav.ui";
type QueueName = "welcome-email";
type CacheKey = "users";
interface Components {
"Modal": { props: Record<string, never>; outputs: Record<string, never> };
"Counter": { props: { "start"?: unknown; "label"?: unknown }; outputs: Record<string, never> };
}
interface ApiContracts {
@@ -30,6 +29,8 @@ declare namespace WRNexusGenerated {
"/api/users/ssr": { GET: ApiContract<typeof import("../api/users/ssr.ts")["GET"]> };
"/api/graphql-example": { POST: ApiContract<typeof import("../api/graphql-example.ts")["POST"]> };
"/api/typed-user": { POST: ApiContract<typeof import("../api/typed-user.ts")["POST"]> };
"/api/accounts": { GET: ApiContract<typeof import("../api/accounts.ts")["GET"]> };
"/api/invite": { POST: ApiContract<typeof import("../api/invite.ts")["POST"]> };
"/api/logout": { POST: ApiContract<typeof import("../api/logout.ts")["POST"]> };
"/api/hello": { GET: ApiContract<typeof import("../api/hello.ts")["GET"]> };
"/api/login": { POST: ApiContract<typeof import("../api/login.ts")["POST"]> };
+8 -1
View File
@@ -7,7 +7,14 @@ const tsconfigRootDir = dirname(fileURLToPath(import.meta.url));
export default tseslint.config(
{
ignores: ["node_modules/**", "dist/**", ".wrnexus/**", "**/.wrnexus/**"],
ignores: [
"node_modules/**",
"dist/**",
".wrnexus/**",
"**/.wrnexus/**",
".wrnexus-*/**",
"**/.wrnexus-*/**",
],
},
{
languageOptions: {
+2 -1
View File
@@ -3,5 +3,6 @@
"compilerOptions": {
"lib": ["ESNext", "DOM", "DOM.Iterable"]
},
"include": ["app"]
"include": ["app"],
"exclude": ["node_modules", "dist", ".wrnexus-*", "**/.wrnexus-*"]
}
@@ -31,11 +31,11 @@ layout Showcase {
<section class="docs-directory-group">
<a class="docs-directory-heading" href="/advanced-forms">Advanced Forms</a>
<div><a data-docs-component-link href="/components/advanced-select">Advanced Select</a><a data-docs-component-link href="/components/combo-box">Combo Box</a><a data-docs-component-link href="/components/copy-markup">Copy Markup</a><a data-docs-component-link href="/components/input-number">Input Number</a><a data-docs-component-link href="/components/pin-input">Pin Input</a><a data-docs-component-link href="/components/strong-password">Strong Password</a><a data-docs-component-link href="/components/toggle-count">Toggle Count</a><a data-docs-component-link href="/components/toggle-password">Toggle Password</a></div>
<div><a data-docs-component-link href="/components/advanced-select">Advanced Select</a><a data-docs-component-link href="/components/combo-box">Combo Box</a><a data-docs-component-link href="/components/input-number">Input Number</a><a data-docs-component-link href="/components/pin-input">Pin Input</a><a data-docs-component-link href="/components/strong-password">Strong Password</a><a data-docs-component-link href="/components/toggle-count">Toggle Count</a><a data-docs-component-link href="/components/toggle-password">Toggle Password</a></div>
</section>
<section class="docs-directory-group">
<a class="docs-directory-heading" href="/base">Base</a>
<div><a data-docs-component-link href="/components/accordion">Accordion</a><a data-docs-component-link href="/components/alert">Alert</a><a data-docs-component-link href="/components/avatar">Avatar</a><a data-docs-component-link href="/components/avatar-group">Avatar Group</a><a data-docs-component-link href="/components/badge">Badge</a><a data-docs-component-link href="/components/blockquote">Blockquote</a><a data-docs-component-link href="/components/button">Button</a><a data-docs-component-link href="/components/button-group">Button Group</a><a data-docs-component-link href="/components/card">Card</a><a data-docs-component-link href="/components/carousel">Carousel</a><a data-docs-component-link href="/components/chat-bubble">Chat Bubble</a><a data-docs-component-link href="/components/collapse">Collapse</a><a data-docs-component-link href="/components/date-picker">Date Picker</a><a data-docs-component-link href="/components/device-frame">Device Frame</a><a data-docs-component-link href="/components/file-upload-progress">File Upload Progress</a><a data-docs-component-link href="/components/legend-indicator">Legend Indicator</a><a data-docs-component-link href="/components/list">List</a><a data-docs-component-link href="/components/list-group">List Group</a><a data-docs-component-link href="/components/marquee">Marquee</a><a data-docs-component-link href="/components/progress">Progress</a><a data-docs-component-link href="/components/rating">Rating</a><a data-docs-component-link href="/components/skeleton">Skeleton</a><a data-docs-component-link href="/components/spinner">Spinner</a><a data-docs-component-link href="/components/styled-icon">Styled Icon</a><a data-docs-component-link href="/components/timeline">Timeline</a><a data-docs-component-link href="/components/toast">Toast</a><a data-docs-component-link href="/components/tree-view">Tree View</a></div>
<div><a data-docs-component-link href="/components/accordion">Accordion</a><a data-docs-component-link href="/components/alert">Alert</a><a data-docs-component-link href="/components/avatar">Avatar</a><a data-docs-component-link href="/components/avatar-group">Avatar Group</a><a data-docs-component-link href="/components/badge">Badge</a><a data-docs-component-link href="/components/blockquote">Blockquote</a><a data-docs-component-link href="/components/button">Button</a><a data-docs-component-link href="/components/button-group">Button Group</a><a data-docs-component-link href="/components/card">Card</a><a data-docs-component-link href="/components/carousel">Carousel</a><a data-docs-component-link href="/components/chat-bubble">Chat Bubble</a><a data-docs-component-link href="/components/collapse">Collapse</a><a data-docs-component-link href="/components/date-picker">Date Picker</a><a data-docs-component-link href="/components/device-frame">Device Frame</a><a data-docs-component-link href="/components/file-upload-progress">File Upload Progress</a><a data-docs-component-link href="/components/legend-indicator">Legend Indicator</a><a data-docs-component-link href="/components/list">List</a><a data-docs-component-link href="/components/list-group">List Group</a><a data-docs-component-link href="/components/marquee">Marquee</a><a data-docs-component-link href="/components/progress">Progress</a><a data-docs-component-link href="/components/rating">Rating</a><a data-docs-component-link href="/components/skeleton">Skeleton</a><a data-docs-component-link href="/components/spinner">Spinner</a><a data-docs-component-link href="/components/styled-icon">Styled Icon</a><a data-docs-component-link href="/components/timeline">Timeline</a><a data-docs-component-link href="/components/tree-view">Tree View</a></div>
</section>
<section class="docs-directory-group">
<a class="docs-directory-heading" href="/core">Core</a>
@@ -51,7 +51,7 @@ layout Showcase {
</section>
<section class="docs-directory-group">
<a class="docs-directory-heading" href="/integrations">Integrations</a>
<div><a data-docs-component-link href="/components/advanced-date-picker">Advanced Date Picker</a><a data-docs-component-link href="/components/advanced-range-slider">Advanced Range Slider</a><a data-docs-component-link href="/components/chart">Chart</a><a data-docs-component-link href="/components/clipboard">Clipboard</a><a data-docs-component-link href="/components/confetti">Confetti</a><a data-docs-component-link href="/components/data-map">Data Map</a><a data-docs-component-link href="/components/drag-and-drop">Drag And Drop</a><a data-docs-component-link href="/components/file-upload">File Upload</a><a data-docs-component-link href="/components/map">Map</a><a data-docs-component-link href="/components/toast-notifications">Toast Notifications</a><a data-docs-component-link href="/components/wysiwyg-editor">Wysiwyg Editor</a></div>
<div><a data-docs-component-link href="/components/chart">Chart</a><a data-docs-component-link href="/components/clipboard">Clipboard</a><a data-docs-component-link href="/components/confetti">Confetti</a><a data-docs-component-link href="/components/data-map">Data Map</a><a data-docs-component-link href="/components/drag-and-drop">Drag And Drop</a><a data-docs-component-link href="/components/map">Map</a><a data-docs-component-link href="/components/wysiwyg-editor">Wysiwyg Editor</a></div>
</section>
<section class="docs-directory-group">
<a class="docs-directory-heading" href="/layout">Layout</a>
@@ -3,14 +3,14 @@ page AdvancedFormsShowcase {
layout = "showcase"
seo {
title = "Advanced Forms components"
description = "8 advanced-forms components from @wrnexus/ui."
description = "7 advanced-forms components from @wrnexus/ui."
}
view {
<header class="showcase-hero showcase-hero--category">
<span class="showcase-eyebrow">Component category</span>
<h1 class="showcase-title">Advanced Forms</h1>
<p class="showcase-description">8 unique, responsive, theme-aware components. Open a component to inspect multiple live configurations and its complete props API.</p>
<div class="category-meta"><span>8 components</span><span>Multiple use cases</span><span>112 live configurations</span></div>
<p class="showcase-description">7 unique, responsive, theme-aware components. Open a component to inspect multiple live configurations and its complete props API.</p>
<div class="category-meta"><span>7 components</span><span>Multiple use cases</span><span>109 live configurations</span></div>
</header>
<div class="catalog-grid">
<article class="catalog-card" data-interactive-preview="false">
@@ -33,16 +33,6 @@ page AdvancedFormsShowcase {
<div class="catalog-card-footer"><span>41 props · 0 slots</span><a href="/components/combo-box">Explore component <span aria-hidden="true">→</span></a></div>
</div>
</article>
<article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div>
<div class="catalog-card-stage"><CopyMarkup size="default" label="Copy Markup" value="sample-1" placeholder="Enter a sample value" disabled="false" required="false" class="showcase-instance showcase-instance--1" /></div>
</div>
<div class="catalog-card-body">
<div><span class="catalog-card-category">Advanced Forms</span><h2>Copy Markup</h2><p>Theme-aware, responsive copy markup component.</p></div>
<div class="catalog-card-footer"><span>13 props · 0 slots</span><a href="/components/copy-markup">Explore component <span aria-hidden="true">→</span></a></div>
</div>
</article>
<article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div>
+5 -15
View File
@@ -3,14 +3,14 @@ page BaseShowcase {
layout = "showcase"
seo {
title = "Base components"
description = "27 base components from @wrnexus/ui."
description = "26 base components from @wrnexus/ui."
}
view {
<header class="showcase-hero showcase-hero--category">
<span class="showcase-eyebrow">Component category</span>
<h1 class="showcase-title">Base</h1>
<p class="showcase-description">27 unique, responsive, theme-aware components. Open a component to inspect multiple live configurations and its complete props API.</p>
<div class="category-meta"><span>27 components</span><span>Multiple use cases</span><span>86 live configurations</span></div>
<p class="showcase-description">26 unique, responsive, theme-aware components. Open a component to inspect multiple live configurations and its complete props API.</p>
<div class="category-meta"><span>26 components</span><span>Multiple use cases</span><span>83 live configurations</span></div>
</header>
<div class="catalog-grid">
<article class="catalog-card" data-interactive-preview="false">
@@ -266,21 +266,11 @@ page BaseShowcase {
<article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div>
<div class="catalog-card-stage"><Toast size="default" title="Toast example" description="A polished default toast for a modern application." items='[{"id":"toast-0-1","key":"toast-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#toast-demo","actionHref":"#toast-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"toast-0-2","key":"toast-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#toast-demo","actionHref":"#toast-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
</div>
<div class="catalog-card-body">
<div><span class="catalog-card-category">Base</span><h2>Toast</h2><p>Theme-aware, responsive toast component.</p></div>
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/toast">Explore component <span aria-hidden="true">→</span></a></div>
</div>
</article>
<article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div>
<div class="catalog-card-stage"><TreeView size="default" title="Tree View example" description="A polished default tree view for a modern application." items='[{"id":"tree-view-0-1","key":"tree-view-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"tree-view-0-2","key":"tree-view-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
<div class="catalog-card-stage"><TreeView title="Tree View example" description="A polished default tree view for a modern application." items='[{"id":"tree-view-0-1","key":"tree-view-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"tree-view-0-2","key":"tree-view-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' defaultExpanded='[{"id":"tree-view-0-1","key":"tree-view-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"tree-view-0-2","key":"tree-view-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' size="default" class="showcase-instance showcase-instance--1" /></div>
</div>
<div class="catalog-card-body">
<div><span class="catalog-card-category">Base</span><h2>Tree View</h2><p>Theme-aware, responsive tree view component.</p></div>
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/tree-view">Explore component <span aria-hidden="true">→</span></a></div>
<div class="catalog-card-footer"><span>10 props · 1 slots</span><a href="/components/tree-view">Explore component <span aria-hidden="true">→</span></a></div>
</div>
</article></div>
}
@@ -1,696 +0,0 @@
// Generated by scripts/generate-showcase.mjs. Do not edit directly.
page AdvancedDatePickerDetail {
layout = "showcase"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
state playground_title = ctx.url.searchParams.get("pg_title") ?? "Advanced Date Picker example"
state playground_description = ctx.url.searchParams.get("pg_description") ?? "A polished default advanced date picker for a modern application."
state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"advanced-date-picker-0-1\",\"key\":\"advanced-date-picker-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#advanced-date-picker-demo\",\"actionHref\":\"#advanced-date-picker-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"advanced-date-picker-0-2\",\"key\":\"advanced-date-picker-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#advanced-date-picker-demo\",\"actionHref\":\"#advanced-date-picker-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]")
state playground_variant = ctx.url.searchParams.get("pg_variant") ?? "default"
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
seo {
title = "Advanced Date Picker"
description = "Theme-aware, responsive advanced date picker component."
}
view {
<nav class="docs-breadcrumbs" aria-label="Breadcrumb">
<a href="/">Components</a><span aria-hidden="true">/</span><a href="/integrations">Integrations</a><span aria-hidden="true">/</span><span>Advanced Date Picker</span>
</nav>
<header class="detail-hero">
<div class="detail-hero-copy">
<span class="showcase-eyebrow">Integrations component</span>
<h1>Advanced Date Picker</h1>
<p>Theme-aware, responsive advanced date picker component.</p>
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>5 outputs</span><span>Theme ready</span><span>Responsive</span></div>
</div>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header>
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="AdvancedDatePicker" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="input,change,open,close,clear">
<div class="detail-section-heading"><span>Interactive playground</span><h2>Configure Advanced Date Picker</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div>
<div class="playground-workbench">
<div class="playground-preview">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="playground-preview-source" data-playground-preview><AdvancedDatePicker size='{playground_size}' color='{playground_color}' title='{playground_title}' description='{playground_description}' items='{playground_items}' variant='{playground_variant}' class='{playground_class}' /></div>
<section class="playground-code" aria-label="Current component code">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;AdvancedDatePicker
title="Advanced Date Picker example"
description="A polished default advanced date picker for a modern application."
items='[
<span>&#123;</span>
"id": "advanced-date-picker-0-1",
"key": "advanced-date-picker-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#advanced-date-picker-demo",
"actionHref": "#advanced-date-picker-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "advanced-date-picker-0-2",
"key": "advanced-date-picker-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#advanced-date-picker-demo",
"actionHref": "#advanced-date-picker-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
<div class="playground-status" data-playground-status aria-live="polite"></div>
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
</div>
<form class="playground-controls" data-playground-form>
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
<div class="playground-fields"><label class="playground-field" for="playground-advanced-date-picker-size"><span><strong>size</strong><small>string</small></span><select id="playground-advanced-date-picker-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-advanced-date-picker-color"><span><strong>color</strong><small>string</small></span><select id="playground-advanced-date-picker-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-advanced-date-picker-title"><span><strong>title</strong><small>string</small></span><input id="playground-advanced-date-picker-title" name="pg_title" type="text" value="Advanced Date Picker example" /></label><label class="playground-field" for="playground-advanced-date-picker-description"><span><strong>description</strong><small>string</small></span><input id="playground-advanced-date-picker-description" name="pg_description" type="text" value="A polished default advanced date picker for a modern application." /></label><label class="playground-field" for="playground-advanced-date-picker-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-advanced-date-picker-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[
&#123;
"id": "advanced-date-picker-0-1",
"key": "advanced-date-picker-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#advanced-date-picker-demo",
"actionHref": "#advanced-date-picker-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
"label": "Nested option B",
"value": "nested-b"
&#125;
],
"links": [
&#123;
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
"label": "API reference",
"href": "#api-reference"
&#125;
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
"id": "advanced-date-picker-0-2",
"key": "advanced-date-picker-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#advanced-date-picker-demo",
"actionHref": "#advanced-date-picker-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
"label": "Nested option B",
"value": "nested-b"
&#125;
],
"links": [
&#123;
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
"label": "API reference",
"href": "#api-reference"
&#125;
],
"values": [
"Included",
"Standard"
]
&#125;
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-advanced-date-picker-variant"><span><strong>variant</strong><small>string</small></span><input id="playground-advanced-date-picker-variant" name="pg_variant" type="text" value="default" /></label><label class="playground-field" for="playground-advanced-date-picker-class"><span><strong>class</strong><small>string</small></span><input id="playground-advanced-date-picker-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
</form>
</div>
</section>
<div class="detail-layout">
<main class="detail-content">
<section class="detail-section"><div class="detail-section-heading"><span>Live examples</span><h2>Designed for real product surfaces</h2><p>Compare configurations and resize the browser to check responsive behavior.</p></div><div class="demo-list">
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Recommended</span><h2>Production default</h2><p>Balanced spacing, hierarchy, and content for the most common product workflow.</p></div>
<span class="demo-case-number">01</span>
</header>
<div class="demo-workbench">
<div id="advanced-date-picker-demo-1" class="demo-canvas demo-canvas--1">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><AdvancedDatePicker size="default" title="Advanced Date Picker example" description="A polished default advanced date picker for a modern application." items='[{"id":"advanced-date-picker-0-1","key":"advanced-date-picker-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#advanced-date-picker-demo","actionHref":"#advanced-date-picker-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"advanced-date-picker-0-2","key":"advanced-date-picker-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#advanced-date-picker-demo","actionHref":"#advanced-date-picker-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
</div>
<section class="demo-code" aria-label="Production default usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;AdvancedDatePicker
title="Advanced Date Picker example"
description="A polished default advanced date picker for a modern application."
items='[
<span>&#123;</span>
"id": "advanced-date-picker-0-1",
"key": "advanced-date-picker-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#advanced-date-picker-demo",
"actionHref": "#advanced-date-picker-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "advanced-date-picker-0-2",
"key": "advanced-date-picker-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#advanced-date-picker-demo",
"actionHref": "#advanced-date-picker-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
</div>
</article>
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Dense UI</span><h2>Compact application</h2><p>A tighter variation for dashboards, side panels, tables, and operational interfaces.</p></div>
<span class="demo-case-number">02</span>
</header>
<div class="demo-workbench">
<div id="advanced-date-picker-demo-2" class="demo-canvas demo-canvas--2">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><AdvancedDatePicker size="sm" title="Compact Advanced Date Picker" description="A compact configuration for dashboards and dense product surfaces." items='[{"id":"advanced-date-picker-1-1","key":"advanced-date-picker-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#advanced-date-picker-demo","actionHref":"#advanced-date-picker-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"advanced-date-picker-1-2","key":"advanced-date-picker-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#advanced-date-picker-demo","actionHref":"#advanced-date-picker-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="secondary" class="showcase-instance showcase-instance--2" /></div>
</div>
<section class="demo-code" aria-label="Compact application usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;AdvancedDatePicker
size="sm"
title="Compact Advanced Date Picker"
description="A compact configuration for dashboards and dense product surfaces."
items='[
<span>&#123;</span>
"id": "advanced-date-picker-1-1",
"key": "advanced-date-picker-1-1",
"value": "primary",
"label": "Secondary workflow",
"title": "Secondary workflow",
"description": "A compact configuration designed for dense application layouts.",
"text": "A configurable sample message.",
"href": "#advanced-date-picker-demo",
"actionHref": "#advanced-date-picker-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 24,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "advanced-date-picker-1-2",
"key": "advanced-date-picker-1-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A compact configuration designed for dense application layouts.",
"text": "Another configurable message.",
"href": "#advanced-date-picker-demo",
"actionHref": "#advanced-date-picker-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 48,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
variant="secondary"
/&gt;</code></pre>
</section>
</div>
</article>
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Extended</span><h2>Rich configuration</h2><p>A more expressive variation using additional data, stronger emphasis, and optional states.</p></div>
<span class="demo-case-number">03</span>
</header>
<div class="demo-workbench">
<div id="advanced-date-picker-demo-3" class="demo-canvas demo-canvas--3">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><AdvancedDatePicker size="lg" title="Advanced Advanced Date Picker" description="A richer configuration demonstrating additional content and hierarchy." items='[{"id":"advanced-date-picker-2-1","key":"advanced-date-picker-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#advanced-date-picker-demo","actionHref":"#advanced-date-picker-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"advanced-date-picker-2-2","key":"advanced-date-picker-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#advanced-date-picker-demo","actionHref":"#advanced-date-picker-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' variant="success" class="showcase-instance showcase-instance--3" /></div>
</div>
<section class="demo-code" aria-label="Rich configuration usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;AdvancedDatePicker
size="lg"
title="Advanced Advanced Date Picker"
description="A richer configuration demonstrating additional content and hierarchy."
items='[
<span>&#123;</span>
"id": "advanced-date-picker-2-1",
"key": "advanced-date-picker-2-1",
"value": "primary",
"label": "Advanced workflow",
"title": "Advanced workflow",
"description": "A richer configuration with more supporting information and actions.",
"text": "A configurable sample message.",
"href": "#advanced-date-picker-demo",
"actionHref": "#advanced-date-picker-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 32,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "advanced-date-picker-2-2",
"key": "advanced-date-picker-2-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A richer configuration with more supporting information and actions.",
"text": "Another configurable message.",
"href": "#advanced-date-picker-demo",
"actionHref": "#advanced-date-picker-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": false,
"selected": false,
"checked": false,
"disabled": true,
"outgoing": true,
"open": true,
"number": 2,
"count": 64,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>
]'
variant="success"
/&gt;</code></pre>
</section>
</div>
</article></div></section>
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@input</code><span>Fires as the editable value changes.</span></div><div><code>@change</code><span>Fires when the component's committed value or selection changes.</span></div><div><code>@open</code><span>Fires after the component opens.</span></div><div><code>@close</code><span>Fires after the component closes.</span></div><div><code>@clear</code><span>Fires after the current value or selection is cleared.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;AdvancedDatePicker
@input='console.log(payload)'
@change='console.log(payload)'
@open='console.log(payload)'
@close='console.log(payload)'
@clear='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"AdvancedDatePicker\"], [data-component=\"AdvancedDatePicker\"]")
if (component) registerOutputHandler(component, "input", (payload) =&gt; <span>&#123;</span>
console.log("input", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "open", (payload) =&gt; <span>&#123;</span>
console.log("open", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "close", (payload) =&gt; <span>&#123;</span>
console.log("close", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "clear", (payload) =&gt; <span>&#123;</span>
console.log("clear", payload)
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Advanced Date Picker"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main>
<aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#advanced-date-picker-demo-1">Production default</a><a href="#advanced-date-picker-demo-2">Compact application</a><a href="#advanced-date-picker-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
</aside>
</div>
<nav class="detail-pagination">
<span></span>
<a href="/components/advanced-range-slider"><small>Next</small><strong>Advanced Range Slider →</strong></a>
</nav>
}
}
@@ -1,691 +0,0 @@
// Generated by scripts/generate-showcase.mjs. Do not edit directly.
page AdvancedRangeSliderDetail {
layout = "showcase"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
state playground_title = ctx.url.searchParams.get("pg_title") ?? "Advanced Range Slider example"
state playground_description = ctx.url.searchParams.get("pg_description") ?? "A polished default advanced range slider for a modern application."
state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"advanced-range-slider-0-1\",\"key\":\"advanced-range-slider-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#advanced-range-slider-demo\",\"actionHref\":\"#advanced-range-slider-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"advanced-range-slider-0-2\",\"key\":\"advanced-range-slider-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#advanced-range-slider-demo\",\"actionHref\":\"#advanced-range-slider-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]")
state playground_variant = ctx.url.searchParams.get("pg_variant") ?? "default"
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
seo {
title = "Advanced Range Slider"
description = "Theme-aware, responsive advanced range slider component."
}
view {
<nav class="docs-breadcrumbs" aria-label="Breadcrumb">
<a href="/">Components</a><span aria-hidden="true">/</span><a href="/integrations">Integrations</a><span aria-hidden="true">/</span><span>Advanced Range Slider</span>
</nav>
<header class="detail-hero">
<div class="detail-hero-copy">
<span class="showcase-eyebrow">Integrations component</span>
<h1>Advanced Range Slider</h1>
<p>Theme-aware, responsive advanced range slider component.</p>
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>4 outputs</span><span>Theme ready</span><span>Responsive</span></div>
</div>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header>
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="AdvancedRangeSlider" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="input,change,start,end">
<div class="detail-section-heading"><span>Interactive playground</span><h2>Configure Advanced Range Slider</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div>
<div class="playground-workbench">
<div class="playground-preview">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="playground-preview-source" data-playground-preview><AdvancedRangeSlider size='{playground_size}' color='{playground_color}' title='{playground_title}' description='{playground_description}' items='{playground_items}' variant='{playground_variant}' class='{playground_class}' /></div>
<section class="playground-code" aria-label="Current component code">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;AdvancedRangeSlider
title="Advanced Range Slider example"
description="A polished default advanced range slider for a modern application."
items='[
<span>&#123;</span>
"id": "advanced-range-slider-0-1",
"key": "advanced-range-slider-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#advanced-range-slider-demo",
"actionHref": "#advanced-range-slider-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "advanced-range-slider-0-2",
"key": "advanced-range-slider-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#advanced-range-slider-demo",
"actionHref": "#advanced-range-slider-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
<div class="playground-status" data-playground-status aria-live="polite"></div>
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
</div>
<form class="playground-controls" data-playground-form>
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
<div class="playground-fields"><label class="playground-field" for="playground-advanced-range-slider-size"><span><strong>size</strong><small>string</small></span><select id="playground-advanced-range-slider-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-advanced-range-slider-color"><span><strong>color</strong><small>string</small></span><select id="playground-advanced-range-slider-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-advanced-range-slider-title"><span><strong>title</strong><small>string</small></span><input id="playground-advanced-range-slider-title" name="pg_title" type="text" value="Advanced Range Slider example" /></label><label class="playground-field" for="playground-advanced-range-slider-description"><span><strong>description</strong><small>string</small></span><input id="playground-advanced-range-slider-description" name="pg_description" type="text" value="A polished default advanced range slider for a modern application." /></label><label class="playground-field" for="playground-advanced-range-slider-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-advanced-range-slider-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[
&#123;
"id": "advanced-range-slider-0-1",
"key": "advanced-range-slider-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#advanced-range-slider-demo",
"actionHref": "#advanced-range-slider-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
"label": "Nested option B",
"value": "nested-b"
&#125;
],
"links": [
&#123;
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
"label": "API reference",
"href": "#api-reference"
&#125;
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
"id": "advanced-range-slider-0-2",
"key": "advanced-range-slider-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#advanced-range-slider-demo",
"actionHref": "#advanced-range-slider-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
"label": "Nested option B",
"value": "nested-b"
&#125;
],
"links": [
&#123;
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
"label": "API reference",
"href": "#api-reference"
&#125;
],
"values": [
"Included",
"Standard"
]
&#125;
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-advanced-range-slider-variant"><span><strong>variant</strong><small>string</small></span><input id="playground-advanced-range-slider-variant" name="pg_variant" type="text" value="default" /></label><label class="playground-field" for="playground-advanced-range-slider-class"><span><strong>class</strong><small>string</small></span><input id="playground-advanced-range-slider-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
</form>
</div>
</section>
<div class="detail-layout">
<main class="detail-content">
<section class="detail-section"><div class="detail-section-heading"><span>Live examples</span><h2>Designed for real product surfaces</h2><p>Compare configurations and resize the browser to check responsive behavior.</p></div><div class="demo-list">
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Recommended</span><h2>Production default</h2><p>Balanced spacing, hierarchy, and content for the most common product workflow.</p></div>
<span class="demo-case-number">01</span>
</header>
<div class="demo-workbench">
<div id="advanced-range-slider-demo-1" class="demo-canvas demo-canvas--1">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><AdvancedRangeSlider size="default" title="Advanced Range Slider example" description="A polished default advanced range slider for a modern application." items='[{"id":"advanced-range-slider-0-1","key":"advanced-range-slider-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#advanced-range-slider-demo","actionHref":"#advanced-range-slider-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"advanced-range-slider-0-2","key":"advanced-range-slider-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#advanced-range-slider-demo","actionHref":"#advanced-range-slider-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
</div>
<section class="demo-code" aria-label="Production default usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;AdvancedRangeSlider
title="Advanced Range Slider example"
description="A polished default advanced range slider for a modern application."
items='[
<span>&#123;</span>
"id": "advanced-range-slider-0-1",
"key": "advanced-range-slider-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#advanced-range-slider-demo",
"actionHref": "#advanced-range-slider-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "advanced-range-slider-0-2",
"key": "advanced-range-slider-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#advanced-range-slider-demo",
"actionHref": "#advanced-range-slider-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
</div>
</article>
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Dense UI</span><h2>Compact application</h2><p>A tighter variation for dashboards, side panels, tables, and operational interfaces.</p></div>
<span class="demo-case-number">02</span>
</header>
<div class="demo-workbench">
<div id="advanced-range-slider-demo-2" class="demo-canvas demo-canvas--2">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><AdvancedRangeSlider size="sm" title="Compact Advanced Range Slider" description="A compact configuration for dashboards and dense product surfaces." items='[{"id":"advanced-range-slider-1-1","key":"advanced-range-slider-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#advanced-range-slider-demo","actionHref":"#advanced-range-slider-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"advanced-range-slider-1-2","key":"advanced-range-slider-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#advanced-range-slider-demo","actionHref":"#advanced-range-slider-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="secondary" class="showcase-instance showcase-instance--2" /></div>
</div>
<section class="demo-code" aria-label="Compact application usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;AdvancedRangeSlider
size="sm"
title="Compact Advanced Range Slider"
description="A compact configuration for dashboards and dense product surfaces."
items='[
<span>&#123;</span>
"id": "advanced-range-slider-1-1",
"key": "advanced-range-slider-1-1",
"value": "primary",
"label": "Secondary workflow",
"title": "Secondary workflow",
"description": "A compact configuration designed for dense application layouts.",
"text": "A configurable sample message.",
"href": "#advanced-range-slider-demo",
"actionHref": "#advanced-range-slider-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 24,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "advanced-range-slider-1-2",
"key": "advanced-range-slider-1-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A compact configuration designed for dense application layouts.",
"text": "Another configurable message.",
"href": "#advanced-range-slider-demo",
"actionHref": "#advanced-range-slider-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 48,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
variant="secondary"
/&gt;</code></pre>
</section>
</div>
</article>
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Extended</span><h2>Rich configuration</h2><p>A more expressive variation using additional data, stronger emphasis, and optional states.</p></div>
<span class="demo-case-number">03</span>
</header>
<div class="demo-workbench">
<div id="advanced-range-slider-demo-3" class="demo-canvas demo-canvas--3">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><AdvancedRangeSlider size="lg" title="Advanced Advanced Range Slider" description="A richer configuration demonstrating additional content and hierarchy." items='[{"id":"advanced-range-slider-2-1","key":"advanced-range-slider-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#advanced-range-slider-demo","actionHref":"#advanced-range-slider-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"advanced-range-slider-2-2","key":"advanced-range-slider-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#advanced-range-slider-demo","actionHref":"#advanced-range-slider-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' variant="success" class="showcase-instance showcase-instance--3" /></div>
</div>
<section class="demo-code" aria-label="Rich configuration usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;AdvancedRangeSlider
size="lg"
title="Advanced Advanced Range Slider"
description="A richer configuration demonstrating additional content and hierarchy."
items='[
<span>&#123;</span>
"id": "advanced-range-slider-2-1",
"key": "advanced-range-slider-2-1",
"value": "primary",
"label": "Advanced workflow",
"title": "Advanced workflow",
"description": "A richer configuration with more supporting information and actions.",
"text": "A configurable sample message.",
"href": "#advanced-range-slider-demo",
"actionHref": "#advanced-range-slider-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 32,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "advanced-range-slider-2-2",
"key": "advanced-range-slider-2-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A richer configuration with more supporting information and actions.",
"text": "Another configurable message.",
"href": "#advanced-range-slider-demo",
"actionHref": "#advanced-range-slider-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": false,
"selected": false,
"checked": false,
"disabled": true,
"outgoing": true,
"open": true,
"number": 2,
"count": 64,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>
]'
variant="success"
/&gt;</code></pre>
</section>
</div>
</article></div></section>
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@input</code><span>Fires as the editable value changes.</span></div><div><code>@change</code><span>Fires when the component's committed value or selection changes.</span></div><div><code>@start</code><span>Fires when the component emits the start event.</span></div><div><code>@end</code><span>Fires when the component emits the end event.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;AdvancedRangeSlider
@input='console.log(payload)'
@change='console.log(payload)'
@start='console.log(payload)'
@end='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"AdvancedRangeSlider\"], [data-component=\"AdvancedRangeSlider\"]")
if (component) registerOutputHandler(component, "input", (payload) =&gt; <span>&#123;</span>
console.log("input", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "start", (payload) =&gt; <span>&#123;</span>
console.log("start", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "end", (payload) =&gt; <span>&#123;</span>
console.log("end", payload)
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Advanced Range Slider"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main>
<aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#advanced-range-slider-demo-1">Production default</a><a href="#advanced-range-slider-demo-2">Compact application</a><a href="#advanced-range-slider-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
</aside>
</div>
<nav class="detail-pagination">
<a href="/components/advanced-date-picker"><small>Previous</small><strong>← Advanced Date Picker</strong></a>
<a href="/components/chart"><small>Next</small><strong>Chart →</strong></a>
</nav>
}
}
@@ -1,12 +1,17 @@
// Generated by scripts/generate-showcase.mjs. Do not edit directly.
page ChartDetail {
layout = "showcase"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
state playground_title = ctx.url.searchParams.get("pg_title") ?? "Chart example"
state playground_description = ctx.url.searchParams.get("pg_description") ?? "A polished default chart for a modern application."
state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"chart-0-1\",\"key\":\"chart-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#chart-demo\",\"actionHref\":\"#chart-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"chart-0-2\",\"key\":\"chart-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#chart-demo\",\"actionHref\":\"#chart-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]")
state playground_variant = ctx.url.searchParams.get("pg_variant") ?? "default"
state playground_valueKey = ctx.url.searchParams.get("pg_valueKey") ?? "value"
state playground_labelKey = ctx.url.searchParams.get("pg_labelKey") ?? "label"
state playground_height = Number(ctx.url.searchParams.get("pg_height") ?? "240")
state playground_showLegend = (ctx.url.searchParams.get("pg_showLegend") ?? "true") === "true"
state playground_showValues = (ctx.url.searchParams.get("pg_showValues") ?? "true") === "true"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
state playground_variant = ctx.url.searchParams.get("pg_variant") ?? "bar"
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
seo {
title = "Chart"
@@ -21,7 +26,7 @@ page ChartDetail {
<span class="showcase-eyebrow">Integrations component</span>
<h1>Chart</h1>
<p>Theme-aware, responsive chart component.</p>
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>3 outputs</span><span>Theme ready</span><span>Responsive</span></div>
<div class="detail-badges"><span>12 props</span><span>1 slots</span><span>3 outputs</span><span>Theme ready</span><span>Responsive</span></div>
</div>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header>
@@ -30,7 +35,7 @@ page ChartDetail {
<div class="playground-workbench">
<div class="playground-preview">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="playground-preview-source" data-playground-preview><Chart size='{playground_size}' color='{playground_color}' title='{playground_title}' description='{playground_description}' items='{playground_items}' variant='{playground_variant}' class='{playground_class}' /></div>
<div class="playground-preview-source" data-playground-preview><Chart title='{playground_title}' description='{playground_description}' items='{playground_items}' valueKey='{playground_valueKey}' labelKey='{playground_labelKey}' height='{playground_height}' showLegend='{playground_showLegend}' showValues='{playground_showValues}' size='{playground_size}' color='{playground_color}' variant='{playground_variant}' class='{playground_class}' /></div>
<section class="playground-code" aria-label="Current component code">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;Chart
@@ -150,8 +155,8 @@ page ChartDetail {
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
</div>
<form class="playground-controls" data-playground-form>
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
<div class="playground-fields"><label class="playground-field" for="playground-chart-size"><span><strong>size</strong><small>string</small></span><select id="playground-chart-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-chart-color"><span><strong>color</strong><small>string</small></span><select id="playground-chart-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-chart-title"><span><strong>title</strong><small>string</small></span><input id="playground-chart-title" name="pg_title" type="text" value="Chart example" /></label><label class="playground-field" for="playground-chart-description"><span><strong>description</strong><small>string</small></span><input id="playground-chart-description" name="pg_description" type="text" value="A polished default chart for a modern application." /></label><label class="playground-field" for="playground-chart-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-chart-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[
<header><div><span>Component props</span><strong>12 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
<div class="playground-fields"><label class="playground-field" for="playground-chart-title"><span><strong>title</strong><small>string</small></span><input id="playground-chart-title" name="pg_title" type="text" value="Chart example" /></label><label class="playground-field" for="playground-chart-description"><span><strong>description</strong><small>string</small></span><input id="playground-chart-description" name="pg_description" type="text" value="A polished default chart for a modern application." /></label><label class="playground-field" for="playground-chart-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-chart-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[
&#123;
"id": "chart-0-1",
"key": "chart-0-1",
@@ -258,7 +263,7 @@ page ChartDetail {
"Standard"
]
&#125;
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-chart-variant"><span><strong>variant</strong><small>string</small></span><input id="playground-chart-variant" name="pg_variant" type="text" value="default" /></label><label class="playground-field" for="playground-chart-class"><span><strong>class</strong><small>string</small></span><input id="playground-chart-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-chart-valueKey"><span><strong>value Key</strong><small>string</small></span><input id="playground-chart-valueKey" name="pg_valueKey" type="text" value="value" /></label><label class="playground-field" for="playground-chart-labelKey"><span><strong>label Key</strong><small>string</small></span><input id="playground-chart-labelKey" name="pg_labelKey" type="text" value="label" /></label><label class="playground-field" for="playground-chart-height"><span><strong>height</strong><small>number</small></span><input id="playground-chart-height" name="pg_height" type="number" value="240" /></label><label class="playground-toggle" for="playground-chart-showLegend"><span><strong>show Legend</strong><small>boolean</small></span><input id="playground-chart-showLegend" name="pg_showLegend" type="checkbox" value="true" checked data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-toggle" for="playground-chart-showValues"><span><strong>show Values</strong><small>boolean</small></span><input id="playground-chart-showValues" name="pg_showValues" type="checkbox" value="true" checked data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-field" for="playground-chart-size"><span><strong>size</strong><small>string</small></span><select id="playground-chart-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-chart-color"><span><strong>color</strong><small>string</small></span><select id="playground-chart-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-chart-variant"><span><strong>variant</strong><small>string</small></span><input id="playground-chart-variant" name="pg_variant" type="text" value="bar" /></label><label class="playground-field" for="playground-chart-class"><span><strong>class</strong><small>string</small></span><input id="playground-chart-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
</form>
</div>
</section>
@@ -274,7 +279,7 @@ page ChartDetail {
<div id="chart-demo-1" class="demo-canvas demo-canvas--1">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Chart size="default" title="Chart example" description="A polished default chart for a modern application." items='[{"id":"chart-0-1","key":"chart-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"chart-0-2","key":"chart-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
<div class="demo-render"><Chart title="Chart example" description="A polished default chart for a modern application." items='[{"id":"chart-0-1","key":"chart-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"chart-0-2","key":"chart-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' height="240" showLegend="true" showValues="true" size="default" variant="bar" class="showcase-instance showcase-instance--1" /></div>
</div>
<section class="demo-code" aria-label="Production default usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
@@ -402,12 +407,11 @@ page ChartDetail {
<div id="chart-demo-2" class="demo-canvas demo-canvas--2">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Chart size="sm" title="Compact Chart" description="A compact configuration for dashboards and dense product surfaces." items='[{"id":"chart-1-1","key":"chart-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"chart-1-2","key":"chart-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="secondary" class="showcase-instance showcase-instance--2" /></div>
<div class="demo-render"><Chart title="Compact Chart" description="A compact configuration for dashboards and dense product surfaces." items='[{"id":"chart-1-1","key":"chart-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"chart-1-2","key":"chart-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' height="240" showLegend="true" showValues="true" size="sm" variant="secondary" class="showcase-instance showcase-instance--2" /></div>
</div>
<section class="demo-code" aria-label="Compact application usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Chart
size="sm"
title="Compact Chart"
description="A compact configuration for dashboards and dense product surfaces."
items='[
@@ -518,6 +522,7 @@ page ChartDetail {
]
<span>&#125;</span>
]'
size="sm"
variant="secondary"
/&gt;</code></pre>
</section>
@@ -532,12 +537,11 @@ page ChartDetail {
<div id="chart-demo-3" class="demo-canvas demo-canvas--3">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Chart size="lg" title="Advanced Chart" description="A richer configuration demonstrating additional content and hierarchy." items='[{"id":"chart-2-1","key":"chart-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"chart-2-2","key":"chart-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' variant="success" class="showcase-instance showcase-instance--3" /></div>
<div class="demo-render"><Chart title="Advanced Chart" description="A richer configuration demonstrating additional content and hierarchy." items='[{"id":"chart-2-1","key":"chart-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"chart-2-2","key":"chart-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' height="240" showLegend="true" showValues="true" size="lg" variant="success" class="showcase-instance showcase-instance--3" /></div>
</div>
<section class="demo-code" aria-label="Rich configuration usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Chart
size="lg"
title="Advanced Chart"
description="A richer configuration demonstrating additional content and hierarchy."
items='[
@@ -648,6 +652,7 @@ page ChartDetail {
]
<span>&#125;</span>
]'
size="lg"
variant="success"
/&gt;</code></pre>
</section>
@@ -672,14 +677,14 @@ if (component) registerOutputHandler(component, "dataPointClick", (payload) =&gt
if (component) registerOutputHandler(component, "legendToggle", (payload) =&gt; <span>&#123;</span>
console.log("legendToggle", payload)
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Chart"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>title</code></td><td>string</td><td><code>"Chart"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>valueKey</code></td><td>string</td><td><code>"value"</code></td><td>No</td></tr><tr><td><code>labelKey</code></td><td>string</td><td><code>"label"</code></td><td>No</td></tr><tr><td><code>height</code></td><td>number</td><td><code>240</code></td><td>No</td></tr><tr><td><code>showLegend</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>showValues</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"bar"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main>
<aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#chart-demo-1">Production default</a><a href="#chart-demo-2">Compact application</a><a href="#chart-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
</aside>
</div>
<nav class="detail-pagination">
<a href="/components/advanced-range-slider"><small>Previous</small><strong>← Advanced Range Slider</strong></a>
<span></span>
<a href="/components/clipboard"><small>Next</small><strong>Clipboard →</strong></a>
</nav>
}
@@ -1,12 +1,16 @@
// Generated by scripts/generate-showcase.mjs. Do not edit directly.
page ClipboardDetail {
layout = "showcase"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
state playground_value = ctx.url.searchParams.get("pg_value") ?? "sample-1"
state playground_label = ctx.url.searchParams.get("pg_label") ?? "Clipboard"
state playground_copiedLabel = ctx.url.searchParams.get("pg_copiedLabel") ?? "Clipboard"
state playground_errorLabel = ctx.url.searchParams.get("pg_errorLabel") ?? "Clipboard"
state playground_title = ctx.url.searchParams.get("pg_title") ?? "Clipboard example"
state playground_description = ctx.url.searchParams.get("pg_description") ?? "A polished default clipboard for a modern application."
state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"clipboard-0-1\",\"key\":\"clipboard-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#clipboard-demo\",\"actionHref\":\"#clipboard-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"clipboard-0-2\",\"key\":\"clipboard-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#clipboard-demo\",\"actionHref\":\"#clipboard-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]")
state playground_variant = ctx.url.searchParams.get("pg_variant") ?? "default"
state playground_code = (ctx.url.searchParams.get("pg_code") ?? "true") === "true"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
state playground_disabled = (ctx.url.searchParams.get("pg_disabled") ?? "false") === "true"
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
seo {
title = "Clipboard"
@@ -21,7 +25,7 @@ page ClipboardDetail {
<span class="showcase-eyebrow">Integrations component</span>
<h1>Clipboard</h1>
<p>Theme-aware, responsive clipboard component.</p>
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>3 outputs</span><span>Theme ready</span><span>Responsive</span></div>
<div class="detail-badges"><span>11 props</span><span>1 slots</span><span>3 outputs</span><span>Theme ready</span><span>Responsive</span></div>
</div>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header>
@@ -30,235 +34,24 @@ page ClipboardDetail {
<div class="playground-workbench">
<div class="playground-preview">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="playground-preview-source" data-playground-preview><Clipboard size='{playground_size}' color='{playground_color}' title='{playground_title}' description='{playground_description}' items='{playground_items}' variant='{playground_variant}' class='{playground_class}' /></div>
<div class="playground-preview-source" data-playground-preview><Clipboard value='{playground_value}' label='{playground_label}' copiedLabel='{playground_copiedLabel}' errorLabel='{playground_errorLabel}' title='{playground_title}' description='{playground_description}' code='{playground_code}' size='{playground_size}' color='{playground_color}' disabled='{playground_disabled}' class='{playground_class}' /></div>
<section class="playground-code" aria-label="Current component code">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;Clipboard
value="sample-1"
label="Clipboard"
copiedLabel="Clipboard"
errorLabel="Clipboard"
title="Clipboard example"
description="A polished default clipboard for a modern application."
items='[
<span>&#123;</span>
"id": "clipboard-0-1",
"key": "clipboard-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#clipboard-demo",
"actionHref": "#clipboard-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "clipboard-0-2",
"key": "clipboard-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#clipboard-demo",
"actionHref": "#clipboard-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
<div class="playground-status" data-playground-status aria-live="polite"></div>
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
</div>
<form class="playground-controls" data-playground-form>
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
<div class="playground-fields"><label class="playground-field" for="playground-clipboard-size"><span><strong>size</strong><small>string</small></span><select id="playground-clipboard-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-clipboard-color"><span><strong>color</strong><small>string</small></span><select id="playground-clipboard-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-clipboard-title"><span><strong>title</strong><small>string</small></span><input id="playground-clipboard-title" name="pg_title" type="text" value="Clipboard example" /></label><label class="playground-field" for="playground-clipboard-description"><span><strong>description</strong><small>string</small></span><input id="playground-clipboard-description" name="pg_description" type="text" value="A polished default clipboard for a modern application." /></label><label class="playground-field" for="playground-clipboard-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-clipboard-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[
&#123;
"id": "clipboard-0-1",
"key": "clipboard-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#clipboard-demo",
"actionHref": "#clipboard-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
"label": "Nested option B",
"value": "nested-b"
&#125;
],
"links": [
&#123;
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
"label": "API reference",
"href": "#api-reference"
&#125;
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
"id": "clipboard-0-2",
"key": "clipboard-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#clipboard-demo",
"actionHref": "#clipboard-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
"label": "Nested option B",
"value": "nested-b"
&#125;
],
"links": [
&#123;
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
"label": "API reference",
"href": "#api-reference"
&#125;
],
"values": [
"Included",
"Standard"
]
&#125;
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-clipboard-variant"><span><strong>variant</strong><small>string</small></span><input id="playground-clipboard-variant" name="pg_variant" type="text" value="default" /></label><label class="playground-field" for="playground-clipboard-class"><span><strong>class</strong><small>string</small></span><input id="playground-clipboard-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
<header><div><span>Component props</span><strong>11 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
<div class="playground-fields"><label class="playground-field" for="playground-clipboard-value"><span><strong>value</strong><small>string</small></span><input id="playground-clipboard-value" name="pg_value" type="text" value="sample-1" /></label><label class="playground-field" for="playground-clipboard-label"><span><strong>label</strong><small>string</small></span><input id="playground-clipboard-label" name="pg_label" type="text" value="Clipboard" /></label><label class="playground-field" for="playground-clipboard-copiedLabel"><span><strong>copied Label</strong><small>string</small></span><input id="playground-clipboard-copiedLabel" name="pg_copiedLabel" type="text" value="Clipboard" /></label><label class="playground-field" for="playground-clipboard-errorLabel"><span><strong>error Label</strong><small>string</small></span><input id="playground-clipboard-errorLabel" name="pg_errorLabel" type="text" value="Clipboard" /></label><label class="playground-field" for="playground-clipboard-title"><span><strong>title</strong><small>string</small></span><input id="playground-clipboard-title" name="pg_title" type="text" value="Clipboard example" /></label><label class="playground-field" for="playground-clipboard-description"><span><strong>description</strong><small>string</small></span><input id="playground-clipboard-description" name="pg_description" type="text" value="A polished default clipboard for a modern application." /></label><label class="playground-toggle" for="playground-clipboard-code"><span><strong>code</strong><small>boolean</small></span><input id="playground-clipboard-code" name="pg_code" type="checkbox" value="true" checked data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-field" for="playground-clipboard-size"><span><strong>size</strong><small>string</small></span><select id="playground-clipboard-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-clipboard-color"><span><strong>color</strong><small>string</small></span><select id="playground-clipboard-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-toggle" for="playground-clipboard-disabled"><span><strong>disabled</strong><small>boolean</small></span><input id="playground-clipboard-disabled" name="pg_disabled" type="checkbox" value="true" data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-field" for="playground-clipboard-class"><span><strong>class</strong><small>string</small></span><input id="playground-clipboard-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
</form>
</div>
</section>
@@ -274,121 +67,17 @@ page ClipboardDetail {
<div id="clipboard-demo-1" class="demo-canvas demo-canvas--1">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Clipboard size="default" title="Clipboard example" description="A polished default clipboard for a modern application." items='[{"id":"clipboard-0-1","key":"clipboard-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#clipboard-demo","actionHref":"#clipboard-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"clipboard-0-2","key":"clipboard-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#clipboard-demo","actionHref":"#clipboard-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
<div class="demo-render"><Clipboard value="sample-1" label="Clipboard" copiedLabel="Clipboard" errorLabel="Clipboard" title="Clipboard example" description="A polished default clipboard for a modern application." code="true" size="default" disabled="false" class="showcase-instance showcase-instance--1" /></div>
</div>
<section class="demo-code" aria-label="Production default usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Clipboard
value="sample-1"
label="Clipboard"
copiedLabel="Clipboard"
errorLabel="Clipboard"
title="Clipboard example"
description="A polished default clipboard for a modern application."
items='[
<span>&#123;</span>
"id": "clipboard-0-1",
"key": "clipboard-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#clipboard-demo",
"actionHref": "#clipboard-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "clipboard-0-2",
"key": "clipboard-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#clipboard-demo",
"actionHref": "#clipboard-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
</div>
@@ -402,123 +91,18 @@ page ClipboardDetail {
<div id="clipboard-demo-2" class="demo-canvas demo-canvas--2">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Clipboard size="sm" title="Compact Clipboard" description="A compact configuration for dashboards and dense product surfaces." items='[{"id":"clipboard-1-1","key":"clipboard-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#clipboard-demo","actionHref":"#clipboard-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"clipboard-1-2","key":"clipboard-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#clipboard-demo","actionHref":"#clipboard-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="secondary" class="showcase-instance showcase-instance--2" /></div>
<div class="demo-render"><Clipboard value="sample-2" label="Compact example" copiedLabel="Compact example" errorLabel="Compact example" title="Compact Clipboard" description="A compact configuration for dashboards and dense product surfaces." code="true" size="sm" disabled="false" class="showcase-instance showcase-instance--2" /></div>
</div>
<section class="demo-code" aria-label="Compact application usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Clipboard
size="sm"
value="sample-2"
label="Compact example"
copiedLabel="Compact example"
errorLabel="Compact example"
title="Compact Clipboard"
description="A compact configuration for dashboards and dense product surfaces."
items='[
<span>&#123;</span>
"id": "clipboard-1-1",
"key": "clipboard-1-1",
"value": "primary",
"label": "Secondary workflow",
"title": "Secondary workflow",
"description": "A compact configuration designed for dense application layouts.",
"text": "A configurable sample message.",
"href": "#clipboard-demo",
"actionHref": "#clipboard-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 24,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "clipboard-1-2",
"key": "clipboard-1-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A compact configuration designed for dense application layouts.",
"text": "Another configurable message.",
"href": "#clipboard-demo",
"actionHref": "#clipboard-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 48,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
variant="secondary"
size="sm"
/&gt;</code></pre>
</section>
</div>
@@ -532,123 +116,18 @@ page ClipboardDetail {
<div id="clipboard-demo-3" class="demo-canvas demo-canvas--3">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Clipboard size="lg" title="Advanced Clipboard" description="A richer configuration demonstrating additional content and hierarchy." items='[{"id":"clipboard-2-1","key":"clipboard-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#clipboard-demo","actionHref":"#clipboard-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"clipboard-2-2","key":"clipboard-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#clipboard-demo","actionHref":"#clipboard-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' variant="success" class="showcase-instance showcase-instance--3" /></div>
<div class="demo-render"><Clipboard value="sample-3" label="Advanced example" copiedLabel="Advanced example" errorLabel="Advanced example" title="Advanced Clipboard" description="A richer configuration demonstrating additional content and hierarchy." code="true" size="lg" disabled="false" class="showcase-instance showcase-instance--3" /></div>
</div>
<section class="demo-code" aria-label="Rich configuration usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Clipboard
size="lg"
value="sample-3"
label="Advanced example"
copiedLabel="Advanced example"
errorLabel="Advanced example"
title="Advanced Clipboard"
description="A richer configuration demonstrating additional content and hierarchy."
items='[
<span>&#123;</span>
"id": "clipboard-2-1",
"key": "clipboard-2-1",
"value": "primary",
"label": "Advanced workflow",
"title": "Advanced workflow",
"description": "A richer configuration with more supporting information and actions.",
"text": "A configurable sample message.",
"href": "#clipboard-demo",
"actionHref": "#clipboard-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 32,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "clipboard-2-2",
"key": "clipboard-2-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A richer configuration with more supporting information and actions.",
"text": "Another configurable message.",
"href": "#clipboard-demo",
"actionHref": "#clipboard-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": false,
"selected": false,
"checked": false,
"disabled": true,
"outgoing": true,
"open": true,
"number": 2,
"count": 64,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>
]'
variant="success"
size="lg"
/&gt;</code></pre>
</section>
</div>
@@ -672,7 +151,7 @@ if (component) registerOutputHandler(component, "success", (payload) =&gt; <span
if (component) registerOutputHandler(component, "error", (payload) =&gt; <span>&#123;</span>
console.log("error", payload)
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Clipboard"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>value</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Copy"</code></td><td>No</td></tr><tr><td><code>copiedLabel</code></td><td>string</td><td><code>"Copied"</code></td><td>No</td></tr><tr><td><code>errorLabel</code></td><td>string</td><td><code>"Copy failed"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Clipboard"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>code</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>disabled</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main>
<aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#clipboard-demo-1">Production default</a><a href="#clipboard-demo-2">Compact application</a><a href="#clipboard-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
@@ -1393,7 +1393,7 @@ if (component) registerOutputHandler(component, "error", (payload) =&gt; <span>&
</div>
<nav class="detail-pagination">
<a href="/components/advanced-select"><small>Previous</small><strong>← Advanced Select</strong></a>
<a href="/components/copy-markup"><small>Next</small><strong>Copy Markup →</strong></a>
<a href="/components/input-number"><small>Next</small><strong>Input Number →</strong></a>
</nav>
}
}
@@ -1,12 +1,13 @@
// Generated by scripts/generate-showcase.mjs. Do not edit directly.
page ConfettiDetail {
layout = "showcase"
state playground_label = ctx.url.searchParams.get("pg_label") ?? "Confetti"
state playground_duration = Number(ctx.url.searchParams.get("pg_duration") ?? "1200")
state playground_count = Number(ctx.url.searchParams.get("pg_count") ?? "3")
state playground_autoStart = (ctx.url.searchParams.get("pg_autoStart") ?? "false") === "true"
state playground_disabled = (ctx.url.searchParams.get("pg_disabled") ?? "false") === "true"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
state playground_title = ctx.url.searchParams.get("pg_title") ?? "Confetti example"
state playground_description = ctx.url.searchParams.get("pg_description") ?? "A polished default confetti for a modern application."
state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"confetti-0-1\",\"key\":\"confetti-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#confetti-demo\",\"actionHref\":\"#confetti-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"confetti-0-2\",\"key\":\"confetti-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#confetti-demo\",\"actionHref\":\"#confetti-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]")
state playground_variant = ctx.url.searchParams.get("pg_variant") ?? "default"
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
seo {
title = "Confetti"
@@ -21,7 +22,7 @@ page ConfettiDetail {
<span class="showcase-eyebrow">Integrations component</span>
<h1>Confetti</h1>
<p>Theme-aware, responsive confetti component.</p>
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>2 outputs</span><span>Theme ready</span><span>Responsive</span></div>
<div class="detail-badges"><span>8 props</span><span>1 slots</span><span>2 outputs</span><span>Theme ready</span><span>Responsive</span></div>
</div>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header>
@@ -30,235 +31,20 @@ page ConfettiDetail {
<div class="playground-workbench">
<div class="playground-preview">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="playground-preview-source" data-playground-preview><Confetti size='{playground_size}' color='{playground_color}' title='{playground_title}' description='{playground_description}' items='{playground_items}' variant='{playground_variant}' class='{playground_class}' /></div>
<div class="playground-preview-source" data-playground-preview><Confetti label='{playground_label}' duration='{playground_duration}' count='{playground_count}' autoStart='{playground_autoStart}' disabled='{playground_disabled}' size='{playground_size}' color='{playground_color}' class='{playground_class}' /></div>
<section class="playground-code" aria-label="Current component code">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;Confetti
title="Confetti example"
description="A polished default confetti for a modern application."
items='[
<span>&#123;</span>
"id": "confetti-0-1",
"key": "confetti-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#confetti-demo",
"actionHref": "#confetti-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "confetti-0-2",
"key": "confetti-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#confetti-demo",
"actionHref": "#confetti-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
label="Confetti"
count="3"
/&gt;</code></pre>
</section>
<div class="playground-status" data-playground-status aria-live="polite"></div>
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
</div>
<form class="playground-controls" data-playground-form>
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
<div class="playground-fields"><label class="playground-field" for="playground-confetti-size"><span><strong>size</strong><small>string</small></span><select id="playground-confetti-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-confetti-color"><span><strong>color</strong><small>string</small></span><select id="playground-confetti-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-confetti-title"><span><strong>title</strong><small>string</small></span><input id="playground-confetti-title" name="pg_title" type="text" value="Confetti example" /></label><label class="playground-field" for="playground-confetti-description"><span><strong>description</strong><small>string</small></span><input id="playground-confetti-description" name="pg_description" type="text" value="A polished default confetti for a modern application." /></label><label class="playground-field" for="playground-confetti-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-confetti-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[
&#123;
"id": "confetti-0-1",
"key": "confetti-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#confetti-demo",
"actionHref": "#confetti-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
"label": "Nested option B",
"value": "nested-b"
&#125;
],
"links": [
&#123;
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
"label": "API reference",
"href": "#api-reference"
&#125;
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
"id": "confetti-0-2",
"key": "confetti-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#confetti-demo",
"actionHref": "#confetti-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
"label": "Nested option B",
"value": "nested-b"
&#125;
],
"links": [
&#123;
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
"label": "API reference",
"href": "#api-reference"
&#125;
],
"values": [
"Included",
"Standard"
]
&#125;
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-confetti-variant"><span><strong>variant</strong><small>string</small></span><input id="playground-confetti-variant" name="pg_variant" type="text" value="default" /></label><label class="playground-field" for="playground-confetti-class"><span><strong>class</strong><small>string</small></span><input id="playground-confetti-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
<header><div><span>Component props</span><strong>8 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
<div class="playground-fields"><label class="playground-field" for="playground-confetti-label"><span><strong>label</strong><small>string</small></span><input id="playground-confetti-label" name="pg_label" type="text" value="Confetti" /></label><label class="playground-field" for="playground-confetti-duration"><span><strong>duration</strong><small>number</small></span><input id="playground-confetti-duration" name="pg_duration" type="number" value="1200" /></label><label class="playground-field" for="playground-confetti-count"><span><strong>count</strong><small>number</small></span><input id="playground-confetti-count" name="pg_count" type="number" value="3" /></label><label class="playground-toggle" for="playground-confetti-autoStart"><span><strong>auto Start</strong><small>boolean</small></span><input id="playground-confetti-autoStart" name="pg_autoStart" type="checkbox" value="true" data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-toggle" for="playground-confetti-disabled"><span><strong>disabled</strong><small>boolean</small></span><input id="playground-confetti-disabled" name="pg_disabled" type="checkbox" value="true" data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-field" for="playground-confetti-size"><span><strong>size</strong><small>string</small></span><select id="playground-confetti-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-confetti-color"><span><strong>color</strong><small>string</small></span><select id="playground-confetti-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-confetti-class"><span><strong>class</strong><small>string</small></span><input id="playground-confetti-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
</form>
</div>
</section>
@@ -274,121 +60,13 @@ page ConfettiDetail {
<div id="confetti-demo-1" class="demo-canvas demo-canvas--1">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Confetti size="default" title="Confetti example" description="A polished default confetti for a modern application." items='[{"id":"confetti-0-1","key":"confetti-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#confetti-demo","actionHref":"#confetti-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"confetti-0-2","key":"confetti-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#confetti-demo","actionHref":"#confetti-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
<div class="demo-render"><Confetti label="Confetti" duration="1200" count="3" autoStart="false" disabled="false" size="default" class="showcase-instance showcase-instance--1" /></div>
</div>
<section class="demo-code" aria-label="Production default usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Confetti
title="Confetti example"
description="A polished default confetti for a modern application."
items='[
<span>&#123;</span>
"id": "confetti-0-1",
"key": "confetti-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#confetti-demo",
"actionHref": "#confetti-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "confetti-0-2",
"key": "confetti-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#confetti-demo",
"actionHref": "#confetti-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
label="Confetti"
count="3"
/&gt;</code></pre>
</section>
</div>
@@ -402,123 +80,14 @@ page ConfettiDetail {
<div id="confetti-demo-2" class="demo-canvas demo-canvas--2">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Confetti size="sm" title="Compact Confetti" description="A compact configuration for dashboards and dense product surfaces." items='[{"id":"confetti-1-1","key":"confetti-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#confetti-demo","actionHref":"#confetti-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"confetti-1-2","key":"confetti-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#confetti-demo","actionHref":"#confetti-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="secondary" class="showcase-instance showcase-instance--2" /></div>
<div class="demo-render"><Confetti label="Compact example" duration="1200" count="7" autoStart="false" disabled="false" size="sm" class="showcase-instance showcase-instance--2" /></div>
</div>
<section class="demo-code" aria-label="Compact application usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Confetti
label="Compact example"
count="7"
size="sm"
title="Compact Confetti"
description="A compact configuration for dashboards and dense product surfaces."
items='[
<span>&#123;</span>
"id": "confetti-1-1",
"key": "confetti-1-1",
"value": "primary",
"label": "Secondary workflow",
"title": "Secondary workflow",
"description": "A compact configuration designed for dense application layouts.",
"text": "A configurable sample message.",
"href": "#confetti-demo",
"actionHref": "#confetti-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 24,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "confetti-1-2",
"key": "confetti-1-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A compact configuration designed for dense application layouts.",
"text": "Another configurable message.",
"href": "#confetti-demo",
"actionHref": "#confetti-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 48,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
variant="secondary"
/&gt;</code></pre>
</section>
</div>
@@ -532,123 +101,14 @@ page ConfettiDetail {
<div id="confetti-demo-3" class="demo-canvas demo-canvas--3">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Confetti size="lg" title="Advanced Confetti" description="A richer configuration demonstrating additional content and hierarchy." items='[{"id":"confetti-2-1","key":"confetti-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#confetti-demo","actionHref":"#confetti-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"confetti-2-2","key":"confetti-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#confetti-demo","actionHref":"#confetti-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' variant="success" class="showcase-instance showcase-instance--3" /></div>
<div class="demo-render"><Confetti label="Advanced example" duration="1200" count="11" autoStart="false" disabled="false" size="lg" class="showcase-instance showcase-instance--3" /></div>
</div>
<section class="demo-code" aria-label="Rich configuration usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Confetti
label="Advanced example"
count="11"
size="lg"
title="Advanced Confetti"
description="A richer configuration demonstrating additional content and hierarchy."
items='[
<span>&#123;</span>
"id": "confetti-2-1",
"key": "confetti-2-1",
"value": "primary",
"label": "Advanced workflow",
"title": "Advanced workflow",
"description": "A richer configuration with more supporting information and actions.",
"text": "A configurable sample message.",
"href": "#confetti-demo",
"actionHref": "#confetti-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 32,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "confetti-2-2",
"key": "confetti-2-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A richer configuration with more supporting information and actions.",
"text": "Another configurable message.",
"href": "#confetti-demo",
"actionHref": "#confetti-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": false,
"selected": false,
"checked": false,
"disabled": true,
"outgoing": true,
"open": true,
"number": 2,
"count": 64,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>
]'
variant="success"
/&gt;</code></pre>
</section>
</div>
@@ -667,7 +127,7 @@ if (component) registerOutputHandler(component, "start", (payload) =&gt; <span>&
if (component) registerOutputHandler(component, "complete", (payload) =&gt; <span>&#123;</span>
console.log("complete", payload)
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Confetti"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>label</code></td><td>string</td><td><code>"Celebrate"</code></td><td>No</td></tr><tr><td><code>duration</code></td><td>number</td><td><code>1200</code></td><td>No</td></tr><tr><td><code>count</code></td><td>number</td><td><code>24</code></td><td>No</td></tr><tr><td><code>autoStart</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>disabled</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main>
<aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#confetti-demo-1">Production default</a><a href="#confetti-demo-2">Compact application</a><a href="#confetti-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
@@ -1,153 +0,0 @@
// Generated by scripts/generate-showcase.mjs. Do not edit directly.
page CopyMarkupDetail {
layout = "showcase"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
state playground_label = ctx.url.searchParams.get("pg_label") ?? "Copy Markup"
state playground_name = ctx.url.searchParams.get("pg_name") ?? ""
state playground_value = ctx.url.searchParams.get("pg_value") ?? "sample-1"
state playground_placeholder = ctx.url.searchParams.get("pg_placeholder") ?? "Enter a sample value"
state playground_type = ctx.url.searchParams.get("pg_type") ?? "text"
state playground_min = ctx.url.searchParams.get("pg_min") ?? ""
state playground_max = ctx.url.searchParams.get("pg_max") ?? ""
state playground_step = ctx.url.searchParams.get("pg_step") ?? ""
state playground_disabled = (ctx.url.searchParams.get("pg_disabled") ?? "false") === "true"
state playground_required = (ctx.url.searchParams.get("pg_required") ?? "false") === "true"
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
seo {
title = "Copy Markup"
description = "Theme-aware, responsive copy markup component."
}
view {
<nav class="docs-breadcrumbs" aria-label="Breadcrumb">
<a href="/">Components</a><span aria-hidden="true">/</span><a href="/advanced-forms">Advanced Forms</a><span aria-hidden="true">/</span><span>Copy Markup</span>
</nav>
<header class="detail-hero">
<div class="detail-hero-copy">
<span class="showcase-eyebrow">Advanced Forms component</span>
<h1>Copy Markup</h1>
<p>Theme-aware, responsive copy markup component.</p>
<div class="detail-badges"><span>13 props</span><span>0 slots</span><span>3 outputs</span><span>Theme ready</span><span>Responsive</span></div>
</div>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header>
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="CopyMarkup" data-playground-public-tag="true" data-playground-has-slot="false" data-playground-events="copy,success,error">
<div class="detail-section-heading"><span>Interactive playground</span><h2>Configure Copy Markup</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div>
<div class="playground-workbench">
<div class="playground-preview">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="playground-preview-source" data-playground-preview><CopyMarkup size='{playground_size}' color='{playground_color}' label='{playground_label}' name='{playground_name}' value='{playground_value}' placeholder='{playground_placeholder}' type='{playground_type}' min='{playground_min}' max='{playground_max}' step='{playground_step}' disabled='{playground_disabled}' required='{playground_required}' class='{playground_class}' /></div>
<section class="playground-code" aria-label="Current component code">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;CopyMarkup
value="sample-1"
placeholder="Enter a sample value"
/&gt;</code></pre>
</section>
<div class="playground-status" data-playground-status aria-live="polite"></div>
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
</div>
<form class="playground-controls" data-playground-form>
<header><div><span>Component props</span><strong>13 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
<div class="playground-fields"><label class="playground-field" for="playground-copy-markup-size"><span><strong>size</strong><small>string</small></span><select id="playground-copy-markup-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-copy-markup-color"><span><strong>color</strong><small>string</small></span><select id="playground-copy-markup-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-copy-markup-label"><span><strong>label</strong><small>string</small></span><input id="playground-copy-markup-label" name="pg_label" type="text" value="Copy Markup" /></label><label class="playground-field" for="playground-copy-markup-name"><span><strong>name</strong><small>string</small></span><input id="playground-copy-markup-name" name="pg_name" type="text" value="" /></label><label class="playground-field" for="playground-copy-markup-value"><span><strong>value</strong><small>string</small></span><input id="playground-copy-markup-value" name="pg_value" type="text" value="sample-1" /></label><label class="playground-field" for="playground-copy-markup-placeholder"><span><strong>placeholder</strong><small>string</small></span><input id="playground-copy-markup-placeholder" name="pg_placeholder" type="text" value="Enter a sample value" /></label><label class="playground-field" for="playground-copy-markup-type"><span><strong>type</strong><small>string</small></span><input id="playground-copy-markup-type" name="pg_type" type="text" value="text" /></label><label class="playground-field" for="playground-copy-markup-min"><span><strong>min</strong><small>string</small></span><input id="playground-copy-markup-min" name="pg_min" type="text" value="" /></label><label class="playground-field" for="playground-copy-markup-max"><span><strong>max</strong><small>string</small></span><input id="playground-copy-markup-max" name="pg_max" type="text" value="" /></label><label class="playground-field" for="playground-copy-markup-step"><span><strong>step</strong><small>string</small></span><input id="playground-copy-markup-step" name="pg_step" type="text" value="" /></label><label class="playground-toggle" for="playground-copy-markup-disabled"><span><strong>disabled</strong><small>boolean</small></span><input id="playground-copy-markup-disabled" name="pg_disabled" type="checkbox" value="true" data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-toggle" for="playground-copy-markup-required"><span><strong>required</strong><small>boolean</small></span><input id="playground-copy-markup-required" name="pg_required" type="checkbox" value="true" data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-field" for="playground-copy-markup-class"><span><strong>class</strong><small>string</small></span><input id="playground-copy-markup-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
</form>
</div>
</section>
<div class="detail-layout">
<main class="detail-content">
<section class="detail-section"><div class="detail-section-heading"><span>Live examples</span><h2>Designed for real product surfaces</h2><p>Compare configurations and resize the browser to check responsive behavior.</p></div><div class="demo-list">
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Recommended</span><h2>Production default</h2><p>Balanced spacing, hierarchy, and content for the most common product workflow.</p></div>
<span class="demo-case-number">01</span>
</header>
<div class="demo-workbench">
<div id="copy-markup-demo-1" class="demo-canvas demo-canvas--1">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><CopyMarkup size="default" label="Copy Markup" value="sample-1" placeholder="Enter a sample value" disabled="false" required="false" class="showcase-instance showcase-instance--1" /></div>
</div>
<section class="demo-code" aria-label="Production default usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;CopyMarkup
value="sample-1"
placeholder="Enter a sample value"
/&gt;</code></pre>
</section>
</div>
</article>
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Dense UI</span><h2>Compact application</h2><p>A tighter variation for dashboards, side panels, tables, and operational interfaces.</p></div>
<span class="demo-case-number">02</span>
</header>
<div class="demo-workbench">
<div id="copy-markup-demo-2" class="demo-canvas demo-canvas--2">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><CopyMarkup size="sm" label="Compact example" value="sample-2" placeholder="Enter a sample value" disabled="false" required="false" class="showcase-instance showcase-instance--2" /></div>
</div>
<section class="demo-code" aria-label="Compact application usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;CopyMarkup
size="sm"
label="Compact example"
value="sample-2"
placeholder="Enter a sample value"
/&gt;</code></pre>
</section>
</div>
</article>
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Extended</span><h2>Rich configuration</h2><p>A more expressive variation using additional data, stronger emphasis, and optional states.</p></div>
<span class="demo-case-number">03</span>
</header>
<div class="demo-workbench">
<div id="copy-markup-demo-3" class="demo-canvas demo-canvas--3">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><CopyMarkup size="lg" label="Advanced example" value="sample-3" placeholder="Search by name, email, or identifier" disabled="false" required="false" class="showcase-instance showcase-instance--3" /></div>
</div>
<section class="demo-code" aria-label="Rich configuration usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;CopyMarkup
size="lg"
label="Advanced example"
value="sample-3"
placeholder="Search by name, email, or identifier"
/&gt;</code></pre>
</section>
</div>
</article></div></section>
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@copy</code><span>Fires when the component emits the copy event.</span></div><div><code>@success</code><span>Fires when the component emits the success event.</span></div><div><code>@error</code><span>Fires when the component cannot complete an operation.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;CopyMarkup
@copy='console.log(payload)'
@success='console.log(payload)'
@error='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"CopyMarkup\"], [data-component=\"CopyMarkup\"]")
if (component) registerOutputHandler(component, "copy", (payload) =&gt; <span>&#123;</span>
console.log("copy", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "success", (payload) =&gt; <span>&#123;</span>
console.log("success", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "error", (payload) =&gt; <span>&#123;</span>
console.log("error", payload)
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Copy Markup"</code></td><td>No</td></tr><tr><td><code>name</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>value</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>placeholder</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>type</code></td><td>string</td><td><code>"text"</code></td><td>No</td></tr><tr><td><code>min</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>max</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>step</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>disabled</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>required</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main>
<aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#copy-markup-demo-1">Production default</a><a href="#copy-markup-demo-2">Compact application</a><a href="#copy-markup-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
</aside>
</div>
<nav class="detail-pagination">
<a href="/components/combo-box"><small>Previous</small><strong>← Combo Box</strong></a>
<a href="/components/input-number"><small>Next</small><strong>Input Number →</strong></a>
</nav>
}
}
@@ -695,7 +695,7 @@ if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>
</div>
<nav class="detail-pagination">
<a href="/components/data-map"><small>Previous</small><strong>← Data Map</strong></a>
<a href="/components/file-upload"><small>Next</small><strong>File Upload →</strong></a>
<a href="/components/map"><small>Next</small><strong>Map →</strong></a>
</nav>
}
}
@@ -1,706 +0,0 @@
// Generated by scripts/generate-showcase.mjs. Do not edit directly.
page FileUploadDetail {
layout = "showcase"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
state playground_title = ctx.url.searchParams.get("pg_title") ?? "File Upload example"
state playground_description = ctx.url.searchParams.get("pg_description") ?? "A polished default file upload for a modern application."
state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"file-upload-0-1\",\"key\":\"file-upload-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#file-upload-demo\",\"actionHref\":\"#file-upload-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"file-upload-0-2\",\"key\":\"file-upload-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#file-upload-demo\",\"actionHref\":\"#file-upload-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]")
state playground_variant = ctx.url.searchParams.get("pg_variant") ?? "default"
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
seo {
title = "File Upload"
description = "Theme-aware, responsive file upload component."
}
view {
<nav class="docs-breadcrumbs" aria-label="Breadcrumb">
<a href="/">Components</a><span aria-hidden="true">/</span><a href="/integrations">Integrations</a><span aria-hidden="true">/</span><span>File Upload</span>
</nav>
<header class="detail-hero">
<div class="detail-hero-copy">
<span class="showcase-eyebrow">Integrations component</span>
<h1>File Upload</h1>
<p>Theme-aware, responsive file upload component.</p>
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>7 outputs</span><span>Theme ready</span><span>Responsive</span></div>
</div>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header>
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="FileUpload" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="select,upload,progress,success,error,cancel,remove">
<div class="detail-section-heading"><span>Interactive playground</span><h2>Configure File Upload</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div>
<div class="playground-workbench">
<div class="playground-preview">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="playground-preview-source" data-playground-preview><FileUpload size='{playground_size}' color='{playground_color}' title='{playground_title}' description='{playground_description}' items='{playground_items}' variant='{playground_variant}' class='{playground_class}' /></div>
<section class="playground-code" aria-label="Current component code">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;FileUpload
title="File Upload example"
description="A polished default file upload for a modern application."
items='[
<span>&#123;</span>
"id": "file-upload-0-1",
"key": "file-upload-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#file-upload-demo",
"actionHref": "#file-upload-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "file-upload-0-2",
"key": "file-upload-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#file-upload-demo",
"actionHref": "#file-upload-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
<div class="playground-status" data-playground-status aria-live="polite"></div>
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
</div>
<form class="playground-controls" data-playground-form>
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
<div class="playground-fields"><label class="playground-field" for="playground-file-upload-size"><span><strong>size</strong><small>string</small></span><select id="playground-file-upload-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-file-upload-color"><span><strong>color</strong><small>string</small></span><select id="playground-file-upload-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-file-upload-title"><span><strong>title</strong><small>string</small></span><input id="playground-file-upload-title" name="pg_title" type="text" value="File Upload example" /></label><label class="playground-field" for="playground-file-upload-description"><span><strong>description</strong><small>string</small></span><input id="playground-file-upload-description" name="pg_description" type="text" value="A polished default file upload for a modern application." /></label><label class="playground-field" for="playground-file-upload-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-file-upload-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[
&#123;
"id": "file-upload-0-1",
"key": "file-upload-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#file-upload-demo",
"actionHref": "#file-upload-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
"label": "Nested option B",
"value": "nested-b"
&#125;
],
"links": [
&#123;
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
"label": "API reference",
"href": "#api-reference"
&#125;
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
"id": "file-upload-0-2",
"key": "file-upload-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#file-upload-demo",
"actionHref": "#file-upload-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
"label": "Nested option B",
"value": "nested-b"
&#125;
],
"links": [
&#123;
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
"label": "API reference",
"href": "#api-reference"
&#125;
],
"values": [
"Included",
"Standard"
]
&#125;
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-file-upload-variant"><span><strong>variant</strong><small>string</small></span><input id="playground-file-upload-variant" name="pg_variant" type="text" value="default" /></label><label class="playground-field" for="playground-file-upload-class"><span><strong>class</strong><small>string</small></span><input id="playground-file-upload-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
</form>
</div>
</section>
<div class="detail-layout">
<main class="detail-content">
<section class="detail-section"><div class="detail-section-heading"><span>Live examples</span><h2>Designed for real product surfaces</h2><p>Compare configurations and resize the browser to check responsive behavior.</p></div><div class="demo-list">
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Recommended</span><h2>Production default</h2><p>Balanced spacing, hierarchy, and content for the most common product workflow.</p></div>
<span class="demo-case-number">01</span>
</header>
<div class="demo-workbench">
<div id="file-upload-demo-1" class="demo-canvas demo-canvas--1">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><FileUpload size="default" title="File Upload example" description="A polished default file upload for a modern application." items='[{"id":"file-upload-0-1","key":"file-upload-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#file-upload-demo","actionHref":"#file-upload-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"file-upload-0-2","key":"file-upload-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#file-upload-demo","actionHref":"#file-upload-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
</div>
<section class="demo-code" aria-label="Production default usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;FileUpload
title="File Upload example"
description="A polished default file upload for a modern application."
items='[
<span>&#123;</span>
"id": "file-upload-0-1",
"key": "file-upload-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#file-upload-demo",
"actionHref": "#file-upload-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "file-upload-0-2",
"key": "file-upload-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#file-upload-demo",
"actionHref": "#file-upload-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
</div>
</article>
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Dense UI</span><h2>Compact application</h2><p>A tighter variation for dashboards, side panels, tables, and operational interfaces.</p></div>
<span class="demo-case-number">02</span>
</header>
<div class="demo-workbench">
<div id="file-upload-demo-2" class="demo-canvas demo-canvas--2">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><FileUpload size="sm" title="Compact File Upload" description="A compact configuration for dashboards and dense product surfaces." items='[{"id":"file-upload-1-1","key":"file-upload-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#file-upload-demo","actionHref":"#file-upload-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"file-upload-1-2","key":"file-upload-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#file-upload-demo","actionHref":"#file-upload-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="secondary" class="showcase-instance showcase-instance--2" /></div>
</div>
<section class="demo-code" aria-label="Compact application usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;FileUpload
size="sm"
title="Compact File Upload"
description="A compact configuration for dashboards and dense product surfaces."
items='[
<span>&#123;</span>
"id": "file-upload-1-1",
"key": "file-upload-1-1",
"value": "primary",
"label": "Secondary workflow",
"title": "Secondary workflow",
"description": "A compact configuration designed for dense application layouts.",
"text": "A configurable sample message.",
"href": "#file-upload-demo",
"actionHref": "#file-upload-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 24,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "file-upload-1-2",
"key": "file-upload-1-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A compact configuration designed for dense application layouts.",
"text": "Another configurable message.",
"href": "#file-upload-demo",
"actionHref": "#file-upload-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 48,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
variant="secondary"
/&gt;</code></pre>
</section>
</div>
</article>
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Extended</span><h2>Rich configuration</h2><p>A more expressive variation using additional data, stronger emphasis, and optional states.</p></div>
<span class="demo-case-number">03</span>
</header>
<div class="demo-workbench">
<div id="file-upload-demo-3" class="demo-canvas demo-canvas--3">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><FileUpload size="lg" title="Advanced File Upload" description="A richer configuration demonstrating additional content and hierarchy." items='[{"id":"file-upload-2-1","key":"file-upload-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#file-upload-demo","actionHref":"#file-upload-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"file-upload-2-2","key":"file-upload-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#file-upload-demo","actionHref":"#file-upload-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' variant="success" class="showcase-instance showcase-instance--3" /></div>
</div>
<section class="demo-code" aria-label="Rich configuration usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;FileUpload
size="lg"
title="Advanced File Upload"
description="A richer configuration demonstrating additional content and hierarchy."
items='[
<span>&#123;</span>
"id": "file-upload-2-1",
"key": "file-upload-2-1",
"value": "primary",
"label": "Advanced workflow",
"title": "Advanced workflow",
"description": "A richer configuration with more supporting information and actions.",
"text": "A configurable sample message.",
"href": "#file-upload-demo",
"actionHref": "#file-upload-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 32,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "file-upload-2-2",
"key": "file-upload-2-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A richer configuration with more supporting information and actions.",
"text": "Another configurable message.",
"href": "#file-upload-demo",
"actionHref": "#file-upload-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": false,
"selected": false,
"checked": false,
"disabled": true,
"outgoing": true,
"open": true,
"number": 2,
"count": 64,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>
]'
variant="success"
/&gt;</code></pre>
</section>
</div>
</article></div></section>
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@select</code><span>Fires when an item, option, card, or result is selected.</span></div><div><code>@upload</code><span>Fires when the component emits the upload event.</span></div><div><code>@progress</code><span>Fires when the component emits the progress event.</span></div><div><code>@success</code><span>Fires when the component emits the success event.</span></div><div><code>@error</code><span>Fires when the component cannot complete an operation.</span></div><div><code>@cancel</code><span>Fires when the user cancels or dismisses the current workflow.</span></div><div><code>@remove</code><span>Fires when the component emits the remove event.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;FileUpload
@select='console.log(payload)'
@upload='console.log(payload)'
@progress='console.log(payload)'
@success='console.log(payload)'
@error='console.log(payload)'
@cancel='console.log(payload)'
@remove='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"FileUpload\"], [data-component=\"FileUpload\"]")
if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span>
console.log("select", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "upload", (payload) =&gt; <span>&#123;</span>
console.log("upload", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "progress", (payload) =&gt; <span>&#123;</span>
console.log("progress", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "success", (payload) =&gt; <span>&#123;</span>
console.log("success", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "error", (payload) =&gt; <span>&#123;</span>
console.log("error", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "cancel", (payload) =&gt; <span>&#123;</span>
console.log("cancel", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "remove", (payload) =&gt; <span>&#123;</span>
console.log("remove", payload)
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"File Upload"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main>
<aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#file-upload-demo-1">Production default</a><a href="#file-upload-demo-2">Compact application</a><a href="#file-upload-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
</aside>
</div>
<nav class="detail-pagination">
<a href="/components/drag-and-drop"><small>Previous</small><strong>← Drag And Drop</strong></a>
<a href="/components/map"><small>Next</small><strong>Map →</strong></a>
</nav>
}
}
@@ -399,7 +399,7 @@ if (component) registerOutputHandler(component, "decrement", (payload) =&gt; <sp
</aside>
</div>
<nav class="detail-pagination">
<a href="/components/copy-markup"><small>Previous</small><strong>← Copy Markup</strong></a>
<a href="/components/combo-box"><small>Previous</small><strong>← Combo Box</strong></a>
<a href="/components/pin-input"><small>Next</small><strong>Pin Input →</strong></a>
</nav>
}
@@ -21,11 +21,11 @@ page ListDetail {
<span class="showcase-eyebrow">Base component</span>
<h1>List</h1>
<p>Present structured responsive linked or status items with icons, descriptions, actions, and selection events.</p>
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>0 outputs</span><span>Theme ready</span><span>Responsive</span></div>
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>1 outputs</span><span>Theme ready</span><span>Responsive</span></div>
</div>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header>
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="List" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="">
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="List" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="select">
<div class="detail-section-heading"><span>Interactive playground</span><h2>Configure List</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div>
<div class="playground-workbench">
<div class="playground-preview">
@@ -147,7 +147,7 @@ page ListDetail {
/&gt;</code></pre>
</section>
<div class="playground-status" data-playground-status aria-live="polite"></div>
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
</div>
<form class="playground-controls" data-playground-form>
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
@@ -374,11 +374,19 @@ page ListDetail {
</section>
</div>
</article></div></section>
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@select</code><span>Fires when an item, option, card, or result is selected.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;List
@select='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"List\"], [data-component=\"List\"]")
if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span>
console.log("select", payload)
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"List"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main>
<aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#list-demo-1">Linked resource list</a><a href="#list-demo-2">Compact status list</a><a href="#list-demo-3">Rich action list</a><a href="#api">Props API</a></div>
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#list-demo-1">Linked resource list</a><a href="#list-demo-2">Compact status list</a><a href="#list-demo-3">Rich action list</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
</aside>
</div>
<nav class="detail-pagination">
@@ -21,11 +21,11 @@ page MapDetail {
<span class="showcase-eyebrow">Integrations component</span>
<h1>Map</h1>
<p>Present responsive location information and markers with map-ready metadata and movement or marker events.</p>
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>0 outputs</span><span>Theme ready</span><span>Responsive</span></div>
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>3 outputs</span><span>Theme ready</span><span>Responsive</span></div>
</div>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header>
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="Map" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="">
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="Map" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="markerClick,select,zoom">
<div class="detail-section-heading"><span>Interactive playground</span><h2>Configure Map</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div>
<div class="playground-workbench">
<div class="playground-preview">
@@ -147,7 +147,7 @@ page MapDetail {
/&gt;</code></pre>
</section>
<div class="playground-status" data-playground-status aria-live="polite"></div>
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
</div>
<form class="playground-controls" data-playground-form>
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
@@ -361,16 +361,34 @@ page MapDetail {
</section>
</div>
</article></div></section>
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@markerClick</code><span>Fires when the component emits the markerClick event.</span></div><div><code>@select</code><span>Fires when an item, option, card, or result is selected.</span></div><div><code>@zoom</code><span>Fires when the component emits the zoom event.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;Map
@markerClick='console.log(payload)'
@select='console.log(payload)'
@zoom='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Map\"], [data-component=\"Map\"]")
if (component) registerOutputHandler(component, "markerClick", (payload) =&gt; <span>&#123;</span>
console.log("markerClick", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span>
console.log("select", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "zoom", (payload) =&gt; <span>&#123;</span>
console.log("zoom", payload)
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Map"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main>
<aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#map-demo-1">Station directory map</a><a href="#map-demo-2">Compact location preview</a><a href="#map-demo-3">Operations map</a><a href="#api">Props API</a></div>
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#map-demo-1">Station directory map</a><a href="#map-demo-2">Compact location preview</a><a href="#map-demo-3">Operations map</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
</aside>
</div>
<nav class="detail-pagination">
<a href="/components/file-upload"><small>Previous</small><strong>← File Upload</strong></a>
<a href="/components/toast-notifications"><small>Next</small><strong>Toast Notifications →</strong></a>
<a href="/components/drag-and-drop"><small>Previous</small><strong>← Drag And Drop</strong></a>
<a href="/components/wysiwyg-editor"><small>Next</small><strong>Wysiwyg Editor →</strong></a>
</nav>
}
}
@@ -21,11 +21,11 @@ page MarqueeDetail {
<span class="showcase-eyebrow">Base component</span>
<h1>Marquee</h1>
<p>Continuously present responsive labels, partners, notices, or capabilities with pause and resume behavior.</p>
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>0 outputs</span><span>Theme ready</span><span>Responsive</span></div>
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>2 outputs</span><span>Theme ready</span><span>Responsive</span></div>
</div>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header>
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="Marquee" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="">
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="Marquee" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="pause,resume">
<div class="detail-section-heading"><span>Interactive playground</span><h2>Configure Marquee</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div>
<div class="playground-workbench">
<div class="playground-preview">
@@ -147,7 +147,7 @@ page MarqueeDetail {
/&gt;</code></pre>
</section>
<div class="playground-status" data-playground-status aria-live="polite"></div>
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
</div>
<form class="playground-controls" data-playground-form>
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
@@ -372,11 +372,24 @@ page MarqueeDetail {
</section>
</div>
</article></div></section>
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@pause</code><span>Fires when the component emits the pause event.</span></div><div><code>@resume</code><span>Fires when the component emits the resume event.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;Marquee
@pause='console.log(payload)'
@resume='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Marquee\"], [data-component=\"Marquee\"]")
if (component) registerOutputHandler(component, "pause", (payload) =&gt; <span>&#123;</span>
console.log("pause", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "resume", (payload) =&gt; <span>&#123;</span>
console.log("resume", payload)
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Marquee"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main>
<aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#marquee-demo-1">Partner and capability strip</a><a href="#marquee-demo-2">Compact announcement ticker</a><a href="#marquee-demo-3">Large visual marquee</a><a href="#api">Props API</a></div>
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#marquee-demo-1">Partner and capability strip</a><a href="#marquee-demo-2">Compact announcement ticker</a><a href="#marquee-demo-3">Large visual marquee</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
</aside>
</div>
<nav class="detail-pagination">
@@ -27,11 +27,11 @@ page SearchBoxDetail {
<span class="showcase-eyebrow">Forms component</span>
<h1>Search Box</h1>
<p>Provide an accessible responsive search field with labels, validation states, sizes, and input or change events.</p>
<div class="detail-badges"><span>13 props</span><span>0 slots</span><span>0 outputs</span><span>Theme ready</span><span>Responsive</span></div>
<div class="detail-badges"><span>13 props</span><span>0 slots</span><span>2 outputs</span><span>Theme ready</span><span>Responsive</span></div>
</div>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header>
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="SearchBox" data-playground-public-tag="true" data-playground-has-slot="false" data-playground-events="">
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="SearchBox" data-playground-public-tag="true" data-playground-has-slot="false" data-playground-events="search,clear">
<div class="detail-section-heading"><span>Interactive playground</span><h2>Configure Search Box</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div>
<div class="playground-workbench">
<div class="playground-preview">
@@ -45,7 +45,7 @@ page SearchBoxDetail {
/&gt;</code></pre>
</section>
<div class="playground-status" data-playground-status aria-live="polite"></div>
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
</div>
<form class="playground-controls" data-playground-form>
<header><div><span>Component props</span><strong>13 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
@@ -126,11 +126,24 @@ page SearchBoxDetail {
</section>
</div>
</article></div></section>
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@search</code><span>Fires when the current search query changes.</span></div><div><code>@clear</code><span>Fires after the current value or selection is cleared.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;SearchBox
@search='console.log(payload)'
@clear='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"SearchBox\"], [data-component=\"SearchBox\"]")
if (component) registerOutputHandler(component, "search", (payload) =&gt; <span>&#123;</span>
console.log("search", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "clear", (payload) =&gt; <span>&#123;</span>
console.log("clear", payload)
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Search Box"</code></td><td>No</td></tr><tr><td><code>name</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>value</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>placeholder</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>type</code></td><td>string</td><td><code>"search"</code></td><td>No</td></tr><tr><td><code>min</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>max</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>step</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>disabled</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>required</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main>
<aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#search-box-demo-1">Site search</a><a href="#search-box-demo-2">Compact directory search</a><a href="#search-box-demo-3">Required service lookup</a><a href="#api">Props API</a></div>
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#search-box-demo-1">Site search</a><a href="#search-box-demo-2">Compact directory search</a><a href="#search-box-demo-3">Required service lookup</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
</aside>
</div>
<nav class="detail-pagination">
@@ -21,11 +21,11 @@ page TimelineDetail {
<span class="showcase-eyebrow">Base component</span>
<h1>Timeline</h1>
<p>Present responsive chronological activity, milestones, or workflow status with rich item metadata.</p>
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>0 outputs</span><span>Theme ready</span><span>Responsive</span></div>
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>1 outputs</span><span>Theme ready</span><span>Responsive</span></div>
</div>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header>
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="Timeline" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="">
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="Timeline" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="select">
<div class="detail-section-heading"><span>Interactive playground</span><h2>Configure Timeline</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div>
<div class="playground-workbench">
<div class="playground-preview">
@@ -147,7 +147,7 @@ page TimelineDetail {
/&gt;</code></pre>
</section>
<div class="playground-status" data-playground-status aria-live="polite"></div>
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
</div>
<form class="playground-controls" data-playground-form>
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
@@ -381,16 +381,24 @@ page TimelineDetail {
</section>
</div>
</article></div></section>
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@select</code><span>Fires when an item, option, card, or result is selected.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;Timeline
@select='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Timeline\"], [data-component=\"Timeline\"]")
if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span>
console.log("select", payload)
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Timeline"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main>
<aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#timeline-demo-1">Application timeline</a><a href="#timeline-demo-2">Compact activity history</a><a href="#timeline-demo-3">Rich milestone timeline</a><a href="#api">Props API</a></div>
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#timeline-demo-1">Application timeline</a><a href="#timeline-demo-2">Compact activity history</a><a href="#timeline-demo-3">Rich milestone timeline</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
</aside>
</div>
<nav class="detail-pagination">
<a href="/components/styled-icon"><small>Previous</small><strong>← Styled Icon</strong></a>
<a href="/components/toast"><small>Next</small><strong>Toast →</strong></a>
<a href="/components/tree-view"><small>Next</small><strong>Tree View →</strong></a>
</nav>
}
}
@@ -1,691 +0,0 @@
// Generated by scripts/generate-showcase.mjs. Do not edit directly.
page ToastNotificationsDetail {
layout = "showcase"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
state playground_title = ctx.url.searchParams.get("pg_title") ?? "Toast Notifications example"
state playground_description = ctx.url.searchParams.get("pg_description") ?? "A polished default toast notifications for a modern application."
state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"toast-notifications-0-1\",\"key\":\"toast-notifications-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#toast-notifications-demo\",\"actionHref\":\"#toast-notifications-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"toast-notifications-0-2\",\"key\":\"toast-notifications-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#toast-notifications-demo\",\"actionHref\":\"#toast-notifications-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]")
state playground_variant = ctx.url.searchParams.get("pg_variant") ?? "default"
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
seo {
title = "Toast Notifications"
description = "Theme-aware, responsive toast notifications component."
}
view {
<nav class="docs-breadcrumbs" aria-label="Breadcrumb">
<a href="/">Components</a><span aria-hidden="true">/</span><a href="/integrations">Integrations</a><span aria-hidden="true">/</span><span>Toast Notifications</span>
</nav>
<header class="detail-hero">
<div class="detail-hero-copy">
<span class="showcase-eyebrow">Integrations component</span>
<h1>Toast Notifications</h1>
<p>Theme-aware, responsive toast notifications component.</p>
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>4 outputs</span><span>Theme ready</span><span>Responsive</span></div>
</div>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header>
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="ToastNotifications" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="add,dismiss,clear,action">
<div class="detail-section-heading"><span>Interactive playground</span><h2>Configure Toast Notifications</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div>
<div class="playground-workbench">
<div class="playground-preview">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="playground-preview-source" data-playground-preview><ToastNotifications size='{playground_size}' color='{playground_color}' title='{playground_title}' description='{playground_description}' items='{playground_items}' variant='{playground_variant}' class='{playground_class}' /></div>
<section class="playground-code" aria-label="Current component code">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;ToastNotifications
title="Toast Notifications example"
description="A polished default toast notifications for a modern application."
items='[
<span>&#123;</span>
"id": "toast-notifications-0-1",
"key": "toast-notifications-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#toast-notifications-demo",
"actionHref": "#toast-notifications-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "toast-notifications-0-2",
"key": "toast-notifications-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#toast-notifications-demo",
"actionHref": "#toast-notifications-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
<div class="playground-status" data-playground-status aria-live="polite"></div>
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
</div>
<form class="playground-controls" data-playground-form>
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
<div class="playground-fields"><label class="playground-field" for="playground-toast-notifications-size"><span><strong>size</strong><small>string</small></span><select id="playground-toast-notifications-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-toast-notifications-color"><span><strong>color</strong><small>string</small></span><select id="playground-toast-notifications-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-toast-notifications-title"><span><strong>title</strong><small>string</small></span><input id="playground-toast-notifications-title" name="pg_title" type="text" value="Toast Notifications example" /></label><label class="playground-field" for="playground-toast-notifications-description"><span><strong>description</strong><small>string</small></span><input id="playground-toast-notifications-description" name="pg_description" type="text" value="A polished default toast notifications for a modern application." /></label><label class="playground-field" for="playground-toast-notifications-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-toast-notifications-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[
&#123;
"id": "toast-notifications-0-1",
"key": "toast-notifications-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#toast-notifications-demo",
"actionHref": "#toast-notifications-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
"label": "Nested option B",
"value": "nested-b"
&#125;
],
"links": [
&#123;
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
"label": "API reference",
"href": "#api-reference"
&#125;
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
"id": "toast-notifications-0-2",
"key": "toast-notifications-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#toast-notifications-demo",
"actionHref": "#toast-notifications-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
"label": "Nested option B",
"value": "nested-b"
&#125;
],
"links": [
&#123;
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
"label": "API reference",
"href": "#api-reference"
&#125;
],
"values": [
"Included",
"Standard"
]
&#125;
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-toast-notifications-variant"><span><strong>variant</strong><small>string</small></span><input id="playground-toast-notifications-variant" name="pg_variant" type="text" value="default" /></label><label class="playground-field" for="playground-toast-notifications-class"><span><strong>class</strong><small>string</small></span><input id="playground-toast-notifications-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
</form>
</div>
</section>
<div class="detail-layout">
<main class="detail-content">
<section class="detail-section"><div class="detail-section-heading"><span>Live examples</span><h2>Designed for real product surfaces</h2><p>Compare configurations and resize the browser to check responsive behavior.</p></div><div class="demo-list">
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Recommended</span><h2>Production default</h2><p>Balanced spacing, hierarchy, and content for the most common product workflow.</p></div>
<span class="demo-case-number">01</span>
</header>
<div class="demo-workbench">
<div id="toast-notifications-demo-1" class="demo-canvas demo-canvas--1">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><ToastNotifications size="default" title="Toast Notifications example" description="A polished default toast notifications for a modern application." items='[{"id":"toast-notifications-0-1","key":"toast-notifications-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#toast-notifications-demo","actionHref":"#toast-notifications-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"toast-notifications-0-2","key":"toast-notifications-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#toast-notifications-demo","actionHref":"#toast-notifications-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
</div>
<section class="demo-code" aria-label="Production default usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;ToastNotifications
title="Toast Notifications example"
description="A polished default toast notifications for a modern application."
items='[
<span>&#123;</span>
"id": "toast-notifications-0-1",
"key": "toast-notifications-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#toast-notifications-demo",
"actionHref": "#toast-notifications-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "toast-notifications-0-2",
"key": "toast-notifications-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#toast-notifications-demo",
"actionHref": "#toast-notifications-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
</div>
</article>
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Dense UI</span><h2>Compact application</h2><p>A tighter variation for dashboards, side panels, tables, and operational interfaces.</p></div>
<span class="demo-case-number">02</span>
</header>
<div class="demo-workbench">
<div id="toast-notifications-demo-2" class="demo-canvas demo-canvas--2">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><ToastNotifications size="sm" title="Compact Toast Notifications" description="A compact configuration for dashboards and dense product surfaces." items='[{"id":"toast-notifications-1-1","key":"toast-notifications-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#toast-notifications-demo","actionHref":"#toast-notifications-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"toast-notifications-1-2","key":"toast-notifications-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#toast-notifications-demo","actionHref":"#toast-notifications-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="secondary" class="showcase-instance showcase-instance--2" /></div>
</div>
<section class="demo-code" aria-label="Compact application usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;ToastNotifications
size="sm"
title="Compact Toast Notifications"
description="A compact configuration for dashboards and dense product surfaces."
items='[
<span>&#123;</span>
"id": "toast-notifications-1-1",
"key": "toast-notifications-1-1",
"value": "primary",
"label": "Secondary workflow",
"title": "Secondary workflow",
"description": "A compact configuration designed for dense application layouts.",
"text": "A configurable sample message.",
"href": "#toast-notifications-demo",
"actionHref": "#toast-notifications-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 24,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "toast-notifications-1-2",
"key": "toast-notifications-1-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A compact configuration designed for dense application layouts.",
"text": "Another configurable message.",
"href": "#toast-notifications-demo",
"actionHref": "#toast-notifications-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 48,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
variant="secondary"
/&gt;</code></pre>
</section>
</div>
</article>
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Extended</span><h2>Rich configuration</h2><p>A more expressive variation using additional data, stronger emphasis, and optional states.</p></div>
<span class="demo-case-number">03</span>
</header>
<div class="demo-workbench">
<div id="toast-notifications-demo-3" class="demo-canvas demo-canvas--3">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><ToastNotifications size="lg" title="Advanced Toast Notifications" description="A richer configuration demonstrating additional content and hierarchy." items='[{"id":"toast-notifications-2-1","key":"toast-notifications-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#toast-notifications-demo","actionHref":"#toast-notifications-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"toast-notifications-2-2","key":"toast-notifications-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#toast-notifications-demo","actionHref":"#toast-notifications-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' variant="success" class="showcase-instance showcase-instance--3" /></div>
</div>
<section class="demo-code" aria-label="Rich configuration usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;ToastNotifications
size="lg"
title="Advanced Toast Notifications"
description="A richer configuration demonstrating additional content and hierarchy."
items='[
<span>&#123;</span>
"id": "toast-notifications-2-1",
"key": "toast-notifications-2-1",
"value": "primary",
"label": "Advanced workflow",
"title": "Advanced workflow",
"description": "A richer configuration with more supporting information and actions.",
"text": "A configurable sample message.",
"href": "#toast-notifications-demo",
"actionHref": "#toast-notifications-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 32,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "toast-notifications-2-2",
"key": "toast-notifications-2-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A richer configuration with more supporting information and actions.",
"text": "Another configurable message.",
"href": "#toast-notifications-demo",
"actionHref": "#toast-notifications-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": false,
"selected": false,
"checked": false,
"disabled": true,
"outgoing": true,
"open": true,
"number": 2,
"count": 64,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>
]'
variant="success"
/&gt;</code></pre>
</section>
</div>
</article></div></section>
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@add</code><span>Fires when the component emits the add event.</span></div><div><code>@dismiss</code><span>Fires after the announcement or feedback surface is dismissed.</span></div><div><code>@clear</code><span>Fires after the current value or selection is cleared.</span></div><div><code>@action</code><span>Fires when the component's primary or item action is activated.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;ToastNotifications
@add='console.log(payload)'
@dismiss='console.log(payload)'
@clear='console.log(payload)'
@action='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"ToastNotifications\"], [data-component=\"ToastNotifications\"]")
if (component) registerOutputHandler(component, "add", (payload) =&gt; <span>&#123;</span>
console.log("add", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "dismiss", (payload) =&gt; <span>&#123;</span>
console.log("dismiss", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "clear", (payload) =&gt; <span>&#123;</span>
console.log("clear", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "action", (payload) =&gt; <span>&#123;</span>
console.log("action", payload)
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Toast Notifications"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main>
<aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#toast-notifications-demo-1">Production default</a><a href="#toast-notifications-demo-2">Compact application</a><a href="#toast-notifications-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
</aside>
</div>
<nav class="detail-pagination">
<a href="/components/map"><small>Previous</small><strong>← Map</strong></a>
<a href="/components/wysiwyg-editor"><small>Next</small><strong>Wysiwyg Editor →</strong></a>
</nav>
}
}
@@ -1,681 +0,0 @@
// Generated by scripts/generate-showcase.mjs. Do not edit directly.
page ToastDetail {
layout = "showcase"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
state playground_title = ctx.url.searchParams.get("pg_title") ?? "Toast example"
state playground_description = ctx.url.searchParams.get("pg_description") ?? "A polished default toast for a modern application."
state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"toast-0-1\",\"key\":\"toast-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#toast-demo\",\"actionHref\":\"#toast-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"toast-0-2\",\"key\":\"toast-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#toast-demo\",\"actionHref\":\"#toast-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]")
state playground_variant = ctx.url.searchParams.get("pg_variant") ?? "default"
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
seo {
title = "Toast"
description = "Theme-aware, responsive toast component."
}
view {
<nav class="docs-breadcrumbs" aria-label="Breadcrumb">
<a href="/">Components</a><span aria-hidden="true">/</span><a href="/base">Base</a><span aria-hidden="true">/</span><span>Toast</span>
</nav>
<header class="detail-hero">
<div class="detail-hero-copy">
<span class="showcase-eyebrow">Base component</span>
<h1>Toast</h1>
<p>Theme-aware, responsive toast component.</p>
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>2 outputs</span><span>Theme ready</span><span>Responsive</span></div>
</div>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header>
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="Toast" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="dismiss,action">
<div class="detail-section-heading"><span>Interactive playground</span><h2>Configure Toast</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div>
<div class="playground-workbench">
<div class="playground-preview">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="playground-preview-source" data-playground-preview><Toast size='{playground_size}' color='{playground_color}' title='{playground_title}' description='{playground_description}' items='{playground_items}' variant='{playground_variant}' class='{playground_class}' /></div>
<section class="playground-code" aria-label="Current component code">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;Toast
title="Toast example"
description="A polished default toast for a modern application."
items='[
<span>&#123;</span>
"id": "toast-0-1",
"key": "toast-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#toast-demo",
"actionHref": "#toast-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "toast-0-2",
"key": "toast-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#toast-demo",
"actionHref": "#toast-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
<div class="playground-status" data-playground-status aria-live="polite"></div>
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
</div>
<form class="playground-controls" data-playground-form>
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
<div class="playground-fields"><label class="playground-field" for="playground-toast-size"><span><strong>size</strong><small>string</small></span><select id="playground-toast-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-toast-color"><span><strong>color</strong><small>string</small></span><select id="playground-toast-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-toast-title"><span><strong>title</strong><small>string</small></span><input id="playground-toast-title" name="pg_title" type="text" value="Toast example" /></label><label class="playground-field" for="playground-toast-description"><span><strong>description</strong><small>string</small></span><input id="playground-toast-description" name="pg_description" type="text" value="A polished default toast for a modern application." /></label><label class="playground-field" for="playground-toast-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-toast-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[
&#123;
"id": "toast-0-1",
"key": "toast-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#toast-demo",
"actionHref": "#toast-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
"label": "Nested option B",
"value": "nested-b"
&#125;
],
"links": [
&#123;
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
"label": "API reference",
"href": "#api-reference"
&#125;
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
"id": "toast-0-2",
"key": "toast-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#toast-demo",
"actionHref": "#toast-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
"label": "Nested option B",
"value": "nested-b"
&#125;
],
"links": [
&#123;
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
"label": "API reference",
"href": "#api-reference"
&#125;
],
"values": [
"Included",
"Standard"
]
&#125;
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-toast-variant"><span><strong>variant</strong><small>string</small></span><input id="playground-toast-variant" name="pg_variant" type="text" value="default" /></label><label class="playground-field" for="playground-toast-class"><span><strong>class</strong><small>string</small></span><input id="playground-toast-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
</form>
</div>
</section>
<div class="detail-layout">
<main class="detail-content">
<section class="detail-section"><div class="detail-section-heading"><span>Live examples</span><h2>Designed for real product surfaces</h2><p>Compare configurations and resize the browser to check responsive behavior.</p></div><div class="demo-list">
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Recommended</span><h2>Production default</h2><p>Balanced spacing, hierarchy, and content for the most common product workflow.</p></div>
<span class="demo-case-number">01</span>
</header>
<div class="demo-workbench">
<div id="toast-demo-1" class="demo-canvas demo-canvas--1">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Toast size="default" title="Toast example" description="A polished default toast for a modern application." items='[{"id":"toast-0-1","key":"toast-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#toast-demo","actionHref":"#toast-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"toast-0-2","key":"toast-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#toast-demo","actionHref":"#toast-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
</div>
<section class="demo-code" aria-label="Production default usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Toast
title="Toast example"
description="A polished default toast for a modern application."
items='[
<span>&#123;</span>
"id": "toast-0-1",
"key": "toast-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#toast-demo",
"actionHref": "#toast-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "toast-0-2",
"key": "toast-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#toast-demo",
"actionHref": "#toast-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
</div>
</article>
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Dense UI</span><h2>Compact application</h2><p>A tighter variation for dashboards, side panels, tables, and operational interfaces.</p></div>
<span class="demo-case-number">02</span>
</header>
<div class="demo-workbench">
<div id="toast-demo-2" class="demo-canvas demo-canvas--2">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Toast size="sm" title="Compact Toast" description="A compact configuration for dashboards and dense product surfaces." items='[{"id":"toast-1-1","key":"toast-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#toast-demo","actionHref":"#toast-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"toast-1-2","key":"toast-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#toast-demo","actionHref":"#toast-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="secondary" class="showcase-instance showcase-instance--2" /></div>
</div>
<section class="demo-code" aria-label="Compact application usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Toast
size="sm"
title="Compact Toast"
description="A compact configuration for dashboards and dense product surfaces."
items='[
<span>&#123;</span>
"id": "toast-1-1",
"key": "toast-1-1",
"value": "primary",
"label": "Secondary workflow",
"title": "Secondary workflow",
"description": "A compact configuration designed for dense application layouts.",
"text": "A configurable sample message.",
"href": "#toast-demo",
"actionHref": "#toast-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 24,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "toast-1-2",
"key": "toast-1-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A compact configuration designed for dense application layouts.",
"text": "Another configurable message.",
"href": "#toast-demo",
"actionHref": "#toast-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 48,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
variant="secondary"
/&gt;</code></pre>
</section>
</div>
</article>
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Extended</span><h2>Rich configuration</h2><p>A more expressive variation using additional data, stronger emphasis, and optional states.</p></div>
<span class="demo-case-number">03</span>
</header>
<div class="demo-workbench">
<div id="toast-demo-3" class="demo-canvas demo-canvas--3">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Toast size="lg" title="Advanced Toast" description="A richer configuration demonstrating additional content and hierarchy." items='[{"id":"toast-2-1","key":"toast-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#toast-demo","actionHref":"#toast-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"toast-2-2","key":"toast-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#toast-demo","actionHref":"#toast-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' variant="success" class="showcase-instance showcase-instance--3" /></div>
</div>
<section class="demo-code" aria-label="Rich configuration usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Toast
size="lg"
title="Advanced Toast"
description="A richer configuration demonstrating additional content and hierarchy."
items='[
<span>&#123;</span>
"id": "toast-2-1",
"key": "toast-2-1",
"value": "primary",
"label": "Advanced workflow",
"title": "Advanced workflow",
"description": "A richer configuration with more supporting information and actions.",
"text": "A configurable sample message.",
"href": "#toast-demo",
"actionHref": "#toast-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 32,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "toast-2-2",
"key": "toast-2-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A richer configuration with more supporting information and actions.",
"text": "Another configurable message.",
"href": "#toast-demo",
"actionHref": "#toast-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": false,
"selected": false,
"checked": false,
"disabled": true,
"outgoing": true,
"open": true,
"number": 2,
"count": 64,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>
]'
variant="success"
/&gt;</code></pre>
</section>
</div>
</article></div></section>
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@dismiss</code><span>Fires after the announcement or feedback surface is dismissed.</span></div><div><code>@action</code><span>Fires when the component's primary or item action is activated.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;Toast
@dismiss='console.log(payload)'
@action='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Toast\"], [data-component=\"Toast\"]")
if (component) registerOutputHandler(component, "dismiss", (payload) =&gt; <span>&#123;</span>
console.log("dismiss", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "action", (payload) =&gt; <span>&#123;</span>
console.log("action", payload)
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Toast"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main>
<aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#toast-demo-1">Production default</a><a href="#toast-demo-2">Compact application</a><a href="#toast-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
</aside>
</div>
<nav class="detail-pagination">
<a href="/components/timeline"><small>Previous</small><strong>← Timeline</strong></a>
<a href="/components/tree-view"><small>Next</small><strong>Tree View →</strong></a>
</nav>
}
}
@@ -1,12 +1,15 @@
// Generated by scripts/generate-showcase.mjs. Do not edit directly.
page TreeViewDetail {
layout = "showcase"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
state playground_title = ctx.url.searchParams.get("pg_title") ?? "Tree View example"
state playground_description = ctx.url.searchParams.get("pg_description") ?? "A polished default tree view for a modern application."
state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"tree-view-0-1\",\"key\":\"tree-view-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#tree-view-demo\",\"actionHref\":\"#tree-view-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"tree-view-0-2\",\"key\":\"tree-view-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#tree-view-demo\",\"actionHref\":\"#tree-view-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]")
state playground_variant = ctx.url.searchParams.get("pg_variant") ?? "default"
state playground_valueKey = ctx.url.searchParams.get("pg_valueKey") ?? "value"
state playground_labelKey = ctx.url.searchParams.get("pg_labelKey") ?? "label"
state playground_defaultExpanded = JSON.parse(ctx.url.searchParams.get("pg_defaultExpanded") ?? "[{\"id\":\"tree-view-0-1\",\"key\":\"tree-view-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#tree-view-demo\",\"actionHref\":\"#tree-view-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"tree-view-0-2\",\"key\":\"tree-view-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#tree-view-demo\",\"actionHref\":\"#tree-view-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]")
state playground_selected = ctx.url.searchParams.get("pg_selected") ?? ""
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
seo {
title = "Tree View"
@@ -21,7 +24,7 @@ page TreeViewDetail {
<span class="showcase-eyebrow">Base component</span>
<h1>Tree View</h1>
<p>Theme-aware, responsive tree view component.</p>
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>4 outputs</span><span>Theme ready</span><span>Responsive</span></div>
<div class="detail-badges"><span>10 props</span><span>1 slots</span><span>4 outputs</span><span>Theme ready</span><span>Responsive</span></div>
</div>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header>
@@ -30,7 +33,7 @@ page TreeViewDetail {
<div class="playground-workbench">
<div class="playground-preview">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="playground-preview-source" data-playground-preview><TreeView size='{playground_size}' color='{playground_color}' title='{playground_title}' description='{playground_description}' items='{playground_items}' variant='{playground_variant}' class='{playground_class}' /></div>
<div class="playground-preview-source" data-playground-preview><TreeView title='{playground_title}' description='{playground_description}' items='{playground_items}' valueKey='{playground_valueKey}' labelKey='{playground_labelKey}' defaultExpanded='{playground_defaultExpanded}' selected='{playground_selected}' size='{playground_size}' color='{playground_color}' class='{playground_class}' /></div>
<section class="playground-code" aria-label="Current component code">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;TreeView
@@ -144,14 +147,122 @@ page TreeViewDetail {
]
<span>&#125;</span>
]'
defaultExpanded='[
<span>&#123;</span>
"id": "tree-view-0-1",
"key": "tree-view-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#tree-view-demo",
"actionHref": "#tree-view-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "tree-view-0-2",
"key": "tree-view-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#tree-view-demo",
"actionHref": "#tree-view-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
<div class="playground-status" data-playground-status aria-live="polite"></div>
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
</div>
<form class="playground-controls" data-playground-form>
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
<div class="playground-fields"><label class="playground-field" for="playground-tree-view-size"><span><strong>size</strong><small>string</small></span><select id="playground-tree-view-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-tree-view-color"><span><strong>color</strong><small>string</small></span><select id="playground-tree-view-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-tree-view-title"><span><strong>title</strong><small>string</small></span><input id="playground-tree-view-title" name="pg_title" type="text" value="Tree View example" /></label><label class="playground-field" for="playground-tree-view-description"><span><strong>description</strong><small>string</small></span><input id="playground-tree-view-description" name="pg_description" type="text" value="A polished default tree view for a modern application." /></label><label class="playground-field" for="playground-tree-view-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-tree-view-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[
<header><div><span>Component props</span><strong>10 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
<div class="playground-fields"><label class="playground-field" for="playground-tree-view-title"><span><strong>title</strong><small>string</small></span><input id="playground-tree-view-title" name="pg_title" type="text" value="Tree View example" /></label><label class="playground-field" for="playground-tree-view-description"><span><strong>description</strong><small>string</small></span><input id="playground-tree-view-description" name="pg_description" type="text" value="A polished default tree view for a modern application." /></label><label class="playground-field" for="playground-tree-view-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-tree-view-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[
&#123;
"id": "tree-view-0-1",
"key": "tree-view-0-1",
@@ -258,7 +369,114 @@ page TreeViewDetail {
"Standard"
]
&#125;
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-tree-view-variant"><span><strong>variant</strong><small>string</small></span><input id="playground-tree-view-variant" name="pg_variant" type="text" value="default" /></label><label class="playground-field" for="playground-tree-view-class"><span><strong>class</strong><small>string</small></span><input id="playground-tree-view-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-tree-view-valueKey"><span><strong>value Key</strong><small>string</small></span><input id="playground-tree-view-valueKey" name="pg_valueKey" type="text" value="value" /></label><label class="playground-field" for="playground-tree-view-labelKey"><span><strong>label Key</strong><small>string</small></span><input id="playground-tree-view-labelKey" name="pg_labelKey" type="text" value="label" /></label><label class="playground-field" for="playground-tree-view-defaultExpanded"><span><strong>default Expanded</strong><small>unknown[]</small></span><textarea id="playground-tree-view-defaultExpanded" name="pg_defaultExpanded" rows="5" spellcheck="false" data-playground-json>[
&#123;
"id": "tree-view-0-1",
"key": "tree-view-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#tree-view-demo",
"actionHref": "#tree-view-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
"label": "Nested option B",
"value": "nested-b"
&#125;
],
"links": [
&#123;
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
"label": "API reference",
"href": "#api-reference"
&#125;
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
"id": "tree-view-0-2",
"key": "tree-view-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#tree-view-demo",
"actionHref": "#tree-view-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
"label": "Nested option B",
"value": "nested-b"
&#125;
],
"links": [
&#123;
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
"label": "API reference",
"href": "#api-reference"
&#125;
],
"values": [
"Included",
"Standard"
]
&#125;
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-tree-view-selected"><span><strong>selected</strong><small>string</small></span><input id="playground-tree-view-selected" name="pg_selected" type="text" value="" /></label><label class="playground-field" for="playground-tree-view-size"><span><strong>size</strong><small>string</small></span><select id="playground-tree-view-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-tree-view-color"><span><strong>color</strong><small>string</small></span><select id="playground-tree-view-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-tree-view-class"><span><strong>class</strong><small>string</small></span><input id="playground-tree-view-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
</form>
</div>
</section>
@@ -274,7 +492,7 @@ page TreeViewDetail {
<div id="tree-view-demo-1" class="demo-canvas demo-canvas--1">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><TreeView size="default" title="Tree View example" description="A polished default tree view for a modern application." items='[{"id":"tree-view-0-1","key":"tree-view-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"tree-view-0-2","key":"tree-view-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
<div class="demo-render"><TreeView title="Tree View example" description="A polished default tree view for a modern application." items='[{"id":"tree-view-0-1","key":"tree-view-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"tree-view-0-2","key":"tree-view-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' defaultExpanded='[{"id":"tree-view-0-1","key":"tree-view-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"tree-view-0-2","key":"tree-view-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' size="default" class="showcase-instance showcase-instance--1" /></div>
</div>
<section class="demo-code" aria-label="Production default usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
@@ -389,6 +607,114 @@ page TreeViewDetail {
]
<span>&#125;</span>
]'
defaultExpanded='[
<span>&#123;</span>
"id": "tree-view-0-1",
"key": "tree-view-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#tree-view-demo",
"actionHref": "#tree-view-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "tree-view-0-2",
"key": "tree-view-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#tree-view-demo",
"actionHref": "#tree-view-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
</div>
@@ -402,12 +728,11 @@ page TreeViewDetail {
<div id="tree-view-demo-2" class="demo-canvas demo-canvas--2">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><TreeView size="sm" title="Compact Tree View" description="A compact configuration for dashboards and dense product surfaces." items='[{"id":"tree-view-1-1","key":"tree-view-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"tree-view-1-2","key":"tree-view-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="secondary" class="showcase-instance showcase-instance--2" /></div>
<div class="demo-render"><TreeView title="Compact Tree View" description="A compact configuration for dashboards and dense product surfaces." items='[{"id":"tree-view-1-1","key":"tree-view-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"tree-view-1-2","key":"tree-view-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' defaultExpanded='[{"id":"tree-view-1-1","key":"tree-view-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"tree-view-1-2","key":"tree-view-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' size="sm" class="showcase-instance showcase-instance--2" /></div>
</div>
<section class="demo-code" aria-label="Compact application usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;TreeView
size="sm"
title="Compact Tree View"
description="A compact configuration for dashboards and dense product surfaces."
items='[
@@ -518,7 +843,115 @@ page TreeViewDetail {
]
<span>&#125;</span>
]'
variant="secondary"
defaultExpanded='[
<span>&#123;</span>
"id": "tree-view-1-1",
"key": "tree-view-1-1",
"value": "primary",
"label": "Secondary workflow",
"title": "Secondary workflow",
"description": "A compact configuration designed for dense application layouts.",
"text": "A configurable sample message.",
"href": "#tree-view-demo",
"actionHref": "#tree-view-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 24,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "tree-view-1-2",
"key": "tree-view-1-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A compact configuration designed for dense application layouts.",
"text": "Another configurable message.",
"href": "#tree-view-demo",
"actionHref": "#tree-view-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 48,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
size="sm"
/&gt;</code></pre>
</section>
</div>
@@ -532,12 +965,11 @@ page TreeViewDetail {
<div id="tree-view-demo-3" class="demo-canvas demo-canvas--3">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><TreeView size="lg" title="Advanced Tree View" description="A richer configuration demonstrating additional content and hierarchy." items='[{"id":"tree-view-2-1","key":"tree-view-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"tree-view-2-2","key":"tree-view-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' variant="success" class="showcase-instance showcase-instance--3" /></div>
<div class="demo-render"><TreeView title="Advanced Tree View" description="A richer configuration demonstrating additional content and hierarchy." items='[{"id":"tree-view-2-1","key":"tree-view-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"tree-view-2-2","key":"tree-view-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' defaultExpanded='[{"id":"tree-view-2-1","key":"tree-view-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"tree-view-2-2","key":"tree-view-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' size="lg" class="showcase-instance showcase-instance--3" /></div>
</div>
<section class="demo-code" aria-label="Rich configuration usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;TreeView
size="lg"
title="Advanced Tree View"
description="A richer configuration demonstrating additional content and hierarchy."
items='[
@@ -648,7 +1080,115 @@ page TreeViewDetail {
]
<span>&#125;</span>
]'
variant="success"
defaultExpanded='[
<span>&#123;</span>
"id": "tree-view-2-1",
"key": "tree-view-2-1",
"value": "primary",
"label": "Advanced workflow",
"title": "Advanced workflow",
"description": "A richer configuration with more supporting information and actions.",
"text": "A configurable sample message.",
"href": "#tree-view-demo",
"actionHref": "#tree-view-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 32,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "tree-view-2-2",
"key": "tree-view-2-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A richer configuration with more supporting information and actions.",
"text": "Another configurable message.",
"href": "#tree-view-demo",
"actionHref": "#tree-view-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": false,
"selected": false,
"checked": false,
"disabled": true,
"outgoing": true,
"open": true,
"number": 2,
"count": 64,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>
]'
size="lg"
/&gt;</code></pre>
</section>
</div>
@@ -677,14 +1217,14 @@ if (component) registerOutputHandler(component, "expand", (payload) =&gt; <span>
if (component) registerOutputHandler(component, "collapse", (payload) =&gt; <span>&#123;</span>
console.log("collapse", payload)
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Tree View"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>title</code></td><td>string</td><td><code>"Tree View"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>valueKey</code></td><td>string</td><td><code>"value"</code></td><td>No</td></tr><tr><td><code>labelKey</code></td><td>string</td><td><code>"label"</code></td><td>No</td></tr><tr><td><code>defaultExpanded</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>selected</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main>
<aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#tree-view-demo-1">Production default</a><a href="#tree-view-demo-2">Compact application</a><a href="#tree-view-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
</aside>
</div>
<nav class="detail-pagination">
<a href="/components/toast"><small>Previous</small><strong>← Toast</strong></a>
<a href="/components/timeline"><small>Previous</small><strong>← Timeline</strong></a>
<span></span>
</nav>
}
@@ -684,7 +684,7 @@ if (component) registerOutputHandler(component, "blur", (payload) =&gt; <span>&#
</aside>
</div>
<nav class="detail-pagination">
<a href="/components/toast-notifications"><small>Previous</small><strong>← Toast Notifications</strong></a>
<a href="/components/map"><small>Previous</small><strong>← Map</strong></a>
<span></span>
</nav>
}
@@ -10,18 +10,18 @@ page ComponentShowcase {
<header class="showcase-hero showcase-hero--home">
<span class="showcase-eyebrow">The UI foundation for WRNexus</span>
<h1 class="showcase-title">Build modern products<br /><em>at framework speed.</em></h1>
<p class="showcase-description">A complete, accessible component system with 108 primitives, ready-made blocks, page templates, responsive behavior, and deeply configurable design tokens.</p>
<p class="showcase-description">A complete, accessible component system with 102 primitives, ready-made blocks, page templates, responsive behavior, and deeply configurable design tokens.</p>
<div class="home-actions"><a class="home-primary-action" href="/docs">Get started <span aria-hidden="true">→</span></a><a class="home-secondary-action" href="/block-library">Explore blocks</a></div>
<div class="home-install"><span>$</span><code>bun add @wrnexus/ui</code><button type="button" data-copy-text="bun add @wrnexus/ui" aria-label="Copy install command"><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span></button></div>
</header>
<section class="home-product-grid"><article><span class="icon-[lucide--component] size-7"></span><strong>108 components</strong><p>Accessible primitives for every product surface.</p><a href="/base">Browse components →</a></article><article><span class="icon-[lucide--layout-template] size-7"></span><strong>Ready-made blocks</strong><p>Composable sections assembled from WRNexus UI.</p><a href="/block-library">Explore blocks →</a></article><article><span class="icon-[lucide--panels-top-left] size-7"></span><strong>Page templates</strong><p>Complete responsive pages ready to customize.</p><a href="/templates">View templates →</a></article></section>
<section class="home-product-grid"><article><span class="icon-[lucide--component] size-7"></span><strong>102 components</strong><p>Accessible primitives for every product surface.</p><a href="/base">Browse components →</a></article><article><span class="icon-[lucide--layout-template] size-7"></span><strong>Ready-made blocks</strong><p>Composable sections assembled from WRNexus UI.</p><a href="/block-library">Explore blocks →</a></article><article><span class="icon-[lucide--panels-top-left] size-7"></span><strong>Page templates</strong><p>Complete responsive pages ready to customize.</p><a href="/templates">View templates →</a></article></section>
<section class="showcase-stats">
<div class="showcase-stat"><strong>108</strong><span>Unique components</span></div>
<div class="showcase-stat"><strong>419</strong><span>Live configurations</span></div>
<div class="showcase-stat"><strong>102</strong><span>Unique components</span></div>
<div class="showcase-stat"><strong>401</strong><span>Live configurations</span></div>
<div class="showcase-stat"><strong>0</strong><span>Duplicate implementations</span></div>
<div class="showcase-stat"><strong>11</strong><span>Focused categories</span></div>
</section>
<section class="home-categories"><div class="home-section-heading"><span>Explore the system</span><h2>Everything your product needs</h2></div><div class="home-category-grid"><a class="home-category home-category--1" href="/advanced-forms"><span class="home-category-index">01</span><div><strong>Advanced Forms</strong><p>8 components · 112 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--2" href="/base"><span class="home-category-index">02</span><div><strong>Base</strong><p>27 components · 86 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--3" href="/core"><span class="home-category-index">03</span><div><strong>Core</strong><p>5 components · 13 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--4" href="/data"><span class="home-category-index">04</span><div><strong>Data</strong><p>3 components · 9 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--1" href="/forms"><span class="home-category-index">05</span><div><strong>Forms</strong><p>12 components · 36 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--2" href="/integrations"><span class="home-category-index">06</span><div><strong>Integrations</strong><p>11 components · 33 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--3" href="/layout"><span class="home-category-index">07</span><div><strong>Layout</strong><p>15 components · 43 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--4" href="/marketing"><span class="home-category-index">08</span><div><strong>Marketing</strong><p>10 components · 30 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--1" href="/navigation"><span class="home-category-index">09</span><div><strong>Navigation</strong><p>10 components · 30 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--2" href="/overlays"><span class="home-category-index">010</span><div><strong>Overlays</strong><p>6 components · 18 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--3" href="/tables"><span class="home-category-index">011</span><div><strong>Tables</strong><p>1 components · 9 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a></div></section>
<section class="home-categories"><div class="home-section-heading"><span>Explore the system</span><h2>Everything your product needs</h2></div><div class="home-category-grid"><a class="home-category home-category--1" href="/advanced-forms"><span class="home-category-index">01</span><div><strong>Advanced Forms</strong><p>7 components · 109 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--2" href="/base"><span class="home-category-index">02</span><div><strong>Base</strong><p>26 components · 83 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--3" href="/core"><span class="home-category-index">03</span><div><strong>Core</strong><p>5 components · 13 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--4" href="/data"><span class="home-category-index">04</span><div><strong>Data</strong><p>3 components · 9 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--1" href="/forms"><span class="home-category-index">05</span><div><strong>Forms</strong><p>12 components · 36 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--2" href="/integrations"><span class="home-category-index">06</span><div><strong>Integrations</strong><p>7 components · 21 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--3" href="/layout"><span class="home-category-index">07</span><div><strong>Layout</strong><p>15 components · 43 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--4" href="/marketing"><span class="home-category-index">08</span><div><strong>Marketing</strong><p>10 components · 30 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--1" href="/navigation"><span class="home-category-index">09</span><div><strong>Navigation</strong><p>10 components · 30 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--2" href="/overlays"><span class="home-category-index">010</span><div><strong>Overlays</strong><p>6 components · 18 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--3" href="/tables"><span class="home-category-index">011</span><div><strong>Tables</strong><p>1 components · 9 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a></div></section>
</div>
}
}
@@ -3,64 +3,44 @@ page IntegrationsShowcase {
layout = "showcase"
seo {
title = "Integrations components"
description = "11 integrations components from @wrnexus/ui."
description = "7 integrations components from @wrnexus/ui."
}
view {
<header class="showcase-hero showcase-hero--category">
<span class="showcase-eyebrow">Component category</span>
<h1 class="showcase-title">Integrations</h1>
<p class="showcase-description">11 unique, responsive, theme-aware components. Open a component to inspect multiple live configurations and its complete props API.</p>
<div class="category-meta"><span>11 components</span><span>Multiple use cases</span><span>33 live configurations</span></div>
<p class="showcase-description">7 unique, responsive, theme-aware components. Open a component to inspect multiple live configurations and its complete props API.</p>
<div class="category-meta"><span>7 components</span><span>Multiple use cases</span><span>21 live configurations</span></div>
</header>
<div class="catalog-grid">
<article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div>
<div class="catalog-card-stage"><AdvancedDatePicker size="default" title="Advanced Date Picker example" description="A polished default advanced date picker for a modern application." items='[{"id":"advanced-date-picker-0-1","key":"advanced-date-picker-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#advanced-date-picker-demo","actionHref":"#advanced-date-picker-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"advanced-date-picker-0-2","key":"advanced-date-picker-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#advanced-date-picker-demo","actionHref":"#advanced-date-picker-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
</div>
<div class="catalog-card-body">
<div><span class="catalog-card-category">Integrations</span><h2>Advanced Date Picker</h2><p>Theme-aware, responsive advanced date picker component.</p></div>
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/advanced-date-picker">Explore component <span aria-hidden="true">→</span></a></div>
</div>
</article>
<article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div>
<div class="catalog-card-stage"><AdvancedRangeSlider size="default" title="Advanced Range Slider example" description="A polished default advanced range slider for a modern application." items='[{"id":"advanced-range-slider-0-1","key":"advanced-range-slider-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#advanced-range-slider-demo","actionHref":"#advanced-range-slider-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"advanced-range-slider-0-2","key":"advanced-range-slider-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#advanced-range-slider-demo","actionHref":"#advanced-range-slider-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
</div>
<div class="catalog-card-body">
<div><span class="catalog-card-category">Integrations</span><h2>Advanced Range Slider</h2><p>Theme-aware, responsive advanced range slider component.</p></div>
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/advanced-range-slider">Explore component <span aria-hidden="true">→</span></a></div>
</div>
</article>
<article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div>
<div class="catalog-card-stage"><Chart size="default" title="Chart example" description="A polished default chart for a modern application." items='[{"id":"chart-0-1","key":"chart-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"chart-0-2","key":"chart-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
<div class="catalog-card-stage"><Chart title="Chart example" description="A polished default chart for a modern application." items='[{"id":"chart-0-1","key":"chart-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"chart-0-2","key":"chart-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' height="240" showLegend="true" showValues="true" size="default" variant="bar" class="showcase-instance showcase-instance--1" /></div>
</div>
<div class="catalog-card-body">
<div><span class="catalog-card-category">Integrations</span><h2>Chart</h2><p>Theme-aware, responsive chart component.</p></div>
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/chart">Explore component <span aria-hidden="true">→</span></a></div>
<div class="catalog-card-footer"><span>12 props · 1 slots</span><a href="/components/chart">Explore component <span aria-hidden="true">→</span></a></div>
</div>
</article>
<article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div>
<div class="catalog-card-stage"><Clipboard size="default" title="Clipboard example" description="A polished default clipboard for a modern application." items='[{"id":"clipboard-0-1","key":"clipboard-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#clipboard-demo","actionHref":"#clipboard-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"clipboard-0-2","key":"clipboard-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#clipboard-demo","actionHref":"#clipboard-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
<div class="catalog-card-stage"><Clipboard value="sample-1" label="Clipboard" copiedLabel="Clipboard" errorLabel="Clipboard" title="Clipboard example" description="A polished default clipboard for a modern application." code="true" size="default" disabled="false" class="showcase-instance showcase-instance--1" /></div>
</div>
<div class="catalog-card-body">
<div><span class="catalog-card-category">Integrations</span><h2>Clipboard</h2><p>Theme-aware, responsive clipboard component.</p></div>
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/clipboard">Explore component <span aria-hidden="true">→</span></a></div>
<div class="catalog-card-footer"><span>11 props · 1 slots</span><a href="/components/clipboard">Explore component <span aria-hidden="true">→</span></a></div>
</div>
</article>
<article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div>
<div class="catalog-card-stage"><Confetti size="default" title="Confetti example" description="A polished default confetti for a modern application." items='[{"id":"confetti-0-1","key":"confetti-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#confetti-demo","actionHref":"#confetti-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"confetti-0-2","key":"confetti-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#confetti-demo","actionHref":"#confetti-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
<div class="catalog-card-stage"><Confetti label="Confetti" duration="1200" count="3" autoStart="false" disabled="false" size="default" class="showcase-instance showcase-instance--1" /></div>
</div>
<div class="catalog-card-body">
<div><span class="catalog-card-category">Integrations</span><h2>Confetti</h2><p>Theme-aware, responsive confetti component.</p></div>
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/confetti">Explore component <span aria-hidden="true">→</span></a></div>
<div class="catalog-card-footer"><span>8 props · 1 slots</span><a href="/components/confetti">Explore component <span aria-hidden="true">→</span></a></div>
</div>
</article>
<article class="catalog-card" data-interactive-preview="false">
@@ -83,16 +63,6 @@ page IntegrationsShowcase {
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/drag-and-drop">Explore component <span aria-hidden="true">→</span></a></div>
</div>
</article>
<article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div>
<div class="catalog-card-stage"><FileUpload size="default" title="File Upload example" description="A polished default file upload for a modern application." items='[{"id":"file-upload-0-1","key":"file-upload-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#file-upload-demo","actionHref":"#file-upload-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"file-upload-0-2","key":"file-upload-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#file-upload-demo","actionHref":"#file-upload-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
</div>
<div class="catalog-card-body">
<div><span class="catalog-card-category">Integrations</span><h2>File Upload</h2><p>Theme-aware, responsive file upload component.</p></div>
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/file-upload">Explore component <span aria-hidden="true">→</span></a></div>
</div>
</article>
<article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div>
@@ -103,16 +73,6 @@ page IntegrationsShowcase {
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/map">Explore component <span aria-hidden="true">→</span></a></div>
</div>
</article>
<article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div>
<div class="catalog-card-stage"><ToastNotifications size="default" title="Toast Notifications example" description="A polished default toast notifications for a modern application." items='[{"id":"toast-notifications-0-1","key":"toast-notifications-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#toast-notifications-demo","actionHref":"#toast-notifications-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"toast-notifications-0-2","key":"toast-notifications-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#toast-notifications-demo","actionHref":"#toast-notifications-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
</div>
<div class="catalog-card-body">
<div><span class="catalog-card-category">Integrations</span><h2>Toast Notifications</h2><p>Theme-aware, responsive toast notifications component.</p></div>
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/toast-notifications">Explore component <span aria-hidden="true">→</span></a></div>
</div>
</article>
<article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div>
@@ -9,8 +9,6 @@ export interface Routes {
"/block-library": Record<string, never>;
"/colors": Record<string, never>;
"/components/accordion": Record<string, never>;
"/components/advanced-date-picker": Record<string, never>;
"/components/advanced-range-slider": Record<string, never>;
"/components/advanced-select": Record<string, never>;
"/components/alert": Record<string, never>;
"/components/announcement-bar": Record<string, never>;
@@ -37,7 +35,6 @@ export interface Routes {
"/components/confetti": Record<string, never>;
"/components/container": Record<string, never>;
"/components/context-menu": Record<string, never>;
"/components/copy-markup": Record<string, never>;
"/components/ctasection": Record<string, never>;
"/components/custom-scrollbar": Record<string, never>;
"/components/data-map": Record<string, never>;
@@ -52,7 +49,6 @@ export interface Routes {
"/components/feature-grid": Record<string, never>;
"/components/feature-icon-card": Record<string, never>;
"/components/file-input": Record<string, never>;
"/components/file-upload": Record<string, never>;
"/components/file-upload-progress": Record<string, never>;
"/components/footer": Record<string, never>;
"/components/grid": Record<string, never>;
@@ -107,8 +103,6 @@ export interface Routes {
"/components/textarea": Record<string, never>;
"/components/time-picker": Record<string, never>;
"/components/timeline": Record<string, never>;
"/components/toast": Record<string, never>;
"/components/toast-notifications": Record<string, never>;
"/components/toaster": Record<string, never>;
"/components/toggle-count": Record<string, never>;
"/components/toggle-password": Record<string, never>;
@@ -152,8 +146,6 @@ export interface RouteNames {
"block.library": "/block-library";
"colors": "/colors";
"components.accordion": "/components/accordion";
"components.advanced.date.picker": "/components/advanced-date-picker";
"components.advanced.range.slider": "/components/advanced-range-slider";
"components.advanced.select": "/components/advanced-select";
"components.alert": "/components/alert";
"components.announcement.bar": "/components/announcement-bar";
@@ -180,7 +172,6 @@ export interface RouteNames {
"components.confetti": "/components/confetti";
"components.container": "/components/container";
"components.context.menu": "/components/context-menu";
"components.copy.markup": "/components/copy-markup";
"components.ctasection": "/components/ctasection";
"components.custom.scrollbar": "/components/custom-scrollbar";
"components.data.map": "/components/data-map";
@@ -195,7 +186,6 @@ export interface RouteNames {
"components.feature.grid": "/components/feature-grid";
"components.feature.icon.card": "/components/feature-icon-card";
"components.file.input": "/components/file-input";
"components.file.upload": "/components/file-upload";
"components.file.upload.progress": "/components/file-upload-progress";
"components.footer": "/components/footer";
"components.grid": "/components/grid";
@@ -250,8 +240,6 @@ export interface RouteNames {
"components.textarea": "/components/textarea";
"components.time.picker": "/components/time-picker";
"components.timeline": "/components/timeline";
"components.toast": "/components/toast";
"components.toast.notifications": "/components/toast-notifications";
"components.toaster": "/components/toaster";
"components.toggle.count": "/components/toggle-count";
"components.toggle.password": "/components/toggle-password";
@@ -364,8 +352,6 @@ export function route<N extends RouteName>(
"block.library": "/block-library",
"colors": "/colors",
"components.accordion": "/components/accordion",
"components.advanced.date.picker": "/components/advanced-date-picker",
"components.advanced.range.slider": "/components/advanced-range-slider",
"components.advanced.select": "/components/advanced-select",
"components.alert": "/components/alert",
"components.announcement.bar": "/components/announcement-bar",
@@ -392,7 +378,6 @@ export function route<N extends RouteName>(
"components.confetti": "/components/confetti",
"components.container": "/components/container",
"components.context.menu": "/components/context-menu",
"components.copy.markup": "/components/copy-markup",
"components.ctasection": "/components/ctasection",
"components.custom.scrollbar": "/components/custom-scrollbar",
"components.data.map": "/components/data-map",
@@ -407,7 +392,6 @@ export function route<N extends RouteName>(
"components.feature.grid": "/components/feature-grid",
"components.feature.icon.card": "/components/feature-icon-card",
"components.file.input": "/components/file-input",
"components.file.upload": "/components/file-upload",
"components.file.upload.progress": "/components/file-upload-progress",
"components.footer": "/components/footer",
"components.grid": "/components/grid",
@@ -462,8 +446,6 @@ export function route<N extends RouteName>(
"components.textarea": "/components/textarea",
"components.time.picker": "/components/time-picker",
"components.timeline": "/components/timeline",
"components.toast": "/components/toast",
"components.toast.notifications": "/components/toast-notifications",
"components.toaster": "/components/toaster",
"components.toggle.count": "/components/toggle-count",
"components.toggle.password": "/components/toggle-password",
+2 -2
View File
@@ -5,8 +5,8 @@
"type": "module",
"scripts": {
"generate:reference": "bun run ../../scripts/generate-ui-component-reference.mjs",
"generate:pages": "node scripts/generate-showcase.mjs",
"generate": "bun run generate:reference && bun run generate:pages",
"generate:pages": "bun run generate:reference && node scripts/generate-showcase.mjs",
"generate": "bun run generate:pages",
"dev": "bun run generate && bun run ../../packages/cli/src/index.ts dev .",
"build": "bun run generate && bun run ../../packages/cli/src/index.ts build .",
"typecheck": "tsc --noEmit -p tsconfig.json",
@@ -2,6 +2,7 @@ import { mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from
import { dirname, join } from "node:path";
import process from "node:process";
import { fileURLToPath } from "node:url";
import prettier from "prettier";
import { eventDescriptionFor, profileFor } from "./showcase-profiles.mjs";
const exampleRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
@@ -1792,11 +1793,12 @@ const showcaseManifest = {
profiled: Boolean(profileFor(component.name)),
})),
};
writeFileSync(
join(exampleRoot, "showcase-manifest.json"),
`${JSON.stringify(showcaseManifest, null, 2)}\n`,
"utf8",
);
const formattedManifest = await prettier.format(`${JSON.stringify(showcaseManifest, null, 2)}\n`, {
parser: "json",
printWidth: 100,
endOfLine: "lf",
});
writeFileSync(join(exampleRoot, "showcase-manifest.json"), formattedManifest, "utf8");
const categoryCards = categoryEntries
.map(
@@ -1,8 +1,8 @@
{
"generatedFrom": "packages/ui/component-reference.json",
"componentCount": 108,
"componentCount": 102,
"categoryCount": 11,
"totalDemoCount": 419,
"totalDemoCount": 401,
"components": [
{
"name": "Accordion",
@@ -16,30 +16,6 @@
"events": ["change", "open", "close"],
"profiled": false
},
{
"name": "AdvancedDatePicker",
"mount": "AdvancedDatePicker",
"slug": "advanced-date-picker",
"category": "integrations",
"purpose": "Theme-aware, responsive advanced date picker component.",
"demoCount": 3,
"propCount": 7,
"slots": ["default"],
"events": ["input", "change", "open", "close", "clear"],
"profiled": false
},
{
"name": "AdvancedRangeSlider",
"mount": "AdvancedRangeSlider",
"slug": "advanced-range-slider",
"category": "integrations",
"purpose": "Theme-aware, responsive advanced range slider component.",
"demoCount": 3,
"propCount": 7,
"slots": ["default"],
"events": ["input", "change", "start", "end"],
"profiled": false
},
{
"name": "AdvancedSelect",
"mount": "AdvancedSelect",
@@ -250,7 +226,7 @@
"category": "integrations",
"purpose": "Theme-aware, responsive chart component.",
"demoCount": 3,
"propCount": 7,
"propCount": 12,
"slots": ["default"],
"events": ["select", "dataPointClick", "legendToggle"],
"profiled": false
@@ -286,7 +262,7 @@
"category": "integrations",
"purpose": "Theme-aware, responsive clipboard component.",
"demoCount": 3,
"propCount": 7,
"propCount": 11,
"slots": ["default"],
"events": ["copy", "success", "error"],
"profiled": false
@@ -346,7 +322,7 @@
"category": "integrations",
"purpose": "Theme-aware, responsive confetti component.",
"demoCount": 3,
"propCount": 7,
"propCount": 8,
"slots": ["default"],
"events": ["start", "complete"],
"profiled": false
@@ -375,18 +351,6 @@
"events": ["open", "close", "select", "action"],
"profiled": true
},
{
"name": "CopyMarkup",
"mount": "CopyMarkup",
"slug": "copy-markup",
"category": "advanced-forms",
"purpose": "Theme-aware, responsive copy markup component.",
"demoCount": 3,
"propCount": 13,
"slots": [],
"events": ["copy", "success", "error"],
"profiled": false
},
{
"name": "CustomScrollbar",
"mount": "CustomScrollbar",
@@ -552,18 +516,6 @@
"events": ["input", "change", "focus", "blur", "select", "clear", "invalid"],
"profiled": false
},
{
"name": "FileUpload",
"mount": "FileUpload",
"slug": "file-upload",
"category": "integrations",
"purpose": "Theme-aware, responsive file upload component.",
"demoCount": 3,
"propCount": 7,
"slots": ["default"],
"events": ["select", "upload", "progress", "success", "error", "cancel", "remove"],
"profiled": false
},
{
"name": "FileUploadProgress",
"mount": "FileUploadProgress",
@@ -729,7 +681,7 @@
"demoCount": 3,
"propCount": 7,
"slots": ["default"],
"events": [],
"events": ["select"],
"profiled": true
},
{
@@ -753,7 +705,7 @@
"demoCount": 3,
"propCount": 7,
"slots": ["default"],
"events": [],
"events": ["markerClick", "select", "zoom"],
"profiled": true
},
{
@@ -777,7 +729,7 @@
"demoCount": 3,
"propCount": 7,
"slots": ["default"],
"events": [],
"events": ["pause", "resume"],
"profiled": true
},
{
@@ -1005,7 +957,7 @@
"demoCount": 3,
"propCount": 13,
"slots": [],
"events": [],
"events": ["search", "clear"],
"profiled": true
},
{
@@ -1209,33 +1161,9 @@
"demoCount": 3,
"propCount": 7,
"slots": ["default"],
"events": [],
"events": ["select"],
"profiled": true
},
{
"name": "Toast",
"mount": "Toast",
"slug": "toast",
"category": "base",
"purpose": "Theme-aware, responsive toast component.",
"demoCount": 3,
"propCount": 7,
"slots": ["default"],
"events": ["dismiss", "action"],
"profiled": false
},
{
"name": "ToastNotifications",
"mount": "ToastNotifications",
"slug": "toast-notifications",
"category": "integrations",
"purpose": "Theme-aware, responsive toast notifications component.",
"demoCount": 3,
"propCount": 7,
"slots": ["default"],
"events": ["add", "dismiss", "clear", "action"],
"profiled": false
},
{
"name": "Toaster",
"mount": "Toaster",
@@ -1291,7 +1219,7 @@
"category": "base",
"purpose": "Theme-aware, responsive tree view component.",
"demoCount": 3,
"propCount": 7,
"propCount": 10,
"slots": ["default"],
"events": ["select", "toggle", "expand", "collapse"],
"profiled": false
@@ -2,5 +2,7 @@ node_modules/
dist/
.wrnexus/
**/.wrnexus/
.wrnexus-*/
**/.wrnexus-*/
*.log
**/CLAUDE.md
@@ -53,11 +53,11 @@
"@iconify-json/lucide": "^1.2.118",
"@iconify/tailwind4": "^1.2.3",
"@tailwindcss/cli": "^4.0.0",
"@types/bun": "latest",
"@types/bun": "^1.3.14",
"eslint": "^9.0.0",
"prettier": "latest",
"prettier": "^3.9.6",
"tailwindcss": "^4.0.0",
"typescript": "^5.5.0",
"typescript-eslint": "latest"
"typescript-eslint": "^8.65.0"
}
}
@@ -53,11 +53,11 @@
"@iconify-json/lucide": "^1.2.118",
"@iconify/tailwind4": "^1.2.3",
"@tailwindcss/cli": "^4.0.0",
"@types/bun": "latest",
"@types/bun": "^1.3.14",
"eslint": "^9.0.0",
"prettier": "latest",
"prettier": "^3.9.6",
"tailwindcss": "^4.0.0",
"typescript": "^5.5.0",
"typescript-eslint": "latest"
"typescript-eslint": "^8.65.0"
}
}
@@ -1,7 +1,6 @@
import { afterEach, expect, test } from "bun:test";
import { implement } from "../../../../../packages/rpc/src/index.ts";
import { handleRpcRequest } from "../../../../../packages/dev-server/src/rpc-dispatch.ts";
import { catalogService } from "../../../packages/shared/src/index.ts";
import catalogServiceImplementation from "../../admin/app/services/catalog.ts";
import { GET } from "../app/api/product.ts";
const secret = process.env.WRNEXUS_RPC_SECRET;
@@ -16,23 +15,17 @@ afterEach(() => {
else process.env.WRNEXUS_INTERNAL_ORIGINS = origins;
});
function startAdmin(allowed: boolean) {
const service = implement(
catalogService,
{ getProduct: ({ sku }) => ({ sku, name: "WRNexus Starter", priceCents: 4900 }) },
{
selfApp: "admin",
checkPermission: (permission, subject) =>
allowed && permission === "catalog:read" && subject?.subjectId === "demo-user",
},
);
function startAdmin() {
return Bun.serve({
port: 0,
hostname: "127.0.0.1",
async fetch(request) {
return (
(await handleRpcRequest(request, new URL(request.url), new Map([["catalog", service]]))) ??
new Response("Not found", { status: 404 })
(await handleRpcRequest(
request,
new URL(request.url),
new Map([["catalog", catalogServiceImplementation]]),
)) ?? new Response("Not found", { status: 404 })
);
},
});
@@ -41,7 +34,7 @@ function startAdmin(allowed: boolean) {
test("web calls admin through private RPC when permitted", async () => {
process.env.WRNEXUS_RPC_SECRET = "test-rpc-secret-at-least-32-chars-long";
process.env.WRNEXUS_APP_NAME = "web";
const server = startAdmin(true);
const server = startAdmin();
process.env.WRNEXUS_INTERNAL_ORIGINS = JSON.stringify({
admin: `http://127.0.0.1:${server.port}`,
});
@@ -62,7 +55,7 @@ test("web calls admin through private RPC when permitted", async () => {
test("web returns 403 when admin denies catalog:read", async () => {
process.env.WRNEXUS_RPC_SECRET = "test-rpc-secret-at-least-32-chars-long";
process.env.WRNEXUS_APP_NAME = "web";
const server = startAdmin(false);
const server = startAdmin();
process.env.WRNEXUS_INTERNAL_ORIGINS = JSON.stringify({
admin: `http://127.0.0.1:${server.port}`,
});
@@ -6,7 +6,17 @@ import tseslint from "typescript-eslint";
const tsconfigRootDir = dirname(fileURLToPath(import.meta.url));
export default tseslint.config(
{ ignores: ["node_modules/**", "dist/**", "**/dist/**", ".wrnexus/**", "**/.wrnexus/**"] },
{
ignores: [
"node_modules/**",
"dist/**",
"**/dist/**",
".wrnexus/**",
"**/.wrnexus/**",
".wrnexus-*/**",
"**/.wrnexus-*/**",
],
},
{ languageOptions: { parserOptions: { tsconfigRootDir } } },
js.configs.recommended,
...tseslint.configs.recommended,
+3 -3
View File
@@ -23,10 +23,10 @@
"devDependencies": {
"@wrnexus/cli": "0.8.6",
"@eslint/js": "^9.0.0",
"@types/bun": "latest",
"@types/bun": "^1.3.14",
"eslint": "^9.0.0",
"prettier": "latest",
"prettier": "^3.9.6",
"typescript": "^5.5.0",
"typescript-eslint": "latest"
"typescript-eslint": "^8.65.0"
}
}
@@ -11,5 +11,5 @@
"allowImportingTsExtensions": true
},
"include": ["wrnexus.workspace.ts", "packages/**/*.ts"],
"exclude": ["node_modules", "dist", "apps"]
"exclude": ["node_modules", "dist", "apps", ".wrnexus-*", "**/.wrnexus-*"]
}
+6 -7
View File
@@ -7,6 +7,8 @@
"workspaces": [
"packages/*",
"examples/*",
"examples/inter-app-api-showcase/apps/*",
"examples/inter-app-api-showcase/packages/*",
"services/*"
],
"scripts": {
@@ -34,24 +36,21 @@
"format": "prettier . --write",
"format:check": "prettier . --check",
"check": "bun run typecheck && bun run lint && bun run check:component-imports && bun run test && bun run format:check",
"verify:0.3": "node scripts/verify-0.3.mjs",
"verify:0.4": "node scripts/verify-0.4.mjs",
"validate:0.4": "node scripts/validate-0.4.mjs",
"auth:dev": "bun run --cwd examples/auth-showcase dev",
"auth:check": "bun run --cwd packages/auth check",
"validate:auth": "node scripts/validate-auth.mjs",
"validate:0.6": "node scripts/validate-0.6.mjs",
"validate:0.7": "node scripts/validate-0.7.mjs",
"security:framework": "node scripts/security-performance-audit.mjs",
"security:asvs": "node scripts/check-security-asvs.mjs",
"generate:security-report": "node scripts/security-performance-audit.mjs --write",
"check:public-api": "node scripts/check-public-api.mjs",
"check:generated-types": "bun run scripts/check-generated-types.ts",
"generate:example-types": "bun run packages/cli/src/index.ts generate types examples/basic-app",
"generate:public-api": "node scripts/check-public-api.mjs --write",
"check:ui-visual": "node scripts/check-ui-visual-contract.mjs",
"generate:ui-visual": "node scripts/check-ui-visual-contract.mjs --write",
"sbom": "node scripts/generate-sbom.mjs",
"benchmark:framework": "node --experimental-transform-types scripts/benchmark-framework.mjs",
"check:production": "bun run check:workspace && bun run check:public-api && bun run check:ui-visual && bun run validate:0.8 && bun run security:framework && bun run security:asvs && bun run check:editor-compiler && bun run check:editor-language-server && bun run check:editor-extension && bun run check",
"benchmark:framework": "node --experimental-transform-types scripts/benchmark-framework.mjs --write",
"check:production": "bun run check:workspace && bun run check:generated-types && bun run check:public-api && bun run check:ui-visual && bun run validate:0.8 && bun run security:framework && bun run security:asvs && bun run check:editor-compiler && bun run check:editor-language-server && bun run check:editor-extension && bun run check && bun run test:examples",
"validate:staging": "node --experimental-transform-types scripts/test-package-integrity.mjs",
"stage:packages": "bun run scripts/publish-packages.ts",
"test:staged-consumers": "node scripts/test-staged-consumers.mjs",
@@ -0,0 +1,130 @@
import { describe, expect, test } from "bun:test";
import { Database } from "bun:sqlite";
import {
RedisCaptchaStore,
SqliteCaptchaStore,
managedCaptchaProvider,
type CaptchaChallengeRecord,
type CaptchaResponseTokenRecord,
type RedisCaptchaClient,
type SqliteDatabaseLike,
} from "../src/index.ts";
const challenge = (id: string, expiresAt = Date.now() + 60_000): CaptchaChallengeRecord => ({
id,
provider: "self-hosted",
type: "text",
presentation: "visual",
action: "signup",
publicChallenge: {
id,
provider: "self-hosted",
type: "text",
presentation: "visual",
action: "signup",
prompt: "Type the text",
createdAt: Date.now(),
expiresAt,
responseField: "wrn-captcha-response",
},
answerDigest: "digest",
answerSalt: "salt",
answerKind: "text",
caseSensitive: false,
attempts: 0,
maxAttempts: 3,
metadata: {},
createdAt: Date.now(),
expiresAt,
});
const token = (tokenHash: string): CaptchaResponseTokenRecord => ({
tokenHash,
provider: "self-hosted",
action: "signup",
createdAt: Date.now(),
expiresAt: Date.now() + 60_000,
});
describe("CAPTCHA durable stores", () => {
test("SQLite atomically mutates, consumes, deletes, and collects records", async () => {
const db = new Database(":memory:");
const store = new SqliteCaptchaStore(db as unknown as SqliteDatabaseLike);
await store.createChallenge(challenge("c1"));
expect((await store.incrementAttempts("c1", Date.now()))?.attempts).toBe(1);
expect(await store.consumeChallenge("c1", Date.now())).toMatchObject({
consumedAt: expect.any(Number),
});
expect(await store.consumeChallenge("c1", Date.now())).toBeUndefined();
await store.createToken(token("t1"));
expect(await store.consumeToken("t1", Date.now())).toMatchObject({
consumedAt: expect.any(Number),
});
expect(await store.consumeToken("t1", Date.now())).toBeUndefined();
await store.deleteChallenge("c1");
await store.deleteToken("t1");
await store.createChallenge(challenge("expired", Date.now() - 1));
await store.gc(Date.now());
expect(await store.getChallenge("expired")).toBeUndefined();
db.close();
});
test("Redis fallback locking preserves one-time consumption without Lua", async () => {
const values = new Map<string, string>();
const redis: RedisCaptchaClient = {
get: (key) => values.get(key) ?? null,
set: (key, value) => void values.set(key, value),
del: (key) => (values.delete(key) ? 1 : 0),
};
const store = new RedisCaptchaStore(redis, { prefix: "test:" });
await store.createChallenge(challenge("c1"));
expect((await store.incrementAttempts("c1", Date.now()))?.attempts).toBe(1);
const consumed = await Promise.all([
store.consumeChallenge("c1", Date.now()),
store.consumeChallenge("c1", Date.now()),
]);
expect(consumed.filter(Boolean)).toHaveLength(1);
await store.createToken(token("t1"));
const tokens = await Promise.all([
store.consumeToken("t1", Date.now()),
store.consumeToken("t1", Date.now()),
]);
expect(tokens.filter(Boolean)).toHaveLength(1);
await store.deleteChallenge("c1");
await store.deleteToken("t1");
expect(await store.getChallenge("c1")).toBeUndefined();
});
test("managed provider separates public creation from secret verification", async () => {
const requests: RequestInit[] = [];
const provider = managedCaptchaProvider({
baseUrl: "https://captcha.test/",
siteKey: "site",
secretKey: "secret",
fetch: (async (_url, init) => {
requests.push(init ?? {});
return Response.json(
requests.length === 1
? {
id: "c1",
provider: "managed",
type: "text",
presentation: "visual",
action: "signup",
prompt: "Type",
createdAt: 1,
expiresAt: 2,
responseField: "token",
}
: { success: true, provider: "managed", action: "signup" },
);
}) as typeof fetch,
});
await provider.createChallenge({ action: "signup" });
await provider.verify({ action: "signup", providerToken: "answer" });
expect(new Headers(requests[0]?.headers).has("authorization")).toBe(false);
expect(new Headers(requests[1]?.headers).get("authorization")).toBe("Bearer secret");
});
});
@@ -0,0 +1,71 @@
import { describe, expect, test } from "bun:test";
import { createContext } from "@wrnexus/core";
import { v } from "@wrnexus/validation";
import {
captchaPlugin,
defineCaptchaProvider,
parseWithCaptcha,
type CaptchaProvider,
} from "../src/index.ts";
import type { PluginContext, TransformContext } from "@wrnexus/plugin";
describe("CAPTCHA validation and development audit", () => {
test("combines schema and action-bound provider verification failures", async () => {
const requests: unknown[] = [];
const provider: CaptchaProvider = defineCaptchaProvider({
name: "fixture",
client: { responseField: "captcha-response" },
async verify(input) {
requests.push(input);
return { success: false, provider: "fixture", action: input.action, message: "Try again" };
},
});
const ctx = createContext(
new Request("https://app.test/signup"),
new URL("https://app.test/signup"),
);
const result = await parseWithCaptcha(
v.object({ email: v.string().email() }),
{ email: "invalid", "captcha-response": "token" },
ctx,
{ action: "signup", provider, bindIp: true },
);
expect(result.ok).toBe(false);
expect(result.errors.email).toBeDefined();
expect(result.errors["captcha-response"]).toBe("Try again");
expect(requests[0]).toEqual(
expect.objectContaining({ action: "signup", hostname: "app.test", providerToken: "token" }),
);
});
test("rejects incomplete custom providers", () => {
expect(() =>
defineCaptchaProvider({ name: "", client: { responseField: "x" } } as never),
).toThrow("stable name");
expect(() =>
defineCaptchaProvider({ name: "x", client: { responseField: "" } } as never),
).toThrow("responseField");
expect(() =>
defineCaptchaProvider({ name: "x", client: { responseField: "token" } } as never),
).toThrow("verify()");
});
test("flags exposed secrets, missing provider keys, and inaccessible hard challenges", async () => {
const metadata = new Map<string, unknown>();
const plugin = captchaPlugin();
const context = {
mode: "development",
file: "signup.wrn",
metadata,
} as TransformContext;
await plugin.transformCode?.(
`<Captcha provider="turnstile" secretKey="leaked" action="signup" disturbance="90" showAudio="false" />`,
context,
);
const panels = await plugin.devToolbarPanels?.({ metadata } as PluginContext);
const ids = panels?.[0]?.issues?.map((issue) => String((issue as { id: string }).id)) ?? [];
expect(ids.some((id) => id.startsWith("client-secret:"))).toBe(true);
expect(ids.some((id) => id.startsWith("missing-site-key:"))).toBe(true);
expect(ids.some((id) => id.startsWith("hard-without-audio:"))).toBe(true);
});
});
+42 -10
View File
@@ -23,7 +23,7 @@ import {
} from "node:fs";
import { basename, dirname, extname, join, relative, resolve } from "node:path";
import { buildRouter, type Route } from "@wrnexus/router";
import { getReactiveRuntime } from "@wrnexus/csr";
import { getComponentControllerRuntime, getReactiveRuntime } from "@wrnexus/csr";
import {
analyzeRuntimeImports,
analyzeRuntimeRequirements,
@@ -44,6 +44,7 @@ import {
bundleCss,
renderStyles,
resolveThemeConfig,
renderActiveThemeCss,
renderThemeCss,
renderThemeRuntime,
} from "@wrnexus/styles";
@@ -109,6 +110,7 @@ export async function runBuild(appRoot: string): Promise<void> {
const compiledDir = join(distDir, "compiled");
const clientModulesDir = join(distDir, "client");
const reactivePath = join(distDir, "reactive.js");
const controllersPath = join(distDir, "controllers.js");
const publicDir = join(root, "public");
const distPublicDir = join(distDir, "public");
const config = await loadAppConfig(root);
@@ -147,7 +149,12 @@ export async function runBuild(appRoot: string): Promise<void> {
const pluginContributions = await pluginRunner.contributions();
const { generateApplicationTypes } = await import("./types.ts");
generateApplicationTypes(root, pluginContributions);
const componentDirs = [uiComponentsDir(), ...pluginContributions.componentDirs];
const bundledUiDir = uiComponentsDir();
const componentDirs = [
bundledUiDir,
join(bundledUiDir, "..", "styles"),
...pluginContributions.componentDirs,
];
await pluginRunner.hook("buildStart");
const virtualModules = new Map<string, string>();
for (const [index, module] of pluginContributions.virtualModules.entries()) {
@@ -477,6 +484,13 @@ export async function runBuild(appRoot: string): Promise<void> {
);
assetHash.update(reactiveCode);
console.log(`✓ Runtime: ${reactivePath}`);
const controllerCode = await buildBrowserRuntime(
getComponentControllerRuntime(),
controllersPath,
join(compiledDir, "controllers.entry.js"),
);
assetHash.update(controllerCode);
console.log(`✓ Controllers: ${controllersPath}`);
// 1a) Theme tokens + client switcher (always emitted; built-in light/dark).
const theme = resolveThemeConfig(config.theme);
@@ -484,6 +498,19 @@ export async function runBuild(appRoot: string): Promise<void> {
const themeJs = renderThemeRuntime(theme);
writeFileSync(join(distDir, "theme.css"), themeCss, "utf8");
writeFileSync(join(distDir, "theme.js"), themeJs, "utf8");
const themeAssetsDir = join(distDir, "theme");
for (const themeName of theme.names) {
const themeDir = join(themeAssetsDir, encodeURIComponent(themeName));
mkdirSync(themeDir, { recursive: true });
writeFileSync(join(themeDir, "_.css"), renderActiveThemeCss(theme, themeName), "utf8");
for (const accentName of theme.accentNames) {
writeFileSync(
join(themeDir, `${encodeURIComponent(accentName)}.css`),
renderActiveThemeCss(theme, themeName, accentName),
"utf8",
);
}
}
assetHash.update(themeCss);
assetHash.update(themeJs);
console.log(`✓ Theme: ${theme.names.length} themes (default: ${theme.default})`);
@@ -539,14 +566,17 @@ export async function runBuild(appRoot: string): Promise<void> {
},
config.styles,
);
const combinedSource = `${frameworkStyles}\n${css}`;
// Theme tokens are request-selected and loaded separately. Keep only the
// shared UI primitives with application CSS so the full theme/accent
// matrix cannot leak back into the blocking stylesheet.
const combinedSource = `${uiStyles}\n${css}`;
const combinedInput = join(distDir, ".wrnexus-combined.css");
writeFileSync(combinedInput, combinedSource, "utf8");
const combinedCss = await bundleCss(combinedInput, "production");
rmSync(combinedInput, { force: true });
assetHash.update(combinedCss);
// One blocking CSS request in production: tokens → UI application CSS.
// The standalone framework.css remains available for apps without global CSS.
// One shared blocking request for UI + application CSS. The small active
// theme stylesheet is selected from the request cookie and loaded first.
writeFileSync(join(distDir, "styles.css"), combinedCss, "utf8");
hasStyles = true;
if (Buffer.byteLength(combinedCss, "utf8") <= INLINE_CSS_LIMIT_BYTES) {
@@ -628,8 +658,8 @@ applyAuthzManifestEarly([${authzSetupEntries}]);
const componentsLit = router.components
.map((c) => {
const v = `c${counter++}`;
imports.push(`import * as ${v} from ${JSON.stringify(importPathFor(c.file))};`);
return `{ name: ${JSON.stringify(c.name)}, mod: ${v} }`;
imports.push(`import { render as ${v} } from ${JSON.stringify(importPathFor(c.file))};`);
return `{ name: ${JSON.stringify(c.name)}, mod: { render: ${v} } }`;
})
.join(", ");
console.log(`✓ Components: ${router.components.length}`);
@@ -638,8 +668,8 @@ applyAuthzManifestEarly([${authzSetupEntries}]);
const layoutsLit = router.layouts
.map((l) => {
const v = `c${counter++}`;
imports.push(`import * as ${v} from ${JSON.stringify(importPathFor(l.file))};`);
return `{ name: ${JSON.stringify(l.name)}, mod: ${v} }`;
imports.push(`import { render as ${v} } from ${JSON.stringify(importPathFor(l.file))};`);
return `{ name: ${JSON.stringify(l.name)}, mod: { render: ${v} } }`;
})
.join(", ");
if (router.layouts.length) console.log(`✓ Layouts: ${router.layouts.length}`);
@@ -693,8 +723,10 @@ await createProductionServer(
},
{
reactivePath: join(import.meta.dir, "reactive.js"),
controllersPath: join(import.meta.dir, "controllers.js"),
clientModulesDir: join(import.meta.dir, "client"),
themePath: join(import.meta.dir, "theme.css"),
themeAssetsDir: join(import.meta.dir, "theme"),
themeJsPath: join(import.meta.dir, "theme.js"),
theme: ${JSON.stringify(theme)},
uiCssPath: join(import.meta.dir, "ui.css"),
@@ -719,7 +751,7 @@ await createProductionServer(
realtime: ${config.realtime ? JSON.stringify(config.realtime) : "undefined"},
publicDir: join(import.meta.dir, "public"),
${hasStyles ? `stylesPath: join(import.meta.dir, "styles.css"),` : ""}
${hasStyles ? `stylesIncludeFramework: true,` : ""}
${hasStyles ? `stylesIncludeUi: true,` : ""}
${inlineStyles ? `inlineStyles: ${JSON.stringify(inlineStyles)},` : ""}
assetVersion: ${JSON.stringify(assetVersion)},
clientRuntimes: ${JSON.stringify(emittedPluginAssets.runtimes)},
+9 -2
View File
@@ -11,7 +11,10 @@ export type InspectTarget =
export function inspectComponent(appRoot: string, requestedName: string): unknown {
const root = resolve(appRoot);
const router = buildRouter(join(root, "app"), { componentDirs: [uiComponentsDir()] });
const bundledUiDir = uiComponentsDir();
const router = buildRouter(join(root, "app"), {
componentDirs: [bundledUiDir, join(bundledUiDir, "..", "styles")],
});
const component = router.components.find(
(candidate) => candidate.name.toLowerCase() === requestedName.toLowerCase(),
);
@@ -139,7 +142,11 @@ export async function inspectProject(appRoot: string, target: InspectTarget): Pr
database: database ?? "default",
}));
const router = buildRouter(join(root, "app"), {
componentDirs: [uiComponentsDir(), ...contributions.componentDirs],
componentDirs: [
uiComponentsDir(),
join(uiComponentsDir(), "..", "styles"),
...contributions.componentDirs,
],
externalRoutes: contributions.routes,
middlewareFiles: contributions.middleware,
});
+1 -2
View File
@@ -75,7 +75,7 @@ function writePluginArtifacts(root: string, contributions?: PluginContributions)
});
writeFileSync(
join(typeDir, "wrnexus.plugins.generated.d.ts"),
`// AUTO-GENERATED plugin type aggregation - do not edit.\n${references.join("\n")}\n`,
`// AUTO-GENERATED plugin type aggregation - do not edit.\n${references.length > 0 ? `${references.join("\n")}\n` : ""}`,
"utf8",
);
const docsDir = join(root, ".wrnexus", "documentation");
@@ -182,7 +182,6 @@ export function generateApplicationTypes(
.map((name) => join(root, name))
.find(existsSync);
const code = `// AUTO-GENERATED by \`wrnexus generate types\` - do not edit.
/* eslint-disable @typescript-eslint/no-empty-object-type */
declare namespace WRNexusGenerated {
type ApiContract<T> = T extends import("@wrnexus/core").DefinedEndpoint<infer I, infer O>
? { input: I; output: O }
+17
View File
@@ -2098,6 +2098,23 @@ const MIGRATIONS: Migration[] = [
// start painting where they previously rendered as nothing.
},
},
{
version: "0.8.7",
id: "0.8.7-ui-component-depth",
description:
"Removes superseded UI scaffolds and moves the remaining utility-styled components to self-contained wire-* BEM styles.",
apply() {
// AdvancedDatePicker -> DatePicker; AdvancedRangeSlider -> RangeSlider;
// FileUpload -> FileInput + FileUploadProgress (or @wrnexus/uploader);
// Toast and ToastNotifications -> Toaster.
//
// List, InputNumber, Marquee, TextLink, Map, SearchBox and Timeline no
// longer render Tailwind utility class names. Application CSS selecting
// those internal utilities must move to the component's wire-* BEM
// classes or its data-size/data-variant attributes. List now emits
// select for non-link items as well as navigation items.
},
},
];
/** Release tooling uses this to require an explicit migration entry per version. */
@@ -0,0 +1,38 @@
import { afterAll, expect, test } from "bun:test";
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { runBuild } from "../src/build.ts";
const scratchRoot = join(import.meta.dir, ".tmp-css-delivery");
mkdirSync(scratchRoot, { recursive: true });
afterAll(() => rmSync(scratchRoot, { recursive: true, force: true }));
test("production ships active themes separately and retains one component style copy", async () => {
const root = mkdtempSync(join(scratchRoot, "app-"));
mkdirSync(join(root, "app", "pages"), { recursive: true });
mkdirSync(join(root, "app", "components"), { recursive: true });
mkdirSync(join(root, "app", "styles"), { recursive: true });
writeFileSync(join(root, "package.json"), JSON.stringify({ name: "css-delivery-fixture" }));
writeFileSync(
join(root, "wrnexus.config.ts"),
`export default { theme: { default: "dark", palette: "violet" }, styles: { entry: "app/styles/global.css" } };\n`,
);
writeFileSync(join(root, "app", "styles", "global.css"), ".app-shell{display:block}\n");
writeFileSync(
join(root, "app", "components", "Probe.wrn"),
`component Probe { style { .unique-server-style-marker{color:red} } view { <p class="unique-server-style-marker">Probe</p> } }\n`,
);
writeFileSync(
join(root, "app", "pages", "index.wrn"),
`import Probe from "../components/Probe.wrn"\npage Home { view { <main class="app-shell"><Probe /></main> } }\n`,
);
await runBuild(root);
const styles = readFileSync(join(root, "dist", "styles.css"), "utf8");
const activeTheme = readFileSync(join(root, "dist", "theme", "dark", "violet.css"), "utf8");
const server = readFileSync(join(root, "dist", "server.js"), "utf8");
expect(styles).toContain(".app-shell");
expect(styles).not.toContain("[data-theme=");
expect(activeTheme.length).toBeLessThan(10_000);
expect(server.match(/unique-server-style-marker/g)).toHaveLength(2);
});
+1
View File
@@ -201,6 +201,7 @@ A file opens with `page <Name>` or `component <Name>` followed by a `{ ... }` bo
- `@event name = function` inside `props` — declares a public component event. Emit it from component behavior with `name(detail)` or `$emit("name", detail)`, and consume it with `<Component @name="handler(event)" />`.
- `state <ident>: Type = <expr>` — typed reactive state seeded from a raw JS expression, including native array and object literals. The annotation is optional for backward compatibility.
- `view { <html> }` — plain HTML with `{expr}` interpolation in text and attributes, JSX-style component props such as `items={items}`, `items={[...]}`, and `options={{...}}`, hyphenated attributes, boolean attributes, `@event="..."` client bindings, and `<!-- comments -->`. Structured component props are serialized safely for SSR; expressions that reference `state` retain their initial value and update reactively in the browser.
- Client functions automatically commit state changed by `setTimeout` callbacks. For other deferred callbacks (observers, third-party APIs, or detached promise callbacks), call the injected `commit()` function after changing local state; returning/awaiting a promise also commits through the normal function boundary.
- `seo { key = "value" ... }` — metadata merged into the generated `meta`.
- `style { <raw css> }` — inlined page/component stylesheet (repeatable).
- `functions { <TypeScript> }` — helpers with typed parameters and return values. Types remain in server output and are safely erased from browser behavior code.
+105 -27
View File
@@ -174,7 +174,7 @@ export function browserModuleRequired(ast: PageAst): boolean {
return functions.length > 0 || selectedBrowserImports(ast, functions).length > 0;
}
function functionEntry(
function _functionEntry(
ast: PageAst,
fn: RuntimeFunctionDecl,
availableFunctions: string[],
@@ -240,35 +240,50 @@ function functionEntry(
const syncStateFromContext = stateNames
.map((name) => `${name} = context.state.${name};`)
.join(" ");
const peerAliases = functionAliases
.map((name) => {
const call = `context.functions[${JSON.stringify(name)}](...__wrnexusPeerArgs)`;
if (!stateNames.length) {
return `const ${name} = (...__wrnexusPeerArgs) => ${call};`;
}
return `const ${name} = (...__wrnexusPeerArgs) => {
${syncStateToContext}
let __wrnexusPeerResult;
const peerAliases = !stateNames.length
? functionAliases
.map(
(name) =>
`const ${name} = (...__wrnexusPeerArgs) => context.functions[${JSON.stringify(name)}](...__wrnexusPeerArgs);`,
)
.join("\n")
: `const __wrnexusFlush = () => { ${syncStateToContext} };
const __wrnexusRestore = () => { ${syncStateFromContext} };
const __wrnexusPeer = (name, args) => {
__wrnexusFlush();
let result;
try {
__wrnexusPeerResult = ${call};
} catch (__wrnexusPeerError) {
${syncStateFromContext}
throw __wrnexusPeerError;
result = context.functions[name](...args);
} catch (error) {
__wrnexusRestore();
throw error;
}
if (__wrnexusPeerResult && typeof __wrnexusPeerResult.then === "function") {
return Promise.resolve(__wrnexusPeerResult).finally(() => { ${syncStateFromContext} });
if (result && typeof result.then === "function") {
return Promise.resolve(result).finally(__wrnexusRestore);
}
${syncStateFromContext}
return __wrnexusPeerResult;
};`;
})
.join("\n");
__wrnexusRestore();
return result;
};
${functionAliases.map((name) => `const ${name} = (...__wrnexusPeerArgs) => __wrnexusPeer(${JSON.stringify(name)}, __wrnexusPeerArgs);`).join("\n")}`;
const copyBack = stateNames
.map(
(name) =>
`if (!Object.is(${name}, __wrnexusInitialState[${JSON.stringify(name)}])) context.state.${name} = ${name};`,
)
.join("\n");
const commitBinding = stateNames.length
? `const __wrnexusCommit = () => { ${copyBack} };
${!parameterNames.has("commit") && !declaredLocals.has("commit") && !functionAliases.includes("commit") ? "const commit = __wrnexusCommit;" : ""}
${
!parameterNames.has("setTimeout") &&
!declaredLocals.has("setTimeout") &&
!functionAliases.includes("setTimeout")
? `const setTimeout = (callback, delay, ...args) => globalThis.setTimeout(() => {
try { return callback(...args); } finally { __wrnexusCommit(); }
}, delay);`
: ""
}`
: "";
const body = eraseFunctionTypes(fn.body);
const runtimeBindings = [
!parameterNames.has("output") ? "const output = context.output;" : "",
@@ -282,12 +297,13 @@ function functionEntry(
${initialStateSnapshot}
${stateAliases}
${propAliases}
${commitBinding}
${peerAliases}
${runtimeBindings}
try {
${body}
} finally {
${copyBack}
${stateNames.length ? "__wrnexusCommit();" : ""}
}
}`;
}
@@ -301,21 +317,83 @@ export function generateBrowserModule(ast: PageAst): string {
const selectedImports = selectedBrowserImports(ast, functions);
const imports = selectedImports.map((entry) => entry.code).join("\n");
const importedBindings = [...new Set(selectedImports.flatMap((entry) => entry.bindings))];
const sharedState = state.filter((name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name));
const sharedProps = ast.props
.map((entry) => entry.name)
.filter(
(name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name) && !sharedState.includes(name),
);
const callableAliases = functionNames.filter(
(name) =>
safeIdentifier(name) &&
!RUNTIME_BINDINGS.has(name) &&
!sharedState.includes(name) &&
!sharedProps.includes(name),
);
const sharedCommit = sharedState.map((name) => `context.state.${name} = ${name};`).join(" ");
const sharedRestore = [
...sharedState.map((name) => `${name} = context.state.${name};`),
...sharedProps.map((name) => `${name} = context.props.${name};`),
].join(" ");
const hasAuthoredCommit = callableAliases.includes("commit");
const hasAuthoredSetTimeout = callableAliases.includes("setTimeout");
const implementations = functions
.map((fn) => {
const parameters = fn.parameters.map((parameter) => parameter.name).join(", ");
return `${JSON.stringify(fn.name)}: ${fn.async ? "async " : ""}function(${parameters}) {
try { ${eraseFunctionTypes(fn.body)} } finally { __wrnexusCommit(); }
}`;
})
.join(",\n");
return `// generated WRNexusJS browser module for ${ast.name}
${imports}
export const __wrnexusClientFunctions = {
${functions.map((fn) => ` ${functionEntry(ast, fn, functionNames)}`).join(",\n")}
${functions
.map(
(fn) =>
` ${JSON.stringify(fn.name)}: (context, ...args) => __wrnexusBindings(context)[${JSON.stringify(fn.name)}](...args)`,
)
.join(",\n")}
};
export const __wrnexusClientState = ${JSON.stringify(state)};
export const __wrnexusOutputs = ${JSON.stringify(ast.outputs)};
export const __wrnexusImportedBindings = { ${importedBindings.join(", ")} };
export function bindClientScope(context) {
function __wrnexusCreateClientFunctions(context) {
${sharedState.length ? `let { ${sharedState.join(", ")} } = context.state;` : ""}
${sharedProps.length ? `let { ${sharedProps.join(", ")} } = context.props;` : ""}
const output = context.output;
const server = context.server;
const props = context.props;
const refs = context.refs;
const __wrnexusCommit = () => { ${sharedCommit} };
const __wrnexusRestore = () => { ${sharedRestore} };
${!hasAuthoredCommit ? "const commit = __wrnexusCommit;" : ""}
${
!hasAuthoredSetTimeout
? `const setTimeout = (callback, delay, ...args) => globalThis.setTimeout(() => {
try { return callback(...args); } finally { __wrnexusCommit(); }
}, delay);`
: ""
}
const implementations = {
${implementations}
};
${callableAliases.map((name) => `const ${name} = (...args) => implementations[${JSON.stringify(name)}](...args);`).join("\n ")}
const functions = {};
const scopedContext = { ...context, functions };
for (const [name, handler] of Object.entries(__wrnexusClientFunctions)) {
functions[name] = (...args) => handler(scopedContext, ...args);
for (const name of Object.keys(implementations)) {
functions[name] = (...args) => {
__wrnexusRestore();
return implementations[name](...args);
};
}
return functions;
}
function __wrnexusBindings(context) {
return context.__wrnexusBoundFunctions ||
(context.__wrnexusBoundFunctions = __wrnexusCreateClientFunctions(context));
}
export function bindClientScope(context) {
return __wrnexusBindings(context);
}
`;
}
+69 -60
View File
@@ -751,16 +751,15 @@ function renderNestedComponentInvocation(
const rendered = ` ${attr.name}="${compiledValue}"`;
if (
wholeExpression ||
!attr.value.includes("{") ||
!exprRefsState(attr.value, ctx.stateNames)
(!exprRefsState(attr.value, ctx.stateNames) && !exprRefsState(attr.value, ctx.propNames))
) {
return rendered;
}
const marker = attrEscape(JSON.stringify([attr.name, attr.value]));
return rendered + ` data-wrn-bind-${bindIndex++}="${escLit(marker)}"`;
return rendered + ` data-wrn-prop-bind-${bindIndex++}="${escLit(marker)}"`;
})
.join("");
@@ -883,7 +882,12 @@ function __wrnexusEscapeHtml(value: unknown): string {
return String(value).replace(/[&<>"']/g, (ch) => __wrnexusHtmlEscapes[ch] ?? ch);
}
function __wrnexusEvalData(data: unknown, body: string, helpers = "", ctx: any): unknown {
type __WrnexusContext = import("@wrnexus/core").Context & {
__wrnexusCallApi?: (path: string, method: string) => Promise<unknown>;
localStorage?: unknown;
};
function __wrnexusEvalData(data: unknown, body: string, helpers = "", ctx: __WrnexusContext): unknown {
const adapters = {
cookies: ctx.cookies,
session: ctx.session,
@@ -914,7 +918,7 @@ function __wrnexusPropAttr(
);
}
async function __wrnexusCallApi(path: string, method: string, ctx: any): Promise<unknown> {
async function __wrnexusCallApi(path: string, method: string, ctx: __WrnexusContext): Promise<unknown> {
if (typeof ctx.__wrnexusCallApi === "function") {
return await ctx.__wrnexusCallApi(path, method);
}
@@ -929,7 +933,7 @@ async function __wrnexusCallApi(path: string, method: string, ctx: any): Promise
return type.includes("application/json") ? await res.json() : await res.text();
}
async function __wrnexusRenderSsrBindings(html: string, ctx: any): Promise<string> {
async function __wrnexusRenderSsrBindings(html: string, ctx: __WrnexusContext): Promise<string> {
for (const binding of __wrnexusSsrBindings) {
const data = await __wrnexusCallApi(binding.path, binding.method, ctx);
const value = __wrnexusEvalData(data, binding.body, binding.helpers, ctx);
@@ -1439,14 +1443,14 @@ export function generate(ast: PageAst): string {
out.push(`const __wrnexusSsrBindings = ${JSON.stringify(ssrBindings, null, 2)};`);
const decls = loopConsts.length > 0 ? loopConsts.join("\n") + "\n" : "";
out.push(
`export default async function ${ast.name}(ctx: any) {
`export default async function ${ast.name}(ctx: __WrnexusContext) {
${storeDeclarations}
${serverLoadAliases}
${decls}
const __state: ${stateType} = ${dynamicStateInitializer};
${ssrStateAliases}
${ssrComputedAliases}
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]]));
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)]));
const __scopeValue = Object.entries(__hydrationState)
.map(([key, value]) => {
let encoded: string;
@@ -1473,13 +1477,13 @@ export function generate(ast: PageAst): string {
);
} else {
out.push(
`export default ${storeBindings.length > 0 ? "async " : ""}function ${ast.name}(ctx: any) {
`export default ${storeBindings.length > 0 ? "async " : ""}function ${ast.name}(ctx: import("@wrnexus/core").Context) {
${storeDeclarations}
${serverLoadAliases}
const __state: ${stateType} = ${dynamicStateInitializer};
${ssrStateAliases}
${ssrComputedAliases}
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]]));
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)]));
const __scopeValue = Object.entries(__hydrationState)
.map(([key, value]) => {
let encoded: string;
@@ -1506,14 +1510,14 @@ export function generate(ast: PageAst): string {
if (staticShellBody !== undefined) {
out.push(
`export async function __wrnexusBuildStaticShell(ctx: any = {}) {
`export async function __wrnexusBuildStaticShell(ctx: import("@wrnexus/core").Context = {} as import("@wrnexus/core").Context) {
${storeDeclarations}
${serverLoadAliases}
${loopConsts.length > 0 ? loopConsts.join("\n") : ""}
const __state: ${stateType} = ${dynamicStateInitializer};
${ssrStateAliases}
${ssrComputedAliases}
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]]));
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)]));
const __scopeValue = Object.entries(__hydrationState)
.map(([key, value]) => {
let encoded: string;
@@ -1573,7 +1577,7 @@ export function generate(ast: PageAst): string {
})
.join("\n");
const visible = exposed.filter((entry) => entry.name);
return `export async function ${exportName}(ctx: any) {
return `export async function ${exportName}(ctx: import("@wrnexus/core").Context) {
${exposed
.filter((entry) => !entry.name)
.map((entry) => entry.body)
@@ -1600,7 +1604,7 @@ ${
);
continue;
}
out.push(`export async function ${action.name}(input: any, ctx: any) {
out.push(`export async function ${action.name}(input: InferSchema<typeof ${action.schema}>, ctx: import("@wrnexus/core").Context) {
const invalidate = (...tags: string[]) => {
const bucket = (ctx.locals.__wrnexusInvalidatedTags ??= []);
bucket.push(...tags.flat());
@@ -1631,7 +1635,7 @@ ${ast.actions
ast.apis.forEach((api, index) => {
const name = `__wrnexusApi_${api.method}_${index}`;
out.push(`// ${api.method} ${apiRoutePath(api.path)}
const ${name} = async (ctx: any) => {${api.body}};`);
const ${name} = async (ctx: import("@wrnexus/core").Context) => {${api.body}};`);
});
const entries = ast.apis.map(
@@ -1653,7 +1657,7 @@ const ${name} = async (ctx: any) => {${api.body}};`);
const handlers = ast.realtimes.flatMap((rt) =>
rt.handlers.map((h) => {
const params = ["ws", ...h.args].join(", ");
return ` ${h.event}(${params}: any) {${h.body}},`;
return ` ${h.event}(${params}: Event) {${h.body}},`;
}),
);
out.push(`export const websocket = {\n${handlers.join("\n")}\n};`);
@@ -1673,6 +1677,8 @@ const ${name} = async (ctx: any) => {${api.body}};`);
interface CompCtx {
/** State names — text referencing any of them stays a reactive client mustache. */
stateNames: Set<string>;
/** Props are signals too, allowing a parent to drive a mounted child. */
propNames: Set<string>;
/** Component functions can read state and therefore make their callers reactive. */
functionNames: Set<string>;
/** Rewrite reserved-word prop/state identifiers to their safe const names. */
@@ -1848,7 +1854,11 @@ function exprRefsState(expr: string, stateNames: Set<string>): boolean {
}
function exprRefsComponentReactiveValue(expr: string, ctx: CompCtx): boolean {
return exprRefsState(expr, ctx.stateNames) || exprRefsState(expr, ctx.functionNames);
return (
exprRefsState(expr, ctx.stateNames) ||
exprRefsState(expr, ctx.propNames) ||
exprRefsState(expr, ctx.functionNames)
);
}
function viewHasEvents(nodes: ViewNode[]): boolean {
@@ -1918,6 +1928,8 @@ function compileText(raw: string, ctx: CompCtx): string {
// Loop variable (from data-for): leave a literal client mustache — the
// list renderer fills it per item; it has no server-side value.
out += escLit(`{${expr}}`);
} else if (expr === "content") {
out += `\${__wireRaw(${ctx.resolveExpr(expr)})}`;
} else if (exprRefsComponentReactiveValue(expr, ctx)) {
// State interpolation: bake the initial value AND keep it reactive via a
// data-text span, so no-JS clients see the real value and hydration
@@ -1926,8 +1938,6 @@ function compileText(raw: string, ctx: CompCtx): string {
escLit(`<span data-text="${attrEscape(expr)}">`) +
`\${__wireHtml(${ctx.resolveExpr(expr)})}` +
escLit(`</span>`);
} else if (expr === "content") {
out += `\${__wireRaw(${ctx.resolveExpr(expr)})}`;
} else {
out += `\${__wireHtml(${ctx.resolveExpr(expr)})}`;
}
@@ -2346,6 +2356,7 @@ function generateComponent(ast: PageAst): string {
};
const ctx: CompCtx = {
stateNames,
propNames: new Set(effectiveProps.map((entry) => entry.name)),
functionNames: new Set(
ast.runtimeFunctions.filter((fn) => fn.runtime !== "server").map((fn) => fn.name),
),
@@ -2374,14 +2385,14 @@ function generateComponent(ast: PageAst): string {
const styles = ast.styles.map((body) => body.trim()).filter(Boolean);
const styleTag = escLit(localStyleTag(ast, styles));
// A component needs a reactive scope only when it has state or event handlers.
// Prop-driven text/attributes are baked server-side, so static components ship
// no JavaScript at all.
// Props remain server-rendered and also become signals so a parent can drive
// a mounted child after hydration.
const behavior = componentBehavior(ast);
const needsScope =
ast.runtime !== "server" &&
(browserStates.length > 0 ||
(effectiveProps.length > 0 ||
browserStates.length > 0 ||
ast.computed.length > 0 ||
viewHasEvents(ast.view) ||
behavior !== null);
@@ -2407,7 +2418,7 @@ function generateComponent(ast: PageAst): string {
);
}
decls.push(
` const ${nameRefs.get(prop.name)}: ${prop.valueType ?? "any"} = __coerce(__p[${JSON.stringify(prop.name)}], (${resolveExpr(prop.default)}), ${JSON.stringify(runtimeTypeOf(prop.valueType))});`,
` const ${nameRefs.get(prop.name)}: ${prop.valueType ?? "unknown"} = __coerce(__p[${JSON.stringify(prop.name)}], (${resolveExpr(prop.default)}), ${JSON.stringify(runtimeTypeOf(prop.valueType))}, ${JSON.stringify(prop.name)}) as ${prop.valueType ?? "unknown"};`,
);
}
if (!effectiveProps.some((prop) => prop.name === "attrs")) {
@@ -2497,7 +2508,7 @@ function generateComponent(ast: PageAst): string {
);
}
out.push(`function __coerce(v: any, def: any, declared: string = "unknown"): any {
out.push(`function __coerce(v: unknown, def: unknown, declared: string = "unknown", propName: string = "prop"): unknown {
if (v === undefined || v === null) {
return def;
}
@@ -2522,14 +2533,14 @@ function generateComponent(ast: PageAst): string {
if (typeof v === "string") {
try {
const parsed = JSON.parse(v);
return Array.isArray(parsed) ? parsed : def;
if (!Array.isArray(parsed)) throw new TypeError("Expected an array prop '" + propName + "'");
return parsed;
} catch {
if (declared === "array") throw new TypeError("Expected an array prop");
return def;
throw new TypeError("Expected an array prop '" + propName + "'");
}
}
return def;
throw new TypeError("Expected an array prop '" + propName + "'");
}
if (declared === "object" || (def !== null && typeof def === "object")) {
@@ -2545,23 +2556,19 @@ function generateComponent(ast: PageAst): string {
try {
const parsed = JSON.parse(v);
return (
parsed !== null &&
typeof parsed === "object" &&
!Array.isArray(parsed)
)
? parsed
: def;
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
throw new TypeError("Expected an object prop '" + propName + "'");
}
return parsed;
} catch {
if (declared === "object") throw new TypeError("Expected an object prop");
return def;
throw new TypeError("Expected an object prop '" + propName + "'");
}
}
return def;
throw new TypeError("Expected an object prop '" + propName + "'");
}
if (declared === "bigint") return BigInt(v);
if (declared === "bigint") return BigInt(v as string | number | bigint | boolean);
if (declared === "function" && typeof v !== "function") {
throw new TypeError("Expected a function prop");
}
@@ -2569,15 +2576,15 @@ function generateComponent(ast: PageAst): string {
}
function __restProps(
props: Record<string, any>,
props: Record<string, unknown>,
declared: Set<string>,
): Record<string, any> {
): Record<string, unknown> {
return Object.fromEntries(
Object.entries(props).filter(([name]) => !declared.has(name)),
);
}
function __wireHtml(v: any): string {
function __wireHtml(v: unknown): string {
return String(v == null ? "" : v).replace(
/[&<>]/g,
(c) =>
@@ -2589,7 +2596,7 @@ function __wireHtml(v: any): string {
);
}
function __wireAttr(v: any): string {
function __wireAttr(v: unknown): string {
return String(v == null ? "" : v).replace(
/[&<>"]/g,
(c) =>
@@ -2603,7 +2610,7 @@ function __wireAttr(v: any): string {
);
}
function __wireBooleanAttr(name: string, value: any): string {
function __wireBooleanAttr(name: string, value: unknown): string {
return value === true ||
value === "true" ||
value === "" ||
@@ -2614,7 +2621,7 @@ function __wireBooleanAttr(name: string, value: any): string {
: "";
}
function __wireSpreadAttrs(value: any): string {
function __wireSpreadAttrs(value: unknown): string {
if (value === null || typeof value !== "object" || Array.isArray(value)) return "";
const booleanAttributes = new Set(${JSON.stringify([...HTML_BOOLEAN_ATTRIBUTES])});
@@ -2628,10 +2635,12 @@ function __wireSpreadAttrs(value: any): string {
lowerName === "style" ||
lowerName === "slot" ||
lowerName === "data-component" ||
// Internal markers must not leak through a spread -- except the
// parent's output handlers, whose whole job is to ride from the mount
// onto the view root so the mounting scope can bind them there.
(lowerName.startsWith("data-wrn") && !lowerName.startsWith("data-wrn-out-"))
// Internal markers must not leak through a spread. Parent-owned output
// and prop bindings ride from the mount onto the
// rendered child root so the mounting scope can bind them there.
(lowerName.startsWith("data-wrn") &&
!lowerName.startsWith("data-wrn-out-") &&
!lowerName.startsWith("data-wrn-prop-bind-"))
) {
continue;
}
@@ -2648,7 +2657,7 @@ function __wireSpreadAttrs(value: any): string {
return attributes.join("");
}
function __wireProp(v: any): string {
function __wireProp(v: unknown): string {
const value =
v !== null && typeof v === "object"
? JSON.stringify(v)
@@ -2657,18 +2666,18 @@ function __wireProp(v: any): string {
return __wireAttr(value);
}
function __wireRaw(v: any): string {
function __wireRaw(v: unknown): string {
return String(v == null ? "" : v);
}`);
if (hasServerEach) {
out.push(`function __wrnexusEncodeLoopLocals(value: Record<string, any>): string {
out.push(`function __wrnexusEncodeLoopLocals(value: Record<string, unknown>): string {
return __WrnexusBuffer.from(JSON.stringify(value), "utf8").toString("base64");
}`);
}
if (needsScope) {
out.push(`function __wrnexusSerializeScopeValue(value: any): string {
out.push(`function __wrnexusSerializeScopeValue(value: unknown): string {
if (value === undefined) {
return "undefined";
}
@@ -2702,7 +2711,7 @@ function __wireRaw(v: any): string {
}
}
function __wrnexusScopeDecl(obj: Record<string, any>): string {
function __wrnexusScopeDecl(obj: Record<string, unknown>): string {
return Object.keys(obj)
.map(
(key) =>
@@ -2723,7 +2732,7 @@ function __wireRaw(v: any): string {
const serverFunctionSource = serverFunctions ? `${serverFunctions}\n` : "";
out.push(
`export function render(props: ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, any>"} = {} as ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, any>"}): string {\n` +
`export function render(props: ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, unknown>"} = {} as ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, unknown>"}): string {\n` +
` const __p = props || {};\n` +
(decls.length > 0 ? decls.join("\n") + "\n" : "") +
serverFunctionSource +
@@ -2738,7 +2747,7 @@ function __wireRaw(v: any): string {
return out.join("\n\n") + "\n";
}
function __wireRaw(v: any): string {
function __wireRaw(v: unknown): string {
return String(v == null ? "" : v);
}
@@ -2747,19 +2756,19 @@ function wholeAttributeExpression(value: string): string | null {
return match?.[1]?.trim() || null;
}
function __wireHtml(v: any): string {
function __wireHtml(v: unknown): string {
return String(v == null ? "" : v).replace(/[&<>]/g, (c) =>
c === "&" ? "&amp;" : c === "<" ? "&lt;" : "&gt;",
);
}
function __wireAttr(v: any): string {
function __wireAttr(v: unknown): string {
return String(v == null ? "" : v).replace(/[&<>"]/g, (c) =>
c === "&" ? "&amp;" : c === "<" ? "&lt;" : c === ">" ? "&gt;" : "&quot;",
);
}
function __wireProp(v: any): string {
function __wireProp(v: unknown): string {
const value =
v !== null && typeof v === "object" ? JSON.stringify(v) : String(v == null ? "" : v);
@@ -45,7 +45,7 @@ export interface CounterOutputs {
"change"(value: number): void;
}
function __coerce(v: any, def: any, declared: string = "unknown"): any {
function __coerce(v: unknown, def: unknown, declared: string = "unknown", propName: string = "prop"): unknown {
if (v === undefined || v === null) {
return def;
}
@@ -70,14 +70,14 @@ function __coerce(v: any, def: any, declared: string = "unknown"): any {
if (typeof v === "string") {
try {
const parsed = JSON.parse(v);
return Array.isArray(parsed) ? parsed : def;
if (!Array.isArray(parsed)) throw new TypeError("Expected an array prop '" + propName + "'");
return parsed;
} catch {
if (declared === "array") throw new TypeError("Expected an array prop");
return def;
throw new TypeError("Expected an array prop '" + propName + "'");
}
}
return def;
throw new TypeError("Expected an array prop '" + propName + "'");
}
if (declared === "object" || (def !== null && typeof def === "object")) {
@@ -93,23 +93,19 @@ function __coerce(v: any, def: any, declared: string = "unknown"): any {
try {
const parsed = JSON.parse(v);
return (
parsed !== null &&
typeof parsed === "object" &&
!Array.isArray(parsed)
)
? parsed
: def;
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
throw new TypeError("Expected an object prop '" + propName + "'");
}
return parsed;
} catch {
if (declared === "object") throw new TypeError("Expected an object prop");
return def;
throw new TypeError("Expected an object prop '" + propName + "'");
}
}
return def;
throw new TypeError("Expected an object prop '" + propName + "'");
}
if (declared === "bigint") return BigInt(v);
if (declared === "bigint") return BigInt(v as string | number | bigint | boolean);
if (declared === "function" && typeof v !== "function") {
throw new TypeError("Expected a function prop");
}
@@ -117,15 +113,15 @@ function __coerce(v: any, def: any, declared: string = "unknown"): any {
}
function __restProps(
props: Record<string, any>,
props: Record<string, unknown>,
declared: Set<string>,
): Record<string, any> {
): Record<string, unknown> {
return Object.fromEntries(
Object.entries(props).filter(([name]) => !declared.has(name)),
);
}
function __wireHtml(v: any): string {
function __wireHtml(v: unknown): string {
return String(v == null ? "" : v).replace(
/[&<>]/g,
(c) =>
@@ -137,7 +133,7 @@ function __wireHtml(v: any): string {
);
}
function __wireAttr(v: any): string {
function __wireAttr(v: unknown): string {
return String(v == null ? "" : v).replace(
/[&<>"]/g,
(c) =>
@@ -151,7 +147,7 @@ function __wireAttr(v: any): string {
);
}
function __wireBooleanAttr(name: string, value: any): string {
function __wireBooleanAttr(name: string, value: unknown): string {
return value === true ||
value === "true" ||
value === "" ||
@@ -162,7 +158,7 @@ function __wireBooleanAttr(name: string, value: any): string {
: "";
}
function __wireSpreadAttrs(value: any): string {
function __wireSpreadAttrs(value: unknown): string {
if (value === null || typeof value !== "object" || Array.isArray(value)) return "";
const booleanAttributes = new Set(["allowfullscreen","async","autofocus","autoplay","checked","controls","default","defer","disabled","formnovalidate","hidden","inert","ismap","itemscope","loop","multiple","muted","nomodule","novalidate","open","playsinline","readonly","required","reversed","selected"]);
@@ -176,10 +172,12 @@ function __wireSpreadAttrs(value: any): string {
lowerName === "style" ||
lowerName === "slot" ||
lowerName === "data-component" ||
// Internal markers must not leak through a spread -- except the
// parent's output handlers, whose whole job is to ride from the mount
// onto the view root so the mounting scope can bind them there.
(lowerName.startsWith("data-wrn") && !lowerName.startsWith("data-wrn-out-"))
// Internal markers must not leak through a spread. Parent-owned output
// and prop bindings ride from the mount onto the
// rendered child root so the mounting scope can bind them there.
(lowerName.startsWith("data-wrn") &&
!lowerName.startsWith("data-wrn-out-") &&
!lowerName.startsWith("data-wrn-prop-bind-"))
) {
continue;
}
@@ -196,7 +194,7 @@ function __wireSpreadAttrs(value: any): string {
return attributes.join("");
}
function __wireProp(v: any): string {
function __wireProp(v: unknown): string {
const value =
v !== null && typeof v === "object"
? JSON.stringify(v)
@@ -205,11 +203,11 @@ function __wireProp(v: any): string {
return __wireAttr(value);
}
function __wireRaw(v: any): string {
function __wireRaw(v: unknown): string {
return String(v == null ? "" : v);
}
function __wrnexusSerializeScopeValue(value: any): string {
function __wrnexusSerializeScopeValue(value: unknown): string {
if (value === undefined) {
return "undefined";
}
@@ -243,7 +241,7 @@ function __wrnexusSerializeScopeValue(value: any): string {
}
}
function __wrnexusScopeDecl(obj: Record<string, any>): string {
function __wrnexusScopeDecl(obj: Record<string, unknown>): string {
return Object.keys(obj)
.map(
(key) =>
@@ -262,14 +260,14 @@ function __wrnexusSerializeScopeValue(value: any): string {
export function render(props: CounterProps = {} as CounterProps): string {
const __p = props || {};
const label: string = __coerce(__p["label"], ("Count"), "string");
const label: string = __coerce(__p["label"], ("Count"), "string", "label") as string;
const __attrs = __restProps(__p, new Set(["label"]));
let count = (0);
const __scopeState = { "label": label, "count": count };
const __scope = __wrnexusScopeDecl(__scopeState);
const __scopePayload = __WrnexusBuffer.from(JSON.stringify(__scopeState), "utf8").toString("base64");
return \`<div data-scope="\${__scope}" data-wrn-scope="\${__scopePayload}" data-wrn-behavior="eyJmdW5jdGlvbnMiOiJmdW5jdGlvbiBpbmNyZW1lbnQoKXtcbiAgICAgIGNvdW50ID0gY291bnQgKyAxXG4gICAgICBvdXRwdXQuY2hhbmdlKGNvdW50KVxuICAgIH0iLCJvdXRwdXRzIjpbeyJuYW1lIjoiY2hhbmdlIiwicGF5bG9hZCI6eyJuYW1lIjoidmFsdWUiLCJ2YWx1ZVR5cGUiOiJudW1iZXIiLCJvcHRpb25hbCI6ZmFsc2V9fV0sImNvbXB1dGVkIjpbXSwiZWZmZWN0cyI6W10sImxpZmVjeWNsZSI6e30sIndhdGNoZXMiOltdfQ==" data-wrn-hydration="Counter:1skggk6" data-wrn-hydrate="load" data-wrn-runtime="universal" data-wrn-client-module="__WRNEXUS_CLIENT_MODULE__">
<div data-component="Button"\${__wireSpreadAttrs(__attrs)} on:click="\${__wireProp(increment)}">\${__wireHtml(label)}: <span data-text="count">\${__wireHtml(count)}</span></div>
<div data-component="Button"\${__wireSpreadAttrs(__attrs)} on:click="\${__wireProp(increment)}"><span data-text="label">\${__wireHtml(label)}</span>: <span data-text="count">\${__wireHtml(count)}</span></div>
</div>\`;
}
+64 -16
View File
@@ -200,9 +200,10 @@ test("typed props enforce required values and runtime-compatible input", async (
}`);
const render = component.render as (props?: Record<string, unknown>) => string;
expect(() => render()).toThrow("TypedInput requires prop 'label' (string)");
expect(render({ label: "Total", count: "4", enabled: "true" })).toContain(
"<span>Total:4:true</span>",
);
const rendered = render({ label: "Total", count: "4", enabled: "true" });
expect(rendered).toContain('<span data-text="label">Total</span>');
expect(rendered).toContain('<span data-text="count">4</span>');
expect(rendered).toContain('<span data-text="enabled">true</span>');
expect(() => render({ label: "Total", count: "many" })).toThrow("Expected a finite number prop");
expect(() => render({ label: "Total", enabled: "sometimes" })).toThrow("Expected a boolean prop");
});
@@ -238,13 +239,14 @@ test("dynamic HTML boolean attributes are omitted when false", async () => {
const render = mod.render as (props?: Record<string, unknown>) => string;
const enabled = render();
expect(enabled).not.toContain(" disabled");
expect(enabled).not.toContain(" checked");
expect(enabled).not.toContain(" required");
expect(enabled).not.toContain("<button disabled");
expect(enabled).not.toContain("<input checked");
expect(enabled).not.toContain("<input required");
const disabled = render({ disabled: "true", checked: "true", required: "true" });
expect(disabled).toContain("<button disabled>");
expect(disabled).toContain("<input checked required>");
expect(disabled).toContain("<button disabled");
expect(disabled).toContain("<input checked");
expect(disabled).toContain(" required");
});
test("components safely forward undeclared HTML attributes to their root", async () => {
@@ -282,7 +284,7 @@ test("components safely forward undeclared HTML attributes to their root", async
expect(html).not.toContain("variant=");
expect(html).not.toContain("onclick=");
expect(html).not.toContain("style=");
expect(html).not.toContain("data-wrn-bind");
expect(html).not.toContain('data-wrn-bind-0="unsafe"');
});
test("an explicit attrs spread overrides automatic root forwarding", async () => {
@@ -300,12 +302,13 @@ test("an explicit attrs spread overrides automatic root forwarding", async () =>
const render = mod.render as (props?: Record<string, unknown>) => string;
const html = render({ label: "Save", class: "wrapper", formaction: "/save" });
expect(html).toContain('<span class="wrapper">');
expect(html).toContain('<span class="wrapper"');
expect(html).not.toContain('<span formaction="/save"');
expect(html).toContain('<button formaction="/save">Save</button>');
expect(html).toContain('<button formaction="/save">');
expect(html).toContain('<span data-text="label">Save</span>');
});
test("stateless component bakes props into server HTML (zero JS)", async () => {
test("prop-driven component renders SSR content and exposes reactive prop signals", async () => {
const mod = await compileAndImport(
`component Button {\n props {\n label = "Button"\n variant = "default"\n class = ""\n }\n view { <button class="wire-btn wire-btn--{variant} {class}">{label}</button> }\n}`,
);
@@ -313,10 +316,11 @@ test("stateless component bakes props into server HTML (zero JS)", async () => {
const out = render({ label: "Save <b>", variant: "primary", class: "mt-2" });
expect(out).toContain('class="wire-btn wire-btn--primary mt-2"');
expect(out).toContain("Save &lt;b&gt;"); // html-escaped
expect(out).not.toContain("data-scope"); // no reactivity → no scope
expect(out).toContain("data-scope");
expect(out).toContain('data-text="label"');
});
test("stateful component: state text baked into a reactive data-text span, prop text baked", async () => {
test("stateful component keeps both prop and state text reactive", async () => {
const mod = await compileAndImport(
`component Counter {\n props {\n start = 0\n label = "Count"\n }\n state count = start\n view { <button @click="count++">{label}: {count}</button> }\n}`,
);
@@ -324,8 +328,7 @@ test("stateful component: state text baked into a reactive data-text span, prop
const out = render({ start: "10", label: "Score" });
expect(out).toContain('data-scope="start: 10, label: &quot;Score&quot;, count: 10"');
expect(out).toContain('data-on-click="count++"');
// label baked as static text; count baked as its initial value AND kept live.
expect(out).toContain('Score: <span data-text="count">10</span>');
expect(out).toContain('<span data-text="label">Score</span>: <span data-text="count">10</span>');
expect(out).toContain('data-scope="');
@@ -521,6 +524,17 @@ component NativeProps {
);
});
test("malformed structured prop strings fail with the prop name", async () => {
const mod = await compileAndImport(`component StructuredProps {
props { items = [] options = {} }
view { <div></div> }
}`);
const render = mod.render as (props?: Record<string, unknown>) => string;
expect(() => render({ items: "not-json" })).toThrow("Expected an array prop 'items'");
expect(() => render({ options: "[]" })).toThrow("Expected an object prop 'options'");
});
test("parses a layout block", () => {
const ast = parse(`
layout AppLayout {
@@ -1083,6 +1097,19 @@ page Home {
expect(output).toContain("__wrnexusPropAttr([");
});
test("nested component props retain parent-owned reactive bindings", () => {
const output = generate(
parse(`component Parent {
props { value = 0 }
state count = value
view { <Child value={count} /> }
}`),
);
expect(output).toContain("data-wrn-prop-bind-0");
expect(output).toContain("[&quot;value&quot;,&quot;{count}&quot;]");
expect(output).toContain('lowerName.startsWith("data-wrn-prop-bind-")');
});
test("component functions are available during server rendering", () => {
const output = generate(
parse(`
@@ -1470,3 +1497,24 @@ test("a boolean attribute bound to a loop variable binds on the client", () => {
expect(code).not.toContain('__wireBooleanAttr("checked"');
expect(code).toContain("data-wrn-bind-");
});
test("block comments inside props report the authoring restriction", () => {
expect(() =>
compileWireFile(`component Example {
props {
/* use a line comment */
label: string = "Example"
}
view { <span>{label}</span> }
}`),
).toThrow("Block comments are not allowed inside props {}; use // line comments instead");
});
test("state page reports its collision with the page keyword", () => {
expect(() =>
compileWireFile(`page Example {
state page = 1
view { <span>{page}</span> }
}`),
).toThrow("State name 'page' collides with the WRN 'page' keyword");
});
+81 -1
View File
@@ -1,4 +1,5 @@
import { expect, test } from "bun:test";
import { gzipSync } from "bun";
import { generateTargets, parse } from "../src/index.ts";
const ast = parse(`component ConfirmDialog {
@@ -130,7 +131,7 @@ test("browser codegen binds peer client functions through the scoped function ta
view { <button @click='run()'>{value}</button> }
}`),
);
expect(targets.browser).toContain("context.functions");
expect(targets.browser).toContain('implementations["increment"]');
const executable = new Function(
`${targets.browser.replace(/^export\s+/gm, "")}\nreturn { bindClientScope };`,
)() as { bindClientScope: (context: Record<string, unknown>) => Record<string, () => void> };
@@ -145,3 +146,82 @@ test("browser codegen binds peer client functions through the scoped function ta
functions.run?.();
expect(state.value).toBe(1);
});
test("deferred timer state writes commit after the client function returns", async () => {
const targets = generateTargets(
parse(`component DeferredWrite {
state { value: number = 0 }
functions {
client function later(): void { setTimeout(() => { value = 2 }, 0) }
}
view { <button @click='later()'>{value}</button> }
}`),
);
const executable = new Function(
`${targets.browser.replace(/^export\s+/gm, "")}\nreturn { bindClientScope };`,
)() as { bindClientScope: (context: Record<string, unknown>) => Record<string, () => void> };
let rendered = "0";
const state = new Proxy(
{ value: 0 },
{
set(target, property, value) {
Reflect.set(target, property, value);
rendered = String(value);
return true;
},
},
);
const functions = executable.bindClientScope({
state,
props: {},
output: {},
server: {},
refs: {},
});
functions.later?.();
expect(state.value).toBe(0);
await new Promise((resolve) => setTimeout(resolve, 10));
expect(state.value).toBe(2);
expect(rendered).toBe("2");
});
test("the deferred commit helper does not shadow an authored commit function", () => {
const targets = generateTargets(
parse(`component AuthoredCommit {
state { value: number = 0 }
functions {
client function commit(): void { value = 1 }
client function save(): void { commit() }
}
view { <button @click='save()'>{value}</button> }
}`),
);
expect(() => new Function(targets.browser.replace(/^export\s+/gm, ""))).not.toThrow();
expect(targets.browser).not.toContain("const commit = __wrnexusCommit");
});
test("generated client modules stay below decoded-size and duplication ratchets", () => {
const states = Array.from({ length: 12 }, (_, index) => `s${index}: number = ${index}`).join(" ");
const functions = Array.from(
{ length: 20 },
(_, index) =>
`client function f${index}(): void { s${index % 12} += 1 ${Array.from(
{ length: 20 },
(_unused, peer) => (peer === index ? "" : `if (false) f${peer}()`),
).join(" ")} }`,
).join("\n");
const browser = generateTargets(
parse(`component ClientSizeStress {
state { ${states} }
functions { ${functions} }
view { <button></button> }
}`),
).browser;
const decoded = new TextEncoder().encode(browser).length;
const compressed = gzipSync(new TextEncoder().encode(browser)).length;
expect(decoded).toBeLessThanOrEqual(13_000);
expect(decoded / compressed).toBeLessThanOrEqual(10);
});
+127 -2
View File
@@ -17,9 +17,134 @@ export { NAV_RUNTIME } from "./nav-runtime.ts";
export { REALTIME_RUNTIME } from "./realtime-runtime.ts";
export { ACTION_RUNTIME } from "./action-runtime.ts";
const CONTROLLER_SECTIONS = ["PRIMARY", "UI", "PIN"] as const;
function runtimeForMode(development: boolean): string {
if (development) {
return REACTIVE_RUNTIME.replace(/\/\*__WRNEXUS_DEV_START__\*\//g, "").replace(
/\/\*__WRNEXUS_DEV_END__\*\//g,
"",
);
}
return REACTIVE_RUNTIME.replace(
/\/\*__WRNEXUS_DEV_START__\*\/[\s\S]*?\/\*__WRNEXUS_DEV_END__\*\//g,
"",
);
}
function controllerSection(source: string, name: (typeof CONTROLLER_SECTIONS)[number]): string {
const pattern = new RegExp(
`/\\*__WRNEXUS_CONTROLLERS_${name}_START__\\*/([\\s\\S]*?)/\\*__WRNEXUS_CONTROLLERS_${name}_END__\\*/`,
);
const match = pattern.exec(source);
if (!match?.[1]) throw new Error(`Missing reactive runtime controller section: ${name}`);
return match[1];
}
function stripControllerSections(source: string): string {
for (const name of CONTROLLER_SECTIONS) {
source = source.replace(
new RegExp(
`/\\*__WRNEXUS_CONTROLLERS_${name}_START__\\*/[\\s\\S]*?/\\*__WRNEXUS_CONTROLLERS_${name}_END__\\*/`,
),
"",
);
}
return source;
}
const CONTROLLER_LOADER = String.raw`
var componentControllerSelector = "[data-wrn-anchored],[data-wrn-dialog],[data-wrn-roving],[data-wrn-scrollspy],[data-wrn-splitter],[data-wrn-navbar],[data-wrn-preferences],[data-wrn-select],[data-wrn-pin-input]";
var componentControllerPromise = null;
var componentControllerUrl = (document.currentScript && document.currentScript.src)
? new URL("./controllers.js", document.currentScript.src).href
: "/__wrnexus/controllers.js";
function needsComponentControllers(root) {
var host = root || document;
return !!((host.matches && host.matches(componentControllerSelector)) ||
(host.querySelector && host.querySelector(componentControllerSelector)));
}
function loadComponentControllers(root) {
if (!needsComponentControllers(root)) return;
if (window.__wrnexusComponentControllers) {
window.__wrnexusComponentControllers.hydrate(root || document);
return;
}
if (!componentControllerPromise) {
componentControllerPromise = new Promise(function (resolve, reject) {
var script = document.createElement("script");
script.src = componentControllerUrl;
script.defer = true;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
componentControllerPromise.then(function () {
if (window.__wrnexusComponentControllers) window.__wrnexusComponentControllers.hydrate(root || document);
}).catch(function (error) { console.error("[wrnexus] failed to load component controllers", error); });
}
`;
/** The reactive runtime served at `/__wrnexus/reactive.js` (plain browser JS). */
export function getReactiveRuntime(): string {
return REACTIVE_RUNTIME;
export function getReactiveRuntime(development = false): string {
let source = stripControllerSections(runtimeForMode(development));
source = source.replace(
" function hydrateScopes(root) {",
`${CONTROLLER_LOADER}\n function hydrateScopes(root) {`,
);
source = source.replace(
" hydrateNavbarControllers(host);\n hydratePreferenceControllers(host);\n hydrateSelectControllers(host);\n hydratePinInputControllers(host);",
" loadComponentControllers(host);",
);
source = source.replace(" startDocumentWatch();\n", "");
source = source.replace(
" setupAnchoredOverlays();\n setupModalDialogs();\n setupRovingFocus();\n setupScrollspy();\n setupSplitters();",
" loadComponentControllers(document);",
);
source = source.replace(
" window.__wrnexusRepositionAnchored = repositionAnchored;",
" window.__wrnexusRepositionAnchored = function (root) { if (window.__wrnexusComponentControllers) window.__wrnexusComponentControllers.reposition(root); };",
);
source = source.replace(
" window.__wrnexusMountClientRoots = mountClientRoots;",
` window.__wrnexusControllerBridge = { invokeComponentOutput: invokeComponentOutput, callServerFunction: callServerFunction, dispatchComponentEvent: dispatchComponentEvent, emitPinInputEvent: emitPinInputEvent, parseScopeDecl: parseScopeDecl, warn: ${development ? "warnOnce" : "function () {}"} };\n window.__wrnexusMountClientRoots = mountClientRoots;`,
);
return source;
}
/** Component-specific controllers, loaded only when their marker is present. */
export function getComponentControllerRuntime(development = false): string {
const source = runtimeForMode(development);
const sections = CONTROLLER_SECTIONS.map((name) => controllerSection(source, name)).join("\n");
return `
(function () {
"use strict";
var bridge = window.__wrnexusControllerBridge || {};
var invokeComponentOutput = bridge.invokeComponentOutput;
var callServerFunction = bridge.callServerFunction;
var dispatchComponentEvent = bridge.dispatchComponentEvent;
var emitPinInputEvent = bridge.emitPinInputEvent;
var parseScopeDecl = bridge.parseScopeDecl;
var warnOnce = bridge.warn || function () {};
${sections}
function hydrate(root) {
var host = root || document;
hydrateNavbarControllers(host);
hydratePreferenceControllers(host);
hydrateSelectControllers(host);
hydratePinInputControllers(host);
syncRovingGroups(host);
}
setupAnchoredOverlays();
setupModalDialogs();
setupRovingFocus();
setupScrollspy();
setupSplitters();
window.__wrnexusComponentControllers = { hydrate: hydrate, reposition: repositionAnchored };
hydrate(document);
})();
`.trim();
}
/** The client-side navigation runtime served at `/__wrnexus/nav.js`. */
+131 -8
View File
@@ -191,6 +191,68 @@ export const REACTIVE_RUNTIME = String.raw`
}
}
/*__WRNEXUS_DEV_START__*/
var developmentWarnings = Object.create(null);
function warnOnce(code, message, element, detail) {
var key = code + "\\n" + message;
if (developmentWarnings[key]) return;
developmentWarnings[key] = true;
var payload = {
code: code,
message: message,
hydrationId: element && element.getAttribute ? element.getAttribute("data-wrn-hydration") : null,
detail: detail || null,
};
console.warn("[" + code + "] " + message, detail || "");
try {
window.dispatchEvent(new CustomEvent("wrnexus:diagnostic", { detail: payload }));
} catch (_) {
// CustomEvent can be unavailable in minimal DOM test environments.
}
}
function warnMissingBindingFunction(statement, resolve, element) {
var match = /^\s*([A-Za-z_$][\w$]*)\s*\(/.exec(statement || "");
if (!match || typeof resolve(match[1]) === "function") return;
warnOnce(
"WRN-DEV-BINDING-MISSING",
"Component binding calls '" + match[1] + "', but that function does not exist in the parent scope.",
element,
{ binding: statement, functionName: match[1] },
);
}
function warnMissingThemeTokens() {
var referenced = Object.create(null);
function collect(rules) {
Array.prototype.forEach.call(rules || [], function (rule) {
var cssText = rule.cssText || "";
var match;
var pattern = /var\(\s*(--wire-[A-Za-z0-9_-]+)/g;
while ((match = pattern.exec(cssText))) referenced[match[1]] = true;
try {
if (rule.cssRules) collect(rule.cssRules);
} catch (_) {
// Cross-origin and disabled stylesheets can deny CSSOM access.
}
});
}
Array.prototype.forEach.call(document.styleSheets || [], function (sheet) {
try { collect(sheet.cssRules); } catch (_) {}
});
var rendered = window.getComputedStyle(document.documentElement);
Object.keys(referenced).forEach(function (token) {
if (rendered.getPropertyValue(token).trim()) return;
warnOnce(
"WRN-DEV-THEME-TOKEN-MISSING",
"Theme token '" + token + "' is referenced by rendered CSS but is not defined.",
document.documentElement,
{ token: token },
);
});
}
/*__WRNEXUS_DEV_END__*/
function scheduleUpdateHook(element, callback) {
pendingUpdateHooks.set(element, callback);
if (updateHooksScheduled) return;
@@ -2493,6 +2555,9 @@ export const REACTIVE_RUNTIME = String.raw`
locals.event = undefined;
locals.$event = undefined;
try {
/*__WRNEXUS_DEV_START__*/
warnMissingBindingFunction(outStmt, function (name) { return peekScope(name); }, componentRoot);
/*__WRNEXUS_DEV_END__*/
return runStmt(outStmt, locals);
} catch (error) {
console.error(
@@ -2535,9 +2600,43 @@ export const REACTIVE_RUNTIME = String.raw`
});
});
if (behavior) {
installBehaviorFunctions(behavior.functions);
var publicScopeApi = {
// Prop expressions belong to the parent that mounted the component. The
// server forwards these markers onto the rendered child root; evaluate
// them here and write changes into the child's prop signals.
Array.prototype.slice
.call(el.querySelectorAll("[data-scope], [data-wrn-scope]"))
.forEach(function (node) {
if (!node.parentNode || ownerScope(node.parentNode) !== el) return;
Array.prototype.slice.call(node.attributes).forEach(function (attr) {
if (attr.name.indexOf("data-wrn-prop-bind-") !== 0) return;
var binding;
try { binding = JSON.parse(attr.value); } catch (_) { return; }
if (!binding || binding.length !== 2) return;
var propName = binding[0];
var template = binding[1];
reactive(function () {
var exact = /^\{([^{}]+)\}$/.exec(template);
var value;
try {
value = exact
? evalExpr(exact[1].trim(), decodeLoopLocals(node))
: template.replace(/\{([^{}]+)\}/g, function (_, expression) {
var part = evalExpr(expression.trim(), decodeLoopLocals(node));
return part == null ? "" : String(part);
});
} catch (_) { return; }
var apply = function () {
if (node.__wrnexusScopeApi) node.__wrnexusScopeApi.set(propName, value);
};
if (node.__wrnexusScopeApi) apply();
else queueMicrotask(apply);
});
});
});
// Every hydrated scope exposes state writes. Prop-only components need the
// same API even when they have no behavior block.
var publicScopeApi = {
get: peekScope,
set: writeScope,
call: function (name) {
@@ -2545,11 +2644,14 @@ export const REACTIVE_RUNTIME = String.raw`
if (typeof fn !== "function") return undefined;
return fn.apply(null, Array.prototype.slice.call(arguments, 1));
},
};
el.__wrnexusScopeApi = publicScopeApi;
el.querySelectorAll("[data-wrn-select]").forEach(function (select) {
select.__wrnexusScopeApi = publicScopeApi;
});
};
el.__wrnexusScopeApi = publicScopeApi;
el.querySelectorAll("[data-wrn-select]").forEach(function (select) {
select.__wrnexusScopeApi = publicScopeApi;
});
if (behavior) {
installBehaviorFunctions(behavior.functions);
(behavior.effects || []).forEach(function (source) {
if (typeof source !== "string" || !source.trim()) return;
@@ -2762,6 +2864,7 @@ export const REACTIVE_RUNTIME = String.raw`
* cleared, so repositioning is idempotent -- re-running it on a panel that
* is already on screen changes nothing and cannot drift.
*/
/*__WRNEXUS_CONTROLLERS_PRIMARY_START__*/
var ANCHORED_SELECTOR = "[data-wrn-anchored]";
var ANCHOR_MARGIN = 8;
var anchoredScheduled = false;
@@ -3378,6 +3481,8 @@ export const REACTIVE_RUNTIME = String.raw`
document.addEventListener("pointercancel", endDrag);
}
/*__WRNEXUS_CONTROLLERS_PRIMARY_END__*/
function hydrateScopes(root) {
var host = root || document;
@@ -3403,8 +3508,12 @@ export const REACTIVE_RUNTIME = String.raw`
hydratePreferenceControllers(host);
hydrateSelectControllers(host);
hydratePinInputControllers(host);
/*__WRNEXUS_DEV_START__*/
warnMissingThemeTokens();
/*__WRNEXUS_DEV_END__*/
}
/*__WRNEXUS_CONTROLLERS_UI_START__*/
var navbarOutsideClickBound = false;
var preferenceOutsideClickBound = false;
@@ -3923,6 +4032,8 @@ export const REACTIVE_RUNTIME = String.raw`
}
}
/*__WRNEXUS_CONTROLLERS_UI_END__*/
function invokeComponentOutput(root, name, payload) {
if (!root || !name) return undefined;
var registry = root.__wrnexusOutputHandlers;
@@ -3953,6 +4064,15 @@ export const REACTIVE_RUNTIME = String.raw`
? Promise.all(values)
: values[values.length - 1];
}
/*__WRNEXUS_DEV_START__*/
warnOnce(
"WRN-DEV-OUTPUT-UNHANDLED",
"Output '" + name + "' has no parent binding. Registered bindings: " +
(registry ? Object.keys(registry).join(", ") || "none" : "none") + ".",
root,
{ output: name, registeredBindings: registry ? Object.keys(registry) : [] },
);
/*__WRNEXUS_DEV_END__*/
return dispatchComponentEvent(root, name, payload);
}
@@ -4021,6 +4141,7 @@ export const REACTIVE_RUNTIME = String.raw`
}
}
/*__WRNEXUS_CONTROLLERS_PIN_START__*/
function setupPinInputController(root) {
if (!root || root.__wrnexusPinInputController) return;
var cells = Array.prototype.slice.call(root.querySelectorAll("[data-pin-cell]"));
@@ -4196,6 +4317,8 @@ export const REACTIVE_RUNTIME = String.raw`
inputs.forEach(setupPinInputController);
}
/*__WRNEXUS_CONTROLLERS_PIN_END__*/
function parseScopeDecl(decl) {
var initial = {};
splitTopLevel(decl, ",").forEach(function (part) {
+159 -2
View File
@@ -1,10 +1,11 @@
import { test, expect, beforeEach } from "bun:test";
import { Window } from "happy-dom";
import { REACTIVE_RUNTIME } from "../src/reactive-runtime.ts";
import { getComponentControllerRuntime, getReactiveRuntime } from "../src/index.ts";
import { mountHtml } from "@wrnexus/test";
// Fresh DOM per test, with the runtime's globals bound.
function mount(html: string): Window {
function mount(html: string, runtime = REACTIVE_RUNTIME, controllers = ""): Window {
const win = new Window() as unknown as Window & Record<string, unknown>;
win.document.body.innerHTML = `<div id="app">${html}</div>`;
(globalThis as Record<string, unknown>).window = win;
@@ -24,7 +25,8 @@ function mount(html: string): Window {
(globalThis as Record<string, unknown>).CustomEvent = (
win as unknown as { CustomEvent: unknown }
).CustomEvent;
(0, eval)(REACTIVE_RUNTIME);
(0, eval)(runtime);
if (controllers) (0, eval)(controllers);
// Hydrate deterministically (auto-init waits on DOMContentLoaded, which the
// test window may not fire). setupScope is idempotent, so this is safe.
const w = win as unknown as { __wrnexusHydrateScopes?: (root: unknown) => void };
@@ -40,6 +42,31 @@ beforeEach(() => {
delete (globalThis as Record<string, unknown>).MutationObserver;
});
test("split runtime hydrates a controller only from the controller asset", () => {
const core = getReactiveRuntime(true);
const controllers = getComponentControllerRuntime(true);
expect(core).not.toContain("function setupRovingFocus");
expect(controllers).toContain("function setupRovingFocus");
const win = mount(
`<div data-wrn-roving="horizontal"><button data-wrn-roving-item>One</button><button data-wrn-roving-item>Two</button></div>`,
core,
controllers,
);
const buttons = win.document.querySelectorAll("button");
(buttons[0] as unknown as HTMLButtonElement).focus();
buttons[0]!.dispatchEvent(new win.KeyboardEvent("keydown", { key: "ArrowRight", bubbles: true }));
expect(win.document.activeElement).toBe(buttons[1]);
});
test("a page without controller markers does not request the controller asset", () => {
const win = mount(
`<main data-scope="count: 1"><span>{count}</span></main>`,
getReactiveRuntime(true),
);
expect(win.document.querySelector('script[src$="controllers.js"]')).toBeNull();
});
test("hydrates {expr} mustaches from data-scope", () => {
const win = mount(`<div data-scope="count: 0"><span>{count}, {count * 2}</span></div>`);
expect(win.document.querySelector("span")!.textContent).toBe("0, 0");
@@ -361,6 +388,20 @@ test("reactive attribute bindings update input and accessibility attributes", ()
expect(button.getAttribute("aria-label")).toBe("Hide password");
});
test("parent state updates a mounted child's reactive prop", async () => {
const win = mount(
`<div data-scope="n: 1">` +
`<button data-on-click="n++">increment</button>` +
`<div data-scope="value: 1" data-wrn-prop-bind-0='["value","{n}"]'>` +
`<span id="child-value" data-text="value">1</span>` +
`</div></div>`,
);
await Promise.resolve();
expect(win.document.querySelector("#child-value")?.textContent).toBe("1");
(win.document.querySelector("button") as unknown as HTMLElement).click();
expect(win.document.querySelector("#child-value")?.textContent).toBe("2");
});
test("independent signals in one scope update correctly (dependency tracking)", () => {
const win = mount(
`<div data-scope="a: 0, b: 100">
@@ -663,6 +704,122 @@ test("a camelCase output reaches a parent binding despite attribute lowercasing"
expect(win.document.querySelector("#out")?.textContent).toBe("yes");
});
test("development runtime warns once for an output with no parent binding", () => {
const win = new Window() as unknown as Window & Record<string, unknown>;
win.document.body.innerHTML = `<div data-scope="" data-wrn-events="complete"><button data-on-click="output.complete({})">go</button></div>`;
(globalThis as Record<string, unknown>).window = win;
(globalThis as Record<string, unknown>).document = win.document;
(globalThis as Record<string, unknown>).location = win.location;
(globalThis as Record<string, unknown>).NodeFilter = (
win as unknown as { NodeFilter: unknown }
).NodeFilter;
(globalThis as Record<string, unknown>).MutationObserver = (
win as unknown as { MutationObserver: unknown }
).MutationObserver;
(globalThis as Record<string, unknown>).CustomEvent = (
win as unknown as { CustomEvent: unknown }
).CustomEvent;
const warnings: unknown[][] = [];
const originalWarn = console.warn;
console.warn = (...args: unknown[]) => warnings.push(args);
try {
(0, eval)(getReactiveRuntime(true));
(
win as unknown as { __wrnexusHydrateScopes?: (root: unknown) => void }
).__wrnexusHydrateScopes?.(win.document);
(win.document.querySelector("button") as unknown as HTMLElement).click();
(win.document.querySelector("button") as unknown as HTMLElement).click();
} finally {
console.warn = originalWarn;
}
expect(warnings).toHaveLength(1);
expect(String(warnings[0]?.[0])).toContain("WRN-DEV-OUTPUT-UNHANDLED");
});
test("development runtime warns when a component binding names a missing function", () => {
const win = new Window() as unknown as Window & Record<string, unknown>;
win.document.body.innerHTML =
`<div data-scope="">` +
`<div data-scope="" data-wrn-hydration="Child:x">` +
`<div data-wrn-events="save" data-wrn-out-save="missingSave(payload)"></div>` +
`</div></div>`;
(globalThis as Record<string, unknown>).window = win;
(globalThis as Record<string, unknown>).document = win.document;
(globalThis as Record<string, unknown>).location = win.location;
(globalThis as Record<string, unknown>).NodeFilter = (
win as unknown as { NodeFilter: unknown }
).NodeFilter;
(globalThis as Record<string, unknown>).MutationObserver = (
win as unknown as { MutationObserver: unknown }
).MutationObserver;
(globalThis as Record<string, unknown>).CustomEvent = (
win as unknown as { CustomEvent: unknown }
).CustomEvent;
const warnings: unknown[][] = [];
const originalWarn = console.warn;
const originalError = console.error;
console.warn = (...args: unknown[]) => warnings.push(args);
console.error = () => {};
try {
(0, eval)(getReactiveRuntime(true));
(
win as unknown as { __wrnexusHydrateScopes?: (root: unknown) => void }
).__wrnexusHydrateScopes?.(win.document);
const target = win.document.querySelector("[data-wrn-events]") as unknown as {
__wrnexusOutputHandlers?: Record<string, Set<(payload: unknown) => unknown>>;
};
target.__wrnexusOutputHandlers?.save?.forEach((handler) => handler({ id: 1 }));
target.__wrnexusOutputHandlers?.save?.forEach((handler) => handler({ id: 2 }));
} finally {
console.warn = originalWarn;
console.error = originalError;
}
expect(warnings).toHaveLength(1);
expect(String(warnings[0]?.[0])).toContain("WRN-DEV-BINDING-MISSING");
expect(String(warnings[0]?.[0])).toContain("missingSave");
});
test("development runtime warns for referenced theme tokens absent from rendered CSS", () => {
const win = new Window() as unknown as Window & Record<string, unknown>;
win.document.head.innerHTML =
`<style>:root { --wire-present-test: red; } .probe { ` +
`color: var(--wire-present-test); background: var(--wire-missing-test); }</style>`;
win.document.body.innerHTML = `<div class="probe" data-scope=""></div>`;
(globalThis as Record<string, unknown>).window = win;
(globalThis as Record<string, unknown>).document = win.document;
(globalThis as Record<string, unknown>).location = win.location;
(globalThis as Record<string, unknown>).NodeFilter = (
win as unknown as { NodeFilter: unknown }
).NodeFilter;
(globalThis as Record<string, unknown>).MutationObserver = (
win as unknown as { MutationObserver: unknown }
).MutationObserver;
(globalThis as Record<string, unknown>).CustomEvent = (
win as unknown as { CustomEvent: unknown }
).CustomEvent;
const warnings: unknown[][] = [];
const originalWarn = console.warn;
console.warn = (...args: unknown[]) => warnings.push(args);
try {
(0, eval)(getReactiveRuntime(true));
(
win as unknown as { __wrnexusHydrateScopes?: (root: unknown) => void }
).__wrnexusHydrateScopes?.(win.document);
} finally {
console.warn = originalWarn;
}
expect(warnings).toHaveLength(1);
expect(String(warnings[0]?.[0])).toContain("WRN-DEV-THEME-TOKEN-MISSING");
expect(String(warnings[0]?.[0])).toContain("--wire-missing-test");
});
test("production runtime strips development diagnostics", () => {
const production = getReactiveRuntime();
expect(production).not.toContain("WRN-DEV-");
expect(production).not.toContain("warnOnce");
expect(REACTIVE_RUNTIME).toContain("__WRNEXUS_DEV_START__");
});
// --- browser globals + regex literals in client expressions ----------------
// Client functions and inline handlers are interpreted by the runtime's own
// eval-free expression engine (so a strict CSP needs no unsafe-eval). Anything
+2 -1
View File
@@ -31,7 +31,8 @@ export function sqliteSessionStore(path = "sessions.db"): SessionBackend {
if (!row) return undefined;
try {
return { data: JSON.parse(row.data) as Record<string, unknown>, expiresAt: row.expiresAt };
} catch {
} catch (error) {
console.warn(`[wrnexus:db] discarded corrupt session '${id}'`, error);
return undefined;
}
},
+17 -1
View File
@@ -12,12 +12,14 @@
import {
getActionRuntime,
getComponentControllerRuntime,
getReactiveRuntime,
getNavRuntime,
getRealtimeRuntime,
} from "@wrnexus/csr";
import {
renderStyles,
renderActiveThemeCss,
renderThemeCss,
renderThemeRuntime,
type ResolvedTheme,
@@ -93,7 +95,9 @@ export function createDevAssetServer(
if (pathname.startsWith("/__wrnexus/client/")) {
return serveWrnBrowserArtifact(pathname) ?? new Response("Not Found", { status: 404 });
}
if (pathname === "/__wrnexus/reactive.js") return jsResponse(getReactiveRuntime());
if (pathname === "/__wrnexus/reactive.js") return jsResponse(getReactiveRuntime(true));
if (pathname === "/__wrnexus/controllers.js")
return jsResponse(getComponentControllerRuntime(true));
if (pathname === "/__wrnexus/nav.js") return jsResponse(getNavRuntime());
if (pathname === "/__wrnexus/realtime.js") return jsResponse(getRealtimeRuntime());
if (pathname === "/__wrnexus/actions.js") return jsResponse(getActionRuntime());
@@ -119,6 +123,18 @@ export function createDevAssetServer(
? cssResponse(renderThemeCss(theme))
: new Response("Not Found", { status: 404 });
}
const activeThemeMatch = /^\/__wrnexus\/theme\/([^/]+)\/([^/]+)\.css$/.exec(pathname);
if (activeThemeMatch && theme) {
try {
const themeName = decodeURIComponent(activeThemeMatch[1]!);
const accentPart = decodeURIComponent(activeThemeMatch[2]!);
return cssResponse(
renderActiveThemeCss(theme, themeName, accentPart === "_" ? undefined : accentPart),
);
} catch {
return new Response("Not Found", { status: 404 });
}
}
if (pathname === "/__wrnexus/theme.js") {
return theme
? jsResponse(renderThemeRuntime(theme))
+15 -4
View File
@@ -6,7 +6,7 @@
* the running process while the HMR socket morphs fresh HTML into the browser.
*/
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { resolve, dirname, isAbsolute, join } from "node:path";
import type { Middleware, Mode, SecurityConfig, SeoConfig } from "@wrnexus/core";
import { buildRouter, type Router } from "@wrnexus/router";
@@ -272,7 +272,11 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
const pluginContributions = await pluginRunner.contributions();
const virtualModules = new Map<string, string>();
const virtualDir = join(appRoot, ".wrnexus", "virtual");
// A live dev server and a test process can share an app root. Keeping the
// generated artifacts in a process-specific directory prevents a test boot
// from deleting the running server's client modules.
const cacheDir = join(appRoot, `.wrnexus-${process.pid}`);
const virtualDir = join(cacheDir, "virtual");
mkdirSync(virtualDir, { recursive: true });
for (const [index, module] of pluginContributions.virtualModules.entries()) {
const output = join(virtualDir, `plugin-${index}.ts`);
@@ -305,7 +309,12 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
console.log(`[wrnexus:plugin] contributed routes: ${pluginContributions.routes.length}`);
const pluginToolbarPanels = await pluginRunner.devToolbarPanels();
const componentDirs = [uiComponentsDir(), ...pluginContributions.componentDirs];
const bundledUiDir = uiComponentsDir();
const componentDirs = [
bundledUiDir,
join(bundledUiDir, "..", "styles"),
...pluginContributions.componentDirs,
];
const hmr = opts.hmr ?? mode === "development";
const port = opts.port ?? 3000;
@@ -327,12 +336,13 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
resetDevCache({
rootDir: appRoot,
cacheDir,
enabled: process.env.WRNEXUS_PRESERVE_CACHE !== "1",
});
// Compile every `.wrn` into ONE cache dir at the project root, instead of a
// `.wrnexus/` next to each source file (and inside node_modules UI dirs).
setCompileCacheDir(join(appRoot, ".wrnexus"));
setCompileCacheDir(cacheDir);
const theme = resolveThemeConfig(opts.theme);
const uiStylesPath = uiCssPath();
@@ -739,6 +749,7 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
unsubscribeDevToolbar?.();
server.stop();
void pluginRunner.hook("shutdown");
rmSync(cacheDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
},
};
}
+2
View File
@@ -259,6 +259,7 @@ async function rewriteArtifactImportsAsync(
}
export async function loadModule(file: string): Promise<Record<string, unknown>> {
file = resolve(file);
let mod = moduleCache.get(file);
if (!mod) {
mod = (async () => {
@@ -631,6 +632,7 @@ export function serveWrnBrowserArtifact(pathname: string): Response | null {
/** Forget one module and force its next dynamic import to bypass Bun's import cache. */
export function invalidateModule(file: string): void {
file = resolve(file);
moduleCache.delete(file);
moduleVersions.set(file, (moduleVersions.get(file) ?? 0) + 1);
}
+34
View File
@@ -20,6 +20,7 @@ import {
} from "@wrnexus/router";
import {
getActionRuntime,
getComponentControllerRuntime,
getReactiveRuntime,
getNavRuntime,
getRealtimeRuntime,
@@ -90,12 +91,18 @@ export interface ProdOptions {
inlineStyles?: string;
/** `stylesPath` contains theme + UI + app CSS in cascade order. */
stylesIncludeFramework?: boolean;
/** `stylesPath` already contains the shared Wire UI stylesheet. */
stylesIncludeUi?: boolean;
/** Absolute path to the pre-built reactive runtime. */
reactivePath?: string;
/** Absolute path to the on-demand component controller runtime. */
controllersPath?: string;
/** Absolute directory containing bundled per-WRN browser modules. */
clientModulesDir?: string;
/** Absolute path to the pre-built theme stylesheet (`theme.css`). */
themePath?: string;
/** Pre-built active theme/accent stylesheets, loaded on demand. */
themeAssetsDir?: string;
/** Absolute path to the pre-built theme runtime (`theme.js`). */
themeJsPath?: string;
/** Resolved theme config: enables `<html data-theme>` + `theme.css` link. */
@@ -341,6 +348,10 @@ function createProdAssetServer(opts: ProdOptions): AssetServer {
}
return new Response(getReactiveRuntime(), { headers: JS_HEADERS });
}
if (pathname === "/__wrnexus/controllers.js") {
if (opts.controllersPath) return serveFile(opts.controllersPath, JS_HEADERS);
return new Response(getComponentControllerRuntime(), { headers: JS_HEADERS });
}
if (pathname === "/__wrnexus/nav.js")
return new Response(getNavRuntime(), { headers: JS_HEADERS });
if (pathname === "/__wrnexus/realtime.js")
@@ -359,6 +370,28 @@ function createProdAssetServer(opts: ProdOptions): AssetServer {
return new Response(opts.schemasJs ?? "window.__wireSchemas={};", { headers: JS_HEADERS });
}
if (pathname === "/__wrnexus/theme.css") return serveFile(opts.themePath, CSS_HEADERS);
const activeThemeMatch = /^\/__wrnexus\/theme\/([^/]+)\/([^/]+)\.css$/.exec(pathname);
if (activeThemeMatch && opts.theme && opts.themeAssetsDir) {
try {
const themeName = decodeURIComponent(activeThemeMatch[1]!);
const accentPart = decodeURIComponent(activeThemeMatch[2]!);
if (!opts.theme.names.includes(themeName))
return new Response("Not Found", { status: 404 });
if (accentPart !== "_" && !opts.theme.accentNames.some((name) => name === accentPart)) {
return new Response("Not Found", { status: 404 });
}
return serveFile(
join(
opts.themeAssetsDir,
encodeURIComponent(themeName),
`${accentPart === "_" ? "_" : encodeURIComponent(accentPart)}.css`,
),
CSS_HEADERS,
);
} catch {
return new Response("Not Found", { status: 404 });
}
}
if (pathname === "/__wrnexus/theme.js") return serveFile(opts.themeJsPath, JS_HEADERS);
if (pathname === "/__wrnexus/ui.css") return serveFile(opts.uiCssPath, CSS_HEADERS);
if (pathname === "/__wrnexus/framework.css")
@@ -470,6 +503,7 @@ export function createProductionHandlers(
i18n: opts.i18n,
inlineStyles: opts.inlineStyles,
stylesIncludeFramework: opts.stylesIncludeFramework,
stylesIncludeUi: opts.stylesIncludeUi,
assetVersion: opts.assetVersion,
clientRuntimes: opts.clientRuntimes,
head: opts.head,
+23 -76
View File
@@ -66,12 +66,12 @@ import type { StoreDefinition } from "@wrnexus/store";
import type { ClientRuntimeDefinition } from "@wrnexus/plugin";
import { CacheCoordinator } from "@wrnexus/cache";
import { generateServiceWorker } from "@wrnexus/pwa";
import { runtimeScriptsForMarkup } from "./plugin-assets.ts";
import { collectScripts, usesMobileRuntime } from "./script-selection.ts";
export { collectScripts, usesMobileRuntime } from "./script-selection.ts";
import {
ACCENT_COOKIE,
THEME_COOKIE,
THEME_CSS_HREF,
THEME_JS_HREF,
activeThemeCssHref,
resolveAccentName,
resolveThemeName,
type MobileConfig,
@@ -81,7 +81,6 @@ import {
type TenancyConfig,
} from "@wrnexus/styles";
import {
I18N_JS_HREF,
renderI18nData,
makeT,
resolveLang,
@@ -161,6 +160,7 @@ export interface RuntimeDeps {
hasFrameworkStyles?: boolean;
/** App stylesheet already contains theme + UI CSS and is the only CSS request needed. */
stylesIncludeFramework?: boolean;
stylesIncludeUi?: boolean;
/** Resolved theme config: enables `/__wrnexus/theme.css` + `<html data-theme>`. */
theme?: ResolvedTheme;
/** Resolved i18n bundle: enables `ctx.t`, `<html lang>`, and `{t:key}` markers. */
@@ -941,15 +941,12 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
// Order: theme tokens, then Wire UI, then the app stylesheet — so the app's
// own CSS (loaded last) can override both the tokens and the UI classes.
const headParts: string[] = [];
if (deps.hasFrameworkStyles && !deps.stylesIncludeFramework) {
if (!deps.theme && deps.hasFrameworkStyles && !deps.stylesIncludeFramework) {
headParts.push(
`<link rel="stylesheet" href="${versionAssetUrl("/__wrnexus/framework.css", deps.assetVersion)}" />`,
);
} else if (deps.theme && !deps.stylesIncludeFramework) {
const themeHref = versionAssetUrl(THEME_CSS_HREF, deps.assetVersion);
headParts.push(`<link rel="stylesheet" href="${themeHref}" />`);
}
if (deps.hasUi && !deps.hasFrameworkStyles && !deps.stylesIncludeFramework) {
if (deps.hasUi && !deps.stylesIncludeUi && !deps.stylesIncludeFramework) {
headParts.push(
`<link rel="stylesheet" href="${versionAssetUrl("/__wrnexus/ui.css", deps.assetVersion)}" />`,
);
@@ -1815,7 +1812,14 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
if (isMobileRequest) body = revealMobileOnlyHtml(body);
// i18n: resolve `{t:key}` / `t:attr` markers against the request language.
if (deps.i18n) body = translateHtml(body, ctx.t);
if (deps.i18n) {
body = translateHtml(body, ctx.t);
// renderDocument prefers the complete document template when one was
// rendered. Keep it in sync with the translated body; otherwise markers
// owned by document/page layouts are replaced by the stale pre-translation
// template even though page-only responses translate correctly.
if (documentTemplate) documentTemplate = body;
}
// Point 3: only ship the JS this page actually uses.
const scripts = collectScripts(body, deps.clientRuntimes, deps.navigation).map((script) =>
@@ -1832,6 +1836,7 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
// <html> attributes: no-flash theme (from cookie, validated) + active lang.
const attrs: string[] = [];
let activeThemeHead = "";
if (deps.theme) {
const themeName = resolveThemeName(ctx.cookies.get(THEME_COOKIE), deps.theme);
@@ -1843,6 +1848,13 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
if (accentName) {
attrs.push(`data-accent="${accentName}"`);
}
if (!deps.stylesIncludeFramework) {
const themeHref = versionAssetUrl(
activeThemeCssHref(themeName, accentName),
deps.assetVersion,
);
activeThemeHead = `<link rel="stylesheet" data-wrnexus-theme href="${themeHref}" />`;
}
}
attrs.push(`lang="${safeLanguageTag(language)}"`);
if (deps.i18n) attrs.push(`dir="${deps.i18n.direction[language] ?? "ltr"}"`);
@@ -1866,6 +1878,7 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
pwaEnabled ? `<link rel="manifest" href="/site.webmanifest" />` : "",
pwaEnabled ? `<meta name="mobile-web-app-capable" content="yes" />` : "",
pwaEnabled ? `<meta name="apple-mobile-web-app-capable" content="yes" />` : "",
activeThemeHead,
extraHead,
]
.filter(Boolean)
@@ -2436,72 +2449,6 @@ export function normalizeComponentName(name: string): string {
return name.toLowerCase().replace(/[-_]/g, "");
}
/**
* Decide which framework scripts a rendered page needs. Components are already
* server-rendered into the HTML; the only script is the reactive runtime, and
* only when the page actually contains a scope or a browser-side API fetch.
*/
export function collectScripts(
body: string,
clientRuntimes: readonly ClientRuntimeDefinition[] = [],
navigation: { mode?: "auto" | "client" | "document" } = {},
): RenderScript[] {
const scripts: RenderScript[] = [];
if (
/\bdata-scope=/.test(body) ||
/\bdata-wrnexus-csr=/.test(body) ||
/\bdata-wrn-client-template=/.test(body) ||
/\bdata-wrn-async=/.test(body)
) {
scripts.push("/__wrnexus/reactive.js");
}
if (/\bdata-wrn-action=/.test(body)) scripts.push("/__wrnexus/actions.js");
// The theme runtime is only needed when the page can switch themes.
if (
/\bdata-wire-theme-(toggle|set)\b/.test(body) ||
/\bdata-wire-accent-(set|clear)\b/.test(body)
) {
scripts.push(THEME_JS_HREF);
}
// Validation: schema descriptors + the generic validator, only for pages with a form.
if (/\bdata-schema="[A-Za-z0-9_-]+"/.test(body)) {
scripts.push("/__wrnexus/schemas.js", "/__wrnexus/validate.js");
}
// Language switcher runtime, only when the page has one.
if (/\bdata-wire-lang-set\b/.test(body) || /\bselect[^>]*\bdata-wire-lang\b/.test(body)) {
scripts.push(I18N_JS_HREF);
}
// Realtime client, only when the page declares a room.
if (/\bdata-room=/.test(body)) {
scripts.push("/__wrnexus/realtime.js");
}
// File-upload runtime (drag-drop + progress), only when a page has an uploader.
if (/\bdata-uploader\b/.test(body)) {
scripts.push("/__wrnexus/uploader.js");
}
// Package runtimes are declarative. Components mark the rendered HTML with
// `data-wrnexus-runtime="id"`; the corresponding package chunk is loaded
// once, without requiring application-authored script tags or public copies.
scripts.push(...runtimeScriptsForMarkup(body, clientRuntimes));
// `auto` is the performance-first default: a fully static page ships no
// framework JavaScript and its links use native document navigation. Routes
// that already need browser behavior also receive progressive navigation.
// `client` preserves the explicit always-on behavior; `document` disables it.
const mode = navigation.mode ?? "auto";
if (mode === "client" || (mode === "auto" && scripts.length > 0)) {
scripts.unshift("/__wrnexus/nav.js");
}
return scripts;
}
/** Whether rendered markup needs the Capacitor/native browser bridge. */
export function usesMobileRuntime(body: string): boolean {
return /\b(?:data-mobile-[\w-]+|data-native-(?:browser|mobile|only|requires|unsupported|options)|data-on-wrnexus-(?:browser|mobile)-[\w-]+)\b/.test(
body,
);
}
function safeLanguageTag(value: string): string {
return /^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/.test(value) ? value : "en";
}
@@ -0,0 +1,51 @@
import { I18N_JS_HREF } from "@wrnexus/i18n";
import type { ClientRuntimeDefinition } from "@wrnexus/plugin";
import { THEME_JS_HREF } from "@wrnexus/styles";
import type { RenderScript } from "@wrnexus/ssr";
import { runtimeScriptsForMarkup } from "./plugin-assets.ts";
/** Select browser runtimes from capabilities present in rendered markup. */
export function collectScripts(
body: string,
clientRuntimes: readonly ClientRuntimeDefinition[] = [],
navigation: { mode?: "auto" | "client" | "document" } = {},
): RenderScript[] {
const scripts: RenderScript[] = [];
if (
/\bdata-scope=/.test(body) ||
/\bdata-wrnexus-csr=/.test(body) ||
/\bdata-wrn-client-template=/.test(body) ||
/\bdata-wrn-async=/.test(body)
) {
scripts.push("/__wrnexus/reactive.js");
}
if (/\bdata-wrn-action=/.test(body)) scripts.push("/__wrnexus/actions.js");
if (
/\bdata-wire-theme-(toggle|set)\b/.test(body) ||
/\bdata-wire-accent-(set|clear)\b/.test(body)
) {
scripts.push(THEME_JS_HREF);
}
if (/\bdata-schema="[A-Za-z0-9_-]+"/.test(body)) {
scripts.push("/__wrnexus/schemas.js", "/__wrnexus/validate.js");
}
if (/\bdata-wire-lang-set\b/.test(body) || /\bselect[^>]*\bdata-wire-lang\b/.test(body)) {
scripts.push(I18N_JS_HREF);
}
if (/\bdata-room=/.test(body)) scripts.push("/__wrnexus/realtime.js");
if (/\bdata-uploader\b/.test(body)) scripts.push("/__wrnexus/uploader.js");
scripts.push(...runtimeScriptsForMarkup(body, clientRuntimes));
const mode = navigation.mode ?? "auto";
if (mode === "client" || (mode === "auto" && scripts.length > 0)) {
scripts.unshift("/__wrnexus/nav.js");
}
return scripts;
}
/** Whether rendered markup needs the Capacitor/native browser bridge. */
export function usesMobileRuntime(body: string): boolean {
return /\b(?:data-mobile-[\w-]+|data-native-(?:browser|mobile|only|requires|unsupported|options)|data-on-wrnexus-(?:browser|mobile)-[\w-]+)\b/.test(
body,
);
}
@@ -1,5 +1,5 @@
import { afterAll, describe, expect, test } from "bun:test";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { getAuthzCatalog, hasAuthzCatalog } from "@wrnexus/authz";
import { startServer } from "../src/index.ts";
@@ -63,9 +63,14 @@ export default defineAuthz({ permissions: { "post:read": { title: "View posts" }
try {
expect(hasAuthzCatalog()).toBe(true);
expect(getAuthzCatalog().permissions.has("post:read")).toBe(true);
const cacheDir = join(appDir, "..", `.wrnexus-${process.pid}`);
mkdirSync(cacheDir, { recursive: true });
writeFileSync(join(cacheDir, "generated.js"), "generated", "utf8");
expect(existsSync(cacheDir)).toBe(true);
} finally {
server.stop();
}
expect(existsSync(join(appDir, "..", `.wrnexus-${process.pid}`))).toBe(false);
});
test("an app with no app/authz declarations boots without throwing", async () => {
@@ -0,0 +1,60 @@
import { expect, test } from "bun:test";
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { join, resolve } from "node:path";
const root = resolve(import.meta.dir, "../../..");
test("per-process dev caches are excluded from root and example project tools", () => {
const eslint = readFileSync(join(root, "eslint.config.js"), "utf8");
const tsconfig = JSON.parse(readFileSync(join(root, "tsconfig.json"), "utf8")) as {
exclude?: string[];
};
const prettier = readFileSync(join(root, ".prettierignore"), "utf8");
expect(eslint).toContain('"**/.wrnexus-*/**"');
expect(tsconfig.exclude).toContain("**/.wrnexus-*");
expect(prettier).toContain("**/.wrnexus-*/**");
const projects = ["basic-app", "inter-app-api-showcase"].map((name) =>
join(root, "examples", name),
);
const caches = projects.map((project) => join(project, ".wrnexus-123"));
for (const project of projects) {
const projectEslint = readFileSync(join(project, "eslint.config.js"), "utf8");
const projectTsconfig = JSON.parse(readFileSync(join(project, "tsconfig.json"), "utf8")) as {
exclude?: string[];
};
const projectPrettier = readFileSync(join(project, ".prettierignore"), "utf8");
expect(projectEslint).toContain('".wrnexus-*/**"');
expect(projectEslint).toContain('"**/.wrnexus-*/**"');
expect(projectTsconfig.exclude).toContain(".wrnexus-*");
expect(projectTsconfig.exclude).toContain("**/.wrnexus-*");
expect(projectPrettier).toContain(".wrnexus-*/");
expect(projectPrettier).toContain("**/.wrnexus-*/");
}
for (const cache of caches) {
mkdirSync(cache, { recursive: true });
writeFileSync(join(cache, "broken.client.mjs"), "const = ;\n", "utf8");
writeFileSync(join(cache, "broken.d.ts"), "type = ;\n", "utf8");
}
try {
const lint = Bun.spawnSync(
[
"bunx",
"eslint",
"--no-warn-ignored",
...caches.flatMap((cache) => [
join(cache, "broken.client.mjs"),
join(cache, "broken.d.ts"),
]),
],
{ cwd: root },
);
expect(lint.exitCode).toBe(0);
} finally {
for (const cache of caches) rmSync(cache, { recursive: true, force: true });
}
}, 15_000);
@@ -0,0 +1,114 @@
import { afterEach, expect, test } from "bun:test";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { basename, join } from "node:path";
import { tmpdir } from "node:os";
import { buildRouter } from "@wrnexus/router";
import { resolveI18n } from "@wrnexus/i18n";
import { createHandlers, type RuntimeDeps } from "../src/runtime.ts";
const roots: string[] = [];
afterEach(() => roots.splice(0).forEach((root) => rmSync(root, { recursive: true, force: true })));
test("a component nested in another component's slot keeps its boundary", async () => {
const root = mkdtempSync(join(tmpdir(), "wrnexus-component-slot-"));
roots.push(root);
const app = join(root, "app");
mkdirSync(join(app, "pages"), { recursive: true });
mkdirSync(join(app, "components"), { recursive: true });
writeFileSync(join(app, "pages/index.ts"), "export default () => '';\n");
writeFileSync(join(app, "components/Card.wrn"), "component Card { view { <slot /> } }\n");
writeFileSync(join(app, "components/Badge.wrn"), "component Badge { view { <b></b> } }\n");
const handlers = createHandlers({
mode: "development",
hmr: false,
router: buildRouter(app),
loadModule: async (file) => {
if (basename(file) === "Card.wrn") {
return { render: () => '<article class="card"><slot /></article>' };
}
if (basename(file) === "Badge.wrn") {
return { render: (props: Record<string, string>) => `<b class="badge">${props.label}</b>` };
}
return {
default: () =>
'<div data-component="Card"><div data-component="Badge" label="x"></div></div>',
};
},
getMiddleware: async () => [],
assets: { serve: async () => null },
} satisfies RuntimeDeps);
const response = await handlers.fetch(new Request("https://example.test/"), {
upgrade: () => false,
});
const html = await response!.text();
expect(html).toContain('<article class="card">');
expect(html).toContain('<b class="badge">x</b>');
expect(html).not.toContain('data-component="Badge"');
});
test("translated text is present in the raw server response", async () => {
const root = mkdtempSync(join(tmpdir(), "wrnexus-ssr-i18n-"));
roots.push(root);
const app = join(root, "app");
mkdirSync(join(app, "pages"), { recursive: true });
writeFileSync(join(app, "pages/index.ts"), "export default () => '';\n");
const handlers = createHandlers({
mode: "development",
hmr: false,
router: buildRouter(app),
i18n: resolveI18n({ en: { nav: { title: "Navigation" } } }, { default: "en" }),
loadModule: async () => ({
default: () => '<nav><span data-t="nav.title"></span></nav>',
}),
getMiddleware: async () => [],
assets: { serve: async () => null },
} satisfies RuntimeDeps);
const response = await handlers.fetch(new Request("https://example.test/"), {
upgrade: () => false,
});
const html = await response!.text();
expect(html).toContain('<span data-t="nav.title">Navigation</span>');
});
test("translated layout text survives document-template rendering", async () => {
const root = mkdtempSync(join(tmpdir(), "wrnexus-layout-ssr-i18n-"));
roots.push(root);
const app = join(root, "app");
mkdirSync(join(app, "pages"), { recursive: true });
mkdirSync(join(app, "layouts"), { recursive: true });
writeFileSync(join(app, "pages/index.ts"), "export default () => '';\n");
writeFileSync(join(app, "layouts/public.wrn"), "layout Public { view { <slot /> } }\n");
writeFileSync(
join(app, "layouts/document.wrn"),
"layout Document { view { <html><head></head><body><slot /></body></html> } }\n",
);
const handlers = createHandlers({
mode: "development",
hmr: false,
router: buildRouter(app),
i18n: resolveI18n({ en: { nav: { home: "Home" } } }, { default: "en" }),
loadModule: async (file) => {
if (basename(file) === "public.wrn") {
return { render: () => '<nav><span data-t="nav.home"></span></nav>' };
}
if (basename(file) === "document.wrn") {
return { render: () => "<html><head></head><body><slot /></body></html>" };
}
return { default: () => "<main>Page</main>", layout: "public" };
},
getMiddleware: async () => [],
assets: { serve: async () => null },
} satisfies RuntimeDeps);
const response = await handlers.fetch(new Request("https://example.test/"), {
upgrade: () => false,
});
const html = await response!.text();
expect(html).toContain('<span data-t="nav.home">Home</span>');
expect(html).not.toContain('<span data-t="nav.home"></span>');
});
+2 -2
View File
@@ -83,7 +83,7 @@ test("invalidateModule loads changed server modules without restarting the proce
}
});
test("invalidateModule recompiles changed WRN files in-process", async () => {
test("invalidateModule recompiles changed WRN files with watcher-normalized separators", async () => {
const root = mkdtempSync(join(tmpdir(), "wrnexus-hmr-"));
const file = join(root, "page.wrn");
setCompileCacheDir(join(root, ".wrnexus"));
@@ -94,7 +94,7 @@ test("invalidateModule recompiles changed WRN files in-process", async () => {
expect(String(first({}))).toContain("First");
writeFileSync(file, "page Home { view { <h1>Second</h1> } }\n");
invalidateModule(file);
invalidateModule(file.replace(/\\/g, "/"));
const second = (await loadModule(file)).default as (ctx: unknown) => unknown;
expect(String(second({}))).toContain("Second");
} finally {
+8 -4
View File
@@ -22,7 +22,7 @@ export function webVitalsClient(options: WebVitalsClientOptions = {}): string {
function send(name, value) {
var body = JSON.stringify({ name: name, value: value, rating: rating(name, value), route: location.pathname, navigationType: performance.getEntriesByType("navigation")[0]?.type });
if (navigator.sendBeacon) navigator.sendBeacon(endpoint, new Blob([body], { type: "application/json" }));
else fetch(endpoint, { method: "POST", headers: { "content-type": "application/json" }, body: body, keepalive: true }).catch(function(){});
else fetch(endpoint, { method: "POST", headers: { "content-type": "application/json" }, body: body, keepalive: true }).catch(function(error){ console.warn("[wrnexus:observability] web-vitals delivery failed", error); });
}
function flush() {
if (flushed) return;
@@ -31,9 +31,13 @@ export function webVitalsClient(options: WebVitalsClientOptions = {}): string {
if (inp) send("INP", inp);
if (cls) send("CLS", cls);
}
try { new PerformanceObserver(function(list){ var entries=list.getEntries(); var last=entries[entries.length-1]; if(last) lcp = Math.max(lcp, last.startTime); }).observe({type:"largest-contentful-paint",buffered:true}); } catch(_) {}
try { new PerformanceObserver(function(list){ list.getEntries().forEach(function(e){ if(!e.hadRecentInput) cls += e.value; }); }).observe({type:"layout-shift",buffered:true}); } catch(_) {}
try { new PerformanceObserver(function(list){ list.getEntries().forEach(function(e){ inp=Math.max(inp,e.duration||0); }); }).observe({type:"event",durationThreshold:40,buffered:true}); } catch(_) {}
function observe(type, callback, options) {
try { new PerformanceObserver(callback).observe(options); }
catch(error) { console.warn("[wrnexus:observability] PerformanceObserver unavailable for " + type, error); }
}
observe("largest-contentful-paint", function(list){ var entries=list.getEntries(); var last=entries[entries.length-1]; if(last) lcp = Math.max(lcp, last.startTime); }, {type:"largest-contentful-paint",buffered:true});
observe("layout-shift", function(list){ list.getEntries().forEach(function(e){ if(!e.hadRecentInput) cls += e.value; }); }, {type:"layout-shift",buffered:true});
observe("event", function(list){ list.getEntries().forEach(function(e){ inp=Math.max(inp,e.duration||0); }); }, {type:"event",durationThreshold:40,buffered:true});
addEventListener("visibilitychange", function(){ if(document.visibilityState === "hidden") flush(); });
addEventListener("pagehide", flush, { once: true });
})();`;
+14
View File
@@ -0,0 +1,14 @@
export type ObservabilityDiagnostic = (message: string, error: unknown) => void;
/** Report exporter failures without feeding them back through the exporter. */
export function reportObservabilityFailure(
message: string,
error: unknown,
diagnostic?: ObservabilityDiagnostic,
): void {
if (diagnostic) {
diagnostic(message, error);
return;
}
console.warn(`[wrnexus:observability] ${message}`, error);
}
+5 -1
View File
@@ -2,6 +2,7 @@ import type { MetricPoint } from "./metrics.ts";
import type { LogRecord } from "./logging.ts";
import type { MetricExporter } from "./server.ts";
import type { SpanExporter, SpanRecord, TraceContext } from "./trace.ts";
import { reportObservabilityFailure, type ObservabilityDiagnostic } from "./diagnostics.ts";
export type FrameworkSpanKind =
"database" | "cache" | "queue" | "realtime" | "server-action" | "application";
@@ -26,6 +27,7 @@ export function createOperationTracer(
context?: () => Partial<TraceContext>;
now?: () => number;
onExportError?: (error: unknown) => void;
diagnostic?: ObservabilityDiagnostic;
} = {},
): OperationTracer {
const now = options.now ?? Date.now;
@@ -66,7 +68,9 @@ export function createOperationTracer(
try {
await options.exporter?.export([record]);
} catch (error) {
options.onExportError?.(error);
if (options.onExportError) options.onExportError(error);
else
reportObservabilityFailure("operation span export failed", error, options.diagnostic);
}
}
},
+4 -1
View File
@@ -1,4 +1,5 @@
import { createTracer, type Context, type Middleware } from "@wrnexus/core";
import { reportObservabilityFailure, type ObservabilityDiagnostic } from "./diagnostics.ts";
export interface TraceContext {
version: "00";
@@ -63,6 +64,7 @@ export interface TraceMiddlewareOptions {
routeName?: (ctx: Context) => string;
serverTiming?: boolean;
onExportError?: (error: unknown, span: SpanRecord) => void | Promise<void>;
diagnostic?: ObservabilityDiagnostic;
}
export function traceMiddleware(options: TraceMiddlewareOptions = {}): Middleware {
@@ -131,7 +133,8 @@ export function traceMiddleware(options: TraceMiddlewareOptions = {}): Middlewar
await options.onSpan?.(span);
await options.exporter?.export([span]);
} catch (error) {
await options.onExportError?.(error, span);
if (options.onExportError) await options.onExportError(error, span);
else reportObservabilityFailure("trace export failed", error, options.diagnostic);
}
}
}
@@ -93,4 +93,14 @@ describe("observability integrations", () => {
expect(await profile("render", () => "ok")).toBe("ok");
expect(profiles[0]).toMatchObject({ name: "render", durationMs: 4 });
});
test("operation exporter failures reach diagnostics without recursive telemetry", async () => {
const diagnostics: string[] = [];
const tracer = createOperationTracer({
exporter: { export: () => Promise.reject(new Error("offline")) },
diagnostic: (message) => diagnostics.push(message),
});
await expect(tracer.span("database", "query", async () => 1)).resolves.toBe(1);
expect(diagnostics).toEqual(["operation span export failed"]);
});
});
@@ -8,6 +8,8 @@ describe("@wrnexus/observability", () => {
expect(client).toContain('addEventListener("pagehide", flush');
expect(client).not.toContain('send("INP",max)');
expect(client).not.toContain('send("LCP", last.startTime)');
expect(client).toContain("web-vitals delivery failed");
expect(client).toContain("PerformanceObserver unavailable for");
});
test("records deterministic counters and histograms", () => {
@@ -81,6 +81,22 @@ describe("production observability operations", () => {
expect(failures).toHaveLength(1);
});
test("reports exporter failures through the non-recursive diagnostic hook", async () => {
const diagnostics: Array<{ message: string; error: unknown }> = [];
const ctx = createContext(
new Request("https://example.test/"),
new URL("https://example.test/"),
);
const middleware = traceMiddleware({
random: (target) => target.fill(8),
exporter: { export: () => Promise.reject(new Error("offline")) },
diagnostic: (message, error) => diagnostics.push({ message, error }),
});
expect((await middleware(ctx, () => new Response("ok"))).status).toBe(200);
expect(diagnostics).toHaveLength(1);
expect(diagnostics[0]?.message).toBe("trace export failed");
});
test("exports OTLP JSON traces and metrics", async () => {
const requests: unknown[] = [];
const send = (async (_url: URL | RequestInfo, init?: RequestInit) => {
+5 -2
View File
@@ -1,6 +1,9 @@
import { createStoreContainer } from "./index.ts";
let globalContainer: ReturnType<typeof createStoreContainer> | undefined;
const storeGlobal = globalThis as typeof globalThis & {
__wrnexusStoreContainer?: ReturnType<typeof createStoreContainer>;
};
export function browserStoreContainer(hydration: Record<string, unknown> = {}) {
if (!globalContainer) {
@@ -17,7 +20,7 @@ export function browserStoreContainer(hydration: Record<string, unknown> = {}) {
}
},
});
(globalThis as any).__wrnexusStoreContainer = globalContainer;
storeGlobal.__wrnexusStoreContainer = globalContainer;
}
return globalContainer;
}
@@ -25,5 +28,5 @@ export function browserStoreContainer(hydration: Record<string, unknown> = {}) {
export async function resetBrowserStores() {
await globalContainer?.dispose();
globalContainer = undefined;
delete (globalThis as any).__wrnexusStoreContainer;
delete storeGlobal.__wrnexusStoreContainer;
}
+20 -13
View File
@@ -27,7 +27,7 @@ function readonlySnapshot<S extends object>(state: S): Readonly<S> {
return Object.freeze(clone(state));
}
function storageFor(kind: StorePersistenceConfig<any>["storage"]): Storage | null {
function storageFor(kind: StorePersistenceConfig<object>["storage"]): Storage | null {
if (typeof window === "undefined") return null;
if (kind === "local") return window.localStorage;
if (kind === "session") return window.sessionStorage;
@@ -91,7 +91,10 @@ export class StoreContainer {
readonly runtime: "server" | "client";
readonly request?: unknown;
readonly routeId?: string;
private readonly instances = new Map<string, StoreInstance<any, any, any>>();
private readonly instances = new Map<
string,
StoreInstance<Record<string, unknown>, Record<string, unknown>, Record<string, StoreFunction>>
>();
private readonly hydration: Record<string, unknown>;
private readonly onMutation?: (mutation: StoreMutation) => void;
private readonly lastMutations = new Map<string, StoreMutation>();
@@ -159,7 +162,7 @@ export class StoreContainer {
Object.fromEntries(
Reflect.ownKeys(instance.computed).map((key) => [
String(key),
(instance.computed as any)[key],
Reflect.get(instance.computed, key),
]),
),
),
@@ -314,7 +317,7 @@ export function createStoreInstance<
const publicState = new Proxy({} as State, {
get(_target, property) {
return (mutableState as any)[property];
return Reflect.get(mutableState, property);
},
set(_target, property) {
throw new TypeError(
@@ -334,7 +337,7 @@ export function createStoreInstance<
return {
enumerable: true,
configurable: true,
value: (mutableState as any)[property],
value: Reflect.get(mutableState, property),
writable: false,
};
},
@@ -360,7 +363,7 @@ export function createStoreInstance<
mutate("$reset", () => {
const next = createInitialState();
for (const key of Object.keys(mutableState)) {
if (!(key in next)) delete (mutableState as any)[key];
if (!(key in next)) Reflect.deleteProperty(mutableState, key);
}
Object.assign(mutableState, next);
});
@@ -382,16 +385,20 @@ export function createStoreInstance<
definitions.find((entry) => entry.runtime === "shared") ??
definitions.find((entry) => entry.runtime === "legacy");
if (!selected) continue;
(actions as any)[name] = async (...args: unknown[]) => {
Reflect.set(actions, name, async (...args: unknown[]) => {
currentAction = name;
internalMutation = true;
try {
return await (selected.handler as any)(context, ...args);
const handler = selected.handler as unknown as (
context: StoreActionContext<State>,
...args: unknown[]
) => unknown;
return await handler(context, ...args);
} finally {
currentAction = "direct";
internalMutation = false;
}
};
});
}
const computed = new Proxy({} as C, {
@@ -461,7 +468,7 @@ export function createStoreInstance<
const result: Partial<State> = {};
const serverKeys = new Set(Object.keys(definition.createServerState?.() ?? {}));
for (const [key, value] of Object.entries(mutableState)) {
if (!serverKeys.has(key)) (result as any)[key] = clone(value);
if (!serverKeys.has(key)) Reflect.set(result, key, clone(value));
}
return result;
},
@@ -479,9 +486,9 @@ export function createStoreInstance<
return new Proxy(core as StoreInstance<State, C, A>, {
get(target, property, receiver) {
if (Reflect.has(target, property)) return Reflect.get(target, property, receiver);
if (property in actions) return (actions as any)[property];
if (property in (definition.computed ?? {})) return (computed as any)[property];
if (property in mutableState) return (publicState as any)[property];
if (property in actions) return Reflect.get(actions, property);
if (property in (definition.computed ?? {})) return Reflect.get(computed, property);
if (property in mutableState) return Reflect.get(publicState, property);
return undefined;
},
set(_target, property) {
+2 -1
View File
@@ -1,7 +1,8 @@
export type StoreKind = "global" | "page";
export type StoreRuntime = "shared" | "client" | "server" | "legacy";
export type PersistenceStorage = "memory" | "session" | "local";
export type StoreFunction = (...args: any[]) => any;
/** Broad callable constraint that preserves each action's concrete parameters and return type. */
export type StoreFunction = (...args: any[]) => unknown;
export type StoreCombinedState<
S extends object,
+3
View File
@@ -61,10 +61,13 @@ export {
THEME_PALETTE_NAMES,
THEME_COOKIE,
THEME_CSS_HREF,
THEME_CSS_PREFIX,
THEME_JS_HREF,
activeThemeCssHref,
resolveThemeConfig,
resolveThemeName,
renderThemeCss,
renderActiveThemeCss,
renderThemeRuntime,
defineThemeTokens,
themeVar,
+52 -30
View File
@@ -121,6 +121,7 @@ export interface ResolvedTheme {
export const THEME_COOKIE = "wire-theme";
export const ACCENT_COOKIE = "wire-accent";
export const THEME_CSS_HREF = "/__wrnexus/theme.css";
export const THEME_CSS_PREFIX = "/__wrnexus/theme/";
export const THEME_JS_HREF = "/__wrnexus/theme.js";
/**
@@ -519,6 +520,44 @@ function selectorValue(value: string): string {
return JSON.stringify(value);
}
function globalThemeCss(): string {
return (
'\nhtml,body{font-family:var(--wire-font-sans,"Plus Jakarta Sans",ui-sans-serif,system-ui,sans-serif);}\n' +
":root{--wire-radius-sm:0.55rem;--wire-radius-md:0.9rem;--wire-radius-lg:1.35rem;" +
"--wire-shadow-1:0 1px 2px color-mix(in srgb, black 22%, transparent)," +
"0 10px 30px color-mix(in srgb, black 14%, transparent);}\n" +
"wrn-slot{display:contents;}\n" +
"[data-for]{display:none !important;}\n"
);
}
/** URL for the small stylesheet containing only one active theme/accent pair. */
export function activeThemeCssHref(themeName: string, accentName?: string): string {
return `${THEME_CSS_PREFIX}${encodeURIComponent(themeName)}/${accentName ? encodeURIComponent(accentName) : "_"}.css`;
}
/** Render only the tokens needed for the current SSR-selected theme and accent. */
export function renderActiveThemeCss(
theme: ResolvedTheme,
themeName: string,
accentName?: string,
): string {
const selectedTheme = theme.themes[themeName];
if (!selectedTheme) throw new Error(`Unknown theme '${themeName}'.`);
const blocks = [`:root{${tokensToDeclarations(selectedTheme)}}`];
if (accentName) {
if (!theme.accentNames.includes(accentName as ThemePaletteName)) {
throw new Error(`Unknown theme accent '${accentName}'.`);
}
blocks.push(
`:root{${tokensToDeclarations(
paletteTokens(THEME_PALETTES[accentName as ThemePaletteName], themeScheme(selectedTheme)),
)}}`,
);
}
return blocks.join("\n") + globalThemeCss();
}
/**
* Generate the theme stylesheet.
*
@@ -559,36 +598,7 @@ export function renderThemeCss(theme: ResolvedTheme): string {
}
}
return (
blocks.join("\n") +
'\nhtml,body{font-family:var(--wire-font-sans,"Plus Jakarta Sans",ui-sans-serif,system-ui,sans-serif);}\n' +
// <wrn-slot> wraps content spliced into a component's <slot> so the client
// runtime can tell which scope authored it. It is a pure ownership marker
// and must never introduce a box: an unknown element would otherwise
// default to display:inline and break the component's own flex/grid layout.
/*
* Shape and elevation tokens the Wire UI stylesheet depends on.
*
* ui.css references --wire-radius-sm (51 times) and --wire-radius-md (21)
* but nothing ever defined them, so every component using them fell back
* to square corners in any app that did not declare them itself.
* --wire-shadow-1 had the same problem: the showcase declared it
* privately, so the library looked right there and flat everywhere else.
* Defining them with the rest of the theme keeps a component looking the
* same in every app; an app can still override them.
*/
":root{--wire-radius-sm:0.55rem;--wire-radius-md:0.9rem;--wire-radius-lg:1.35rem;" +
"--wire-shadow-1:0 1px 2px color-mix(in srgb, black 22%, transparent)," +
"0 10px 30px color-mix(in srgb, black 14%, transparent);}\n" +
"wrn-slot{display:contents;}\n" +
// A [data-for] element is the loop TEMPLATE, not a rendered row: the server
// ships it with mustaches unresolved ({item.title}) and hydration replaces
// it with a comment marker. Painted as-is it flashes one blank, literal
// row on every page load before the runtime boots. Hiding it costs
// nothing after hydration -- the runtime strips data-for from the clones
// it renders, so this only ever matches the template itself.
"[data-for]{display:none !important;}\n"
);
return blocks.join("\n") + globalThemeCss();
}
/**
@@ -603,6 +613,7 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
const names = JSON.stringify(theme.names);
const accentNames = JSON.stringify(theme.accentNames);
const defaultAccent = JSON.stringify(theme.defaultAccent ?? null);
const cssPrefix = JSON.stringify(THEME_CSS_PREFIX);
return `(function(){
var THEME_COOKIE=${JSON.stringify(THEME_COOKIE)};
@@ -611,6 +622,7 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
var ACCENTS=${accentNames};
var DEFAULT_THEME=${JSON.stringify(theme.default)};
var DEFAULT_ACCENT=${defaultAccent};
var THEME_CSS_PREFIX=${cssPrefix};
var MAX_AGE=31536000;
var el=document.documentElement;
@@ -637,6 +649,13 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
document.cookie=encodeURIComponent(name)+"=;path=/;max-age=0;samesite=lax"+secure;
}
function syncStylesheet(themeName,accentName){
var link=document.querySelector("link[data-wrnexus-theme]");
if(!link)return;
var accent=accentName?encodeURIComponent(accentName):"_";
link.href=THEME_CSS_PREFIX+encodeURIComponent(themeName)+"/"+accent+".css";
}
function getTheme(){
var current=el.getAttribute("data-theme")||readCookie(THEME_COOKIE)||DEFAULT_THEME;
return THEMES.indexOf(current)>=0?current:DEFAULT_THEME;
@@ -665,6 +684,7 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
if(THEMES.indexOf(name)<0)return;
el.setAttribute("data-theme",name);
writeCookie(THEME_COOKIE,name);
syncStylesheet(name,getAccent());
syncThemeButtons(document);
window.dispatchEvent(new CustomEvent("wrnexus:theme-changed",{detail:{theme:name}}));
}
@@ -678,6 +698,7 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
if(ACCENTS.indexOf(name)<0)return;
el.setAttribute("data-accent",name);
writeCookie(ACCENT_COOKIE,name);
syncStylesheet(getTheme(),name);
syncAccentButtons(document);
window.dispatchEvent(new CustomEvent("wrnexus:accent-changed",{detail:{accent:name}}));
}
@@ -685,6 +706,7 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
function clearAccent(){
el.removeAttribute("data-accent");
deleteCookie(ACCENT_COOKIE);
syncStylesheet(getTheme(),null);
syncAccentButtons(document);
window.dispatchEvent(new CustomEvent("wrnexus:accent-changed",{detail:{accent:null}}));
}
+13
View File
@@ -5,6 +5,8 @@ import {
resolveThemeConfig,
resolveThemeName,
renderThemeCss,
renderActiveThemeCss,
activeThemeCssHref,
renderThemeRuntime,
} from "../src/index.ts";
@@ -85,6 +87,17 @@ test("renderThemeCss emits :root + per-theme blocks and --wire-* vars", () => {
expect(css).toContain("font-family:var(--wire-font-sans");
});
test("active theme CSS contains only the selected theme and accent", () => {
const theme = resolveThemeConfig({ default: "dark", palette: "rose" });
const full = renderThemeCss(theme);
const active = renderActiveThemeCss(theme, "dark", "rose");
expect(active).toContain(":root{");
expect(active).not.toContain('[data-theme="light"]');
expect(active.length).toBeLessThan(full.length / 4);
expect(activeThemeCssHref("dark", "rose")).toBe("/__wrnexus/theme/dark/rose.css");
expect(() => renderActiveThemeCss(theme, "missing")).toThrow("Unknown theme");
});
test("renderThemeRuntime bakes the theme names for cycling", () => {
const t = resolveThemeConfig();
const js = renderThemeRuntime(t);
+15 -1
View File
@@ -439,7 +439,14 @@ export function parse(source: string): PageAst {
// props { name: Type = <default>; @event name = function }
lx.next();
expect("lbrace");
while (lx.peek().type !== "rbrace") {
while (true) {
if (lx.startsWithBlockComment()) {
throw new ParseError(
"Block comments are not allowed inside props {}; use // line comments instead",
"WRN-PROPS-BLOCK-COMMENT",
);
}
if (lx.peek().type === "rbrace") break;
const t = lx.peek();
if (t.type === "eof") throw new ParseError("Unexpected end of input inside props");
if (t.type === "at") {
@@ -829,6 +836,13 @@ export function parse(source: string): PageAst {
const declaredStates = new Set(states.map((state) => state.name));
if (declaredStates.has("page")) {
throw new ParseError(
"State name 'page' collides with the WRN 'page' keyword; choose another state name",
"WRN-STATE-RESERVED-NAME",
);
}
for (const watcher of watches) {
if (!declaredStates.has(watcher.state)) {
throw new ParseError(`Cannot watch undeclared state '${watcher.state}'`);
+6
View File
@@ -55,6 +55,12 @@ export class Lexer {
}
}
/** True when the next non-trivia characters open a block comment. */
startsWithBlockComment(): boolean {
this.skipTrivia();
return this.src[this.pos] === "/" && this.src[this.pos + 1] === "*";
}
/** Read and consume the next structural token. */
next(): Token {
this.skipTrivia();
@@ -0,0 +1,76 @@
import { describe, expect, test } from "bun:test";
import {
createSourceRange,
diagnose,
diagnosticSummary,
sliceSource,
supportsSyntaxFeature,
} from "../src/index.ts";
describe("syntax contract and security diagnostics", () => {
test("validates source ranges and summarizes diagnostic codes", () => {
const range = createSourceRange(2, 6);
expect(sliceSource("0123456789", range)).toBe("2345");
expect(() => createSourceRange(-1, 2)).toThrow(RangeError);
expect(() => createSourceRange(4, 3)).toThrow(RangeError);
expect(supportsSyntaxFeature("runtime-markers")).toBe(true);
expect(supportsSyntaxFeature("dynamic-eval")).toBe(false);
const summary = diagnosticSummary([
{ code: "A", severity: "error", message: "a" },
{ code: "A", severity: "warning", message: "b" },
{ code: "B", severity: "info", message: "c" },
]);
expect(summary).toEqual({ errors: 1, warnings: 1, info: 1, codes: { A: 2, B: 1 } });
});
test("rejects browser secret reads, executable sinks, and sensitive persistence", () => {
const diagnostics = diagnose(`page store Unsafe {
client state { apiToken: string = process.env.API_TOKEN }
persist { storage = "local" include = ["apiToken"] version = 1 }
functions {
client function render(raw: string): void { document.write(raw); setTimeout("run()", 1) }
}
}`);
const codes = diagnostics.map((diagnostic) => diagnostic.code);
expect(codes).toContain("WRN-SEC-SERVER-SECRET-SOURCE");
expect(codes).toContain("WRN-SEC-DOM-SINK");
expect(codes).toContain("WRN-SEC-STRING-TIMER");
expect(codes).toContain("WRN-PERSIST-SENSITIVE");
});
test.each([
[
"server output call",
`component Bad { outputs { save() } functions { server function run(): void { output.save() } } view { <div></div> } }`,
"WRN-OUTPUT-SERVER-CALL",
],
[
"unknown output call",
`component Bad { functions { client function run(): void { output.missing() } } view { <button @click="run()">Run</button> } }`,
"WRN-OUTPUT-UNKNOWN",
],
[
"server API in client function",
`component Bad { functions { client function run(): void { process.cwd() } } view { <button @click="run()">Run</button> } }`,
"WRN-CLIENT-SERVER-API",
],
[
"browser API in server function",
`component Bad { functions { server function run(): void { document.title = "bad" } } view { <div></div> } }`,
"WRN-SERVER-BROWSER-API",
],
[
"non-serializable shared state",
`global store Bad { state { values: unknown = new Map() } }`,
"WRN-STATE-NON-SERIALIZABLE",
],
[
"unknown persisted state",
`global store Bad { state { value: string = "x" } persist { storage = "local" include = ["missing"] version = 1 } }`,
"WRN-PERSIST-UNKNOWN-FIELD",
],
])("diagnoses %s", (_name, source, expectedCode) => {
expect(diagnose(source).map((diagnostic) => diagnostic.code)).toContain(expectedCode);
});
});
+2 -1
View File
@@ -24,7 +24,7 @@ import { pathToFileURL } from "node:url";
import { compileWireFile } from "@wrnexus/compiler";
import { createContext } from "@wrnexus/core";
import type { Context } from "@wrnexus/core";
import { getReactiveRuntime } from "@wrnexus/csr";
import { getComponentControllerRuntime, getReactiveRuntime } from "@wrnexus/csr";
import { startServer } from "@wrnexus/dev-server";
import {
loadAppConfig,
@@ -100,6 +100,7 @@ export function mountHtml(html: string): {
g.HTMLSelectElement = win.HTMLSelectElement;
g.HTMLTextAreaElement = win.HTMLTextAreaElement;
(0, eval)(getReactiveRuntime());
(0, eval)(getComponentControllerRuntime());
(win as { __wrnexusHydrateScopes?: (root: unknown) => void }).__wrnexusHydrateScopes?.(
win.document,
);
+9 -63
View File
@@ -1,6 +1,6 @@
# WRNexus UI component reference
This reference is generated from the 108 packaged `.wrn` component sources. Props marked required have no default; all others show their runtime default.
This reference is generated from the 102 packaged `.wrn` component sources. Props marked required have no default; all others show their runtime default.
## Advanced-forms
@@ -22,15 +22,6 @@ Editable autocomplete combobox with local and remote suggestions.
- Slots: None
- Outputs: `search({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `select({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `change({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `clear({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `open({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `close({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `load({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `error({ error: Error | string; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | Error | string)`
### CopyMarkup
Theme-aware, responsive copy markup component.
- Mount: `data-component="CopyMarkup"`
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Copy Markup"`, `name: string = ""`, `value: string = ""`, `placeholder: string = ""`, `type: string = "text"`, `min: string = ""`, `max: string = ""`, `step: string = ""`, `disabled: boolean = false`, `required: boolean = false`, `class: string = ""`
- Slots: None
- Outputs: `copy({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])`, `success({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `error({ error: Error | string; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | Error | string)`
### InputNumber
Theme-aware, responsive input number component.
@@ -303,23 +294,14 @@ Present responsive chronological activity, milestones, or workflow status with r
- Slots: `default`
- Outputs: `select({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`
### Toast
Theme-aware, responsive toast component.
- Mount: `data-component="Toast"`
- Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "Toast"`, `description: string = ""`, `items: unknown[] = []`, `variant: string = "default"`, `class: string = ""`
- Slots: `default`
- Outputs: `dismiss({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `action({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`
### TreeView
Theme-aware, responsive tree view component.
- Mount: `data-component="TreeView"`
- Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "Tree View"`, `description: string = ""`, `items: unknown[] = []`, `variant: string = "default"`, `class: string = ""`
- Props: `title: string = "Tree View"`, `description: string = ""`, `items: unknown[] = []`, `valueKey: string = "value"`, `labelKey: string = "label"`, `defaultExpanded: unknown[] = []`, `selected: string = ""`, `size: string = "default"`, `color: string = "primary"`, `class: string = ""`
- Slots: `default`
- Outputs: `select({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `toggle({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `expand({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `collapse({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`
- Outputs: `select({ item: object; value: string; sourceEvent?: Event })`, `toggle({ item: object; value: string; expanded: boolean; sourceEvent?: Event })`, `expand({ item: object; value: string; sourceEvent?: Event })`, `collapse({ item: object; value: string; sourceEvent?: Event })`
## Core
@@ -509,50 +491,32 @@ Theme-aware, responsive time picker component.
## Integrations
### AdvancedDatePicker
Theme-aware, responsive advanced date picker component.
- Mount: `data-component="AdvancedDatePicker"`
- Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "Advanced Date Picker"`, `description: string = ""`, `items: unknown[] = []`, `variant: string = "default"`, `class: string = ""`
- Slots: `default`
- Outputs: `input({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `change({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `open({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `close({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `clear({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`
### AdvancedRangeSlider
Theme-aware, responsive advanced range slider component.
- Mount: `data-component="AdvancedRangeSlider"`
- Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "Advanced Range Slider"`, `description: string = ""`, `items: unknown[] = []`, `variant: string = "default"`, `class: string = ""`
- Slots: `default`
- Outputs: `input({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `change({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `start({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `end({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`
### Chart
Theme-aware, responsive chart component.
- Mount: `data-component="Chart"`
- Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "Chart"`, `description: string = ""`, `items: unknown[] = []`, `variant: string = "default"`, `class: string = ""`
- Props: `title: string = "Chart"`, `description: string = ""`, `items: unknown[] = []`, `valueKey: string = "value"`, `labelKey: string = "label"`, `height: number = 240`, `showLegend: boolean = true`, `showValues: boolean = true`, `size: string = "default"`, `color: string = "primary"`, `variant: string = "bar"`, `class: string = ""`
- Slots: `default`
- Outputs: `select({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `dataPointClick({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])`, `legendToggle({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])`
- Outputs: `select({ item: object; index: number; sourceEvent?: Event })`, `dataPointClick({ item: object; index: number; sourceEvent?: Event })`, `legendToggle({ item: object; index: number; hidden: boolean; sourceEvent?: Event })`
### Clipboard
Theme-aware, responsive clipboard component.
- Mount: `data-component="Clipboard"`
- Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "Clipboard"`, `description: string = ""`, `items: unknown[] = []`, `variant: string = "default"`, `class: string = ""`
- Props: `value: string = ""`, `label: string = "Copy"`, `copiedLabel: string = "Copied"`, `errorLabel: string = "Copy failed"`, `title: string = "Clipboard"`, `description: string = ""`, `code: boolean = true`, `size: string = "default"`, `color: string = "primary"`, `disabled: boolean = false`, `class: string = ""`
- Slots: `default`
- Outputs: `copy({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])`, `success({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `error({ error: Error | string; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | Error | string)`
- Outputs: `copy({ value: string; sourceEvent?: Event })`, `success({ value: string; sourceEvent?: Event })`, `error({ error: Error | string; value: string; sourceEvent?: Event })`
### Confetti
Theme-aware, responsive confetti component.
- Mount: `data-component="Confetti"`
- Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "Confetti"`, `description: string = ""`, `items: unknown[] = []`, `variant: string = "default"`, `class: string = ""`
- Props: `label: string = "Celebrate"`, `duration: number = 1200`, `count: number = 24`, `autoStart: boolean = false`, `disabled: boolean = false`, `size: string = "default"`, `color: string = "primary"`, `class: string = ""`
- Slots: `default`
- Outputs: `start({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `complete({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`
- Outputs: `start({ sourceEvent?: Event; duration: number })`, `complete({ sourceEvent?: Event; duration: number })`
### DataMap
@@ -572,15 +536,6 @@ Theme-aware, responsive drag and drop component.
- Slots: `default`
- Outputs: `dragStart({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `dragEnd({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `dragEnter({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `dragLeave({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `drop({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `change({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`
### FileUpload
Theme-aware, responsive file upload component.
- Mount: `data-component="FileUpload"`
- Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "File Upload"`, `description: string = ""`, `items: unknown[] = []`, `variant: string = "default"`, `class: string = ""`
- Slots: `default`
- Outputs: `select({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `upload({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `progress({ progress: number; [key: string]: string | number | boolean | null | object } | number)`, `success({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `error({ error: Error | string; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | Error | string)`, `cancel({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `remove({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`
### Map
Present responsive location information and markers with map-ready metadata and movement or marker events.
@@ -590,15 +545,6 @@ Present responsive location information and markers with map-ready metadata and
- Slots: `default`
- Outputs: `markerClick({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `select({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `zoom({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`
### ToastNotifications
Theme-aware, responsive toast notifications component.
- Mount: `data-component="ToastNotifications"`
- Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "Toast Notifications"`, `description: string = ""`, `items: unknown[] = []`, `variant: string = "default"`, `class: string = ""`
- Slots: `default`
- Outputs: `add({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])`, `dismiss({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `clear({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `action({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`
### WysiwygEditor
Theme-aware, responsive wysiwyg editor component.
+5 -9
View File
@@ -118,16 +118,12 @@ are `.wrn` files rendered server-side.
The framework serves this stylesheet once at `/__wrnexus/ui.css`, so pages get all
component styles from a single request.
### Tailwind and motion
### Component styles and motion
Components use static Tailwind utility classes alongside the shared `.wire-*`
layer. If the package is consumed by a separate Tailwind build, include its
component sources so every utility is generated:
```css
@import "tailwindcss";
@source "../node_modules/@wrnexus/ui/components/*.wrn";
```
Components own their BEM styles in local `style {}` blocks and do not require a
Tailwind scan. `ui.css` supplies only global tokens, resets, and shared
primitives. Applications can still use Tailwind independently in their own
source files.
The shared stylesheet gives all component boundaries consistent, GPU-friendly
entry and interaction motion. Override `--wire-motion-fast`,
-30
View File
@@ -6,16 +6,6 @@
"category": "base",
"purpose": "Theme-aware, responsive accordion component."
},
{
"name": "AdvancedDatePicker",
"category": "integrations",
"purpose": "Theme-aware, responsive advanced date picker component."
},
{
"name": "AdvancedRangeSlider",
"category": "integrations",
"purpose": "Theme-aware, responsive advanced range slider component."
},
{
"name": "AdvancedSelect",
"category": "advanced-forms",
@@ -151,11 +141,6 @@
"category": "overlays",
"purpose": "Open an accessible keyboard-aware action menu from pointer or keyboard context interactions."
},
{
"name": "CopyMarkup",
"category": "advanced-forms",
"purpose": "Theme-aware, responsive copy markup component."
},
{
"name": "CustomScrollbar",
"category": "layout",
@@ -221,11 +206,6 @@
"category": "forms",
"purpose": "Theme-aware, responsive file input component."
},
{
"name": "FileUpload",
"category": "integrations",
"purpose": "Theme-aware, responsive file upload component."
},
{
"name": "FileUploadProgress",
"category": "base",
@@ -496,16 +476,6 @@
"category": "base",
"purpose": "Present responsive chronological activity, milestones, or workflow status with rich item metadata."
},
{
"name": "Toast",
"category": "base",
"purpose": "Theme-aware, responsive toast component."
},
{
"name": "ToastNotifications",
"category": "integrations",
"purpose": "Theme-aware, responsive toast notifications component."
},
{
"name": "Toaster",
"category": "core",

Some files were not shown because too many files have changed in this diff Show More