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/ node_modules/
dist/ dist/
.wrnexus/ .wrnexus/
.wrnexus-*/
*.log *.log
*.db *.db
*.db-shm *.db-shm
+2
View File
@@ -1,6 +1,8 @@
node_modules/ node_modules/
dist/ dist/
**/dist/ **/dist/
**/.wrnexus/**
**/.wrnexus-*/**
.publish/ .publish/
**/.wirefw/ **/.wirefw/
coverage/ coverage/
+231 -4
View File
@@ -109,12 +109,101 @@
"name": "inter-app-api-showcase", "name": "inter-app-api-showcase",
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.0.0", "@eslint/js": "^9.0.0",
"@types/bun": "latest", "@types/bun": "^1.3.14",
"@wrnexus/cli": "0.8.6", "@wrnexus/cli": "0.8.6",
"eslint": "^9.0.0", "eslint": "^9.0.0",
"prettier": "latest", "prettier": "^3.9.6",
"typescript": "^5.5.0", "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": { "packages/ai": {
@@ -563,7 +652,7 @@
}, },
"services/managed-captcha": { "services/managed-captcha": {
"name": "@wrnexus/managed-captcha-service", "name": "@wrnexus/managed-captcha-service",
"version": "0.8.0", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/captcha": "workspace:*", "@wrnexus/captcha": "workspace:*",
}, },
@@ -580,6 +669,8 @@
"packages": { "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=="], "@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=="], "@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=="], "@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=="], "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=="], "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=="], "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=="], "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=="], "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=="], "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=="], "@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=="], "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=="], "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=="], "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=="], "@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=="], "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=="], "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=="], "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/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=="], "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=="], "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/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=="], "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/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=="], "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 # 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 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 proven the item says so and makes proving it step one — nothing in this document
should be implemented on the strength of a guess. 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 are what currently limits what anyone can build. **§2.1 is the highest-value
item in this entire document.** 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 **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 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 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 The production size ratchet now caps the split core runtime at 49,000 bytes; it
69,947 bytes minified today. 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 **Issue.** State written after a client function returns — in a `setTimeout`, a
promise callback, an observer, a `pointermove` — is discarded. The peer-function 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. interacts with re-render batching.
**How to test.** A test where a client function writes state inside a **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 **Issue.** Component props and slot content do not track page state. The
controlled-component pattern does not work at all: passing `value={pageState}` 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 **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 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 **Issue.** Nesting a component tag in another component's slot drops the tag and
leaves its children orphaned. leaves its children orphaned.
@@ -255,9 +277,9 @@ it is fixed, emit the §2.1 diagnostic — a dropped component must never be
silent. silent.
**How to test.** Render `<Card><Badge label="x" /></Card>` and assert the Badge **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 **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 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 **How to test.** A compiler test asserting the specific diagnostic for
`items={{ a: 1 }}`, and one asserting the hoisted form still compiles. `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. **Original issue.** Translated text shipped as empty spans and was filled after
Navigation is blank until the runtime loads. hydration. Navigation was blank until the runtime loaded.
**Why it matters.** This is an SSR-first framework failing to server-render **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 text. It is an SEO problem, a layout-shift problem, and it undercuts the
framework's central claim. framework's central claim.
**Change.** Resolve the active locale during SSR and emit the translated text **Change.** Done. The active locale is resolved during SSR and translated text
into the HTML, keeping the client path for locale switches. 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.** **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 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. 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 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. 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 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. 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 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 are markup shells that declare outputs. "Add an emitter" is not the work — there
is nothing to emit from. is nothing to emit from.
@@ -393,9 +417,7 @@ self-contained. `TreeView` is medium — recursive rendering plus expand state.
**How to test.** **How to test.**
- The ratchet in `packages/ui/test/ui.test.ts` is pinned at 22 and only ever - The ratchet in `packages/ui/test/ui.test.ts` is now pinned at **0**.
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.
- For a removal: `bun run check:public-api` flags the dropped export, and the - For a removal: `bun run check:public-api` flags the dropped export, and the
migration entry is required before `release:prepare` will pass. migration entry is required before `release:prepare` will pass.
- For a build: the component renders, its behaviour works in a browser, its - 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 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. 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 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 scaffold that was rebuilt. A scaffold in a published library is a promise the
library does not keep. library does not keep.
**Evidence.** Counted as components with no `style {}`, no `function` and no **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; `@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, > AdvancedDatePicker, AdvancedRangeSlider, AuthSplitLayout, avatar, Blockquote,
> button, Chart, Clipboard, Confetti, CopyMarkup, DataMap, DragAndDrop, > 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 ToastNotifications, TreeView. Do §3.1 first and this list drops to 19 without
any extra work. Do not plan the two items separately. any extra work. Do not plan the two items separately.
Separately, **58 components still carry the `wire-next` scaffold class** in Separately, 58 components carried the standalone `wire-next` scaffold class in
their markup, including many that are otherwise finished. That class is a the original audit; **zero retain it now**. Component-specific BEM identifiers
generation artefact rather than a design, and it is what ties them to `ui.css` such as `wire-next--alert` remain locally owned implementation details, but no
instead of their own styles (§4.1). component is bound to a global stylesheet through the generation artefact.
**Change.** Rebuild them under §0.1, or remove them. Note that several are **Change.** Rebuild them under §0.1, or remove them. Note that several are
primitives where a scaffold is nearly the right answer — `skeleton`, `spinner`, 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 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 `TextLink` (14), `Map` (12), `SearchBox` (8), `Timeline` (8) still style
themselves with utility classes in markup. 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 {}` **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 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 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. 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 **Issue.** The `@click` handler sits on the `item.href` branch only, so an item
without a link is not selectable. Pre-existing, and surprising. 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. 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. **Original issue.** `ui.css` was **175,848 bytes, 26,350 gzipped**, served on
Only **42 of 108** components have a local `style {}` block, so 66 still depend every page. Only **42 of 108** components had a local `style {}` block.
on it.
**Change.** Migrate the remaining 66 under §0.1. This is mechanical, high-value, **Result.** The maintained library now has **102 of 102** components with local
and shrinks `ui.css` toward the global-only baseline it should be. Do it in styles. Built `ui.css` is **15,927 bytes / 3,762 gzipped**. The `.wire-next`,
groups, one commit per group, regenerating the visual contract each time. `.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: **How to test.** Track the number down, every time:
```bash ```bash
ls packages/ui/components/*.wrn | wc -l # 108 ls packages/ui/components/*.wrn | wc -l # 102
grep -l '^ style {' packages/ui/components/*.wrn | wc -l # 42 today grep -lE '^\s*style\s*\{' packages/ui/components/*.wrn | wc -l # 102
bun run build && gzip -c examples/basic-app/dist/ui.css | wc -c # 26350 today 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 Add a ratchet test asserting the gzipped size only ever decreases, so this
cannot silently regress. 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 **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 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 `packages/ui/components/*.wrn`, reload the page **without restarting**, and see
the change. That is the whole acceptance criterion. 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 empties `.wrnexus`, and every client module then 404s until a restart. This
produced several confusing "the Sidebar is broken" symptoms this session that produced several confusing "the Sidebar is broken" symptoms this session that
were not component bugs at all. 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 `WRNEXUS_PRESERVE_CACHE=1`. Any dev server a test spawns against the repo root
therefore deletes the running server's cache. 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), - Give each dev-server instance its own cache directory (keyed by port or pid),
so instances cannot collide; or so instances cannot collide; or
@@ -553,23 +592,22 @@ Prefer the first — it fixes the class rather than one caller. Either way,
owns. owns.
**How to test.** Start the dev server, load a page, run `bun test packages` to **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 completion, reload the page without restarting. It must still work.
reliably today.
### 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 or newly added props and outputs are invisible to the showcase generator. This
is undocumented and cost real time. is undocumented and cost real time.
**Change.** Make the showcase generator depend on the reference explicitly, or **Change.** Done. `generate:pages` regenerates the reference first, and both
fail loudly when the reference is older than the component sources. generators emit idempotent Prettier-compatible artifacts.
**How to test.** Add a prop to a component, run the showcase generator without **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 regenerating the reference, and assert it errors rather than silently emitting a
stale page. 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 **Issue.** Block comments are not allowed inside `props {}` — line comments
only — and `state page` collides with the `page` keyword. Both currently fail in 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". "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. 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 and re-minifying gives its true cost:
| Subsystem | Minified | Share | | 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 15,076 gzipped: the expression engine, the scope and reactivity core, and loop
diffing. 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 Measured against the example app by checking which controller markers appear in
the served HTML: 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. 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** - **490,212 bytes decoded** across 11 client modules, **21,026 transferred**
a 23:1 compression ratio. 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 = - One line appears **162 times**: `brand = context.state.brand; topLinks =
context.state.topLinks; ...` — the full state-restore prologue. 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 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 bytes, not transferred bytes**. Half a megabyte of JavaScript is parsed to run
one page. one page.
@@ -720,16 +765,18 @@ bun run build
Add the ratio itself as a signal: any module compressing better than about 10:1 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 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. 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` `TypeError: factories[entry.category] is not a function`
(`scripts/generate-ui-complete-catalog.mjs:156`) — but not before it has already (`scripts/generate-ui-complete-catalog.mjs:156`) — but not before it has already
started writing. It **overwrites real components with bare scaffolds and deletes started writing. It **overwrote real components with bare scaffolds and deleted
others**, then crashes, leaving the library in a wrecked state. 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, **Evidence.** Running it on 2026-08-09 rewrote Accordion, alert, Badge,
AvatarGroup, ToggleCount and LayoutSplitter down to ~15-line stubs, deleted 24 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 Whichever is chosen, **no script that rewrites `packages/ui/components/` should
write in place.** Generate to a staging directory, validate, then move. 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 the repository or start servers when run — `install-captcha.mjs`, the
`validate-*.mjs` and `verify-*.mjs` families, and `benchmark-framework.mjs`. All `validate-*.mjs` and `verify-*.mjs` families, and `benchmark-framework.mjs`.
of them fail today. They should either be repaired and gated, or removed. A 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 `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 dev servers on ports 3000-3002 and rewrite the component library is a hazard to
anyone exploring the repo, human or otherwise. 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 modify tracked files, the way `check:workspace` and `check:public-api` already
do. 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 **Issue.** Four components were tracked in git under lowercase names
(`card.wrn`, `container.wrn`, `divider.wrn`, `grid.wrn`) while existing on disk (`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 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. 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 This is the execution order used during remediation, retained as implementation
below it, which is why it is first. history. Every row is complete.
| # | Item | Why now | | # | 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. | | 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. | | 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. | | 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. | | 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. | | 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. | | 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", "createOutputProxy",
"createServerProxy", "createServerProxy",
"getActionRuntime", "getActionRuntime",
"getComponentControllerRuntime",
"getNavRuntime", "getNavRuntime",
"getReactiveRuntime", "getReactiveRuntime",
"getRealtimeRuntime", "getRealtimeRuntime",
@@ -2603,6 +2604,7 @@
"StylesMode", "StylesMode",
"THEME_COOKIE", "THEME_COOKIE",
"THEME_CSS_HREF", "THEME_CSS_HREF",
"THEME_CSS_PREFIX",
"THEME_JS_HREF", "THEME_JS_HREF",
"THEME_PALETTES", "THEME_PALETTES",
"THEME_PALETTE_NAMES", "THEME_PALETTE_NAMES",
@@ -2613,6 +2615,7 @@
"ThemeSemanticColor", "ThemeSemanticColor",
"ThemeToken", "ThemeToken",
"ThemeTokens", "ThemeTokens",
"activeThemeCssHref",
"auditCssPerformance", "auditCssPerformance",
"auditWireTokens", "auditWireTokens",
"bundleCss", "bundleCss",
@@ -2628,6 +2631,7 @@
"loadEnv", "loadEnv",
"loadRawConfig", "loadRawConfig",
"normalizeStyleSources", "normalizeStyleSources",
"renderActiveThemeCss",
"renderFontHead", "renderFontHead",
"renderProductionFontHead", "renderProductionFontHead",
"renderStyles", "renderStyles",
+77 -80
View File
@@ -2,114 +2,111 @@
"schemaVersion": 1, "schemaVersion": 1,
"releaseLine": "0.8", "releaseLine": "0.8",
"artifacts": { "artifacts": {
"packages/ui/components/Accordion.wrn": "2b2e756a2197052bbcaf7499d01ec4b68b53a3acaf4524b7ca6424b71ba2e9a3", "packages/ui/components/Accordion.wrn": "285df0aa50733618afb1b82aa125a67030d3af4bd8b9de52bc2e6366cea871de",
"packages/ui/components/AdvancedDatePicker.wrn": "b9b5b08e9464544837a5400bbf9a2cb25724fac037fe196a219b42e5841ad672", "packages/ui/components/AdvancedSelect.wrn": "e8666638204f9eb044ee6f163d6af432b119bf7f39d9df20243cfae471d20bcd",
"packages/ui/components/AdvancedRangeSlider.wrn": "e78b5cf825503d0cd306c6e490d71317ee3db1844882d13b500df1f7f1c408e1",
"packages/ui/components/AdvancedSelect.wrn": "29d43f8019a3e0e98af7cf12440f41523665535a39a973441b4faea78e67114a",
"packages/ui/components/AnnouncementBar.wrn": "903bfd9421a7350c923ace3315cedc5763fe3e6b7b54b660a0f26281d46a8754", "packages/ui/components/AnnouncementBar.wrn": "903bfd9421a7350c923ace3315cedc5763fe3e6b7b54b660a0f26281d46a8754",
"packages/ui/components/AuthForm.wrn": "496d240090269e69679e01cda3b071c012ec4e925a7c01ae883d25e359e381e1", "packages/ui/components/AuthForm.wrn": "ec8796a67320afa2fb2070edba194382d1ab0a00cbd574f936a502da819d00fa",
"packages/ui/components/AuthSplitLayout.wrn": "72b82495dd14d40a2d594ccdcf3adc925d1567c155750fb1072347b8845aab7b", "packages/ui/components/AuthSplitLayout.wrn": "675c6ac9b07e0e9c5d6572f30028dda2391f9c6599b45945eb626df056a5b987",
"packages/ui/components/AvatarGroup.wrn": "66f3d211b7658ca5193c750d0ccb3e392185038d8960d3206f6e8a249e10214e", "packages/ui/components/AvatarGroup.wrn": "85dd6c64f8064d0980bf4157963bf366410c2ba391fd65df86a54b1a336f43a4",
"packages/ui/components/BackToTop.wrn": "b12c56ec8b7aabad68cbdc47f4b3237e2c21a61398f0aa51f9add6229b7357cd", "packages/ui/components/BackToTop.wrn": "b12c56ec8b7aabad68cbdc47f4b3237e2c21a61398f0aa51f9add6229b7357cd",
"packages/ui/components/Badge.wrn": "e96cdcf182ff9834509464bb2fa4c0b041748d3f7671aa46ab489bae6f3d98ae", "packages/ui/components/Blockquote.wrn": "6f91cee9538c11858aaeb98ef1a098a814a6e319e3777e59cecd2de078b3beaf",
"packages/ui/components/Blockquote.wrn": "6016dd4450de7cbf5c3cb413599baa2e502321e8eedef54439a0df6d0aae8bcb", "packages/ui/components/Breadcrumb.wrn": "5ab85d273ce72e4d5b87907975bb33f0249a34f2f87f01dc0812e827f775eed9",
"packages/ui/components/Breadcrumb.wrn": "14732e91468f5793ee5da8599f8e40d84ec51c93d660fb0c0056b956ec707850", "packages/ui/components/ButtonGroup.wrn": "0bb23d927e03926cc303498bc4ac1ab098343782082ac1fceeb2402702945842",
"packages/ui/components/ButtonGroup.wrn": "0d97fbeed531d1d8ef4b59531923b47b34100d0de71d7bf524b02c668920b3d8",
"packages/ui/components/CTASection.wrn": "2d2241d4f1018cfd2f8ea1ddd5fa8c8fcb8d1bbb2d4e4498cb9f39ff47a3801c", "packages/ui/components/CTASection.wrn": "2d2241d4f1018cfd2f8ea1ddd5fa8c8fcb8d1bbb2d4e4498cb9f39ff47a3801c",
"packages/ui/components/Card.wrn": "457bc546e0b227692ac96ec8f413823463d3cfe9194b6f4847c21a5beee5e8a4", "packages/ui/components/Card.wrn": "ba1b577ae4ffa9bd93c1e6a650ce90745df3f78cd6ff9ee85530e32dab56860a",
"packages/ui/components/Chart.wrn": "841def6b024720bd24898975a1434a1cb39c89e1272d42db431d35d27c98c553", "packages/ui/components/Carousel.wrn": "0d39e17a623fb623022f48e79ea1ab9f268e2eb359c491609c137f711a1a3ffc",
"packages/ui/components/ChatBubble.wrn": "0da2bc86830ac712481559bd2fe7214d26f25101f2e8c2e4629e25c004388b52", "packages/ui/components/Chart.wrn": "d2cedd0e02818d0908708c2d07a495c629fad13fa021a2e035d5023dc5c367de",
"packages/ui/components/Checkbox.wrn": "45e6046e3ad4acb1c1805b51fd5bf0b95b346d3d53973ededc5e43ea2fd91f70", "packages/ui/components/ChatBubble.wrn": "c46b9aef38f54e6243663046fe1b18ba6f9223cbe9b479d9335f96f217d401cc",
"packages/ui/components/Clipboard.wrn": "bb8651c7e44517db827603d0c0e546b6891093d7e6589c0b6e45d91b6187591b", "packages/ui/components/Clipboard.wrn": "03fcfb9786008fd7fb0a7719c3279711253afc5bbb9b0262b805dd553c44da8a",
"packages/ui/components/Collapse.wrn": "c537239831c6938f3c39be0ca3ae8ba928c2490e21cfe5853c10f7221a4678ca", "packages/ui/components/Collapse.wrn": "8869ff4a39541b7e0d538e2b63b9a3dcdcfcd4055fa362519c7e73f164f1be9d",
"packages/ui/components/ColorPicker.wrn": "6940b7ae48f1358008b8c3ae6dc7da31f96a1aefadeef18252568b2cda0b5cbf", "packages/ui/components/ColorPicker.wrn": "02f5744c3eb984de4b24de26fbfd3f69021633d4b0b33aceeba84df32f6a7284",
"packages/ui/components/Columns.wrn": "e01496aa53025444cc51671efcce40d6d24d86775b2cbb1ee0def1862bb6315d", "packages/ui/components/Columns.wrn": "e01496aa53025444cc51671efcce40d6d24d86775b2cbb1ee0def1862bb6315d",
"packages/ui/components/Combobox.wrn": "62a7d861b0e536ceb088cea3552c59acbafaea5184101e3651dc8f4976b99dbc", "packages/ui/components/Combobox.wrn": "140b038735a5902eb901386281a5a01e16fd1c345560a48ff2b143ebaebe1f3d",
"packages/ui/components/Confetti.wrn": "22d687beefd5047055f65e806b4e39f81e18887532ec18d439ac364a044b230a", "packages/ui/components/Confetti.wrn": "b9aece64735c721c24356122e52ca84602d9f646c7d8be1e0b80d9de3163a501",
"packages/ui/components/Container.wrn": "4a0acbb896f4c11714e3925d912b10ef9aeedfa904b4ad8561aa2cbd9a0fdca0", "packages/ui/components/Container.wrn": "4a0acbb896f4c11714e3925d912b10ef9aeedfa904b4ad8561aa2cbd9a0fdca0",
"packages/ui/components/ContextMenu.wrn": "2e011e9cb1c09a3a2344ed3fa29dcc74331cd39f838535d8aee4b249dceef0ce", "packages/ui/components/ContextMenu.wrn": "33fb0f60268b12411d66e3031dc0557cb231149e7a8d83340b4b453a6e37cb09",
"packages/ui/components/CopyMarkup.wrn": "8d57a7d72e02c126f855a181fc36e1b969edb90f683dce6b6ffe546d49d2b29b",
"packages/ui/components/CustomScrollbar.wrn": "fedb2ab06cc2cf9499c130590945ca6971a3edfb60407b6658ef7756e4be9823", "packages/ui/components/CustomScrollbar.wrn": "fedb2ab06cc2cf9499c130590945ca6971a3edfb60407b6658ef7756e4be9823",
"packages/ui/components/DataMap.wrn": "65552d73ecd1a148427dbb10d611f9352ca36bd490f595ab1e1a47e173445ba1", "packages/ui/components/DataMap.wrn": "6058f92be7fb95e9eedcc714cad1ad43fa70e40c9dd50fd8bba5aae9935953a7",
"packages/ui/components/DataTable.wrn": "5a76e50e8559c724aaf01ec954958ac640c99758d44f1eef8e6eb1cb6dc4f862", "packages/ui/components/DataTable.wrn": "edb935b091750ee3d1aff354007eabd4998fc7e5584d50a6c726c6657765d800",
"packages/ui/components/DatePicker.wrn": "18b07e59c27c2720bf960e9c04ad70d9e422ddcf0817fede74e71899811bb387", "packages/ui/components/DatePicker.wrn": "2c46f92612528cf60aa0b7e8aaae9826094acb5518d3982c2db65aca6d01e103",
"packages/ui/components/DeviceFrame.wrn": "a881cf2cc1f859b43cc3c7a1e1ca0fb40cea7a9e530ab5bdcb985aa9592d11c0", "packages/ui/components/DeviceFrame.wrn": "d918d213e6e0b74d690911ab3b3f440e9b62fa019fd9e8140c5a56c5c0a0843d",
"packages/ui/components/Divider.wrn": "f5cc449611022b3d3eedf982ae48f6b1a289db7855cc9b6ea0248dcda807ffb3", "packages/ui/components/Divider.wrn": "f5cc449611022b3d3eedf982ae48f6b1a289db7855cc9b6ea0248dcda807ffb3",
"packages/ui/components/DragAndDrop.wrn": "9a403ce9ed20d36211d8916911871875c9c7d0952a7de1a21a433eaa0b3c3324", "packages/ui/components/DragAndDrop.wrn": "31ffe9d41fc655a38257c841a520c947d3ce2338e174b5e89e32fe9ccf23b9b3",
"packages/ui/components/Drawer.wrn": "04a77a69edfad6793b5ab60981a719f11061984c087d0d61663566e322649fef", "packages/ui/components/Drawer.wrn": "e6e2778e515361a3475439ab91b6efec3895efab2a4dfdcfb535713f656cc7ed",
"packages/ui/components/Dropdown.wrn": "c61b46ae6e54f6aacac9f4ed422f6c659deea9a637fd944645acd37bf270b01d", "packages/ui/components/Dropdown.wrn": "627319de0f555f0f07f80e19a59f9fd52e7f74af7cf2cf9ff09e114678ad9eb8",
"packages/ui/components/FeatureCard.wrn": "a6afd6d4080917106b2c504ea3836b92ac3c18e85f1aafd74ffe49592782b5ec", "packages/ui/components/FeatureCard.wrn": "2a1bfb01cec8a9892d3ff066e48c276fa7c548cb59c15bcd9b4bbf85450a07a2",
"packages/ui/components/FeatureGrid.wrn": "9e3c0440d08c6982861d3e732eeb20f1023413a58243844dfb9d7033bac13d2b", "packages/ui/components/FeatureGrid.wrn": "9bd3e9becb5d7359841ed21d607131c58da20f2e8e87e25a9fb48f6b1a2b17e5",
"packages/ui/components/FeatureIconCard.wrn": "afe56213543dd78932b060fa19c4e645e558b211b380c42b6d4b0d18e9d543df", "packages/ui/components/FeatureIconCard.wrn": "620f66a36ca5c8ad8f2ea09e94dc4471e4146189c42149e9d31db58501281639",
"packages/ui/components/FileInput.wrn": "8b63811deb90a03763620bedf0d5d3c7d05a20b8ae34292eaface9b756b32ecd", "packages/ui/components/FileInput.wrn": "cc4b8f95f11d91ead9a5796e202b2264eeea24aa1c3b1718327a6ea1dd28d785",
"packages/ui/components/FileUpload.wrn": "3346d8978e134c3a1e6bc742201a3fbb0cffb89da40da6fcd856a2adbb971393", "packages/ui/components/FileUploadProgress.wrn": "c4b4174025a1de75c35a3497e0afd0b8acc645ad5f85c3ed7296ea295bb05749",
"packages/ui/components/FileUploadProgress.wrn": "83bcc8558c70fbe0e0670642f65998abd222c235f1031be46bd7c65e6b5c5154", "packages/ui/components/Footer.wrn": "71790c99e4b15a62fdaf4b7a65d7016ca427408b62a2b1c7695ef1b39ba29440",
"packages/ui/components/Footer.wrn": "d9075d2206af75a1ec4317d7974d47311889400699260e990d672780f43f40bf",
"packages/ui/components/Grid.wrn": "d089d8657298b4d33993ba4bf7b581f816c65db513d8e48cf1fdd18da19e05a8", "packages/ui/components/Grid.wrn": "d089d8657298b4d33993ba4bf7b581f816c65db513d8e48cf1fdd18da19e05a8",
"packages/ui/components/Hero.wrn": "e4e39dcd59c273192c7e4136f6583279e20b0956bcba4cfbc3f6e01a1fade152", "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/Image.wrn": "8113441bf90ab0d1d8618d477125ad339021f39e853db89d656c72c492e7a20b",
"packages/ui/components/Input.wrn": "9ad394eed60f1b05bbd2ccd50dc2de7b78245c52c87afd46055d772c38b7b0c9", "packages/ui/components/InputGroup.wrn": "95333e7cb883c52958e3d6112899f337b00825e7678a2323baed1724983a7238",
"packages/ui/components/InputGroup.wrn": "0e23ea541a60e893c9d9e6f95113911ee459468d8d6105bf03aeb55395359123", "packages/ui/components/InputNumber.wrn": "0ec020084f3e556d438f8264d61f836cfab4951c52d4df60e4944fb734fb38a9",
"packages/ui/components/InputNumber.wrn": "26936385e9f01ec888a1df61719991aa7a2782305328b02c939d3c2c94e3e8e0",
"packages/ui/components/Kbd.wrn": "5afaabbb15902bd8fb82cb4155cf6cbfff55600927b54d564d27e2dd8b752386", "packages/ui/components/Kbd.wrn": "5afaabbb15902bd8fb82cb4155cf6cbfff55600927b54d564d27e2dd8b752386",
"packages/ui/components/LayoutSplitter.wrn": "6bd6812e364420ed5d3439862116c58d0b011b298679005795bdccdbb7455066", "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/Link.wrn": "b986abcc66bc0b1c6296f3ff4e560812e35d89cc680d5251d7a3883e68c1d053",
"packages/ui/components/List.wrn": "8cf4505ff6a079feb3b77822d7ed6977a70393dd1e6267f94e75234d3ac215d6", "packages/ui/components/List.wrn": "9bfa9613f388e5b126fc27d505e8165d7aa4a058e72132b755585464e116490e",
"packages/ui/components/ListGroup.wrn": "37e2ec61ceab20ed020a834da03208c493702b41abcf586816e29f2e50d8ee11", "packages/ui/components/ListGroup.wrn": "885020eea4dbdfc90b5a659e48a339cba7a205ddb539e30d7f1aa57244d728a5",
"packages/ui/components/Map.wrn": "990656db93e1acd73a301a4addaf353afa14430805366d2e5f124b831044afbe", "packages/ui/components/Map.wrn": "9023a7a188480b24222a3910d26f6926918470369a1ab610f841c31a6120b9aa",
"packages/ui/components/MarketingSectionHeader.wrn": "7d6ffcc01a9331474475ea57ca58598be757abd9428b66bdfaf6f3603c5b145b", "packages/ui/components/MarketingSectionHeader.wrn": "6f89a3b298ea299db686ddf903f92c4fe5d321719213943b16be82ba36c23302",
"packages/ui/components/Marquee.wrn": "3395235807aea43ac10c4d562706e510dac2a32786b4a5d5d73927989ede68df", "packages/ui/components/Marquee.wrn": "2a88c8bd8378469add26b0d937adccac4aa9f2c2005393010298e8e4fa89693e",
"packages/ui/components/MegaMenu.wrn": "8a9a9b348ca1c5e182914ff55d981a91f47a49c52ef5e5efd8e4cb6e56361a43", "packages/ui/components/MegaMenu.wrn": "8a9a9b348ca1c5e182914ff55d981a91f47a49c52ef5e5efd8e4cb6e56361a43",
"packages/ui/components/MetricCard.wrn": "6451182739298691908f68258c0250cce2a78b0dc27c97115579ece30d7d9f92", "packages/ui/components/MetricCard.wrn": "6451182739298691908f68258c0250cce2a78b0dc27c97115579ece30d7d9f92",
"packages/ui/components/MetricGrid.wrn": "6018a98c10628ed240ed236ed916c0ff60994d192c3aadafd10bc876be0f9364", "packages/ui/components/MetricGrid.wrn": "6018a98c10628ed240ed236ed916c0ff60994d192c3aadafd10bc876be0f9364",
"packages/ui/components/Modal.wrn": "59d4ae9d6dd2700692d9edecfe53ee868e9f864363a4885961d1813cb2bee51f", "packages/ui/components/Modal.wrn": "59d4ae9d6dd2700692d9edecfe53ee868e9f864363a4885961d1813cb2bee51f",
"packages/ui/components/Nav.wrn": "7dc456b879e9248bde267b986cdcb138b8f3127d98b51e27bc9708129953f5e2", "packages/ui/components/Nav.wrn": "7dc456b879e9248bde267b986cdcb138b8f3127d98b51e27bc9708129953f5e2",
"packages/ui/components/Navbar.wrn": "7989d25819312014c0a0855370b80b925d53ff735d682fdb61e9677a9dacb7f3", "packages/ui/components/Navbar.wrn": "70640f9ff6e615c70ea9a5e996058bad45ebf0e97b02c9d827228a8f475f72e2",
"packages/ui/components/PageHeader.wrn": "0761235a4924eec09877be40b292d27b70db22d210be7d8e989493165c20df86", "packages/ui/components/PageHeader.wrn": "59264dfb26a132fd3eb265c4374f9fcae781f30dd94a087c57e6e8256717cf14",
"packages/ui/components/Pagination.wrn": "9ceb74745b159150b015bbbb1974c68e14a7d4b92bd03491cb23b8855aa07983", "packages/ui/components/Pagination.wrn": "9ceb74745b159150b015bbbb1974c68e14a7d4b92bd03491cb23b8855aa07983",
"packages/ui/components/PinInput.wrn": "5196f584de8d548a5dfa03688c3c95da3c948cead2662b05e927386ccea74299", "packages/ui/components/PinInput.wrn": "2b656cf832c02fa9c28e2fe78bf1147ff05b17a9d8e744c33e1a63bad7c7ebb3",
"packages/ui/components/Popover.wrn": "167f6c476cf3114ac5062ecf7739bddd9b5a81b1d209396a3675067dad1577b2", "packages/ui/components/Popover.wrn": "f2996f00fad3e35fba2d48e70af17f592854eb73b9e8c6a83526aa78c4e41293",
"packages/ui/components/PortalDashboard.wrn": "037d4300b59c7d60543abc7d4aba5c738efc143e9b61abc48aad0ddfcfe6845b", "packages/ui/components/PortalDashboard.wrn": "79b54868f150a551cede0731cc5390a45ce22375f1929f2d537ce67a987bde50",
"packages/ui/components/PreferenceSwitcher.wrn": "2cc186d4dcb6580b4b152e3a265d9ed5ad210d3fdc76330b9092db21894465f0", "packages/ui/components/PreferenceSwitcher.wrn": "241060fb24a16e795c53981506d273fe9110cbb250c73c85928c788c75d05ded",
"packages/ui/components/PublicPageShell.wrn": "369b74f65e27227311a2ed73e9ca56c8b60908100d6840a798e8ba5f2c8b1040", "packages/ui/components/PublicPageShell.wrn": "369b74f65e27227311a2ed73e9ca56c8b60908100d6840a798e8ba5f2c8b1040",
"packages/ui/components/Radio.wrn": "0b2d38a5aee3e859280e4590fa1d509d789cc2239082054872fa342817735f1b", "packages/ui/components/RangeSlider.wrn": "eff9db1616d424f9c803185b4efe011942866acad3328179438a200210d97710",
"packages/ui/components/RangeSlider.wrn": "21eb7a5df0ee2cb5e78f628eb920265998eb9f1a4933d4e5c57db0323fd66b41", "packages/ui/components/Rating.wrn": "41bbce2eb7fb6ad08a3aacde97d03af6e346824962148e7655743028c59185d3",
"packages/ui/components/Rating.wrn": "a2d74dc748fc7d98892684c760518b87fa65411b1102e3611252b87245b3ffcb",
"packages/ui/components/Scrollspy.wrn": "76d1c17580c8d460f1222ce97585bdcac9779141f2f30d5460c84ff9f75413d8", "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/Section.wrn": "33eb8a82a2102229c2b1f64cec497e9e8e70b67adab8dbe396be860fccbb699c",
"packages/ui/components/SectionHeader.wrn": "8eecf2ea7a93139512260e3d46ca2a1afeb341b8bd46b7c30baa6e2c75e25d10", "packages/ui/components/SectionHeader.wrn": "8eecf2ea7a93139512260e3d46ca2a1afeb341b8bd46b7c30baa6e2c75e25d10",
"packages/ui/components/Select.wrn": "7f046bb11b7c2470dae26d91a4b2261c66a40ae054d0e04c69b6748912d1e204",
"packages/ui/components/Sidebar.wrn": "03f1bbce75ca6d50ed940e62df39c539610e89eadd2064fe24abc7d5470f98bf", "packages/ui/components/Sidebar.wrn": "03f1bbce75ca6d50ed940e62df39c539610e89eadd2064fe24abc7d5470f98bf",
"packages/ui/components/SplitHero.wrn": "70e843565ff869bcf413b11b6d9830b2e2bd229863a00904596f71e2dff0e76d", "packages/ui/components/SplitHero.wrn": "70e843565ff869bcf413b11b6d9830b2e2bd229863a00904596f71e2dff0e76d",
"packages/ui/components/StatsBar.wrn": "c7d1df3895f25b6d3a2e1012a18b97592c7f33e65418b880d486f20c2d490686", "packages/ui/components/StatsBar.wrn": "c7d1df3895f25b6d3a2e1012a18b97592c7f33e65418b880d486f20c2d490686",
"packages/ui/components/Stepper.wrn": "df9093e4222ef3250963a8c6fbfec74477242735d23eabe896c29731c54fc14d", "packages/ui/components/Stepper.wrn": "df9093e4222ef3250963a8c6fbfec74477242735d23eabe896c29731c54fc14d",
"packages/ui/components/StrongPassword.wrn": "c7c5ef26ece6170dd7db9882f0dc98cb2e4607f1e5d43eb3fd39e5d15bbd3a2e", "packages/ui/components/StrongPassword.wrn": "0df060bf01ace77ad0158df489588df75fd5d426dc04f000abacc2b3ddd93fc5",
"packages/ui/components/StyledIcon.wrn": "4a4504e357dee9dd0418edbe85fc90b824ccdb3752123a2125da95b77bf0b336", "packages/ui/components/StyledIcon.wrn": "bf9b41e9c402c4886feddda504c3f12348bbbe49b9003de69d807db4bf52d1b1",
"packages/ui/components/Switch.wrn": "ccb74599fab72b0d68b09a7f1f90b7732cb2f9cbed67a84219e897575ce30c28",
"packages/ui/components/Tabs.wrn": "e38336d28876caa4e97da32a569749e3803b030284a85ce9f35fa788e51cc5b0", "packages/ui/components/Tabs.wrn": "e38336d28876caa4e97da32a569749e3803b030284a85ce9f35fa788e51cc5b0",
"packages/ui/components/TextLink.wrn": "30782039293eb36d63b7b3a4f32a71a47177a3a68c7e90184be7cf7b4385eb19", "packages/ui/components/TextLink.wrn": "6e18906337cd3d30c2a0122593d249bde8f7cedc697b90ae388e2bd0e8eff3e1",
"packages/ui/components/Textarea.wrn": "ddf0b4f124b2cf0c0ab3d820d3ac0085f7c20466e977231949be264cd0cee8cf", "packages/ui/components/TimePicker.wrn": "d2fb7cd156bb2abb997a9e85d27c36b80f4ba81515b523a125e793363568475c",
"packages/ui/components/TimePicker.wrn": "2e8e7a90f6b6069a07e7ffd2725ba1e1031e84d55a4f1254025befbb314fa695", "packages/ui/components/Timeline.wrn": "686a37ebc62732842846d004491336970db94945bca942f226097d863e159d1c",
"packages/ui/components/Timeline.wrn": "aec123bf69f6bfa41c3564e4eac18b2a245214dc6905ebd0bf5b69f7c2736ae6", "packages/ui/components/Toaster.wrn": "288b2b370f65bf823e8d2a6f8c8cf66972332b8174303d33ebe8546d5e1d52c4",
"packages/ui/components/Toast.wrn": "f37c584d1c1a66401deb53d915c8ee0c70aaf7baa1d3c297fa74c5bdb914dd1d", "packages/ui/components/ToggleCount.wrn": "285abf0ed5c7156c5a6f404af0c2519f181b591975515e845cbc3c21349c5cfd",
"packages/ui/components/ToastNotifications.wrn": "33ff76b2a0a129ff896baea8979b4be97f7ec4471a92a2da970403a5c46e8b03", "packages/ui/components/TogglePassword.wrn": "94cd3579bb48e53283c82400bff58ea0f2ca92646b87385502e3b474ba787a96",
"packages/ui/components/Toaster.wrn": "7481ecddde9ed5bf1f448d45a78da7ee0009f84963814bf681735db4e5ab75f9", "packages/ui/components/TreeView.wrn": "c354fce9c5e81fed6eec4089dfd2a7f6bb84eb189c94efc88e00251efdc12c0b",
"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/Typography.wrn": "1b4d1b91dee0ff522d27c566becc3bc49bd9ec979ae6334e6a15fca5d88eccac", "packages/ui/components/Typography.wrn": "1b4d1b91dee0ff522d27c566becc3bc49bd9ec979ae6334e6a15fca5d88eccac",
"packages/ui/components/WysiwygEditor.wrn": "636e60b9f9be7a5807cca7ad20e0b0f370ee1ec6c1ecba5d0a577726cc86bc98", "packages/ui/components/WysiwygEditor.wrn": "231da25f4064b065adb2783252d430aa336585e008547a010a41174a41284524",
"packages/ui/components/alert.wrn": "3f4c4c90c2b5a00a13d0fa1e7e48e4898869dd27a50ff412a51d7bcc87a0baac", "packages/ui/components/alert.wrn": "6dde2cee818a66e1d80df6b93e9f971d5389f95387961593350b0c297e3ed367",
"packages/ui/components/avatar.wrn": "571b70790aff28a5b7d6adbf0196e77ece1ce153a95b384148cb2bb5f74ab2e9", "packages/ui/components/avatar.wrn": "ac2239cc746ec35b81a34ec6e27f4ee74ca04dc6058bad5853aa66640f2a9426",
"packages/ui/components/button.wrn": "647af3918142fd9151e46555345532980633cc3677ec26d0940c253ea789bc31", "packages/ui/components/badge.wrn": "9e1e884e9e2e9d881348c5121f53f4c85b91355cbfa49eeb2bff43f5ab38fab5",
"packages/ui/components/carousel.wrn": "37bdde82de59e90cf02c9fed0ee11f5ddd2979246932701dad9412c4fd358a6a", "packages/ui/components/button.wrn": "da1660c671daf2feffe1024203ac1a8bc947beb07691bef595c1552c664b9cde",
"packages/ui/components/progress.wrn": "ba6f4dfcc00f04f34e9533be675bb4a6200c3cdcd7d03fc597377e48520fdec7", "packages/ui/components/checkbox.wrn": "a5fb8410558696b3c8e63d5cd8dd49b44d51ed35b809cb2b189501e2e7b323e2",
"packages/ui/components/skeleton.wrn": "ceec8af147b8be08155500e378393ac7c72d819da61b62191c076c80e943d5a9", "packages/ui/components/input.wrn": "184066a837982ef3fe44e518730d0cccb7dd6772d5af58116c0774b71cb894e2",
"packages/ui/components/spinner.wrn": "abc4ee3ede2e019289257e9009eee012c880a85839cc220fac6dbe1b780caf52", "packages/ui/components/progress.wrn": "61b01ba575d35691e3dfebfcb03ccd1c6b79f1df1c0d41857ec3b01944081a5d",
"packages/ui/ui.css": "3138b86f269b6f7e67922ef2c56918444d562b11f4ae6f1fab98b63805cd78dc" "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"; "use strict";
// Generated by scripts/build-editor-compiler.mjs. Do not edit directly. // 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 // WRN editor compiler generator hash: c71e7fe4258c97b73b384ff14b321f0cf0b30cc2ed0322f5f84b04e757159b18
// Generated with TypeScript: 5.9.3 // Generated with TypeScript: 5.9.3
const __nodeRequire = require; const __nodeRequire = require;
@@ -558,7 +558,7 @@ function browserModuleRequired(ast) {
const functions = ast.runtimeFunctions.filter((fn) => ["legacy", "client", "shared"].includes(fn.runtime)); const functions = ast.runtimeFunctions.filter((fn) => ["legacy", "client", "shared"].includes(fn.runtime));
return functions.length > 0 || selectedBrowserImports(ast, functions).length > 0; 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 parameterNames = new Set(fn.parameters.map((parameter) => parameter.name));
/* /*
* Names the function body declares for itself. * Names the function body declares for itself.
@@ -608,32 +608,42 @@ function functionEntry(ast, fn, availableFunctions) {
const syncStateFromContext = stateNames const syncStateFromContext = stateNames
.map((name) => `${name} = context.state.${name};`) .map((name) => `${name} = context.state.${name};`)
.join(" "); .join(" ");
const peerAliases = functionAliases const peerAliases = !stateNames.length
.map((name) => { ? functionAliases
const call = `context.functions[${JSON.stringify(name)}](...__wrnexusPeerArgs)`; .map((name) => `const ${name} = (...__wrnexusPeerArgs) => context.functions[${JSON.stringify(name)}](...__wrnexusPeerArgs);`)
if (!stateNames.length) { .join("\n")
return `const ${name} = (...__wrnexusPeerArgs) => ${call};`; : `const __wrnexusFlush = () => { ${syncStateToContext} };
} const __wrnexusRestore = () => { ${syncStateFromContext} };
return `const ${name} = (...__wrnexusPeerArgs) => { const __wrnexusPeer = (name, args) => {
${syncStateToContext} __wrnexusFlush();
let __wrnexusPeerResult; let result;
try { try {
__wrnexusPeerResult = ${call}; result = context.functions[name](...args);
} catch (__wrnexusPeerError) { } catch (error) {
${syncStateFromContext} __wrnexusRestore();
throw __wrnexusPeerError; throw error;
} }
if (__wrnexusPeerResult && typeof __wrnexusPeerResult.then === "function") { if (result && typeof result.then === "function") {
return Promise.resolve(__wrnexusPeerResult).finally(() => { ${syncStateFromContext} }); return Promise.resolve(result).finally(__wrnexusRestore);
} }
${syncStateFromContext} __wrnexusRestore();
return __wrnexusPeerResult; return result;
};`; };
}) ${functionAliases.map((name) => `const ${name} = (...__wrnexusPeerArgs) => __wrnexusPeer(${JSON.stringify(name)}, __wrnexusPeerArgs);`).join("\n")}`;
.join("\n");
const copyBack = stateNames const copyBack = stateNames
.map((name) => `if (!Object.is(${name}, __wrnexusInitialState[${JSON.stringify(name)}])) context.state.${name} = ${name};`) .map((name) => `if (!Object.is(${name}, __wrnexusInitialState[${JSON.stringify(name)}])) context.state.${name} = ${name};`)
.join("\n"); .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 body = (0, syntax_1.eraseFunctionTypes)(fn.body);
const runtimeBindings = [ const runtimeBindings = [
!parameterNames.has("output") ? "const output = context.output;" : "", !parameterNames.has("output") ? "const output = context.output;" : "",
@@ -647,12 +657,13 @@ function functionEntry(ast, fn, availableFunctions) {
${initialStateSnapshot} ${initialStateSnapshot}
${stateAliases} ${stateAliases}
${propAliases} ${propAliases}
${commitBinding}
${peerAliases} ${peerAliases}
${runtimeBindings} ${runtimeBindings}
try { try {
${body} ${body}
} finally { } finally {
${copyBack} ${stateNames.length ? "__wrnexusCommit();" : ""}
} }
}`; }`;
} }
@@ -663,22 +674,74 @@ function generateBrowserModule(ast) {
const selectedImports = selectedBrowserImports(ast, functions); const selectedImports = selectedBrowserImports(ast, functions);
const imports = selectedImports.map((entry) => entry.code).join("\n"); const imports = selectedImports.map((entry) => entry.code).join("\n");
const importedBindings = [...new Set(selectedImports.flatMap((entry) => entry.bindings))]; 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} return `// generated WRNexusJS browser module for ${ast.name}
${imports} ${imports}
export const __wrnexusClientFunctions = { 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 __wrnexusClientState = ${JSON.stringify(state)};
export const __wrnexusOutputs = ${JSON.stringify(ast.outputs)}; export const __wrnexusOutputs = ${JSON.stringify(ast.outputs)};
export const __wrnexusImportedBindings = { ${importedBindings.join(", ")} }; 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 functions = {};
const scopedContext = { ...context, functions }; for (const name of Object.keys(implementations)) {
for (const [name, handler] of Object.entries(__wrnexusClientFunctions)) { functions[name] = (...args) => {
functions[name] = (...args) => handler(scopedContext, ...args); __wrnexusRestore();
return implementations[name](...args);
};
} }
return functions; 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)})}` ? `\${__wireProp(${ctx.resolveExpr(wholeExpression)})}`
: compileAttrValue(attr.value, ctx); : compileAttrValue(attr.value, ctx);
const rendered = ` ${attr.name}="${compiledValue}"`; const rendered = ` ${attr.name}="${compiledValue}"`;
if (wholeExpression || if (!attr.value.includes("{") ||
!attr.value.includes("{") || (!exprRefsState(attr.value, ctx.stateNames) && !exprRefsState(attr.value, ctx.propNames))) {
!exprRefsState(attr.value, ctx.stateNames)) {
return rendered; return rendered;
} }
const marker = attrEscape(JSON.stringify([attr.name, attr.value])); 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(""); .join("");
const loops = loopVarsOf(node); const loops = loopVarsOf(node);
@@ -1392,7 +1454,12 @@ function __wrnexusEscapeHtml(value: unknown): string {
return String(value).replace(/[&<>"']/g, (ch) => __wrnexusHtmlEscapes[ch] ?? ch); 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 = { const adapters = {
cookies: ctx.cookies, cookies: ctx.cookies,
session: ctx.session, 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") { if (typeof ctx.__wrnexusCallApi === "function") {
return await ctx.__wrnexusCallApi(path, method); 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(); 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) { for (const binding of __wrnexusSsrBindings) {
const data = await __wrnexusCallApi(binding.path, binding.method, ctx); const data = await __wrnexusCallApi(binding.path, binding.method, ctx);
const value = __wrnexusEvalData(data, binding.body, binding.helpers, ctx); const value = __wrnexusEvalData(data, binding.body, binding.helpers, ctx);
@@ -1871,14 +1938,14 @@ function generate(ast) {
out.push(ssrRuntimeSource()); out.push(ssrRuntimeSource());
out.push(`const __wrnexusSsrBindings = ${JSON.stringify(ssrBindings, null, 2)};`); out.push(`const __wrnexusSsrBindings = ${JSON.stringify(ssrBindings, null, 2)};`);
const decls = loopConsts.length > 0 ? loopConsts.join("\n") + "\n" : ""; 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} ${storeDeclarations}
${serverLoadAliases} ${serverLoadAliases}
${decls} ${decls}
const __state: ${stateType} = ${dynamicStateInitializer}; const __state: ${stateType} = ${dynamicStateInitializer};
${ssrStateAliases} ${ssrStateAliases}
${ssrComputedAliases} ${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) const __scopeValue = Object.entries(__hydrationState)
.map(([key, value]) => { .map(([key, value]) => {
let encoded: string; let encoded: string;
@@ -1904,13 +1971,13 @@ function generate(ast) {
}`); }`);
} }
else { 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} ${storeDeclarations}
${serverLoadAliases} ${serverLoadAliases}
const __state: ${stateType} = ${dynamicStateInitializer}; const __state: ${stateType} = ${dynamicStateInitializer};
${ssrStateAliases} ${ssrStateAliases}
${ssrComputedAliases} ${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) const __scopeValue = Object.entries(__hydrationState)
.map(([key, value]) => { .map(([key, value]) => {
let encoded: string; let encoded: string;
@@ -1934,14 +2001,14 @@ function generate(ast) {
}`); }`);
} }
if (staticShellBody !== undefined) { 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} ${storeDeclarations}
${serverLoadAliases} ${serverLoadAliases}
${loopConsts.length > 0 ? loopConsts.join("\n") : ""} ${loopConsts.length > 0 ? loopConsts.join("\n") : ""}
const __state: ${stateType} = ${dynamicStateInitializer}; const __state: ${stateType} = ${dynamicStateInitializer};
${ssrStateAliases} ${ssrStateAliases}
${ssrComputedAliases} ${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) const __scopeValue = Object.entries(__hydrationState)
.map(([key, value]) => { .map(([key, value]) => {
let encoded: string; let encoded: string;
@@ -1991,7 +2058,7 @@ function generate(ast) {
}) })
.join("\n"); .join("\n");
const visible = exposed.filter((entry) => entry.name); 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 ${exposed
.filter((entry) => !entry.name) .filter((entry) => !entry.name)
.map((entry) => entry.body) .map((entry) => entry.body)
@@ -2014,7 +2081,7 @@ ${visible.length
out.push(`export async function ${action.name}(${action.args.join(", ")}) {${action.body}}`); out.push(`export async function ${action.name}(${action.args.join(", ")}) {${action.body}}`);
continue; 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 invalidate = (...tags: string[]) => {
const bucket = (ctx.locals.__wrnexusInvalidatedTags ??= []); const bucket = (ctx.locals.__wrnexusInvalidatedTags ??= []);
bucket.push(...tags.flat()); bucket.push(...tags.flat());
@@ -2036,7 +2103,7 @@ ${ast.actions
ast.apis.forEach((api, index) => { ast.apis.forEach((api, index) => {
const name = `__wrnexusApi_${api.method}_${index}`; const name = `__wrnexusApi_${api.method}_${index}`;
out.push(`// ${api.method} ${apiRoutePath(api.path)} 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},`); 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};`); 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) { if (ast.realtimes.length > 0) {
const handlers = ast.realtimes.flatMap((rt) => rt.handlers.map((h) => { const handlers = ast.realtimes.flatMap((rt) => rt.handlers.map((h) => {
const params = ["ws", ...h.args].join(", "); 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};`); out.push(`export const websocket = {\n${handlers.join("\n")}\n};`);
} }
@@ -2178,7 +2245,9 @@ function exprRefsState(expr, stateNames) {
return false; return false;
} }
function exprRefsComponentReactiveValue(expr, ctx) { 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) { function viewHasEvents(nodes) {
return nodes.some((node) => { return nodes.some((node) => {
@@ -2242,6 +2311,9 @@ function compileText(raw, ctx) {
// list renderer fills it per item; it has no server-side value. // list renderer fills it per item; it has no server-side value.
out += escLit(`{${expr}}`); out += escLit(`{${expr}}`);
} }
else if (expr === "content") {
out += `\${__wireRaw(${ctx.resolveExpr(expr)})}`;
}
else if (exprRefsComponentReactiveValue(expr, ctx)) { else if (exprRefsComponentReactiveValue(expr, ctx)) {
// State interpolation: bake the initial value AND keep it reactive via a // 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 // 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)})}` + `\${__wireHtml(${ctx.resolveExpr(expr)})}` +
escLit(`</span>`); escLit(`</span>`);
} }
else if (expr === "content") {
out += `\${__wireRaw(${ctx.resolveExpr(expr)})}`;
}
else { else {
out += `\${__wireHtml(${ctx.resolveExpr(expr)})}`; out += `\${__wireHtml(${ctx.resolveExpr(expr)})}`;
} }
@@ -2593,6 +2662,7 @@ function generateComponent(ast) {
}; };
const ctx = { const ctx = {
stateNames, stateNames,
propNames: new Set(effectiveProps.map((entry) => entry.name)),
functionNames: new Set(ast.runtimeFunctions.filter((fn) => fn.runtime !== "server").map((fn) => fn.name)), functionNames: new Set(ast.runtimeFunctions.filter((fn) => fn.runtime !== "server").map((fn) => fn.name)),
resolveExpr, resolveExpr,
eventNames: publicOutputNames(ast), eventNames: publicOutputNames(ast),
@@ -2610,12 +2680,12 @@ function generateComponent(ast) {
.join(""); .join("");
const styles = ast.styles.map((body) => body.trim()).filter(Boolean); const styles = ast.styles.map((body) => body.trim()).filter(Boolean);
const styleTag = escLit(localStyleTag(ast, styles)); const styleTag = escLit(localStyleTag(ast, styles));
// A component needs a reactive scope only when it has state or event handlers. // Props remain server-rendered and also become signals so a parent can drive
// Prop-driven text/attributes are baked server-side, so static components ship // a mounted child after hydration.
// no JavaScript at all.
const behavior = componentBehavior(ast); const behavior = componentBehavior(ast);
const needsScope = ast.runtime !== "server" && const needsScope = ast.runtime !== "server" &&
(browserStates.length > 0 || (effectiveProps.length > 0 ||
browserStates.length > 0 ||
ast.computed.length > 0 || ast.computed.length > 0 ||
viewHasEvents(ast.view) || viewHasEvents(ast.view) ||
behavior !== null); behavior !== null);
@@ -2632,7 +2702,7 @@ function generateComponent(ast) {
if (prop.required) { 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(` 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")) { if (!effectiveProps.some((prop) => prop.name === "attrs")) {
decls.push(` const __attrs = __restProps(__p, new Set(${JSON.stringify(effectiveProps.map((prop) => prop.name))}));`); 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;`) .map((output) => ` ${JSON.stringify(output.name)}(${output.payload ? `${output.payload.name}${output.payload.optional ? "?" : ""}: ${output.payload.valueType}` : ""}): void;`)
.join("\n")}\n}`); .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) { if (v === undefined || v === null) {
return def; return def;
} }
@@ -2722,14 +2792,14 @@ function generateComponent(ast) {
if (typeof v === "string") { if (typeof v === "string") {
try { try {
const parsed = JSON.parse(v); 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 { } catch {
if (declared === "array") throw new TypeError("Expected an array prop"); throw new TypeError("Expected an array prop '" + propName + "'");
return def;
} }
} }
return def; throw new TypeError("Expected an array prop '" + propName + "'");
} }
if (declared === "object" || (def !== null && typeof def === "object")) { if (declared === "object" || (def !== null && typeof def === "object")) {
@@ -2745,23 +2815,19 @@ function generateComponent(ast) {
try { try {
const parsed = JSON.parse(v); const parsed = JSON.parse(v);
return ( if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
parsed !== null && throw new TypeError("Expected an object prop '" + propName + "'");
typeof parsed === "object" && }
!Array.isArray(parsed) return parsed;
)
? parsed
: def;
} catch { } catch {
if (declared === "object") throw new TypeError("Expected an object prop"); throw new TypeError("Expected an object prop '" + propName + "'");
return def;
} }
} }
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") { if (declared === "function" && typeof v !== "function") {
throw new TypeError("Expected a function prop"); throw new TypeError("Expected a function prop");
} }
@@ -2769,15 +2835,15 @@ function generateComponent(ast) {
} }
function __restProps( function __restProps(
props: Record<string, any>, props: Record<string, unknown>,
declared: Set<string>, declared: Set<string>,
): Record<string, any> { ): Record<string, unknown> {
return Object.fromEntries( return Object.fromEntries(
Object.entries(props).filter(([name]) => !declared.has(name)), Object.entries(props).filter(([name]) => !declared.has(name)),
); );
} }
function __wireHtml(v: any): string { function __wireHtml(v: unknown): string {
return String(v == null ? "" : v).replace( return String(v == null ? "" : v).replace(
/[&<>]/g, /[&<>]/g,
(c) => (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( return String(v == null ? "" : v).replace(
/[&<>"]/g, /[&<>"]/g,
(c) => (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 || return value === true ||
value === "true" || value === "true" ||
value === "" || 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 ""; if (value === null || typeof value !== "object" || Array.isArray(value)) return "";
const booleanAttributes = new Set(${JSON.stringify([...HTML_BOOLEAN_ATTRIBUTES])}); const booleanAttributes = new Set(${JSON.stringify([...HTML_BOOLEAN_ATTRIBUTES])});
@@ -2828,10 +2894,12 @@ function __wireSpreadAttrs(value: any): string {
lowerName === "style" || lowerName === "style" ||
lowerName === "slot" || lowerName === "slot" ||
lowerName === "data-component" || lowerName === "data-component" ||
// Internal markers must not leak through a spread -- except the // Internal markers must not leak through a spread. Parent-owned output
// parent's output handlers, whose whole job is to ride from the mount // and prop bindings ride from the mount onto the
// onto the view root so the mounting scope can bind them there. // rendered child root so the mounting scope can bind them there.
(lowerName.startsWith("data-wrn") && !lowerName.startsWith("data-wrn-out-")) (lowerName.startsWith("data-wrn") &&
!lowerName.startsWith("data-wrn-out-") &&
!lowerName.startsWith("data-wrn-prop-bind-"))
) { ) {
continue; continue;
} }
@@ -2848,7 +2916,7 @@ function __wireSpreadAttrs(value: any): string {
return attributes.join(""); return attributes.join("");
} }
function __wireProp(v: any): string { function __wireProp(v: unknown): string {
const value = const value =
v !== null && typeof v === "object" v !== null && typeof v === "object"
? JSON.stringify(v) ? JSON.stringify(v)
@@ -2857,16 +2925,16 @@ function __wireProp(v: any): string {
return __wireAttr(value); return __wireAttr(value);
} }
function __wireRaw(v: any): string { function __wireRaw(v: unknown): string {
return String(v == null ? "" : v); return String(v == null ? "" : v);
}`); }`);
if (hasServerEach) { 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"); return __WrnexusBuffer.from(JSON.stringify(value), "utf8").toString("base64");
}`); }`);
} }
if (needsScope) { if (needsScope) {
out.push(`function __wrnexusSerializeScopeValue(value: any): string { out.push(`function __wrnexusSerializeScopeValue(value: unknown): string {
if (value === undefined) { if (value === undefined) {
return "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) return Object.keys(obj)
.map( .map(
(key) => (key) =>
@@ -2918,7 +2986,7 @@ function __wireRaw(v: any): string {
}`); }`);
} }
const serverFunctionSource = serverFunctions ? `${serverFunctions}\n` : ""; 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` + ` const __p = props || {};\n` +
(decls.length > 0 ? decls.join("\n") + "\n" : "") + (decls.length > 0 ? decls.join("\n") + "\n" : "") +
serverFunctionSource + serverFunctionSource +
@@ -5634,7 +5702,12 @@ function parse(source) {
// props { name: Type = <default>; @event name = function } // props { name: Type = <default>; @event name = function }
lx.next(); lx.next();
expect("lbrace"); 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(); const t = lx.peek();
if (t.type === "eof") if (t.type === "eof")
throw new ParseError("Unexpected end of input inside props"); throw new ParseError("Unexpected end of input inside props");
@@ -6013,6 +6086,9 @@ function parse(source) {
} }
expect("rbrace"); expect("rbrace");
const declaredStates = new Set(states.map((state) => state.name)); 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) { for (const watcher of watches) {
if (!declaredStates.has(watcher.state)) { if (!declaredStates.has(watcher.state)) {
throw new ParseError(`Cannot watch undeclared state '${watcher.state}'`); throw new ParseError(`Cannot watch undeclared state '${watcher.state}'`);
@@ -6543,6 +6619,11 @@ class Lexer {
break; 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. */ /** Read and consume the next structural token. */
next() { next() {
this.skipTrivia(); 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 // WRN editor extension generator hash: 456d1d614e44e5fb1f19b784176c09cf2ade9b64ef73a17934c2698150b62728
"use strict"; "use strict";
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports); 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)); const functions = ast.runtimeFunctions.filter((fn) => ["legacy", "client", "shared"].includes(fn.runtime));
return functions.length > 0 || selectedBrowserImports(ast, functions).length > 0; 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 parameterNames = new Set(fn.parameters.map((parameter) => parameter.name));
const declaredLocals = new Set; 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)) { 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 propAliases = propNames.length ? `const { ${propNames.join(", ")} } = context.props;` : "";
const syncStateToContext = stateNames.map((name) => `context.state.${name} = ${name};`).join(" "); const syncStateToContext = stateNames.map((name) => `context.state.${name} = ${name};`).join(" ");
const syncStateFromContext = stateNames.map((name) => `${name} = context.state.${name};`).join(" "); const syncStateFromContext = stateNames.map((name) => `${name} = context.state.${name};`).join(" ");
const peerAliases = functionAliases.map((name) => { const peerAliases = !stateNames.length ? functionAliases.map((name) => `const ${name} = (...__wrnexusPeerArgs) => context.functions[${JSON.stringify(name)}](...__wrnexusPeerArgs);`).join(`
const call = `context.functions[${JSON.stringify(name)}](...__wrnexusPeerArgs)`; `) : `const __wrnexusFlush = () => { ${syncStateToContext} };
if (!stateNames.length) { const __wrnexusRestore = () => { ${syncStateFromContext} };
return `const ${name} = (...__wrnexusPeerArgs) => ${call};`; const __wrnexusPeer = (name, args) => {
} __wrnexusFlush();
return `const ${name} = (...__wrnexusPeerArgs) => { let result;
${syncStateToContext}
let __wrnexusPeerResult;
try { try {
__wrnexusPeerResult = ${call}; result = context.functions[name](...args);
} catch (__wrnexusPeerError) { } catch (error) {
${syncStateFromContext} __wrnexusRestore();
throw __wrnexusPeerError; throw error;
} }
if (__wrnexusPeerResult && typeof __wrnexusPeerResult.then === "function") { if (result && typeof result.then === "function") {
return Promise.resolve(__wrnexusPeerResult).finally(() => { ${syncStateFromContext} }); return Promise.resolve(result).finally(__wrnexusRestore);
} }
${syncStateFromContext} __wrnexusRestore();
return __wrnexusPeerResult; return result;
};`; };
}).join(` ${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 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 body = (0, syntax_1.eraseFunctionTypes)(fn.body);
const runtimeBindings = [ const runtimeBindings = [
!parameterNames.has("output") ? "const output = context.output;" : "", !parameterNames.has("output") ? "const output = context.output;" : "",
@@ -23256,12 +23259,13 @@ ${(0, codegen_ts_1.generate)(ast)}`,
${initialStateSnapshot} ${initialStateSnapshot}
${stateAliases} ${stateAliases}
${propAliases} ${propAliases}
${commitBinding}
${peerAliases} ${peerAliases}
${runtimeBindings} ${runtimeBindings}
try { try {
${body} ${body}
} finally { } finally {
${copyBack} ${stateNames.length ? "__wrnexusCommit();" : ""}
} }
}`; }`;
} }
@@ -23273,23 +23277,66 @@ ${(0, codegen_ts_1.generate)(ast)}`,
const imports = selectedImports.map((entry) => entry.code).join(` const imports = selectedImports.map((entry) => entry.code).join(`
`); `);
const importedBindings = [...new Set(selectedImports.flatMap((entry) => entry.bindings))]; 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} return `// generated WRNexusJS browser module for ${ast.name}
${imports} ${imports}
export const __wrnexusClientFunctions = { 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 __wrnexusClientState = ${JSON.stringify(state)};
export const __wrnexusOutputs = ${JSON.stringify(ast.outputs)}; export const __wrnexusOutputs = ${JSON.stringify(ast.outputs)};
export const __wrnexusImportedBindings = { ${importedBindings.join(", ")} }; 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 functions = {};
const scopedContext = { ...context, functions }; for (const name of Object.keys(implementations)) {
for (const [name, handler] of Object.entries(__wrnexusClientFunctions)) { functions[name] = (...args) => {
functions[name] = (...args) => handler(scopedContext, ...args); __wrnexusRestore();
return implementations[name](...args);
};
} }
return functions; 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 wholeExpression = wholeAttributeExpression(attr.value);
const compiledValue = wholeExpression ? `\${__wireProp(${ctx.resolveExpr(wholeExpression)})}` : compileAttrValue(attr.value, ctx); const compiledValue = wholeExpression ? `\${__wireProp(${ctx.resolveExpr(wholeExpression)})}` : compileAttrValue(attr.value, ctx);
const rendered = ` ${attr.name}="${compiledValue}"`; 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; return rendered;
} }
const marker = attrEscape(JSON.stringify([attr.name, attr.value])); 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(""); }).join("");
const loops = loopVarsOf(node); const loops = loopVarsOf(node);
const childCtx = loops.length > 0 ? { const childCtx = loops.length > 0 ? {
@@ -23816,7 +23863,12 @@ function __wrnexusEscapeHtml(value: unknown): string {
return String(value).replace(/[&<>"']/g, (ch) => __wrnexusHtmlEscapes[ch] ?? ch); 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 = { const adapters = {
cookies: ctx.cookies, cookies: ctx.cookies,
session: ctx.session, 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") { if (typeof ctx.__wrnexusCallApi === "function") {
return await ctx.__wrnexusCallApi(path, method); 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(); 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) { for (const binding of __wrnexusSsrBindings) {
const data = await __wrnexusCallApi(binding.path, binding.method, ctx); const data = await __wrnexusCallApi(binding.path, binding.method, ctx);
const value = __wrnexusEvalData(data, binding.body, binding.helpers, ctx); const value = __wrnexusEvalData(data, binding.body, binding.helpers, ctx);
@@ -24253,14 +24305,14 @@ ${helpers}`);
const decls = loopConsts.length > 0 ? loopConsts.join(` 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} ${storeDeclarations}
${serverLoadAliases} ${serverLoadAliases}
${decls} ${decls}
const __state: ${stateType} = ${dynamicStateInitializer}; const __state: ${stateType} = ${dynamicStateInitializer};
${ssrStateAliases} ${ssrStateAliases}
${ssrComputedAliases} ${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) const __scopeValue = Object.entries(__hydrationState)
.map(([key, value]) => { .map(([key, value]) => {
let encoded: string; let encoded: string;
@@ -24285,13 +24337,13 @@ ${helpers}`);
return await __wrnexusRenderSsrBindings(html, ctx); return await __wrnexusRenderSsrBindings(html, ctx);
}`); }`);
} else { } 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} ${storeDeclarations}
${serverLoadAliases} ${serverLoadAliases}
const __state: ${stateType} = ${dynamicStateInitializer}; const __state: ${stateType} = ${dynamicStateInitializer};
${ssrStateAliases} ${ssrStateAliases}
${ssrComputedAliases} ${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) const __scopeValue = Object.entries(__hydrationState)
.map(([key, value]) => { .map(([key, value]) => {
let encoded: string; let encoded: string;
@@ -24315,7 +24367,7 @@ ${helpers}`);
}`); }`);
} }
if (staticShellBody !== undefined) { 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} ${storeDeclarations}
${serverLoadAliases} ${serverLoadAliases}
${loopConsts.length > 0 ? loopConsts.join(` ${loopConsts.length > 0 ? loopConsts.join(`
@@ -24323,7 +24375,7 @@ ${helpers}`);
const __state: ${stateType} = ${dynamicStateInitializer}; const __state: ${stateType} = ${dynamicStateInitializer};
${ssrStateAliases} ${ssrStateAliases}
${ssrComputedAliases} ${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) const __scopeValue = Object.entries(__hydrationState)
.map(([key, value]) => { .map(([key, value]) => {
let encoded: string; let encoded: string;
@@ -24370,7 +24422,7 @@ ${helpers}`);
}).join(` }).join(`
`); `);
const visible = exposed.filter((entry) => entry.name); 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(` ${exposed.filter((entry) => !entry.name).map((entry) => entry.body).join(`
`)} `)}
${declarations} ${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}}`); out.push(`export async function ${action.name}(${action.args.join(", ")}) {${action.body}}`);
continue; 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 invalidate = (...tags: string[]) => {
const bucket = (ctx.locals.__wrnexusInvalidatedTags ??= []); const bucket = (ctx.locals.__wrnexusInvalidatedTags ??= []);
bucket.push(...tags.flat()); bucket.push(...tags.flat());
@@ -24407,7 +24459,7 @@ ${ast.actions.map((action) => ` ${action.name}: createActionClient<${action.sch
ast.apis.forEach((api, index) => { ast.apis.forEach((api, index) => {
const name = `__wrnexusApi_${api.method}_${index}`; const name = `__wrnexusApi_${api.method}_${index}`;
out.push(`// ${api.method} ${apiRoutePath(api.path)} 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},`); const entries = ast.apis.map((api, index) => ` ${JSON.stringify(`${api.method} ${apiRoutePath(api.path)}`)}: __wrnexusApi_${api.method}_${index},`);
out.push(`export const __wrnexusApi = { out.push(`export const __wrnexusApi = {
@@ -24425,7 +24477,7 @@ ${entries.join(`
if (ast.realtimes.length > 0) { if (ast.realtimes.length > 0) {
const handlers = ast.realtimes.flatMap((rt) => rt.handlers.map((h) => { const handlers = ast.realtimes.flatMap((rt) => rt.handlers.map((h) => {
const params = ["ws", ...h.args].join(", "); 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 = { out.push(`export const websocket = {
${handlers.join(` ${handlers.join(`
@@ -24547,7 +24599,7 @@ ${handlers.join(`
return false; return false;
} }
function exprRefsComponentReactiveValue(expr, ctx) { 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) { function viewHasEvents(nodes) {
return nodes.some((node) => { return nodes.some((node) => {
@@ -24599,10 +24651,10 @@ ${handlers.join(`
out += escLit(`<span data-t="${attrEscape(expr.slice(2).trim())}"></span>`); out += escLit(`<span data-t="${attrEscape(expr.slice(2).trim())}"></span>`);
} else if (ctx.loopVars && exprRefsState(expr, ctx.loopVars)) { } else if (ctx.loopVars && exprRefsState(expr, ctx.loopVars)) {
out += escLit(`{${expr}}`); 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") { } else if (expr === "content") {
out += `\${__wireRaw(${ctx.resolveExpr(expr)})}`; out += `\${__wireRaw(${ctx.resolveExpr(expr)})}`;
} else if (exprRefsComponentReactiveValue(expr, ctx)) {
out += escLit(`<span data-text="${attrEscape(expr)}">`) + `\${__wireHtml(${ctx.resolveExpr(expr)})}` + escLit(`</span>`);
} else { } else {
out += `\${__wireHtml(${ctx.resolveExpr(expr)})}`; out += `\${__wireHtml(${ctx.resolveExpr(expr)})}`;
} }
@@ -24855,6 +24907,7 @@ ${handlers.join(`
}; };
const ctx = { const ctx = {
stateNames, stateNames,
propNames: new Set(effectiveProps.map((entry) => entry.name)),
functionNames: new Set(ast.runtimeFunctions.filter((fn) => fn.runtime !== "server").map((fn) => fn.name)), functionNames: new Set(ast.runtimeFunctions.filter((fn) => fn.runtime !== "server").map((fn) => fn.name)),
resolveExpr, resolveExpr,
eventNames: publicOutputNames(ast) eventNames: publicOutputNames(ast)
@@ -24867,7 +24920,7 @@ ${handlers.join(`
const styles = ast.styles.map((body) => body.trim()).filter(Boolean); const styles = ast.styles.map((body) => body.trim()).filter(Boolean);
const styleTag = escLit(localStyleTag(ast, styles)); const styleTag = escLit(localStyleTag(ast, styles));
const behavior = componentBehavior(ast); 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) { if (hasServerEach || needsScope) {
out.push(`import { Buffer as __WrnexusBuffer } from "node:buffer";`); out.push(`import { Buffer as __WrnexusBuffer } from "node:buffer";`);
} }
@@ -24881,7 +24934,7 @@ ${handlers.join(`
if (prop.required) { 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(` 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")) { if (!effectiveProps.some((prop) => prop.name === "attrs")) {
decls.push(` const __attrs = __restProps(__p, new Set(${JSON.stringify(effectiveProps.map((prop) => prop.name))}));`); 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) { if (v === undefined || v === null) {
return def; return def;
} }
@@ -24966,14 +25019,14 @@ ${ast.outputs.map((output) => ` ${JSON.stringify(output.name)}(${output.payload
if (typeof v === "string") { if (typeof v === "string") {
try { try {
const parsed = JSON.parse(v); 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 { } catch {
if (declared === "array") throw new TypeError("Expected an array prop"); throw new TypeError("Expected an array prop '" + propName + "'");
return def;
} }
} }
return def; throw new TypeError("Expected an array prop '" + propName + "'");
} }
if (declared === "object" || (def !== null && typeof def === "object")) { if (declared === "object" || (def !== null && typeof def === "object")) {
@@ -24989,23 +25042,19 @@ ${ast.outputs.map((output) => ` ${JSON.stringify(output.name)}(${output.payload
try { try {
const parsed = JSON.parse(v); const parsed = JSON.parse(v);
return ( if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
parsed !== null && throw new TypeError("Expected an object prop '" + propName + "'");
typeof parsed === "object" && }
!Array.isArray(parsed) return parsed;
)
? parsed
: def;
} catch { } catch {
if (declared === "object") throw new TypeError("Expected an object prop"); throw new TypeError("Expected an object prop '" + propName + "'");
return def;
} }
} }
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") { if (declared === "function" && typeof v !== "function") {
throw new TypeError("Expected a function prop"); throw new TypeError("Expected a function prop");
} }
@@ -25013,15 +25062,15 @@ ${ast.outputs.map((output) => ` ${JSON.stringify(output.name)}(${output.payload
} }
function __restProps( function __restProps(
props: Record<string, any>, props: Record<string, unknown>,
declared: Set<string>, declared: Set<string>,
): Record<string, any> { ): Record<string, unknown> {
return Object.fromEntries( return Object.fromEntries(
Object.entries(props).filter(([name]) => !declared.has(name)), Object.entries(props).filter(([name]) => !declared.has(name)),
); );
} }
function __wireHtml(v: any): string { function __wireHtml(v: unknown): string {
return String(v == null ? "" : v).replace( return String(v == null ? "" : v).replace(
/[&<>]/g, /[&<>]/g,
(c) => (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( return String(v == null ? "" : v).replace(
/[&<>"]/g, /[&<>"]/g,
(c) => (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 || return value === true ||
value === "true" || value === "true" ||
value === "" || 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 ""; if (value === null || typeof value !== "object" || Array.isArray(value)) return "";
const booleanAttributes = new Set(${JSON.stringify([...HTML_BOOLEAN_ATTRIBUTES])}); const booleanAttributes = new Set(${JSON.stringify([...HTML_BOOLEAN_ATTRIBUTES])});
@@ -25072,10 +25121,12 @@ function __wireSpreadAttrs(value: any): string {
lowerName === "style" || lowerName === "style" ||
lowerName === "slot" || lowerName === "slot" ||
lowerName === "data-component" || lowerName === "data-component" ||
// Internal markers must not leak through a spread -- except the // Internal markers must not leak through a spread. Parent-owned output
// parent's output handlers, whose whole job is to ride from the mount // and prop bindings ride from the mount onto the
// onto the view root so the mounting scope can bind them there. // rendered child root so the mounting scope can bind them there.
(lowerName.startsWith("data-wrn") && !lowerName.startsWith("data-wrn-out-")) (lowerName.startsWith("data-wrn") &&
!lowerName.startsWith("data-wrn-out-") &&
!lowerName.startsWith("data-wrn-prop-bind-"))
) { ) {
continue; continue;
} }
@@ -25092,7 +25143,7 @@ function __wireSpreadAttrs(value: any): string {
return attributes.join(""); return attributes.join("");
} }
function __wireProp(v: any): string { function __wireProp(v: unknown): string {
const value = const value =
v !== null && typeof v === "object" v !== null && typeof v === "object"
? JSON.stringify(v) ? JSON.stringify(v)
@@ -25101,16 +25152,16 @@ function __wireProp(v: any): string {
return __wireAttr(value); return __wireAttr(value);
} }
function __wireRaw(v: any): string { function __wireRaw(v: unknown): string {
return String(v == null ? "" : v); return String(v == null ? "" : v);
}`); }`);
if (hasServerEach) { 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"); return __WrnexusBuffer.from(JSON.stringify(value), "utf8").toString("base64");
}`); }`);
} }
if (needsScope) { if (needsScope) {
out.push(`function __wrnexusSerializeScopeValue(value: any): string { out.push(`function __wrnexusSerializeScopeValue(value: unknown): string {
if (value === undefined) { if (value === undefined) {
return "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) return Object.keys(obj)
.map( .map(
(key) => (key) =>
@@ -25163,7 +25214,7 @@ function __wireRaw(v: any): string {
} }
const serverFunctionSource = serverFunctions ? `${serverFunctions} 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 || {}; ` + ` const __p = props || {};
` + (decls.length > 0 ? decls.join(` ` + (decls.length > 0 ? decls.join(`
`) + ` `) + `
@@ -27916,7 +27967,12 @@ ${serverFunctions}
case "props": { case "props": {
lx.next(); lx.next();
expect("lbrace"); 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(); const t = lx.peek();
if (t.type === "eof") if (t.type === "eof")
throw new ParseError("Unexpected end of input inside props"); throw new ParseError("Unexpected end of input inside props");
@@ -28275,6 +28331,9 @@ ${serverFunctions}
} }
expect("rbrace"); expect("rbrace");
const declaredStates = new Set(states.map((state) => state.name)); 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) { for (const watcher of watches) {
if (!declaredStates.has(watcher.state)) { if (!declaredStates.has(watcher.state)) {
throw new ParseError(`Cannot watch undeclared state '${watcher.state}'`); throw new ParseError(`Cannot watch undeclared state '${watcher.state}'`);
@@ -28748,6 +28807,10 @@ ${serverFunctions}
break; break;
} }
} }
startsWithBlockComment() {
this.skipTrivia();
return this.src[this.pos] === "/" && this.src[this.pos + 1] === "*";
}
next() { next() {
this.skipTrivia(); this.skipTrivia();
const { src } = this; const { src } = this;
+14 -2
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env node #!/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 // WRN editor language server generator hash: f593a44aaf05495b789ce7a3086bee1eebb951b884d41c0e017bbcfe5f547e72
// @bun @bun-cjs // @bun @bun-cjs
(function(exports, require, module, __filename, __dirname) {var __create = Object.create; (function(exports, require, module, __filename, __dirname) {var __create = Object.create;
@@ -168786,6 +168786,10 @@ class Lexer {
break; break;
} }
} }
startsWithBlockComment() {
this.skipTrivia();
return this.src[this.pos] === "/" && this.src[this.pos + 1] === "*";
}
next() { next() {
this.skipTrivia(); this.skipTrivia();
const { src } = this; const { src } = this;
@@ -170521,7 +170525,12 @@ function parse(source) {
case "props": { case "props": {
lx.next(); lx.next();
expect("lbrace"); 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(); const t = lx.peek();
if (t.type === "eof") if (t.type === "eof")
throw new ParseError("Unexpected end of input inside props"); throw new ParseError("Unexpected end of input inside props");
@@ -170880,6 +170889,9 @@ function parse(source) {
} }
expect("rbrace"); expect("rbrace");
const declaredStates = new Set(states.map((state) => state.name)); 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) { for (const watcher of watches) {
if (!declaredStates.has(watcher.state)) { if (!declaredStates.has(watcher.state)) {
throw new ParseError(`Cannot watch undeclared state '${watcher.state}'`); throw new ParseError(`Cannot watch undeclared state '${watcher.state}'`);
+1
View File
@@ -13,6 +13,7 @@ export default tseslint.config(
"**/dist/**", "**/dist/**",
".publish/**", ".publish/**",
"**/.wrnexus/**", "**/.wrnexus/**",
"**/.wrnexus-*/**",
"coverage/**", "coverage/**",
"bun.lock", "bun.lock",
"bun.lockb", "bun.lockb",
+2
View File
@@ -1,6 +1,8 @@
node_modules/ node_modules/
dist/ dist/
.wrnexus/ .wrnexus/
.wrnexus-*/
**/.wrnexus-*/
.wirefw/ .wirefw/
**/.wirefw/ **/.wirefw/
**/*.gen.ts **/*.gen.ts
+29 -1
View File
@@ -60,10 +60,27 @@ describe("full app", () => {
}); });
afterAll(() => app?.close()); 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("/"); const res = await app.fetch("/");
expect(res.status).toBe(200); expect(res.status).toBe(200);
expect(res.headers.get("content-type")).toContain("text/html"); 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 () => { test("GET /api/hello returns a translated greeting", async () => {
@@ -94,6 +111,17 @@ describe("full app", () => {
expect(html).not.toContain("Card title"); 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 () => { test("platform showcase uses the public layout and styled reactive primitives", async () => {
const res = await app.fetch("/platform-showcase"); const res = await app.fetch("/platform-showcase");
expect(res.status).toBe(200); 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 // Modal demo page. Route: /modal
// //
// Five real-world use cases for the shared @wrnexus/ui Modal component — each // 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 — // this page. Any client expression can raise one with the runtime global —
// toast("..."), toast.success(...), toast.error(...) — so pages never // toast("..."), toast.success(...), toast.error(...) — so pages never
// hand-roll a status banner and never reach for a blocking alert(). // 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 { page ModalPage {
state noteText = "Remember to ship the release notes" 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 // Component event test page. Route: /test
// //
// Events bubble from the exact ComboBox or AdvancedSelect that emitted them. // Events bubble from the exact ComboBox or AdvancedSelect that emitted them.
+5 -4
View File
@@ -13,15 +13,14 @@ declare namespace WRNexusGenerated {
: never; : never;
type RealtimeMessage<T> = T extends import("@wrnexus/core").RoomDefinition<any, infer M> ? M : unknown; 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 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 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/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 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 RealtimeRoute = "/realtime/chat" | "/realtime/hello";
type EnvironmentKey = "APP_LABEL" | "DATABASE_URL" | "DEMO_SHARED" | "HOST" | "NODE_ENV" | "PORT" | "SESSION_SECRET" | "UAT_ONLY"; 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 QueueName = "welcome-email";
type CacheKey = "users"; type CacheKey = "users";
interface Components { interface Components {
"Modal": { props: Record<string, never>; outputs: Record<string, never> };
"Counter": { props: { "start"?: unknown; "label"?: unknown }; outputs: Record<string, never> }; "Counter": { props: { "start"?: unknown; "label"?: unknown }; outputs: Record<string, never> };
} }
interface ApiContracts { interface ApiContracts {
@@ -30,6 +29,8 @@ declare namespace WRNexusGenerated {
"/api/users/ssr": { GET: ApiContract<typeof import("../api/users/ssr.ts")["GET"]> }; "/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/graphql-example": { POST: ApiContract<typeof import("../api/graphql-example.ts")["POST"]> };
"/api/typed-user": { POST: ApiContract<typeof import("../api/typed-user.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/logout": { POST: ApiContract<typeof import("../api/logout.ts")["POST"]> };
"/api/hello": { GET: ApiContract<typeof import("../api/hello.ts")["GET"]> }; "/api/hello": { GET: ApiContract<typeof import("../api/hello.ts")["GET"]> };
"/api/login": { POST: ApiContract<typeof import("../api/login.ts")["POST"]> }; "/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( export default tseslint.config(
{ {
ignores: ["node_modules/**", "dist/**", ".wrnexus/**", "**/.wrnexus/**"], ignores: [
"node_modules/**",
"dist/**",
".wrnexus/**",
"**/.wrnexus/**",
".wrnexus-*/**",
"**/.wrnexus-*/**",
],
}, },
{ {
languageOptions: { languageOptions: {
+2 -1
View File
@@ -3,5 +3,6 @@
"compilerOptions": { "compilerOptions": {
"lib": ["ESNext", "DOM", "DOM.Iterable"] "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"> <section class="docs-directory-group">
<a class="docs-directory-heading" href="/advanced-forms">Advanced Forms</a> <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>
<section class="docs-directory-group"> <section class="docs-directory-group">
<a class="docs-directory-heading" href="/base">Base</a> <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>
<section class="docs-directory-group"> <section class="docs-directory-group">
<a class="docs-directory-heading" href="/core">Core</a> <a class="docs-directory-heading" href="/core">Core</a>
@@ -51,7 +51,7 @@ layout Showcase {
</section> </section>
<section class="docs-directory-group"> <section class="docs-directory-group">
<a class="docs-directory-heading" href="/integrations">Integrations</a> <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>
<section class="docs-directory-group"> <section class="docs-directory-group">
<a class="docs-directory-heading" href="/layout">Layout</a> <a class="docs-directory-heading" href="/layout">Layout</a>
@@ -3,14 +3,14 @@ page AdvancedFormsShowcase {
layout = "showcase" layout = "showcase"
seo { seo {
title = "Advanced Forms components" title = "Advanced Forms components"
description = "8 advanced-forms components from @wrnexus/ui." description = "7 advanced-forms components from @wrnexus/ui."
} }
view { view {
<header class="showcase-hero showcase-hero--category"> <header class="showcase-hero showcase-hero--category">
<span class="showcase-eyebrow">Component category</span> <span class="showcase-eyebrow">Component category</span>
<h1 class="showcase-title">Advanced Forms</h1> <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> <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>8 components</span><span>Multiple use cases</span><span>112 live configurations</span></div> <div class="category-meta"><span>7 components</span><span>Multiple use cases</span><span>109 live configurations</span></div>
</header> </header>
<div class="catalog-grid"> <div class="catalog-grid">
<article class="catalog-card" data-interactive-preview="false"> <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 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> </div>
</article> </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"> <article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview"> <div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div> <div class="catalog-card-glow" aria-hidden="true"></div>
+5 -15
View File
@@ -3,14 +3,14 @@ page BaseShowcase {
layout = "showcase" layout = "showcase"
seo { seo {
title = "Base components" title = "Base components"
description = "27 base components from @wrnexus/ui." description = "26 base components from @wrnexus/ui."
} }
view { view {
<header class="showcase-hero showcase-hero--category"> <header class="showcase-hero showcase-hero--category">
<span class="showcase-eyebrow">Component category</span> <span class="showcase-eyebrow">Component category</span>
<h1 class="showcase-title">Base</h1> <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> <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>27 components</span><span>Multiple use cases</span><span>86 live configurations</span></div> <div class="category-meta"><span>26 components</span><span>Multiple use cases</span><span>83 live configurations</span></div>
</header> </header>
<div class="catalog-grid"> <div class="catalog-grid">
<article class="catalog-card" data-interactive-preview="false"> <article class="catalog-card" data-interactive-preview="false">
@@ -266,21 +266,11 @@ page BaseShowcase {
<article class="catalog-card" data-interactive-preview="false"> <article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview"> <div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div> <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 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>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> </div>
<div class="catalog-card-body"> <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><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> </div>
</article></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. // Generated by scripts/generate-showcase.mjs. Do not edit directly.
page ChartDetail { page ChartDetail {
layout = "showcase" 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_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_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_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" state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
seo { seo {
title = "Chart" title = "Chart"
@@ -21,7 +26,7 @@ page ChartDetail {
<span class="showcase-eyebrow">Integrations component</span> <span class="showcase-eyebrow">Integrations component</span>
<h1>Chart</h1> <h1>Chart</h1>
<p>Theme-aware, responsive chart component.</p> <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>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div> <div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header> </header>
@@ -30,7 +35,7 @@ page ChartDetail {
<div class="playground-workbench"> <div class="playground-workbench">
<div class="playground-preview"> <div class="playground-preview">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <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"> <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> <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 <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 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> </div>
<form class="playground-controls" data-playground-form> <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> <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-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>[ <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; &#123;
"id": "chart-0-1", "id": "chart-0-1",
"key": "chart-0-1", "key": "chart-0-1",
@@ -258,7 +263,7 @@ page ChartDetail {
"Standard" "Standard"
] ]
&#125; &#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> </form>
</div> </div>
</section> </section>
@@ -274,7 +279,7 @@ page ChartDetail {
<div id="chart-demo-1" class="demo-canvas demo-canvas--1"> <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-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> </div>
<section class="demo-code" aria-label="Production default usage"> <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> <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 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-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> </div>
<section class="demo-code" aria-label="Compact application usage"> <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> <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 <pre><code>&lt;Chart
size="sm"
title="Compact Chart" title="Compact Chart"
description="A compact configuration for dashboards and dense product surfaces." description="A compact configuration for dashboards and dense product surfaces."
items='[ items='[
@@ -518,6 +522,7 @@ page ChartDetail {
] ]
<span>&#125;</span> <span>&#125;</span>
]' ]'
size="sm"
variant="secondary" variant="secondary"
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
@@ -532,12 +537,11 @@ page ChartDetail {
<div id="chart-demo-3" class="demo-canvas demo-canvas--3"> <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-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> </div>
<section class="demo-code" aria-label="Rich configuration usage"> <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> <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 <pre><code>&lt;Chart
size="lg"
title="Advanced Chart" title="Advanced Chart"
description="A richer configuration demonstrating additional content and hierarchy." description="A richer configuration demonstrating additional content and hierarchy."
items='[ items='[
@@ -648,6 +652,7 @@ page ChartDetail {
] ]
<span>&#125;</span> <span>&#125;</span>
]' ]'
size="lg"
variant="success" variant="success"
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
@@ -672,14 +677,14 @@ if (component) registerOutputHandler(component, "dataPointClick", (payload) =&gt
if (component) registerOutputHandler(component, "legendToggle", (payload) =&gt; <span>&#123;</span> if (component) registerOutputHandler(component, "legendToggle", (payload) =&gt; <span>&#123;</span>
console.log("legendToggle", payload) console.log("legendToggle", payload)
<span>&#125;</span>)</code></pre></section></div></div></section> <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> </main>
<aside class="detail-aside"> <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> <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> </aside>
</div> </div>
<nav class="detail-pagination"> <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> <a href="/components/clipboard"><small>Next</small><strong>Clipboard →</strong></a>
</nav> </nav>
} }
@@ -1,12 +1,16 @@
// Generated by scripts/generate-showcase.mjs. Do not edit directly. // Generated by scripts/generate-showcase.mjs. Do not edit directly.
page ClipboardDetail { page ClipboardDetail {
layout = "showcase" layout = "showcase"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default" state playground_value = ctx.url.searchParams.get("pg_value") ?? "sample-1"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary" 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_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_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_code = (ctx.url.searchParams.get("pg_code") ?? "true") === "true"
state playground_variant = ctx.url.searchParams.get("pg_variant") ?? "default" 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" state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
seo { seo {
title = "Clipboard" title = "Clipboard"
@@ -21,7 +25,7 @@ page ClipboardDetail {
<span class="showcase-eyebrow">Integrations component</span> <span class="showcase-eyebrow">Integrations component</span>
<h1>Clipboard</h1> <h1>Clipboard</h1>
<p>Theme-aware, responsive clipboard component.</p> <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>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div> <div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header> </header>
@@ -30,235 +34,24 @@ page ClipboardDetail {
<div class="playground-workbench"> <div class="playground-workbench">
<div class="playground-preview"> <div class="playground-preview">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <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"> <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> <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 <pre><code data-playground-code>&lt;Clipboard
value="sample-1"
label="Clipboard"
copiedLabel="Clipboard"
errorLabel="Clipboard"
title="Clipboard example" title="Clipboard example"
description="A polished default clipboard for a modern application." 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> /&gt;</code></pre>
</section> </section>
<div class="playground-status" data-playground-status aria-live="polite"></div> <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 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> </div>
<form class="playground-controls" data-playground-form> <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> <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-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>[ <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>
&#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>
</form> </form>
</div> </div>
</section> </section>
@@ -274,121 +67,17 @@ page ClipboardDetail {
<div id="clipboard-demo-1" class="demo-canvas demo-canvas--1"> <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-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> </div>
<section class="demo-code" aria-label="Production default usage"> <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> <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 <pre><code>&lt;Clipboard
value="sample-1"
label="Clipboard"
copiedLabel="Clipboard"
errorLabel="Clipboard"
title="Clipboard example" title="Clipboard example"
description="A polished default clipboard for a modern application." 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> /&gt;</code></pre>
</section> </section>
</div> </div>
@@ -402,123 +91,18 @@ page ClipboardDetail {
<div id="clipboard-demo-2" class="demo-canvas demo-canvas--2"> <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-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> </div>
<section class="demo-code" aria-label="Compact application usage"> <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> <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 <pre><code>&lt;Clipboard
size="sm" value="sample-2"
label="Compact example"
copiedLabel="Compact example"
errorLabel="Compact example"
title="Compact Clipboard" title="Compact Clipboard"
description="A compact configuration for dashboards and dense product surfaces." description="A compact configuration for dashboards and dense product surfaces."
items='[ size="sm"
<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"
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
</div> </div>
@@ -532,123 +116,18 @@ page ClipboardDetail {
<div id="clipboard-demo-3" class="demo-canvas demo-canvas--3"> <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-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> </div>
<section class="demo-code" aria-label="Rich configuration usage"> <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> <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 <pre><code>&lt;Clipboard
size="lg" value="sample-3"
label="Advanced example"
copiedLabel="Advanced example"
errorLabel="Advanced example"
title="Advanced Clipboard" title="Advanced Clipboard"
description="A richer configuration demonstrating additional content and hierarchy." description="A richer configuration demonstrating additional content and hierarchy."
items='[ size="lg"
<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"
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
</div> </div>
@@ -672,7 +151,7 @@ if (component) registerOutputHandler(component, "success", (payload) =&gt; <span
if (component) registerOutputHandler(component, "error", (payload) =&gt; <span>&#123;</span> if (component) registerOutputHandler(component, "error", (payload) =&gt; <span>&#123;</span>
console.log("error", payload) console.log("error", payload)
<span>&#125;</span>)</code></pre></section></div></div></section> <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> </main>
<aside class="detail-aside"> <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> <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> </div>
<nav class="detail-pagination"> <nav class="detail-pagination">
<a href="/components/advanced-select"><small>Previous</small><strong>← Advanced Select</strong></a> <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> </nav>
} }
} }
@@ -1,12 +1,13 @@
// Generated by scripts/generate-showcase.mjs. Do not edit directly. // Generated by scripts/generate-showcase.mjs. Do not edit directly.
page ConfettiDetail { page ConfettiDetail {
layout = "showcase" 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_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary" 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" state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
seo { seo {
title = "Confetti" title = "Confetti"
@@ -21,7 +22,7 @@ page ConfettiDetail {
<span class="showcase-eyebrow">Integrations component</span> <span class="showcase-eyebrow">Integrations component</span>
<h1>Confetti</h1> <h1>Confetti</h1>
<p>Theme-aware, responsive confetti component.</p> <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>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div> <div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header> </header>
@@ -30,235 +31,20 @@ page ConfettiDetail {
<div class="playground-workbench"> <div class="playground-workbench">
<div class="playground-preview"> <div class="playground-preview">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <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"> <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> <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 <pre><code data-playground-code>&lt;Confetti
title="Confetti example" label="Confetti"
description="A polished default confetti for a modern application." count="3"
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>
]'
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
<div class="playground-status" data-playground-status aria-live="polite"></div> <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 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> </div>
<form class="playground-controls" data-playground-form> <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> <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-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>[ <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>
&#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>
</form> </form>
</div> </div>
</section> </section>
@@ -274,121 +60,13 @@ page ConfettiDetail {
<div id="confetti-demo-1" class="demo-canvas demo-canvas--1"> <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-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> </div>
<section class="demo-code" aria-label="Production default usage"> <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> <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 <pre><code>&lt;Confetti
title="Confetti example" label="Confetti"
description="A polished default confetti for a modern application." count="3"
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>
]'
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
</div> </div>
@@ -402,123 +80,14 @@ page ConfettiDetail {
<div id="confetti-demo-2" class="demo-canvas demo-canvas--2"> <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-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> </div>
<section class="demo-code" aria-label="Compact application usage"> <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> <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 <pre><code>&lt;Confetti
label="Compact example"
count="7"
size="sm" 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> /&gt;</code></pre>
</section> </section>
</div> </div>
@@ -532,123 +101,14 @@ page ConfettiDetail {
<div id="confetti-demo-3" class="demo-canvas demo-canvas--3"> <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-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> </div>
<section class="demo-code" aria-label="Rich configuration usage"> <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> <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 <pre><code>&lt;Confetti
label="Advanced example"
count="11"
size="lg" 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> /&gt;</code></pre>
</section> </section>
</div> </div>
@@ -667,7 +127,7 @@ if (component) registerOutputHandler(component, "start", (payload) =&gt; <span>&
if (component) registerOutputHandler(component, "complete", (payload) =&gt; <span>&#123;</span> if (component) registerOutputHandler(component, "complete", (payload) =&gt; <span>&#123;</span>
console.log("complete", payload) console.log("complete", payload)
<span>&#125;</span>)</code></pre></section></div></div></section> <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> </main>
<aside class="detail-aside"> <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> <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> </div>
<nav class="detail-pagination"> <nav class="detail-pagination">
<a href="/components/data-map"><small>Previous</small><strong>← Data Map</strong></a> <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> </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> </aside>
</div> </div>
<nav class="detail-pagination"> <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> <a href="/components/pin-input"><small>Next</small><strong>Pin Input →</strong></a>
</nav> </nav>
} }
@@ -21,11 +21,11 @@ page ListDetail {
<span class="showcase-eyebrow">Base component</span> <span class="showcase-eyebrow">Base component</span>
<h1>List</h1> <h1>List</h1>
<p>Present structured responsive linked or status items with icons, descriptions, actions, and selection events.</p> <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>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div> <div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header> </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="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-workbench">
<div class="playground-preview"> <div class="playground-preview">
@@ -147,7 +147,7 @@ page ListDetail {
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
<div class="playground-status" data-playground-status aria-live="polite"></div> <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> </div>
<form class="playground-controls" data-playground-form> <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> <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> </section>
</div> </div>
</article></div></section> </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> <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> </main>
<aside class="detail-aside"> <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> </aside>
</div> </div>
<nav class="detail-pagination"> <nav class="detail-pagination">
@@ -21,11 +21,11 @@ page MapDetail {
<span class="showcase-eyebrow">Integrations component</span> <span class="showcase-eyebrow">Integrations component</span>
<h1>Map</h1> <h1>Map</h1>
<p>Present responsive location information and markers with map-ready metadata and movement or marker events.</p> <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>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div> <div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header> </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="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-workbench">
<div class="playground-preview"> <div class="playground-preview">
@@ -147,7 +147,7 @@ page MapDetail {
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
<div class="playground-status" data-playground-status aria-live="polite"></div> <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> </div>
<form class="playground-controls" data-playground-form> <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> <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> </section>
</div> </div>
</article></div></section> </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> <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> </main>
<aside class="detail-aside"> <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> </aside>
</div> </div>
<nav class="detail-pagination"> <nav class="detail-pagination">
<a href="/components/file-upload"><small>Previous</small><strong>← File Upload</strong></a> <a href="/components/drag-and-drop"><small>Previous</small><strong>← Drag And Drop</strong></a>
<a href="/components/toast-notifications"><small>Next</small><strong>Toast Notifications →</strong></a> <a href="/components/wysiwyg-editor"><small>Next</small><strong>Wysiwyg Editor →</strong></a>
</nav> </nav>
} }
} }
@@ -21,11 +21,11 @@ page MarqueeDetail {
<span class="showcase-eyebrow">Base component</span> <span class="showcase-eyebrow">Base component</span>
<h1>Marquee</h1> <h1>Marquee</h1>
<p>Continuously present responsive labels, partners, notices, or capabilities with pause and resume behavior.</p> <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>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div> <div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header> </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="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-workbench">
<div class="playground-preview"> <div class="playground-preview">
@@ -147,7 +147,7 @@ page MarqueeDetail {
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
<div class="playground-status" data-playground-status aria-live="polite"></div> <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> </div>
<form class="playground-controls" data-playground-form> <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> <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> </section>
</div> </div>
</article></div></section> </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> <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> </main>
<aside class="detail-aside"> <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> </aside>
</div> </div>
<nav class="detail-pagination"> <nav class="detail-pagination">
@@ -27,11 +27,11 @@ page SearchBoxDetail {
<span class="showcase-eyebrow">Forms component</span> <span class="showcase-eyebrow">Forms component</span>
<h1>Search Box</h1> <h1>Search Box</h1>
<p>Provide an accessible responsive search field with labels, validation states, sizes, and input or change events.</p> <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>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div> <div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header> </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="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-workbench">
<div class="playground-preview"> <div class="playground-preview">
@@ -45,7 +45,7 @@ page SearchBoxDetail {
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
<div class="playground-status" data-playground-status aria-live="polite"></div> <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> </div>
<form class="playground-controls" data-playground-form> <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> <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> </section>
</div> </div>
</article></div></section> </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> <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> </main>
<aside class="detail-aside"> <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> </aside>
</div> </div>
<nav class="detail-pagination"> <nav class="detail-pagination">
@@ -21,11 +21,11 @@ page TimelineDetail {
<span class="showcase-eyebrow">Base component</span> <span class="showcase-eyebrow">Base component</span>
<h1>Timeline</h1> <h1>Timeline</h1>
<p>Present responsive chronological activity, milestones, or workflow status with rich item metadata.</p> <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>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div> <div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header> </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="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-workbench">
<div class="playground-preview"> <div class="playground-preview">
@@ -147,7 +147,7 @@ page TimelineDetail {
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
<div class="playground-status" data-playground-status aria-live="polite"></div> <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> </div>
<form class="playground-controls" data-playground-form> <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> <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> </section>
</div> </div>
</article></div></section> </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> <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> </main>
<aside class="detail-aside"> <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> </aside>
</div> </div>
<nav class="detail-pagination"> <nav class="detail-pagination">
<a href="/components/styled-icon"><small>Previous</small><strong>← Styled Icon</strong></a> <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> </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. // Generated by scripts/generate-showcase.mjs. Do not edit directly.
page TreeViewDetail { page TreeViewDetail {
layout = "showcase" 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_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_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_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" state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
seo { seo {
title = "Tree View" title = "Tree View"
@@ -21,7 +24,7 @@ page TreeViewDetail {
<span class="showcase-eyebrow">Base component</span> <span class="showcase-eyebrow">Base component</span>
<h1>Tree View</h1> <h1>Tree View</h1>
<p>Theme-aware, responsive tree view component.</p> <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>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div> <div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header> </header>
@@ -30,7 +33,7 @@ page TreeViewDetail {
<div class="playground-workbench"> <div class="playground-workbench">
<div class="playground-preview"> <div class="playground-preview">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <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"> <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> <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 <pre><code data-playground-code>&lt;TreeView
@@ -144,14 +147,122 @@ page TreeViewDetail {
] ]
<span>&#125;</span> <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> /&gt;</code></pre>
</section> </section>
<div class="playground-status" data-playground-status aria-live="polite"></div> <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 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> </div>
<form class="playground-controls" data-playground-form> <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> <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-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>[ <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; &#123;
"id": "tree-view-0-1", "id": "tree-view-0-1",
"key": "tree-view-0-1", "key": "tree-view-0-1",
@@ -258,7 +369,114 @@ page TreeViewDetail {
"Standard" "Standard"
] ]
&#125; &#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> </form>
</div> </div>
</section> </section>
@@ -274,7 +492,7 @@ page TreeViewDetail {
<div id="tree-view-demo-1" class="demo-canvas demo-canvas--1"> <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-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> </div>
<section class="demo-code" aria-label="Production default usage"> <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> <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> <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> /&gt;</code></pre>
</section> </section>
</div> </div>
@@ -402,12 +728,11 @@ page TreeViewDetail {
<div id="tree-view-demo-2" class="demo-canvas demo-canvas--2"> <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-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> </div>
<section class="demo-code" aria-label="Compact application usage"> <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> <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 <pre><code>&lt;TreeView
size="sm"
title="Compact Tree View" title="Compact Tree View"
description="A compact configuration for dashboards and dense product surfaces." description="A compact configuration for dashboards and dense product surfaces."
items='[ items='[
@@ -518,7 +843,115 @@ page TreeViewDetail {
] ]
<span>&#125;</span> <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> /&gt;</code></pre>
</section> </section>
</div> </div>
@@ -532,12 +965,11 @@ page TreeViewDetail {
<div id="tree-view-demo-3" class="demo-canvas demo-canvas--3"> <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-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> </div>
<section class="demo-code" aria-label="Rich configuration usage"> <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> <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 <pre><code>&lt;TreeView
size="lg"
title="Advanced Tree View" title="Advanced Tree View"
description="A richer configuration demonstrating additional content and hierarchy." description="A richer configuration demonstrating additional content and hierarchy."
items='[ items='[
@@ -648,7 +1080,115 @@ page TreeViewDetail {
] ]
<span>&#125;</span> <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> /&gt;</code></pre>
</section> </section>
</div> </div>
@@ -677,14 +1217,14 @@ if (component) registerOutputHandler(component, "expand", (payload) =&gt; <span>
if (component) registerOutputHandler(component, "collapse", (payload) =&gt; <span>&#123;</span> if (component) registerOutputHandler(component, "collapse", (payload) =&gt; <span>&#123;</span>
console.log("collapse", payload) console.log("collapse", payload)
<span>&#125;</span>)</code></pre></section></div></div></section> <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> </main>
<aside class="detail-aside"> <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> <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> </aside>
</div> </div>
<nav class="detail-pagination"> <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> <span></span>
</nav> </nav>
} }
@@ -684,7 +684,7 @@ if (component) registerOutputHandler(component, "blur", (payload) =&gt; <span>&#
</aside> </aside>
</div> </div>
<nav class="detail-pagination"> <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> <span></span>
</nav> </nav>
} }
@@ -10,18 +10,18 @@ page ComponentShowcase {
<header class="showcase-hero showcase-hero--home"> <header class="showcase-hero showcase-hero--home">
<span class="showcase-eyebrow">The UI foundation for WRNexus</span> <span class="showcase-eyebrow">The UI foundation for WRNexus</span>
<h1 class="showcase-title">Build modern products<br /><em>at framework speed.</em></h1> <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-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> <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> </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"> <section class="showcase-stats">
<div class="showcase-stat"><strong>108</strong><span>Unique components</span></div> <div class="showcase-stat"><strong>102</strong><span>Unique components</span></div>
<div class="showcase-stat"><strong>419</strong><span>Live configurations</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>0</strong><span>Duplicate implementations</span></div>
<div class="showcase-stat"><strong>11</strong><span>Focused categories</span></div> <div class="showcase-stat"><strong>11</strong><span>Focused categories</span></div>
</section> </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> </div>
} }
} }
@@ -3,64 +3,44 @@ page IntegrationsShowcase {
layout = "showcase" layout = "showcase"
seo { seo {
title = "Integrations components" title = "Integrations components"
description = "11 integrations components from @wrnexus/ui." description = "7 integrations components from @wrnexus/ui."
} }
view { view {
<header class="showcase-hero showcase-hero--category"> <header class="showcase-hero showcase-hero--category">
<span class="showcase-eyebrow">Component category</span> <span class="showcase-eyebrow">Component category</span>
<h1 class="showcase-title">Integrations</h1> <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> <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>11 components</span><span>Multiple use cases</span><span>33 live configurations</span></div> <div class="category-meta"><span>7 components</span><span>Multiple use cases</span><span>21 live configurations</span></div>
</header> </header>
<div class="catalog-grid"> <div class="catalog-grid">
<article class="catalog-card" data-interactive-preview="false"> <article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview"> <div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div> <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 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>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> </div>
<div class="catalog-card-body"> <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><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> </div>
</article> </article>
<article class="catalog-card" data-interactive-preview="false"> <article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview"> <div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div> <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>
<div class="catalog-card-body"> <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><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> </div>
</article> </article>
<article class="catalog-card" data-interactive-preview="false"> <article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview"> <div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div> <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>
<div class="catalog-card-body"> <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><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> </div>
</article> </article>
<article class="catalog-card" data-interactive-preview="false"> <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 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> </div>
</article> </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"> <article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview"> <div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div> <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 class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/map">Explore component <span aria-hidden="true">→</span></a></div>
</div> </div>
</article> </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"> <article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview"> <div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div> <div class="catalog-card-glow" aria-hidden="true"></div>
@@ -9,8 +9,6 @@ export interface Routes {
"/block-library": Record<string, never>; "/block-library": Record<string, never>;
"/colors": Record<string, never>; "/colors": Record<string, never>;
"/components/accordion": 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/advanced-select": Record<string, never>;
"/components/alert": Record<string, never>; "/components/alert": Record<string, never>;
"/components/announcement-bar": Record<string, never>; "/components/announcement-bar": Record<string, never>;
@@ -37,7 +35,6 @@ export interface Routes {
"/components/confetti": Record<string, never>; "/components/confetti": Record<string, never>;
"/components/container": Record<string, never>; "/components/container": Record<string, never>;
"/components/context-menu": Record<string, never>; "/components/context-menu": Record<string, never>;
"/components/copy-markup": Record<string, never>;
"/components/ctasection": Record<string, never>; "/components/ctasection": Record<string, never>;
"/components/custom-scrollbar": Record<string, never>; "/components/custom-scrollbar": Record<string, never>;
"/components/data-map": Record<string, never>; "/components/data-map": Record<string, never>;
@@ -52,7 +49,6 @@ export interface Routes {
"/components/feature-grid": Record<string, never>; "/components/feature-grid": Record<string, never>;
"/components/feature-icon-card": Record<string, never>; "/components/feature-icon-card": Record<string, never>;
"/components/file-input": Record<string, never>; "/components/file-input": Record<string, never>;
"/components/file-upload": Record<string, never>;
"/components/file-upload-progress": Record<string, never>; "/components/file-upload-progress": Record<string, never>;
"/components/footer": Record<string, never>; "/components/footer": Record<string, never>;
"/components/grid": Record<string, never>; "/components/grid": Record<string, never>;
@@ -107,8 +103,6 @@ export interface Routes {
"/components/textarea": Record<string, never>; "/components/textarea": Record<string, never>;
"/components/time-picker": Record<string, never>; "/components/time-picker": Record<string, never>;
"/components/timeline": 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/toaster": Record<string, never>;
"/components/toggle-count": Record<string, never>; "/components/toggle-count": Record<string, never>;
"/components/toggle-password": Record<string, never>; "/components/toggle-password": Record<string, never>;
@@ -152,8 +146,6 @@ export interface RouteNames {
"block.library": "/block-library"; "block.library": "/block-library";
"colors": "/colors"; "colors": "/colors";
"components.accordion": "/components/accordion"; "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.advanced.select": "/components/advanced-select";
"components.alert": "/components/alert"; "components.alert": "/components/alert";
"components.announcement.bar": "/components/announcement-bar"; "components.announcement.bar": "/components/announcement-bar";
@@ -180,7 +172,6 @@ export interface RouteNames {
"components.confetti": "/components/confetti"; "components.confetti": "/components/confetti";
"components.container": "/components/container"; "components.container": "/components/container";
"components.context.menu": "/components/context-menu"; "components.context.menu": "/components/context-menu";
"components.copy.markup": "/components/copy-markup";
"components.ctasection": "/components/ctasection"; "components.ctasection": "/components/ctasection";
"components.custom.scrollbar": "/components/custom-scrollbar"; "components.custom.scrollbar": "/components/custom-scrollbar";
"components.data.map": "/components/data-map"; "components.data.map": "/components/data-map";
@@ -195,7 +186,6 @@ export interface RouteNames {
"components.feature.grid": "/components/feature-grid"; "components.feature.grid": "/components/feature-grid";
"components.feature.icon.card": "/components/feature-icon-card"; "components.feature.icon.card": "/components/feature-icon-card";
"components.file.input": "/components/file-input"; "components.file.input": "/components/file-input";
"components.file.upload": "/components/file-upload";
"components.file.upload.progress": "/components/file-upload-progress"; "components.file.upload.progress": "/components/file-upload-progress";
"components.footer": "/components/footer"; "components.footer": "/components/footer";
"components.grid": "/components/grid"; "components.grid": "/components/grid";
@@ -250,8 +240,6 @@ export interface RouteNames {
"components.textarea": "/components/textarea"; "components.textarea": "/components/textarea";
"components.time.picker": "/components/time-picker"; "components.time.picker": "/components/time-picker";
"components.timeline": "/components/timeline"; "components.timeline": "/components/timeline";
"components.toast": "/components/toast";
"components.toast.notifications": "/components/toast-notifications";
"components.toaster": "/components/toaster"; "components.toaster": "/components/toaster";
"components.toggle.count": "/components/toggle-count"; "components.toggle.count": "/components/toggle-count";
"components.toggle.password": "/components/toggle-password"; "components.toggle.password": "/components/toggle-password";
@@ -364,8 +352,6 @@ export function route<N extends RouteName>(
"block.library": "/block-library", "block.library": "/block-library",
"colors": "/colors", "colors": "/colors",
"components.accordion": "/components/accordion", "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.advanced.select": "/components/advanced-select",
"components.alert": "/components/alert", "components.alert": "/components/alert",
"components.announcement.bar": "/components/announcement-bar", "components.announcement.bar": "/components/announcement-bar",
@@ -392,7 +378,6 @@ export function route<N extends RouteName>(
"components.confetti": "/components/confetti", "components.confetti": "/components/confetti",
"components.container": "/components/container", "components.container": "/components/container",
"components.context.menu": "/components/context-menu", "components.context.menu": "/components/context-menu",
"components.copy.markup": "/components/copy-markup",
"components.ctasection": "/components/ctasection", "components.ctasection": "/components/ctasection",
"components.custom.scrollbar": "/components/custom-scrollbar", "components.custom.scrollbar": "/components/custom-scrollbar",
"components.data.map": "/components/data-map", "components.data.map": "/components/data-map",
@@ -407,7 +392,6 @@ export function route<N extends RouteName>(
"components.feature.grid": "/components/feature-grid", "components.feature.grid": "/components/feature-grid",
"components.feature.icon.card": "/components/feature-icon-card", "components.feature.icon.card": "/components/feature-icon-card",
"components.file.input": "/components/file-input", "components.file.input": "/components/file-input",
"components.file.upload": "/components/file-upload",
"components.file.upload.progress": "/components/file-upload-progress", "components.file.upload.progress": "/components/file-upload-progress",
"components.footer": "/components/footer", "components.footer": "/components/footer",
"components.grid": "/components/grid", "components.grid": "/components/grid",
@@ -462,8 +446,6 @@ export function route<N extends RouteName>(
"components.textarea": "/components/textarea", "components.textarea": "/components/textarea",
"components.time.picker": "/components/time-picker", "components.time.picker": "/components/time-picker",
"components.timeline": "/components/timeline", "components.timeline": "/components/timeline",
"components.toast": "/components/toast",
"components.toast.notifications": "/components/toast-notifications",
"components.toaster": "/components/toaster", "components.toaster": "/components/toaster",
"components.toggle.count": "/components/toggle-count", "components.toggle.count": "/components/toggle-count",
"components.toggle.password": "/components/toggle-password", "components.toggle.password": "/components/toggle-password",
+2 -2
View File
@@ -5,8 +5,8 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"generate:reference": "bun run ../../scripts/generate-ui-component-reference.mjs", "generate:reference": "bun run ../../scripts/generate-ui-component-reference.mjs",
"generate:pages": "node scripts/generate-showcase.mjs", "generate:pages": "bun run generate:reference && node scripts/generate-showcase.mjs",
"generate": "bun run generate:reference && bun run generate:pages", "generate": "bun run generate:pages",
"dev": "bun run generate && bun run ../../packages/cli/src/index.ts dev .", "dev": "bun run generate && bun run ../../packages/cli/src/index.ts dev .",
"build": "bun run generate && bun run ../../packages/cli/src/index.ts build .", "build": "bun run generate && bun run ../../packages/cli/src/index.ts build .",
"typecheck": "tsc --noEmit -p tsconfig.json", "typecheck": "tsc --noEmit -p tsconfig.json",
@@ -2,6 +2,7 @@ import { mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from
import { dirname, join } from "node:path"; import { dirname, join } from "node:path";
import process from "node:process"; import process from "node:process";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import prettier from "prettier";
import { eventDescriptionFor, profileFor } from "./showcase-profiles.mjs"; import { eventDescriptionFor, profileFor } from "./showcase-profiles.mjs";
const exampleRoot = join(dirname(fileURLToPath(import.meta.url)), ".."); const exampleRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
@@ -1792,11 +1793,12 @@ const showcaseManifest = {
profiled: Boolean(profileFor(component.name)), profiled: Boolean(profileFor(component.name)),
})), })),
}; };
writeFileSync( const formattedManifest = await prettier.format(`${JSON.stringify(showcaseManifest, null, 2)}\n`, {
join(exampleRoot, "showcase-manifest.json"), parser: "json",
`${JSON.stringify(showcaseManifest, null, 2)}\n`, printWidth: 100,
"utf8", endOfLine: "lf",
); });
writeFileSync(join(exampleRoot, "showcase-manifest.json"), formattedManifest, "utf8");
const categoryCards = categoryEntries const categoryCards = categoryEntries
.map( .map(
@@ -1,8 +1,8 @@
{ {
"generatedFrom": "packages/ui/component-reference.json", "generatedFrom": "packages/ui/component-reference.json",
"componentCount": 108, "componentCount": 102,
"categoryCount": 11, "categoryCount": 11,
"totalDemoCount": 419, "totalDemoCount": 401,
"components": [ "components": [
{ {
"name": "Accordion", "name": "Accordion",
@@ -16,30 +16,6 @@
"events": ["change", "open", "close"], "events": ["change", "open", "close"],
"profiled": false "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", "name": "AdvancedSelect",
"mount": "AdvancedSelect", "mount": "AdvancedSelect",
@@ -250,7 +226,7 @@
"category": "integrations", "category": "integrations",
"purpose": "Theme-aware, responsive chart component.", "purpose": "Theme-aware, responsive chart component.",
"demoCount": 3, "demoCount": 3,
"propCount": 7, "propCount": 12,
"slots": ["default"], "slots": ["default"],
"events": ["select", "dataPointClick", "legendToggle"], "events": ["select", "dataPointClick", "legendToggle"],
"profiled": false "profiled": false
@@ -286,7 +262,7 @@
"category": "integrations", "category": "integrations",
"purpose": "Theme-aware, responsive clipboard component.", "purpose": "Theme-aware, responsive clipboard component.",
"demoCount": 3, "demoCount": 3,
"propCount": 7, "propCount": 11,
"slots": ["default"], "slots": ["default"],
"events": ["copy", "success", "error"], "events": ["copy", "success", "error"],
"profiled": false "profiled": false
@@ -346,7 +322,7 @@
"category": "integrations", "category": "integrations",
"purpose": "Theme-aware, responsive confetti component.", "purpose": "Theme-aware, responsive confetti component.",
"demoCount": 3, "demoCount": 3,
"propCount": 7, "propCount": 8,
"slots": ["default"], "slots": ["default"],
"events": ["start", "complete"], "events": ["start", "complete"],
"profiled": false "profiled": false
@@ -375,18 +351,6 @@
"events": ["open", "close", "select", "action"], "events": ["open", "close", "select", "action"],
"profiled": true "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", "name": "CustomScrollbar",
"mount": "CustomScrollbar", "mount": "CustomScrollbar",
@@ -552,18 +516,6 @@
"events": ["input", "change", "focus", "blur", "select", "clear", "invalid"], "events": ["input", "change", "focus", "blur", "select", "clear", "invalid"],
"profiled": false "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", "name": "FileUploadProgress",
"mount": "FileUploadProgress", "mount": "FileUploadProgress",
@@ -729,7 +681,7 @@
"demoCount": 3, "demoCount": 3,
"propCount": 7, "propCount": 7,
"slots": ["default"], "slots": ["default"],
"events": [], "events": ["select"],
"profiled": true "profiled": true
}, },
{ {
@@ -753,7 +705,7 @@
"demoCount": 3, "demoCount": 3,
"propCount": 7, "propCount": 7,
"slots": ["default"], "slots": ["default"],
"events": [], "events": ["markerClick", "select", "zoom"],
"profiled": true "profiled": true
}, },
{ {
@@ -777,7 +729,7 @@
"demoCount": 3, "demoCount": 3,
"propCount": 7, "propCount": 7,
"slots": ["default"], "slots": ["default"],
"events": [], "events": ["pause", "resume"],
"profiled": true "profiled": true
}, },
{ {
@@ -1005,7 +957,7 @@
"demoCount": 3, "demoCount": 3,
"propCount": 13, "propCount": 13,
"slots": [], "slots": [],
"events": [], "events": ["search", "clear"],
"profiled": true "profiled": true
}, },
{ {
@@ -1209,33 +1161,9 @@
"demoCount": 3, "demoCount": 3,
"propCount": 7, "propCount": 7,
"slots": ["default"], "slots": ["default"],
"events": [], "events": ["select"],
"profiled": true "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", "name": "Toaster",
"mount": "Toaster", "mount": "Toaster",
@@ -1291,7 +1219,7 @@
"category": "base", "category": "base",
"purpose": "Theme-aware, responsive tree view component.", "purpose": "Theme-aware, responsive tree view component.",
"demoCount": 3, "demoCount": 3,
"propCount": 7, "propCount": 10,
"slots": ["default"], "slots": ["default"],
"events": ["select", "toggle", "expand", "collapse"], "events": ["select", "toggle", "expand", "collapse"],
"profiled": false "profiled": false
@@ -2,5 +2,7 @@ node_modules/
dist/ dist/
.wrnexus/ .wrnexus/
**/.wrnexus/ **/.wrnexus/
.wrnexus-*/
**/.wrnexus-*/
*.log *.log
**/CLAUDE.md **/CLAUDE.md
@@ -53,11 +53,11 @@
"@iconify-json/lucide": "^1.2.118", "@iconify-json/lucide": "^1.2.118",
"@iconify/tailwind4": "^1.2.3", "@iconify/tailwind4": "^1.2.3",
"@tailwindcss/cli": "^4.0.0", "@tailwindcss/cli": "^4.0.0",
"@types/bun": "latest", "@types/bun": "^1.3.14",
"eslint": "^9.0.0", "eslint": "^9.0.0",
"prettier": "latest", "prettier": "^3.9.6",
"tailwindcss": "^4.0.0", "tailwindcss": "^4.0.0",
"typescript": "^5.5.0", "typescript": "^5.5.0",
"typescript-eslint": "latest" "typescript-eslint": "^8.65.0"
} }
} }
@@ -53,11 +53,11 @@
"@iconify-json/lucide": "^1.2.118", "@iconify-json/lucide": "^1.2.118",
"@iconify/tailwind4": "^1.2.3", "@iconify/tailwind4": "^1.2.3",
"@tailwindcss/cli": "^4.0.0", "@tailwindcss/cli": "^4.0.0",
"@types/bun": "latest", "@types/bun": "^1.3.14",
"eslint": "^9.0.0", "eslint": "^9.0.0",
"prettier": "latest", "prettier": "^3.9.6",
"tailwindcss": "^4.0.0", "tailwindcss": "^4.0.0",
"typescript": "^5.5.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 { 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 { 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"; import { GET } from "../app/api/product.ts";
const secret = process.env.WRNEXUS_RPC_SECRET; const secret = process.env.WRNEXUS_RPC_SECRET;
@@ -16,23 +15,17 @@ afterEach(() => {
else process.env.WRNEXUS_INTERNAL_ORIGINS = origins; else process.env.WRNEXUS_INTERNAL_ORIGINS = origins;
}); });
function startAdmin(allowed: boolean) { function startAdmin() {
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",
},
);
return Bun.serve({ return Bun.serve({
port: 0, port: 0,
hostname: "127.0.0.1", hostname: "127.0.0.1",
async fetch(request) { async fetch(request) {
return ( return (
(await handleRpcRequest(request, new URL(request.url), new Map([["catalog", service]]))) ?? (await handleRpcRequest(
new Response("Not found", { status: 404 }) 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 () => { 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_RPC_SECRET = "test-rpc-secret-at-least-32-chars-long";
process.env.WRNEXUS_APP_NAME = "web"; process.env.WRNEXUS_APP_NAME = "web";
const server = startAdmin(true); const server = startAdmin();
process.env.WRNEXUS_INTERNAL_ORIGINS = JSON.stringify({ process.env.WRNEXUS_INTERNAL_ORIGINS = JSON.stringify({
admin: `http://127.0.0.1:${server.port}`, 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 () => { 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_RPC_SECRET = "test-rpc-secret-at-least-32-chars-long";
process.env.WRNEXUS_APP_NAME = "web"; process.env.WRNEXUS_APP_NAME = "web";
const server = startAdmin(false); const server = startAdmin();
process.env.WRNEXUS_INTERNAL_ORIGINS = JSON.stringify({ process.env.WRNEXUS_INTERNAL_ORIGINS = JSON.stringify({
admin: `http://127.0.0.1:${server.port}`, admin: `http://127.0.0.1:${server.port}`,
}); });
@@ -6,7 +6,17 @@ import tseslint from "typescript-eslint";
const tsconfigRootDir = dirname(fileURLToPath(import.meta.url)); const tsconfigRootDir = dirname(fileURLToPath(import.meta.url));
export default tseslint.config( export default tseslint.config(
{ ignores: ["node_modules/**", "dist/**", "**/dist/**", ".wrnexus/**", "**/.wrnexus/**"] }, {
ignores: [
"node_modules/**",
"dist/**",
"**/dist/**",
".wrnexus/**",
"**/.wrnexus/**",
".wrnexus-*/**",
"**/.wrnexus-*/**",
],
},
{ languageOptions: { parserOptions: { tsconfigRootDir } } }, { languageOptions: { parserOptions: { tsconfigRootDir } } },
js.configs.recommended, js.configs.recommended,
...tseslint.configs.recommended, ...tseslint.configs.recommended,
+3 -3
View File
@@ -23,10 +23,10 @@
"devDependencies": { "devDependencies": {
"@wrnexus/cli": "0.8.6", "@wrnexus/cli": "0.8.6",
"@eslint/js": "^9.0.0", "@eslint/js": "^9.0.0",
"@types/bun": "latest", "@types/bun": "^1.3.14",
"eslint": "^9.0.0", "eslint": "^9.0.0",
"prettier": "latest", "prettier": "^3.9.6",
"typescript": "^5.5.0", "typescript": "^5.5.0",
"typescript-eslint": "latest" "typescript-eslint": "^8.65.0"
} }
} }
@@ -11,5 +11,5 @@
"allowImportingTsExtensions": true "allowImportingTsExtensions": true
}, },
"include": ["wrnexus.workspace.ts", "packages/**/*.ts"], "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": [ "workspaces": [
"packages/*", "packages/*",
"examples/*", "examples/*",
"examples/inter-app-api-showcase/apps/*",
"examples/inter-app-api-showcase/packages/*",
"services/*" "services/*"
], ],
"scripts": { "scripts": {
@@ -34,24 +36,21 @@
"format": "prettier . --write", "format": "prettier . --write",
"format:check": "prettier . --check", "format:check": "prettier . --check",
"check": "bun run typecheck && bun run lint && bun run check:component-imports && bun run test && bun run format: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:dev": "bun run --cwd examples/auth-showcase dev",
"auth:check": "bun run --cwd packages/auth check", "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", "validate:0.7": "node scripts/validate-0.7.mjs",
"security:framework": "node scripts/security-performance-audit.mjs", "security:framework": "node scripts/security-performance-audit.mjs",
"security:asvs": "node scripts/check-security-asvs.mjs", "security:asvs": "node scripts/check-security-asvs.mjs",
"generate:security-report": "node scripts/security-performance-audit.mjs --write", "generate:security-report": "node scripts/security-performance-audit.mjs --write",
"check:public-api": "node scripts/check-public-api.mjs", "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", "generate:public-api": "node scripts/check-public-api.mjs --write",
"check:ui-visual": "node scripts/check-ui-visual-contract.mjs", "check:ui-visual": "node scripts/check-ui-visual-contract.mjs",
"generate:ui-visual": "node scripts/check-ui-visual-contract.mjs --write", "generate:ui-visual": "node scripts/check-ui-visual-contract.mjs --write",
"sbom": "node scripts/generate-sbom.mjs", "sbom": "node scripts/generate-sbom.mjs",
"benchmark:framework": "node --experimental-transform-types scripts/benchmark-framework.mjs", "benchmark:framework": "node --experimental-transform-types scripts/benchmark-framework.mjs --write",
"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", "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", "validate:staging": "node --experimental-transform-types scripts/test-package-integrity.mjs",
"stage:packages": "bun run scripts/publish-packages.ts", "stage:packages": "bun run scripts/publish-packages.ts",
"test:staged-consumers": "node scripts/test-staged-consumers.mjs", "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"; } from "node:fs";
import { basename, dirname, extname, join, relative, resolve } from "node:path"; import { basename, dirname, extname, join, relative, resolve } from "node:path";
import { buildRouter, type Route } from "@wrnexus/router"; import { buildRouter, type Route } from "@wrnexus/router";
import { getReactiveRuntime } from "@wrnexus/csr"; import { getComponentControllerRuntime, getReactiveRuntime } from "@wrnexus/csr";
import { import {
analyzeRuntimeImports, analyzeRuntimeImports,
analyzeRuntimeRequirements, analyzeRuntimeRequirements,
@@ -44,6 +44,7 @@ import {
bundleCss, bundleCss,
renderStyles, renderStyles,
resolveThemeConfig, resolveThemeConfig,
renderActiveThemeCss,
renderThemeCss, renderThemeCss,
renderThemeRuntime, renderThemeRuntime,
} from "@wrnexus/styles"; } from "@wrnexus/styles";
@@ -109,6 +110,7 @@ export async function runBuild(appRoot: string): Promise<void> {
const compiledDir = join(distDir, "compiled"); const compiledDir = join(distDir, "compiled");
const clientModulesDir = join(distDir, "client"); const clientModulesDir = join(distDir, "client");
const reactivePath = join(distDir, "reactive.js"); const reactivePath = join(distDir, "reactive.js");
const controllersPath = join(distDir, "controllers.js");
const publicDir = join(root, "public"); const publicDir = join(root, "public");
const distPublicDir = join(distDir, "public"); const distPublicDir = join(distDir, "public");
const config = await loadAppConfig(root); const config = await loadAppConfig(root);
@@ -147,7 +149,12 @@ export async function runBuild(appRoot: string): Promise<void> {
const pluginContributions = await pluginRunner.contributions(); const pluginContributions = await pluginRunner.contributions();
const { generateApplicationTypes } = await import("./types.ts"); const { generateApplicationTypes } = await import("./types.ts");
generateApplicationTypes(root, pluginContributions); generateApplicationTypes(root, pluginContributions);
const componentDirs = [uiComponentsDir(), ...pluginContributions.componentDirs]; const bundledUiDir = uiComponentsDir();
const componentDirs = [
bundledUiDir,
join(bundledUiDir, "..", "styles"),
...pluginContributions.componentDirs,
];
await pluginRunner.hook("buildStart"); await pluginRunner.hook("buildStart");
const virtualModules = new Map<string, string>(); const virtualModules = new Map<string, string>();
for (const [index, module] of pluginContributions.virtualModules.entries()) { for (const [index, module] of pluginContributions.virtualModules.entries()) {
@@ -477,6 +484,13 @@ export async function runBuild(appRoot: string): Promise<void> {
); );
assetHash.update(reactiveCode); assetHash.update(reactiveCode);
console.log(`✓ Runtime: ${reactivePath}`); 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). // 1a) Theme tokens + client switcher (always emitted; built-in light/dark).
const theme = resolveThemeConfig(config.theme); const theme = resolveThemeConfig(config.theme);
@@ -484,6 +498,19 @@ export async function runBuild(appRoot: string): Promise<void> {
const themeJs = renderThemeRuntime(theme); const themeJs = renderThemeRuntime(theme);
writeFileSync(join(distDir, "theme.css"), themeCss, "utf8"); writeFileSync(join(distDir, "theme.css"), themeCss, "utf8");
writeFileSync(join(distDir, "theme.js"), themeJs, "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(themeCss);
assetHash.update(themeJs); assetHash.update(themeJs);
console.log(`✓ Theme: ${theme.names.length} themes (default: ${theme.default})`); console.log(`✓ Theme: ${theme.names.length} themes (default: ${theme.default})`);
@@ -539,14 +566,17 @@ export async function runBuild(appRoot: string): Promise<void> {
}, },
config.styles, 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"); const combinedInput = join(distDir, ".wrnexus-combined.css");
writeFileSync(combinedInput, combinedSource, "utf8"); writeFileSync(combinedInput, combinedSource, "utf8");
const combinedCss = await bundleCss(combinedInput, "production"); const combinedCss = await bundleCss(combinedInput, "production");
rmSync(combinedInput, { force: true }); rmSync(combinedInput, { force: true });
assetHash.update(combinedCss); assetHash.update(combinedCss);
// One blocking CSS request in production: tokens → UI application CSS. // One shared blocking request for UI + application CSS. The small active
// The standalone framework.css remains available for apps without global CSS. // theme stylesheet is selected from the request cookie and loaded first.
writeFileSync(join(distDir, "styles.css"), combinedCss, "utf8"); writeFileSync(join(distDir, "styles.css"), combinedCss, "utf8");
hasStyles = true; hasStyles = true;
if (Buffer.byteLength(combinedCss, "utf8") <= INLINE_CSS_LIMIT_BYTES) { if (Buffer.byteLength(combinedCss, "utf8") <= INLINE_CSS_LIMIT_BYTES) {
@@ -628,8 +658,8 @@ applyAuthzManifestEarly([${authzSetupEntries}]);
const componentsLit = router.components const componentsLit = router.components
.map((c) => { .map((c) => {
const v = `c${counter++}`; const v = `c${counter++}`;
imports.push(`import * as ${v} from ${JSON.stringify(importPathFor(c.file))};`); imports.push(`import { render as ${v} } from ${JSON.stringify(importPathFor(c.file))};`);
return `{ name: ${JSON.stringify(c.name)}, mod: ${v} }`; return `{ name: ${JSON.stringify(c.name)}, mod: { render: ${v} } }`;
}) })
.join(", "); .join(", ");
console.log(`✓ Components: ${router.components.length}`); console.log(`✓ Components: ${router.components.length}`);
@@ -638,8 +668,8 @@ applyAuthzManifestEarly([${authzSetupEntries}]);
const layoutsLit = router.layouts const layoutsLit = router.layouts
.map((l) => { .map((l) => {
const v = `c${counter++}`; const v = `c${counter++}`;
imports.push(`import * as ${v} from ${JSON.stringify(importPathFor(l.file))};`); imports.push(`import { render as ${v} } from ${JSON.stringify(importPathFor(l.file))};`);
return `{ name: ${JSON.stringify(l.name)}, mod: ${v} }`; return `{ name: ${JSON.stringify(l.name)}, mod: { render: ${v} } }`;
}) })
.join(", "); .join(", ");
if (router.layouts.length) console.log(`✓ Layouts: ${router.layouts.length}`); if (router.layouts.length) console.log(`✓ Layouts: ${router.layouts.length}`);
@@ -693,8 +723,10 @@ await createProductionServer(
}, },
{ {
reactivePath: join(import.meta.dir, "reactive.js"), reactivePath: join(import.meta.dir, "reactive.js"),
controllersPath: join(import.meta.dir, "controllers.js"),
clientModulesDir: join(import.meta.dir, "client"), clientModulesDir: join(import.meta.dir, "client"),
themePath: join(import.meta.dir, "theme.css"), themePath: join(import.meta.dir, "theme.css"),
themeAssetsDir: join(import.meta.dir, "theme"),
themeJsPath: join(import.meta.dir, "theme.js"), themeJsPath: join(import.meta.dir, "theme.js"),
theme: ${JSON.stringify(theme)}, theme: ${JSON.stringify(theme)},
uiCssPath: join(import.meta.dir, "ui.css"), uiCssPath: join(import.meta.dir, "ui.css"),
@@ -719,7 +751,7 @@ await createProductionServer(
realtime: ${config.realtime ? JSON.stringify(config.realtime) : "undefined"}, realtime: ${config.realtime ? JSON.stringify(config.realtime) : "undefined"},
publicDir: join(import.meta.dir, "public"), publicDir: join(import.meta.dir, "public"),
${hasStyles ? `stylesPath: join(import.meta.dir, "styles.css"),` : ""} ${hasStyles ? `stylesPath: join(import.meta.dir, "styles.css"),` : ""}
${hasStyles ? `stylesIncludeFramework: true,` : ""} ${hasStyles ? `stylesIncludeUi: true,` : ""}
${inlineStyles ? `inlineStyles: ${JSON.stringify(inlineStyles)},` : ""} ${inlineStyles ? `inlineStyles: ${JSON.stringify(inlineStyles)},` : ""}
assetVersion: ${JSON.stringify(assetVersion)}, assetVersion: ${JSON.stringify(assetVersion)},
clientRuntimes: ${JSON.stringify(emittedPluginAssets.runtimes)}, clientRuntimes: ${JSON.stringify(emittedPluginAssets.runtimes)},
+9 -2
View File
@@ -11,7 +11,10 @@ export type InspectTarget =
export function inspectComponent(appRoot: string, requestedName: string): unknown { export function inspectComponent(appRoot: string, requestedName: string): unknown {
const root = resolve(appRoot); 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( const component = router.components.find(
(candidate) => candidate.name.toLowerCase() === requestedName.toLowerCase(), (candidate) => candidate.name.toLowerCase() === requestedName.toLowerCase(),
); );
@@ -139,7 +142,11 @@ export async function inspectProject(appRoot: string, target: InspectTarget): Pr
database: database ?? "default", database: database ?? "default",
})); }));
const router = buildRouter(join(root, "app"), { const router = buildRouter(join(root, "app"), {
componentDirs: [uiComponentsDir(), ...contributions.componentDirs], componentDirs: [
uiComponentsDir(),
join(uiComponentsDir(), "..", "styles"),
...contributions.componentDirs,
],
externalRoutes: contributions.routes, externalRoutes: contributions.routes,
middlewareFiles: contributions.middleware, middlewareFiles: contributions.middleware,
}); });
+1 -2
View File
@@ -75,7 +75,7 @@ function writePluginArtifacts(root: string, contributions?: PluginContributions)
}); });
writeFileSync( writeFileSync(
join(typeDir, "wrnexus.plugins.generated.d.ts"), 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", "utf8",
); );
const docsDir = join(root, ".wrnexus", "documentation"); const docsDir = join(root, ".wrnexus", "documentation");
@@ -182,7 +182,6 @@ export function generateApplicationTypes(
.map((name) => join(root, name)) .map((name) => join(root, name))
.find(existsSync); .find(existsSync);
const code = `// AUTO-GENERATED by \`wrnexus generate types\` - do not edit. const code = `// AUTO-GENERATED by \`wrnexus generate types\` - do not edit.
/* eslint-disable @typescript-eslint/no-empty-object-type */
declare namespace WRNexusGenerated { declare namespace WRNexusGenerated {
type ApiContract<T> = T extends import("@wrnexus/core").DefinedEndpoint<infer I, infer O> type ApiContract<T> = T extends import("@wrnexus/core").DefinedEndpoint<infer I, infer O>
? { input: I; output: O } ? { input: I; output: O }
+17
View File
@@ -2098,6 +2098,23 @@ const MIGRATIONS: Migration[] = [
// start painting where they previously rendered as nothing. // 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. */ /** 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)" />`. - `@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. - `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. - `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`. - `seo { key = "value" ... }` — metadata merged into the generated `meta`.
- `style { <raw css> }` — inlined page/component stylesheet (repeatable). - `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. - `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; return functions.length > 0 || selectedBrowserImports(ast, functions).length > 0;
} }
function functionEntry( function _functionEntry(
ast: PageAst, ast: PageAst,
fn: RuntimeFunctionDecl, fn: RuntimeFunctionDecl,
availableFunctions: string[], availableFunctions: string[],
@@ -240,35 +240,50 @@ function functionEntry(
const syncStateFromContext = stateNames const syncStateFromContext = stateNames
.map((name) => `${name} = context.state.${name};`) .map((name) => `${name} = context.state.${name};`)
.join(" "); .join(" ");
const peerAliases = functionAliases const peerAliases = !stateNames.length
.map((name) => { ? functionAliases
const call = `context.functions[${JSON.stringify(name)}](...__wrnexusPeerArgs)`; .map(
if (!stateNames.length) { (name) =>
return `const ${name} = (...__wrnexusPeerArgs) => ${call};`; `const ${name} = (...__wrnexusPeerArgs) => context.functions[${JSON.stringify(name)}](...__wrnexusPeerArgs);`,
} )
return `const ${name} = (...__wrnexusPeerArgs) => { .join("\n")
${syncStateToContext} : `const __wrnexusFlush = () => { ${syncStateToContext} };
let __wrnexusPeerResult; const __wrnexusRestore = () => { ${syncStateFromContext} };
const __wrnexusPeer = (name, args) => {
__wrnexusFlush();
let result;
try { try {
__wrnexusPeerResult = ${call}; result = context.functions[name](...args);
} catch (__wrnexusPeerError) { } catch (error) {
${syncStateFromContext} __wrnexusRestore();
throw __wrnexusPeerError; throw error;
} }
if (__wrnexusPeerResult && typeof __wrnexusPeerResult.then === "function") { if (result && typeof result.then === "function") {
return Promise.resolve(__wrnexusPeerResult).finally(() => { ${syncStateFromContext} }); return Promise.resolve(result).finally(__wrnexusRestore);
} }
${syncStateFromContext} __wrnexusRestore();
return __wrnexusPeerResult; return result;
};`; };
}) ${functionAliases.map((name) => `const ${name} = (...__wrnexusPeerArgs) => __wrnexusPeer(${JSON.stringify(name)}, __wrnexusPeerArgs);`).join("\n")}`;
.join("\n");
const copyBack = stateNames const copyBack = stateNames
.map( .map(
(name) => (name) =>
`if (!Object.is(${name}, __wrnexusInitialState[${JSON.stringify(name)}])) context.state.${name} = ${name};`, `if (!Object.is(${name}, __wrnexusInitialState[${JSON.stringify(name)}])) context.state.${name} = ${name};`,
) )
.join("\n"); .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 body = eraseFunctionTypes(fn.body);
const runtimeBindings = [ const runtimeBindings = [
!parameterNames.has("output") ? "const output = context.output;" : "", !parameterNames.has("output") ? "const output = context.output;" : "",
@@ -282,12 +297,13 @@ function functionEntry(
${initialStateSnapshot} ${initialStateSnapshot}
${stateAliases} ${stateAliases}
${propAliases} ${propAliases}
${commitBinding}
${peerAliases} ${peerAliases}
${runtimeBindings} ${runtimeBindings}
try { try {
${body} ${body}
} finally { } finally {
${copyBack} ${stateNames.length ? "__wrnexusCommit();" : ""}
} }
}`; }`;
} }
@@ -301,21 +317,83 @@ export function generateBrowserModule(ast: PageAst): string {
const selectedImports = selectedBrowserImports(ast, functions); const selectedImports = selectedBrowserImports(ast, functions);
const imports = selectedImports.map((entry) => entry.code).join("\n"); const imports = selectedImports.map((entry) => entry.code).join("\n");
const importedBindings = [...new Set(selectedImports.flatMap((entry) => entry.bindings))]; 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} return `// generated WRNexusJS browser module for ${ast.name}
${imports} ${imports}
export const __wrnexusClientFunctions = { 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 __wrnexusClientState = ${JSON.stringify(state)};
export const __wrnexusOutputs = ${JSON.stringify(ast.outputs)}; export const __wrnexusOutputs = ${JSON.stringify(ast.outputs)};
export const __wrnexusImportedBindings = { ${importedBindings.join(", ")} }; 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 functions = {};
const scopedContext = { ...context, functions }; for (const name of Object.keys(implementations)) {
for (const [name, handler] of Object.entries(__wrnexusClientFunctions)) { functions[name] = (...args) => {
functions[name] = (...args) => handler(scopedContext, ...args); __wrnexusRestore();
return implementations[name](...args);
};
} }
return functions; 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}"`; const rendered = ` ${attr.name}="${compiledValue}"`;
if ( if (
wholeExpression ||
!attr.value.includes("{") || !attr.value.includes("{") ||
!exprRefsState(attr.value, ctx.stateNames) (!exprRefsState(attr.value, ctx.stateNames) && !exprRefsState(attr.value, ctx.propNames))
) { ) {
return rendered; return rendered;
} }
const marker = attrEscape(JSON.stringify([attr.name, attr.value])); 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(""); .join("");
@@ -883,7 +882,12 @@ function __wrnexusEscapeHtml(value: unknown): string {
return String(value).replace(/[&<>"']/g, (ch) => __wrnexusHtmlEscapes[ch] ?? ch); 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 = { const adapters = {
cookies: ctx.cookies, cookies: ctx.cookies,
session: ctx.session, 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") { if (typeof ctx.__wrnexusCallApi === "function") {
return await ctx.__wrnexusCallApi(path, method); 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(); 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) { for (const binding of __wrnexusSsrBindings) {
const data = await __wrnexusCallApi(binding.path, binding.method, ctx); const data = await __wrnexusCallApi(binding.path, binding.method, ctx);
const value = __wrnexusEvalData(data, binding.body, binding.helpers, 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)};`); out.push(`const __wrnexusSsrBindings = ${JSON.stringify(ssrBindings, null, 2)};`);
const decls = loopConsts.length > 0 ? loopConsts.join("\n") + "\n" : ""; const decls = loopConsts.length > 0 ? loopConsts.join("\n") + "\n" : "";
out.push( out.push(
`export default async function ${ast.name}(ctx: any) { `export default async function ${ast.name}(ctx: __WrnexusContext) {
${storeDeclarations} ${storeDeclarations}
${serverLoadAliases} ${serverLoadAliases}
${decls} ${decls}
const __state: ${stateType} = ${dynamicStateInitializer}; const __state: ${stateType} = ${dynamicStateInitializer};
${ssrStateAliases} ${ssrStateAliases}
${ssrComputedAliases} ${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) const __scopeValue = Object.entries(__hydrationState)
.map(([key, value]) => { .map(([key, value]) => {
let encoded: string; let encoded: string;
@@ -1473,13 +1477,13 @@ export function generate(ast: PageAst): string {
); );
} else { } else {
out.push( 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} ${storeDeclarations}
${serverLoadAliases} ${serverLoadAliases}
const __state: ${stateType} = ${dynamicStateInitializer}; const __state: ${stateType} = ${dynamicStateInitializer};
${ssrStateAliases} ${ssrStateAliases}
${ssrComputedAliases} ${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) const __scopeValue = Object.entries(__hydrationState)
.map(([key, value]) => { .map(([key, value]) => {
let encoded: string; let encoded: string;
@@ -1506,14 +1510,14 @@ export function generate(ast: PageAst): string {
if (staticShellBody !== undefined) { if (staticShellBody !== undefined) {
out.push( out.push(
`export async function __wrnexusBuildStaticShell(ctx: any = {}) { `export async function __wrnexusBuildStaticShell(ctx: import("@wrnexus/core").Context = {} as import("@wrnexus/core").Context) {
${storeDeclarations} ${storeDeclarations}
${serverLoadAliases} ${serverLoadAliases}
${loopConsts.length > 0 ? loopConsts.join("\n") : ""} ${loopConsts.length > 0 ? loopConsts.join("\n") : ""}
const __state: ${stateType} = ${dynamicStateInitializer}; const __state: ${stateType} = ${dynamicStateInitializer};
${ssrStateAliases} ${ssrStateAliases}
${ssrComputedAliases} ${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) const __scopeValue = Object.entries(__hydrationState)
.map(([key, value]) => { .map(([key, value]) => {
let encoded: string; let encoded: string;
@@ -1573,7 +1577,7 @@ export function generate(ast: PageAst): string {
}) })
.join("\n"); .join("\n");
const visible = exposed.filter((entry) => entry.name); 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 ${exposed
.filter((entry) => !entry.name) .filter((entry) => !entry.name)
.map((entry) => entry.body) .map((entry) => entry.body)
@@ -1600,7 +1604,7 @@ ${
); );
continue; 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 invalidate = (...tags: string[]) => {
const bucket = (ctx.locals.__wrnexusInvalidatedTags ??= []); const bucket = (ctx.locals.__wrnexusInvalidatedTags ??= []);
bucket.push(...tags.flat()); bucket.push(...tags.flat());
@@ -1631,7 +1635,7 @@ ${ast.actions
ast.apis.forEach((api, index) => { ast.apis.forEach((api, index) => {
const name = `__wrnexusApi_${api.method}_${index}`; const name = `__wrnexusApi_${api.method}_${index}`;
out.push(`// ${api.method} ${apiRoutePath(api.path)} 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( const entries = ast.apis.map(
@@ -1653,7 +1657,7 @@ const ${name} = async (ctx: any) => {${api.body}};`);
const handlers = ast.realtimes.flatMap((rt) => const handlers = ast.realtimes.flatMap((rt) =>
rt.handlers.map((h) => { rt.handlers.map((h) => {
const params = ["ws", ...h.args].join(", "); 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};`); out.push(`export const websocket = {\n${handlers.join("\n")}\n};`);
@@ -1673,6 +1677,8 @@ const ${name} = async (ctx: any) => {${api.body}};`);
interface CompCtx { interface CompCtx {
/** State names — text referencing any of them stays a reactive client mustache. */ /** State names — text referencing any of them stays a reactive client mustache. */
stateNames: Set<string>; 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. */ /** Component functions can read state and therefore make their callers reactive. */
functionNames: Set<string>; functionNames: Set<string>;
/** Rewrite reserved-word prop/state identifiers to their safe const names. */ /** 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 { 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 { 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 // Loop variable (from data-for): leave a literal client mustache — the
// list renderer fills it per item; it has no server-side value. // list renderer fills it per item; it has no server-side value.
out += escLit(`{${expr}}`); out += escLit(`{${expr}}`);
} else if (expr === "content") {
out += `\${__wireRaw(${ctx.resolveExpr(expr)})}`;
} else if (exprRefsComponentReactiveValue(expr, ctx)) { } else if (exprRefsComponentReactiveValue(expr, ctx)) {
// State interpolation: bake the initial value AND keep it reactive via a // 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 // 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)}">`) + escLit(`<span data-text="${attrEscape(expr)}">`) +
`\${__wireHtml(${ctx.resolveExpr(expr)})}` + `\${__wireHtml(${ctx.resolveExpr(expr)})}` +
escLit(`</span>`); escLit(`</span>`);
} else if (expr === "content") {
out += `\${__wireRaw(${ctx.resolveExpr(expr)})}`;
} else { } else {
out += `\${__wireHtml(${ctx.resolveExpr(expr)})}`; out += `\${__wireHtml(${ctx.resolveExpr(expr)})}`;
} }
@@ -2346,6 +2356,7 @@ function generateComponent(ast: PageAst): string {
}; };
const ctx: CompCtx = { const ctx: CompCtx = {
stateNames, stateNames,
propNames: new Set(effectiveProps.map((entry) => entry.name)),
functionNames: new Set( functionNames: new Set(
ast.runtimeFunctions.filter((fn) => fn.runtime !== "server").map((fn) => fn.name), 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 styles = ast.styles.map((body) => body.trim()).filter(Boolean);
const styleTag = escLit(localStyleTag(ast, styles)); const styleTag = escLit(localStyleTag(ast, styles));
// A component needs a reactive scope only when it has state or event handlers. // Props remain server-rendered and also become signals so a parent can drive
// Prop-driven text/attributes are baked server-side, so static components ship // a mounted child after hydration.
// no JavaScript at all.
const behavior = componentBehavior(ast); const behavior = componentBehavior(ast);
const needsScope = const needsScope =
ast.runtime !== "server" && ast.runtime !== "server" &&
(browserStates.length > 0 || (effectiveProps.length > 0 ||
browserStates.length > 0 ||
ast.computed.length > 0 || ast.computed.length > 0 ||
viewHasEvents(ast.view) || viewHasEvents(ast.view) ||
behavior !== null); behavior !== null);
@@ -2407,7 +2418,7 @@ function generateComponent(ast: PageAst): string {
); );
} }
decls.push( 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")) { 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) { if (v === undefined || v === null) {
return def; return def;
} }
@@ -2522,14 +2533,14 @@ function generateComponent(ast: PageAst): string {
if (typeof v === "string") { if (typeof v === "string") {
try { try {
const parsed = JSON.parse(v); 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 { } catch {
if (declared === "array") throw new TypeError("Expected an array prop"); throw new TypeError("Expected an array prop '" + propName + "'");
return def;
} }
} }
return def; throw new TypeError("Expected an array prop '" + propName + "'");
} }
if (declared === "object" || (def !== null && typeof def === "object")) { if (declared === "object" || (def !== null && typeof def === "object")) {
@@ -2545,23 +2556,19 @@ function generateComponent(ast: PageAst): string {
try { try {
const parsed = JSON.parse(v); const parsed = JSON.parse(v);
return ( if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
parsed !== null && throw new TypeError("Expected an object prop '" + propName + "'");
typeof parsed === "object" && }
!Array.isArray(parsed) return parsed;
)
? parsed
: def;
} catch { } catch {
if (declared === "object") throw new TypeError("Expected an object prop"); throw new TypeError("Expected an object prop '" + propName + "'");
return def;
} }
} }
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") { if (declared === "function" && typeof v !== "function") {
throw new TypeError("Expected a function prop"); throw new TypeError("Expected a function prop");
} }
@@ -2569,15 +2576,15 @@ function generateComponent(ast: PageAst): string {
} }
function __restProps( function __restProps(
props: Record<string, any>, props: Record<string, unknown>,
declared: Set<string>, declared: Set<string>,
): Record<string, any> { ): Record<string, unknown> {
return Object.fromEntries( return Object.fromEntries(
Object.entries(props).filter(([name]) => !declared.has(name)), Object.entries(props).filter(([name]) => !declared.has(name)),
); );
} }
function __wireHtml(v: any): string { function __wireHtml(v: unknown): string {
return String(v == null ? "" : v).replace( return String(v == null ? "" : v).replace(
/[&<>]/g, /[&<>]/g,
(c) => (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( return String(v == null ? "" : v).replace(
/[&<>"]/g, /[&<>"]/g,
(c) => (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 || return value === true ||
value === "true" || value === "true" ||
value === "" || 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 ""; if (value === null || typeof value !== "object" || Array.isArray(value)) return "";
const booleanAttributes = new Set(${JSON.stringify([...HTML_BOOLEAN_ATTRIBUTES])}); const booleanAttributes = new Set(${JSON.stringify([...HTML_BOOLEAN_ATTRIBUTES])});
@@ -2628,10 +2635,12 @@ function __wireSpreadAttrs(value: any): string {
lowerName === "style" || lowerName === "style" ||
lowerName === "slot" || lowerName === "slot" ||
lowerName === "data-component" || lowerName === "data-component" ||
// Internal markers must not leak through a spread -- except the // Internal markers must not leak through a spread. Parent-owned output
// parent's output handlers, whose whole job is to ride from the mount // and prop bindings ride from the mount onto the
// onto the view root so the mounting scope can bind them there. // rendered child root so the mounting scope can bind them there.
(lowerName.startsWith("data-wrn") && !lowerName.startsWith("data-wrn-out-")) (lowerName.startsWith("data-wrn") &&
!lowerName.startsWith("data-wrn-out-") &&
!lowerName.startsWith("data-wrn-prop-bind-"))
) { ) {
continue; continue;
} }
@@ -2648,7 +2657,7 @@ function __wireSpreadAttrs(value: any): string {
return attributes.join(""); return attributes.join("");
} }
function __wireProp(v: any): string { function __wireProp(v: unknown): string {
const value = const value =
v !== null && typeof v === "object" v !== null && typeof v === "object"
? JSON.stringify(v) ? JSON.stringify(v)
@@ -2657,18 +2666,18 @@ function __wireProp(v: any): string {
return __wireAttr(value); return __wireAttr(value);
} }
function __wireRaw(v: any): string { function __wireRaw(v: unknown): string {
return String(v == null ? "" : v); return String(v == null ? "" : v);
}`); }`);
if (hasServerEach) { 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"); return __WrnexusBuffer.from(JSON.stringify(value), "utf8").toString("base64");
}`); }`);
} }
if (needsScope) { if (needsScope) {
out.push(`function __wrnexusSerializeScopeValue(value: any): string { out.push(`function __wrnexusSerializeScopeValue(value: unknown): string {
if (value === undefined) { if (value === undefined) {
return "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) return Object.keys(obj)
.map( .map(
(key) => (key) =>
@@ -2723,7 +2732,7 @@ function __wireRaw(v: any): string {
const serverFunctionSource = serverFunctions ? `${serverFunctions}\n` : ""; const serverFunctionSource = serverFunctions ? `${serverFunctions}\n` : "";
out.push( 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` + ` const __p = props || {};\n` +
(decls.length > 0 ? decls.join("\n") + "\n" : "") + (decls.length > 0 ? decls.join("\n") + "\n" : "") +
serverFunctionSource + serverFunctionSource +
@@ -2738,7 +2747,7 @@ function __wireRaw(v: any): string {
return out.join("\n\n") + "\n"; return out.join("\n\n") + "\n";
} }
function __wireRaw(v: any): string { function __wireRaw(v: unknown): string {
return String(v == null ? "" : v); return String(v == null ? "" : v);
} }
@@ -2747,19 +2756,19 @@ function wholeAttributeExpression(value: string): string | null {
return match?.[1]?.trim() || null; return match?.[1]?.trim() || null;
} }
function __wireHtml(v: any): string { function __wireHtml(v: unknown): string {
return String(v == null ? "" : v).replace(/[&<>]/g, (c) => return String(v == null ? "" : v).replace(/[&<>]/g, (c) =>
c === "&" ? "&amp;" : c === "<" ? "&lt;" : "&gt;", c === "&" ? "&amp;" : c === "<" ? "&lt;" : "&gt;",
); );
} }
function __wireAttr(v: any): string { function __wireAttr(v: unknown): string {
return String(v == null ? "" : v).replace(/[&<>"]/g, (c) => return String(v == null ? "" : v).replace(/[&<>"]/g, (c) =>
c === "&" ? "&amp;" : c === "<" ? "&lt;" : c === ">" ? "&gt;" : "&quot;", c === "&" ? "&amp;" : c === "<" ? "&lt;" : c === ">" ? "&gt;" : "&quot;",
); );
} }
function __wireProp(v: any): string { function __wireProp(v: unknown): string {
const value = const value =
v !== null && typeof v === "object" ? JSON.stringify(v) : String(v == null ? "" : v); v !== null && typeof v === "object" ? JSON.stringify(v) : String(v == null ? "" : v);
@@ -45,7 +45,7 @@ export interface CounterOutputs {
"change"(value: number): void; "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) { if (v === undefined || v === null) {
return def; return def;
} }
@@ -70,14 +70,14 @@ function __coerce(v: any, def: any, declared: string = "unknown"): any {
if (typeof v === "string") { if (typeof v === "string") {
try { try {
const parsed = JSON.parse(v); 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 { } catch {
if (declared === "array") throw new TypeError("Expected an array prop"); throw new TypeError("Expected an array prop '" + propName + "'");
return def;
} }
} }
return def; throw new TypeError("Expected an array prop '" + propName + "'");
} }
if (declared === "object" || (def !== null && typeof def === "object")) { if (declared === "object" || (def !== null && typeof def === "object")) {
@@ -93,23 +93,19 @@ function __coerce(v: any, def: any, declared: string = "unknown"): any {
try { try {
const parsed = JSON.parse(v); const parsed = JSON.parse(v);
return ( if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
parsed !== null && throw new TypeError("Expected an object prop '" + propName + "'");
typeof parsed === "object" && }
!Array.isArray(parsed) return parsed;
)
? parsed
: def;
} catch { } catch {
if (declared === "object") throw new TypeError("Expected an object prop"); throw new TypeError("Expected an object prop '" + propName + "'");
return def;
} }
} }
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") { if (declared === "function" && typeof v !== "function") {
throw new TypeError("Expected a function prop"); throw new TypeError("Expected a function prop");
} }
@@ -117,15 +113,15 @@ function __coerce(v: any, def: any, declared: string = "unknown"): any {
} }
function __restProps( function __restProps(
props: Record<string, any>, props: Record<string, unknown>,
declared: Set<string>, declared: Set<string>,
): Record<string, any> { ): Record<string, unknown> {
return Object.fromEntries( return Object.fromEntries(
Object.entries(props).filter(([name]) => !declared.has(name)), Object.entries(props).filter(([name]) => !declared.has(name)),
); );
} }
function __wireHtml(v: any): string { function __wireHtml(v: unknown): string {
return String(v == null ? "" : v).replace( return String(v == null ? "" : v).replace(
/[&<>]/g, /[&<>]/g,
(c) => (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( return String(v == null ? "" : v).replace(
/[&<>"]/g, /[&<>"]/g,
(c) => (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 || return value === true ||
value === "true" || value === "true" ||
value === "" || 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 ""; 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"]); 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 === "style" ||
lowerName === "slot" || lowerName === "slot" ||
lowerName === "data-component" || lowerName === "data-component" ||
// Internal markers must not leak through a spread -- except the // Internal markers must not leak through a spread. Parent-owned output
// parent's output handlers, whose whole job is to ride from the mount // and prop bindings ride from the mount onto the
// onto the view root so the mounting scope can bind them there. // rendered child root so the mounting scope can bind them there.
(lowerName.startsWith("data-wrn") && !lowerName.startsWith("data-wrn-out-")) (lowerName.startsWith("data-wrn") &&
!lowerName.startsWith("data-wrn-out-") &&
!lowerName.startsWith("data-wrn-prop-bind-"))
) { ) {
continue; continue;
} }
@@ -196,7 +194,7 @@ function __wireSpreadAttrs(value: any): string {
return attributes.join(""); return attributes.join("");
} }
function __wireProp(v: any): string { function __wireProp(v: unknown): string {
const value = const value =
v !== null && typeof v === "object" v !== null && typeof v === "object"
? JSON.stringify(v) ? JSON.stringify(v)
@@ -205,11 +203,11 @@ function __wireProp(v: any): string {
return __wireAttr(value); return __wireAttr(value);
} }
function __wireRaw(v: any): string { function __wireRaw(v: unknown): string {
return String(v == null ? "" : v); return String(v == null ? "" : v);
} }
function __wrnexusSerializeScopeValue(value: any): string { function __wrnexusSerializeScopeValue(value: unknown): string {
if (value === undefined) { if (value === undefined) {
return "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) return Object.keys(obj)
.map( .map(
(key) => (key) =>
@@ -262,14 +260,14 @@ function __wrnexusSerializeScopeValue(value: any): string {
export function render(props: CounterProps = {} as CounterProps): string { export function render(props: CounterProps = {} as CounterProps): string {
const __p = props || {}; 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"])); const __attrs = __restProps(__p, new Set(["label"]));
let count = (0); let count = (0);
const __scopeState = { "label": label, "count": count }; const __scopeState = { "label": label, "count": count };
const __scope = __wrnexusScopeDecl(__scopeState); const __scope = __wrnexusScopeDecl(__scopeState);
const __scopePayload = __WrnexusBuffer.from(JSON.stringify(__scopeState), "utf8").toString("base64"); 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__"> 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>\`; </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; const render = component.render as (props?: Record<string, unknown>) => string;
expect(() => render()).toThrow("TypedInput requires prop 'label' (string)"); expect(() => render()).toThrow("TypedInput requires prop 'label' (string)");
expect(render({ label: "Total", count: "4", enabled: "true" })).toContain( const rendered = render({ label: "Total", count: "4", enabled: "true" });
"<span>Total:4:true</span>", 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", count: "many" })).toThrow("Expected a finite number prop");
expect(() => render({ label: "Total", enabled: "sometimes" })).toThrow("Expected a boolean 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 render = mod.render as (props?: Record<string, unknown>) => string;
const enabled = render(); const enabled = render();
expect(enabled).not.toContain(" disabled"); expect(enabled).not.toContain("<button disabled");
expect(enabled).not.toContain(" checked"); expect(enabled).not.toContain("<input checked");
expect(enabled).not.toContain(" required"); expect(enabled).not.toContain("<input required");
const disabled = render({ disabled: "true", checked: "true", required: "true" }); const disabled = render({ disabled: "true", checked: "true", required: "true" });
expect(disabled).toContain("<button disabled>"); expect(disabled).toContain("<button disabled");
expect(disabled).toContain("<input checked required>"); expect(disabled).toContain("<input checked");
expect(disabled).toContain(" required");
}); });
test("components safely forward undeclared HTML attributes to their root", async () => { 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("variant=");
expect(html).not.toContain("onclick="); expect(html).not.toContain("onclick=");
expect(html).not.toContain("style="); 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 () => { 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 render = mod.render as (props?: Record<string, unknown>) => string;
const html = render({ label: "Save", class: "wrapper", formaction: "/save" }); 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).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( 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}`, `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" }); 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('class="wire-btn wire-btn--primary mt-2"');
expect(out).toContain("Save &lt;b&gt;"); // html-escaped 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( 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}`, `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" }); const out = render({ start: "10", label: "Score" });
expect(out).toContain('data-scope="start: 10, label: &quot;Score&quot;, count: 10"'); expect(out).toContain('data-scope="start: 10, label: &quot;Score&quot;, count: 10"');
expect(out).toContain('data-on-click="count++"'); expect(out).toContain('data-on-click="count++"');
// label baked as static text; count baked as its initial value AND kept live. expect(out).toContain('<span data-text="label">Score</span>: <span data-text="count">10</span>');
expect(out).toContain('Score: <span data-text="count">10</span>');
expect(out).toContain('data-scope="'); 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", () => { test("parses a layout block", () => {
const ast = parse(` const ast = parse(`
layout AppLayout { layout AppLayout {
@@ -1083,6 +1097,19 @@ page Home {
expect(output).toContain("__wrnexusPropAttr(["); 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", () => { test("component functions are available during server rendering", () => {
const output = generate( const output = generate(
parse(` 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).not.toContain('__wireBooleanAttr("checked"');
expect(code).toContain("data-wrn-bind-"); 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 { expect, test } from "bun:test";
import { gzipSync } from "bun";
import { generateTargets, parse } from "../src/index.ts"; import { generateTargets, parse } from "../src/index.ts";
const ast = parse(`component ConfirmDialog { 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> } view { <button @click='run()'>{value}</button> }
}`), }`),
); );
expect(targets.browser).toContain("context.functions"); expect(targets.browser).toContain('implementations["increment"]');
const executable = new Function( const executable = new Function(
`${targets.browser.replace(/^export\s+/gm, "")}\nreturn { bindClientScope };`, `${targets.browser.replace(/^export\s+/gm, "")}\nreturn { bindClientScope };`,
)() as { bindClientScope: (context: Record<string, unknown>) => Record<string, () => void> }; )() 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?.(); functions.run?.();
expect(state.value).toBe(1); 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 { REALTIME_RUNTIME } from "./realtime-runtime.ts";
export { ACTION_RUNTIME } from "./action-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). */ /** The reactive runtime served at `/__wrnexus/reactive.js` (plain browser JS). */
export function getReactiveRuntime(): string { export function getReactiveRuntime(development = false): string {
return REACTIVE_RUNTIME; 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`. */ /** The client-side navigation runtime served at `/__wrnexus/nav.js`. */
+125 -2
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) { function scheduleUpdateHook(element, callback) {
pendingUpdateHooks.set(element, callback); pendingUpdateHooks.set(element, callback);
if (updateHooksScheduled) return; if (updateHooksScheduled) return;
@@ -2493,6 +2555,9 @@ export const REACTIVE_RUNTIME = String.raw`
locals.event = undefined; locals.event = undefined;
locals.$event = undefined; locals.$event = undefined;
try { try {
/*__WRNEXUS_DEV_START__*/
warnMissingBindingFunction(outStmt, function (name) { return peekScope(name); }, componentRoot);
/*__WRNEXUS_DEV_END__*/
return runStmt(outStmt, locals); return runStmt(outStmt, locals);
} catch (error) { } catch (error) {
console.error( console.error(
@@ -2535,8 +2600,42 @@ export const REACTIVE_RUNTIME = String.raw`
}); });
}); });
if (behavior) { // Prop expressions belong to the parent that mounted the component. The
installBehaviorFunctions(behavior.functions); // 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 = { var publicScopeApi = {
get: peekScope, get: peekScope,
set: writeScope, set: writeScope,
@@ -2551,6 +2650,9 @@ export const REACTIVE_RUNTIME = String.raw`
select.__wrnexusScopeApi = publicScopeApi; select.__wrnexusScopeApi = publicScopeApi;
}); });
if (behavior) {
installBehaviorFunctions(behavior.functions);
(behavior.effects || []).forEach(function (source) { (behavior.effects || []).forEach(function (source) {
if (typeof source !== "string" || !source.trim()) return; if (typeof source !== "string" || !source.trim()) return;
reactive(function () { reactive(function () {
@@ -2762,6 +2864,7 @@ export const REACTIVE_RUNTIME = String.raw`
* cleared, so repositioning is idempotent -- re-running it on a panel that * cleared, so repositioning is idempotent -- re-running it on a panel that
* is already on screen changes nothing and cannot drift. * is already on screen changes nothing and cannot drift.
*/ */
/*__WRNEXUS_CONTROLLERS_PRIMARY_START__*/
var ANCHORED_SELECTOR = "[data-wrn-anchored]"; var ANCHORED_SELECTOR = "[data-wrn-anchored]";
var ANCHOR_MARGIN = 8; var ANCHOR_MARGIN = 8;
var anchoredScheduled = false; var anchoredScheduled = false;
@@ -3378,6 +3481,8 @@ export const REACTIVE_RUNTIME = String.raw`
document.addEventListener("pointercancel", endDrag); document.addEventListener("pointercancel", endDrag);
} }
/*__WRNEXUS_CONTROLLERS_PRIMARY_END__*/
function hydrateScopes(root) { function hydrateScopes(root) {
var host = root || document; var host = root || document;
@@ -3403,8 +3508,12 @@ export const REACTIVE_RUNTIME = String.raw`
hydratePreferenceControllers(host); hydratePreferenceControllers(host);
hydrateSelectControllers(host); hydrateSelectControllers(host);
hydratePinInputControllers(host); hydratePinInputControllers(host);
/*__WRNEXUS_DEV_START__*/
warnMissingThemeTokens();
/*__WRNEXUS_DEV_END__*/
} }
/*__WRNEXUS_CONTROLLERS_UI_START__*/
var navbarOutsideClickBound = false; var navbarOutsideClickBound = false;
var preferenceOutsideClickBound = false; var preferenceOutsideClickBound = false;
@@ -3923,6 +4032,8 @@ export const REACTIVE_RUNTIME = String.raw`
} }
} }
/*__WRNEXUS_CONTROLLERS_UI_END__*/
function invokeComponentOutput(root, name, payload) { function invokeComponentOutput(root, name, payload) {
if (!root || !name) return undefined; if (!root || !name) return undefined;
var registry = root.__wrnexusOutputHandlers; var registry = root.__wrnexusOutputHandlers;
@@ -3953,6 +4064,15 @@ export const REACTIVE_RUNTIME = String.raw`
? Promise.all(values) ? Promise.all(values)
: values[values.length - 1]; : 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); return dispatchComponentEvent(root, name, payload);
} }
@@ -4021,6 +4141,7 @@ export const REACTIVE_RUNTIME = String.raw`
} }
} }
/*__WRNEXUS_CONTROLLERS_PIN_START__*/
function setupPinInputController(root) { function setupPinInputController(root) {
if (!root || root.__wrnexusPinInputController) return; if (!root || root.__wrnexusPinInputController) return;
var cells = Array.prototype.slice.call(root.querySelectorAll("[data-pin-cell]")); var cells = Array.prototype.slice.call(root.querySelectorAll("[data-pin-cell]"));
@@ -4196,6 +4317,8 @@ export const REACTIVE_RUNTIME = String.raw`
inputs.forEach(setupPinInputController); inputs.forEach(setupPinInputController);
} }
/*__WRNEXUS_CONTROLLERS_PIN_END__*/
function parseScopeDecl(decl) { function parseScopeDecl(decl) {
var initial = {}; var initial = {};
splitTopLevel(decl, ",").forEach(function (part) { splitTopLevel(decl, ",").forEach(function (part) {
+159 -2
View File
@@ -1,10 +1,11 @@
import { test, expect, beforeEach } from "bun:test"; import { test, expect, beforeEach } from "bun:test";
import { Window } from "happy-dom"; import { Window } from "happy-dom";
import { REACTIVE_RUNTIME } from "../src/reactive-runtime.ts"; import { REACTIVE_RUNTIME } from "../src/reactive-runtime.ts";
import { getComponentControllerRuntime, getReactiveRuntime } from "../src/index.ts";
import { mountHtml } from "@wrnexus/test"; import { mountHtml } from "@wrnexus/test";
// Fresh DOM per test, with the runtime's globals bound. // 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>; const win = new Window() as unknown as Window & Record<string, unknown>;
win.document.body.innerHTML = `<div id="app">${html}</div>`; win.document.body.innerHTML = `<div id="app">${html}</div>`;
(globalThis as Record<string, unknown>).window = win; (globalThis as Record<string, unknown>).window = win;
@@ -24,7 +25,8 @@ function mount(html: string): Window {
(globalThis as Record<string, unknown>).CustomEvent = ( (globalThis as Record<string, unknown>).CustomEvent = (
win as unknown as { CustomEvent: unknown } win as unknown as { CustomEvent: unknown }
).CustomEvent; ).CustomEvent;
(0, eval)(REACTIVE_RUNTIME); (0, eval)(runtime);
if (controllers) (0, eval)(controllers);
// Hydrate deterministically (auto-init waits on DOMContentLoaded, which the // Hydrate deterministically (auto-init waits on DOMContentLoaded, which the
// test window may not fire). setupScope is idempotent, so this is safe. // test window may not fire). setupScope is idempotent, so this is safe.
const w = win as unknown as { __wrnexusHydrateScopes?: (root: unknown) => void }; const w = win as unknown as { __wrnexusHydrateScopes?: (root: unknown) => void };
@@ -40,6 +42,31 @@ beforeEach(() => {
delete (globalThis as Record<string, unknown>).MutationObserver; 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", () => { test("hydrates {expr} mustaches from data-scope", () => {
const win = mount(`<div data-scope="count: 0"><span>{count}, {count * 2}</span></div>`); const win = mount(`<div data-scope="count: 0"><span>{count}, {count * 2}</span></div>`);
expect(win.document.querySelector("span")!.textContent).toBe("0, 0"); 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"); 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)", () => { test("independent signals in one scope update correctly (dependency tracking)", () => {
const win = mount( const win = mount(
`<div data-scope="a: 0, b: 100"> `<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"); 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 ---------------- // --- browser globals + regex literals in client expressions ----------------
// Client functions and inline handlers are interpreted by the runtime's own // 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 // 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; if (!row) return undefined;
try { try {
return { data: JSON.parse(row.data) as Record<string, unknown>, expiresAt: row.expiresAt }; 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; return undefined;
} }
}, },
+17 -1
View File
@@ -12,12 +12,14 @@
import { import {
getActionRuntime, getActionRuntime,
getComponentControllerRuntime,
getReactiveRuntime, getReactiveRuntime,
getNavRuntime, getNavRuntime,
getRealtimeRuntime, getRealtimeRuntime,
} from "@wrnexus/csr"; } from "@wrnexus/csr";
import { import {
renderStyles, renderStyles,
renderActiveThemeCss,
renderThemeCss, renderThemeCss,
renderThemeRuntime, renderThemeRuntime,
type ResolvedTheme, type ResolvedTheme,
@@ -93,7 +95,9 @@ export function createDevAssetServer(
if (pathname.startsWith("/__wrnexus/client/")) { if (pathname.startsWith("/__wrnexus/client/")) {
return serveWrnBrowserArtifact(pathname) ?? new Response("Not Found", { status: 404 }); 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/nav.js") return jsResponse(getNavRuntime());
if (pathname === "/__wrnexus/realtime.js") return jsResponse(getRealtimeRuntime()); if (pathname === "/__wrnexus/realtime.js") return jsResponse(getRealtimeRuntime());
if (pathname === "/__wrnexus/actions.js") return jsResponse(getActionRuntime()); if (pathname === "/__wrnexus/actions.js") return jsResponse(getActionRuntime());
@@ -119,6 +123,18 @@ export function createDevAssetServer(
? cssResponse(renderThemeCss(theme)) ? cssResponse(renderThemeCss(theme))
: new Response("Not Found", { status: 404 }); : 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") { if (pathname === "/__wrnexus/theme.js") {
return theme return theme
? jsResponse(renderThemeRuntime(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. * 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 { resolve, dirname, isAbsolute, join } from "node:path";
import type { Middleware, Mode, SecurityConfig, SeoConfig } from "@wrnexus/core"; import type { Middleware, Mode, SecurityConfig, SeoConfig } from "@wrnexus/core";
import { buildRouter, type Router } from "@wrnexus/router"; 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 pluginContributions = await pluginRunner.contributions();
const virtualModules = new Map<string, string>(); 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 }); mkdirSync(virtualDir, { recursive: true });
for (const [index, module] of pluginContributions.virtualModules.entries()) { for (const [index, module] of pluginContributions.virtualModules.entries()) {
const output = join(virtualDir, `plugin-${index}.ts`); 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}`); console.log(`[wrnexus:plugin] contributed routes: ${pluginContributions.routes.length}`);
const pluginToolbarPanels = await pluginRunner.devToolbarPanels(); 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 hmr = opts.hmr ?? mode === "development";
const port = opts.port ?? 3000; const port = opts.port ?? 3000;
@@ -327,12 +336,13 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
resetDevCache({ resetDevCache({
rootDir: appRoot, rootDir: appRoot,
cacheDir,
enabled: process.env.WRNEXUS_PRESERVE_CACHE !== "1", enabled: process.env.WRNEXUS_PRESERVE_CACHE !== "1",
}); });
// Compile every `.wrn` into ONE cache dir at the project root, instead of a // 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). // `.wrnexus/` next to each source file (and inside node_modules UI dirs).
setCompileCacheDir(join(appRoot, ".wrnexus")); setCompileCacheDir(cacheDir);
const theme = resolveThemeConfig(opts.theme); const theme = resolveThemeConfig(opts.theme);
const uiStylesPath = uiCssPath(); const uiStylesPath = uiCssPath();
@@ -739,6 +749,7 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
unsubscribeDevToolbar?.(); unsubscribeDevToolbar?.();
server.stop(); server.stop();
void pluginRunner.hook("shutdown"); 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>> { export async function loadModule(file: string): Promise<Record<string, unknown>> {
file = resolve(file);
let mod = moduleCache.get(file); let mod = moduleCache.get(file);
if (!mod) { if (!mod) {
mod = (async () => { 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. */ /** Forget one module and force its next dynamic import to bypass Bun's import cache. */
export function invalidateModule(file: string): void { export function invalidateModule(file: string): void {
file = resolve(file);
moduleCache.delete(file); moduleCache.delete(file);
moduleVersions.set(file, (moduleVersions.get(file) ?? 0) + 1); moduleVersions.set(file, (moduleVersions.get(file) ?? 0) + 1);
} }
+34
View File
@@ -20,6 +20,7 @@ import {
} from "@wrnexus/router"; } from "@wrnexus/router";
import { import {
getActionRuntime, getActionRuntime,
getComponentControllerRuntime,
getReactiveRuntime, getReactiveRuntime,
getNavRuntime, getNavRuntime,
getRealtimeRuntime, getRealtimeRuntime,
@@ -90,12 +91,18 @@ export interface ProdOptions {
inlineStyles?: string; inlineStyles?: string;
/** `stylesPath` contains theme + UI + app CSS in cascade order. */ /** `stylesPath` contains theme + UI + app CSS in cascade order. */
stylesIncludeFramework?: boolean; stylesIncludeFramework?: boolean;
/** `stylesPath` already contains the shared Wire UI stylesheet. */
stylesIncludeUi?: boolean;
/** Absolute path to the pre-built reactive runtime. */ /** Absolute path to the pre-built reactive runtime. */
reactivePath?: string; reactivePath?: string;
/** Absolute path to the on-demand component controller runtime. */
controllersPath?: string;
/** Absolute directory containing bundled per-WRN browser modules. */ /** Absolute directory containing bundled per-WRN browser modules. */
clientModulesDir?: string; clientModulesDir?: string;
/** Absolute path to the pre-built theme stylesheet (`theme.css`). */ /** Absolute path to the pre-built theme stylesheet (`theme.css`). */
themePath?: string; themePath?: string;
/** Pre-built active theme/accent stylesheets, loaded on demand. */
themeAssetsDir?: string;
/** Absolute path to the pre-built theme runtime (`theme.js`). */ /** Absolute path to the pre-built theme runtime (`theme.js`). */
themeJsPath?: string; themeJsPath?: string;
/** Resolved theme config: enables `<html data-theme>` + `theme.css` link. */ /** 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 }); 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") if (pathname === "/__wrnexus/nav.js")
return new Response(getNavRuntime(), { headers: JS_HEADERS }); return new Response(getNavRuntime(), { headers: JS_HEADERS });
if (pathname === "/__wrnexus/realtime.js") if (pathname === "/__wrnexus/realtime.js")
@@ -359,6 +370,28 @@ function createProdAssetServer(opts: ProdOptions): AssetServer {
return new Response(opts.schemasJs ?? "window.__wireSchemas={};", { headers: JS_HEADERS }); return new Response(opts.schemasJs ?? "window.__wireSchemas={};", { headers: JS_HEADERS });
} }
if (pathname === "/__wrnexus/theme.css") return serveFile(opts.themePath, CSS_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/theme.js") return serveFile(opts.themeJsPath, JS_HEADERS);
if (pathname === "/__wrnexus/ui.css") return serveFile(opts.uiCssPath, CSS_HEADERS); if (pathname === "/__wrnexus/ui.css") return serveFile(opts.uiCssPath, CSS_HEADERS);
if (pathname === "/__wrnexus/framework.css") if (pathname === "/__wrnexus/framework.css")
@@ -470,6 +503,7 @@ export function createProductionHandlers(
i18n: opts.i18n, i18n: opts.i18n,
inlineStyles: opts.inlineStyles, inlineStyles: opts.inlineStyles,
stylesIncludeFramework: opts.stylesIncludeFramework, stylesIncludeFramework: opts.stylesIncludeFramework,
stylesIncludeUi: opts.stylesIncludeUi,
assetVersion: opts.assetVersion, assetVersion: opts.assetVersion,
clientRuntimes: opts.clientRuntimes, clientRuntimes: opts.clientRuntimes,
head: opts.head, head: opts.head,
+23 -76
View File
@@ -66,12 +66,12 @@ import type { StoreDefinition } from "@wrnexus/store";
import type { ClientRuntimeDefinition } from "@wrnexus/plugin"; import type { ClientRuntimeDefinition } from "@wrnexus/plugin";
import { CacheCoordinator } from "@wrnexus/cache"; import { CacheCoordinator } from "@wrnexus/cache";
import { generateServiceWorker } from "@wrnexus/pwa"; 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 { import {
ACCENT_COOKIE, ACCENT_COOKIE,
THEME_COOKIE, THEME_COOKIE,
THEME_CSS_HREF, activeThemeCssHref,
THEME_JS_HREF,
resolveAccentName, resolveAccentName,
resolveThemeName, resolveThemeName,
type MobileConfig, type MobileConfig,
@@ -81,7 +81,6 @@ import {
type TenancyConfig, type TenancyConfig,
} from "@wrnexus/styles"; } from "@wrnexus/styles";
import { import {
I18N_JS_HREF,
renderI18nData, renderI18nData,
makeT, makeT,
resolveLang, resolveLang,
@@ -161,6 +160,7 @@ export interface RuntimeDeps {
hasFrameworkStyles?: boolean; hasFrameworkStyles?: boolean;
/** App stylesheet already contains theme + UI CSS and is the only CSS request needed. */ /** App stylesheet already contains theme + UI CSS and is the only CSS request needed. */
stylesIncludeFramework?: boolean; stylesIncludeFramework?: boolean;
stylesIncludeUi?: boolean;
/** Resolved theme config: enables `/__wrnexus/theme.css` + `<html data-theme>`. */ /** Resolved theme config: enables `/__wrnexus/theme.css` + `<html data-theme>`. */
theme?: ResolvedTheme; theme?: ResolvedTheme;
/** Resolved i18n bundle: enables `ctx.t`, `<html lang>`, and `{t:key}` markers. */ /** 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 // 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. // own CSS (loaded last) can override both the tokens and the UI classes.
const headParts: string[] = []; const headParts: string[] = [];
if (deps.hasFrameworkStyles && !deps.stylesIncludeFramework) { if (!deps.theme && deps.hasFrameworkStyles && !deps.stylesIncludeFramework) {
headParts.push( headParts.push(
`<link rel="stylesheet" href="${versionAssetUrl("/__wrnexus/framework.css", deps.assetVersion)}" />`, `<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( headParts.push(
`<link rel="stylesheet" href="${versionAssetUrl("/__wrnexus/ui.css", deps.assetVersion)}" />`, `<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); if (isMobileRequest) body = revealMobileOnlyHtml(body);
// i18n: resolve `{t:key}` / `t:attr` markers against the request language. // 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. // Point 3: only ship the JS this page actually uses.
const scripts = collectScripts(body, deps.clientRuntimes, deps.navigation).map((script) => 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. // <html> attributes: no-flash theme (from cookie, validated) + active lang.
const attrs: string[] = []; const attrs: string[] = [];
let activeThemeHead = "";
if (deps.theme) { if (deps.theme) {
const themeName = resolveThemeName(ctx.cookies.get(THEME_COOKIE), deps.theme); const themeName = resolveThemeName(ctx.cookies.get(THEME_COOKIE), deps.theme);
@@ -1843,6 +1848,13 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
if (accentName) { if (accentName) {
attrs.push(`data-accent="${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)}"`); attrs.push(`lang="${safeLanguageTag(language)}"`);
if (deps.i18n) attrs.push(`dir="${deps.i18n.direction[language] ?? "ltr"}"`); 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 ? `<link rel="manifest" href="/site.webmanifest" />` : "",
pwaEnabled ? `<meta name="mobile-web-app-capable" content="yes" />` : "", pwaEnabled ? `<meta name="mobile-web-app-capable" content="yes" />` : "",
pwaEnabled ? `<meta name="apple-mobile-web-app-capable" content="yes" />` : "", pwaEnabled ? `<meta name="apple-mobile-web-app-capable" content="yes" />` : "",
activeThemeHead,
extraHead, extraHead,
] ]
.filter(Boolean) .filter(Boolean)
@@ -2436,72 +2449,6 @@ export function normalizeComponentName(name: string): string {
return name.toLowerCase().replace(/[-_]/g, ""); 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 { function safeLanguageTag(value: string): string {
return /^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/.test(value) ? value : "en"; 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 { 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 { join } from "node:path";
import { getAuthzCatalog, hasAuthzCatalog } from "@wrnexus/authz"; import { getAuthzCatalog, hasAuthzCatalog } from "@wrnexus/authz";
import { startServer } from "../src/index.ts"; import { startServer } from "../src/index.ts";
@@ -63,9 +63,14 @@ export default defineAuthz({ permissions: { "post:read": { title: "View posts" }
try { try {
expect(hasAuthzCatalog()).toBe(true); expect(hasAuthzCatalog()).toBe(true);
expect(getAuthzCatalog().permissions.has("post:read")).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 { } finally {
server.stop(); server.stop();
} }
expect(existsSync(join(appDir, "..", `.wrnexus-${process.pid}`))).toBe(false);
}); });
test("an app with no app/authz declarations boots without throwing", async () => { 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 root = mkdtempSync(join(tmpdir(), "wrnexus-hmr-"));
const file = join(root, "page.wrn"); const file = join(root, "page.wrn");
setCompileCacheDir(join(root, ".wrnexus")); setCompileCacheDir(join(root, ".wrnexus"));
@@ -94,7 +94,7 @@ test("invalidateModule recompiles changed WRN files in-process", async () => {
expect(String(first({}))).toContain("First"); expect(String(first({}))).toContain("First");
writeFileSync(file, "page Home { view { <h1>Second</h1> } }\n"); 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; const second = (await loadModule(file)).default as (ctx: unknown) => unknown;
expect(String(second({}))).toContain("Second"); expect(String(second({}))).toContain("Second");
} finally { } finally {
+8 -4
View File
@@ -22,7 +22,7 @@ export function webVitalsClient(options: WebVitalsClientOptions = {}): string {
function send(name, value) { 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 }); 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" })); 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() { function flush() {
if (flushed) return; if (flushed) return;
@@ -31,9 +31,13 @@ export function webVitalsClient(options: WebVitalsClientOptions = {}): string {
if (inp) send("INP", inp); if (inp) send("INP", inp);
if (cls) send("CLS", cls); 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(_) {} function observe(type, callback, options) {
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(callback).observe(options); }
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(_) {} 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("visibilitychange", function(){ if(document.visibilityState === "hidden") flush(); });
addEventListener("pagehide", flush, { once: true }); 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 { LogRecord } from "./logging.ts";
import type { MetricExporter } from "./server.ts"; import type { MetricExporter } from "./server.ts";
import type { SpanExporter, SpanRecord, TraceContext } from "./trace.ts"; import type { SpanExporter, SpanRecord, TraceContext } from "./trace.ts";
import { reportObservabilityFailure, type ObservabilityDiagnostic } from "./diagnostics.ts";
export type FrameworkSpanKind = export type FrameworkSpanKind =
"database" | "cache" | "queue" | "realtime" | "server-action" | "application"; "database" | "cache" | "queue" | "realtime" | "server-action" | "application";
@@ -26,6 +27,7 @@ export function createOperationTracer(
context?: () => Partial<TraceContext>; context?: () => Partial<TraceContext>;
now?: () => number; now?: () => number;
onExportError?: (error: unknown) => void; onExportError?: (error: unknown) => void;
diagnostic?: ObservabilityDiagnostic;
} = {}, } = {},
): OperationTracer { ): OperationTracer {
const now = options.now ?? Date.now; const now = options.now ?? Date.now;
@@ -66,7 +68,9 @@ export function createOperationTracer(
try { try {
await options.exporter?.export([record]); await options.exporter?.export([record]);
} catch (error) { } 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 { createTracer, type Context, type Middleware } from "@wrnexus/core";
import { reportObservabilityFailure, type ObservabilityDiagnostic } from "./diagnostics.ts";
export interface TraceContext { export interface TraceContext {
version: "00"; version: "00";
@@ -63,6 +64,7 @@ export interface TraceMiddlewareOptions {
routeName?: (ctx: Context) => string; routeName?: (ctx: Context) => string;
serverTiming?: boolean; serverTiming?: boolean;
onExportError?: (error: unknown, span: SpanRecord) => void | Promise<void>; onExportError?: (error: unknown, span: SpanRecord) => void | Promise<void>;
diagnostic?: ObservabilityDiagnostic;
} }
export function traceMiddleware(options: TraceMiddlewareOptions = {}): Middleware { export function traceMiddleware(options: TraceMiddlewareOptions = {}): Middleware {
@@ -131,7 +133,8 @@ export function traceMiddleware(options: TraceMiddlewareOptions = {}): Middlewar
await options.onSpan?.(span); await options.onSpan?.(span);
await options.exporter?.export([span]); await options.exporter?.export([span]);
} catch (error) { } 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(await profile("render", () => "ok")).toBe("ok");
expect(profiles[0]).toMatchObject({ name: "render", durationMs: 4 }); 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).toContain('addEventListener("pagehide", flush');
expect(client).not.toContain('send("INP",max)'); expect(client).not.toContain('send("INP",max)');
expect(client).not.toContain('send("LCP", last.startTime)'); 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", () => { test("records deterministic counters and histograms", () => {
@@ -81,6 +81,22 @@ describe("production observability operations", () => {
expect(failures).toHaveLength(1); 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 () => { test("exports OTLP JSON traces and metrics", async () => {
const requests: unknown[] = []; const requests: unknown[] = [];
const send = (async (_url: URL | RequestInfo, init?: RequestInit) => { const send = (async (_url: URL | RequestInfo, init?: RequestInit) => {
+5 -2
View File
@@ -1,6 +1,9 @@
import { createStoreContainer } from "./index.ts"; import { createStoreContainer } from "./index.ts";
let globalContainer: ReturnType<typeof createStoreContainer> | undefined; let globalContainer: ReturnType<typeof createStoreContainer> | undefined;
const storeGlobal = globalThis as typeof globalThis & {
__wrnexusStoreContainer?: ReturnType<typeof createStoreContainer>;
};
export function browserStoreContainer(hydration: Record<string, unknown> = {}) { export function browserStoreContainer(hydration: Record<string, unknown> = {}) {
if (!globalContainer) { if (!globalContainer) {
@@ -17,7 +20,7 @@ export function browserStoreContainer(hydration: Record<string, unknown> = {}) {
} }
}, },
}); });
(globalThis as any).__wrnexusStoreContainer = globalContainer; storeGlobal.__wrnexusStoreContainer = globalContainer;
} }
return globalContainer; return globalContainer;
} }
@@ -25,5 +28,5 @@ export function browserStoreContainer(hydration: Record<string, unknown> = {}) {
export async function resetBrowserStores() { export async function resetBrowserStores() {
await globalContainer?.dispose(); await globalContainer?.dispose();
globalContainer = undefined; 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)); 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 (typeof window === "undefined") return null;
if (kind === "local") return window.localStorage; if (kind === "local") return window.localStorage;
if (kind === "session") return window.sessionStorage; if (kind === "session") return window.sessionStorage;
@@ -91,7 +91,10 @@ export class StoreContainer {
readonly runtime: "server" | "client"; readonly runtime: "server" | "client";
readonly request?: unknown; readonly request?: unknown;
readonly routeId?: string; 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 hydration: Record<string, unknown>;
private readonly onMutation?: (mutation: StoreMutation) => void; private readonly onMutation?: (mutation: StoreMutation) => void;
private readonly lastMutations = new Map<string, StoreMutation>(); private readonly lastMutations = new Map<string, StoreMutation>();
@@ -159,7 +162,7 @@ export class StoreContainer {
Object.fromEntries( Object.fromEntries(
Reflect.ownKeys(instance.computed).map((key) => [ Reflect.ownKeys(instance.computed).map((key) => [
String(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, { const publicState = new Proxy({} as State, {
get(_target, property) { get(_target, property) {
return (mutableState as any)[property]; return Reflect.get(mutableState, property);
}, },
set(_target, property) { set(_target, property) {
throw new TypeError( throw new TypeError(
@@ -334,7 +337,7 @@ export function createStoreInstance<
return { return {
enumerable: true, enumerable: true,
configurable: true, configurable: true,
value: (mutableState as any)[property], value: Reflect.get(mutableState, property),
writable: false, writable: false,
}; };
}, },
@@ -360,7 +363,7 @@ export function createStoreInstance<
mutate("$reset", () => { mutate("$reset", () => {
const next = createInitialState(); const next = createInitialState();
for (const key of Object.keys(mutableState)) { 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); Object.assign(mutableState, next);
}); });
@@ -382,16 +385,20 @@ export function createStoreInstance<
definitions.find((entry) => entry.runtime === "shared") ?? definitions.find((entry) => entry.runtime === "shared") ??
definitions.find((entry) => entry.runtime === "legacy"); definitions.find((entry) => entry.runtime === "legacy");
if (!selected) continue; if (!selected) continue;
(actions as any)[name] = async (...args: unknown[]) => { Reflect.set(actions, name, async (...args: unknown[]) => {
currentAction = name; currentAction = name;
internalMutation = true; internalMutation = true;
try { 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 { } finally {
currentAction = "direct"; currentAction = "direct";
internalMutation = false; internalMutation = false;
} }
}; });
} }
const computed = new Proxy({} as C, { const computed = new Proxy({} as C, {
@@ -461,7 +468,7 @@ export function createStoreInstance<
const result: Partial<State> = {}; const result: Partial<State> = {};
const serverKeys = new Set(Object.keys(definition.createServerState?.() ?? {})); const serverKeys = new Set(Object.keys(definition.createServerState?.() ?? {}));
for (const [key, value] of Object.entries(mutableState)) { 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; return result;
}, },
@@ -479,9 +486,9 @@ export function createStoreInstance<
return new Proxy(core as StoreInstance<State, C, A>, { return new Proxy(core as StoreInstance<State, C, A>, {
get(target, property, receiver) { get(target, property, receiver) {
if (Reflect.has(target, property)) return Reflect.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 actions) return Reflect.get(actions, property);
if (property in (definition.computed ?? {})) return (computed as any)[property]; if (property in (definition.computed ?? {})) return Reflect.get(computed, property);
if (property in mutableState) return (publicState as any)[property]; if (property in mutableState) return Reflect.get(publicState, property);
return undefined; return undefined;
}, },
set(_target, property) { set(_target, property) {
+2 -1
View File
@@ -1,7 +1,8 @@
export type StoreKind = "global" | "page"; export type StoreKind = "global" | "page";
export type StoreRuntime = "shared" | "client" | "server" | "legacy"; export type StoreRuntime = "shared" | "client" | "server" | "legacy";
export type PersistenceStorage = "memory" | "session" | "local"; 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< export type StoreCombinedState<
S extends object, S extends object,
+3
View File
@@ -61,10 +61,13 @@ export {
THEME_PALETTE_NAMES, THEME_PALETTE_NAMES,
THEME_COOKIE, THEME_COOKIE,
THEME_CSS_HREF, THEME_CSS_HREF,
THEME_CSS_PREFIX,
THEME_JS_HREF, THEME_JS_HREF,
activeThemeCssHref,
resolveThemeConfig, resolveThemeConfig,
resolveThemeName, resolveThemeName,
renderThemeCss, renderThemeCss,
renderActiveThemeCss,
renderThemeRuntime, renderThemeRuntime,
defineThemeTokens, defineThemeTokens,
themeVar, themeVar,
+52 -30
View File
@@ -121,6 +121,7 @@ export interface ResolvedTheme {
export const THEME_COOKIE = "wire-theme"; export const THEME_COOKIE = "wire-theme";
export const ACCENT_COOKIE = "wire-accent"; export const ACCENT_COOKIE = "wire-accent";
export const THEME_CSS_HREF = "/__wrnexus/theme.css"; export const THEME_CSS_HREF = "/__wrnexus/theme.css";
export const THEME_CSS_PREFIX = "/__wrnexus/theme/";
export const THEME_JS_HREF = "/__wrnexus/theme.js"; export const THEME_JS_HREF = "/__wrnexus/theme.js";
/** /**
@@ -519,6 +520,44 @@ function selectorValue(value: string): string {
return JSON.stringify(value); 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. * Generate the theme stylesheet.
* *
@@ -559,36 +598,7 @@ export function renderThemeCss(theme: ResolvedTheme): string {
} }
} }
return ( return blocks.join("\n") + globalThemeCss();
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"
);
} }
/** /**
@@ -603,6 +613,7 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
const names = JSON.stringify(theme.names); const names = JSON.stringify(theme.names);
const accentNames = JSON.stringify(theme.accentNames); const accentNames = JSON.stringify(theme.accentNames);
const defaultAccent = JSON.stringify(theme.defaultAccent ?? null); const defaultAccent = JSON.stringify(theme.defaultAccent ?? null);
const cssPrefix = JSON.stringify(THEME_CSS_PREFIX);
return `(function(){ return `(function(){
var THEME_COOKIE=${JSON.stringify(THEME_COOKIE)}; var THEME_COOKIE=${JSON.stringify(THEME_COOKIE)};
@@ -611,6 +622,7 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
var ACCENTS=${accentNames}; var ACCENTS=${accentNames};
var DEFAULT_THEME=${JSON.stringify(theme.default)}; var DEFAULT_THEME=${JSON.stringify(theme.default)};
var DEFAULT_ACCENT=${defaultAccent}; var DEFAULT_ACCENT=${defaultAccent};
var THEME_CSS_PREFIX=${cssPrefix};
var MAX_AGE=31536000; var MAX_AGE=31536000;
var el=document.documentElement; 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; 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(){ function getTheme(){
var current=el.getAttribute("data-theme")||readCookie(THEME_COOKIE)||DEFAULT_THEME; var current=el.getAttribute("data-theme")||readCookie(THEME_COOKIE)||DEFAULT_THEME;
return THEMES.indexOf(current)>=0?current: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; if(THEMES.indexOf(name)<0)return;
el.setAttribute("data-theme",name); el.setAttribute("data-theme",name);
writeCookie(THEME_COOKIE,name); writeCookie(THEME_COOKIE,name);
syncStylesheet(name,getAccent());
syncThemeButtons(document); syncThemeButtons(document);
window.dispatchEvent(new CustomEvent("wrnexus:theme-changed",{detail:{theme:name}})); 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; if(ACCENTS.indexOf(name)<0)return;
el.setAttribute("data-accent",name); el.setAttribute("data-accent",name);
writeCookie(ACCENT_COOKIE,name); writeCookie(ACCENT_COOKIE,name);
syncStylesheet(getTheme(),name);
syncAccentButtons(document); syncAccentButtons(document);
window.dispatchEvent(new CustomEvent("wrnexus:accent-changed",{detail:{accent:name}})); window.dispatchEvent(new CustomEvent("wrnexus:accent-changed",{detail:{accent:name}}));
} }
@@ -685,6 +706,7 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
function clearAccent(){ function clearAccent(){
el.removeAttribute("data-accent"); el.removeAttribute("data-accent");
deleteCookie(ACCENT_COOKIE); deleteCookie(ACCENT_COOKIE);
syncStylesheet(getTheme(),null);
syncAccentButtons(document); syncAccentButtons(document);
window.dispatchEvent(new CustomEvent("wrnexus:accent-changed",{detail:{accent:null}})); window.dispatchEvent(new CustomEvent("wrnexus:accent-changed",{detail:{accent:null}}));
} }
+13
View File
@@ -5,6 +5,8 @@ import {
resolveThemeConfig, resolveThemeConfig,
resolveThemeName, resolveThemeName,
renderThemeCss, renderThemeCss,
renderActiveThemeCss,
activeThemeCssHref,
renderThemeRuntime, renderThemeRuntime,
} from "../src/index.ts"; } 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"); 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", () => { test("renderThemeRuntime bakes the theme names for cycling", () => {
const t = resolveThemeConfig(); const t = resolveThemeConfig();
const js = renderThemeRuntime(t); 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 } // props { name: Type = <default>; @event name = function }
lx.next(); lx.next();
expect("lbrace"); 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(); const t = lx.peek();
if (t.type === "eof") throw new ParseError("Unexpected end of input inside props"); if (t.type === "eof") throw new ParseError("Unexpected end of input inside props");
if (t.type === "at") { if (t.type === "at") {
@@ -829,6 +836,13 @@ export function parse(source: string): PageAst {
const declaredStates = new Set(states.map((state) => state.name)); 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) { for (const watcher of watches) {
if (!declaredStates.has(watcher.state)) { if (!declaredStates.has(watcher.state)) {
throw new ParseError(`Cannot watch undeclared state '${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. */ /** Read and consume the next structural token. */
next(): Token { next(): Token {
this.skipTrivia(); 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 { compileWireFile } from "@wrnexus/compiler";
import { createContext } from "@wrnexus/core"; import { createContext } from "@wrnexus/core";
import type { Context } 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 { startServer } from "@wrnexus/dev-server";
import { import {
loadAppConfig, loadAppConfig,
@@ -100,6 +100,7 @@ export function mountHtml(html: string): {
g.HTMLSelectElement = win.HTMLSelectElement; g.HTMLSelectElement = win.HTMLSelectElement;
g.HTMLTextAreaElement = win.HTMLTextAreaElement; g.HTMLTextAreaElement = win.HTMLTextAreaElement;
(0, eval)(getReactiveRuntime()); (0, eval)(getReactiveRuntime());
(0, eval)(getComponentControllerRuntime());
(win as { __wrnexusHydrateScopes?: (root: unknown) => void }).__wrnexusHydrateScopes?.( (win as { __wrnexusHydrateScopes?: (root: unknown) => void }).__wrnexusHydrateScopes?.(
win.document, win.document,
); );
+9 -63
View File
@@ -1,6 +1,6 @@
# WRNexus UI component reference # 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 ## Advanced-forms
@@ -22,15 +22,6 @@ Editable autocomplete combobox with local and remote suggestions.
- Slots: None - 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)` - 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 ### InputNumber
Theme-aware, responsive input number component. Theme-aware, responsive input number component.
@@ -303,23 +294,14 @@ Present responsive chronological activity, milestones, or workflow status with r
- Slots: `default` - Slots: `default`
- Outputs: `select({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })` - 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 ### TreeView
Theme-aware, responsive tree view component. Theme-aware, responsive tree view component.
- Mount: `data-component="TreeView"` - 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` - 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 ## Core
@@ -509,50 +491,32 @@ Theme-aware, responsive time picker component.
## Integrations ## 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 ### Chart
Theme-aware, responsive chart component. Theme-aware, responsive chart component.
- Mount: `data-component="Chart"` - 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` - 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 ### Clipboard
Theme-aware, responsive clipboard component. Theme-aware, responsive clipboard component.
- Mount: `data-component="Clipboard"` - 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` - 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 ### Confetti
Theme-aware, responsive confetti component. Theme-aware, responsive confetti component.
- Mount: `data-component="Confetti"` - 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` - 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 ### DataMap
@@ -572,15 +536,6 @@ Theme-aware, responsive drag and drop component.
- Slots: `default` - 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)` - 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 ### Map
Present responsive location information and markers with map-ready metadata and movement or marker events. 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` - 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 })` - 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 ### WysiwygEditor
Theme-aware, responsive wysiwyg editor component. 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 The framework serves this stylesheet once at `/__wrnexus/ui.css`, so pages get all
component styles from a single request. component styles from a single request.
### Tailwind and motion ### Component styles and motion
Components use static Tailwind utility classes alongside the shared `.wire-*` Components own their BEM styles in local `style {}` blocks and do not require a
layer. If the package is consumed by a separate Tailwind build, include its Tailwind scan. `ui.css` supplies only global tokens, resets, and shared
component sources so every utility is generated: primitives. Applications can still use Tailwind independently in their own
source files.
```css
@import "tailwindcss";
@source "../node_modules/@wrnexus/ui/components/*.wrn";
```
The shared stylesheet gives all component boundaries consistent, GPU-friendly The shared stylesheet gives all component boundaries consistent, GPU-friendly
entry and interaction motion. Override `--wire-motion-fast`, entry and interaction motion. Override `--wire-motion-fast`,
-30
View File
@@ -6,16 +6,6 @@
"category": "base", "category": "base",
"purpose": "Theme-aware, responsive accordion component." "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", "name": "AdvancedSelect",
"category": "advanced-forms", "category": "advanced-forms",
@@ -151,11 +141,6 @@
"category": "overlays", "category": "overlays",
"purpose": "Open an accessible keyboard-aware action menu from pointer or keyboard context interactions." "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", "name": "CustomScrollbar",
"category": "layout", "category": "layout",
@@ -221,11 +206,6 @@
"category": "forms", "category": "forms",
"purpose": "Theme-aware, responsive file input component." "purpose": "Theme-aware, responsive file input component."
}, },
{
"name": "FileUpload",
"category": "integrations",
"purpose": "Theme-aware, responsive file upload component."
},
{ {
"name": "FileUploadProgress", "name": "FileUploadProgress",
"category": "base", "category": "base",
@@ -496,16 +476,6 @@
"category": "base", "category": "base",
"purpose": "Present responsive chronological activity, milestones, or workflow status with rich item metadata." "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", "name": "Toaster",
"category": "core", "category": "core",

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