Compare commits
36
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bca9549f3a | ||
|
|
232d8e6734 | ||
|
|
8f19a5eb2b | ||
|
|
937ccb9e8e | ||
|
|
6859b5c4ff | ||
|
|
3f5ec8696f | ||
|
|
9fd80b7cef | ||
|
|
7d87200e31 | ||
|
|
51286d0fa3 | ||
|
|
905cbce0c1 | ||
|
|
09ebd44b6c | ||
|
|
2e5c0cc953 | ||
|
|
1660044ed2 | ||
|
|
324928cfca | ||
|
|
5c2c6a0a8f | ||
|
|
ff62918dbb | ||
|
|
54a93c1d14 | ||
|
|
b6bd5cfcb7 | ||
|
|
fdf6282aed | ||
|
|
ca8aabf640 | ||
|
|
a90d3683d3 | ||
|
|
d85be6c456 | ||
|
|
8ef6233ef3 | ||
|
|
91e8b2ab05 | ||
|
|
b2e83bc941 | ||
|
|
3e77a621f5 | ||
|
|
a1f671ed5d | ||
|
|
7c584c1d2e | ||
|
|
709a38feb4 | ||
|
|
35cd28aad4 | ||
|
|
de8d792e37 | ||
|
|
64bb0e366a | ||
|
|
e4502f2437 | ||
|
|
504d065003 | ||
|
|
f09341fcfa | ||
|
|
535ad5af6d |
@@ -1,6 +1,7 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.wrnexus/
|
||||
.wrnexus-*/
|
||||
*.log
|
||||
*.db
|
||||
*.db-shm
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
node_modules/
|
||||
dist/
|
||||
**/dist/
|
||||
**/.wrnexus/**
|
||||
**/.wrnexus-*/**
|
||||
.publish/
|
||||
**/.wirefw/
|
||||
coverage/
|
||||
|
||||
@@ -109,12 +109,101 @@
|
||||
"name": "inter-app-api-showcase",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.0.0",
|
||||
"@types/bun": "latest",
|
||||
"@types/bun": "^1.3.14",
|
||||
"@wrnexus/cli": "0.8.6",
|
||||
"eslint": "^9.0.0",
|
||||
"prettier": "latest",
|
||||
"prettier": "^3.9.6",
|
||||
"typescript": "^5.5.0",
|
||||
"typescript-eslint": "latest",
|
||||
"typescript-eslint": "^8.65.0",
|
||||
},
|
||||
},
|
||||
"examples/inter-app-api-showcase/apps/admin": {
|
||||
"name": "admin",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@wrnexus/ai": "0.8.6",
|
||||
"@wrnexus/auth": "0.8.6",
|
||||
"@wrnexus/authz": "0.8.6",
|
||||
"@wrnexus/captcha": "0.8.6",
|
||||
"@wrnexus/core": "0.8.6",
|
||||
"@wrnexus/csr": "0.8.6",
|
||||
"@wrnexus/db": "0.8.6",
|
||||
"@wrnexus/dev-server": "0.8.6",
|
||||
"@wrnexus/encryption": "0.8.6",
|
||||
"@wrnexus/helpers": "0.8.6",
|
||||
"@wrnexus/i18n": "0.8.6",
|
||||
"@wrnexus/image": "0.8.6",
|
||||
"@wrnexus/jwt": "0.8.6",
|
||||
"@wrnexus/observability": "0.8.6",
|
||||
"@wrnexus/realtime": "0.8.6",
|
||||
"@wrnexus/security": "0.8.6",
|
||||
"@wrnexus/store": "0.8.6",
|
||||
"@wrnexus/styles": "0.8.6",
|
||||
"@wrnexus/tracking": "0.8.6",
|
||||
"@wrnexus/ui": "0.8.6",
|
||||
"@wrnexus/uploader": "0.8.6",
|
||||
"@wrnexus/validation": "0.8.6",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.0.0",
|
||||
"@iconify-json/lucide": "^1.2.118",
|
||||
"@iconify/tailwind4": "^1.2.3",
|
||||
"@tailwindcss/cli": "^4.0.0",
|
||||
"@types/bun": "^1.3.14",
|
||||
"@wrnexus/cli": "0.8.6",
|
||||
"eslint": "^9.0.0",
|
||||
"prettier": "^3.9.6",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"typescript": "^5.5.0",
|
||||
"typescript-eslint": "^8.65.0",
|
||||
},
|
||||
},
|
||||
"examples/inter-app-api-showcase/apps/web": {
|
||||
"name": "web",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@wrnexus/ai": "0.8.6",
|
||||
"@wrnexus/auth": "0.8.6",
|
||||
"@wrnexus/authz": "0.8.6",
|
||||
"@wrnexus/captcha": "0.8.6",
|
||||
"@wrnexus/core": "0.8.6",
|
||||
"@wrnexus/csr": "0.8.6",
|
||||
"@wrnexus/db": "0.8.6",
|
||||
"@wrnexus/dev-server": "0.8.6",
|
||||
"@wrnexus/encryption": "0.8.6",
|
||||
"@wrnexus/helpers": "0.8.6",
|
||||
"@wrnexus/i18n": "0.8.6",
|
||||
"@wrnexus/image": "0.8.6",
|
||||
"@wrnexus/jwt": "0.8.6",
|
||||
"@wrnexus/observability": "0.8.6",
|
||||
"@wrnexus/realtime": "0.8.6",
|
||||
"@wrnexus/security": "0.8.6",
|
||||
"@wrnexus/store": "0.8.6",
|
||||
"@wrnexus/styles": "0.8.6",
|
||||
"@wrnexus/tracking": "0.8.6",
|
||||
"@wrnexus/ui": "0.8.6",
|
||||
"@wrnexus/uploader": "0.8.6",
|
||||
"@wrnexus/validation": "0.8.6",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.0.0",
|
||||
"@iconify-json/lucide": "^1.2.118",
|
||||
"@iconify/tailwind4": "^1.2.3",
|
||||
"@tailwindcss/cli": "^4.0.0",
|
||||
"@types/bun": "^1.3.14",
|
||||
"@wrnexus/cli": "0.8.6",
|
||||
"eslint": "^9.0.0",
|
||||
"prettier": "^3.9.6",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"typescript": "^5.5.0",
|
||||
"typescript-eslint": "^8.65.0",
|
||||
},
|
||||
},
|
||||
"examples/inter-app-api-showcase/packages/shared": {
|
||||
"name": "@app/shared",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@wrnexus/pubsub": "0.8.6",
|
||||
},
|
||||
},
|
||||
"packages/ai": {
|
||||
@@ -563,7 +652,7 @@
|
||||
},
|
||||
"services/managed-captcha": {
|
||||
"name": "@wrnexus/managed-captcha-service",
|
||||
"version": "0.8.0",
|
||||
"version": "0.8.6",
|
||||
"dependencies": {
|
||||
"@wrnexus/captcha": "workspace:*",
|
||||
},
|
||||
@@ -580,6 +669,8 @@
|
||||
"packages": {
|
||||
"@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="],
|
||||
|
||||
"@app/shared": ["@app/shared@workspace:examples/inter-app-api-showcase/packages/shared"],
|
||||
|
||||
"@cyberalien/svg-utils": ["@cyberalien/svg-utils@1.2.19", "", { "dependencies": { "@iconify/types": "^2.0.0" } }, "sha512-paDJoDu+LhuH5Ma1q0BDqBpg6d16Xk22cZZrlg/s/J9oB8KgnnpU/snaEETJu2G1pwYcWQINUz6WUqu0Wm9k6A=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
|
||||
@@ -950,6 +1041,8 @@
|
||||
|
||||
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
|
||||
|
||||
"admin": ["admin@workspace:examples/inter-app-api-showcase/apps/admin"],
|
||||
|
||||
"ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
@@ -1286,6 +1379,8 @@
|
||||
|
||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||
|
||||
"web": ["web@workspace:examples/inter-app-api-showcase/apps/web"],
|
||||
|
||||
"whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
@@ -1318,6 +1413,12 @@
|
||||
|
||||
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||
|
||||
"admin/@eslint/js": ["@eslint/js@9.39.5", "", {}, "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A=="],
|
||||
|
||||
"admin/eslint": ["eslint@9.39.5", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.6", "@eslint/js": "9.39.5", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw=="],
|
||||
|
||||
"admin/typescript-eslint": ["typescript-eslint@8.66.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.66.0", "@typescript-eslint/parser": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0", "@typescript-eslint/utils": "8.66.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-QlEbBPz/RuJ1XUHj29nm3t0F/O/cSlEnntozqPOYHnnTGAXFamnMBu5i9Vn6vhUPHGAjR+Vl+5J8vPN/BMUrJw=="],
|
||||
|
||||
"csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="],
|
||||
|
||||
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
@@ -1338,8 +1439,38 @@
|
||||
|
||||
"tsyringe/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="],
|
||||
|
||||
"web/@eslint/js": ["@eslint/js@9.39.5", "", {}, "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A=="],
|
||||
|
||||
"web/eslint": ["eslint@9.39.5", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.6", "@eslint/js": "9.39.5", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw=="],
|
||||
|
||||
"web/typescript-eslint": ["typescript-eslint@8.66.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.66.0", "@typescript-eslint/parser": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0", "@typescript-eslint/utils": "8.66.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-QlEbBPz/RuJ1XUHj29nm3t0F/O/cSlEnntozqPOYHnnTGAXFamnMBu5i9Vn6vhUPHGAjR+Vl+5J8vPN/BMUrJw=="],
|
||||
|
||||
"@eslint/eslintrc/espree/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
|
||||
|
||||
"admin/eslint/@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="],
|
||||
|
||||
"admin/eslint/@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="],
|
||||
|
||||
"admin/eslint/@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
|
||||
|
||||
"admin/eslint/@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
|
||||
|
||||
"admin/eslint/eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
|
||||
|
||||
"admin/eslint/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
|
||||
|
||||
"admin/eslint/espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
|
||||
|
||||
"admin/eslint/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
|
||||
|
||||
"admin/typescript-eslint/@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.66.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.66.0", "@typescript-eslint/type-utils": "8.66.0", "@typescript-eslint/utils": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.66.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A=="],
|
||||
|
||||
"admin/typescript-eslint/@typescript-eslint/parser": ["@typescript-eslint/parser@8.66.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g=="],
|
||||
|
||||
"admin/typescript-eslint/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.66.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.66.0", "@typescript-eslint/tsconfig-utils": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg=="],
|
||||
|
||||
"admin/typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.66.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA=="],
|
||||
|
||||
"csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="],
|
||||
|
||||
"inter-app-api-showcase/eslint/@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="],
|
||||
@@ -1366,6 +1497,58 @@
|
||||
|
||||
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.66.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA=="],
|
||||
|
||||
"web/eslint/@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="],
|
||||
|
||||
"web/eslint/@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="],
|
||||
|
||||
"web/eslint/@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
|
||||
|
||||
"web/eslint/@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
|
||||
|
||||
"web/eslint/eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
|
||||
|
||||
"web/eslint/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
|
||||
|
||||
"web/eslint/espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
|
||||
|
||||
"web/eslint/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
|
||||
|
||||
"web/typescript-eslint/@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.66.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.66.0", "@typescript-eslint/type-utils": "8.66.0", "@typescript-eslint/utils": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.66.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A=="],
|
||||
|
||||
"web/typescript-eslint/@typescript-eslint/parser": ["@typescript-eslint/parser@8.66.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g=="],
|
||||
|
||||
"web/typescript-eslint/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.66.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.66.0", "@typescript-eslint/tsconfig-utils": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg=="],
|
||||
|
||||
"web/typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.66.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA=="],
|
||||
|
||||
"admin/eslint/@eslint/config-array/@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
|
||||
|
||||
"admin/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0" } }, "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g=="],
|
||||
|
||||
"admin/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0", "@typescript-eslint/utils": "8.66.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-LG2dWfjZQQp0ADtAu/EWJVayefGL2UEZ3CDeI44D9v3rXB/WYUqE/jpO28KrEKul5AySrmI+Zh1v6v+xW2U9+g=="],
|
||||
|
||||
"admin/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
|
||||
|
||||
"admin/typescript-eslint/@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||
|
||||
"admin/typescript-eslint/@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0" } }, "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g=="],
|
||||
|
||||
"admin/typescript-eslint/@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
|
||||
|
||||
"admin/typescript-eslint/@typescript-eslint/parser/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
|
||||
|
||||
"admin/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.66.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.66.0", "@typescript-eslint/types": "^8.66.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g=="],
|
||||
|
||||
"admin/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.66.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g=="],
|
||||
|
||||
"admin/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
|
||||
|
||||
"admin/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
|
||||
|
||||
"admin/typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0" } }, "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g=="],
|
||||
|
||||
"admin/typescript-eslint/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
|
||||
|
||||
"inter-app-api-showcase/eslint/@eslint/config-array/@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
|
||||
|
||||
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0" } }, "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g=="],
|
||||
@@ -1394,6 +1577,42 @@
|
||||
|
||||
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
|
||||
|
||||
"web/eslint/@eslint/config-array/@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
|
||||
|
||||
"web/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0" } }, "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g=="],
|
||||
|
||||
"web/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0", "@typescript-eslint/utils": "8.66.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-LG2dWfjZQQp0ADtAu/EWJVayefGL2UEZ3CDeI44D9v3rXB/WYUqE/jpO28KrEKul5AySrmI+Zh1v6v+xW2U9+g=="],
|
||||
|
||||
"web/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
|
||||
|
||||
"web/typescript-eslint/@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||
|
||||
"web/typescript-eslint/@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0" } }, "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g=="],
|
||||
|
||||
"web/typescript-eslint/@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
|
||||
|
||||
"web/typescript-eslint/@typescript-eslint/parser/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
|
||||
|
||||
"web/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.66.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.66.0", "@typescript-eslint/types": "^8.66.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g=="],
|
||||
|
||||
"web/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.66.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g=="],
|
||||
|
||||
"web/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
|
||||
|
||||
"web/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
|
||||
|
||||
"web/typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0" } }, "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g=="],
|
||||
|
||||
"web/typescript-eslint/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
|
||||
|
||||
"admin/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
|
||||
|
||||
"admin/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
|
||||
|
||||
"admin/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
|
||||
|
||||
"admin/typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
|
||||
|
||||
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
|
||||
|
||||
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
|
||||
@@ -1401,5 +1620,13 @@
|
||||
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
|
||||
|
||||
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
|
||||
|
||||
"web/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
|
||||
|
||||
"web/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
|
||||
|
||||
"web/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
|
||||
|
||||
"web/typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,29 @@
|
||||
# Framework remediation plan
|
||||
|
||||
**Status: completed.** The
|
||||
repository has 102 maintained UI components, zero
|
||||
undeclared non-native output emitters, zero components matching the scaffold
|
||||
definition, and local styles on every component. The core reactive runtime is
|
||||
48,124 minified bytes; component controllers ship separately at 24,027 bytes.
|
||||
|
||||
The §4.1 ownership migration is complete: `ui.css` contains global and
|
||||
cross-cutting primitives only. Shared component foundations are carried by
|
||||
three internal style-only components and render on demand; the former global
|
||||
component families and standalone `wire-next` generation class are absent.
|
||||
|
||||
Final validation passed the production, package, example, service, editor,
|
||||
showcase-generation, typecheck, lint, formatting, public-API, visual-contract,
|
||||
security, and runtime-size gates. The only skipped test is the explicitly
|
||||
environment-dependent live PostgreSQL/MySQL test.
|
||||
|
||||
Production CSS delivery now links the selected palette/accent sheet (6,632
|
||||
decoded bytes for dark/violet) instead of the complete 74,764-byte theme
|
||||
matrix. The compatibility `theme.css` endpoint remains available but is not
|
||||
linked by generated pages. Generated application declarations are committed
|
||||
and checked for drift by `check:production`.
|
||||
|
||||
Written 2026-08-09, after the 0.8.6 release. Every number here was measured
|
||||
against the tree at commit `7a2b5865`, not estimated. Where a cause is not yet
|
||||
against the completed tree, not estimated. Where a cause is not yet
|
||||
proven the item says so and makes proving it step one — nothing in this document
|
||||
should be implemented on the strength of a guess.
|
||||
|
||||
@@ -155,7 +177,7 @@ This section is the real work. These are capability gaps, not defects, and they
|
||||
are what currently limits what anyone can build. **§2.1 is the highest-value
|
||||
item in this entire document.**
|
||||
|
||||
### 2.1 The framework fails silently, everywhere
|
||||
### 2.1 Development diagnostics — FIXED
|
||||
|
||||
**Issue.** Nothing in §1 produced a single diagnostic at any layer. That is not
|
||||
five unlucky bugs; it is a systemic property. The failure mode is always
|
||||
@@ -195,10 +217,10 @@ bun test packages/csr/test/diagnostics.test.ts
|
||||
bun run build && grep -c "WRN-DEV-" examples/basic-app/dist/reactive.js # expect 0
|
||||
```
|
||||
|
||||
Add a runtime size assertion so the production bundle does not grow: it is
|
||||
69,947 bytes minified today.
|
||||
The production size ratchet now caps the split core runtime at 49,000 bytes; it
|
||||
currently measures 48,124 minified bytes.
|
||||
|
||||
### 2.2 Deferred state writes are silently dropped
|
||||
### 2.2 Deferred state writes — FIXED
|
||||
|
||||
**Issue.** State written after a client function returns — in a `setTimeout`, a
|
||||
promise callback, an observer, a `pointermove` — is discarded. The peer-function
|
||||
@@ -219,9 +241,9 @@ model limitation being paid for in bundle size.
|
||||
interacts with re-render batching.
|
||||
|
||||
**How to test.** A test where a client function writes state inside a
|
||||
`setTimeout` and the DOM reflects it after the timer. It must fail today.
|
||||
`setTimeout` and the DOM reflects it after the timer. This failed before the fix.
|
||||
|
||||
### 2.3 Props and slot content render once
|
||||
### 2.3 Reactive props and slot content — FIXED
|
||||
|
||||
**Issue.** Component props and slot content do not track page state. The
|
||||
controlled-component pattern does not work at all: passing `value={pageState}`
|
||||
@@ -243,9 +265,9 @@ quietly doesn't.
|
||||
|
||||
**How to test.** A page with `state n`, a component bound to `value={n}`, and a
|
||||
button incrementing `n`; the component's rendered output must follow. Must fail
|
||||
today.
|
||||
before the fix.
|
||||
|
||||
### 2.4 A component tag inside another component's slot is dropped
|
||||
### 2.4 Nested component slots — FIXED
|
||||
|
||||
**Issue.** Nesting a component tag in another component's slot drops the tag and
|
||||
leaves its children orphaned.
|
||||
@@ -255,9 +277,9 @@ it is fixed, emit the §2.1 diagnostic — a dropped component must never be
|
||||
silent.
|
||||
|
||||
**How to test.** Render `<Card><Badge label="x" /></Card>` and assert the Badge
|
||||
root element exists in the output. Must fail today.
|
||||
root element exists in the output. This failed before the fix.
|
||||
|
||||
### 2.5 Object props must be hand-hoisted
|
||||
### 2.5 Structured object props — FIXED
|
||||
|
||||
**Issue.** An object or array prop written inline in an attribute is read as
|
||||
interpolation. Three encodings were tried this session and all failed: bare
|
||||
@@ -274,17 +296,19 @@ outcomes for a plain authoring mistake.
|
||||
**How to test.** A compiler test asserting the specific diagnostic for
|
||||
`items={{ a: 1 }}`, and one asserting the hoisted form still compiles.
|
||||
|
||||
### 2.6 i18n text is not server-rendered
|
||||
### 2.6 i18n text is server-rendered — FIXED
|
||||
|
||||
**Issue.** Translated text ships as empty spans and is filled after hydration.
|
||||
Navigation is blank until the runtime loads.
|
||||
**Original issue.** Translated text shipped as empty spans and was filled after
|
||||
hydration. Navigation was blank until the runtime loaded.
|
||||
|
||||
**Why it matters.** This is an SSR-first framework failing to server-render
|
||||
text. It is an SEO problem, a layout-shift problem, and it undercuts the
|
||||
framework's central claim.
|
||||
|
||||
**Change.** Resolve the active locale during SSR and emit the translated text
|
||||
into the HTML, keeping the client path for locale switches.
|
||||
**Change.** Done. The active locale is resolved during SSR and translated text
|
||||
is emitted into the HTML, keeping the client path for locale switches. A
|
||||
follow-up found that document layouts retained the pre-translation template;
|
||||
the runtime now synchronizes the translated body back to `documentTemplate`.
|
||||
|
||||
**How to test.**
|
||||
|
||||
@@ -293,13 +317,13 @@ curl -s http://localhost:3520/ | grep -c "Navigation" # expect > 0
|
||||
```
|
||||
|
||||
Assert against the raw server response with JavaScript disabled — not the
|
||||
hydrated DOM, which already looks correct today.
|
||||
hydrated DOM.
|
||||
|
||||
---
|
||||
|
||||
## 3. Smaller but real
|
||||
|
||||
### 3.1 Twenty-two outputs still have no emitter
|
||||
### 3.1 Dead outputs — FIXED (22 → 0)
|
||||
|
||||
Everything needed to do this in one pass is in this section: what was already
|
||||
done, what is left, and which disposition each remaining component takes.
|
||||
@@ -331,9 +355,9 @@ all ten rewired components. **`Map` was not verified** — its three outputs wer
|
||||
converted by the same mechanical change and the build passes, but no Map was on
|
||||
the probe page. Put one on a page and confirm before treating it as done.
|
||||
|
||||
#### 3.1.2 What is left — 22 outputs, 9 components
|
||||
#### 3.1.2 Original remaining work — resolved
|
||||
|
||||
**Issue.** These are not miswired. All nine are **pure scaffolds**: roughly 25
|
||||
**Original issue.** These were not miswired. All nine were **pure scaffolds**: roughly 25
|
||||
lines each, zero state, zero functions, no style block, no event handlers. They
|
||||
are markup shells that declare outputs. "Add an emitter" is not the work — there
|
||||
is nothing to emit from.
|
||||
@@ -393,9 +417,7 @@ self-contained. `TreeView` is medium — recursive rendering plus expand state.
|
||||
|
||||
**How to test.**
|
||||
|
||||
- The ratchet in `packages/ui/test/ui.test.ts` is pinned at 22 and only ever
|
||||
moves down. Lower the ceiling **in the same commit** that fixes or removes a
|
||||
component — never in a separate one, or the ratchet stops meaning anything.
|
||||
- The ratchet in `packages/ui/test/ui.test.ts` is now pinned at **0**.
|
||||
- For a removal: `bun run check:public-api` flags the dropped export, and the
|
||||
migration entry is required before `release:prepare` will pass.
|
||||
- For a build: the component renders, its behaviour works in a browser, its
|
||||
@@ -403,16 +425,16 @@ self-contained. `TreeView` is medium — recursive rendering plus expand state.
|
||||
output from a page and confirm it arrives — §1.1 and §1.2 are both cases where
|
||||
reading the source said it worked and the browser said otherwise.
|
||||
|
||||
### 3.2 Twenty-eight components are still scaffolds
|
||||
### 3.2 Component scaffolds — FIXED (28 → 0)
|
||||
|
||||
**Issue.** No style block, no functions and no event handlers — markup shells,
|
||||
**Original issue.** No style block, no functions and no event handlers — markup shells,
|
||||
the same shape as the Table scaffold that was removed and the LayoutSplitter
|
||||
scaffold that was rebuilt. A scaffold in a published library is a promise the
|
||||
library does not keep.
|
||||
|
||||
**Evidence.** Counted as components with no `style {}`, no `function` and no
|
||||
`@handler`. An earlier figure of 23 in the audit was measured with a looser rule;
|
||||
28 is the number:
|
||||
28 was the number:
|
||||
|
||||
> AdvancedDatePicker, AdvancedRangeSlider, AuthSplitLayout, avatar, Blockquote,
|
||||
> button, Chart, Clipboard, Confetti, CopyMarkup, DataMap, DragAndDrop,
|
||||
@@ -425,10 +447,10 @@ AdvancedRangeSlider, Chart, Confetti, CopyMarkup, FileUpload, Toast,
|
||||
ToastNotifications, TreeView. Do §3.1 first and this list drops to 19 without
|
||||
any extra work. Do not plan the two items separately.
|
||||
|
||||
Separately, **58 components still carry the `wire-next` scaffold class** in
|
||||
their markup, including many that are otherwise finished. That class is a
|
||||
generation artefact rather than a design, and it is what ties them to `ui.css`
|
||||
instead of their own styles (§4.1).
|
||||
Separately, 58 components carried the standalone `wire-next` scaffold class in
|
||||
the original audit; **zero retain it now**. Component-specific BEM identifiers
|
||||
such as `wire-next--alert` remain locally owned implementation details, but no
|
||||
component is bound to a global stylesheet through the generation artefact.
|
||||
|
||||
**Change.** Rebuild them under §0.1, or remove them. Note that several are
|
||||
primitives where a scaffold is nearly the right answer — `skeleton`, `spinner`,
|
||||
@@ -446,12 +468,15 @@ Track the count down the same way as §4.1:
|
||||
bun run build && gzip -c examples/basic-app/dist/ui.css | wc -c
|
||||
```
|
||||
|
||||
### 3.3 Seven components still use Tailwind utilities
|
||||
### 3.3 Tailwind component migrations — FIXED (7 → 0)
|
||||
|
||||
**Issue.** `List` (23 utility classes), `InputNumber` (22), `Marquee` (15),
|
||||
**Original issue.** `List` (23 utility classes), `InputNumber` (22), `Marquee` (15),
|
||||
`TextLink` (14), `Map` (12), `SearchBox` (8), `Timeline` (8) still style
|
||||
themselves with utility classes in markup.
|
||||
|
||||
All seven now use local `wire-*` BEM styles. `InputNumber` styles are emitted as
|
||||
component-scoped CSS and therefore do not appear in the global `ui.css` file.
|
||||
|
||||
**Change.** Migrate to `wire-*` BEM classes in the component's own `style {}`
|
||||
block, per §0.1. Note this changes the rendered class list, so application CSS
|
||||
selecting on those utilities stops matching — it needs a migration note, exactly
|
||||
@@ -466,7 +491,7 @@ bun run generate:ui-visual # only after reviewing
|
||||
|
||||
Compare screenshots before and after; the visual result should be unchanged.
|
||||
|
||||
### 3.4 `List` only fires `select` for items with an href
|
||||
### 3.4 `List` selection without an href — FIXED
|
||||
|
||||
**Issue.** The `@click` handler sits on the `item.href` branch only, so an item
|
||||
without a link is not selectable. Pre-existing, and surprising.
|
||||
@@ -481,28 +506,37 @@ whichever behaviour was chosen.
|
||||
|
||||
## 4. Delivery and the dev loop
|
||||
|
||||
### 4.1 `ui.css` ships whole to every page
|
||||
### 4.1 Global `ui.css` migration — FIXED
|
||||
|
||||
**Issue.** `ui.css` is **175,848 bytes, 26,350 gzipped**, served on every page.
|
||||
Only **42 of 108** components have a local `style {}` block, so 66 still depend
|
||||
on it.
|
||||
**Original issue.** `ui.css` was **175,848 bytes, 26,350 gzipped**, served on
|
||||
every page. Only **42 of 108** components had a local `style {}` block.
|
||||
|
||||
**Change.** Migrate the remaining 66 under §0.1. This is mechanical, high-value,
|
||||
and shrinks `ui.css` toward the global-only baseline it should be. Do it in
|
||||
groups, one commit per group, regenerating the visual contract each time.
|
||||
**Result.** The maintained library now has **102 of 102** components with local
|
||||
styles. Built `ui.css` is **15,927 bytes / 3,762 gzipped**. The `.wire-next`,
|
||||
`.wire-btn`, `.wire-dropdown`, `.wire-alert`, `.wire-card`, `.wire-footer`,
|
||||
`.wire-metric-card`, and `.wire-segmented-group` global families are gone.
|
||||
Reusable field, select, color, and size foundations live in three internal
|
||||
style-only components; the renderer emits and deduplicates them only when a
|
||||
component that needs them renders. An example-app SSR guard verifies that
|
||||
on-demand path, while the UI suite prevents the global families or standalone
|
||||
`wire-next` class from returning.
|
||||
|
||||
**Change.** Done in groups with the visual contract regenerated after each
|
||||
migration.
|
||||
|
||||
**How to test.** Track the number down, every time:
|
||||
|
||||
```bash
|
||||
ls packages/ui/components/*.wrn | wc -l # 108
|
||||
grep -l '^ style {' packages/ui/components/*.wrn | wc -l # 42 today
|
||||
bun run build && gzip -c examples/basic-app/dist/ui.css | wc -c # 26350 today
|
||||
ls packages/ui/components/*.wrn | wc -l # 102
|
||||
grep -lE '^\s*style\s*\{' packages/ui/components/*.wrn | wc -l # 102
|
||||
bun run build
|
||||
bun -e "const b=await Bun.file('examples/basic-app/dist/ui.css').arrayBuffer(); console.log(Bun.gzipSync(new Uint8Array(b)).length)" # 3762
|
||||
```
|
||||
|
||||
Add a ratchet test asserting the gzipped size only ever decreases, so this
|
||||
cannot silently regress.
|
||||
|
||||
### 4.2 Dev server: `packages/ui` edits do not take effect
|
||||
### 4.2 Dev-server package UI hot reload — FIXED
|
||||
|
||||
**Issue.** Editing a component under `packages/ui` does not hot-reload. Every UI
|
||||
change this session needed a full server restart, which is the single biggest
|
||||
@@ -529,9 +563,9 @@ confirming it.
|
||||
`packages/ui/components/*.wrn`, reload the page **without restarting**, and see
|
||||
the change. That is the whole acceptance criterion.
|
||||
|
||||
### 4.3 Running tests wipes the cache of a live dev server
|
||||
### 4.3 Per-process development caches — FIXED
|
||||
|
||||
**Issue.** Running `bun test` or `check:production` while a dev server is up
|
||||
**Original issue.** Running `bun test` or `check:production` while a dev server was up
|
||||
empties `.wrnexus`, and every client module then 404s until a restart. This
|
||||
produced several confusing "the Sidebar is broken" symptoms this session that
|
||||
were not component bugs at all.
|
||||
@@ -541,7 +575,12 @@ were not component bugs at all.
|
||||
`WRNEXUS_PRESERVE_CACHE=1`. Any dev server a test spawns against the repo root
|
||||
therefore deletes the running server's cache.
|
||||
|
||||
**Change.** Two options, both cheap:
|
||||
**Change.** The first option was implemented: each process uses
|
||||
`.wrnexus-<pid>`, normal server shutdown removes its directory, and
|
||||
`.wrnexus-*/` is excluded by Git, ESLint, TypeScript, and Prettier so an
|
||||
interrupted process cannot pollute a commit or a repository-wide tool run.
|
||||
|
||||
The considered options were:
|
||||
|
||||
- Give each dev-server instance its own cache directory (keyed by port or pid),
|
||||
so instances cannot collide; or
|
||||
@@ -553,23 +592,22 @@ Prefer the first — it fixes the class rather than one caller. Either way,
|
||||
owns.
|
||||
|
||||
**How to test.** Start the dev server, load a page, run `bun test packages` to
|
||||
completion, reload the page without restarting. It must still work. This fails
|
||||
reliably today.
|
||||
completion, reload the page without restarting. It must still work.
|
||||
|
||||
### 4.4 Generated artefacts have an order dependency
|
||||
### 4.4 Generated artefact ordering — FIXED
|
||||
|
||||
**Issue.** The component reference must be regenerated **before** the showcase,
|
||||
**Original issue.** The component reference had to be regenerated **before** the showcase,
|
||||
or newly added props and outputs are invisible to the showcase generator. This
|
||||
is undocumented and cost real time.
|
||||
|
||||
**Change.** Make the showcase generator depend on the reference explicitly, or
|
||||
fail loudly when the reference is older than the component sources.
|
||||
**Change.** Done. `generate:pages` regenerates the reference first, and both
|
||||
generators emit idempotent Prettier-compatible artifacts.
|
||||
|
||||
**How to test.** Add a prop to a component, run the showcase generator without
|
||||
regenerating the reference, and assert it errors rather than silently emitting a
|
||||
stale page.
|
||||
|
||||
### 4.5 Two authoring traps worth compiler errors
|
||||
### 4.5 Compiler diagnostics for authoring traps — FIXED
|
||||
|
||||
**Issue.** Block comments are not allowed inside `props {}` — line comments
|
||||
only — and `state page` collides with the `page` keyword. Both currently fail in
|
||||
@@ -593,9 +631,9 @@ the real number.
|
||||
"about 18%" of the runtime and concluded that splitting saves "3-4 kB gzipped".
|
||||
Both figures were wrong, and the conclusion that followed from them was wrong.
|
||||
|
||||
#### The runtime today
|
||||
#### Runtime before remediation
|
||||
|
||||
`reactive.js` is **70,101 bytes minified, 21,736 gzipped**. Removing each
|
||||
`reactive.js` was **70,101 bytes minified, 21,736 gzipped**. Removing each
|
||||
subsystem and re-minifying gives its true cost:
|
||||
|
||||
| Subsystem | Minified | Share |
|
||||
@@ -619,7 +657,11 @@ fetch, which are framework features) total **23,722 minified / 6,660 gzipped —
|
||||
15,076 gzipped: the expression engine, the scope and reactivity core, and loop
|
||||
diffing.
|
||||
|
||||
#### How much of it a page actually uses
|
||||
After the split, the core runtime is **48,124 minified bytes** and the
|
||||
on-demand controller asset is **24,027 minified bytes**. `/` requests no
|
||||
controller asset.
|
||||
|
||||
#### How much of it a page actually used before the split
|
||||
|
||||
Measured against the example app by checking which controller markers appear in
|
||||
the served HTML:
|
||||
@@ -650,9 +692,9 @@ bun run scripts/lib/measure-runtime-size.ts # core must stay under budget
|
||||
|
||||
Plus a browser check on `/`: zero controller chunks requested.
|
||||
|
||||
#### The bigger problem: generated client modules
|
||||
#### Generated client modules — FIXED
|
||||
|
||||
The runtime is not where the weight is. On `/navigation`:
|
||||
Before remediation, `/navigation` measured:
|
||||
|
||||
- **490,212 bytes decoded** across 11 client modules, **21,026 transferred** —
|
||||
a 23:1 compression ratio.
|
||||
@@ -661,6 +703,9 @@ The runtime is not where the weight is. On `/navigation`:
|
||||
- One line appears **162 times**: `brand = context.state.brand; topLinks =
|
||||
context.state.topLinks; ...` — the full state-restore prologue.
|
||||
|
||||
After hoisting and closure deduplication, the page is **101,253 decoded bytes**;
|
||||
the largest module is **51,946 bytes** with **2.4% duplicated lines**.
|
||||
|
||||
Gzip hides this on the wire, but **parse and compile cost scales with decoded
|
||||
bytes, not transferred bytes**. Half a megabyte of JavaScript is parsed to run
|
||||
one page.
|
||||
@@ -720,16 +765,18 @@ bun run build
|
||||
|
||||
Add the ratio itself as a signal: any module compressing better than about 10:1
|
||||
is duplicating itself and should fail the check. Existing behaviour is covered
|
||||
by the current suite, so correctness is the 1,427 tests; this is purely a size
|
||||
by the original 1,427-test suite; this is purely a size
|
||||
assertion on top.
|
||||
|
||||
### 4.7 `generate-ui-complete-catalog.mjs` is broken and destructive
|
||||
### 4.7 Destructive generator — REMOVED
|
||||
|
||||
**Issue.** The script fails partway through with
|
||||
**Original issue.** The script failed partway through with
|
||||
`TypeError: factories[entry.category] is not a function`
|
||||
(`scripts/generate-ui-complete-catalog.mjs:156`) — but not before it has already
|
||||
started writing. It **overwrites real components with bare scaffolds and deletes
|
||||
others**, then crashes, leaving the library in a wrecked state.
|
||||
started writing. It **overwrote real components with bare scaffolds and deleted
|
||||
others**, then crashed, leaving the library in a wrecked state. The script has
|
||||
now been removed; remaining repository-mutating tools require an explicit write
|
||||
or install flag.
|
||||
|
||||
**Evidence.** Running it on 2026-08-09 rewrote Accordion, alert, Badge,
|
||||
AvatarGroup, ToggleCount and LayoutSplitter down to ~15-line stubs, deleted 24
|
||||
@@ -754,10 +801,11 @@ a case-sensitive filesystem the same script produces duplicate files instead.
|
||||
Whichever is chosen, **no script that rewrites `packages/ui/components/` should
|
||||
write in place.** Generate to a staging directory, validate, then move.
|
||||
|
||||
**Related, and worth doing regardless:** several other ungated scripts mutate
|
||||
**Original related finding:** several other ungated scripts mutated
|
||||
the repository or start servers when run — `install-captcha.mjs`, the
|
||||
`validate-*.mjs` and `verify-*.mjs` families, and `benchmark-framework.mjs`. All
|
||||
of them fail today. They should either be repaired and gated, or removed. A
|
||||
`validate-*.mjs` and `verify-*.mjs` families, and `benchmark-framework.mjs`.
|
||||
Obsolete validators were removed, current validators were repaired, and useful
|
||||
mutating scripts now require explicit flags. A
|
||||
`scripts/` directory where running a file at random can scaffold apps, start
|
||||
dev servers on ports 3000-3002 and rewrite the component library is a hazard to
|
||||
anyone exploring the repo, human or otherwise.
|
||||
@@ -772,7 +820,7 @@ Add a check that runs each generator in `--check` mode and fails if it would
|
||||
modify tracked files, the way `check:workspace` and `check:public-api` already
|
||||
do.
|
||||
|
||||
### 4.8 Filename casing is not consistent with the git index
|
||||
### 4.8 Filename casing — FIXED
|
||||
|
||||
**Issue.** Four components were tracked in git under lowercase names
|
||||
(`card.wrn`, `container.wrn`, `divider.wrn`, `grid.wrn`) while existing on disk
|
||||
@@ -797,10 +845,10 @@ must return the capitalised names. Better, set `git config core.ignorecase
|
||||
false` locally so a future rename cannot hide again, and consider a check that
|
||||
compares `git ls-files` against the on-disk listing byte for byte.
|
||||
|
||||
## 5. Order of work
|
||||
## 5. Historical order of work
|
||||
|
||||
Ranked by return, not by size. The first item changes the cost of every item
|
||||
below it, which is why it is first.
|
||||
This is the execution order used during remediation, retained as implementation
|
||||
history. Every row is complete.
|
||||
|
||||
| # | Item | Why now |
|
||||
| --- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -810,7 +858,7 @@ below it, which is why it is first.
|
||||
| 3 | §2.6 server-rendered i18n | Contained work; the core SSR claim currently fails on text. |
|
||||
| 4 | §4.2 + §4.3 dev loop | Cheap, and it compounds across every task below. |
|
||||
| 5 | §4.6 de-duplicate generated client modules | 490 kB decoded parsed per page, 90% of it duplicated. Codegen-only, no API impact. |
|
||||
| 6 | §4.1 finish the CSS migration | 66 components, mechanical, takes ~26 kB off every page. |
|
||||
| 6 | §4.1 finish the CSS migration | Localized component ownership and reduced the always-served sheet by ~160 kB. |
|
||||
| 7 | §4.6 split controllers out of the core runtime | 6.6 kB gzipped a typical page never executes. |
|
||||
| 8 | §3.1 Group A: supersede the 5 duplicate scaffolds | 15 of the 22 dead outputs, and 5 fewer components to maintain. Needs a migration entry, not new code. |
|
||||
| 9 | §3.2 scaffolds that need only their styles | Larger and cheaper than it looks; folds into item 6. |
|
||||
|
||||
@@ -1316,6 +1316,7 @@
|
||||
"createOutputProxy",
|
||||
"createServerProxy",
|
||||
"getActionRuntime",
|
||||
"getComponentControllerRuntime",
|
||||
"getNavRuntime",
|
||||
"getReactiveRuntime",
|
||||
"getRealtimeRuntime",
|
||||
@@ -2603,6 +2604,7 @@
|
||||
"StylesMode",
|
||||
"THEME_COOKIE",
|
||||
"THEME_CSS_HREF",
|
||||
"THEME_CSS_PREFIX",
|
||||
"THEME_JS_HREF",
|
||||
"THEME_PALETTES",
|
||||
"THEME_PALETTE_NAMES",
|
||||
@@ -2613,6 +2615,7 @@
|
||||
"ThemeSemanticColor",
|
||||
"ThemeToken",
|
||||
"ThemeTokens",
|
||||
"activeThemeCssHref",
|
||||
"auditCssPerformance",
|
||||
"auditWireTokens",
|
||||
"bundleCss",
|
||||
@@ -2628,6 +2631,7 @@
|
||||
"loadEnv",
|
||||
"loadRawConfig",
|
||||
"normalizeStyleSources",
|
||||
"renderActiveThemeCss",
|
||||
"renderFontHead",
|
||||
"renderProductionFontHead",
|
||||
"renderStyles",
|
||||
|
||||
@@ -2,114 +2,111 @@
|
||||
"schemaVersion": 1,
|
||||
"releaseLine": "0.8",
|
||||
"artifacts": {
|
||||
"packages/ui/components/Accordion.wrn": "2b2e756a2197052bbcaf7499d01ec4b68b53a3acaf4524b7ca6424b71ba2e9a3",
|
||||
"packages/ui/components/AdvancedDatePicker.wrn": "b9b5b08e9464544837a5400bbf9a2cb25724fac037fe196a219b42e5841ad672",
|
||||
"packages/ui/components/AdvancedRangeSlider.wrn": "e78b5cf825503d0cd306c6e490d71317ee3db1844882d13b500df1f7f1c408e1",
|
||||
"packages/ui/components/AdvancedSelect.wrn": "29d43f8019a3e0e98af7cf12440f41523665535a39a973441b4faea78e67114a",
|
||||
"packages/ui/components/Accordion.wrn": "285df0aa50733618afb1b82aa125a67030d3af4bd8b9de52bc2e6366cea871de",
|
||||
"packages/ui/components/AdvancedSelect.wrn": "e8666638204f9eb044ee6f163d6af432b119bf7f39d9df20243cfae471d20bcd",
|
||||
"packages/ui/components/AnnouncementBar.wrn": "903bfd9421a7350c923ace3315cedc5763fe3e6b7b54b660a0f26281d46a8754",
|
||||
"packages/ui/components/AuthForm.wrn": "496d240090269e69679e01cda3b071c012ec4e925a7c01ae883d25e359e381e1",
|
||||
"packages/ui/components/AuthSplitLayout.wrn": "72b82495dd14d40a2d594ccdcf3adc925d1567c155750fb1072347b8845aab7b",
|
||||
"packages/ui/components/AvatarGroup.wrn": "66f3d211b7658ca5193c750d0ccb3e392185038d8960d3206f6e8a249e10214e",
|
||||
"packages/ui/components/AuthForm.wrn": "ec8796a67320afa2fb2070edba194382d1ab0a00cbd574f936a502da819d00fa",
|
||||
"packages/ui/components/AuthSplitLayout.wrn": "675c6ac9b07e0e9c5d6572f30028dda2391f9c6599b45945eb626df056a5b987",
|
||||
"packages/ui/components/AvatarGroup.wrn": "85dd6c64f8064d0980bf4157963bf366410c2ba391fd65df86a54b1a336f43a4",
|
||||
"packages/ui/components/BackToTop.wrn": "b12c56ec8b7aabad68cbdc47f4b3237e2c21a61398f0aa51f9add6229b7357cd",
|
||||
"packages/ui/components/Badge.wrn": "e96cdcf182ff9834509464bb2fa4c0b041748d3f7671aa46ab489bae6f3d98ae",
|
||||
"packages/ui/components/Blockquote.wrn": "6016dd4450de7cbf5c3cb413599baa2e502321e8eedef54439a0df6d0aae8bcb",
|
||||
"packages/ui/components/Breadcrumb.wrn": "14732e91468f5793ee5da8599f8e40d84ec51c93d660fb0c0056b956ec707850",
|
||||
"packages/ui/components/ButtonGroup.wrn": "0d97fbeed531d1d8ef4b59531923b47b34100d0de71d7bf524b02c668920b3d8",
|
||||
"packages/ui/components/Blockquote.wrn": "6f91cee9538c11858aaeb98ef1a098a814a6e319e3777e59cecd2de078b3beaf",
|
||||
"packages/ui/components/Breadcrumb.wrn": "5ab85d273ce72e4d5b87907975bb33f0249a34f2f87f01dc0812e827f775eed9",
|
||||
"packages/ui/components/ButtonGroup.wrn": "0bb23d927e03926cc303498bc4ac1ab098343782082ac1fceeb2402702945842",
|
||||
"packages/ui/components/CTASection.wrn": "2d2241d4f1018cfd2f8ea1ddd5fa8c8fcb8d1bbb2d4e4498cb9f39ff47a3801c",
|
||||
"packages/ui/components/Card.wrn": "457bc546e0b227692ac96ec8f413823463d3cfe9194b6f4847c21a5beee5e8a4",
|
||||
"packages/ui/components/Chart.wrn": "841def6b024720bd24898975a1434a1cb39c89e1272d42db431d35d27c98c553",
|
||||
"packages/ui/components/ChatBubble.wrn": "0da2bc86830ac712481559bd2fe7214d26f25101f2e8c2e4629e25c004388b52",
|
||||
"packages/ui/components/Checkbox.wrn": "45e6046e3ad4acb1c1805b51fd5bf0b95b346d3d53973ededc5e43ea2fd91f70",
|
||||
"packages/ui/components/Clipboard.wrn": "bb8651c7e44517db827603d0c0e546b6891093d7e6589c0b6e45d91b6187591b",
|
||||
"packages/ui/components/Collapse.wrn": "c537239831c6938f3c39be0ca3ae8ba928c2490e21cfe5853c10f7221a4678ca",
|
||||
"packages/ui/components/ColorPicker.wrn": "6940b7ae48f1358008b8c3ae6dc7da31f96a1aefadeef18252568b2cda0b5cbf",
|
||||
"packages/ui/components/Card.wrn": "ba1b577ae4ffa9bd93c1e6a650ce90745df3f78cd6ff9ee85530e32dab56860a",
|
||||
"packages/ui/components/Carousel.wrn": "0d39e17a623fb623022f48e79ea1ab9f268e2eb359c491609c137f711a1a3ffc",
|
||||
"packages/ui/components/Chart.wrn": "d2cedd0e02818d0908708c2d07a495c629fad13fa021a2e035d5023dc5c367de",
|
||||
"packages/ui/components/ChatBubble.wrn": "c46b9aef38f54e6243663046fe1b18ba6f9223cbe9b479d9335f96f217d401cc",
|
||||
"packages/ui/components/Clipboard.wrn": "03fcfb9786008fd7fb0a7719c3279711253afc5bbb9b0262b805dd553c44da8a",
|
||||
"packages/ui/components/Collapse.wrn": "8869ff4a39541b7e0d538e2b63b9a3dcdcfcd4055fa362519c7e73f164f1be9d",
|
||||
"packages/ui/components/ColorPicker.wrn": "02f5744c3eb984de4b24de26fbfd3f69021633d4b0b33aceeba84df32f6a7284",
|
||||
"packages/ui/components/Columns.wrn": "e01496aa53025444cc51671efcce40d6d24d86775b2cbb1ee0def1862bb6315d",
|
||||
"packages/ui/components/Combobox.wrn": "62a7d861b0e536ceb088cea3552c59acbafaea5184101e3651dc8f4976b99dbc",
|
||||
"packages/ui/components/Confetti.wrn": "22d687beefd5047055f65e806b4e39f81e18887532ec18d439ac364a044b230a",
|
||||
"packages/ui/components/Combobox.wrn": "140b038735a5902eb901386281a5a01e16fd1c345560a48ff2b143ebaebe1f3d",
|
||||
"packages/ui/components/Confetti.wrn": "b9aece64735c721c24356122e52ca84602d9f646c7d8be1e0b80d9de3163a501",
|
||||
"packages/ui/components/Container.wrn": "4a0acbb896f4c11714e3925d912b10ef9aeedfa904b4ad8561aa2cbd9a0fdca0",
|
||||
"packages/ui/components/ContextMenu.wrn": "2e011e9cb1c09a3a2344ed3fa29dcc74331cd39f838535d8aee4b249dceef0ce",
|
||||
"packages/ui/components/CopyMarkup.wrn": "8d57a7d72e02c126f855a181fc36e1b969edb90f683dce6b6ffe546d49d2b29b",
|
||||
"packages/ui/components/ContextMenu.wrn": "33fb0f60268b12411d66e3031dc0557cb231149e7a8d83340b4b453a6e37cb09",
|
||||
"packages/ui/components/CustomScrollbar.wrn": "fedb2ab06cc2cf9499c130590945ca6971a3edfb60407b6658ef7756e4be9823",
|
||||
"packages/ui/components/DataMap.wrn": "65552d73ecd1a148427dbb10d611f9352ca36bd490f595ab1e1a47e173445ba1",
|
||||
"packages/ui/components/DataTable.wrn": "5a76e50e8559c724aaf01ec954958ac640c99758d44f1eef8e6eb1cb6dc4f862",
|
||||
"packages/ui/components/DatePicker.wrn": "18b07e59c27c2720bf960e9c04ad70d9e422ddcf0817fede74e71899811bb387",
|
||||
"packages/ui/components/DeviceFrame.wrn": "a881cf2cc1f859b43cc3c7a1e1ca0fb40cea7a9e530ab5bdcb985aa9592d11c0",
|
||||
"packages/ui/components/DataMap.wrn": "6058f92be7fb95e9eedcc714cad1ad43fa70e40c9dd50fd8bba5aae9935953a7",
|
||||
"packages/ui/components/DataTable.wrn": "edb935b091750ee3d1aff354007eabd4998fc7e5584d50a6c726c6657765d800",
|
||||
"packages/ui/components/DatePicker.wrn": "2c46f92612528cf60aa0b7e8aaae9826094acb5518d3982c2db65aca6d01e103",
|
||||
"packages/ui/components/DeviceFrame.wrn": "d918d213e6e0b74d690911ab3b3f440e9b62fa019fd9e8140c5a56c5c0a0843d",
|
||||
"packages/ui/components/Divider.wrn": "f5cc449611022b3d3eedf982ae48f6b1a289db7855cc9b6ea0248dcda807ffb3",
|
||||
"packages/ui/components/DragAndDrop.wrn": "9a403ce9ed20d36211d8916911871875c9c7d0952a7de1a21a433eaa0b3c3324",
|
||||
"packages/ui/components/Drawer.wrn": "04a77a69edfad6793b5ab60981a719f11061984c087d0d61663566e322649fef",
|
||||
"packages/ui/components/Dropdown.wrn": "c61b46ae6e54f6aacac9f4ed422f6c659deea9a637fd944645acd37bf270b01d",
|
||||
"packages/ui/components/FeatureCard.wrn": "a6afd6d4080917106b2c504ea3836b92ac3c18e85f1aafd74ffe49592782b5ec",
|
||||
"packages/ui/components/FeatureGrid.wrn": "9e3c0440d08c6982861d3e732eeb20f1023413a58243844dfb9d7033bac13d2b",
|
||||
"packages/ui/components/FeatureIconCard.wrn": "afe56213543dd78932b060fa19c4e645e558b211b380c42b6d4b0d18e9d543df",
|
||||
"packages/ui/components/FileInput.wrn": "8b63811deb90a03763620bedf0d5d3c7d05a20b8ae34292eaface9b756b32ecd",
|
||||
"packages/ui/components/FileUpload.wrn": "3346d8978e134c3a1e6bc742201a3fbb0cffb89da40da6fcd856a2adbb971393",
|
||||
"packages/ui/components/FileUploadProgress.wrn": "83bcc8558c70fbe0e0670642f65998abd222c235f1031be46bd7c65e6b5c5154",
|
||||
"packages/ui/components/Footer.wrn": "d9075d2206af75a1ec4317d7974d47311889400699260e990d672780f43f40bf",
|
||||
"packages/ui/components/DragAndDrop.wrn": "31ffe9d41fc655a38257c841a520c947d3ce2338e174b5e89e32fe9ccf23b9b3",
|
||||
"packages/ui/components/Drawer.wrn": "e6e2778e515361a3475439ab91b6efec3895efab2a4dfdcfb535713f656cc7ed",
|
||||
"packages/ui/components/Dropdown.wrn": "627319de0f555f0f07f80e19a59f9fd52e7f74af7cf2cf9ff09e114678ad9eb8",
|
||||
"packages/ui/components/FeatureCard.wrn": "2a1bfb01cec8a9892d3ff066e48c276fa7c548cb59c15bcd9b4bbf85450a07a2",
|
||||
"packages/ui/components/FeatureGrid.wrn": "9bd3e9becb5d7359841ed21d607131c58da20f2e8e87e25a9fb48f6b1a2b17e5",
|
||||
"packages/ui/components/FeatureIconCard.wrn": "620f66a36ca5c8ad8f2ea09e94dc4471e4146189c42149e9d31db58501281639",
|
||||
"packages/ui/components/FileInput.wrn": "cc4b8f95f11d91ead9a5796e202b2264eeea24aa1c3b1718327a6ea1dd28d785",
|
||||
"packages/ui/components/FileUploadProgress.wrn": "c4b4174025a1de75c35a3497e0afd0b8acc645ad5f85c3ed7296ea295bb05749",
|
||||
"packages/ui/components/Footer.wrn": "71790c99e4b15a62fdaf4b7a65d7016ca427408b62a2b1c7695ef1b39ba29440",
|
||||
"packages/ui/components/Grid.wrn": "d089d8657298b4d33993ba4bf7b581f816c65db513d8e48cf1fdd18da19e05a8",
|
||||
"packages/ui/components/Hero.wrn": "e4e39dcd59c273192c7e4136f6583279e20b0956bcba4cfbc3f6e01a1fade152",
|
||||
"packages/ui/components/HeroActions.wrn": "c5720efe2ce7f6c114eb2ea4ccc3e8102a1099078ba23fd13770eddeb6aec640",
|
||||
"packages/ui/components/HeroActions.wrn": "77f69085db73b946c00aa684d50fb5ae474684e440e81edbccfd8ef5dc357e33",
|
||||
"packages/ui/components/Image.wrn": "8113441bf90ab0d1d8618d477125ad339021f39e853db89d656c72c492e7a20b",
|
||||
"packages/ui/components/Input.wrn": "9ad394eed60f1b05bbd2ccd50dc2de7b78245c52c87afd46055d772c38b7b0c9",
|
||||
"packages/ui/components/InputGroup.wrn": "0e23ea541a60e893c9d9e6f95113911ee459468d8d6105bf03aeb55395359123",
|
||||
"packages/ui/components/InputNumber.wrn": "26936385e9f01ec888a1df61719991aa7a2782305328b02c939d3c2c94e3e8e0",
|
||||
"packages/ui/components/InputGroup.wrn": "95333e7cb883c52958e3d6112899f337b00825e7678a2323baed1724983a7238",
|
||||
"packages/ui/components/InputNumber.wrn": "0ec020084f3e556d438f8264d61f836cfab4951c52d4df60e4944fb734fb38a9",
|
||||
"packages/ui/components/Kbd.wrn": "5afaabbb15902bd8fb82cb4155cf6cbfff55600927b54d564d27e2dd8b752386",
|
||||
"packages/ui/components/LayoutSplitter.wrn": "6bd6812e364420ed5d3439862116c58d0b011b298679005795bdccdbb7455066",
|
||||
"packages/ui/components/LegendIndicator.wrn": "6a7607b27a17196f899132eb952203a1073e4077365a94935f485e700cbac665",
|
||||
"packages/ui/components/LegendIndicator.wrn": "11f13413242e106daf347638ee2b2313fd20b05fc7001f03c0f4c00a558dfec0",
|
||||
"packages/ui/components/Link.wrn": "b986abcc66bc0b1c6296f3ff4e560812e35d89cc680d5251d7a3883e68c1d053",
|
||||
"packages/ui/components/List.wrn": "8cf4505ff6a079feb3b77822d7ed6977a70393dd1e6267f94e75234d3ac215d6",
|
||||
"packages/ui/components/ListGroup.wrn": "37e2ec61ceab20ed020a834da03208c493702b41abcf586816e29f2e50d8ee11",
|
||||
"packages/ui/components/Map.wrn": "990656db93e1acd73a301a4addaf353afa14430805366d2e5f124b831044afbe",
|
||||
"packages/ui/components/MarketingSectionHeader.wrn": "7d6ffcc01a9331474475ea57ca58598be757abd9428b66bdfaf6f3603c5b145b",
|
||||
"packages/ui/components/Marquee.wrn": "3395235807aea43ac10c4d562706e510dac2a32786b4a5d5d73927989ede68df",
|
||||
"packages/ui/components/List.wrn": "9bfa9613f388e5b126fc27d505e8165d7aa4a058e72132b755585464e116490e",
|
||||
"packages/ui/components/ListGroup.wrn": "885020eea4dbdfc90b5a659e48a339cba7a205ddb539e30d7f1aa57244d728a5",
|
||||
"packages/ui/components/Map.wrn": "9023a7a188480b24222a3910d26f6926918470369a1ab610f841c31a6120b9aa",
|
||||
"packages/ui/components/MarketingSectionHeader.wrn": "6f89a3b298ea299db686ddf903f92c4fe5d321719213943b16be82ba36c23302",
|
||||
"packages/ui/components/Marquee.wrn": "2a88c8bd8378469add26b0d937adccac4aa9f2c2005393010298e8e4fa89693e",
|
||||
"packages/ui/components/MegaMenu.wrn": "8a9a9b348ca1c5e182914ff55d981a91f47a49c52ef5e5efd8e4cb6e56361a43",
|
||||
"packages/ui/components/MetricCard.wrn": "6451182739298691908f68258c0250cce2a78b0dc27c97115579ece30d7d9f92",
|
||||
"packages/ui/components/MetricGrid.wrn": "6018a98c10628ed240ed236ed916c0ff60994d192c3aadafd10bc876be0f9364",
|
||||
"packages/ui/components/Modal.wrn": "59d4ae9d6dd2700692d9edecfe53ee868e9f864363a4885961d1813cb2bee51f",
|
||||
"packages/ui/components/Nav.wrn": "7dc456b879e9248bde267b986cdcb138b8f3127d98b51e27bc9708129953f5e2",
|
||||
"packages/ui/components/Navbar.wrn": "7989d25819312014c0a0855370b80b925d53ff735d682fdb61e9677a9dacb7f3",
|
||||
"packages/ui/components/PageHeader.wrn": "0761235a4924eec09877be40b292d27b70db22d210be7d8e989493165c20df86",
|
||||
"packages/ui/components/Navbar.wrn": "70640f9ff6e615c70ea9a5e996058bad45ebf0e97b02c9d827228a8f475f72e2",
|
||||
"packages/ui/components/PageHeader.wrn": "59264dfb26a132fd3eb265c4374f9fcae781f30dd94a087c57e6e8256717cf14",
|
||||
"packages/ui/components/Pagination.wrn": "9ceb74745b159150b015bbbb1974c68e14a7d4b92bd03491cb23b8855aa07983",
|
||||
"packages/ui/components/PinInput.wrn": "5196f584de8d548a5dfa03688c3c95da3c948cead2662b05e927386ccea74299",
|
||||
"packages/ui/components/Popover.wrn": "167f6c476cf3114ac5062ecf7739bddd9b5a81b1d209396a3675067dad1577b2",
|
||||
"packages/ui/components/PortalDashboard.wrn": "037d4300b59c7d60543abc7d4aba5c738efc143e9b61abc48aad0ddfcfe6845b",
|
||||
"packages/ui/components/PreferenceSwitcher.wrn": "2cc186d4dcb6580b4b152e3a265d9ed5ad210d3fdc76330b9092db21894465f0",
|
||||
"packages/ui/components/PinInput.wrn": "2b656cf832c02fa9c28e2fe78bf1147ff05b17a9d8e744c33e1a63bad7c7ebb3",
|
||||
"packages/ui/components/Popover.wrn": "f2996f00fad3e35fba2d48e70af17f592854eb73b9e8c6a83526aa78c4e41293",
|
||||
"packages/ui/components/PortalDashboard.wrn": "79b54868f150a551cede0731cc5390a45ce22375f1929f2d537ce67a987bde50",
|
||||
"packages/ui/components/PreferenceSwitcher.wrn": "241060fb24a16e795c53981506d273fe9110cbb250c73c85928c788c75d05ded",
|
||||
"packages/ui/components/PublicPageShell.wrn": "369b74f65e27227311a2ed73e9ca56c8b60908100d6840a798e8ba5f2c8b1040",
|
||||
"packages/ui/components/Radio.wrn": "0b2d38a5aee3e859280e4590fa1d509d789cc2239082054872fa342817735f1b",
|
||||
"packages/ui/components/RangeSlider.wrn": "21eb7a5df0ee2cb5e78f628eb920265998eb9f1a4933d4e5c57db0323fd66b41",
|
||||
"packages/ui/components/Rating.wrn": "a2d74dc748fc7d98892684c760518b87fa65411b1102e3611252b87245b3ffcb",
|
||||
"packages/ui/components/RangeSlider.wrn": "eff9db1616d424f9c803185b4efe011942866acad3328179438a200210d97710",
|
||||
"packages/ui/components/Rating.wrn": "41bbce2eb7fb6ad08a3aacde97d03af6e346824962148e7655743028c59185d3",
|
||||
"packages/ui/components/Scrollspy.wrn": "76d1c17580c8d460f1222ce97585bdcac9779141f2f30d5460c84ff9f75413d8",
|
||||
"packages/ui/components/SearchBox.wrn": "4a07358530ffb197d80d816b6ee4335a20812b6bee1e66a845c4075305bee6f7",
|
||||
"packages/ui/components/SearchBox.wrn": "717f7647fabd762f8ed60a7c3306eac3ddb83c0da292a53c76d91886c2b8f94f",
|
||||
"packages/ui/components/Section.wrn": "33eb8a82a2102229c2b1f64cec497e9e8e70b67adab8dbe396be860fccbb699c",
|
||||
"packages/ui/components/SectionHeader.wrn": "8eecf2ea7a93139512260e3d46ca2a1afeb341b8bd46b7c30baa6e2c75e25d10",
|
||||
"packages/ui/components/Select.wrn": "7f046bb11b7c2470dae26d91a4b2261c66a40ae054d0e04c69b6748912d1e204",
|
||||
"packages/ui/components/Sidebar.wrn": "03f1bbce75ca6d50ed940e62df39c539610e89eadd2064fe24abc7d5470f98bf",
|
||||
"packages/ui/components/SplitHero.wrn": "70e843565ff869bcf413b11b6d9830b2e2bd229863a00904596f71e2dff0e76d",
|
||||
"packages/ui/components/StatsBar.wrn": "c7d1df3895f25b6d3a2e1012a18b97592c7f33e65418b880d486f20c2d490686",
|
||||
"packages/ui/components/Stepper.wrn": "df9093e4222ef3250963a8c6fbfec74477242735d23eabe896c29731c54fc14d",
|
||||
"packages/ui/components/StrongPassword.wrn": "c7c5ef26ece6170dd7db9882f0dc98cb2e4607f1e5d43eb3fd39e5d15bbd3a2e",
|
||||
"packages/ui/components/StyledIcon.wrn": "4a4504e357dee9dd0418edbe85fc90b824ccdb3752123a2125da95b77bf0b336",
|
||||
"packages/ui/components/Switch.wrn": "ccb74599fab72b0d68b09a7f1f90b7732cb2f9cbed67a84219e897575ce30c28",
|
||||
"packages/ui/components/StrongPassword.wrn": "0df060bf01ace77ad0158df489588df75fd5d426dc04f000abacc2b3ddd93fc5",
|
||||
"packages/ui/components/StyledIcon.wrn": "bf9b41e9c402c4886feddda504c3f12348bbbe49b9003de69d807db4bf52d1b1",
|
||||
"packages/ui/components/Tabs.wrn": "e38336d28876caa4e97da32a569749e3803b030284a85ce9f35fa788e51cc5b0",
|
||||
"packages/ui/components/TextLink.wrn": "30782039293eb36d63b7b3a4f32a71a47177a3a68c7e90184be7cf7b4385eb19",
|
||||
"packages/ui/components/Textarea.wrn": "ddf0b4f124b2cf0c0ab3d820d3ac0085f7c20466e977231949be264cd0cee8cf",
|
||||
"packages/ui/components/TimePicker.wrn": "2e8e7a90f6b6069a07e7ffd2725ba1e1031e84d55a4f1254025befbb314fa695",
|
||||
"packages/ui/components/Timeline.wrn": "aec123bf69f6bfa41c3564e4eac18b2a245214dc6905ebd0bf5b69f7c2736ae6",
|
||||
"packages/ui/components/Toast.wrn": "f37c584d1c1a66401deb53d915c8ee0c70aaf7baa1d3c297fa74c5bdb914dd1d",
|
||||
"packages/ui/components/ToastNotifications.wrn": "33ff76b2a0a129ff896baea8979b4be97f7ec4471a92a2da970403a5c46e8b03",
|
||||
"packages/ui/components/Toaster.wrn": "7481ecddde9ed5bf1f448d45a78da7ee0009f84963814bf681735db4e5ab75f9",
|
||||
"packages/ui/components/ToggleCount.wrn": "17710fb37fc6953911acec4e80cd95c1202836e9b958ae4ce34c9a9639edae95",
|
||||
"packages/ui/components/TogglePassword.wrn": "405a85cbfa3d0ff0185b52d2805fba88497a5f25501f01b2438ed3a28591a38d",
|
||||
"packages/ui/components/Tooltip.wrn": "e6c8c14a75062d04a2e64245df1a40e67785240573c73bae4b0b32e76ef822bc",
|
||||
"packages/ui/components/TreeView.wrn": "1791a10135595b17b5c746af9e0476e8520d516ba46d64c79dc4efe0af2b500d",
|
||||
"packages/ui/components/TextLink.wrn": "6e18906337cd3d30c2a0122593d249bde8f7cedc697b90ae388e2bd0e8eff3e1",
|
||||
"packages/ui/components/TimePicker.wrn": "d2fb7cd156bb2abb997a9e85d27c36b80f4ba81515b523a125e793363568475c",
|
||||
"packages/ui/components/Timeline.wrn": "686a37ebc62732842846d004491336970db94945bca942f226097d863e159d1c",
|
||||
"packages/ui/components/Toaster.wrn": "288b2b370f65bf823e8d2a6f8c8cf66972332b8174303d33ebe8546d5e1d52c4",
|
||||
"packages/ui/components/ToggleCount.wrn": "285abf0ed5c7156c5a6f404af0c2519f181b591975515e845cbc3c21349c5cfd",
|
||||
"packages/ui/components/TogglePassword.wrn": "94cd3579bb48e53283c82400bff58ea0f2ca92646b87385502e3b474ba787a96",
|
||||
"packages/ui/components/TreeView.wrn": "c354fce9c5e81fed6eec4089dfd2a7f6bb84eb189c94efc88e00251efdc12c0b",
|
||||
"packages/ui/components/Typography.wrn": "1b4d1b91dee0ff522d27c566becc3bc49bd9ec979ae6334e6a15fca5d88eccac",
|
||||
"packages/ui/components/WysiwygEditor.wrn": "636e60b9f9be7a5807cca7ad20e0b0f370ee1ec6c1ecba5d0a577726cc86bc98",
|
||||
"packages/ui/components/alert.wrn": "3f4c4c90c2b5a00a13d0fa1e7e48e4898869dd27a50ff412a51d7bcc87a0baac",
|
||||
"packages/ui/components/avatar.wrn": "571b70790aff28a5b7d6adbf0196e77ece1ce153a95b384148cb2bb5f74ab2e9",
|
||||
"packages/ui/components/button.wrn": "647af3918142fd9151e46555345532980633cc3677ec26d0940c253ea789bc31",
|
||||
"packages/ui/components/carousel.wrn": "37bdde82de59e90cf02c9fed0ee11f5ddd2979246932701dad9412c4fd358a6a",
|
||||
"packages/ui/components/progress.wrn": "ba6f4dfcc00f04f34e9533be675bb4a6200c3cdcd7d03fc597377e48520fdec7",
|
||||
"packages/ui/components/skeleton.wrn": "ceec8af147b8be08155500e378393ac7c72d819da61b62191c076c80e943d5a9",
|
||||
"packages/ui/components/spinner.wrn": "abc4ee3ede2e019289257e9009eee012c880a85839cc220fac6dbe1b780caf52",
|
||||
"packages/ui/ui.css": "3138b86f269b6f7e67922ef2c56918444d562b11f4ae6f1fab98b63805cd78dc"
|
||||
"packages/ui/components/WysiwygEditor.wrn": "231da25f4064b065adb2783252d430aa336585e008547a010a41174a41284524",
|
||||
"packages/ui/components/alert.wrn": "6dde2cee818a66e1d80df6b93e9f971d5389f95387961593350b0c297e3ed367",
|
||||
"packages/ui/components/avatar.wrn": "ac2239cc746ec35b81a34ec6e27f4ee74ca04dc6058bad5853aa66640f2a9426",
|
||||
"packages/ui/components/badge.wrn": "9e1e884e9e2e9d881348c5121f53f4c85b91355cbfa49eeb2bff43f5ab38fab5",
|
||||
"packages/ui/components/button.wrn": "da1660c671daf2feffe1024203ac1a8bc947beb07691bef595c1552c664b9cde",
|
||||
"packages/ui/components/checkbox.wrn": "a5fb8410558696b3c8e63d5cd8dd49b44d51ed35b809cb2b189501e2e7b323e2",
|
||||
"packages/ui/components/input.wrn": "184066a837982ef3fe44e518730d0cccb7dd6772d5af58116c0774b71cb894e2",
|
||||
"packages/ui/components/progress.wrn": "61b01ba575d35691e3dfebfcb03ccd1c6b79f1df1c0d41857ec3b01944081a5d",
|
||||
"packages/ui/components/radio.wrn": "cc65395aad6c0bac690c18cb7c36b1d2784e4c3284be3d3cb277b745f2f29bbc",
|
||||
"packages/ui/components/select.wrn": "d58b454958edccce99f96c642eb67d5a0e64e2b97e58e742d445fe45164a7660",
|
||||
"packages/ui/components/skeleton.wrn": "dda95a2c59c6060ab2b9f916eb046fbe74a68b398a051afc0bde41808e059c3a",
|
||||
"packages/ui/components/spinner.wrn": "ffbd28ec6fc017fc94c67778f8966eab2eefa3b47f3f27f064c77dbeb65d84c1",
|
||||
"packages/ui/components/switch.wrn": "917219d77b2089584bc444fe94f2cb0670f800d73f69ae70f0368bfece16be0c",
|
||||
"packages/ui/components/textarea.wrn": "602ef2c8a081476f790408119b96ec2b51fb18abe76a43ee2b955dcb60debd17",
|
||||
"packages/ui/components/tooltip.wrn": "40028dfbb17f6725c76c2e091af9c30ea4db6aa4dea919d38f53874ea70315d1",
|
||||
"packages/ui/styles/ComponentBaseStyles.wrn": "566724a7bcc8f7caa067d5415177bf982ebae7b579a34052ce6f2092cbade4d7",
|
||||
"packages/ui/styles/FieldStyles.wrn": "460fc8cbb0228151927fd6a61caceae0a148fdbc3009c13ba511ab0a4010b42d",
|
||||
"packages/ui/styles/SelectStyles.wrn": "2c45e9c04678aaff76632e908b3c061410be0d6999a54a9203a39a3817d698f2",
|
||||
"packages/ui/ui.css": "b7e2ae796ffbb738451364f63fce13022a81d81207f7e49c7f6220d24c36acd9"
|
||||
}
|
||||
}
|
||||
|
||||
+168
-87
@@ -1,6 +1,6 @@
|
||||
"use strict";
|
||||
// Generated by scripts/build-editor-compiler.mjs. Do not edit directly.
|
||||
// WRN editor compiler source hash: 451706f47a7734fd5808cdeeb5786e1b97a183afec98db99fc041369eb16a205
|
||||
// WRN editor compiler source hash: 196a514b18fb6e31fdd8f4ca667a76b0b62cab547af33725ba78564d70bbc01b
|
||||
// WRN editor compiler generator hash: c71e7fe4258c97b73b384ff14b321f0cf0b30cc2ed0322f5f84b04e757159b18
|
||||
// Generated with TypeScript: 5.9.3
|
||||
const __nodeRequire = require;
|
||||
@@ -558,7 +558,7 @@ function browserModuleRequired(ast) {
|
||||
const functions = ast.runtimeFunctions.filter((fn) => ["legacy", "client", "shared"].includes(fn.runtime));
|
||||
return functions.length > 0 || selectedBrowserImports(ast, functions).length > 0;
|
||||
}
|
||||
function functionEntry(ast, fn, availableFunctions) {
|
||||
function _functionEntry(ast, fn, availableFunctions) {
|
||||
const parameterNames = new Set(fn.parameters.map((parameter) => parameter.name));
|
||||
/*
|
||||
* Names the function body declares for itself.
|
||||
@@ -608,32 +608,42 @@ function functionEntry(ast, fn, availableFunctions) {
|
||||
const syncStateFromContext = stateNames
|
||||
.map((name) => `${name} = context.state.${name};`)
|
||||
.join(" ");
|
||||
const peerAliases = functionAliases
|
||||
.map((name) => {
|
||||
const call = `context.functions[${JSON.stringify(name)}](...__wrnexusPeerArgs)`;
|
||||
if (!stateNames.length) {
|
||||
return `const ${name} = (...__wrnexusPeerArgs) => ${call};`;
|
||||
}
|
||||
return `const ${name} = (...__wrnexusPeerArgs) => {
|
||||
${syncStateToContext}
|
||||
let __wrnexusPeerResult;
|
||||
const peerAliases = !stateNames.length
|
||||
? functionAliases
|
||||
.map((name) => `const ${name} = (...__wrnexusPeerArgs) => context.functions[${JSON.stringify(name)}](...__wrnexusPeerArgs);`)
|
||||
.join("\n")
|
||||
: `const __wrnexusFlush = () => { ${syncStateToContext} };
|
||||
const __wrnexusRestore = () => { ${syncStateFromContext} };
|
||||
const __wrnexusPeer = (name, args) => {
|
||||
__wrnexusFlush();
|
||||
let result;
|
||||
try {
|
||||
__wrnexusPeerResult = ${call};
|
||||
} catch (__wrnexusPeerError) {
|
||||
${syncStateFromContext}
|
||||
throw __wrnexusPeerError;
|
||||
result = context.functions[name](...args);
|
||||
} catch (error) {
|
||||
__wrnexusRestore();
|
||||
throw error;
|
||||
}
|
||||
if (__wrnexusPeerResult && typeof __wrnexusPeerResult.then === "function") {
|
||||
return Promise.resolve(__wrnexusPeerResult).finally(() => { ${syncStateFromContext} });
|
||||
if (result && typeof result.then === "function") {
|
||||
return Promise.resolve(result).finally(__wrnexusRestore);
|
||||
}
|
||||
${syncStateFromContext}
|
||||
return __wrnexusPeerResult;
|
||||
};`;
|
||||
})
|
||||
.join("\n");
|
||||
__wrnexusRestore();
|
||||
return result;
|
||||
};
|
||||
${functionAliases.map((name) => `const ${name} = (...__wrnexusPeerArgs) => __wrnexusPeer(${JSON.stringify(name)}, __wrnexusPeerArgs);`).join("\n")}`;
|
||||
const copyBack = stateNames
|
||||
.map((name) => `if (!Object.is(${name}, __wrnexusInitialState[${JSON.stringify(name)}])) context.state.${name} = ${name};`)
|
||||
.join("\n");
|
||||
const commitBinding = stateNames.length
|
||||
? `const __wrnexusCommit = () => { ${copyBack} };
|
||||
${!parameterNames.has("commit") && !declaredLocals.has("commit") && !functionAliases.includes("commit") ? "const commit = __wrnexusCommit;" : ""}
|
||||
${!parameterNames.has("setTimeout") &&
|
||||
!declaredLocals.has("setTimeout") &&
|
||||
!functionAliases.includes("setTimeout")
|
||||
? `const setTimeout = (callback, delay, ...args) => globalThis.setTimeout(() => {
|
||||
try { return callback(...args); } finally { __wrnexusCommit(); }
|
||||
}, delay);`
|
||||
: ""}`
|
||||
: "";
|
||||
const body = (0, syntax_1.eraseFunctionTypes)(fn.body);
|
||||
const runtimeBindings = [
|
||||
!parameterNames.has("output") ? "const output = context.output;" : "",
|
||||
@@ -647,12 +657,13 @@ function functionEntry(ast, fn, availableFunctions) {
|
||||
${initialStateSnapshot}
|
||||
${stateAliases}
|
||||
${propAliases}
|
||||
${commitBinding}
|
||||
${peerAliases}
|
||||
${runtimeBindings}
|
||||
try {
|
||||
${body}
|
||||
} finally {
|
||||
${copyBack}
|
||||
${stateNames.length ? "__wrnexusCommit();" : ""}
|
||||
}
|
||||
}`;
|
||||
}
|
||||
@@ -663,22 +674,74 @@ function generateBrowserModule(ast) {
|
||||
const selectedImports = selectedBrowserImports(ast, functions);
|
||||
const imports = selectedImports.map((entry) => entry.code).join("\n");
|
||||
const importedBindings = [...new Set(selectedImports.flatMap((entry) => entry.bindings))];
|
||||
const sharedState = state.filter((name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name));
|
||||
const sharedProps = ast.props
|
||||
.map((entry) => entry.name)
|
||||
.filter((name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name) && !sharedState.includes(name));
|
||||
const callableAliases = functionNames.filter((name) => safeIdentifier(name) &&
|
||||
!RUNTIME_BINDINGS.has(name) &&
|
||||
!sharedState.includes(name) &&
|
||||
!sharedProps.includes(name));
|
||||
const sharedCommit = sharedState.map((name) => `context.state.${name} = ${name};`).join(" ");
|
||||
const sharedRestore = [
|
||||
...sharedState.map((name) => `${name} = context.state.${name};`),
|
||||
...sharedProps.map((name) => `${name} = context.props.${name};`),
|
||||
].join(" ");
|
||||
const hasAuthoredCommit = callableAliases.includes("commit");
|
||||
const hasAuthoredSetTimeout = callableAliases.includes("setTimeout");
|
||||
const implementations = functions
|
||||
.map((fn) => {
|
||||
const parameters = fn.parameters.map((parameter) => parameter.name).join(", ");
|
||||
return `${JSON.stringify(fn.name)}: ${fn.async ? "async " : ""}function(${parameters}) {
|
||||
try { ${(0, syntax_1.eraseFunctionTypes)(fn.body)} } finally { __wrnexusCommit(); }
|
||||
}`;
|
||||
})
|
||||
.join(",\n");
|
||||
return `// generated WRNexusJS browser module for ${ast.name}
|
||||
${imports}
|
||||
export const __wrnexusClientFunctions = {
|
||||
${functions.map((fn) => ` ${functionEntry(ast, fn, functionNames)}`).join(",\n")}
|
||||
${functions
|
||||
.map((fn) => ` ${JSON.stringify(fn.name)}: (context, ...args) => __wrnexusBindings(context)[${JSON.stringify(fn.name)}](...args)`)
|
||||
.join(",\n")}
|
||||
};
|
||||
export const __wrnexusClientState = ${JSON.stringify(state)};
|
||||
export const __wrnexusOutputs = ${JSON.stringify(ast.outputs)};
|
||||
export const __wrnexusImportedBindings = { ${importedBindings.join(", ")} };
|
||||
export function bindClientScope(context) {
|
||||
function __wrnexusCreateClientFunctions(context) {
|
||||
${sharedState.length ? `let { ${sharedState.join(", ")} } = context.state;` : ""}
|
||||
${sharedProps.length ? `let { ${sharedProps.join(", ")} } = context.props;` : ""}
|
||||
const output = context.output;
|
||||
const server = context.server;
|
||||
const props = context.props;
|
||||
const refs = context.refs;
|
||||
const __wrnexusCommit = () => { ${sharedCommit} };
|
||||
const __wrnexusRestore = () => { ${sharedRestore} };
|
||||
${!hasAuthoredCommit ? "const commit = __wrnexusCommit;" : ""}
|
||||
${!hasAuthoredSetTimeout
|
||||
? `const setTimeout = (callback, delay, ...args) => globalThis.setTimeout(() => {
|
||||
try { return callback(...args); } finally { __wrnexusCommit(); }
|
||||
}, delay);`
|
||||
: ""}
|
||||
const implementations = {
|
||||
${implementations}
|
||||
};
|
||||
${callableAliases.map((name) => `const ${name} = (...args) => implementations[${JSON.stringify(name)}](...args);`).join("\n ")}
|
||||
const functions = {};
|
||||
const scopedContext = { ...context, functions };
|
||||
for (const [name, handler] of Object.entries(__wrnexusClientFunctions)) {
|
||||
functions[name] = (...args) => handler(scopedContext, ...args);
|
||||
for (const name of Object.keys(implementations)) {
|
||||
functions[name] = (...args) => {
|
||||
__wrnexusRestore();
|
||||
return implementations[name](...args);
|
||||
};
|
||||
}
|
||||
return functions;
|
||||
}
|
||||
function __wrnexusBindings(context) {
|
||||
return context.__wrnexusBoundFunctions ||
|
||||
(context.__wrnexusBoundFunctions = __wrnexusCreateClientFunctions(context));
|
||||
}
|
||||
export function bindClientScope(context) {
|
||||
return __wrnexusBindings(context);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1282,13 +1345,12 @@ function renderNestedComponentInvocation(node, ctx) {
|
||||
? `\${__wireProp(${ctx.resolveExpr(wholeExpression)})}`
|
||||
: compileAttrValue(attr.value, ctx);
|
||||
const rendered = ` ${attr.name}="${compiledValue}"`;
|
||||
if (wholeExpression ||
|
||||
!attr.value.includes("{") ||
|
||||
!exprRefsState(attr.value, ctx.stateNames)) {
|
||||
if (!attr.value.includes("{") ||
|
||||
(!exprRefsState(attr.value, ctx.stateNames) && !exprRefsState(attr.value, ctx.propNames))) {
|
||||
return rendered;
|
||||
}
|
||||
const marker = attrEscape(JSON.stringify([attr.name, attr.value]));
|
||||
return rendered + ` data-wrn-bind-${bindIndex++}="${escLit(marker)}"`;
|
||||
return rendered + ` data-wrn-prop-bind-${bindIndex++}="${escLit(marker)}"`;
|
||||
})
|
||||
.join("");
|
||||
const loops = loopVarsOf(node);
|
||||
@@ -1392,7 +1454,12 @@ function __wrnexusEscapeHtml(value: unknown): string {
|
||||
return String(value).replace(/[&<>"']/g, (ch) => __wrnexusHtmlEscapes[ch] ?? ch);
|
||||
}
|
||||
|
||||
function __wrnexusEvalData(data: unknown, body: string, helpers = "", ctx: any): unknown {
|
||||
type __WrnexusContext = import("@wrnexus/core").Context & {
|
||||
__wrnexusCallApi?: (path: string, method: string) => Promise<unknown>;
|
||||
localStorage?: unknown;
|
||||
};
|
||||
|
||||
function __wrnexusEvalData(data: unknown, body: string, helpers = "", ctx: __WrnexusContext): unknown {
|
||||
const adapters = {
|
||||
cookies: ctx.cookies,
|
||||
session: ctx.session,
|
||||
@@ -1423,7 +1490,7 @@ function __wrnexusPropAttr(
|
||||
);
|
||||
}
|
||||
|
||||
async function __wrnexusCallApi(path: string, method: string, ctx: any): Promise<unknown> {
|
||||
async function __wrnexusCallApi(path: string, method: string, ctx: __WrnexusContext): Promise<unknown> {
|
||||
if (typeof ctx.__wrnexusCallApi === "function") {
|
||||
return await ctx.__wrnexusCallApi(path, method);
|
||||
}
|
||||
@@ -1438,7 +1505,7 @@ async function __wrnexusCallApi(path: string, method: string, ctx: any): Promise
|
||||
return type.includes("application/json") ? await res.json() : await res.text();
|
||||
}
|
||||
|
||||
async function __wrnexusRenderSsrBindings(html: string, ctx: any): Promise<string> {
|
||||
async function __wrnexusRenderSsrBindings(html: string, ctx: __WrnexusContext): Promise<string> {
|
||||
for (const binding of __wrnexusSsrBindings) {
|
||||
const data = await __wrnexusCallApi(binding.path, binding.method, ctx);
|
||||
const value = __wrnexusEvalData(data, binding.body, binding.helpers, ctx);
|
||||
@@ -1871,14 +1938,14 @@ function generate(ast) {
|
||||
out.push(ssrRuntimeSource());
|
||||
out.push(`const __wrnexusSsrBindings = ${JSON.stringify(ssrBindings, null, 2)};`);
|
||||
const decls = loopConsts.length > 0 ? loopConsts.join("\n") + "\n" : "";
|
||||
out.push(`export default async function ${ast.name}(ctx: any) {
|
||||
out.push(`export default async function ${ast.name}(ctx: __WrnexusContext) {
|
||||
${storeDeclarations}
|
||||
${serverLoadAliases}
|
||||
${decls}
|
||||
const __state: ${stateType} = ${dynamicStateInitializer};
|
||||
${ssrStateAliases}
|
||||
${ssrComputedAliases}
|
||||
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]]));
|
||||
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)]));
|
||||
const __scopeValue = Object.entries(__hydrationState)
|
||||
.map(([key, value]) => {
|
||||
let encoded: string;
|
||||
@@ -1904,13 +1971,13 @@ function generate(ast) {
|
||||
}`);
|
||||
}
|
||||
else {
|
||||
out.push(`export default ${storeBindings.length > 0 ? "async " : ""}function ${ast.name}(ctx: any) {
|
||||
out.push(`export default ${storeBindings.length > 0 ? "async " : ""}function ${ast.name}(ctx: import("@wrnexus/core").Context) {
|
||||
${storeDeclarations}
|
||||
${serverLoadAliases}
|
||||
const __state: ${stateType} = ${dynamicStateInitializer};
|
||||
${ssrStateAliases}
|
||||
${ssrComputedAliases}
|
||||
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]]));
|
||||
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)]));
|
||||
const __scopeValue = Object.entries(__hydrationState)
|
||||
.map(([key, value]) => {
|
||||
let encoded: string;
|
||||
@@ -1934,14 +2001,14 @@ function generate(ast) {
|
||||
}`);
|
||||
}
|
||||
if (staticShellBody !== undefined) {
|
||||
out.push(`export async function __wrnexusBuildStaticShell(ctx: any = {}) {
|
||||
out.push(`export async function __wrnexusBuildStaticShell(ctx: import("@wrnexus/core").Context = {} as import("@wrnexus/core").Context) {
|
||||
${storeDeclarations}
|
||||
${serverLoadAliases}
|
||||
${loopConsts.length > 0 ? loopConsts.join("\n") : ""}
|
||||
const __state: ${stateType} = ${dynamicStateInitializer};
|
||||
${ssrStateAliases}
|
||||
${ssrComputedAliases}
|
||||
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]]));
|
||||
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)]));
|
||||
const __scopeValue = Object.entries(__hydrationState)
|
||||
.map(([key, value]) => {
|
||||
let encoded: string;
|
||||
@@ -1991,7 +2058,7 @@ function generate(ast) {
|
||||
})
|
||||
.join("\n");
|
||||
const visible = exposed.filter((entry) => entry.name);
|
||||
return `export async function ${exportName}(ctx: any) {
|
||||
return `export async function ${exportName}(ctx: import("@wrnexus/core").Context) {
|
||||
${exposed
|
||||
.filter((entry) => !entry.name)
|
||||
.map((entry) => entry.body)
|
||||
@@ -2014,7 +2081,7 @@ ${visible.length
|
||||
out.push(`export async function ${action.name}(${action.args.join(", ")}) {${action.body}}`);
|
||||
continue;
|
||||
}
|
||||
out.push(`export async function ${action.name}(input: any, ctx: any) {
|
||||
out.push(`export async function ${action.name}(input: InferSchema<typeof ${action.schema}>, ctx: import("@wrnexus/core").Context) {
|
||||
const invalidate = (...tags: string[]) => {
|
||||
const bucket = (ctx.locals.__wrnexusInvalidatedTags ??= []);
|
||||
bucket.push(...tags.flat());
|
||||
@@ -2036,7 +2103,7 @@ ${ast.actions
|
||||
ast.apis.forEach((api, index) => {
|
||||
const name = `__wrnexusApi_${api.method}_${index}`;
|
||||
out.push(`// ${api.method} ${apiRoutePath(api.path)}
|
||||
const ${name} = async (ctx: any) => {${api.body}};`);
|
||||
const ${name} = async (ctx: import("@wrnexus/core").Context) => {${api.body}};`);
|
||||
});
|
||||
const entries = ast.apis.map((api, index) => ` ${JSON.stringify(`${api.method} ${apiRoutePath(api.path)}`)}: __wrnexusApi_${api.method}_${index},`);
|
||||
out.push(`export const __wrnexusApi = {\n${entries.join("\n")}\n};`);
|
||||
@@ -2052,7 +2119,7 @@ const ${name} = async (ctx: any) => {${api.body}};`);
|
||||
if (ast.realtimes.length > 0) {
|
||||
const handlers = ast.realtimes.flatMap((rt) => rt.handlers.map((h) => {
|
||||
const params = ["ws", ...h.args].join(", ");
|
||||
return ` ${h.event}(${params}: any) {${h.body}},`;
|
||||
return ` ${h.event}(${params}: Event) {${h.body}},`;
|
||||
}));
|
||||
out.push(`export const websocket = {\n${handlers.join("\n")}\n};`);
|
||||
}
|
||||
@@ -2178,7 +2245,9 @@ function exprRefsState(expr, stateNames) {
|
||||
return false;
|
||||
}
|
||||
function exprRefsComponentReactiveValue(expr, ctx) {
|
||||
return exprRefsState(expr, ctx.stateNames) || exprRefsState(expr, ctx.functionNames);
|
||||
return (exprRefsState(expr, ctx.stateNames) ||
|
||||
exprRefsState(expr, ctx.propNames) ||
|
||||
exprRefsState(expr, ctx.functionNames));
|
||||
}
|
||||
function viewHasEvents(nodes) {
|
||||
return nodes.some((node) => {
|
||||
@@ -2242,6 +2311,9 @@ function compileText(raw, ctx) {
|
||||
// list renderer fills it per item; it has no server-side value.
|
||||
out += escLit(`{${expr}}`);
|
||||
}
|
||||
else if (expr === "content") {
|
||||
out += `\${__wireRaw(${ctx.resolveExpr(expr)})}`;
|
||||
}
|
||||
else if (exprRefsComponentReactiveValue(expr, ctx)) {
|
||||
// State interpolation: bake the initial value AND keep it reactive via a
|
||||
// data-text span, so no-JS clients see the real value and hydration
|
||||
@@ -2251,9 +2323,6 @@ function compileText(raw, ctx) {
|
||||
`\${__wireHtml(${ctx.resolveExpr(expr)})}` +
|
||||
escLit(`</span>`);
|
||||
}
|
||||
else if (expr === "content") {
|
||||
out += `\${__wireRaw(${ctx.resolveExpr(expr)})}`;
|
||||
}
|
||||
else {
|
||||
out += `\${__wireHtml(${ctx.resolveExpr(expr)})}`;
|
||||
}
|
||||
@@ -2593,6 +2662,7 @@ function generateComponent(ast) {
|
||||
};
|
||||
const ctx = {
|
||||
stateNames,
|
||||
propNames: new Set(effectiveProps.map((entry) => entry.name)),
|
||||
functionNames: new Set(ast.runtimeFunctions.filter((fn) => fn.runtime !== "server").map((fn) => fn.name)),
|
||||
resolveExpr,
|
||||
eventNames: publicOutputNames(ast),
|
||||
@@ -2610,12 +2680,12 @@ function generateComponent(ast) {
|
||||
.join("");
|
||||
const styles = ast.styles.map((body) => body.trim()).filter(Boolean);
|
||||
const styleTag = escLit(localStyleTag(ast, styles));
|
||||
// A component needs a reactive scope only when it has state or event handlers.
|
||||
// Prop-driven text/attributes are baked server-side, so static components ship
|
||||
// no JavaScript at all.
|
||||
// Props remain server-rendered and also become signals so a parent can drive
|
||||
// a mounted child after hydration.
|
||||
const behavior = componentBehavior(ast);
|
||||
const needsScope = ast.runtime !== "server" &&
|
||||
(browserStates.length > 0 ||
|
||||
(effectiveProps.length > 0 ||
|
||||
browserStates.length > 0 ||
|
||||
ast.computed.length > 0 ||
|
||||
viewHasEvents(ast.view) ||
|
||||
behavior !== null);
|
||||
@@ -2632,7 +2702,7 @@ function generateComponent(ast) {
|
||||
if (prop.required) {
|
||||
decls.push(` if (__p[${JSON.stringify(prop.name)}] === undefined) throw new TypeError(${JSON.stringify(`${ast.name} requires prop '${prop.name}' (${prop.valueType ?? "unknown"})`)});`);
|
||||
}
|
||||
decls.push(` const ${nameRefs.get(prop.name)}: ${prop.valueType ?? "any"} = __coerce(__p[${JSON.stringify(prop.name)}], (${resolveExpr(prop.default)}), ${JSON.stringify((0, types_ts_1.runtimeTypeOf)(prop.valueType))});`);
|
||||
decls.push(` const ${nameRefs.get(prop.name)}: ${prop.valueType ?? "unknown"} = __coerce(__p[${JSON.stringify(prop.name)}], (${resolveExpr(prop.default)}), ${JSON.stringify((0, types_ts_1.runtimeTypeOf)(prop.valueType))}, ${JSON.stringify(prop.name)}) as ${prop.valueType ?? "unknown"};`);
|
||||
}
|
||||
if (!effectiveProps.some((prop) => prop.name === "attrs")) {
|
||||
decls.push(` const __attrs = __restProps(__p, new Set(${JSON.stringify(effectiveProps.map((prop) => prop.name))}));`);
|
||||
@@ -2697,7 +2767,7 @@ function generateComponent(ast) {
|
||||
.map((output) => ` ${JSON.stringify(output.name)}(${output.payload ? `${output.payload.name}${output.payload.optional ? "?" : ""}: ${output.payload.valueType}` : ""}): void;`)
|
||||
.join("\n")}\n}`);
|
||||
}
|
||||
out.push(`function __coerce(v: any, def: any, declared: string = "unknown"): any {
|
||||
out.push(`function __coerce(v: unknown, def: unknown, declared: string = "unknown", propName: string = "prop"): unknown {
|
||||
if (v === undefined || v === null) {
|
||||
return def;
|
||||
}
|
||||
@@ -2722,14 +2792,14 @@ function generateComponent(ast) {
|
||||
if (typeof v === "string") {
|
||||
try {
|
||||
const parsed = JSON.parse(v);
|
||||
return Array.isArray(parsed) ? parsed : def;
|
||||
if (!Array.isArray(parsed)) throw new TypeError("Expected an array prop '" + propName + "'");
|
||||
return parsed;
|
||||
} catch {
|
||||
if (declared === "array") throw new TypeError("Expected an array prop");
|
||||
return def;
|
||||
throw new TypeError("Expected an array prop '" + propName + "'");
|
||||
}
|
||||
}
|
||||
|
||||
return def;
|
||||
throw new TypeError("Expected an array prop '" + propName + "'");
|
||||
}
|
||||
|
||||
if (declared === "object" || (def !== null && typeof def === "object")) {
|
||||
@@ -2745,23 +2815,19 @@ function generateComponent(ast) {
|
||||
try {
|
||||
const parsed = JSON.parse(v);
|
||||
|
||||
return (
|
||||
parsed !== null &&
|
||||
typeof parsed === "object" &&
|
||||
!Array.isArray(parsed)
|
||||
)
|
||||
? parsed
|
||||
: def;
|
||||
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
||||
throw new TypeError("Expected an object prop '" + propName + "'");
|
||||
}
|
||||
return parsed;
|
||||
} catch {
|
||||
if (declared === "object") throw new TypeError("Expected an object prop");
|
||||
return def;
|
||||
throw new TypeError("Expected an object prop '" + propName + "'");
|
||||
}
|
||||
}
|
||||
|
||||
return def;
|
||||
throw new TypeError("Expected an object prop '" + propName + "'");
|
||||
}
|
||||
|
||||
if (declared === "bigint") return BigInt(v);
|
||||
if (declared === "bigint") return BigInt(v as string | number | bigint | boolean);
|
||||
if (declared === "function" && typeof v !== "function") {
|
||||
throw new TypeError("Expected a function prop");
|
||||
}
|
||||
@@ -2769,15 +2835,15 @@ function generateComponent(ast) {
|
||||
}
|
||||
|
||||
function __restProps(
|
||||
props: Record<string, any>,
|
||||
props: Record<string, unknown>,
|
||||
declared: Set<string>,
|
||||
): Record<string, any> {
|
||||
): Record<string, unknown> {
|
||||
return Object.fromEntries(
|
||||
Object.entries(props).filter(([name]) => !declared.has(name)),
|
||||
);
|
||||
}
|
||||
|
||||
function __wireHtml(v: any): string {
|
||||
function __wireHtml(v: unknown): string {
|
||||
return String(v == null ? "" : v).replace(
|
||||
/[&<>]/g,
|
||||
(c) =>
|
||||
@@ -2789,7 +2855,7 @@ function __wireHtml(v: any): string {
|
||||
);
|
||||
}
|
||||
|
||||
function __wireAttr(v: any): string {
|
||||
function __wireAttr(v: unknown): string {
|
||||
return String(v == null ? "" : v).replace(
|
||||
/[&<>"]/g,
|
||||
(c) =>
|
||||
@@ -2803,7 +2869,7 @@ function __wireAttr(v: any): string {
|
||||
);
|
||||
}
|
||||
|
||||
function __wireBooleanAttr(name: string, value: any): string {
|
||||
function __wireBooleanAttr(name: string, value: unknown): string {
|
||||
return value === true ||
|
||||
value === "true" ||
|
||||
value === "" ||
|
||||
@@ -2814,7 +2880,7 @@ function __wireBooleanAttr(name: string, value: any): string {
|
||||
: "";
|
||||
}
|
||||
|
||||
function __wireSpreadAttrs(value: any): string {
|
||||
function __wireSpreadAttrs(value: unknown): string {
|
||||
if (value === null || typeof value !== "object" || Array.isArray(value)) return "";
|
||||
|
||||
const booleanAttributes = new Set(${JSON.stringify([...HTML_BOOLEAN_ATTRIBUTES])});
|
||||
@@ -2828,10 +2894,12 @@ function __wireSpreadAttrs(value: any): string {
|
||||
lowerName === "style" ||
|
||||
lowerName === "slot" ||
|
||||
lowerName === "data-component" ||
|
||||
// Internal markers must not leak through a spread -- except the
|
||||
// parent's output handlers, whose whole job is to ride from the mount
|
||||
// onto the view root so the mounting scope can bind them there.
|
||||
(lowerName.startsWith("data-wrn") && !lowerName.startsWith("data-wrn-out-"))
|
||||
// Internal markers must not leak through a spread. Parent-owned output
|
||||
// and prop bindings ride from the mount onto the
|
||||
// rendered child root so the mounting scope can bind them there.
|
||||
(lowerName.startsWith("data-wrn") &&
|
||||
!lowerName.startsWith("data-wrn-out-") &&
|
||||
!lowerName.startsWith("data-wrn-prop-bind-"))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
@@ -2848,7 +2916,7 @@ function __wireSpreadAttrs(value: any): string {
|
||||
return attributes.join("");
|
||||
}
|
||||
|
||||
function __wireProp(v: any): string {
|
||||
function __wireProp(v: unknown): string {
|
||||
const value =
|
||||
v !== null && typeof v === "object"
|
||||
? JSON.stringify(v)
|
||||
@@ -2857,16 +2925,16 @@ function __wireProp(v: any): string {
|
||||
return __wireAttr(value);
|
||||
}
|
||||
|
||||
function __wireRaw(v: any): string {
|
||||
function __wireRaw(v: unknown): string {
|
||||
return String(v == null ? "" : v);
|
||||
}`);
|
||||
if (hasServerEach) {
|
||||
out.push(`function __wrnexusEncodeLoopLocals(value: Record<string, any>): string {
|
||||
out.push(`function __wrnexusEncodeLoopLocals(value: Record<string, unknown>): string {
|
||||
return __WrnexusBuffer.from(JSON.stringify(value), "utf8").toString("base64");
|
||||
}`);
|
||||
}
|
||||
if (needsScope) {
|
||||
out.push(`function __wrnexusSerializeScopeValue(value: any): string {
|
||||
out.push(`function __wrnexusSerializeScopeValue(value: unknown): string {
|
||||
if (value === undefined) {
|
||||
return "undefined";
|
||||
}
|
||||
@@ -2900,7 +2968,7 @@ function __wireRaw(v: any): string {
|
||||
}
|
||||
}
|
||||
|
||||
function __wrnexusScopeDecl(obj: Record<string, any>): string {
|
||||
function __wrnexusScopeDecl(obj: Record<string, unknown>): string {
|
||||
return Object.keys(obj)
|
||||
.map(
|
||||
(key) =>
|
||||
@@ -2918,7 +2986,7 @@ function __wireRaw(v: any): string {
|
||||
}`);
|
||||
}
|
||||
const serverFunctionSource = serverFunctions ? `${serverFunctions}\n` : "";
|
||||
out.push(`export function render(props: ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, any>"} = {} as ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, any>"}): string {\n` +
|
||||
out.push(`export function render(props: ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, unknown>"} = {} as ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, unknown>"}): string {\n` +
|
||||
` const __p = props || {};\n` +
|
||||
(decls.length > 0 ? decls.join("\n") + "\n" : "") +
|
||||
serverFunctionSource +
|
||||
@@ -5634,7 +5702,12 @@ function parse(source) {
|
||||
// props { name: Type = <default>; @event name = function }
|
||||
lx.next();
|
||||
expect("lbrace");
|
||||
while (lx.peek().type !== "rbrace") {
|
||||
while (true) {
|
||||
if (lx.startsWithBlockComment()) {
|
||||
throw new ParseError("Block comments are not allowed inside props {}; use // line comments instead", "WRN-PROPS-BLOCK-COMMENT");
|
||||
}
|
||||
if (lx.peek().type === "rbrace")
|
||||
break;
|
||||
const t = lx.peek();
|
||||
if (t.type === "eof")
|
||||
throw new ParseError("Unexpected end of input inside props");
|
||||
@@ -6013,6 +6086,9 @@ function parse(source) {
|
||||
}
|
||||
expect("rbrace");
|
||||
const declaredStates = new Set(states.map((state) => state.name));
|
||||
if (declaredStates.has("page")) {
|
||||
throw new ParseError("State name 'page' collides with the WRN 'page' keyword; choose another state name", "WRN-STATE-RESERVED-NAME");
|
||||
}
|
||||
for (const watcher of watches) {
|
||||
if (!declaredStates.has(watcher.state)) {
|
||||
throw new ParseError(`Cannot watch undeclared state '${watcher.state}'`);
|
||||
@@ -6543,6 +6619,11 @@ class Lexer {
|
||||
break;
|
||||
}
|
||||
}
|
||||
/** True when the next non-trivia characters open a block comment. */
|
||||
startsWithBlockComment() {
|
||||
this.skipTrivia();
|
||||
return this.src[this.pos] === "/" && this.src[this.pos + 1] === "*";
|
||||
}
|
||||
/** Read and consume the next structural token. */
|
||||
next() {
|
||||
this.skipTrivia();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// WRN editor extension source hash: 6165420f37af57e441086efeb6b25528f72e77a39abb2c76ca5bc8748dd90c2f
|
||||
// WRN editor extension source hash: 47188196a996fce0b8f1e2a4302e563767451d9b56df885abc60e9c925653b21
|
||||
// WRN editor extension generator hash: 456d1d614e44e5fb1f19b784176c09cf2ade9b64ef73a17934c2698150b62728
|
||||
"use strict";
|
||||
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
||||
@@ -23202,7 +23202,7 @@ ${(0, codegen_ts_1.generate)(ast)}`,
|
||||
const functions = ast.runtimeFunctions.filter((fn) => ["legacy", "client", "shared"].includes(fn.runtime));
|
||||
return functions.length > 0 || selectedBrowserImports(ast, functions).length > 0;
|
||||
}
|
||||
function functionEntry(ast, fn, availableFunctions) {
|
||||
function _functionEntry(ast, fn, availableFunctions) {
|
||||
const parameterNames = new Set(fn.parameters.map((parameter) => parameter.name));
|
||||
const declaredLocals = new Set;
|
||||
for (const match of fn.body.matchAll(/\b(?:var|let|const)\s+([A-Za-z_$][\w$]*)|\bfunction\s+([A-Za-z_$][\w$]*)/g)) {
|
||||
@@ -23220,30 +23220,33 @@ ${(0, codegen_ts_1.generate)(ast)}`,
|
||||
const propAliases = propNames.length ? `const { ${propNames.join(", ")} } = context.props;` : "";
|
||||
const syncStateToContext = stateNames.map((name) => `context.state.${name} = ${name};`).join(" ");
|
||||
const syncStateFromContext = stateNames.map((name) => `${name} = context.state.${name};`).join(" ");
|
||||
const peerAliases = functionAliases.map((name) => {
|
||||
const call = `context.functions[${JSON.stringify(name)}](...__wrnexusPeerArgs)`;
|
||||
if (!stateNames.length) {
|
||||
return `const ${name} = (...__wrnexusPeerArgs) => ${call};`;
|
||||
}
|
||||
return `const ${name} = (...__wrnexusPeerArgs) => {
|
||||
${syncStateToContext}
|
||||
let __wrnexusPeerResult;
|
||||
const peerAliases = !stateNames.length ? functionAliases.map((name) => `const ${name} = (...__wrnexusPeerArgs) => context.functions[${JSON.stringify(name)}](...__wrnexusPeerArgs);`).join(`
|
||||
`) : `const __wrnexusFlush = () => { ${syncStateToContext} };
|
||||
const __wrnexusRestore = () => { ${syncStateFromContext} };
|
||||
const __wrnexusPeer = (name, args) => {
|
||||
__wrnexusFlush();
|
||||
let result;
|
||||
try {
|
||||
__wrnexusPeerResult = ${call};
|
||||
} catch (__wrnexusPeerError) {
|
||||
${syncStateFromContext}
|
||||
throw __wrnexusPeerError;
|
||||
result = context.functions[name](...args);
|
||||
} catch (error) {
|
||||
__wrnexusRestore();
|
||||
throw error;
|
||||
}
|
||||
if (__wrnexusPeerResult && typeof __wrnexusPeerResult.then === "function") {
|
||||
return Promise.resolve(__wrnexusPeerResult).finally(() => { ${syncStateFromContext} });
|
||||
if (result && typeof result.then === "function") {
|
||||
return Promise.resolve(result).finally(__wrnexusRestore);
|
||||
}
|
||||
${syncStateFromContext}
|
||||
return __wrnexusPeerResult;
|
||||
};`;
|
||||
}).join(`
|
||||
`);
|
||||
__wrnexusRestore();
|
||||
return result;
|
||||
};
|
||||
${functionAliases.map((name) => `const ${name} = (...__wrnexusPeerArgs) => __wrnexusPeer(${JSON.stringify(name)}, __wrnexusPeerArgs);`).join(`
|
||||
`)}`;
|
||||
const copyBack = stateNames.map((name) => `if (!Object.is(${name}, __wrnexusInitialState[${JSON.stringify(name)}])) context.state.${name} = ${name};`).join(`
|
||||
`);
|
||||
const commitBinding = stateNames.length ? `const __wrnexusCommit = () => { ${copyBack} };
|
||||
${!parameterNames.has("commit") && !declaredLocals.has("commit") && !functionAliases.includes("commit") ? "const commit = __wrnexusCommit;" : ""}
|
||||
${!parameterNames.has("setTimeout") && !declaredLocals.has("setTimeout") && !functionAliases.includes("setTimeout") ? `const setTimeout = (callback, delay, ...args) => globalThis.setTimeout(() => {
|
||||
try { return callback(...args); } finally { __wrnexusCommit(); }
|
||||
}, delay);` : ""}` : "";
|
||||
const body = (0, syntax_1.eraseFunctionTypes)(fn.body);
|
||||
const runtimeBindings = [
|
||||
!parameterNames.has("output") ? "const output = context.output;" : "",
|
||||
@@ -23256,12 +23259,13 @@ ${(0, codegen_ts_1.generate)(ast)}`,
|
||||
${initialStateSnapshot}
|
||||
${stateAliases}
|
||||
${propAliases}
|
||||
${commitBinding}
|
||||
${peerAliases}
|
||||
${runtimeBindings}
|
||||
try {
|
||||
${body}
|
||||
} finally {
|
||||
${copyBack}
|
||||
${stateNames.length ? "__wrnexusCommit();" : ""}
|
||||
}
|
||||
}`;
|
||||
}
|
||||
@@ -23273,23 +23277,66 @@ ${(0, codegen_ts_1.generate)(ast)}`,
|
||||
const imports = selectedImports.map((entry) => entry.code).join(`
|
||||
`);
|
||||
const importedBindings = [...new Set(selectedImports.flatMap((entry) => entry.bindings))];
|
||||
const sharedState = state.filter((name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name));
|
||||
const sharedProps = ast.props.map((entry) => entry.name).filter((name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name) && !sharedState.includes(name));
|
||||
const callableAliases = functionNames.filter((name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name) && !sharedState.includes(name) && !sharedProps.includes(name));
|
||||
const sharedCommit = sharedState.map((name) => `context.state.${name} = ${name};`).join(" ");
|
||||
const sharedRestore = [
|
||||
...sharedState.map((name) => `${name} = context.state.${name};`),
|
||||
...sharedProps.map((name) => `${name} = context.props.${name};`)
|
||||
].join(" ");
|
||||
const hasAuthoredCommit = callableAliases.includes("commit");
|
||||
const hasAuthoredSetTimeout = callableAliases.includes("setTimeout");
|
||||
const implementations = functions.map((fn) => {
|
||||
const parameters = fn.parameters.map((parameter) => parameter.name).join(", ");
|
||||
return `${JSON.stringify(fn.name)}: ${fn.async ? "async " : ""}function(${parameters}) {
|
||||
try { ${(0, syntax_1.eraseFunctionTypes)(fn.body)} } finally { __wrnexusCommit(); }
|
||||
}`;
|
||||
}).join(`,
|
||||
`);
|
||||
return `// generated WRNexusJS browser module for ${ast.name}
|
||||
${imports}
|
||||
export const __wrnexusClientFunctions = {
|
||||
${functions.map((fn) => ` ${functionEntry(ast, fn, functionNames)}`).join(`,
|
||||
${functions.map((fn) => ` ${JSON.stringify(fn.name)}: (context, ...args) => __wrnexusBindings(context)[${JSON.stringify(fn.name)}](...args)`).join(`,
|
||||
`)}
|
||||
};
|
||||
export const __wrnexusClientState = ${JSON.stringify(state)};
|
||||
export const __wrnexusOutputs = ${JSON.stringify(ast.outputs)};
|
||||
export const __wrnexusImportedBindings = { ${importedBindings.join(", ")} };
|
||||
export function bindClientScope(context) {
|
||||
function __wrnexusCreateClientFunctions(context) {
|
||||
${sharedState.length ? `let { ${sharedState.join(", ")} } = context.state;` : ""}
|
||||
${sharedProps.length ? `let { ${sharedProps.join(", ")} } = context.props;` : ""}
|
||||
const output = context.output;
|
||||
const server = context.server;
|
||||
const props = context.props;
|
||||
const refs = context.refs;
|
||||
const __wrnexusCommit = () => { ${sharedCommit} };
|
||||
const __wrnexusRestore = () => { ${sharedRestore} };
|
||||
${!hasAuthoredCommit ? "const commit = __wrnexusCommit;" : ""}
|
||||
${!hasAuthoredSetTimeout ? `const setTimeout = (callback, delay, ...args) => globalThis.setTimeout(() => {
|
||||
try { return callback(...args); } finally { __wrnexusCommit(); }
|
||||
}, delay);` : ""}
|
||||
const implementations = {
|
||||
${implementations}
|
||||
};
|
||||
${callableAliases.map((name) => `const ${name} = (...args) => implementations[${JSON.stringify(name)}](...args);`).join(`
|
||||
`)}
|
||||
const functions = {};
|
||||
const scopedContext = { ...context, functions };
|
||||
for (const [name, handler] of Object.entries(__wrnexusClientFunctions)) {
|
||||
functions[name] = (...args) => handler(scopedContext, ...args);
|
||||
for (const name of Object.keys(implementations)) {
|
||||
functions[name] = (...args) => {
|
||||
__wrnexusRestore();
|
||||
return implementations[name](...args);
|
||||
};
|
||||
}
|
||||
return functions;
|
||||
}
|
||||
function __wrnexusBindings(context) {
|
||||
return context.__wrnexusBoundFunctions ||
|
||||
(context.__wrnexusBoundFunctions = __wrnexusCreateClientFunctions(context));
|
||||
}
|
||||
export function bindClientScope(context) {
|
||||
return __wrnexusBindings(context);
|
||||
}
|
||||
`;
|
||||
}
|
||||
},
|
||||
@@ -23719,11 +23766,11 @@ export function bindClientScope(context) {
|
||||
const wholeExpression = wholeAttributeExpression(attr.value);
|
||||
const compiledValue = wholeExpression ? `\${__wireProp(${ctx.resolveExpr(wholeExpression)})}` : compileAttrValue(attr.value, ctx);
|
||||
const rendered = ` ${attr.name}="${compiledValue}"`;
|
||||
if (wholeExpression || !attr.value.includes("{") || !exprRefsState(attr.value, ctx.stateNames)) {
|
||||
if (!attr.value.includes("{") || !exprRefsState(attr.value, ctx.stateNames) && !exprRefsState(attr.value, ctx.propNames)) {
|
||||
return rendered;
|
||||
}
|
||||
const marker = attrEscape(JSON.stringify([attr.name, attr.value]));
|
||||
return rendered + ` data-wrn-bind-${bindIndex++}="${escLit(marker)}"`;
|
||||
return rendered + ` data-wrn-prop-bind-${bindIndex++}="${escLit(marker)}"`;
|
||||
}).join("");
|
||||
const loops = loopVarsOf(node);
|
||||
const childCtx = loops.length > 0 ? {
|
||||
@@ -23816,7 +23863,12 @@ function __wrnexusEscapeHtml(value: unknown): string {
|
||||
return String(value).replace(/[&<>"']/g, (ch) => __wrnexusHtmlEscapes[ch] ?? ch);
|
||||
}
|
||||
|
||||
function __wrnexusEvalData(data: unknown, body: string, helpers = "", ctx: any): unknown {
|
||||
type __WrnexusContext = import("@wrnexus/core").Context & {
|
||||
__wrnexusCallApi?: (path: string, method: string) => Promise<unknown>;
|
||||
localStorage?: unknown;
|
||||
};
|
||||
|
||||
function __wrnexusEvalData(data: unknown, body: string, helpers = "", ctx: __WrnexusContext): unknown {
|
||||
const adapters = {
|
||||
cookies: ctx.cookies,
|
||||
session: ctx.session,
|
||||
@@ -23847,7 +23899,7 @@ function __wrnexusPropAttr(
|
||||
);
|
||||
}
|
||||
|
||||
async function __wrnexusCallApi(path: string, method: string, ctx: any): Promise<unknown> {
|
||||
async function __wrnexusCallApi(path: string, method: string, ctx: __WrnexusContext): Promise<unknown> {
|
||||
if (typeof ctx.__wrnexusCallApi === "function") {
|
||||
return await ctx.__wrnexusCallApi(path, method);
|
||||
}
|
||||
@@ -23862,7 +23914,7 @@ async function __wrnexusCallApi(path: string, method: string, ctx: any): Promise
|
||||
return type.includes("application/json") ? await res.json() : await res.text();
|
||||
}
|
||||
|
||||
async function __wrnexusRenderSsrBindings(html: string, ctx: any): Promise<string> {
|
||||
async function __wrnexusRenderSsrBindings(html: string, ctx: __WrnexusContext): Promise<string> {
|
||||
for (const binding of __wrnexusSsrBindings) {
|
||||
const data = await __wrnexusCallApi(binding.path, binding.method, ctx);
|
||||
const value = __wrnexusEvalData(data, binding.body, binding.helpers, ctx);
|
||||
@@ -24253,14 +24305,14 @@ ${helpers}`);
|
||||
const decls = loopConsts.length > 0 ? loopConsts.join(`
|
||||
`) + `
|
||||
` : "";
|
||||
out.push(`export default async function ${ast.name}(ctx: any) {
|
||||
out.push(`export default async function ${ast.name}(ctx: __WrnexusContext) {
|
||||
${storeDeclarations}
|
||||
${serverLoadAliases}
|
||||
${decls}
|
||||
const __state: ${stateType} = ${dynamicStateInitializer};
|
||||
${ssrStateAliases}
|
||||
${ssrComputedAliases}
|
||||
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]]));
|
||||
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)]));
|
||||
const __scopeValue = Object.entries(__hydrationState)
|
||||
.map(([key, value]) => {
|
||||
let encoded: string;
|
||||
@@ -24285,13 +24337,13 @@ ${helpers}`);
|
||||
return await __wrnexusRenderSsrBindings(html, ctx);
|
||||
}`);
|
||||
} else {
|
||||
out.push(`export default ${storeBindings.length > 0 ? "async " : ""}function ${ast.name}(ctx: any) {
|
||||
out.push(`export default ${storeBindings.length > 0 ? "async " : ""}function ${ast.name}(ctx: import("@wrnexus/core").Context) {
|
||||
${storeDeclarations}
|
||||
${serverLoadAliases}
|
||||
const __state: ${stateType} = ${dynamicStateInitializer};
|
||||
${ssrStateAliases}
|
||||
${ssrComputedAliases}
|
||||
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]]));
|
||||
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)]));
|
||||
const __scopeValue = Object.entries(__hydrationState)
|
||||
.map(([key, value]) => {
|
||||
let encoded: string;
|
||||
@@ -24315,7 +24367,7 @@ ${helpers}`);
|
||||
}`);
|
||||
}
|
||||
if (staticShellBody !== undefined) {
|
||||
out.push(`export async function __wrnexusBuildStaticShell(ctx: any = {}) {
|
||||
out.push(`export async function __wrnexusBuildStaticShell(ctx: import("@wrnexus/core").Context = {} as import("@wrnexus/core").Context) {
|
||||
${storeDeclarations}
|
||||
${serverLoadAliases}
|
||||
${loopConsts.length > 0 ? loopConsts.join(`
|
||||
@@ -24323,7 +24375,7 @@ ${helpers}`);
|
||||
const __state: ${stateType} = ${dynamicStateInitializer};
|
||||
${ssrStateAliases}
|
||||
${ssrComputedAliases}
|
||||
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]]));
|
||||
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)]));
|
||||
const __scopeValue = Object.entries(__hydrationState)
|
||||
.map(([key, value]) => {
|
||||
let encoded: string;
|
||||
@@ -24370,7 +24422,7 @@ ${helpers}`);
|
||||
}).join(`
|
||||
`);
|
||||
const visible = exposed.filter((entry) => entry.name);
|
||||
return `export async function ${exportName}(ctx: any) {
|
||||
return `export async function ${exportName}(ctx: import("@wrnexus/core").Context) {
|
||||
${exposed.filter((entry) => !entry.name).map((entry) => entry.body).join(`
|
||||
`)}
|
||||
${declarations}
|
||||
@@ -24389,7 +24441,7 @@ ${visible.length ? ` const __values = await Promise.all([${visible.map((entry)
|
||||
out.push(`export async function ${action.name}(${action.args.join(", ")}) {${action.body}}`);
|
||||
continue;
|
||||
}
|
||||
out.push(`export async function ${action.name}(input: any, ctx: any) {
|
||||
out.push(`export async function ${action.name}(input: InferSchema<typeof ${action.schema}>, ctx: import("@wrnexus/core").Context) {
|
||||
const invalidate = (...tags: string[]) => {
|
||||
const bucket = (ctx.locals.__wrnexusInvalidatedTags ??= []);
|
||||
bucket.push(...tags.flat());
|
||||
@@ -24407,7 +24459,7 @@ ${ast.actions.map((action) => ` ${action.name}: createActionClient<${action.sch
|
||||
ast.apis.forEach((api, index) => {
|
||||
const name = `__wrnexusApi_${api.method}_${index}`;
|
||||
out.push(`// ${api.method} ${apiRoutePath(api.path)}
|
||||
const ${name} = async (ctx: any) => {${api.body}};`);
|
||||
const ${name} = async (ctx: import("@wrnexus/core").Context) => {${api.body}};`);
|
||||
});
|
||||
const entries = ast.apis.map((api, index) => ` ${JSON.stringify(`${api.method} ${apiRoutePath(api.path)}`)}: __wrnexusApi_${api.method}_${index},`);
|
||||
out.push(`export const __wrnexusApi = {
|
||||
@@ -24425,7 +24477,7 @@ ${entries.join(`
|
||||
if (ast.realtimes.length > 0) {
|
||||
const handlers = ast.realtimes.flatMap((rt) => rt.handlers.map((h) => {
|
||||
const params = ["ws", ...h.args].join(", ");
|
||||
return ` ${h.event}(${params}: any) {${h.body}},`;
|
||||
return ` ${h.event}(${params}: Event) {${h.body}},`;
|
||||
}));
|
||||
out.push(`export const websocket = {
|
||||
${handlers.join(`
|
||||
@@ -24547,7 +24599,7 @@ ${handlers.join(`
|
||||
return false;
|
||||
}
|
||||
function exprRefsComponentReactiveValue(expr, ctx) {
|
||||
return exprRefsState(expr, ctx.stateNames) || exprRefsState(expr, ctx.functionNames);
|
||||
return exprRefsState(expr, ctx.stateNames) || exprRefsState(expr, ctx.propNames) || exprRefsState(expr, ctx.functionNames);
|
||||
}
|
||||
function viewHasEvents(nodes) {
|
||||
return nodes.some((node) => {
|
||||
@@ -24599,10 +24651,10 @@ ${handlers.join(`
|
||||
out += escLit(`<span data-t="${attrEscape(expr.slice(2).trim())}"></span>`);
|
||||
} else if (ctx.loopVars && exprRefsState(expr, ctx.loopVars)) {
|
||||
out += escLit(`{${expr}}`);
|
||||
} else if (exprRefsComponentReactiveValue(expr, ctx)) {
|
||||
out += escLit(`<span data-text="${attrEscape(expr)}">`) + `\${__wireHtml(${ctx.resolveExpr(expr)})}` + escLit(`</span>`);
|
||||
} else if (expr === "content") {
|
||||
out += `\${__wireRaw(${ctx.resolveExpr(expr)})}`;
|
||||
} else if (exprRefsComponentReactiveValue(expr, ctx)) {
|
||||
out += escLit(`<span data-text="${attrEscape(expr)}">`) + `\${__wireHtml(${ctx.resolveExpr(expr)})}` + escLit(`</span>`);
|
||||
} else {
|
||||
out += `\${__wireHtml(${ctx.resolveExpr(expr)})}`;
|
||||
}
|
||||
@@ -24855,6 +24907,7 @@ ${handlers.join(`
|
||||
};
|
||||
const ctx = {
|
||||
stateNames,
|
||||
propNames: new Set(effectiveProps.map((entry) => entry.name)),
|
||||
functionNames: new Set(ast.runtimeFunctions.filter((fn) => fn.runtime !== "server").map((fn) => fn.name)),
|
||||
resolveExpr,
|
||||
eventNames: publicOutputNames(ast)
|
||||
@@ -24867,7 +24920,7 @@ ${handlers.join(`
|
||||
const styles = ast.styles.map((body) => body.trim()).filter(Boolean);
|
||||
const styleTag = escLit(localStyleTag(ast, styles));
|
||||
const behavior = componentBehavior(ast);
|
||||
const needsScope = ast.runtime !== "server" && (browserStates.length > 0 || ast.computed.length > 0 || viewHasEvents(ast.view) || behavior !== null);
|
||||
const needsScope = ast.runtime !== "server" && (effectiveProps.length > 0 || browserStates.length > 0 || ast.computed.length > 0 || viewHasEvents(ast.view) || behavior !== null);
|
||||
if (hasServerEach || needsScope) {
|
||||
out.push(`import { Buffer as __WrnexusBuffer } from "node:buffer";`);
|
||||
}
|
||||
@@ -24881,7 +24934,7 @@ ${handlers.join(`
|
||||
if (prop.required) {
|
||||
decls.push(` if (__p[${JSON.stringify(prop.name)}] === undefined) throw new TypeError(${JSON.stringify(`${ast.name} requires prop '${prop.name}' (${prop.valueType ?? "unknown"})`)});`);
|
||||
}
|
||||
decls.push(` const ${nameRefs.get(prop.name)}: ${prop.valueType ?? "any"} = __coerce(__p[${JSON.stringify(prop.name)}], (${resolveExpr(prop.default)}), ${JSON.stringify((0, types_ts_1.runtimeTypeOf)(prop.valueType))});`);
|
||||
decls.push(` const ${nameRefs.get(prop.name)}: ${prop.valueType ?? "unknown"} = __coerce(__p[${JSON.stringify(prop.name)}], (${resolveExpr(prop.default)}), ${JSON.stringify((0, types_ts_1.runtimeTypeOf)(prop.valueType))}, ${JSON.stringify(prop.name)}) as ${prop.valueType ?? "unknown"};`);
|
||||
}
|
||||
if (!effectiveProps.some((prop) => prop.name === "attrs")) {
|
||||
decls.push(` const __attrs = __restProps(__p, new Set(${JSON.stringify(effectiveProps.map((prop) => prop.name))}));`);
|
||||
@@ -24941,7 +24994,7 @@ ${ast.outputs.map((output) => ` ${JSON.stringify(output.name)}(${output.payload
|
||||
`)}
|
||||
}`);
|
||||
}
|
||||
out.push(`function __coerce(v: any, def: any, declared: string = "unknown"): any {
|
||||
out.push(`function __coerce(v: unknown, def: unknown, declared: string = "unknown", propName: string = "prop"): unknown {
|
||||
if (v === undefined || v === null) {
|
||||
return def;
|
||||
}
|
||||
@@ -24966,14 +25019,14 @@ ${ast.outputs.map((output) => ` ${JSON.stringify(output.name)}(${output.payload
|
||||
if (typeof v === "string") {
|
||||
try {
|
||||
const parsed = JSON.parse(v);
|
||||
return Array.isArray(parsed) ? parsed : def;
|
||||
if (!Array.isArray(parsed)) throw new TypeError("Expected an array prop '" + propName + "'");
|
||||
return parsed;
|
||||
} catch {
|
||||
if (declared === "array") throw new TypeError("Expected an array prop");
|
||||
return def;
|
||||
throw new TypeError("Expected an array prop '" + propName + "'");
|
||||
}
|
||||
}
|
||||
|
||||
return def;
|
||||
throw new TypeError("Expected an array prop '" + propName + "'");
|
||||
}
|
||||
|
||||
if (declared === "object" || (def !== null && typeof def === "object")) {
|
||||
@@ -24989,23 +25042,19 @@ ${ast.outputs.map((output) => ` ${JSON.stringify(output.name)}(${output.payload
|
||||
try {
|
||||
const parsed = JSON.parse(v);
|
||||
|
||||
return (
|
||||
parsed !== null &&
|
||||
typeof parsed === "object" &&
|
||||
!Array.isArray(parsed)
|
||||
)
|
||||
? parsed
|
||||
: def;
|
||||
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
||||
throw new TypeError("Expected an object prop '" + propName + "'");
|
||||
}
|
||||
return parsed;
|
||||
} catch {
|
||||
if (declared === "object") throw new TypeError("Expected an object prop");
|
||||
return def;
|
||||
throw new TypeError("Expected an object prop '" + propName + "'");
|
||||
}
|
||||
}
|
||||
|
||||
return def;
|
||||
throw new TypeError("Expected an object prop '" + propName + "'");
|
||||
}
|
||||
|
||||
if (declared === "bigint") return BigInt(v);
|
||||
if (declared === "bigint") return BigInt(v as string | number | bigint | boolean);
|
||||
if (declared === "function" && typeof v !== "function") {
|
||||
throw new TypeError("Expected a function prop");
|
||||
}
|
||||
@@ -25013,15 +25062,15 @@ ${ast.outputs.map((output) => ` ${JSON.stringify(output.name)}(${output.payload
|
||||
}
|
||||
|
||||
function __restProps(
|
||||
props: Record<string, any>,
|
||||
props: Record<string, unknown>,
|
||||
declared: Set<string>,
|
||||
): Record<string, any> {
|
||||
): Record<string, unknown> {
|
||||
return Object.fromEntries(
|
||||
Object.entries(props).filter(([name]) => !declared.has(name)),
|
||||
);
|
||||
}
|
||||
|
||||
function __wireHtml(v: any): string {
|
||||
function __wireHtml(v: unknown): string {
|
||||
return String(v == null ? "" : v).replace(
|
||||
/[&<>]/g,
|
||||
(c) =>
|
||||
@@ -25033,7 +25082,7 @@ function __wireHtml(v: any): string {
|
||||
);
|
||||
}
|
||||
|
||||
function __wireAttr(v: any): string {
|
||||
function __wireAttr(v: unknown): string {
|
||||
return String(v == null ? "" : v).replace(
|
||||
/[&<>"]/g,
|
||||
(c) =>
|
||||
@@ -25047,7 +25096,7 @@ function __wireAttr(v: any): string {
|
||||
);
|
||||
}
|
||||
|
||||
function __wireBooleanAttr(name: string, value: any): string {
|
||||
function __wireBooleanAttr(name: string, value: unknown): string {
|
||||
return value === true ||
|
||||
value === "true" ||
|
||||
value === "" ||
|
||||
@@ -25058,7 +25107,7 @@ function __wireBooleanAttr(name: string, value: any): string {
|
||||
: "";
|
||||
}
|
||||
|
||||
function __wireSpreadAttrs(value: any): string {
|
||||
function __wireSpreadAttrs(value: unknown): string {
|
||||
if (value === null || typeof value !== "object" || Array.isArray(value)) return "";
|
||||
|
||||
const booleanAttributes = new Set(${JSON.stringify([...HTML_BOOLEAN_ATTRIBUTES])});
|
||||
@@ -25072,10 +25121,12 @@ function __wireSpreadAttrs(value: any): string {
|
||||
lowerName === "style" ||
|
||||
lowerName === "slot" ||
|
||||
lowerName === "data-component" ||
|
||||
// Internal markers must not leak through a spread -- except the
|
||||
// parent's output handlers, whose whole job is to ride from the mount
|
||||
// onto the view root so the mounting scope can bind them there.
|
||||
(lowerName.startsWith("data-wrn") && !lowerName.startsWith("data-wrn-out-"))
|
||||
// Internal markers must not leak through a spread. Parent-owned output
|
||||
// and prop bindings ride from the mount onto the
|
||||
// rendered child root so the mounting scope can bind them there.
|
||||
(lowerName.startsWith("data-wrn") &&
|
||||
!lowerName.startsWith("data-wrn-out-") &&
|
||||
!lowerName.startsWith("data-wrn-prop-bind-"))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
@@ -25092,7 +25143,7 @@ function __wireSpreadAttrs(value: any): string {
|
||||
return attributes.join("");
|
||||
}
|
||||
|
||||
function __wireProp(v: any): string {
|
||||
function __wireProp(v: unknown): string {
|
||||
const value =
|
||||
v !== null && typeof v === "object"
|
||||
? JSON.stringify(v)
|
||||
@@ -25101,16 +25152,16 @@ function __wireProp(v: any): string {
|
||||
return __wireAttr(value);
|
||||
}
|
||||
|
||||
function __wireRaw(v: any): string {
|
||||
function __wireRaw(v: unknown): string {
|
||||
return String(v == null ? "" : v);
|
||||
}`);
|
||||
if (hasServerEach) {
|
||||
out.push(`function __wrnexusEncodeLoopLocals(value: Record<string, any>): string {
|
||||
out.push(`function __wrnexusEncodeLoopLocals(value: Record<string, unknown>): string {
|
||||
return __WrnexusBuffer.from(JSON.stringify(value), "utf8").toString("base64");
|
||||
}`);
|
||||
}
|
||||
if (needsScope) {
|
||||
out.push(`function __wrnexusSerializeScopeValue(value: any): string {
|
||||
out.push(`function __wrnexusSerializeScopeValue(value: unknown): string {
|
||||
if (value === undefined) {
|
||||
return "undefined";
|
||||
}
|
||||
@@ -25144,7 +25195,7 @@ function __wireRaw(v: any): string {
|
||||
}
|
||||
}
|
||||
|
||||
function __wrnexusScopeDecl(obj: Record<string, any>): string {
|
||||
function __wrnexusScopeDecl(obj: Record<string, unknown>): string {
|
||||
return Object.keys(obj)
|
||||
.map(
|
||||
(key) =>
|
||||
@@ -25163,7 +25214,7 @@ function __wireRaw(v: any): string {
|
||||
}
|
||||
const serverFunctionSource = serverFunctions ? `${serverFunctions}
|
||||
` : "";
|
||||
out.push(`export function render(props: ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, any>"} = {} as ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, any>"}): string {
|
||||
out.push(`export function render(props: ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, unknown>"} = {} as ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, unknown>"}): string {
|
||||
` + ` const __p = props || {};
|
||||
` + (decls.length > 0 ? decls.join(`
|
||||
`) + `
|
||||
@@ -27916,7 +27967,12 @@ ${serverFunctions}
|
||||
case "props": {
|
||||
lx.next();
|
||||
expect("lbrace");
|
||||
while (lx.peek().type !== "rbrace") {
|
||||
while (true) {
|
||||
if (lx.startsWithBlockComment()) {
|
||||
throw new ParseError("Block comments are not allowed inside props {}; use // line comments instead", "WRN-PROPS-BLOCK-COMMENT");
|
||||
}
|
||||
if (lx.peek().type === "rbrace")
|
||||
break;
|
||||
const t = lx.peek();
|
||||
if (t.type === "eof")
|
||||
throw new ParseError("Unexpected end of input inside props");
|
||||
@@ -28275,6 +28331,9 @@ ${serverFunctions}
|
||||
}
|
||||
expect("rbrace");
|
||||
const declaredStates = new Set(states.map((state) => state.name));
|
||||
if (declaredStates.has("page")) {
|
||||
throw new ParseError("State name 'page' collides with the WRN 'page' keyword; choose another state name", "WRN-STATE-RESERVED-NAME");
|
||||
}
|
||||
for (const watcher of watches) {
|
||||
if (!declaredStates.has(watcher.state)) {
|
||||
throw new ParseError(`Cannot watch undeclared state '${watcher.state}'`);
|
||||
@@ -28748,6 +28807,10 @@ ${serverFunctions}
|
||||
break;
|
||||
}
|
||||
}
|
||||
startsWithBlockComment() {
|
||||
this.skipTrivia();
|
||||
return this.src[this.pos] === "/" && this.src[this.pos + 1] === "*";
|
||||
}
|
||||
next() {
|
||||
this.skipTrivia();
|
||||
const { src } = this;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
// WRN editor language server source hash: 65a5536efbc7c9fee15040559bb48afe78c4686fc5e7341ffc941cc7113c8556
|
||||
// WRN editor language server source hash: 4c7e59ebeb512e23d95f4169dd80da2b951ae39c31d31919994368b8e90f5cec
|
||||
// WRN editor language server generator hash: f593a44aaf05495b789ce7a3086bee1eebb951b884d41c0e017bbcfe5f547e72
|
||||
// @bun @bun-cjs
|
||||
(function(exports, require, module, __filename, __dirname) {var __create = Object.create;
|
||||
@@ -168786,6 +168786,10 @@ class Lexer {
|
||||
break;
|
||||
}
|
||||
}
|
||||
startsWithBlockComment() {
|
||||
this.skipTrivia();
|
||||
return this.src[this.pos] === "/" && this.src[this.pos + 1] === "*";
|
||||
}
|
||||
next() {
|
||||
this.skipTrivia();
|
||||
const { src } = this;
|
||||
@@ -170521,7 +170525,12 @@ function parse(source) {
|
||||
case "props": {
|
||||
lx.next();
|
||||
expect("lbrace");
|
||||
while (lx.peek().type !== "rbrace") {
|
||||
while (true) {
|
||||
if (lx.startsWithBlockComment()) {
|
||||
throw new ParseError("Block comments are not allowed inside props {}; use // line comments instead", "WRN-PROPS-BLOCK-COMMENT");
|
||||
}
|
||||
if (lx.peek().type === "rbrace")
|
||||
break;
|
||||
const t = lx.peek();
|
||||
if (t.type === "eof")
|
||||
throw new ParseError("Unexpected end of input inside props");
|
||||
@@ -170880,6 +170889,9 @@ function parse(source) {
|
||||
}
|
||||
expect("rbrace");
|
||||
const declaredStates = new Set(states.map((state) => state.name));
|
||||
if (declaredStates.has("page")) {
|
||||
throw new ParseError("State name 'page' collides with the WRN 'page' keyword; choose another state name", "WRN-STATE-RESERVED-NAME");
|
||||
}
|
||||
for (const watcher of watches) {
|
||||
if (!declaredStates.has(watcher.state)) {
|
||||
throw new ParseError(`Cannot watch undeclared state '${watcher.state}'`);
|
||||
|
||||
@@ -13,6 +13,7 @@ export default tseslint.config(
|
||||
"**/dist/**",
|
||||
".publish/**",
|
||||
"**/.wrnexus/**",
|
||||
"**/.wrnexus-*/**",
|
||||
"coverage/**",
|
||||
"bun.lock",
|
||||
"bun.lockb",
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.wrnexus/
|
||||
.wrnexus-*/
|
||||
**/.wrnexus-*/
|
||||
.wirefw/
|
||||
**/.wirefw/
|
||||
**/*.gen.ts
|
||||
|
||||
@@ -60,10 +60,27 @@ describe("full app", () => {
|
||||
});
|
||||
afterAll(() => app?.close());
|
||||
|
||||
test("home page responds with HTML", async () => {
|
||||
test("home page server-renders translated page and layout text", async () => {
|
||||
const res = await app.fetch("/");
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get("content-type")).toContain("text/html");
|
||||
const html = await res.text();
|
||||
expect(html).toContain('<span data-t="nav.home">Home</span>');
|
||||
expect(html).toContain('<span data-t="home.title">Hello from WrNexus</span>');
|
||||
expect(html).not.toContain('<span data-t="nav.home"></span>');
|
||||
});
|
||||
|
||||
test("home page ships only its active theme stylesheet", async () => {
|
||||
const response = await app.fetch("/");
|
||||
const html = await response.text();
|
||||
const href = html.match(/data-wrnexus-theme href="([^"]+)"/)?.[1];
|
||||
expect(href).toContain("/__wrnexus/theme/dark/");
|
||||
expect(html).not.toContain('href="/__wrnexus/theme.css');
|
||||
const cssResponse = await app.fetch(String(href));
|
||||
expect(cssResponse.status).toBe(200);
|
||||
const css = await cssResponse.text();
|
||||
expect(css.length).toBeLessThan(10_000);
|
||||
expect(css).not.toContain("[data-theme=");
|
||||
});
|
||||
|
||||
test("GET /api/hello returns a translated greeting", async () => {
|
||||
@@ -94,6 +111,17 @@ describe("full app", () => {
|
||||
expect(html).not.toContain("Card title");
|
||||
});
|
||||
|
||||
test("shared UI foundations render on demand instead of shipping in ui.css", async () => {
|
||||
const fieldResponse = await app.fetch("/modal");
|
||||
expect(fieldResponse.status).toBe(200);
|
||||
const fieldHtml = await fieldResponse.text();
|
||||
expect(fieldHtml).toContain('data-wrnexus-style-owner="FieldStyles"');
|
||||
expect(fieldHtml).toContain('data-wrnexus-style-owner="ComponentBaseStyles"');
|
||||
const selectResponse = await app.fetch("/test");
|
||||
expect(selectResponse.status).toBe(200);
|
||||
expect(await selectResponse.text()).toContain('data-wrnexus-style-owner="SelectStyles"');
|
||||
});
|
||||
|
||||
test("platform showcase uses the public layout and styled reactive primitives", async () => {
|
||||
const res = await app.fetch("/platform-showcase");
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import Input from "@wrnexus/ui/components/input.wrn"
|
||||
import Modal from "@wrnexus/ui/components/Modal.wrn"
|
||||
import Toaster from "@wrnexus/ui/components/Toaster.wrn"
|
||||
|
||||
// Modal demo page. Route: /modal
|
||||
//
|
||||
// Five real-world use cases for the shared @wrnexus/ui Modal component — each
|
||||
@@ -9,13 +13,6 @@
|
||||
// this page. Any client expression can raise one with the runtime global —
|
||||
// toast("..."), toast.success(...), toast.error(...) — so pages never
|
||||
// hand-roll a status banner and never reach for a blocking alert().
|
||||
//
|
||||
// No import here on purpose: component tags resolve by directory scan, and
|
||||
// @wrnexus/ui's index.ts intentionally has no runtime exports (registry.ts
|
||||
// holds the server-only filesystem helpers instead) — an explicit
|
||||
// `import { Modal } from "@wrnexus/ui"` fails to bundle since there's no
|
||||
// matching export. The WRN-IMPORT-IMPLICIT warning this triggers is safe to
|
||||
// ignore for component tags.
|
||||
page ModalPage {
|
||||
|
||||
state noteText = "Remember to ship the release notes"
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import AdvancedSelect from "@wrnexus/ui/components/AdvancedSelect.wrn"
|
||||
import ComboBox from "@wrnexus/ui/components/Combobox.wrn"
|
||||
|
||||
// Component event test page. Route: /test
|
||||
//
|
||||
// Events bubble from the exact ComboBox or AdvancedSelect that emitted them.
|
||||
|
||||
+5
-4
@@ -13,15 +13,14 @@ declare namespace WRNexusGenerated {
|
||||
: never;
|
||||
type RealtimeMessage<T> = T extends import("@wrnexus/core").RoomDefinition<any, infer M> ? M : unknown;
|
||||
type QueuePayload<T> = T extends import("@wrnexus/queue").JobDefinition<infer I> ? I : unknown;
|
||||
type RouteName = "about" | "async.data" | "chat" | "client.only" | "dashboard" | "hello" | "index" | "language.tools" | "login" | "modal" | "partial.static" | "platform.showcase" | "reactive" | "server.actions" | "test" | "ui";
|
||||
type ApiRoute = "/api/echo" | "/api/graphql-example" | "/api/hello" | "/api/login" | "/api/logout" | "/api/me" | "/api/typed-user" | "/api/users/csr" | "/api/users/ssr" | "/api/webhooks/payment";
|
||||
type RouteName = "about" | "async.data" | "chat" | "client.only" | "dashboard" | "hello" | "index" | "language.tools" | "layout" | "login" | "modal" | "navigation" | "partial.static" | "platform.showcase" | "reactive" | "server.actions" | "table" | "test" | "ui";
|
||||
type ApiRoute = "/api/accounts" | "/api/echo" | "/api/graphql-example" | "/api/hello" | "/api/invite" | "/api/login" | "/api/logout" | "/api/me" | "/api/typed-user" | "/api/users/csr" | "/api/users/ssr" | "/api/webhooks/payment";
|
||||
type RealtimeRoute = "/realtime/chat" | "/realtime/hello";
|
||||
type EnvironmentKey = "APP_LABEL" | "DATABASE_URL" | "DEMO_SHARED" | "HOST" | "NODE_ENV" | "PORT" | "SESSION_SECRET" | "UAT_ONLY";
|
||||
type TranslationKey = "api.greeting" | "home.intro" | "home.title" | "nav.about" | "nav.chat" | "nav.dashboard" | "nav.home" | "nav.ui";
|
||||
type TranslationKey = "api.greeting" | "home.intro" | "home.title" | "nav.about" | "nav.chat" | "nav.dashboard" | "nav.home" | "nav.layout" | "nav.navigation" | "nav.ui";
|
||||
type QueueName = "welcome-email";
|
||||
type CacheKey = "users";
|
||||
interface Components {
|
||||
"Modal": { props: Record<string, never>; outputs: Record<string, never> };
|
||||
"Counter": { props: { "start"?: unknown; "label"?: unknown }; outputs: Record<string, never> };
|
||||
}
|
||||
interface ApiContracts {
|
||||
@@ -30,6 +29,8 @@ declare namespace WRNexusGenerated {
|
||||
"/api/users/ssr": { GET: ApiContract<typeof import("../api/users/ssr.ts")["GET"]> };
|
||||
"/api/graphql-example": { POST: ApiContract<typeof import("../api/graphql-example.ts")["POST"]> };
|
||||
"/api/typed-user": { POST: ApiContract<typeof import("../api/typed-user.ts")["POST"]> };
|
||||
"/api/accounts": { GET: ApiContract<typeof import("../api/accounts.ts")["GET"]> };
|
||||
"/api/invite": { POST: ApiContract<typeof import("../api/invite.ts")["POST"]> };
|
||||
"/api/logout": { POST: ApiContract<typeof import("../api/logout.ts")["POST"]> };
|
||||
"/api/hello": { GET: ApiContract<typeof import("../api/hello.ts")["GET"]> };
|
||||
"/api/login": { POST: ApiContract<typeof import("../api/login.ts")["POST"]> };
|
||||
|
||||
@@ -7,7 +7,14 @@ const tsconfigRootDir = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
export default tseslint.config(
|
||||
{
|
||||
ignores: ["node_modules/**", "dist/**", ".wrnexus/**", "**/.wrnexus/**"],
|
||||
ignores: [
|
||||
"node_modules/**",
|
||||
"dist/**",
|
||||
".wrnexus/**",
|
||||
"**/.wrnexus/**",
|
||||
".wrnexus-*/**",
|
||||
"**/.wrnexus-*/**",
|
||||
],
|
||||
},
|
||||
{
|
||||
languageOptions: {
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
"compilerOptions": {
|
||||
"lib": ["ESNext", "DOM", "DOM.Iterable"]
|
||||
},
|
||||
"include": ["app"]
|
||||
"include": ["app"],
|
||||
"exclude": ["node_modules", "dist", ".wrnexus-*", "**/.wrnexus-*"]
|
||||
}
|
||||
|
||||
@@ -31,11 +31,11 @@ layout Showcase {
|
||||
|
||||
<section class="docs-directory-group">
|
||||
<a class="docs-directory-heading" href="/advanced-forms">Advanced Forms</a>
|
||||
<div><a data-docs-component-link href="/components/advanced-select">Advanced Select</a><a data-docs-component-link href="/components/combo-box">Combo Box</a><a data-docs-component-link href="/components/copy-markup">Copy Markup</a><a data-docs-component-link href="/components/input-number">Input Number</a><a data-docs-component-link href="/components/pin-input">Pin Input</a><a data-docs-component-link href="/components/strong-password">Strong Password</a><a data-docs-component-link href="/components/toggle-count">Toggle Count</a><a data-docs-component-link href="/components/toggle-password">Toggle Password</a></div>
|
||||
<div><a data-docs-component-link href="/components/advanced-select">Advanced Select</a><a data-docs-component-link href="/components/combo-box">Combo Box</a><a data-docs-component-link href="/components/input-number">Input Number</a><a data-docs-component-link href="/components/pin-input">Pin Input</a><a data-docs-component-link href="/components/strong-password">Strong Password</a><a data-docs-component-link href="/components/toggle-count">Toggle Count</a><a data-docs-component-link href="/components/toggle-password">Toggle Password</a></div>
|
||||
</section>
|
||||
<section class="docs-directory-group">
|
||||
<a class="docs-directory-heading" href="/base">Base</a>
|
||||
<div><a data-docs-component-link href="/components/accordion">Accordion</a><a data-docs-component-link href="/components/alert">Alert</a><a data-docs-component-link href="/components/avatar">Avatar</a><a data-docs-component-link href="/components/avatar-group">Avatar Group</a><a data-docs-component-link href="/components/badge">Badge</a><a data-docs-component-link href="/components/blockquote">Blockquote</a><a data-docs-component-link href="/components/button">Button</a><a data-docs-component-link href="/components/button-group">Button Group</a><a data-docs-component-link href="/components/card">Card</a><a data-docs-component-link href="/components/carousel">Carousel</a><a data-docs-component-link href="/components/chat-bubble">Chat Bubble</a><a data-docs-component-link href="/components/collapse">Collapse</a><a data-docs-component-link href="/components/date-picker">Date Picker</a><a data-docs-component-link href="/components/device-frame">Device Frame</a><a data-docs-component-link href="/components/file-upload-progress">File Upload Progress</a><a data-docs-component-link href="/components/legend-indicator">Legend Indicator</a><a data-docs-component-link href="/components/list">List</a><a data-docs-component-link href="/components/list-group">List Group</a><a data-docs-component-link href="/components/marquee">Marquee</a><a data-docs-component-link href="/components/progress">Progress</a><a data-docs-component-link href="/components/rating">Rating</a><a data-docs-component-link href="/components/skeleton">Skeleton</a><a data-docs-component-link href="/components/spinner">Spinner</a><a data-docs-component-link href="/components/styled-icon">Styled Icon</a><a data-docs-component-link href="/components/timeline">Timeline</a><a data-docs-component-link href="/components/toast">Toast</a><a data-docs-component-link href="/components/tree-view">Tree View</a></div>
|
||||
<div><a data-docs-component-link href="/components/accordion">Accordion</a><a data-docs-component-link href="/components/alert">Alert</a><a data-docs-component-link href="/components/avatar">Avatar</a><a data-docs-component-link href="/components/avatar-group">Avatar Group</a><a data-docs-component-link href="/components/badge">Badge</a><a data-docs-component-link href="/components/blockquote">Blockquote</a><a data-docs-component-link href="/components/button">Button</a><a data-docs-component-link href="/components/button-group">Button Group</a><a data-docs-component-link href="/components/card">Card</a><a data-docs-component-link href="/components/carousel">Carousel</a><a data-docs-component-link href="/components/chat-bubble">Chat Bubble</a><a data-docs-component-link href="/components/collapse">Collapse</a><a data-docs-component-link href="/components/date-picker">Date Picker</a><a data-docs-component-link href="/components/device-frame">Device Frame</a><a data-docs-component-link href="/components/file-upload-progress">File Upload Progress</a><a data-docs-component-link href="/components/legend-indicator">Legend Indicator</a><a data-docs-component-link href="/components/list">List</a><a data-docs-component-link href="/components/list-group">List Group</a><a data-docs-component-link href="/components/marquee">Marquee</a><a data-docs-component-link href="/components/progress">Progress</a><a data-docs-component-link href="/components/rating">Rating</a><a data-docs-component-link href="/components/skeleton">Skeleton</a><a data-docs-component-link href="/components/spinner">Spinner</a><a data-docs-component-link href="/components/styled-icon">Styled Icon</a><a data-docs-component-link href="/components/timeline">Timeline</a><a data-docs-component-link href="/components/tree-view">Tree View</a></div>
|
||||
</section>
|
||||
<section class="docs-directory-group">
|
||||
<a class="docs-directory-heading" href="/core">Core</a>
|
||||
@@ -51,7 +51,7 @@ layout Showcase {
|
||||
</section>
|
||||
<section class="docs-directory-group">
|
||||
<a class="docs-directory-heading" href="/integrations">Integrations</a>
|
||||
<div><a data-docs-component-link href="/components/advanced-date-picker">Advanced Date Picker</a><a data-docs-component-link href="/components/advanced-range-slider">Advanced Range Slider</a><a data-docs-component-link href="/components/chart">Chart</a><a data-docs-component-link href="/components/clipboard">Clipboard</a><a data-docs-component-link href="/components/confetti">Confetti</a><a data-docs-component-link href="/components/data-map">Data Map</a><a data-docs-component-link href="/components/drag-and-drop">Drag And Drop</a><a data-docs-component-link href="/components/file-upload">File Upload</a><a data-docs-component-link href="/components/map">Map</a><a data-docs-component-link href="/components/toast-notifications">Toast Notifications</a><a data-docs-component-link href="/components/wysiwyg-editor">Wysiwyg Editor</a></div>
|
||||
<div><a data-docs-component-link href="/components/chart">Chart</a><a data-docs-component-link href="/components/clipboard">Clipboard</a><a data-docs-component-link href="/components/confetti">Confetti</a><a data-docs-component-link href="/components/data-map">Data Map</a><a data-docs-component-link href="/components/drag-and-drop">Drag And Drop</a><a data-docs-component-link href="/components/map">Map</a><a data-docs-component-link href="/components/wysiwyg-editor">Wysiwyg Editor</a></div>
|
||||
</section>
|
||||
<section class="docs-directory-group">
|
||||
<a class="docs-directory-heading" href="/layout">Layout</a>
|
||||
|
||||
@@ -3,14 +3,14 @@ page AdvancedFormsShowcase {
|
||||
layout = "showcase"
|
||||
seo {
|
||||
title = "Advanced Forms components"
|
||||
description = "8 advanced-forms components from @wrnexus/ui."
|
||||
description = "7 advanced-forms components from @wrnexus/ui."
|
||||
}
|
||||
view {
|
||||
<header class="showcase-hero showcase-hero--category">
|
||||
<span class="showcase-eyebrow">Component category</span>
|
||||
<h1 class="showcase-title">Advanced Forms</h1>
|
||||
<p class="showcase-description">8 unique, responsive, theme-aware components. Open a component to inspect multiple live configurations and its complete props API.</p>
|
||||
<div class="category-meta"><span>8 components</span><span>Multiple use cases</span><span>112 live configurations</span></div>
|
||||
<p class="showcase-description">7 unique, responsive, theme-aware components. Open a component to inspect multiple live configurations and its complete props API.</p>
|
||||
<div class="category-meta"><span>7 components</span><span>Multiple use cases</span><span>109 live configurations</span></div>
|
||||
</header>
|
||||
<div class="catalog-grid">
|
||||
<article class="catalog-card" data-interactive-preview="false">
|
||||
@@ -33,16 +33,6 @@ page AdvancedFormsShowcase {
|
||||
<div class="catalog-card-footer"><span>41 props · 0 slots</span><a href="/components/combo-box">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="catalog-card" data-interactive-preview="false">
|
||||
<div class="catalog-card-preview">
|
||||
<div class="catalog-card-glow" aria-hidden="true"></div>
|
||||
<div class="catalog-card-stage"><CopyMarkup size="default" label="Copy Markup" value="sample-1" placeholder="Enter a sample value" disabled="false" required="false" class="showcase-instance showcase-instance--1" /></div>
|
||||
</div>
|
||||
<div class="catalog-card-body">
|
||||
<div><span class="catalog-card-category">Advanced Forms</span><h2>Copy Markup</h2><p>Theme-aware, responsive copy markup component.</p></div>
|
||||
<div class="catalog-card-footer"><span>13 props · 0 slots</span><a href="/components/copy-markup">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="catalog-card" data-interactive-preview="false">
|
||||
<div class="catalog-card-preview">
|
||||
<div class="catalog-card-glow" aria-hidden="true"></div>
|
||||
|
||||
@@ -3,14 +3,14 @@ page BaseShowcase {
|
||||
layout = "showcase"
|
||||
seo {
|
||||
title = "Base components"
|
||||
description = "27 base components from @wrnexus/ui."
|
||||
description = "26 base components from @wrnexus/ui."
|
||||
}
|
||||
view {
|
||||
<header class="showcase-hero showcase-hero--category">
|
||||
<span class="showcase-eyebrow">Component category</span>
|
||||
<h1 class="showcase-title">Base</h1>
|
||||
<p class="showcase-description">27 unique, responsive, theme-aware components. Open a component to inspect multiple live configurations and its complete props API.</p>
|
||||
<div class="category-meta"><span>27 components</span><span>Multiple use cases</span><span>86 live configurations</span></div>
|
||||
<p class="showcase-description">26 unique, responsive, theme-aware components. Open a component to inspect multiple live configurations and its complete props API.</p>
|
||||
<div class="category-meta"><span>26 components</span><span>Multiple use cases</span><span>83 live configurations</span></div>
|
||||
</header>
|
||||
<div class="catalog-grid">
|
||||
<article class="catalog-card" data-interactive-preview="false">
|
||||
@@ -266,21 +266,11 @@ page BaseShowcase {
|
||||
<article class="catalog-card" data-interactive-preview="false">
|
||||
<div class="catalog-card-preview">
|
||||
<div class="catalog-card-glow" aria-hidden="true"></div>
|
||||
<div class="catalog-card-stage"><Toast size="default" title="Toast example" description="A polished default toast for a modern application." items='[{"id":"toast-0-1","key":"toast-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#toast-demo","actionHref":"#toast-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"toast-0-2","key":"toast-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#toast-demo","actionHref":"#toast-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
|
||||
</div>
|
||||
<div class="catalog-card-body">
|
||||
<div><span class="catalog-card-category">Base</span><h2>Toast</h2><p>Theme-aware, responsive toast component.</p></div>
|
||||
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/toast">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="catalog-card" data-interactive-preview="false">
|
||||
<div class="catalog-card-preview">
|
||||
<div class="catalog-card-glow" aria-hidden="true"></div>
|
||||
<div class="catalog-card-stage"><TreeView size="default" title="Tree View example" description="A polished default tree view for a modern application." items='[{"id":"tree-view-0-1","key":"tree-view-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"tree-view-0-2","key":"tree-view-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
|
||||
<div class="catalog-card-stage"><TreeView title="Tree View example" description="A polished default tree view for a modern application." items='[{"id":"tree-view-0-1","key":"tree-view-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"tree-view-0-2","key":"tree-view-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' defaultExpanded='[{"id":"tree-view-0-1","key":"tree-view-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"tree-view-0-2","key":"tree-view-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' size="default" class="showcase-instance showcase-instance--1" /></div>
|
||||
</div>
|
||||
<div class="catalog-card-body">
|
||||
<div><span class="catalog-card-category">Base</span><h2>Tree View</h2><p>Theme-aware, responsive tree view component.</p></div>
|
||||
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/tree-view">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
<div class="catalog-card-footer"><span>10 props · 1 slots</span><a href="/components/tree-view">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
</div>
|
||||
</article></div>
|
||||
}
|
||||
|
||||
@@ -1,696 +0,0 @@
|
||||
// Generated by scripts/generate-showcase.mjs. Do not edit directly.
|
||||
page AdvancedDatePickerDetail {
|
||||
layout = "showcase"
|
||||
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
|
||||
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
|
||||
state playground_title = ctx.url.searchParams.get("pg_title") ?? "Advanced Date Picker example"
|
||||
state playground_description = ctx.url.searchParams.get("pg_description") ?? "A polished default advanced date picker for a modern application."
|
||||
state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"advanced-date-picker-0-1\",\"key\":\"advanced-date-picker-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#advanced-date-picker-demo\",\"actionHref\":\"#advanced-date-picker-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"advanced-date-picker-0-2\",\"key\":\"advanced-date-picker-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#advanced-date-picker-demo\",\"actionHref\":\"#advanced-date-picker-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]")
|
||||
state playground_variant = ctx.url.searchParams.get("pg_variant") ?? "default"
|
||||
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
|
||||
seo {
|
||||
title = "Advanced Date Picker"
|
||||
description = "Theme-aware, responsive advanced date picker component."
|
||||
}
|
||||
view {
|
||||
<nav class="docs-breadcrumbs" aria-label="Breadcrumb">
|
||||
<a href="/">Components</a><span aria-hidden="true">/</span><a href="/integrations">Integrations</a><span aria-hidden="true">/</span><span>Advanced Date Picker</span>
|
||||
</nav>
|
||||
<header class="detail-hero">
|
||||
<div class="detail-hero-copy">
|
||||
<span class="showcase-eyebrow">Integrations component</span>
|
||||
<h1>Advanced Date Picker</h1>
|
||||
<p>Theme-aware, responsive advanced date picker component.</p>
|
||||
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>5 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
</div>
|
||||
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
|
||||
</header>
|
||||
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="AdvancedDatePicker" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="input,change,open,close,clear">
|
||||
<div class="detail-section-heading"><span>Interactive playground</span><h2>Configure Advanced Date Picker</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div>
|
||||
<div class="playground-workbench">
|
||||
<div class="playground-preview">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
<div class="playground-preview-source" data-playground-preview><AdvancedDatePicker size='{playground_size}' color='{playground_color}' title='{playground_title}' description='{playground_description}' items='{playground_items}' variant='{playground_variant}' class='{playground_class}' /></div>
|
||||
<section class="playground-code" aria-label="Current component code">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
|
||||
<pre><code data-playground-code><AdvancedDatePicker
|
||||
title="Advanced Date Picker example"
|
||||
description="A polished default advanced date picker for a modern application."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
/></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>[
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]</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><AdvancedDatePicker
|
||||
title="Advanced Date Picker example"
|
||||
description="A polished default advanced date picker for a modern application."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
/></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><AdvancedDatePicker
|
||||
size="sm"
|
||||
title="Compact Advanced Date Picker"
|
||||
description="A compact configuration for dashboards and dense product surfaces."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
variant="secondary"
|
||||
/></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><AdvancedDatePicker
|
||||
size="lg"
|
||||
title="Advanced Advanced Date Picker"
|
||||
description="A richer configuration demonstrating additional content and hierarchy."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Unlimited"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Unlimited"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
variant="success"
|
||||
/></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><AdvancedDatePicker
|
||||
@input='console.log(payload)'
|
||||
@change='console.log(payload)'
|
||||
@open='console.log(payload)'
|
||||
@close='console.log(payload)'
|
||||
@clear='console.log(payload)'
|
||||
/></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>{</span> registerOutputHandler <span>}</span> from "@wrnexus/csr/outputs"
|
||||
|
||||
const component = document.querySelector("[data-ui-component=\"AdvancedDatePicker\"], [data-component=\"AdvancedDatePicker\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "input", (payload) => <span>{</span>
|
||||
console.log("input", payload)
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "change", (payload) => <span>{</span>
|
||||
console.log("change", payload)
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "open", (payload) => <span>{</span>
|
||||
console.log("open", payload)
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "close", (payload) => <span>{</span>
|
||||
console.log("close", payload)
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "clear", (payload) => <span>{</span>
|
||||
console.log("clear", payload)
|
||||
<span>}</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><AdvancedRangeSlider
|
||||
title="Advanced Range Slider example"
|
||||
description="A polished default advanced range slider for a modern application."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
/></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>[
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]</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><AdvancedRangeSlider
|
||||
title="Advanced Range Slider example"
|
||||
description="A polished default advanced range slider for a modern application."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
/></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><AdvancedRangeSlider
|
||||
size="sm"
|
||||
title="Compact Advanced Range Slider"
|
||||
description="A compact configuration for dashboards and dense product surfaces."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
variant="secondary"
|
||||
/></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><AdvancedRangeSlider
|
||||
size="lg"
|
||||
title="Advanced Advanced Range Slider"
|
||||
description="A richer configuration demonstrating additional content and hierarchy."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Unlimited"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Unlimited"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
variant="success"
|
||||
/></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><AdvancedRangeSlider
|
||||
@input='console.log(payload)'
|
||||
@change='console.log(payload)'
|
||||
@start='console.log(payload)'
|
||||
@end='console.log(payload)'
|
||||
/></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>{</span> registerOutputHandler <span>}</span> from "@wrnexus/csr/outputs"
|
||||
|
||||
const component = document.querySelector("[data-ui-component=\"AdvancedRangeSlider\"], [data-component=\"AdvancedRangeSlider\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "input", (payload) => <span>{</span>
|
||||
console.log("input", payload)
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "change", (payload) => <span>{</span>
|
||||
console.log("change", payload)
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "start", (payload) => <span>{</span>
|
||||
console.log("start", payload)
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "end", (payload) => <span>{</span>
|
||||
console.log("end", payload)
|
||||
<span>}</span>)</code></pre></section></div></div></section>
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Advanced Range Slider"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
</main>
|
||||
<aside class="detail-aside">
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#advanced-range-slider-demo-1">Production default</a><a href="#advanced-range-slider-demo-2">Compact application</a><a href="#advanced-range-slider-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
|
||||
</aside>
|
||||
</div>
|
||||
<nav class="detail-pagination">
|
||||
<a href="/components/advanced-date-picker"><small>Previous</small><strong>← Advanced Date Picker</strong></a>
|
||||
<a href="/components/chart"><small>Next</small><strong>Chart →</strong></a>
|
||||
</nav>
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,17 @@
|
||||
// Generated by scripts/generate-showcase.mjs. Do not edit directly.
|
||||
page ChartDetail {
|
||||
layout = "showcase"
|
||||
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
|
||||
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
|
||||
state playground_title = ctx.url.searchParams.get("pg_title") ?? "Chart example"
|
||||
state playground_description = ctx.url.searchParams.get("pg_description") ?? "A polished default chart for a modern application."
|
||||
state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"chart-0-1\",\"key\":\"chart-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#chart-demo\",\"actionHref\":\"#chart-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"chart-0-2\",\"key\":\"chart-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#chart-demo\",\"actionHref\":\"#chart-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]")
|
||||
state playground_variant = ctx.url.searchParams.get("pg_variant") ?? "default"
|
||||
state playground_valueKey = ctx.url.searchParams.get("pg_valueKey") ?? "value"
|
||||
state playground_labelKey = ctx.url.searchParams.get("pg_labelKey") ?? "label"
|
||||
state playground_height = Number(ctx.url.searchParams.get("pg_height") ?? "240")
|
||||
state playground_showLegend = (ctx.url.searchParams.get("pg_showLegend") ?? "true") === "true"
|
||||
state playground_showValues = (ctx.url.searchParams.get("pg_showValues") ?? "true") === "true"
|
||||
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
|
||||
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
|
||||
state playground_variant = ctx.url.searchParams.get("pg_variant") ?? "bar"
|
||||
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
|
||||
seo {
|
||||
title = "Chart"
|
||||
@@ -21,7 +26,7 @@ page ChartDetail {
|
||||
<span class="showcase-eyebrow">Integrations component</span>
|
||||
<h1>Chart</h1>
|
||||
<p>Theme-aware, responsive chart component.</p>
|
||||
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>3 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
<div class="detail-badges"><span>12 props</span><span>1 slots</span><span>3 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
</div>
|
||||
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
|
||||
</header>
|
||||
@@ -30,7 +35,7 @@ page ChartDetail {
|
||||
<div class="playground-workbench">
|
||||
<div class="playground-preview">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
<div class="playground-preview-source" data-playground-preview><Chart size='{playground_size}' color='{playground_color}' title='{playground_title}' description='{playground_description}' items='{playground_items}' variant='{playground_variant}' class='{playground_class}' /></div>
|
||||
<div class="playground-preview-source" data-playground-preview><Chart title='{playground_title}' description='{playground_description}' items='{playground_items}' valueKey='{playground_valueKey}' labelKey='{playground_labelKey}' height='{playground_height}' showLegend='{playground_showLegend}' showValues='{playground_showValues}' size='{playground_size}' color='{playground_color}' variant='{playground_variant}' class='{playground_class}' /></div>
|
||||
<section class="playground-code" aria-label="Current component code">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
|
||||
<pre><code data-playground-code><Chart
|
||||
@@ -150,8 +155,8 @@ page ChartDetail {
|
||||
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
|
||||
</div>
|
||||
<form class="playground-controls" data-playground-form>
|
||||
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
|
||||
<div class="playground-fields"><label class="playground-field" for="playground-chart-size"><span><strong>size</strong><small>string</small></span><select id="playground-chart-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-chart-color"><span><strong>color</strong><small>string</small></span><select id="playground-chart-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-chart-title"><span><strong>title</strong><small>string</small></span><input id="playground-chart-title" name="pg_title" type="text" value="Chart example" /></label><label class="playground-field" for="playground-chart-description"><span><strong>description</strong><small>string</small></span><input id="playground-chart-description" name="pg_description" type="text" value="A polished default chart for a modern application." /></label><label class="playground-field" for="playground-chart-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-chart-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[
|
||||
<header><div><span>Component props</span><strong>12 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
|
||||
<div class="playground-fields"><label class="playground-field" for="playground-chart-title"><span><strong>title</strong><small>string</small></span><input id="playground-chart-title" name="pg_title" type="text" value="Chart example" /></label><label class="playground-field" for="playground-chart-description"><span><strong>description</strong><small>string</small></span><input id="playground-chart-description" name="pg_description" type="text" value="A polished default chart for a modern application." /></label><label class="playground-field" for="playground-chart-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-chart-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[
|
||||
{
|
||||
"id": "chart-0-1",
|
||||
"key": "chart-0-1",
|
||||
@@ -258,7 +263,7 @@ page ChartDetail {
|
||||
"Standard"
|
||||
]
|
||||
}
|
||||
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-chart-variant"><span><strong>variant</strong><small>string</small></span><input id="playground-chart-variant" name="pg_variant" type="text" value="default" /></label><label class="playground-field" for="playground-chart-class"><span><strong>class</strong><small>string</small></span><input id="playground-chart-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
|
||||
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-chart-valueKey"><span><strong>value Key</strong><small>string</small></span><input id="playground-chart-valueKey" name="pg_valueKey" type="text" value="value" /></label><label class="playground-field" for="playground-chart-labelKey"><span><strong>label Key</strong><small>string</small></span><input id="playground-chart-labelKey" name="pg_labelKey" type="text" value="label" /></label><label class="playground-field" for="playground-chart-height"><span><strong>height</strong><small>number</small></span><input id="playground-chart-height" name="pg_height" type="number" value="240" /></label><label class="playground-toggle" for="playground-chart-showLegend"><span><strong>show Legend</strong><small>boolean</small></span><input id="playground-chart-showLegend" name="pg_showLegend" type="checkbox" value="true" checked data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-toggle" for="playground-chart-showValues"><span><strong>show Values</strong><small>boolean</small></span><input id="playground-chart-showValues" name="pg_showValues" type="checkbox" value="true" checked data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-field" for="playground-chart-size"><span><strong>size</strong><small>string</small></span><select id="playground-chart-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-chart-color"><span><strong>color</strong><small>string</small></span><select id="playground-chart-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-chart-variant"><span><strong>variant</strong><small>string</small></span><input id="playground-chart-variant" name="pg_variant" type="text" value="bar" /></label><label class="playground-field" for="playground-chart-class"><span><strong>class</strong><small>string</small></span><input id="playground-chart-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
@@ -274,7 +279,7 @@ page ChartDetail {
|
||||
<div id="chart-demo-1" class="demo-canvas demo-canvas--1">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><Chart size="default" title="Chart example" description="A polished default chart for a modern application." items='[{"id":"chart-0-1","key":"chart-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"chart-0-2","key":"chart-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
|
||||
<div class="demo-render"><Chart title="Chart example" description="A polished default chart for a modern application." items='[{"id":"chart-0-1","key":"chart-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"chart-0-2","key":"chart-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' height="240" showLegend="true" showValues="true" size="default" variant="bar" class="showcase-instance showcase-instance--1" /></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Production default usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
@@ -402,12 +407,11 @@ page ChartDetail {
|
||||
<div id="chart-demo-2" class="demo-canvas demo-canvas--2">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><Chart size="sm" title="Compact Chart" description="A compact configuration for dashboards and dense product surfaces." items='[{"id":"chart-1-1","key":"chart-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"chart-1-2","key":"chart-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="secondary" class="showcase-instance showcase-instance--2" /></div>
|
||||
<div class="demo-render"><Chart title="Compact Chart" description="A compact configuration for dashboards and dense product surfaces." items='[{"id":"chart-1-1","key":"chart-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"chart-1-2","key":"chart-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' height="240" showLegend="true" showValues="true" size="sm" variant="secondary" class="showcase-instance showcase-instance--2" /></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Compact application usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
<pre><code><Chart
|
||||
size="sm"
|
||||
title="Compact Chart"
|
||||
description="A compact configuration for dashboards and dense product surfaces."
|
||||
items='[
|
||||
@@ -518,6 +522,7 @@ page ChartDetail {
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
/></code></pre>
|
||||
</section>
|
||||
@@ -532,12 +537,11 @@ page ChartDetail {
|
||||
<div id="chart-demo-3" class="demo-canvas demo-canvas--3">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><Chart size="lg" title="Advanced Chart" description="A richer configuration demonstrating additional content and hierarchy." items='[{"id":"chart-2-1","key":"chart-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"chart-2-2","key":"chart-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' variant="success" class="showcase-instance showcase-instance--3" /></div>
|
||||
<div class="demo-render"><Chart title="Advanced Chart" description="A richer configuration demonstrating additional content and hierarchy." items='[{"id":"chart-2-1","key":"chart-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"chart-2-2","key":"chart-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' height="240" showLegend="true" showValues="true" size="lg" variant="success" class="showcase-instance showcase-instance--3" /></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Rich configuration usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
<pre><code><Chart
|
||||
size="lg"
|
||||
title="Advanced Chart"
|
||||
description="A richer configuration demonstrating additional content and hierarchy."
|
||||
items='[
|
||||
@@ -648,6 +652,7 @@ page ChartDetail {
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
size="lg"
|
||||
variant="success"
|
||||
/></code></pre>
|
||||
</section>
|
||||
@@ -672,14 +677,14 @@ if (component) registerOutputHandler(component, "dataPointClick", (payload) =>
|
||||
if (component) registerOutputHandler(component, "legendToggle", (payload) => <span>{</span>
|
||||
console.log("legendToggle", payload)
|
||||
<span>}</span>)</code></pre></section></div></div></section>
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Chart"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>title</code></td><td>string</td><td><code>"Chart"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>valueKey</code></td><td>string</td><td><code>"value"</code></td><td>No</td></tr><tr><td><code>labelKey</code></td><td>string</td><td><code>"label"</code></td><td>No</td></tr><tr><td><code>height</code></td><td>number</td><td><code>240</code></td><td>No</td></tr><tr><td><code>showLegend</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>showValues</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"bar"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
</main>
|
||||
<aside class="detail-aside">
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#chart-demo-1">Production default</a><a href="#chart-demo-2">Compact application</a><a href="#chart-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
|
||||
</aside>
|
||||
</div>
|
||||
<nav class="detail-pagination">
|
||||
<a href="/components/advanced-range-slider"><small>Previous</small><strong>← Advanced Range Slider</strong></a>
|
||||
<span></span>
|
||||
<a href="/components/clipboard"><small>Next</small><strong>Clipboard →</strong></a>
|
||||
</nav>
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
// Generated by scripts/generate-showcase.mjs. Do not edit directly.
|
||||
page ClipboardDetail {
|
||||
layout = "showcase"
|
||||
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
|
||||
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
|
||||
state playground_value = ctx.url.searchParams.get("pg_value") ?? "sample-1"
|
||||
state playground_label = ctx.url.searchParams.get("pg_label") ?? "Clipboard"
|
||||
state playground_copiedLabel = ctx.url.searchParams.get("pg_copiedLabel") ?? "Clipboard"
|
||||
state playground_errorLabel = ctx.url.searchParams.get("pg_errorLabel") ?? "Clipboard"
|
||||
state playground_title = ctx.url.searchParams.get("pg_title") ?? "Clipboard example"
|
||||
state playground_description = ctx.url.searchParams.get("pg_description") ?? "A polished default clipboard for a modern application."
|
||||
state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"clipboard-0-1\",\"key\":\"clipboard-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#clipboard-demo\",\"actionHref\":\"#clipboard-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"clipboard-0-2\",\"key\":\"clipboard-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#clipboard-demo\",\"actionHref\":\"#clipboard-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]")
|
||||
state playground_variant = ctx.url.searchParams.get("pg_variant") ?? "default"
|
||||
state playground_code = (ctx.url.searchParams.get("pg_code") ?? "true") === "true"
|
||||
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
|
||||
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
|
||||
state playground_disabled = (ctx.url.searchParams.get("pg_disabled") ?? "false") === "true"
|
||||
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
|
||||
seo {
|
||||
title = "Clipboard"
|
||||
@@ -21,7 +25,7 @@ page ClipboardDetail {
|
||||
<span class="showcase-eyebrow">Integrations component</span>
|
||||
<h1>Clipboard</h1>
|
||||
<p>Theme-aware, responsive clipboard component.</p>
|
||||
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>3 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
<div class="detail-badges"><span>11 props</span><span>1 slots</span><span>3 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
</div>
|
||||
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
|
||||
</header>
|
||||
@@ -30,235 +34,24 @@ page ClipboardDetail {
|
||||
<div class="playground-workbench">
|
||||
<div class="playground-preview">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
<div class="playground-preview-source" data-playground-preview><Clipboard size='{playground_size}' color='{playground_color}' title='{playground_title}' description='{playground_description}' items='{playground_items}' variant='{playground_variant}' class='{playground_class}' /></div>
|
||||
<div class="playground-preview-source" data-playground-preview><Clipboard value='{playground_value}' label='{playground_label}' copiedLabel='{playground_copiedLabel}' errorLabel='{playground_errorLabel}' title='{playground_title}' description='{playground_description}' code='{playground_code}' size='{playground_size}' color='{playground_color}' disabled='{playground_disabled}' class='{playground_class}' /></div>
|
||||
<section class="playground-code" aria-label="Current component code">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
|
||||
<pre><code data-playground-code><Clipboard
|
||||
value="sample-1"
|
||||
label="Clipboard"
|
||||
copiedLabel="Clipboard"
|
||||
errorLabel="Clipboard"
|
||||
title="Clipboard example"
|
||||
description="A polished default clipboard for a modern application."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
/></code></pre>
|
||||
</section>
|
||||
<div class="playground-status" data-playground-status aria-live="polite"></div>
|
||||
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
|
||||
</div>
|
||||
<form class="playground-controls" data-playground-form>
|
||||
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
|
||||
<div class="playground-fields"><label class="playground-field" for="playground-clipboard-size"><span><strong>size</strong><small>string</small></span><select id="playground-clipboard-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-clipboard-color"><span><strong>color</strong><small>string</small></span><select id="playground-clipboard-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-clipboard-title"><span><strong>title</strong><small>string</small></span><input id="playground-clipboard-title" name="pg_title" type="text" value="Clipboard example" /></label><label class="playground-field" for="playground-clipboard-description"><span><strong>description</strong><small>string</small></span><input id="playground-clipboard-description" name="pg_description" type="text" value="A polished default clipboard for a modern application." /></label><label class="playground-field" for="playground-clipboard-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-clipboard-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-clipboard-variant"><span><strong>variant</strong><small>string</small></span><input id="playground-clipboard-variant" name="pg_variant" type="text" value="default" /></label><label class="playground-field" for="playground-clipboard-class"><span><strong>class</strong><small>string</small></span><input id="playground-clipboard-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
|
||||
<header><div><span>Component props</span><strong>11 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
|
||||
<div class="playground-fields"><label class="playground-field" for="playground-clipboard-value"><span><strong>value</strong><small>string</small></span><input id="playground-clipboard-value" name="pg_value" type="text" value="sample-1" /></label><label class="playground-field" for="playground-clipboard-label"><span><strong>label</strong><small>string</small></span><input id="playground-clipboard-label" name="pg_label" type="text" value="Clipboard" /></label><label class="playground-field" for="playground-clipboard-copiedLabel"><span><strong>copied Label</strong><small>string</small></span><input id="playground-clipboard-copiedLabel" name="pg_copiedLabel" type="text" value="Clipboard" /></label><label class="playground-field" for="playground-clipboard-errorLabel"><span><strong>error Label</strong><small>string</small></span><input id="playground-clipboard-errorLabel" name="pg_errorLabel" type="text" value="Clipboard" /></label><label class="playground-field" for="playground-clipboard-title"><span><strong>title</strong><small>string</small></span><input id="playground-clipboard-title" name="pg_title" type="text" value="Clipboard example" /></label><label class="playground-field" for="playground-clipboard-description"><span><strong>description</strong><small>string</small></span><input id="playground-clipboard-description" name="pg_description" type="text" value="A polished default clipboard for a modern application." /></label><label class="playground-toggle" for="playground-clipboard-code"><span><strong>code</strong><small>boolean</small></span><input id="playground-clipboard-code" name="pg_code" type="checkbox" value="true" checked data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-field" for="playground-clipboard-size"><span><strong>size</strong><small>string</small></span><select id="playground-clipboard-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-clipboard-color"><span><strong>color</strong><small>string</small></span><select id="playground-clipboard-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-toggle" for="playground-clipboard-disabled"><span><strong>disabled</strong><small>boolean</small></span><input id="playground-clipboard-disabled" name="pg_disabled" type="checkbox" value="true" data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-field" for="playground-clipboard-class"><span><strong>class</strong><small>string</small></span><input id="playground-clipboard-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
@@ -274,121 +67,17 @@ page ClipboardDetail {
|
||||
<div id="clipboard-demo-1" class="demo-canvas demo-canvas--1">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><Clipboard size="default" title="Clipboard example" description="A polished default clipboard for a modern application." items='[{"id":"clipboard-0-1","key":"clipboard-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#clipboard-demo","actionHref":"#clipboard-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"clipboard-0-2","key":"clipboard-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#clipboard-demo","actionHref":"#clipboard-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
|
||||
<div class="demo-render"><Clipboard value="sample-1" label="Clipboard" copiedLabel="Clipboard" errorLabel="Clipboard" title="Clipboard example" description="A polished default clipboard for a modern application." code="true" size="default" disabled="false" class="showcase-instance showcase-instance--1" /></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Production default usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
<pre><code><Clipboard
|
||||
value="sample-1"
|
||||
label="Clipboard"
|
||||
copiedLabel="Clipboard"
|
||||
errorLabel="Clipboard"
|
||||
title="Clipboard example"
|
||||
description="A polished default clipboard for a modern application."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
/></code></pre>
|
||||
</section>
|
||||
</div>
|
||||
@@ -402,123 +91,18 @@ page ClipboardDetail {
|
||||
<div id="clipboard-demo-2" class="demo-canvas demo-canvas--2">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><Clipboard size="sm" title="Compact Clipboard" description="A compact configuration for dashboards and dense product surfaces." items='[{"id":"clipboard-1-1","key":"clipboard-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#clipboard-demo","actionHref":"#clipboard-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"clipboard-1-2","key":"clipboard-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#clipboard-demo","actionHref":"#clipboard-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="secondary" class="showcase-instance showcase-instance--2" /></div>
|
||||
<div class="demo-render"><Clipboard value="sample-2" label="Compact example" copiedLabel="Compact example" errorLabel="Compact example" title="Compact Clipboard" description="A compact configuration for dashboards and dense product surfaces." code="true" size="sm" disabled="false" class="showcase-instance showcase-instance--2" /></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Compact application usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
<pre><code><Clipboard
|
||||
size="sm"
|
||||
value="sample-2"
|
||||
label="Compact example"
|
||||
copiedLabel="Compact example"
|
||||
errorLabel="Compact example"
|
||||
title="Compact Clipboard"
|
||||
description="A compact configuration for dashboards and dense product surfaces."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
/></code></pre>
|
||||
</section>
|
||||
</div>
|
||||
@@ -532,123 +116,18 @@ page ClipboardDetail {
|
||||
<div id="clipboard-demo-3" class="demo-canvas demo-canvas--3">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><Clipboard size="lg" title="Advanced Clipboard" description="A richer configuration demonstrating additional content and hierarchy." items='[{"id":"clipboard-2-1","key":"clipboard-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#clipboard-demo","actionHref":"#clipboard-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"clipboard-2-2","key":"clipboard-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#clipboard-demo","actionHref":"#clipboard-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' variant="success" class="showcase-instance showcase-instance--3" /></div>
|
||||
<div class="demo-render"><Clipboard value="sample-3" label="Advanced example" copiedLabel="Advanced example" errorLabel="Advanced example" title="Advanced Clipboard" description="A richer configuration demonstrating additional content and hierarchy." code="true" size="lg" disabled="false" class="showcase-instance showcase-instance--3" /></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Rich configuration usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
<pre><code><Clipboard
|
||||
size="lg"
|
||||
value="sample-3"
|
||||
label="Advanced example"
|
||||
copiedLabel="Advanced example"
|
||||
errorLabel="Advanced example"
|
||||
title="Advanced Clipboard"
|
||||
description="A richer configuration demonstrating additional content and hierarchy."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Unlimited"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Unlimited"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
variant="success"
|
||||
size="lg"
|
||||
/></code></pre>
|
||||
</section>
|
||||
</div>
|
||||
@@ -672,7 +151,7 @@ if (component) registerOutputHandler(component, "success", (payload) => <span
|
||||
if (component) registerOutputHandler(component, "error", (payload) => <span>{</span>
|
||||
console.log("error", payload)
|
||||
<span>}</span>)</code></pre></section></div></div></section>
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Clipboard"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>value</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Copy"</code></td><td>No</td></tr><tr><td><code>copiedLabel</code></td><td>string</td><td><code>"Copied"</code></td><td>No</td></tr><tr><td><code>errorLabel</code></td><td>string</td><td><code>"Copy failed"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Clipboard"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>code</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>disabled</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
</main>
|
||||
<aside class="detail-aside">
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#clipboard-demo-1">Production default</a><a href="#clipboard-demo-2">Compact application</a><a href="#clipboard-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
|
||||
|
||||
@@ -1393,7 +1393,7 @@ if (component) registerOutputHandler(component, "error", (payload) => <span>&
|
||||
</div>
|
||||
<nav class="detail-pagination">
|
||||
<a href="/components/advanced-select"><small>Previous</small><strong>← Advanced Select</strong></a>
|
||||
<a href="/components/copy-markup"><small>Next</small><strong>Copy Markup →</strong></a>
|
||||
<a href="/components/input-number"><small>Next</small><strong>Input Number →</strong></a>
|
||||
</nav>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
// Generated by scripts/generate-showcase.mjs. Do not edit directly.
|
||||
page ConfettiDetail {
|
||||
layout = "showcase"
|
||||
state playground_label = ctx.url.searchParams.get("pg_label") ?? "Confetti"
|
||||
state playground_duration = Number(ctx.url.searchParams.get("pg_duration") ?? "1200")
|
||||
state playground_count = Number(ctx.url.searchParams.get("pg_count") ?? "3")
|
||||
state playground_autoStart = (ctx.url.searchParams.get("pg_autoStart") ?? "false") === "true"
|
||||
state playground_disabled = (ctx.url.searchParams.get("pg_disabled") ?? "false") === "true"
|
||||
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
|
||||
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
|
||||
state playground_title = ctx.url.searchParams.get("pg_title") ?? "Confetti example"
|
||||
state playground_description = ctx.url.searchParams.get("pg_description") ?? "A polished default confetti for a modern application."
|
||||
state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"confetti-0-1\",\"key\":\"confetti-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#confetti-demo\",\"actionHref\":\"#confetti-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"confetti-0-2\",\"key\":\"confetti-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#confetti-demo\",\"actionHref\":\"#confetti-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]")
|
||||
state playground_variant = ctx.url.searchParams.get("pg_variant") ?? "default"
|
||||
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
|
||||
seo {
|
||||
title = "Confetti"
|
||||
@@ -21,7 +22,7 @@ page ConfettiDetail {
|
||||
<span class="showcase-eyebrow">Integrations component</span>
|
||||
<h1>Confetti</h1>
|
||||
<p>Theme-aware, responsive confetti component.</p>
|
||||
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>2 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
<div class="detail-badges"><span>8 props</span><span>1 slots</span><span>2 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
</div>
|
||||
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
|
||||
</header>
|
||||
@@ -30,235 +31,20 @@ page ConfettiDetail {
|
||||
<div class="playground-workbench">
|
||||
<div class="playground-preview">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
<div class="playground-preview-source" data-playground-preview><Confetti size='{playground_size}' color='{playground_color}' title='{playground_title}' description='{playground_description}' items='{playground_items}' variant='{playground_variant}' class='{playground_class}' /></div>
|
||||
<div class="playground-preview-source" data-playground-preview><Confetti label='{playground_label}' duration='{playground_duration}' count='{playground_count}' autoStart='{playground_autoStart}' disabled='{playground_disabled}' size='{playground_size}' color='{playground_color}' class='{playground_class}' /></div>
|
||||
<section class="playground-code" aria-label="Current component code">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
|
||||
<pre><code data-playground-code><Confetti
|
||||
title="Confetti example"
|
||||
description="A polished default confetti for a modern application."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
label="Confetti"
|
||||
count="3"
|
||||
/></code></pre>
|
||||
</section>
|
||||
<div class="playground-status" data-playground-status aria-live="polite"></div>
|
||||
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
|
||||
</div>
|
||||
<form class="playground-controls" data-playground-form>
|
||||
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
|
||||
<div class="playground-fields"><label class="playground-field" for="playground-confetti-size"><span><strong>size</strong><small>string</small></span><select id="playground-confetti-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-confetti-color"><span><strong>color</strong><small>string</small></span><select id="playground-confetti-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-confetti-title"><span><strong>title</strong><small>string</small></span><input id="playground-confetti-title" name="pg_title" type="text" value="Confetti example" /></label><label class="playground-field" for="playground-confetti-description"><span><strong>description</strong><small>string</small></span><input id="playground-confetti-description" name="pg_description" type="text" value="A polished default confetti for a modern application." /></label><label class="playground-field" for="playground-confetti-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-confetti-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-confetti-variant"><span><strong>variant</strong><small>string</small></span><input id="playground-confetti-variant" name="pg_variant" type="text" value="default" /></label><label class="playground-field" for="playground-confetti-class"><span><strong>class</strong><small>string</small></span><input id="playground-confetti-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
|
||||
<header><div><span>Component props</span><strong>8 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
|
||||
<div class="playground-fields"><label class="playground-field" for="playground-confetti-label"><span><strong>label</strong><small>string</small></span><input id="playground-confetti-label" name="pg_label" type="text" value="Confetti" /></label><label class="playground-field" for="playground-confetti-duration"><span><strong>duration</strong><small>number</small></span><input id="playground-confetti-duration" name="pg_duration" type="number" value="1200" /></label><label class="playground-field" for="playground-confetti-count"><span><strong>count</strong><small>number</small></span><input id="playground-confetti-count" name="pg_count" type="number" value="3" /></label><label class="playground-toggle" for="playground-confetti-autoStart"><span><strong>auto Start</strong><small>boolean</small></span><input id="playground-confetti-autoStart" name="pg_autoStart" type="checkbox" value="true" data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-toggle" for="playground-confetti-disabled"><span><strong>disabled</strong><small>boolean</small></span><input id="playground-confetti-disabled" name="pg_disabled" type="checkbox" value="true" data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-field" for="playground-confetti-size"><span><strong>size</strong><small>string</small></span><select id="playground-confetti-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-confetti-color"><span><strong>color</strong><small>string</small></span><select id="playground-confetti-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-confetti-class"><span><strong>class</strong><small>string</small></span><input id="playground-confetti-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
@@ -274,121 +60,13 @@ page ConfettiDetail {
|
||||
<div id="confetti-demo-1" class="demo-canvas demo-canvas--1">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><Confetti size="default" title="Confetti example" description="A polished default confetti for a modern application." items='[{"id":"confetti-0-1","key":"confetti-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#confetti-demo","actionHref":"#confetti-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"confetti-0-2","key":"confetti-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#confetti-demo","actionHref":"#confetti-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
|
||||
<div class="demo-render"><Confetti label="Confetti" duration="1200" count="3" autoStart="false" disabled="false" size="default" class="showcase-instance showcase-instance--1" /></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Production default usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
<pre><code><Confetti
|
||||
title="Confetti example"
|
||||
description="A polished default confetti for a modern application."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
label="Confetti"
|
||||
count="3"
|
||||
/></code></pre>
|
||||
</section>
|
||||
</div>
|
||||
@@ -402,123 +80,14 @@ page ConfettiDetail {
|
||||
<div id="confetti-demo-2" class="demo-canvas demo-canvas--2">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><Confetti size="sm" title="Compact Confetti" description="A compact configuration for dashboards and dense product surfaces." items='[{"id":"confetti-1-1","key":"confetti-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#confetti-demo","actionHref":"#confetti-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"confetti-1-2","key":"confetti-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#confetti-demo","actionHref":"#confetti-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="secondary" class="showcase-instance showcase-instance--2" /></div>
|
||||
<div class="demo-render"><Confetti label="Compact example" duration="1200" count="7" autoStart="false" disabled="false" size="sm" class="showcase-instance showcase-instance--2" /></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Compact application usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
<pre><code><Confetti
|
||||
label="Compact example"
|
||||
count="7"
|
||||
size="sm"
|
||||
title="Compact Confetti"
|
||||
description="A compact configuration for dashboards and dense product surfaces."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
variant="secondary"
|
||||
/></code></pre>
|
||||
</section>
|
||||
</div>
|
||||
@@ -532,123 +101,14 @@ page ConfettiDetail {
|
||||
<div id="confetti-demo-3" class="demo-canvas demo-canvas--3">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><Confetti size="lg" title="Advanced Confetti" description="A richer configuration demonstrating additional content and hierarchy." items='[{"id":"confetti-2-1","key":"confetti-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#confetti-demo","actionHref":"#confetti-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"confetti-2-2","key":"confetti-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#confetti-demo","actionHref":"#confetti-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' variant="success" class="showcase-instance showcase-instance--3" /></div>
|
||||
<div class="demo-render"><Confetti label="Advanced example" duration="1200" count="11" autoStart="false" disabled="false" size="lg" class="showcase-instance showcase-instance--3" /></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Rich configuration usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
<pre><code><Confetti
|
||||
label="Advanced example"
|
||||
count="11"
|
||||
size="lg"
|
||||
title="Advanced Confetti"
|
||||
description="A richer configuration demonstrating additional content and hierarchy."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Unlimited"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Unlimited"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
variant="success"
|
||||
/></code></pre>
|
||||
</section>
|
||||
</div>
|
||||
@@ -667,7 +127,7 @@ if (component) registerOutputHandler(component, "start", (payload) => <span>&
|
||||
if (component) registerOutputHandler(component, "complete", (payload) => <span>{</span>
|
||||
console.log("complete", payload)
|
||||
<span>}</span>)</code></pre></section></div></div></section>
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Confetti"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>label</code></td><td>string</td><td><code>"Celebrate"</code></td><td>No</td></tr><tr><td><code>duration</code></td><td>number</td><td><code>1200</code></td><td>No</td></tr><tr><td><code>count</code></td><td>number</td><td><code>24</code></td><td>No</td></tr><tr><td><code>autoStart</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>disabled</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
</main>
|
||||
<aside class="detail-aside">
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#confetti-demo-1">Production default</a><a href="#confetti-demo-2">Compact application</a><a href="#confetti-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
// Generated by scripts/generate-showcase.mjs. Do not edit directly.
|
||||
page CopyMarkupDetail {
|
||||
layout = "showcase"
|
||||
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
|
||||
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
|
||||
state playground_label = ctx.url.searchParams.get("pg_label") ?? "Copy Markup"
|
||||
state playground_name = ctx.url.searchParams.get("pg_name") ?? ""
|
||||
state playground_value = ctx.url.searchParams.get("pg_value") ?? "sample-1"
|
||||
state playground_placeholder = ctx.url.searchParams.get("pg_placeholder") ?? "Enter a sample value"
|
||||
state playground_type = ctx.url.searchParams.get("pg_type") ?? "text"
|
||||
state playground_min = ctx.url.searchParams.get("pg_min") ?? ""
|
||||
state playground_max = ctx.url.searchParams.get("pg_max") ?? ""
|
||||
state playground_step = ctx.url.searchParams.get("pg_step") ?? ""
|
||||
state playground_disabled = (ctx.url.searchParams.get("pg_disabled") ?? "false") === "true"
|
||||
state playground_required = (ctx.url.searchParams.get("pg_required") ?? "false") === "true"
|
||||
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
|
||||
seo {
|
||||
title = "Copy Markup"
|
||||
description = "Theme-aware, responsive copy markup component."
|
||||
}
|
||||
view {
|
||||
<nav class="docs-breadcrumbs" aria-label="Breadcrumb">
|
||||
<a href="/">Components</a><span aria-hidden="true">/</span><a href="/advanced-forms">Advanced Forms</a><span aria-hidden="true">/</span><span>Copy Markup</span>
|
||||
</nav>
|
||||
<header class="detail-hero">
|
||||
<div class="detail-hero-copy">
|
||||
<span class="showcase-eyebrow">Advanced Forms component</span>
|
||||
<h1>Copy Markup</h1>
|
||||
<p>Theme-aware, responsive copy markup component.</p>
|
||||
<div class="detail-badges"><span>13 props</span><span>0 slots</span><span>3 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
</div>
|
||||
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
|
||||
</header>
|
||||
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="CopyMarkup" data-playground-public-tag="true" data-playground-has-slot="false" data-playground-events="copy,success,error">
|
||||
<div class="detail-section-heading"><span>Interactive playground</span><h2>Configure Copy Markup</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div>
|
||||
<div class="playground-workbench">
|
||||
<div class="playground-preview">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
<div class="playground-preview-source" data-playground-preview><CopyMarkup size='{playground_size}' color='{playground_color}' label='{playground_label}' name='{playground_name}' value='{playground_value}' placeholder='{playground_placeholder}' type='{playground_type}' min='{playground_min}' max='{playground_max}' step='{playground_step}' disabled='{playground_disabled}' required='{playground_required}' class='{playground_class}' /></div>
|
||||
<section class="playground-code" aria-label="Current component code">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
|
||||
<pre><code data-playground-code><CopyMarkup
|
||||
value="sample-1"
|
||||
placeholder="Enter a sample value"
|
||||
/></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><CopyMarkup
|
||||
value="sample-1"
|
||||
placeholder="Enter a sample value"
|
||||
/></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><CopyMarkup
|
||||
size="sm"
|
||||
label="Compact example"
|
||||
value="sample-2"
|
||||
placeholder="Enter a sample value"
|
||||
/></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><CopyMarkup
|
||||
size="lg"
|
||||
label="Advanced example"
|
||||
value="sample-3"
|
||||
placeholder="Search by name, email, or identifier"
|
||||
/></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><CopyMarkup
|
||||
@copy='console.log(payload)'
|
||||
@success='console.log(payload)'
|
||||
@error='console.log(payload)'
|
||||
/></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>{</span> registerOutputHandler <span>}</span> from "@wrnexus/csr/outputs"
|
||||
|
||||
const component = document.querySelector("[data-ui-component=\"CopyMarkup\"], [data-component=\"CopyMarkup\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "copy", (payload) => <span>{</span>
|
||||
console.log("copy", payload)
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "success", (payload) => <span>{</span>
|
||||
console.log("success", payload)
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "error", (payload) => <span>{</span>
|
||||
console.log("error", payload)
|
||||
<span>}</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) => <span>
|
||||
</div>
|
||||
<nav class="detail-pagination">
|
||||
<a href="/components/data-map"><small>Previous</small><strong>← Data Map</strong></a>
|
||||
<a href="/components/file-upload"><small>Next</small><strong>File Upload →</strong></a>
|
||||
<a href="/components/map"><small>Next</small><strong>Map →</strong></a>
|
||||
</nav>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,706 +0,0 @@
|
||||
// Generated by scripts/generate-showcase.mjs. Do not edit directly.
|
||||
page FileUploadDetail {
|
||||
layout = "showcase"
|
||||
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
|
||||
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
|
||||
state playground_title = ctx.url.searchParams.get("pg_title") ?? "File Upload example"
|
||||
state playground_description = ctx.url.searchParams.get("pg_description") ?? "A polished default file upload for a modern application."
|
||||
state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"file-upload-0-1\",\"key\":\"file-upload-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#file-upload-demo\",\"actionHref\":\"#file-upload-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"file-upload-0-2\",\"key\":\"file-upload-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#file-upload-demo\",\"actionHref\":\"#file-upload-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]")
|
||||
state playground_variant = ctx.url.searchParams.get("pg_variant") ?? "default"
|
||||
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
|
||||
seo {
|
||||
title = "File Upload"
|
||||
description = "Theme-aware, responsive file upload component."
|
||||
}
|
||||
view {
|
||||
<nav class="docs-breadcrumbs" aria-label="Breadcrumb">
|
||||
<a href="/">Components</a><span aria-hidden="true">/</span><a href="/integrations">Integrations</a><span aria-hidden="true">/</span><span>File Upload</span>
|
||||
</nav>
|
||||
<header class="detail-hero">
|
||||
<div class="detail-hero-copy">
|
||||
<span class="showcase-eyebrow">Integrations component</span>
|
||||
<h1>File Upload</h1>
|
||||
<p>Theme-aware, responsive file upload component.</p>
|
||||
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>7 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
</div>
|
||||
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
|
||||
</header>
|
||||
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="FileUpload" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="select,upload,progress,success,error,cancel,remove">
|
||||
<div class="detail-section-heading"><span>Interactive playground</span><h2>Configure File Upload</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div>
|
||||
<div class="playground-workbench">
|
||||
<div class="playground-preview">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
<div class="playground-preview-source" data-playground-preview><FileUpload size='{playground_size}' color='{playground_color}' title='{playground_title}' description='{playground_description}' items='{playground_items}' variant='{playground_variant}' class='{playground_class}' /></div>
|
||||
<section class="playground-code" aria-label="Current component code">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
|
||||
<pre><code data-playground-code><FileUpload
|
||||
title="File Upload example"
|
||||
description="A polished default file upload for a modern application."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
/></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>[
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]</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><FileUpload
|
||||
title="File Upload example"
|
||||
description="A polished default file upload for a modern application."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
/></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><FileUpload
|
||||
size="sm"
|
||||
title="Compact File Upload"
|
||||
description="A compact configuration for dashboards and dense product surfaces."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
variant="secondary"
|
||||
/></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><FileUpload
|
||||
size="lg"
|
||||
title="Advanced File Upload"
|
||||
description="A richer configuration demonstrating additional content and hierarchy."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Unlimited"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Unlimited"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
variant="success"
|
||||
/></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><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)'
|
||||
/></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>{</span> registerOutputHandler <span>}</span> from "@wrnexus/csr/outputs"
|
||||
|
||||
const component = document.querySelector("[data-ui-component=\"FileUpload\"], [data-component=\"FileUpload\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "select", (payload) => <span>{</span>
|
||||
console.log("select", payload)
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "upload", (payload) => <span>{</span>
|
||||
console.log("upload", payload)
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "progress", (payload) => <span>{</span>
|
||||
console.log("progress", payload)
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "success", (payload) => <span>{</span>
|
||||
console.log("success", payload)
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "error", (payload) => <span>{</span>
|
||||
console.log("error", payload)
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "cancel", (payload) => <span>{</span>
|
||||
console.log("cancel", payload)
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "remove", (payload) => <span>{</span>
|
||||
console.log("remove", payload)
|
||||
<span>}</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) => <sp
|
||||
</aside>
|
||||
</div>
|
||||
<nav class="detail-pagination">
|
||||
<a href="/components/copy-markup"><small>Previous</small><strong>← Copy Markup</strong></a>
|
||||
<a href="/components/combo-box"><small>Previous</small><strong>← Combo Box</strong></a>
|
||||
<a href="/components/pin-input"><small>Next</small><strong>Pin Input →</strong></a>
|
||||
</nav>
|
||||
}
|
||||
|
||||
@@ -21,11 +21,11 @@ page ListDetail {
|
||||
<span class="showcase-eyebrow">Base component</span>
|
||||
<h1>List</h1>
|
||||
<p>Present structured responsive linked or status items with icons, descriptions, actions, and selection events.</p>
|
||||
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>0 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>1 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
</div>
|
||||
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
|
||||
</header>
|
||||
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="List" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="">
|
||||
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="List" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="select">
|
||||
<div class="detail-section-heading"><span>Interactive playground</span><h2>Configure List</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div>
|
||||
<div class="playground-workbench">
|
||||
<div class="playground-preview">
|
||||
@@ -147,7 +147,7 @@ page ListDetail {
|
||||
/></code></pre>
|
||||
</section>
|
||||
<div class="playground-status" data-playground-status aria-live="polite"></div>
|
||||
|
||||
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
|
||||
</div>
|
||||
<form class="playground-controls" data-playground-form>
|
||||
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
|
||||
@@ -374,11 +374,19 @@ page ListDetail {
|
||||
</section>
|
||||
</div>
|
||||
</article></div></section>
|
||||
|
||||
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@select</code><span>Fires when an item, option, card, or result is selected.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code><List
|
||||
@select='console.log(payload)'
|
||||
/></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>{</span> registerOutputHandler <span>}</span> from "@wrnexus/csr/outputs"
|
||||
|
||||
const component = document.querySelector("[data-ui-component=\"List\"], [data-component=\"List\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "select", (payload) => <span>{</span>
|
||||
console.log("select", payload)
|
||||
<span>}</span>)</code></pre></section></div></div></section>
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"List"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
</main>
|
||||
<aside class="detail-aside">
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#list-demo-1">Linked resource list</a><a href="#list-demo-2">Compact status list</a><a href="#list-demo-3">Rich action list</a><a href="#api">Props API</a></div>
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#list-demo-1">Linked resource list</a><a href="#list-demo-2">Compact status list</a><a href="#list-demo-3">Rich action list</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
|
||||
</aside>
|
||||
</div>
|
||||
<nav class="detail-pagination">
|
||||
|
||||
@@ -21,11 +21,11 @@ page MapDetail {
|
||||
<span class="showcase-eyebrow">Integrations component</span>
|
||||
<h1>Map</h1>
|
||||
<p>Present responsive location information and markers with map-ready metadata and movement or marker events.</p>
|
||||
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>0 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>3 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
</div>
|
||||
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
|
||||
</header>
|
||||
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="Map" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="">
|
||||
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="Map" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="markerClick,select,zoom">
|
||||
<div class="detail-section-heading"><span>Interactive playground</span><h2>Configure Map</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div>
|
||||
<div class="playground-workbench">
|
||||
<div class="playground-preview">
|
||||
@@ -147,7 +147,7 @@ page MapDetail {
|
||||
/></code></pre>
|
||||
</section>
|
||||
<div class="playground-status" data-playground-status aria-live="polite"></div>
|
||||
|
||||
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
|
||||
</div>
|
||||
<form class="playground-controls" data-playground-form>
|
||||
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
|
||||
@@ -361,16 +361,34 @@ page MapDetail {
|
||||
</section>
|
||||
</div>
|
||||
</article></div></section>
|
||||
|
||||
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@markerClick</code><span>Fires when the component emits the markerClick event.</span></div><div><code>@select</code><span>Fires when an item, option, card, or result is selected.</span></div><div><code>@zoom</code><span>Fires when the component emits the zoom event.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code><Map
|
||||
@markerClick='console.log(payload)'
|
||||
@select='console.log(payload)'
|
||||
@zoom='console.log(payload)'
|
||||
/></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>{</span> registerOutputHandler <span>}</span> from "@wrnexus/csr/outputs"
|
||||
|
||||
const component = document.querySelector("[data-ui-component=\"Map\"], [data-component=\"Map\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "markerClick", (payload) => <span>{</span>
|
||||
console.log("markerClick", payload)
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "select", (payload) => <span>{</span>
|
||||
console.log("select", payload)
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "zoom", (payload) => <span>{</span>
|
||||
console.log("zoom", payload)
|
||||
<span>}</span>)</code></pre></section></div></div></section>
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Map"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
</main>
|
||||
<aside class="detail-aside">
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#map-demo-1">Station directory map</a><a href="#map-demo-2">Compact location preview</a><a href="#map-demo-3">Operations map</a><a href="#api">Props API</a></div>
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#map-demo-1">Station directory map</a><a href="#map-demo-2">Compact location preview</a><a href="#map-demo-3">Operations map</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
|
||||
</aside>
|
||||
</div>
|
||||
<nav class="detail-pagination">
|
||||
<a href="/components/file-upload"><small>Previous</small><strong>← File Upload</strong></a>
|
||||
<a href="/components/toast-notifications"><small>Next</small><strong>Toast Notifications →</strong></a>
|
||||
<a href="/components/drag-and-drop"><small>Previous</small><strong>← Drag And Drop</strong></a>
|
||||
<a href="/components/wysiwyg-editor"><small>Next</small><strong>Wysiwyg Editor →</strong></a>
|
||||
</nav>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,11 @@ page MarqueeDetail {
|
||||
<span class="showcase-eyebrow">Base component</span>
|
||||
<h1>Marquee</h1>
|
||||
<p>Continuously present responsive labels, partners, notices, or capabilities with pause and resume behavior.</p>
|
||||
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>0 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>2 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
</div>
|
||||
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
|
||||
</header>
|
||||
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="Marquee" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="">
|
||||
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="Marquee" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="pause,resume">
|
||||
<div class="detail-section-heading"><span>Interactive playground</span><h2>Configure Marquee</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div>
|
||||
<div class="playground-workbench">
|
||||
<div class="playground-preview">
|
||||
@@ -147,7 +147,7 @@ page MarqueeDetail {
|
||||
/></code></pre>
|
||||
</section>
|
||||
<div class="playground-status" data-playground-status aria-live="polite"></div>
|
||||
|
||||
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
|
||||
</div>
|
||||
<form class="playground-controls" data-playground-form>
|
||||
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
|
||||
@@ -372,11 +372,24 @@ page MarqueeDetail {
|
||||
</section>
|
||||
</div>
|
||||
</article></div></section>
|
||||
|
||||
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@pause</code><span>Fires when the component emits the pause event.</span></div><div><code>@resume</code><span>Fires when the component emits the resume event.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code><Marquee
|
||||
@pause='console.log(payload)'
|
||||
@resume='console.log(payload)'
|
||||
/></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>{</span> registerOutputHandler <span>}</span> from "@wrnexus/csr/outputs"
|
||||
|
||||
const component = document.querySelector("[data-ui-component=\"Marquee\"], [data-component=\"Marquee\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "pause", (payload) => <span>{</span>
|
||||
console.log("pause", payload)
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "resume", (payload) => <span>{</span>
|
||||
console.log("resume", payload)
|
||||
<span>}</span>)</code></pre></section></div></div></section>
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Marquee"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
</main>
|
||||
<aside class="detail-aside">
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#marquee-demo-1">Partner and capability strip</a><a href="#marquee-demo-2">Compact announcement ticker</a><a href="#marquee-demo-3">Large visual marquee</a><a href="#api">Props API</a></div>
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#marquee-demo-1">Partner and capability strip</a><a href="#marquee-demo-2">Compact announcement ticker</a><a href="#marquee-demo-3">Large visual marquee</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
|
||||
</aside>
|
||||
</div>
|
||||
<nav class="detail-pagination">
|
||||
|
||||
@@ -27,11 +27,11 @@ page SearchBoxDetail {
|
||||
<span class="showcase-eyebrow">Forms component</span>
|
||||
<h1>Search Box</h1>
|
||||
<p>Provide an accessible responsive search field with labels, validation states, sizes, and input or change events.</p>
|
||||
<div class="detail-badges"><span>13 props</span><span>0 slots</span><span>0 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
<div class="detail-badges"><span>13 props</span><span>0 slots</span><span>2 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
</div>
|
||||
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
|
||||
</header>
|
||||
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="SearchBox" data-playground-public-tag="true" data-playground-has-slot="false" data-playground-events="">
|
||||
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="SearchBox" data-playground-public-tag="true" data-playground-has-slot="false" data-playground-events="search,clear">
|
||||
<div class="detail-section-heading"><span>Interactive playground</span><h2>Configure Search Box</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div>
|
||||
<div class="playground-workbench">
|
||||
<div class="playground-preview">
|
||||
@@ -45,7 +45,7 @@ page SearchBoxDetail {
|
||||
/></code></pre>
|
||||
</section>
|
||||
<div class="playground-status" data-playground-status aria-live="polite"></div>
|
||||
|
||||
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
|
||||
</div>
|
||||
<form class="playground-controls" data-playground-form>
|
||||
<header><div><span>Component props</span><strong>13 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
|
||||
@@ -126,11 +126,24 @@ page SearchBoxDetail {
|
||||
</section>
|
||||
</div>
|
||||
</article></div></section>
|
||||
|
||||
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@search</code><span>Fires when the current search query changes.</span></div><div><code>@clear</code><span>Fires after the current value or selection is cleared.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code><SearchBox
|
||||
@search='console.log(payload)'
|
||||
@clear='console.log(payload)'
|
||||
/></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>{</span> registerOutputHandler <span>}</span> from "@wrnexus/csr/outputs"
|
||||
|
||||
const component = document.querySelector("[data-ui-component=\"SearchBox\"], [data-component=\"SearchBox\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "search", (payload) => <span>{</span>
|
||||
console.log("search", payload)
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "clear", (payload) => <span>{</span>
|
||||
console.log("clear", payload)
|
||||
<span>}</span>)</code></pre></section></div></div></section>
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Search Box"</code></td><td>No</td></tr><tr><td><code>name</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>value</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>placeholder</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>type</code></td><td>string</td><td><code>"search"</code></td><td>No</td></tr><tr><td><code>min</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>max</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>step</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>disabled</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>required</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
</main>
|
||||
<aside class="detail-aside">
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#search-box-demo-1">Site search</a><a href="#search-box-demo-2">Compact directory search</a><a href="#search-box-demo-3">Required service lookup</a><a href="#api">Props API</a></div>
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#search-box-demo-1">Site search</a><a href="#search-box-demo-2">Compact directory search</a><a href="#search-box-demo-3">Required service lookup</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
|
||||
</aside>
|
||||
</div>
|
||||
<nav class="detail-pagination">
|
||||
|
||||
@@ -21,11 +21,11 @@ page TimelineDetail {
|
||||
<span class="showcase-eyebrow">Base component</span>
|
||||
<h1>Timeline</h1>
|
||||
<p>Present responsive chronological activity, milestones, or workflow status with rich item metadata.</p>
|
||||
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>0 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>1 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
</div>
|
||||
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
|
||||
</header>
|
||||
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="Timeline" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="">
|
||||
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="Timeline" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="select">
|
||||
<div class="detail-section-heading"><span>Interactive playground</span><h2>Configure Timeline</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div>
|
||||
<div class="playground-workbench">
|
||||
<div class="playground-preview">
|
||||
@@ -147,7 +147,7 @@ page TimelineDetail {
|
||||
/></code></pre>
|
||||
</section>
|
||||
<div class="playground-status" data-playground-status aria-live="polite"></div>
|
||||
|
||||
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
|
||||
</div>
|
||||
<form class="playground-controls" data-playground-form>
|
||||
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
|
||||
@@ -381,16 +381,24 @@ page TimelineDetail {
|
||||
</section>
|
||||
</div>
|
||||
</article></div></section>
|
||||
|
||||
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@select</code><span>Fires when an item, option, card, or result is selected.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code><Timeline
|
||||
@select='console.log(payload)'
|
||||
/></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>{</span> registerOutputHandler <span>}</span> from "@wrnexus/csr/outputs"
|
||||
|
||||
const component = document.querySelector("[data-ui-component=\"Timeline\"], [data-component=\"Timeline\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "select", (payload) => <span>{</span>
|
||||
console.log("select", payload)
|
||||
<span>}</span>)</code></pre></section></div></div></section>
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Timeline"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
</main>
|
||||
<aside class="detail-aside">
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#timeline-demo-1">Application timeline</a><a href="#timeline-demo-2">Compact activity history</a><a href="#timeline-demo-3">Rich milestone timeline</a><a href="#api">Props API</a></div>
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#timeline-demo-1">Application timeline</a><a href="#timeline-demo-2">Compact activity history</a><a href="#timeline-demo-3">Rich milestone timeline</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
|
||||
</aside>
|
||||
</div>
|
||||
<nav class="detail-pagination">
|
||||
<a href="/components/styled-icon"><small>Previous</small><strong>← Styled Icon</strong></a>
|
||||
<a href="/components/toast"><small>Next</small><strong>Toast →</strong></a>
|
||||
<a href="/components/tree-view"><small>Next</small><strong>Tree View →</strong></a>
|
||||
</nav>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,691 +0,0 @@
|
||||
// Generated by scripts/generate-showcase.mjs. Do not edit directly.
|
||||
page ToastNotificationsDetail {
|
||||
layout = "showcase"
|
||||
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
|
||||
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
|
||||
state playground_title = ctx.url.searchParams.get("pg_title") ?? "Toast Notifications example"
|
||||
state playground_description = ctx.url.searchParams.get("pg_description") ?? "A polished default toast notifications for a modern application."
|
||||
state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"toast-notifications-0-1\",\"key\":\"toast-notifications-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#toast-notifications-demo\",\"actionHref\":\"#toast-notifications-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"toast-notifications-0-2\",\"key\":\"toast-notifications-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#toast-notifications-demo\",\"actionHref\":\"#toast-notifications-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]")
|
||||
state playground_variant = ctx.url.searchParams.get("pg_variant") ?? "default"
|
||||
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
|
||||
seo {
|
||||
title = "Toast Notifications"
|
||||
description = "Theme-aware, responsive toast notifications component."
|
||||
}
|
||||
view {
|
||||
<nav class="docs-breadcrumbs" aria-label="Breadcrumb">
|
||||
<a href="/">Components</a><span aria-hidden="true">/</span><a href="/integrations">Integrations</a><span aria-hidden="true">/</span><span>Toast Notifications</span>
|
||||
</nav>
|
||||
<header class="detail-hero">
|
||||
<div class="detail-hero-copy">
|
||||
<span class="showcase-eyebrow">Integrations component</span>
|
||||
<h1>Toast Notifications</h1>
|
||||
<p>Theme-aware, responsive toast notifications component.</p>
|
||||
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>4 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
</div>
|
||||
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
|
||||
</header>
|
||||
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="ToastNotifications" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="add,dismiss,clear,action">
|
||||
<div class="detail-section-heading"><span>Interactive playground</span><h2>Configure Toast Notifications</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div>
|
||||
<div class="playground-workbench">
|
||||
<div class="playground-preview">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
<div class="playground-preview-source" data-playground-preview><ToastNotifications size='{playground_size}' color='{playground_color}' title='{playground_title}' description='{playground_description}' items='{playground_items}' variant='{playground_variant}' class='{playground_class}' /></div>
|
||||
<section class="playground-code" aria-label="Current component code">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
|
||||
<pre><code data-playground-code><ToastNotifications
|
||||
title="Toast Notifications example"
|
||||
description="A polished default toast notifications for a modern application."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
/></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>[
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]</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><ToastNotifications
|
||||
title="Toast Notifications example"
|
||||
description="A polished default toast notifications for a modern application."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
/></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><ToastNotifications
|
||||
size="sm"
|
||||
title="Compact Toast Notifications"
|
||||
description="A compact configuration for dashboards and dense product surfaces."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
variant="secondary"
|
||||
/></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><ToastNotifications
|
||||
size="lg"
|
||||
title="Advanced Toast Notifications"
|
||||
description="A richer configuration demonstrating additional content and hierarchy."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Unlimited"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Unlimited"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
variant="success"
|
||||
/></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><ToastNotifications
|
||||
@add='console.log(payload)'
|
||||
@dismiss='console.log(payload)'
|
||||
@clear='console.log(payload)'
|
||||
@action='console.log(payload)'
|
||||
/></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>{</span> registerOutputHandler <span>}</span> from "@wrnexus/csr/outputs"
|
||||
|
||||
const component = document.querySelector("[data-ui-component=\"ToastNotifications\"], [data-component=\"ToastNotifications\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "add", (payload) => <span>{</span>
|
||||
console.log("add", payload)
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "dismiss", (payload) => <span>{</span>
|
||||
console.log("dismiss", payload)
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "clear", (payload) => <span>{</span>
|
||||
console.log("clear", payload)
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "action", (payload) => <span>{</span>
|
||||
console.log("action", payload)
|
||||
<span>}</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><Toast
|
||||
title="Toast example"
|
||||
description="A polished default toast for a modern application."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
/></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>[
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]</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><Toast
|
||||
title="Toast example"
|
||||
description="A polished default toast for a modern application."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
/></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><Toast
|
||||
size="sm"
|
||||
title="Compact Toast"
|
||||
description="A compact configuration for dashboards and dense product surfaces."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
variant="secondary"
|
||||
/></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><Toast
|
||||
size="lg"
|
||||
title="Advanced Toast"
|
||||
description="A richer configuration demonstrating additional content and hierarchy."
|
||||
items='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Unlimited"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Unlimited"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
variant="success"
|
||||
/></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><Toast
|
||||
@dismiss='console.log(payload)'
|
||||
@action='console.log(payload)'
|
||||
/></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>{</span> registerOutputHandler <span>}</span> from "@wrnexus/csr/outputs"
|
||||
|
||||
const component = document.querySelector("[data-ui-component=\"Toast\"], [data-component=\"Toast\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "dismiss", (payload) => <span>{</span>
|
||||
console.log("dismiss", payload)
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "action", (payload) => <span>{</span>
|
||||
console.log("action", payload)
|
||||
<span>}</span>)</code></pre></section></div></div></section>
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Toast"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
</main>
|
||||
<aside class="detail-aside">
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#toast-demo-1">Production default</a><a href="#toast-demo-2">Compact application</a><a href="#toast-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
|
||||
</aside>
|
||||
</div>
|
||||
<nav class="detail-pagination">
|
||||
<a href="/components/timeline"><small>Previous</small><strong>← Timeline</strong></a>
|
||||
<a href="/components/tree-view"><small>Next</small><strong>Tree View →</strong></a>
|
||||
</nav>
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
// Generated by scripts/generate-showcase.mjs. Do not edit directly.
|
||||
page TreeViewDetail {
|
||||
layout = "showcase"
|
||||
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
|
||||
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
|
||||
state playground_title = ctx.url.searchParams.get("pg_title") ?? "Tree View example"
|
||||
state playground_description = ctx.url.searchParams.get("pg_description") ?? "A polished default tree view for a modern application."
|
||||
state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"tree-view-0-1\",\"key\":\"tree-view-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#tree-view-demo\",\"actionHref\":\"#tree-view-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"tree-view-0-2\",\"key\":\"tree-view-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#tree-view-demo\",\"actionHref\":\"#tree-view-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]")
|
||||
state playground_variant = ctx.url.searchParams.get("pg_variant") ?? "default"
|
||||
state playground_valueKey = ctx.url.searchParams.get("pg_valueKey") ?? "value"
|
||||
state playground_labelKey = ctx.url.searchParams.get("pg_labelKey") ?? "label"
|
||||
state playground_defaultExpanded = JSON.parse(ctx.url.searchParams.get("pg_defaultExpanded") ?? "[{\"id\":\"tree-view-0-1\",\"key\":\"tree-view-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#tree-view-demo\",\"actionHref\":\"#tree-view-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"tree-view-0-2\",\"key\":\"tree-view-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#tree-view-demo\",\"actionHref\":\"#tree-view-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]")
|
||||
state playground_selected = ctx.url.searchParams.get("pg_selected") ?? ""
|
||||
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
|
||||
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
|
||||
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
|
||||
seo {
|
||||
title = "Tree View"
|
||||
@@ -21,7 +24,7 @@ page TreeViewDetail {
|
||||
<span class="showcase-eyebrow">Base component</span>
|
||||
<h1>Tree View</h1>
|
||||
<p>Theme-aware, responsive tree view component.</p>
|
||||
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>4 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
<div class="detail-badges"><span>10 props</span><span>1 slots</span><span>4 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
</div>
|
||||
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
|
||||
</header>
|
||||
@@ -30,7 +33,7 @@ page TreeViewDetail {
|
||||
<div class="playground-workbench">
|
||||
<div class="playground-preview">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
<div class="playground-preview-source" data-playground-preview><TreeView size='{playground_size}' color='{playground_color}' title='{playground_title}' description='{playground_description}' items='{playground_items}' variant='{playground_variant}' class='{playground_class}' /></div>
|
||||
<div class="playground-preview-source" data-playground-preview><TreeView title='{playground_title}' description='{playground_description}' items='{playground_items}' valueKey='{playground_valueKey}' labelKey='{playground_labelKey}' defaultExpanded='{playground_defaultExpanded}' selected='{playground_selected}' size='{playground_size}' color='{playground_color}' class='{playground_class}' /></div>
|
||||
<section class="playground-code" aria-label="Current component code">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
|
||||
<pre><code data-playground-code><TreeView
|
||||
@@ -144,14 +147,122 @@ page TreeViewDetail {
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
defaultExpanded='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
/></code></pre>
|
||||
</section>
|
||||
<div class="playground-status" data-playground-status aria-live="polite"></div>
|
||||
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
|
||||
</div>
|
||||
<form class="playground-controls" data-playground-form>
|
||||
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
|
||||
<div class="playground-fields"><label class="playground-field" for="playground-tree-view-size"><span><strong>size</strong><small>string</small></span><select id="playground-tree-view-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-tree-view-color"><span><strong>color</strong><small>string</small></span><select id="playground-tree-view-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-tree-view-title"><span><strong>title</strong><small>string</small></span><input id="playground-tree-view-title" name="pg_title" type="text" value="Tree View example" /></label><label class="playground-field" for="playground-tree-view-description"><span><strong>description</strong><small>string</small></span><input id="playground-tree-view-description" name="pg_description" type="text" value="A polished default tree view for a modern application." /></label><label class="playground-field" for="playground-tree-view-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-tree-view-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[
|
||||
<header><div><span>Component props</span><strong>10 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
|
||||
<div class="playground-fields"><label class="playground-field" for="playground-tree-view-title"><span><strong>title</strong><small>string</small></span><input id="playground-tree-view-title" name="pg_title" type="text" value="Tree View example" /></label><label class="playground-field" for="playground-tree-view-description"><span><strong>description</strong><small>string</small></span><input id="playground-tree-view-description" name="pg_description" type="text" value="A polished default tree view for a modern application." /></label><label class="playground-field" for="playground-tree-view-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-tree-view-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[
|
||||
{
|
||||
"id": "tree-view-0-1",
|
||||
"key": "tree-view-0-1",
|
||||
@@ -258,7 +369,114 @@ page TreeViewDetail {
|
||||
"Standard"
|
||||
]
|
||||
}
|
||||
]</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>[
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-tree-view-selected"><span><strong>selected</strong><small>string</small></span><input id="playground-tree-view-selected" name="pg_selected" type="text" value="" /></label><label class="playground-field" for="playground-tree-view-size"><span><strong>size</strong><small>string</small></span><select id="playground-tree-view-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-tree-view-color"><span><strong>color</strong><small>string</small></span><select id="playground-tree-view-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-tree-view-class"><span><strong>class</strong><small>string</small></span><input id="playground-tree-view-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
@@ -274,7 +492,7 @@ page TreeViewDetail {
|
||||
<div id="tree-view-demo-1" class="demo-canvas demo-canvas--1">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><TreeView size="default" title="Tree View example" description="A polished default tree view for a modern application." items='[{"id":"tree-view-0-1","key":"tree-view-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"tree-view-0-2","key":"tree-view-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
|
||||
<div class="demo-render"><TreeView title="Tree View example" description="A polished default tree view for a modern application." items='[{"id":"tree-view-0-1","key":"tree-view-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"tree-view-0-2","key":"tree-view-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' defaultExpanded='[{"id":"tree-view-0-1","key":"tree-view-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"tree-view-0-2","key":"tree-view-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' size="default" class="showcase-instance showcase-instance--1" /></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Production default usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
@@ -389,6 +607,114 @@ page TreeViewDetail {
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
defaultExpanded='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
/></code></pre>
|
||||
</section>
|
||||
</div>
|
||||
@@ -402,12 +728,11 @@ page TreeViewDetail {
|
||||
<div id="tree-view-demo-2" class="demo-canvas demo-canvas--2">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><TreeView size="sm" title="Compact Tree View" description="A compact configuration for dashboards and dense product surfaces." items='[{"id":"tree-view-1-1","key":"tree-view-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"tree-view-1-2","key":"tree-view-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="secondary" class="showcase-instance showcase-instance--2" /></div>
|
||||
<div class="demo-render"><TreeView title="Compact Tree View" description="A compact configuration for dashboards and dense product surfaces." items='[{"id":"tree-view-1-1","key":"tree-view-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"tree-view-1-2","key":"tree-view-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' defaultExpanded='[{"id":"tree-view-1-1","key":"tree-view-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"tree-view-1-2","key":"tree-view-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' size="sm" class="showcase-instance showcase-instance--2" /></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Compact application usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
<pre><code><TreeView
|
||||
size="sm"
|
||||
title="Compact Tree View"
|
||||
description="A compact configuration for dashboards and dense product surfaces."
|
||||
items='[
|
||||
@@ -518,7 +843,115 @@ page TreeViewDetail {
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
variant="secondary"
|
||||
defaultExpanded='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Standard"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
size="sm"
|
||||
/></code></pre>
|
||||
</section>
|
||||
</div>
|
||||
@@ -532,12 +965,11 @@ page TreeViewDetail {
|
||||
<div id="tree-view-demo-3" class="demo-canvas demo-canvas--3">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><TreeView size="lg" title="Advanced Tree View" description="A richer configuration demonstrating additional content and hierarchy." items='[{"id":"tree-view-2-1","key":"tree-view-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"tree-view-2-2","key":"tree-view-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' variant="success" class="showcase-instance showcase-instance--3" /></div>
|
||||
<div class="demo-render"><TreeView title="Advanced Tree View" description="A richer configuration demonstrating additional content and hierarchy." items='[{"id":"tree-view-2-1","key":"tree-view-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"tree-view-2-2","key":"tree-view-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' defaultExpanded='[{"id":"tree-view-2-1","key":"tree-view-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"tree-view-2-2","key":"tree-view-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' size="lg" class="showcase-instance showcase-instance--3" /></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Rich configuration usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
<pre><code><TreeView
|
||||
size="lg"
|
||||
title="Advanced Tree View"
|
||||
description="A richer configuration demonstrating additional content and hierarchy."
|
||||
items='[
|
||||
@@ -648,7 +1080,115 @@ page TreeViewDetail {
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
variant="success"
|
||||
defaultExpanded='[
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Unlimited"
|
||||
]
|
||||
<span>}</span>,
|
||||
<span>{</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>{</span>
|
||||
"label": "Nested option A",
|
||||
"value": "nested-a"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Nested option B",
|
||||
"value": "nested-b"
|
||||
<span>}</span>
|
||||
],
|
||||
"links": [
|
||||
<span>{</span>
|
||||
"label": "Documentation",
|
||||
"href": "#documentation"
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "API reference",
|
||||
"href": "#api-reference"
|
||||
<span>}</span>
|
||||
],
|
||||
"values": [
|
||||
"Included",
|
||||
"Unlimited"
|
||||
]
|
||||
<span>}</span>
|
||||
]'
|
||||
size="lg"
|
||||
/></code></pre>
|
||||
</section>
|
||||
</div>
|
||||
@@ -677,14 +1217,14 @@ if (component) registerOutputHandler(component, "expand", (payload) => <span>
|
||||
if (component) registerOutputHandler(component, "collapse", (payload) => <span>{</span>
|
||||
console.log("collapse", payload)
|
||||
<span>}</span>)</code></pre></section></div></div></section>
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Tree View"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>title</code></td><td>string</td><td><code>"Tree View"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>valueKey</code></td><td>string</td><td><code>"value"</code></td><td>No</td></tr><tr><td><code>labelKey</code></td><td>string</td><td><code>"label"</code></td><td>No</td></tr><tr><td><code>defaultExpanded</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>selected</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
</main>
|
||||
<aside class="detail-aside">
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#tree-view-demo-1">Production default</a><a href="#tree-view-demo-2">Compact application</a><a href="#tree-view-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
|
||||
</aside>
|
||||
</div>
|
||||
<nav class="detail-pagination">
|
||||
<a href="/components/toast"><small>Previous</small><strong>← Toast</strong></a>
|
||||
<a href="/components/timeline"><small>Previous</small><strong>← Timeline</strong></a>
|
||||
<span></span>
|
||||
</nav>
|
||||
}
|
||||
|
||||
@@ -684,7 +684,7 @@ if (component) registerOutputHandler(component, "blur", (payload) => <span>&#
|
||||
</aside>
|
||||
</div>
|
||||
<nav class="detail-pagination">
|
||||
<a href="/components/toast-notifications"><small>Previous</small><strong>← Toast Notifications</strong></a>
|
||||
<a href="/components/map"><small>Previous</small><strong>← Map</strong></a>
|
||||
<span></span>
|
||||
</nav>
|
||||
}
|
||||
|
||||
@@ -10,18 +10,18 @@ page ComponentShowcase {
|
||||
<header class="showcase-hero showcase-hero--home">
|
||||
<span class="showcase-eyebrow">The UI foundation for WRNexus</span>
|
||||
<h1 class="showcase-title">Build modern products<br /><em>at framework speed.</em></h1>
|
||||
<p class="showcase-description">A complete, accessible component system with 108 primitives, ready-made blocks, page templates, responsive behavior, and deeply configurable design tokens.</p>
|
||||
<p class="showcase-description">A complete, accessible component system with 102 primitives, ready-made blocks, page templates, responsive behavior, and deeply configurable design tokens.</p>
|
||||
<div class="home-actions"><a class="home-primary-action" href="/docs">Get started <span aria-hidden="true">→</span></a><a class="home-secondary-action" href="/block-library">Explore blocks</a></div>
|
||||
<div class="home-install"><span>$</span><code>bun add @wrnexus/ui</code><button type="button" data-copy-text="bun add @wrnexus/ui" aria-label="Copy install command"><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span></button></div>
|
||||
</header>
|
||||
<section class="home-product-grid"><article><span class="icon-[lucide--component] size-7"></span><strong>108 components</strong><p>Accessible primitives for every product surface.</p><a href="/base">Browse components →</a></article><article><span class="icon-[lucide--layout-template] size-7"></span><strong>Ready-made blocks</strong><p>Composable sections assembled from WRNexus UI.</p><a href="/block-library">Explore blocks →</a></article><article><span class="icon-[lucide--panels-top-left] size-7"></span><strong>Page templates</strong><p>Complete responsive pages ready to customize.</p><a href="/templates">View templates →</a></article></section>
|
||||
<section class="home-product-grid"><article><span class="icon-[lucide--component] size-7"></span><strong>102 components</strong><p>Accessible primitives for every product surface.</p><a href="/base">Browse components →</a></article><article><span class="icon-[lucide--layout-template] size-7"></span><strong>Ready-made blocks</strong><p>Composable sections assembled from WRNexus UI.</p><a href="/block-library">Explore blocks →</a></article><article><span class="icon-[lucide--panels-top-left] size-7"></span><strong>Page templates</strong><p>Complete responsive pages ready to customize.</p><a href="/templates">View templates →</a></article></section>
|
||||
<section class="showcase-stats">
|
||||
<div class="showcase-stat"><strong>108</strong><span>Unique components</span></div>
|
||||
<div class="showcase-stat"><strong>419</strong><span>Live configurations</span></div>
|
||||
<div class="showcase-stat"><strong>102</strong><span>Unique components</span></div>
|
||||
<div class="showcase-stat"><strong>401</strong><span>Live configurations</span></div>
|
||||
<div class="showcase-stat"><strong>0</strong><span>Duplicate implementations</span></div>
|
||||
<div class="showcase-stat"><strong>11</strong><span>Focused categories</span></div>
|
||||
</section>
|
||||
<section class="home-categories"><div class="home-section-heading"><span>Explore the system</span><h2>Everything your product needs</h2></div><div class="home-category-grid"><a class="home-category home-category--1" href="/advanced-forms"><span class="home-category-index">01</span><div><strong>Advanced Forms</strong><p>8 components · 112 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--2" href="/base"><span class="home-category-index">02</span><div><strong>Base</strong><p>27 components · 86 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--3" href="/core"><span class="home-category-index">03</span><div><strong>Core</strong><p>5 components · 13 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--4" href="/data"><span class="home-category-index">04</span><div><strong>Data</strong><p>3 components · 9 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--1" href="/forms"><span class="home-category-index">05</span><div><strong>Forms</strong><p>12 components · 36 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--2" href="/integrations"><span class="home-category-index">06</span><div><strong>Integrations</strong><p>11 components · 33 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--3" href="/layout"><span class="home-category-index">07</span><div><strong>Layout</strong><p>15 components · 43 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--4" href="/marketing"><span class="home-category-index">08</span><div><strong>Marketing</strong><p>10 components · 30 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--1" href="/navigation"><span class="home-category-index">09</span><div><strong>Navigation</strong><p>10 components · 30 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--2" href="/overlays"><span class="home-category-index">010</span><div><strong>Overlays</strong><p>6 components · 18 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--3" href="/tables"><span class="home-category-index">011</span><div><strong>Tables</strong><p>1 components · 9 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a></div></section>
|
||||
<section class="home-categories"><div class="home-section-heading"><span>Explore the system</span><h2>Everything your product needs</h2></div><div class="home-category-grid"><a class="home-category home-category--1" href="/advanced-forms"><span class="home-category-index">01</span><div><strong>Advanced Forms</strong><p>7 components · 109 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--2" href="/base"><span class="home-category-index">02</span><div><strong>Base</strong><p>26 components · 83 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--3" href="/core"><span class="home-category-index">03</span><div><strong>Core</strong><p>5 components · 13 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--4" href="/data"><span class="home-category-index">04</span><div><strong>Data</strong><p>3 components · 9 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--1" href="/forms"><span class="home-category-index">05</span><div><strong>Forms</strong><p>12 components · 36 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--2" href="/integrations"><span class="home-category-index">06</span><div><strong>Integrations</strong><p>7 components · 21 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--3" href="/layout"><span class="home-category-index">07</span><div><strong>Layout</strong><p>15 components · 43 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--4" href="/marketing"><span class="home-category-index">08</span><div><strong>Marketing</strong><p>10 components · 30 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--1" href="/navigation"><span class="home-category-index">09</span><div><strong>Navigation</strong><p>10 components · 30 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--2" href="/overlays"><span class="home-category-index">010</span><div><strong>Overlays</strong><p>6 components · 18 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a><a class="home-category home-category--3" href="/tables"><span class="home-category-index">011</span><div><strong>Tables</strong><p>1 components · 9 live demos</p></div><span class="icon-[lucide--arrow-up-right] size-5" aria-hidden="true"></span></a></div></section>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,64 +3,44 @@ page IntegrationsShowcase {
|
||||
layout = "showcase"
|
||||
seo {
|
||||
title = "Integrations components"
|
||||
description = "11 integrations components from @wrnexus/ui."
|
||||
description = "7 integrations components from @wrnexus/ui."
|
||||
}
|
||||
view {
|
||||
<header class="showcase-hero showcase-hero--category">
|
||||
<span class="showcase-eyebrow">Component category</span>
|
||||
<h1 class="showcase-title">Integrations</h1>
|
||||
<p class="showcase-description">11 unique, responsive, theme-aware components. Open a component to inspect multiple live configurations and its complete props API.</p>
|
||||
<div class="category-meta"><span>11 components</span><span>Multiple use cases</span><span>33 live configurations</span></div>
|
||||
<p class="showcase-description">7 unique, responsive, theme-aware components. Open a component to inspect multiple live configurations and its complete props API.</p>
|
||||
<div class="category-meta"><span>7 components</span><span>Multiple use cases</span><span>21 live configurations</span></div>
|
||||
</header>
|
||||
<div class="catalog-grid">
|
||||
<article class="catalog-card" data-interactive-preview="false">
|
||||
<div class="catalog-card-preview">
|
||||
<div class="catalog-card-glow" aria-hidden="true"></div>
|
||||
<div class="catalog-card-stage"><AdvancedDatePicker size="default" title="Advanced Date Picker example" description="A polished default advanced date picker for a modern application." items='[{"id":"advanced-date-picker-0-1","key":"advanced-date-picker-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#advanced-date-picker-demo","actionHref":"#advanced-date-picker-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"advanced-date-picker-0-2","key":"advanced-date-picker-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#advanced-date-picker-demo","actionHref":"#advanced-date-picker-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
|
||||
</div>
|
||||
<div class="catalog-card-body">
|
||||
<div><span class="catalog-card-category">Integrations</span><h2>Advanced Date Picker</h2><p>Theme-aware, responsive advanced date picker component.</p></div>
|
||||
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/advanced-date-picker">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="catalog-card" data-interactive-preview="false">
|
||||
<div class="catalog-card-preview">
|
||||
<div class="catalog-card-glow" aria-hidden="true"></div>
|
||||
<div class="catalog-card-stage"><AdvancedRangeSlider size="default" title="Advanced Range Slider example" description="A polished default advanced range slider for a modern application." items='[{"id":"advanced-range-slider-0-1","key":"advanced-range-slider-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#advanced-range-slider-demo","actionHref":"#advanced-range-slider-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"advanced-range-slider-0-2","key":"advanced-range-slider-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#advanced-range-slider-demo","actionHref":"#advanced-range-slider-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
|
||||
</div>
|
||||
<div class="catalog-card-body">
|
||||
<div><span class="catalog-card-category">Integrations</span><h2>Advanced Range Slider</h2><p>Theme-aware, responsive advanced range slider component.</p></div>
|
||||
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/advanced-range-slider">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="catalog-card" data-interactive-preview="false">
|
||||
<div class="catalog-card-preview">
|
||||
<div class="catalog-card-glow" aria-hidden="true"></div>
|
||||
<div class="catalog-card-stage"><Chart size="default" title="Chart example" description="A polished default chart for a modern application." items='[{"id":"chart-0-1","key":"chart-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"chart-0-2","key":"chart-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
|
||||
<div class="catalog-card-stage"><Chart title="Chart example" description="A polished default chart for a modern application." items='[{"id":"chart-0-1","key":"chart-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"chart-0-2","key":"chart-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' height="240" showLegend="true" showValues="true" size="default" variant="bar" class="showcase-instance showcase-instance--1" /></div>
|
||||
</div>
|
||||
<div class="catalog-card-body">
|
||||
<div><span class="catalog-card-category">Integrations</span><h2>Chart</h2><p>Theme-aware, responsive chart component.</p></div>
|
||||
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/chart">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
<div class="catalog-card-footer"><span>12 props · 1 slots</span><a href="/components/chart">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="catalog-card" data-interactive-preview="false">
|
||||
<div class="catalog-card-preview">
|
||||
<div class="catalog-card-glow" aria-hidden="true"></div>
|
||||
<div class="catalog-card-stage"><Clipboard size="default" title="Clipboard example" description="A polished default clipboard for a modern application." items='[{"id":"clipboard-0-1","key":"clipboard-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#clipboard-demo","actionHref":"#clipboard-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"clipboard-0-2","key":"clipboard-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#clipboard-demo","actionHref":"#clipboard-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
|
||||
<div class="catalog-card-stage"><Clipboard value="sample-1" label="Clipboard" copiedLabel="Clipboard" errorLabel="Clipboard" title="Clipboard example" description="A polished default clipboard for a modern application." code="true" size="default" disabled="false" class="showcase-instance showcase-instance--1" /></div>
|
||||
</div>
|
||||
<div class="catalog-card-body">
|
||||
<div><span class="catalog-card-category">Integrations</span><h2>Clipboard</h2><p>Theme-aware, responsive clipboard component.</p></div>
|
||||
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/clipboard">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
<div class="catalog-card-footer"><span>11 props · 1 slots</span><a href="/components/clipboard">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="catalog-card" data-interactive-preview="false">
|
||||
<div class="catalog-card-preview">
|
||||
<div class="catalog-card-glow" aria-hidden="true"></div>
|
||||
<div class="catalog-card-stage"><Confetti size="default" title="Confetti example" description="A polished default confetti for a modern application." items='[{"id":"confetti-0-1","key":"confetti-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#confetti-demo","actionHref":"#confetti-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"confetti-0-2","key":"confetti-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#confetti-demo","actionHref":"#confetti-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
|
||||
<div class="catalog-card-stage"><Confetti label="Confetti" duration="1200" count="3" autoStart="false" disabled="false" size="default" class="showcase-instance showcase-instance--1" /></div>
|
||||
</div>
|
||||
<div class="catalog-card-body">
|
||||
<div><span class="catalog-card-category">Integrations</span><h2>Confetti</h2><p>Theme-aware, responsive confetti component.</p></div>
|
||||
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/confetti">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
<div class="catalog-card-footer"><span>8 props · 1 slots</span><a href="/components/confetti">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="catalog-card" data-interactive-preview="false">
|
||||
@@ -83,16 +63,6 @@ page IntegrationsShowcase {
|
||||
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/drag-and-drop">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="catalog-card" data-interactive-preview="false">
|
||||
<div class="catalog-card-preview">
|
||||
<div class="catalog-card-glow" aria-hidden="true"></div>
|
||||
<div class="catalog-card-stage"><FileUpload size="default" title="File Upload example" description="A polished default file upload for a modern application." items='[{"id":"file-upload-0-1","key":"file-upload-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#file-upload-demo","actionHref":"#file-upload-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"file-upload-0-2","key":"file-upload-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#file-upload-demo","actionHref":"#file-upload-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
|
||||
</div>
|
||||
<div class="catalog-card-body">
|
||||
<div><span class="catalog-card-category">Integrations</span><h2>File Upload</h2><p>Theme-aware, responsive file upload component.</p></div>
|
||||
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/file-upload">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="catalog-card" data-interactive-preview="false">
|
||||
<div class="catalog-card-preview">
|
||||
<div class="catalog-card-glow" aria-hidden="true"></div>
|
||||
@@ -103,16 +73,6 @@ page IntegrationsShowcase {
|
||||
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/map">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="catalog-card" data-interactive-preview="false">
|
||||
<div class="catalog-card-preview">
|
||||
<div class="catalog-card-glow" aria-hidden="true"></div>
|
||||
<div class="catalog-card-stage"><ToastNotifications size="default" title="Toast Notifications example" description="A polished default toast notifications for a modern application." items='[{"id":"toast-notifications-0-1","key":"toast-notifications-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#toast-notifications-demo","actionHref":"#toast-notifications-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"toast-notifications-0-2","key":"toast-notifications-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#toast-notifications-demo","actionHref":"#toast-notifications-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
|
||||
</div>
|
||||
<div class="catalog-card-body">
|
||||
<div><span class="catalog-card-category">Integrations</span><h2>Toast Notifications</h2><p>Theme-aware, responsive toast notifications component.</p></div>
|
||||
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/toast-notifications">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="catalog-card" data-interactive-preview="false">
|
||||
<div class="catalog-card-preview">
|
||||
<div class="catalog-card-glow" aria-hidden="true"></div>
|
||||
|
||||
@@ -9,8 +9,6 @@ export interface Routes {
|
||||
"/block-library": Record<string, never>;
|
||||
"/colors": Record<string, never>;
|
||||
"/components/accordion": Record<string, never>;
|
||||
"/components/advanced-date-picker": Record<string, never>;
|
||||
"/components/advanced-range-slider": Record<string, never>;
|
||||
"/components/advanced-select": Record<string, never>;
|
||||
"/components/alert": Record<string, never>;
|
||||
"/components/announcement-bar": Record<string, never>;
|
||||
@@ -37,7 +35,6 @@ export interface Routes {
|
||||
"/components/confetti": Record<string, never>;
|
||||
"/components/container": Record<string, never>;
|
||||
"/components/context-menu": Record<string, never>;
|
||||
"/components/copy-markup": Record<string, never>;
|
||||
"/components/ctasection": Record<string, never>;
|
||||
"/components/custom-scrollbar": Record<string, never>;
|
||||
"/components/data-map": Record<string, never>;
|
||||
@@ -52,7 +49,6 @@ export interface Routes {
|
||||
"/components/feature-grid": Record<string, never>;
|
||||
"/components/feature-icon-card": Record<string, never>;
|
||||
"/components/file-input": Record<string, never>;
|
||||
"/components/file-upload": Record<string, never>;
|
||||
"/components/file-upload-progress": Record<string, never>;
|
||||
"/components/footer": Record<string, never>;
|
||||
"/components/grid": Record<string, never>;
|
||||
@@ -107,8 +103,6 @@ export interface Routes {
|
||||
"/components/textarea": Record<string, never>;
|
||||
"/components/time-picker": Record<string, never>;
|
||||
"/components/timeline": Record<string, never>;
|
||||
"/components/toast": Record<string, never>;
|
||||
"/components/toast-notifications": Record<string, never>;
|
||||
"/components/toaster": Record<string, never>;
|
||||
"/components/toggle-count": Record<string, never>;
|
||||
"/components/toggle-password": Record<string, never>;
|
||||
@@ -152,8 +146,6 @@ export interface RouteNames {
|
||||
"block.library": "/block-library";
|
||||
"colors": "/colors";
|
||||
"components.accordion": "/components/accordion";
|
||||
"components.advanced.date.picker": "/components/advanced-date-picker";
|
||||
"components.advanced.range.slider": "/components/advanced-range-slider";
|
||||
"components.advanced.select": "/components/advanced-select";
|
||||
"components.alert": "/components/alert";
|
||||
"components.announcement.bar": "/components/announcement-bar";
|
||||
@@ -180,7 +172,6 @@ export interface RouteNames {
|
||||
"components.confetti": "/components/confetti";
|
||||
"components.container": "/components/container";
|
||||
"components.context.menu": "/components/context-menu";
|
||||
"components.copy.markup": "/components/copy-markup";
|
||||
"components.ctasection": "/components/ctasection";
|
||||
"components.custom.scrollbar": "/components/custom-scrollbar";
|
||||
"components.data.map": "/components/data-map";
|
||||
@@ -195,7 +186,6 @@ export interface RouteNames {
|
||||
"components.feature.grid": "/components/feature-grid";
|
||||
"components.feature.icon.card": "/components/feature-icon-card";
|
||||
"components.file.input": "/components/file-input";
|
||||
"components.file.upload": "/components/file-upload";
|
||||
"components.file.upload.progress": "/components/file-upload-progress";
|
||||
"components.footer": "/components/footer";
|
||||
"components.grid": "/components/grid";
|
||||
@@ -250,8 +240,6 @@ export interface RouteNames {
|
||||
"components.textarea": "/components/textarea";
|
||||
"components.time.picker": "/components/time-picker";
|
||||
"components.timeline": "/components/timeline";
|
||||
"components.toast": "/components/toast";
|
||||
"components.toast.notifications": "/components/toast-notifications";
|
||||
"components.toaster": "/components/toaster";
|
||||
"components.toggle.count": "/components/toggle-count";
|
||||
"components.toggle.password": "/components/toggle-password";
|
||||
@@ -364,8 +352,6 @@ export function route<N extends RouteName>(
|
||||
"block.library": "/block-library",
|
||||
"colors": "/colors",
|
||||
"components.accordion": "/components/accordion",
|
||||
"components.advanced.date.picker": "/components/advanced-date-picker",
|
||||
"components.advanced.range.slider": "/components/advanced-range-slider",
|
||||
"components.advanced.select": "/components/advanced-select",
|
||||
"components.alert": "/components/alert",
|
||||
"components.announcement.bar": "/components/announcement-bar",
|
||||
@@ -392,7 +378,6 @@ export function route<N extends RouteName>(
|
||||
"components.confetti": "/components/confetti",
|
||||
"components.container": "/components/container",
|
||||
"components.context.menu": "/components/context-menu",
|
||||
"components.copy.markup": "/components/copy-markup",
|
||||
"components.ctasection": "/components/ctasection",
|
||||
"components.custom.scrollbar": "/components/custom-scrollbar",
|
||||
"components.data.map": "/components/data-map",
|
||||
@@ -407,7 +392,6 @@ export function route<N extends RouteName>(
|
||||
"components.feature.grid": "/components/feature-grid",
|
||||
"components.feature.icon.card": "/components/feature-icon-card",
|
||||
"components.file.input": "/components/file-input",
|
||||
"components.file.upload": "/components/file-upload",
|
||||
"components.file.upload.progress": "/components/file-upload-progress",
|
||||
"components.footer": "/components/footer",
|
||||
"components.grid": "/components/grid",
|
||||
@@ -462,8 +446,6 @@ export function route<N extends RouteName>(
|
||||
"components.textarea": "/components/textarea",
|
||||
"components.time.picker": "/components/time-picker",
|
||||
"components.timeline": "/components/timeline",
|
||||
"components.toast": "/components/toast",
|
||||
"components.toast.notifications": "/components/toast-notifications",
|
||||
"components.toaster": "/components/toaster",
|
||||
"components.toggle.count": "/components/toggle-count",
|
||||
"components.toggle.password": "/components/toggle-password",
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"generate:reference": "bun run ../../scripts/generate-ui-component-reference.mjs",
|
||||
"generate:pages": "node scripts/generate-showcase.mjs",
|
||||
"generate": "bun run generate:reference && bun run generate:pages",
|
||||
"generate:pages": "bun run generate:reference && node scripts/generate-showcase.mjs",
|
||||
"generate": "bun run generate:pages",
|
||||
"dev": "bun run generate && bun run ../../packages/cli/src/index.ts dev .",
|
||||
"build": "bun run generate && bun run ../../packages/cli/src/index.ts build .",
|
||||
"typecheck": "tsc --noEmit -p tsconfig.json",
|
||||
|
||||
@@ -2,6 +2,7 @@ import { mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from
|
||||
import { dirname, join } from "node:path";
|
||||
import process from "node:process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import prettier from "prettier";
|
||||
import { eventDescriptionFor, profileFor } from "./showcase-profiles.mjs";
|
||||
|
||||
const exampleRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
@@ -1792,11 +1793,12 @@ const showcaseManifest = {
|
||||
profiled: Boolean(profileFor(component.name)),
|
||||
})),
|
||||
};
|
||||
writeFileSync(
|
||||
join(exampleRoot, "showcase-manifest.json"),
|
||||
`${JSON.stringify(showcaseManifest, null, 2)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
const formattedManifest = await prettier.format(`${JSON.stringify(showcaseManifest, null, 2)}\n`, {
|
||||
parser: "json",
|
||||
printWidth: 100,
|
||||
endOfLine: "lf",
|
||||
});
|
||||
writeFileSync(join(exampleRoot, "showcase-manifest.json"), formattedManifest, "utf8");
|
||||
|
||||
const categoryCards = categoryEntries
|
||||
.map(
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"generatedFrom": "packages/ui/component-reference.json",
|
||||
"componentCount": 108,
|
||||
"componentCount": 102,
|
||||
"categoryCount": 11,
|
||||
"totalDemoCount": 419,
|
||||
"totalDemoCount": 401,
|
||||
"components": [
|
||||
{
|
||||
"name": "Accordion",
|
||||
@@ -16,30 +16,6 @@
|
||||
"events": ["change", "open", "close"],
|
||||
"profiled": false
|
||||
},
|
||||
{
|
||||
"name": "AdvancedDatePicker",
|
||||
"mount": "AdvancedDatePicker",
|
||||
"slug": "advanced-date-picker",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive advanced date picker component.",
|
||||
"demoCount": 3,
|
||||
"propCount": 7,
|
||||
"slots": ["default"],
|
||||
"events": ["input", "change", "open", "close", "clear"],
|
||||
"profiled": false
|
||||
},
|
||||
{
|
||||
"name": "AdvancedRangeSlider",
|
||||
"mount": "AdvancedRangeSlider",
|
||||
"slug": "advanced-range-slider",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive advanced range slider component.",
|
||||
"demoCount": 3,
|
||||
"propCount": 7,
|
||||
"slots": ["default"],
|
||||
"events": ["input", "change", "start", "end"],
|
||||
"profiled": false
|
||||
},
|
||||
{
|
||||
"name": "AdvancedSelect",
|
||||
"mount": "AdvancedSelect",
|
||||
@@ -250,7 +226,7 @@
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive chart component.",
|
||||
"demoCount": 3,
|
||||
"propCount": 7,
|
||||
"propCount": 12,
|
||||
"slots": ["default"],
|
||||
"events": ["select", "dataPointClick", "legendToggle"],
|
||||
"profiled": false
|
||||
@@ -286,7 +262,7 @@
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive clipboard component.",
|
||||
"demoCount": 3,
|
||||
"propCount": 7,
|
||||
"propCount": 11,
|
||||
"slots": ["default"],
|
||||
"events": ["copy", "success", "error"],
|
||||
"profiled": false
|
||||
@@ -346,7 +322,7 @@
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive confetti component.",
|
||||
"demoCount": 3,
|
||||
"propCount": 7,
|
||||
"propCount": 8,
|
||||
"slots": ["default"],
|
||||
"events": ["start", "complete"],
|
||||
"profiled": false
|
||||
@@ -375,18 +351,6 @@
|
||||
"events": ["open", "close", "select", "action"],
|
||||
"profiled": true
|
||||
},
|
||||
{
|
||||
"name": "CopyMarkup",
|
||||
"mount": "CopyMarkup",
|
||||
"slug": "copy-markup",
|
||||
"category": "advanced-forms",
|
||||
"purpose": "Theme-aware, responsive copy markup component.",
|
||||
"demoCount": 3,
|
||||
"propCount": 13,
|
||||
"slots": [],
|
||||
"events": ["copy", "success", "error"],
|
||||
"profiled": false
|
||||
},
|
||||
{
|
||||
"name": "CustomScrollbar",
|
||||
"mount": "CustomScrollbar",
|
||||
@@ -552,18 +516,6 @@
|
||||
"events": ["input", "change", "focus", "blur", "select", "clear", "invalid"],
|
||||
"profiled": false
|
||||
},
|
||||
{
|
||||
"name": "FileUpload",
|
||||
"mount": "FileUpload",
|
||||
"slug": "file-upload",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive file upload component.",
|
||||
"demoCount": 3,
|
||||
"propCount": 7,
|
||||
"slots": ["default"],
|
||||
"events": ["select", "upload", "progress", "success", "error", "cancel", "remove"],
|
||||
"profiled": false
|
||||
},
|
||||
{
|
||||
"name": "FileUploadProgress",
|
||||
"mount": "FileUploadProgress",
|
||||
@@ -729,7 +681,7 @@
|
||||
"demoCount": 3,
|
||||
"propCount": 7,
|
||||
"slots": ["default"],
|
||||
"events": [],
|
||||
"events": ["select"],
|
||||
"profiled": true
|
||||
},
|
||||
{
|
||||
@@ -753,7 +705,7 @@
|
||||
"demoCount": 3,
|
||||
"propCount": 7,
|
||||
"slots": ["default"],
|
||||
"events": [],
|
||||
"events": ["markerClick", "select", "zoom"],
|
||||
"profiled": true
|
||||
},
|
||||
{
|
||||
@@ -777,7 +729,7 @@
|
||||
"demoCount": 3,
|
||||
"propCount": 7,
|
||||
"slots": ["default"],
|
||||
"events": [],
|
||||
"events": ["pause", "resume"],
|
||||
"profiled": true
|
||||
},
|
||||
{
|
||||
@@ -1005,7 +957,7 @@
|
||||
"demoCount": 3,
|
||||
"propCount": 13,
|
||||
"slots": [],
|
||||
"events": [],
|
||||
"events": ["search", "clear"],
|
||||
"profiled": true
|
||||
},
|
||||
{
|
||||
@@ -1209,33 +1161,9 @@
|
||||
"demoCount": 3,
|
||||
"propCount": 7,
|
||||
"slots": ["default"],
|
||||
"events": [],
|
||||
"events": ["select"],
|
||||
"profiled": true
|
||||
},
|
||||
{
|
||||
"name": "Toast",
|
||||
"mount": "Toast",
|
||||
"slug": "toast",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive toast component.",
|
||||
"demoCount": 3,
|
||||
"propCount": 7,
|
||||
"slots": ["default"],
|
||||
"events": ["dismiss", "action"],
|
||||
"profiled": false
|
||||
},
|
||||
{
|
||||
"name": "ToastNotifications",
|
||||
"mount": "ToastNotifications",
|
||||
"slug": "toast-notifications",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive toast notifications component.",
|
||||
"demoCount": 3,
|
||||
"propCount": 7,
|
||||
"slots": ["default"],
|
||||
"events": ["add", "dismiss", "clear", "action"],
|
||||
"profiled": false
|
||||
},
|
||||
{
|
||||
"name": "Toaster",
|
||||
"mount": "Toaster",
|
||||
@@ -1291,7 +1219,7 @@
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive tree view component.",
|
||||
"demoCount": 3,
|
||||
"propCount": 7,
|
||||
"propCount": 10,
|
||||
"slots": ["default"],
|
||||
"events": ["select", "toggle", "expand", "collapse"],
|
||||
"profiled": false
|
||||
|
||||
@@ -2,5 +2,7 @@ node_modules/
|
||||
dist/
|
||||
.wrnexus/
|
||||
**/.wrnexus/
|
||||
.wrnexus-*/
|
||||
**/.wrnexus-*/
|
||||
*.log
|
||||
**/CLAUDE.md
|
||||
|
||||
@@ -53,11 +53,11 @@
|
||||
"@iconify-json/lucide": "^1.2.118",
|
||||
"@iconify/tailwind4": "^1.2.3",
|
||||
"@tailwindcss/cli": "^4.0.0",
|
||||
"@types/bun": "latest",
|
||||
"@types/bun": "^1.3.14",
|
||||
"eslint": "^9.0.0",
|
||||
"prettier": "latest",
|
||||
"prettier": "^3.9.6",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"typescript": "^5.5.0",
|
||||
"typescript-eslint": "latest"
|
||||
"typescript-eslint": "^8.65.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,11 +53,11 @@
|
||||
"@iconify-json/lucide": "^1.2.118",
|
||||
"@iconify/tailwind4": "^1.2.3",
|
||||
"@tailwindcss/cli": "^4.0.0",
|
||||
"@types/bun": "latest",
|
||||
"@types/bun": "^1.3.14",
|
||||
"eslint": "^9.0.0",
|
||||
"prettier": "latest",
|
||||
"prettier": "^3.9.6",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"typescript": "^5.5.0",
|
||||
"typescript-eslint": "latest"
|
||||
"typescript-eslint": "^8.65.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { afterEach, expect, test } from "bun:test";
|
||||
import { implement } from "../../../../../packages/rpc/src/index.ts";
|
||||
import { handleRpcRequest } from "../../../../../packages/dev-server/src/rpc-dispatch.ts";
|
||||
import { catalogService } from "../../../packages/shared/src/index.ts";
|
||||
import catalogServiceImplementation from "../../admin/app/services/catalog.ts";
|
||||
import { GET } from "../app/api/product.ts";
|
||||
|
||||
const secret = process.env.WRNEXUS_RPC_SECRET;
|
||||
@@ -16,23 +15,17 @@ afterEach(() => {
|
||||
else process.env.WRNEXUS_INTERNAL_ORIGINS = origins;
|
||||
});
|
||||
|
||||
function startAdmin(allowed: boolean) {
|
||||
const service = implement(
|
||||
catalogService,
|
||||
{ getProduct: ({ sku }) => ({ sku, name: "WRNexus Starter", priceCents: 4900 }) },
|
||||
{
|
||||
selfApp: "admin",
|
||||
checkPermission: (permission, subject) =>
|
||||
allowed && permission === "catalog:read" && subject?.subjectId === "demo-user",
|
||||
},
|
||||
);
|
||||
function startAdmin() {
|
||||
return Bun.serve({
|
||||
port: 0,
|
||||
hostname: "127.0.0.1",
|
||||
async fetch(request) {
|
||||
return (
|
||||
(await handleRpcRequest(request, new URL(request.url), new Map([["catalog", service]]))) ??
|
||||
new Response("Not found", { status: 404 })
|
||||
(await handleRpcRequest(
|
||||
request,
|
||||
new URL(request.url),
|
||||
new Map([["catalog", catalogServiceImplementation]]),
|
||||
)) ?? new Response("Not found", { status: 404 })
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -41,7 +34,7 @@ function startAdmin(allowed: boolean) {
|
||||
test("web calls admin through private RPC when permitted", async () => {
|
||||
process.env.WRNEXUS_RPC_SECRET = "test-rpc-secret-at-least-32-chars-long";
|
||||
process.env.WRNEXUS_APP_NAME = "web";
|
||||
const server = startAdmin(true);
|
||||
const server = startAdmin();
|
||||
process.env.WRNEXUS_INTERNAL_ORIGINS = JSON.stringify({
|
||||
admin: `http://127.0.0.1:${server.port}`,
|
||||
});
|
||||
@@ -62,7 +55,7 @@ test("web calls admin through private RPC when permitted", async () => {
|
||||
test("web returns 403 when admin denies catalog:read", async () => {
|
||||
process.env.WRNEXUS_RPC_SECRET = "test-rpc-secret-at-least-32-chars-long";
|
||||
process.env.WRNEXUS_APP_NAME = "web";
|
||||
const server = startAdmin(false);
|
||||
const server = startAdmin();
|
||||
process.env.WRNEXUS_INTERNAL_ORIGINS = JSON.stringify({
|
||||
admin: `http://127.0.0.1:${server.port}`,
|
||||
});
|
||||
|
||||
@@ -6,7 +6,17 @@ import tseslint from "typescript-eslint";
|
||||
const tsconfigRootDir = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ["node_modules/**", "dist/**", "**/dist/**", ".wrnexus/**", "**/.wrnexus/**"] },
|
||||
{
|
||||
ignores: [
|
||||
"node_modules/**",
|
||||
"dist/**",
|
||||
"**/dist/**",
|
||||
".wrnexus/**",
|
||||
"**/.wrnexus/**",
|
||||
".wrnexus-*/**",
|
||||
"**/.wrnexus-*/**",
|
||||
],
|
||||
},
|
||||
{ languageOptions: { parserOptions: { tsconfigRootDir } } },
|
||||
js.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
|
||||
@@ -23,10 +23,10 @@
|
||||
"devDependencies": {
|
||||
"@wrnexus/cli": "0.8.6",
|
||||
"@eslint/js": "^9.0.0",
|
||||
"@types/bun": "latest",
|
||||
"@types/bun": "^1.3.14",
|
||||
"eslint": "^9.0.0",
|
||||
"prettier": "latest",
|
||||
"prettier": "^3.9.6",
|
||||
"typescript": "^5.5.0",
|
||||
"typescript-eslint": "latest"
|
||||
"typescript-eslint": "^8.65.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,5 +11,5 @@
|
||||
"allowImportingTsExtensions": true
|
||||
},
|
||||
"include": ["wrnexus.workspace.ts", "packages/**/*.ts"],
|
||||
"exclude": ["node_modules", "dist", "apps"]
|
||||
"exclude": ["node_modules", "dist", "apps", ".wrnexus-*", "**/.wrnexus-*"]
|
||||
}
|
||||
|
||||
+6
-7
@@ -7,6 +7,8 @@
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
"examples/*",
|
||||
"examples/inter-app-api-showcase/apps/*",
|
||||
"examples/inter-app-api-showcase/packages/*",
|
||||
"services/*"
|
||||
],
|
||||
"scripts": {
|
||||
@@ -34,24 +36,21 @@
|
||||
"format": "prettier . --write",
|
||||
"format:check": "prettier . --check",
|
||||
"check": "bun run typecheck && bun run lint && bun run check:component-imports && bun run test && bun run format:check",
|
||||
"verify:0.3": "node scripts/verify-0.3.mjs",
|
||||
"verify:0.4": "node scripts/verify-0.4.mjs",
|
||||
"validate:0.4": "node scripts/validate-0.4.mjs",
|
||||
"auth:dev": "bun run --cwd examples/auth-showcase dev",
|
||||
"auth:check": "bun run --cwd packages/auth check",
|
||||
"validate:auth": "node scripts/validate-auth.mjs",
|
||||
"validate:0.6": "node scripts/validate-0.6.mjs",
|
||||
"validate:0.7": "node scripts/validate-0.7.mjs",
|
||||
"security:framework": "node scripts/security-performance-audit.mjs",
|
||||
"security:asvs": "node scripts/check-security-asvs.mjs",
|
||||
"generate:security-report": "node scripts/security-performance-audit.mjs --write",
|
||||
"check:public-api": "node scripts/check-public-api.mjs",
|
||||
"check:generated-types": "bun run scripts/check-generated-types.ts",
|
||||
"generate:example-types": "bun run packages/cli/src/index.ts generate types examples/basic-app",
|
||||
"generate:public-api": "node scripts/check-public-api.mjs --write",
|
||||
"check:ui-visual": "node scripts/check-ui-visual-contract.mjs",
|
||||
"generate:ui-visual": "node scripts/check-ui-visual-contract.mjs --write",
|
||||
"sbom": "node scripts/generate-sbom.mjs",
|
||||
"benchmark:framework": "node --experimental-transform-types scripts/benchmark-framework.mjs",
|
||||
"check:production": "bun run check:workspace && bun run check:public-api && bun run check:ui-visual && bun run validate:0.8 && bun run security:framework && bun run security:asvs && bun run check:editor-compiler && bun run check:editor-language-server && bun run check:editor-extension && bun run check",
|
||||
"benchmark:framework": "node --experimental-transform-types scripts/benchmark-framework.mjs --write",
|
||||
"check:production": "bun run check:workspace && bun run check:generated-types && bun run check:public-api && bun run check:ui-visual && bun run validate:0.8 && bun run security:framework && bun run security:asvs && bun run check:editor-compiler && bun run check:editor-language-server && bun run check:editor-extension && bun run check && bun run test:examples",
|
||||
"validate:staging": "node --experimental-transform-types scripts/test-package-integrity.mjs",
|
||||
"stage:packages": "bun run scripts/publish-packages.ts",
|
||||
"test:staged-consumers": "node scripts/test-staged-consumers.mjs",
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { Database } from "bun:sqlite";
|
||||
import {
|
||||
RedisCaptchaStore,
|
||||
SqliteCaptchaStore,
|
||||
managedCaptchaProvider,
|
||||
type CaptchaChallengeRecord,
|
||||
type CaptchaResponseTokenRecord,
|
||||
type RedisCaptchaClient,
|
||||
type SqliteDatabaseLike,
|
||||
} from "../src/index.ts";
|
||||
|
||||
const challenge = (id: string, expiresAt = Date.now() + 60_000): CaptchaChallengeRecord => ({
|
||||
id,
|
||||
provider: "self-hosted",
|
||||
type: "text",
|
||||
presentation: "visual",
|
||||
action: "signup",
|
||||
publicChallenge: {
|
||||
id,
|
||||
provider: "self-hosted",
|
||||
type: "text",
|
||||
presentation: "visual",
|
||||
action: "signup",
|
||||
prompt: "Type the text",
|
||||
createdAt: Date.now(),
|
||||
expiresAt,
|
||||
responseField: "wrn-captcha-response",
|
||||
},
|
||||
answerDigest: "digest",
|
||||
answerSalt: "salt",
|
||||
answerKind: "text",
|
||||
caseSensitive: false,
|
||||
attempts: 0,
|
||||
maxAttempts: 3,
|
||||
metadata: {},
|
||||
createdAt: Date.now(),
|
||||
expiresAt,
|
||||
});
|
||||
|
||||
const token = (tokenHash: string): CaptchaResponseTokenRecord => ({
|
||||
tokenHash,
|
||||
provider: "self-hosted",
|
||||
action: "signup",
|
||||
createdAt: Date.now(),
|
||||
expiresAt: Date.now() + 60_000,
|
||||
});
|
||||
|
||||
describe("CAPTCHA durable stores", () => {
|
||||
test("SQLite atomically mutates, consumes, deletes, and collects records", async () => {
|
||||
const db = new Database(":memory:");
|
||||
const store = new SqliteCaptchaStore(db as unknown as SqliteDatabaseLike);
|
||||
await store.createChallenge(challenge("c1"));
|
||||
expect((await store.incrementAttempts("c1", Date.now()))?.attempts).toBe(1);
|
||||
expect(await store.consumeChallenge("c1", Date.now())).toMatchObject({
|
||||
consumedAt: expect.any(Number),
|
||||
});
|
||||
expect(await store.consumeChallenge("c1", Date.now())).toBeUndefined();
|
||||
|
||||
await store.createToken(token("t1"));
|
||||
expect(await store.consumeToken("t1", Date.now())).toMatchObject({
|
||||
consumedAt: expect.any(Number),
|
||||
});
|
||||
expect(await store.consumeToken("t1", Date.now())).toBeUndefined();
|
||||
await store.deleteChallenge("c1");
|
||||
await store.deleteToken("t1");
|
||||
await store.createChallenge(challenge("expired", Date.now() - 1));
|
||||
await store.gc(Date.now());
|
||||
expect(await store.getChallenge("expired")).toBeUndefined();
|
||||
db.close();
|
||||
});
|
||||
|
||||
test("Redis fallback locking preserves one-time consumption without Lua", async () => {
|
||||
const values = new Map<string, string>();
|
||||
const redis: RedisCaptchaClient = {
|
||||
get: (key) => values.get(key) ?? null,
|
||||
set: (key, value) => void values.set(key, value),
|
||||
del: (key) => (values.delete(key) ? 1 : 0),
|
||||
};
|
||||
const store = new RedisCaptchaStore(redis, { prefix: "test:" });
|
||||
await store.createChallenge(challenge("c1"));
|
||||
expect((await store.incrementAttempts("c1", Date.now()))?.attempts).toBe(1);
|
||||
const consumed = await Promise.all([
|
||||
store.consumeChallenge("c1", Date.now()),
|
||||
store.consumeChallenge("c1", Date.now()),
|
||||
]);
|
||||
expect(consumed.filter(Boolean)).toHaveLength(1);
|
||||
|
||||
await store.createToken(token("t1"));
|
||||
const tokens = await Promise.all([
|
||||
store.consumeToken("t1", Date.now()),
|
||||
store.consumeToken("t1", Date.now()),
|
||||
]);
|
||||
expect(tokens.filter(Boolean)).toHaveLength(1);
|
||||
await store.deleteChallenge("c1");
|
||||
await store.deleteToken("t1");
|
||||
expect(await store.getChallenge("c1")).toBeUndefined();
|
||||
});
|
||||
|
||||
test("managed provider separates public creation from secret verification", async () => {
|
||||
const requests: RequestInit[] = [];
|
||||
const provider = managedCaptchaProvider({
|
||||
baseUrl: "https://captcha.test/",
|
||||
siteKey: "site",
|
||||
secretKey: "secret",
|
||||
fetch: (async (_url, init) => {
|
||||
requests.push(init ?? {});
|
||||
return Response.json(
|
||||
requests.length === 1
|
||||
? {
|
||||
id: "c1",
|
||||
provider: "managed",
|
||||
type: "text",
|
||||
presentation: "visual",
|
||||
action: "signup",
|
||||
prompt: "Type",
|
||||
createdAt: 1,
|
||||
expiresAt: 2,
|
||||
responseField: "token",
|
||||
}
|
||||
: { success: true, provider: "managed", action: "signup" },
|
||||
);
|
||||
}) as typeof fetch,
|
||||
});
|
||||
await provider.createChallenge({ action: "signup" });
|
||||
await provider.verify({ action: "signup", providerToken: "answer" });
|
||||
expect(new Headers(requests[0]?.headers).has("authorization")).toBe(false);
|
||||
expect(new Headers(requests[1]?.headers).get("authorization")).toBe("Bearer secret");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { createContext } from "@wrnexus/core";
|
||||
import { v } from "@wrnexus/validation";
|
||||
import {
|
||||
captchaPlugin,
|
||||
defineCaptchaProvider,
|
||||
parseWithCaptcha,
|
||||
type CaptchaProvider,
|
||||
} from "../src/index.ts";
|
||||
import type { PluginContext, TransformContext } from "@wrnexus/plugin";
|
||||
|
||||
describe("CAPTCHA validation and development audit", () => {
|
||||
test("combines schema and action-bound provider verification failures", async () => {
|
||||
const requests: unknown[] = [];
|
||||
const provider: CaptchaProvider = defineCaptchaProvider({
|
||||
name: "fixture",
|
||||
client: { responseField: "captcha-response" },
|
||||
async verify(input) {
|
||||
requests.push(input);
|
||||
return { success: false, provider: "fixture", action: input.action, message: "Try again" };
|
||||
},
|
||||
});
|
||||
const ctx = createContext(
|
||||
new Request("https://app.test/signup"),
|
||||
new URL("https://app.test/signup"),
|
||||
);
|
||||
const result = await parseWithCaptcha(
|
||||
v.object({ email: v.string().email() }),
|
||||
{ email: "invalid", "captcha-response": "token" },
|
||||
ctx,
|
||||
{ action: "signup", provider, bindIp: true },
|
||||
);
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.errors.email).toBeDefined();
|
||||
expect(result.errors["captcha-response"]).toBe("Try again");
|
||||
expect(requests[0]).toEqual(
|
||||
expect.objectContaining({ action: "signup", hostname: "app.test", providerToken: "token" }),
|
||||
);
|
||||
});
|
||||
|
||||
test("rejects incomplete custom providers", () => {
|
||||
expect(() =>
|
||||
defineCaptchaProvider({ name: "", client: { responseField: "x" } } as never),
|
||||
).toThrow("stable name");
|
||||
expect(() =>
|
||||
defineCaptchaProvider({ name: "x", client: { responseField: "" } } as never),
|
||||
).toThrow("responseField");
|
||||
expect(() =>
|
||||
defineCaptchaProvider({ name: "x", client: { responseField: "token" } } as never),
|
||||
).toThrow("verify()");
|
||||
});
|
||||
|
||||
test("flags exposed secrets, missing provider keys, and inaccessible hard challenges", async () => {
|
||||
const metadata = new Map<string, unknown>();
|
||||
const plugin = captchaPlugin();
|
||||
const context = {
|
||||
mode: "development",
|
||||
file: "signup.wrn",
|
||||
metadata,
|
||||
} as TransformContext;
|
||||
await plugin.transformCode?.(
|
||||
`<Captcha provider="turnstile" secretKey="leaked" action="signup" disturbance="90" showAudio="false" />`,
|
||||
context,
|
||||
);
|
||||
const panels = await plugin.devToolbarPanels?.({ metadata } as PluginContext);
|
||||
const ids = panels?.[0]?.issues?.map((issue) => String((issue as { id: string }).id)) ?? [];
|
||||
expect(ids.some((id) => id.startsWith("client-secret:"))).toBe(true);
|
||||
expect(ids.some((id) => id.startsWith("missing-site-key:"))).toBe(true);
|
||||
expect(ids.some((id) => id.startsWith("hard-without-audio:"))).toBe(true);
|
||||
});
|
||||
});
|
||||
+42
-10
@@ -23,7 +23,7 @@ import {
|
||||
} from "node:fs";
|
||||
import { basename, dirname, extname, join, relative, resolve } from "node:path";
|
||||
import { buildRouter, type Route } from "@wrnexus/router";
|
||||
import { getReactiveRuntime } from "@wrnexus/csr";
|
||||
import { getComponentControllerRuntime, getReactiveRuntime } from "@wrnexus/csr";
|
||||
import {
|
||||
analyzeRuntimeImports,
|
||||
analyzeRuntimeRequirements,
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
bundleCss,
|
||||
renderStyles,
|
||||
resolveThemeConfig,
|
||||
renderActiveThemeCss,
|
||||
renderThemeCss,
|
||||
renderThemeRuntime,
|
||||
} from "@wrnexus/styles";
|
||||
@@ -109,6 +110,7 @@ export async function runBuild(appRoot: string): Promise<void> {
|
||||
const compiledDir = join(distDir, "compiled");
|
||||
const clientModulesDir = join(distDir, "client");
|
||||
const reactivePath = join(distDir, "reactive.js");
|
||||
const controllersPath = join(distDir, "controllers.js");
|
||||
const publicDir = join(root, "public");
|
||||
const distPublicDir = join(distDir, "public");
|
||||
const config = await loadAppConfig(root);
|
||||
@@ -147,7 +149,12 @@ export async function runBuild(appRoot: string): Promise<void> {
|
||||
const pluginContributions = await pluginRunner.contributions();
|
||||
const { generateApplicationTypes } = await import("./types.ts");
|
||||
generateApplicationTypes(root, pluginContributions);
|
||||
const componentDirs = [uiComponentsDir(), ...pluginContributions.componentDirs];
|
||||
const bundledUiDir = uiComponentsDir();
|
||||
const componentDirs = [
|
||||
bundledUiDir,
|
||||
join(bundledUiDir, "..", "styles"),
|
||||
...pluginContributions.componentDirs,
|
||||
];
|
||||
await pluginRunner.hook("buildStart");
|
||||
const virtualModules = new Map<string, string>();
|
||||
for (const [index, module] of pluginContributions.virtualModules.entries()) {
|
||||
@@ -477,6 +484,13 @@ export async function runBuild(appRoot: string): Promise<void> {
|
||||
);
|
||||
assetHash.update(reactiveCode);
|
||||
console.log(`✓ Runtime: ${reactivePath}`);
|
||||
const controllerCode = await buildBrowserRuntime(
|
||||
getComponentControllerRuntime(),
|
||||
controllersPath,
|
||||
join(compiledDir, "controllers.entry.js"),
|
||||
);
|
||||
assetHash.update(controllerCode);
|
||||
console.log(`✓ Controllers: ${controllersPath}`);
|
||||
|
||||
// 1a) Theme tokens + client switcher (always emitted; built-in light/dark).
|
||||
const theme = resolveThemeConfig(config.theme);
|
||||
@@ -484,6 +498,19 @@ export async function runBuild(appRoot: string): Promise<void> {
|
||||
const themeJs = renderThemeRuntime(theme);
|
||||
writeFileSync(join(distDir, "theme.css"), themeCss, "utf8");
|
||||
writeFileSync(join(distDir, "theme.js"), themeJs, "utf8");
|
||||
const themeAssetsDir = join(distDir, "theme");
|
||||
for (const themeName of theme.names) {
|
||||
const themeDir = join(themeAssetsDir, encodeURIComponent(themeName));
|
||||
mkdirSync(themeDir, { recursive: true });
|
||||
writeFileSync(join(themeDir, "_.css"), renderActiveThemeCss(theme, themeName), "utf8");
|
||||
for (const accentName of theme.accentNames) {
|
||||
writeFileSync(
|
||||
join(themeDir, `${encodeURIComponent(accentName)}.css`),
|
||||
renderActiveThemeCss(theme, themeName, accentName),
|
||||
"utf8",
|
||||
);
|
||||
}
|
||||
}
|
||||
assetHash.update(themeCss);
|
||||
assetHash.update(themeJs);
|
||||
console.log(`✓ Theme: ${theme.names.length} themes (default: ${theme.default})`);
|
||||
@@ -539,14 +566,17 @@ export async function runBuild(appRoot: string): Promise<void> {
|
||||
},
|
||||
config.styles,
|
||||
);
|
||||
const combinedSource = `${frameworkStyles}\n${css}`;
|
||||
// Theme tokens are request-selected and loaded separately. Keep only the
|
||||
// shared UI primitives with application CSS so the full theme/accent
|
||||
// matrix cannot leak back into the blocking stylesheet.
|
||||
const combinedSource = `${uiStyles}\n${css}`;
|
||||
const combinedInput = join(distDir, ".wrnexus-combined.css");
|
||||
writeFileSync(combinedInput, combinedSource, "utf8");
|
||||
const combinedCss = await bundleCss(combinedInput, "production");
|
||||
rmSync(combinedInput, { force: true });
|
||||
assetHash.update(combinedCss);
|
||||
// One blocking CSS request in production: tokens → UI → application CSS.
|
||||
// The standalone framework.css remains available for apps without global CSS.
|
||||
// One shared blocking request for UI + application CSS. The small active
|
||||
// theme stylesheet is selected from the request cookie and loaded first.
|
||||
writeFileSync(join(distDir, "styles.css"), combinedCss, "utf8");
|
||||
hasStyles = true;
|
||||
if (Buffer.byteLength(combinedCss, "utf8") <= INLINE_CSS_LIMIT_BYTES) {
|
||||
@@ -628,8 +658,8 @@ applyAuthzManifestEarly([${authzSetupEntries}]);
|
||||
const componentsLit = router.components
|
||||
.map((c) => {
|
||||
const v = `c${counter++}`;
|
||||
imports.push(`import * as ${v} from ${JSON.stringify(importPathFor(c.file))};`);
|
||||
return `{ name: ${JSON.stringify(c.name)}, mod: ${v} }`;
|
||||
imports.push(`import { render as ${v} } from ${JSON.stringify(importPathFor(c.file))};`);
|
||||
return `{ name: ${JSON.stringify(c.name)}, mod: { render: ${v} } }`;
|
||||
})
|
||||
.join(", ");
|
||||
console.log(`✓ Components: ${router.components.length}`);
|
||||
@@ -638,8 +668,8 @@ applyAuthzManifestEarly([${authzSetupEntries}]);
|
||||
const layoutsLit = router.layouts
|
||||
.map((l) => {
|
||||
const v = `c${counter++}`;
|
||||
imports.push(`import * as ${v} from ${JSON.stringify(importPathFor(l.file))};`);
|
||||
return `{ name: ${JSON.stringify(l.name)}, mod: ${v} }`;
|
||||
imports.push(`import { render as ${v} } from ${JSON.stringify(importPathFor(l.file))};`);
|
||||
return `{ name: ${JSON.stringify(l.name)}, mod: { render: ${v} } }`;
|
||||
})
|
||||
.join(", ");
|
||||
if (router.layouts.length) console.log(`✓ Layouts: ${router.layouts.length}`);
|
||||
@@ -693,8 +723,10 @@ await createProductionServer(
|
||||
},
|
||||
{
|
||||
reactivePath: join(import.meta.dir, "reactive.js"),
|
||||
controllersPath: join(import.meta.dir, "controllers.js"),
|
||||
clientModulesDir: join(import.meta.dir, "client"),
|
||||
themePath: join(import.meta.dir, "theme.css"),
|
||||
themeAssetsDir: join(import.meta.dir, "theme"),
|
||||
themeJsPath: join(import.meta.dir, "theme.js"),
|
||||
theme: ${JSON.stringify(theme)},
|
||||
uiCssPath: join(import.meta.dir, "ui.css"),
|
||||
@@ -719,7 +751,7 @@ await createProductionServer(
|
||||
realtime: ${config.realtime ? JSON.stringify(config.realtime) : "undefined"},
|
||||
publicDir: join(import.meta.dir, "public"),
|
||||
${hasStyles ? `stylesPath: join(import.meta.dir, "styles.css"),` : ""}
|
||||
${hasStyles ? `stylesIncludeFramework: true,` : ""}
|
||||
${hasStyles ? `stylesIncludeUi: true,` : ""}
|
||||
${inlineStyles ? `inlineStyles: ${JSON.stringify(inlineStyles)},` : ""}
|
||||
assetVersion: ${JSON.stringify(assetVersion)},
|
||||
clientRuntimes: ${JSON.stringify(emittedPluginAssets.runtimes)},
|
||||
|
||||
@@ -11,7 +11,10 @@ export type InspectTarget =
|
||||
|
||||
export function inspectComponent(appRoot: string, requestedName: string): unknown {
|
||||
const root = resolve(appRoot);
|
||||
const router = buildRouter(join(root, "app"), { componentDirs: [uiComponentsDir()] });
|
||||
const bundledUiDir = uiComponentsDir();
|
||||
const router = buildRouter(join(root, "app"), {
|
||||
componentDirs: [bundledUiDir, join(bundledUiDir, "..", "styles")],
|
||||
});
|
||||
const component = router.components.find(
|
||||
(candidate) => candidate.name.toLowerCase() === requestedName.toLowerCase(),
|
||||
);
|
||||
@@ -139,7 +142,11 @@ export async function inspectProject(appRoot: string, target: InspectTarget): Pr
|
||||
database: database ?? "default",
|
||||
}));
|
||||
const router = buildRouter(join(root, "app"), {
|
||||
componentDirs: [uiComponentsDir(), ...contributions.componentDirs],
|
||||
componentDirs: [
|
||||
uiComponentsDir(),
|
||||
join(uiComponentsDir(), "..", "styles"),
|
||||
...contributions.componentDirs,
|
||||
],
|
||||
externalRoutes: contributions.routes,
|
||||
middlewareFiles: contributions.middleware,
|
||||
});
|
||||
|
||||
@@ -75,7 +75,7 @@ function writePluginArtifacts(root: string, contributions?: PluginContributions)
|
||||
});
|
||||
writeFileSync(
|
||||
join(typeDir, "wrnexus.plugins.generated.d.ts"),
|
||||
`// AUTO-GENERATED plugin type aggregation - do not edit.\n${references.join("\n")}\n`,
|
||||
`// AUTO-GENERATED plugin type aggregation - do not edit.\n${references.length > 0 ? `${references.join("\n")}\n` : ""}`,
|
||||
"utf8",
|
||||
);
|
||||
const docsDir = join(root, ".wrnexus", "documentation");
|
||||
@@ -182,7 +182,6 @@ export function generateApplicationTypes(
|
||||
.map((name) => join(root, name))
|
||||
.find(existsSync);
|
||||
const code = `// AUTO-GENERATED by \`wrnexus generate types\` - do not edit.
|
||||
/* eslint-disable @typescript-eslint/no-empty-object-type */
|
||||
declare namespace WRNexusGenerated {
|
||||
type ApiContract<T> = T extends import("@wrnexus/core").DefinedEndpoint<infer I, infer O>
|
||||
? { input: I; output: O }
|
||||
|
||||
@@ -2098,6 +2098,23 @@ const MIGRATIONS: Migration[] = [
|
||||
// start painting where they previously rendered as nothing.
|
||||
},
|
||||
},
|
||||
{
|
||||
version: "0.8.7",
|
||||
id: "0.8.7-ui-component-depth",
|
||||
description:
|
||||
"Removes superseded UI scaffolds and moves the remaining utility-styled components to self-contained wire-* BEM styles.",
|
||||
apply() {
|
||||
// AdvancedDatePicker -> DatePicker; AdvancedRangeSlider -> RangeSlider;
|
||||
// FileUpload -> FileInput + FileUploadProgress (or @wrnexus/uploader);
|
||||
// Toast and ToastNotifications -> Toaster.
|
||||
//
|
||||
// List, InputNumber, Marquee, TextLink, Map, SearchBox and Timeline no
|
||||
// longer render Tailwind utility class names. Application CSS selecting
|
||||
// those internal utilities must move to the component's wire-* BEM
|
||||
// classes or its data-size/data-variant attributes. List now emits
|
||||
// select for non-link items as well as navigation items.
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/** Release tooling uses this to require an explicit migration entry per version. */
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { afterAll, expect, test } from "bun:test";
|
||||
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { runBuild } from "../src/build.ts";
|
||||
|
||||
const scratchRoot = join(import.meta.dir, ".tmp-css-delivery");
|
||||
mkdirSync(scratchRoot, { recursive: true });
|
||||
afterAll(() => rmSync(scratchRoot, { recursive: true, force: true }));
|
||||
|
||||
test("production ships active themes separately and retains one component style copy", async () => {
|
||||
const root = mkdtempSync(join(scratchRoot, "app-"));
|
||||
mkdirSync(join(root, "app", "pages"), { recursive: true });
|
||||
mkdirSync(join(root, "app", "components"), { recursive: true });
|
||||
mkdirSync(join(root, "app", "styles"), { recursive: true });
|
||||
writeFileSync(join(root, "package.json"), JSON.stringify({ name: "css-delivery-fixture" }));
|
||||
writeFileSync(
|
||||
join(root, "wrnexus.config.ts"),
|
||||
`export default { theme: { default: "dark", palette: "violet" }, styles: { entry: "app/styles/global.css" } };\n`,
|
||||
);
|
||||
writeFileSync(join(root, "app", "styles", "global.css"), ".app-shell{display:block}\n");
|
||||
writeFileSync(
|
||||
join(root, "app", "components", "Probe.wrn"),
|
||||
`component Probe { style { .unique-server-style-marker{color:red} } view { <p class="unique-server-style-marker">Probe</p> } }\n`,
|
||||
);
|
||||
writeFileSync(
|
||||
join(root, "app", "pages", "index.wrn"),
|
||||
`import Probe from "../components/Probe.wrn"\npage Home { view { <main class="app-shell"><Probe /></main> } }\n`,
|
||||
);
|
||||
|
||||
await runBuild(root);
|
||||
const styles = readFileSync(join(root, "dist", "styles.css"), "utf8");
|
||||
const activeTheme = readFileSync(join(root, "dist", "theme", "dark", "violet.css"), "utf8");
|
||||
const server = readFileSync(join(root, "dist", "server.js"), "utf8");
|
||||
expect(styles).toContain(".app-shell");
|
||||
expect(styles).not.toContain("[data-theme=");
|
||||
expect(activeTheme.length).toBeLessThan(10_000);
|
||||
expect(server.match(/unique-server-style-marker/g)).toHaveLength(2);
|
||||
});
|
||||
@@ -201,6 +201,7 @@ A file opens with `page <Name>` or `component <Name>` followed by a `{ ... }` bo
|
||||
- `@event name = function` inside `props` — declares a public component event. Emit it from component behavior with `name(detail)` or `$emit("name", detail)`, and consume it with `<Component @name="handler(event)" />`.
|
||||
- `state <ident>: Type = <expr>` — typed reactive state seeded from a raw JS expression, including native array and object literals. The annotation is optional for backward compatibility.
|
||||
- `view { <html> }` — plain HTML with `{expr}` interpolation in text and attributes, JSX-style component props such as `items={items}`, `items={[...]}`, and `options={{...}}`, hyphenated attributes, boolean attributes, `@event="..."` client bindings, and `<!-- comments -->`. Structured component props are serialized safely for SSR; expressions that reference `state` retain their initial value and update reactively in the browser.
|
||||
- Client functions automatically commit state changed by `setTimeout` callbacks. For other deferred callbacks (observers, third-party APIs, or detached promise callbacks), call the injected `commit()` function after changing local state; returning/awaiting a promise also commits through the normal function boundary.
|
||||
- `seo { key = "value" ... }` — metadata merged into the generated `meta`.
|
||||
- `style { <raw css> }` — inlined page/component stylesheet (repeatable).
|
||||
- `functions { <TypeScript> }` — helpers with typed parameters and return values. Types remain in server output and are safely erased from browser behavior code.
|
||||
|
||||
@@ -174,7 +174,7 @@ export function browserModuleRequired(ast: PageAst): boolean {
|
||||
return functions.length > 0 || selectedBrowserImports(ast, functions).length > 0;
|
||||
}
|
||||
|
||||
function functionEntry(
|
||||
function _functionEntry(
|
||||
ast: PageAst,
|
||||
fn: RuntimeFunctionDecl,
|
||||
availableFunctions: string[],
|
||||
@@ -240,35 +240,50 @@ function functionEntry(
|
||||
const syncStateFromContext = stateNames
|
||||
.map((name) => `${name} = context.state.${name};`)
|
||||
.join(" ");
|
||||
const peerAliases = functionAliases
|
||||
.map((name) => {
|
||||
const call = `context.functions[${JSON.stringify(name)}](...__wrnexusPeerArgs)`;
|
||||
if (!stateNames.length) {
|
||||
return `const ${name} = (...__wrnexusPeerArgs) => ${call};`;
|
||||
}
|
||||
return `const ${name} = (...__wrnexusPeerArgs) => {
|
||||
${syncStateToContext}
|
||||
let __wrnexusPeerResult;
|
||||
const peerAliases = !stateNames.length
|
||||
? functionAliases
|
||||
.map(
|
||||
(name) =>
|
||||
`const ${name} = (...__wrnexusPeerArgs) => context.functions[${JSON.stringify(name)}](...__wrnexusPeerArgs);`,
|
||||
)
|
||||
.join("\n")
|
||||
: `const __wrnexusFlush = () => { ${syncStateToContext} };
|
||||
const __wrnexusRestore = () => { ${syncStateFromContext} };
|
||||
const __wrnexusPeer = (name, args) => {
|
||||
__wrnexusFlush();
|
||||
let result;
|
||||
try {
|
||||
__wrnexusPeerResult = ${call};
|
||||
} catch (__wrnexusPeerError) {
|
||||
${syncStateFromContext}
|
||||
throw __wrnexusPeerError;
|
||||
result = context.functions[name](...args);
|
||||
} catch (error) {
|
||||
__wrnexusRestore();
|
||||
throw error;
|
||||
}
|
||||
if (__wrnexusPeerResult && typeof __wrnexusPeerResult.then === "function") {
|
||||
return Promise.resolve(__wrnexusPeerResult).finally(() => { ${syncStateFromContext} });
|
||||
if (result && typeof result.then === "function") {
|
||||
return Promise.resolve(result).finally(__wrnexusRestore);
|
||||
}
|
||||
${syncStateFromContext}
|
||||
return __wrnexusPeerResult;
|
||||
};`;
|
||||
})
|
||||
.join("\n");
|
||||
__wrnexusRestore();
|
||||
return result;
|
||||
};
|
||||
${functionAliases.map((name) => `const ${name} = (...__wrnexusPeerArgs) => __wrnexusPeer(${JSON.stringify(name)}, __wrnexusPeerArgs);`).join("\n")}`;
|
||||
const copyBack = stateNames
|
||||
.map(
|
||||
(name) =>
|
||||
`if (!Object.is(${name}, __wrnexusInitialState[${JSON.stringify(name)}])) context.state.${name} = ${name};`,
|
||||
)
|
||||
.join("\n");
|
||||
const commitBinding = stateNames.length
|
||||
? `const __wrnexusCommit = () => { ${copyBack} };
|
||||
${!parameterNames.has("commit") && !declaredLocals.has("commit") && !functionAliases.includes("commit") ? "const commit = __wrnexusCommit;" : ""}
|
||||
${
|
||||
!parameterNames.has("setTimeout") &&
|
||||
!declaredLocals.has("setTimeout") &&
|
||||
!functionAliases.includes("setTimeout")
|
||||
? `const setTimeout = (callback, delay, ...args) => globalThis.setTimeout(() => {
|
||||
try { return callback(...args); } finally { __wrnexusCommit(); }
|
||||
}, delay);`
|
||||
: ""
|
||||
}`
|
||||
: "";
|
||||
const body = eraseFunctionTypes(fn.body);
|
||||
const runtimeBindings = [
|
||||
!parameterNames.has("output") ? "const output = context.output;" : "",
|
||||
@@ -282,12 +297,13 @@ function functionEntry(
|
||||
${initialStateSnapshot}
|
||||
${stateAliases}
|
||||
${propAliases}
|
||||
${commitBinding}
|
||||
${peerAliases}
|
||||
${runtimeBindings}
|
||||
try {
|
||||
${body}
|
||||
} finally {
|
||||
${copyBack}
|
||||
${stateNames.length ? "__wrnexusCommit();" : ""}
|
||||
}
|
||||
}`;
|
||||
}
|
||||
@@ -301,21 +317,83 @@ export function generateBrowserModule(ast: PageAst): string {
|
||||
const selectedImports = selectedBrowserImports(ast, functions);
|
||||
const imports = selectedImports.map((entry) => entry.code).join("\n");
|
||||
const importedBindings = [...new Set(selectedImports.flatMap((entry) => entry.bindings))];
|
||||
const sharedState = state.filter((name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name));
|
||||
const sharedProps = ast.props
|
||||
.map((entry) => entry.name)
|
||||
.filter(
|
||||
(name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name) && !sharedState.includes(name),
|
||||
);
|
||||
const callableAliases = functionNames.filter(
|
||||
(name) =>
|
||||
safeIdentifier(name) &&
|
||||
!RUNTIME_BINDINGS.has(name) &&
|
||||
!sharedState.includes(name) &&
|
||||
!sharedProps.includes(name),
|
||||
);
|
||||
const sharedCommit = sharedState.map((name) => `context.state.${name} = ${name};`).join(" ");
|
||||
const sharedRestore = [
|
||||
...sharedState.map((name) => `${name} = context.state.${name};`),
|
||||
...sharedProps.map((name) => `${name} = context.props.${name};`),
|
||||
].join(" ");
|
||||
const hasAuthoredCommit = callableAliases.includes("commit");
|
||||
const hasAuthoredSetTimeout = callableAliases.includes("setTimeout");
|
||||
const implementations = functions
|
||||
.map((fn) => {
|
||||
const parameters = fn.parameters.map((parameter) => parameter.name).join(", ");
|
||||
return `${JSON.stringify(fn.name)}: ${fn.async ? "async " : ""}function(${parameters}) {
|
||||
try { ${eraseFunctionTypes(fn.body)} } finally { __wrnexusCommit(); }
|
||||
}`;
|
||||
})
|
||||
.join(",\n");
|
||||
return `// generated WRNexusJS browser module for ${ast.name}
|
||||
${imports}
|
||||
export const __wrnexusClientFunctions = {
|
||||
${functions.map((fn) => ` ${functionEntry(ast, fn, functionNames)}`).join(",\n")}
|
||||
${functions
|
||||
.map(
|
||||
(fn) =>
|
||||
` ${JSON.stringify(fn.name)}: (context, ...args) => __wrnexusBindings(context)[${JSON.stringify(fn.name)}](...args)`,
|
||||
)
|
||||
.join(",\n")}
|
||||
};
|
||||
export const __wrnexusClientState = ${JSON.stringify(state)};
|
||||
export const __wrnexusOutputs = ${JSON.stringify(ast.outputs)};
|
||||
export const __wrnexusImportedBindings = { ${importedBindings.join(", ")} };
|
||||
export function bindClientScope(context) {
|
||||
function __wrnexusCreateClientFunctions(context) {
|
||||
${sharedState.length ? `let { ${sharedState.join(", ")} } = context.state;` : ""}
|
||||
${sharedProps.length ? `let { ${sharedProps.join(", ")} } = context.props;` : ""}
|
||||
const output = context.output;
|
||||
const server = context.server;
|
||||
const props = context.props;
|
||||
const refs = context.refs;
|
||||
const __wrnexusCommit = () => { ${sharedCommit} };
|
||||
const __wrnexusRestore = () => { ${sharedRestore} };
|
||||
${!hasAuthoredCommit ? "const commit = __wrnexusCommit;" : ""}
|
||||
${
|
||||
!hasAuthoredSetTimeout
|
||||
? `const setTimeout = (callback, delay, ...args) => globalThis.setTimeout(() => {
|
||||
try { return callback(...args); } finally { __wrnexusCommit(); }
|
||||
}, delay);`
|
||||
: ""
|
||||
}
|
||||
const implementations = {
|
||||
${implementations}
|
||||
};
|
||||
${callableAliases.map((name) => `const ${name} = (...args) => implementations[${JSON.stringify(name)}](...args);`).join("\n ")}
|
||||
const functions = {};
|
||||
const scopedContext = { ...context, functions };
|
||||
for (const [name, handler] of Object.entries(__wrnexusClientFunctions)) {
|
||||
functions[name] = (...args) => handler(scopedContext, ...args);
|
||||
for (const name of Object.keys(implementations)) {
|
||||
functions[name] = (...args) => {
|
||||
__wrnexusRestore();
|
||||
return implementations[name](...args);
|
||||
};
|
||||
}
|
||||
return functions;
|
||||
}
|
||||
function __wrnexusBindings(context) {
|
||||
return context.__wrnexusBoundFunctions ||
|
||||
(context.__wrnexusBoundFunctions = __wrnexusCreateClientFunctions(context));
|
||||
}
|
||||
export function bindClientScope(context) {
|
||||
return __wrnexusBindings(context);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -751,16 +751,15 @@ function renderNestedComponentInvocation(
|
||||
const rendered = ` ${attr.name}="${compiledValue}"`;
|
||||
|
||||
if (
|
||||
wholeExpression ||
|
||||
!attr.value.includes("{") ||
|
||||
!exprRefsState(attr.value, ctx.stateNames)
|
||||
(!exprRefsState(attr.value, ctx.stateNames) && !exprRefsState(attr.value, ctx.propNames))
|
||||
) {
|
||||
return rendered;
|
||||
}
|
||||
|
||||
const marker = attrEscape(JSON.stringify([attr.name, attr.value]));
|
||||
|
||||
return rendered + ` data-wrn-bind-${bindIndex++}="${escLit(marker)}"`;
|
||||
return rendered + ` data-wrn-prop-bind-${bindIndex++}="${escLit(marker)}"`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
@@ -883,7 +882,12 @@ function __wrnexusEscapeHtml(value: unknown): string {
|
||||
return String(value).replace(/[&<>"']/g, (ch) => __wrnexusHtmlEscapes[ch] ?? ch);
|
||||
}
|
||||
|
||||
function __wrnexusEvalData(data: unknown, body: string, helpers = "", ctx: any): unknown {
|
||||
type __WrnexusContext = import("@wrnexus/core").Context & {
|
||||
__wrnexusCallApi?: (path: string, method: string) => Promise<unknown>;
|
||||
localStorage?: unknown;
|
||||
};
|
||||
|
||||
function __wrnexusEvalData(data: unknown, body: string, helpers = "", ctx: __WrnexusContext): unknown {
|
||||
const adapters = {
|
||||
cookies: ctx.cookies,
|
||||
session: ctx.session,
|
||||
@@ -914,7 +918,7 @@ function __wrnexusPropAttr(
|
||||
);
|
||||
}
|
||||
|
||||
async function __wrnexusCallApi(path: string, method: string, ctx: any): Promise<unknown> {
|
||||
async function __wrnexusCallApi(path: string, method: string, ctx: __WrnexusContext): Promise<unknown> {
|
||||
if (typeof ctx.__wrnexusCallApi === "function") {
|
||||
return await ctx.__wrnexusCallApi(path, method);
|
||||
}
|
||||
@@ -929,7 +933,7 @@ async function __wrnexusCallApi(path: string, method: string, ctx: any): Promise
|
||||
return type.includes("application/json") ? await res.json() : await res.text();
|
||||
}
|
||||
|
||||
async function __wrnexusRenderSsrBindings(html: string, ctx: any): Promise<string> {
|
||||
async function __wrnexusRenderSsrBindings(html: string, ctx: __WrnexusContext): Promise<string> {
|
||||
for (const binding of __wrnexusSsrBindings) {
|
||||
const data = await __wrnexusCallApi(binding.path, binding.method, ctx);
|
||||
const value = __wrnexusEvalData(data, binding.body, binding.helpers, ctx);
|
||||
@@ -1439,14 +1443,14 @@ export function generate(ast: PageAst): string {
|
||||
out.push(`const __wrnexusSsrBindings = ${JSON.stringify(ssrBindings, null, 2)};`);
|
||||
const decls = loopConsts.length > 0 ? loopConsts.join("\n") + "\n" : "";
|
||||
out.push(
|
||||
`export default async function ${ast.name}(ctx: any) {
|
||||
`export default async function ${ast.name}(ctx: __WrnexusContext) {
|
||||
${storeDeclarations}
|
||||
${serverLoadAliases}
|
||||
${decls}
|
||||
const __state: ${stateType} = ${dynamicStateInitializer};
|
||||
${ssrStateAliases}
|
||||
${ssrComputedAliases}
|
||||
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]]));
|
||||
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)]));
|
||||
const __scopeValue = Object.entries(__hydrationState)
|
||||
.map(([key, value]) => {
|
||||
let encoded: string;
|
||||
@@ -1473,13 +1477,13 @@ export function generate(ast: PageAst): string {
|
||||
);
|
||||
} else {
|
||||
out.push(
|
||||
`export default ${storeBindings.length > 0 ? "async " : ""}function ${ast.name}(ctx: any) {
|
||||
`export default ${storeBindings.length > 0 ? "async " : ""}function ${ast.name}(ctx: import("@wrnexus/core").Context) {
|
||||
${storeDeclarations}
|
||||
${serverLoadAliases}
|
||||
const __state: ${stateType} = ${dynamicStateInitializer};
|
||||
${ssrStateAliases}
|
||||
${ssrComputedAliases}
|
||||
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]]));
|
||||
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)]));
|
||||
const __scopeValue = Object.entries(__hydrationState)
|
||||
.map(([key, value]) => {
|
||||
let encoded: string;
|
||||
@@ -1506,14 +1510,14 @@ export function generate(ast: PageAst): string {
|
||||
|
||||
if (staticShellBody !== undefined) {
|
||||
out.push(
|
||||
`export async function __wrnexusBuildStaticShell(ctx: any = {}) {
|
||||
`export async function __wrnexusBuildStaticShell(ctx: import("@wrnexus/core").Context = {} as import("@wrnexus/core").Context) {
|
||||
${storeDeclarations}
|
||||
${serverLoadAliases}
|
||||
${loopConsts.length > 0 ? loopConsts.join("\n") : ""}
|
||||
const __state: ${stateType} = ${dynamicStateInitializer};
|
||||
${ssrStateAliases}
|
||||
${ssrComputedAliases}
|
||||
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]]));
|
||||
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)]));
|
||||
const __scopeValue = Object.entries(__hydrationState)
|
||||
.map(([key, value]) => {
|
||||
let encoded: string;
|
||||
@@ -1573,7 +1577,7 @@ export function generate(ast: PageAst): string {
|
||||
})
|
||||
.join("\n");
|
||||
const visible = exposed.filter((entry) => entry.name);
|
||||
return `export async function ${exportName}(ctx: any) {
|
||||
return `export async function ${exportName}(ctx: import("@wrnexus/core").Context) {
|
||||
${exposed
|
||||
.filter((entry) => !entry.name)
|
||||
.map((entry) => entry.body)
|
||||
@@ -1600,7 +1604,7 @@ ${
|
||||
);
|
||||
continue;
|
||||
}
|
||||
out.push(`export async function ${action.name}(input: any, ctx: any) {
|
||||
out.push(`export async function ${action.name}(input: InferSchema<typeof ${action.schema}>, ctx: import("@wrnexus/core").Context) {
|
||||
const invalidate = (...tags: string[]) => {
|
||||
const bucket = (ctx.locals.__wrnexusInvalidatedTags ??= []);
|
||||
bucket.push(...tags.flat());
|
||||
@@ -1631,7 +1635,7 @@ ${ast.actions
|
||||
ast.apis.forEach((api, index) => {
|
||||
const name = `__wrnexusApi_${api.method}_${index}`;
|
||||
out.push(`// ${api.method} ${apiRoutePath(api.path)}
|
||||
const ${name} = async (ctx: any) => {${api.body}};`);
|
||||
const ${name} = async (ctx: import("@wrnexus/core").Context) => {${api.body}};`);
|
||||
});
|
||||
|
||||
const entries = ast.apis.map(
|
||||
@@ -1653,7 +1657,7 @@ const ${name} = async (ctx: any) => {${api.body}};`);
|
||||
const handlers = ast.realtimes.flatMap((rt) =>
|
||||
rt.handlers.map((h) => {
|
||||
const params = ["ws", ...h.args].join(", ");
|
||||
return ` ${h.event}(${params}: any) {${h.body}},`;
|
||||
return ` ${h.event}(${params}: Event) {${h.body}},`;
|
||||
}),
|
||||
);
|
||||
out.push(`export const websocket = {\n${handlers.join("\n")}\n};`);
|
||||
@@ -1673,6 +1677,8 @@ const ${name} = async (ctx: any) => {${api.body}};`);
|
||||
interface CompCtx {
|
||||
/** State names — text referencing any of them stays a reactive client mustache. */
|
||||
stateNames: Set<string>;
|
||||
/** Props are signals too, allowing a parent to drive a mounted child. */
|
||||
propNames: Set<string>;
|
||||
/** Component functions can read state and therefore make their callers reactive. */
|
||||
functionNames: Set<string>;
|
||||
/** Rewrite reserved-word prop/state identifiers to their safe const names. */
|
||||
@@ -1848,7 +1854,11 @@ function exprRefsState(expr: string, stateNames: Set<string>): boolean {
|
||||
}
|
||||
|
||||
function exprRefsComponentReactiveValue(expr: string, ctx: CompCtx): boolean {
|
||||
return exprRefsState(expr, ctx.stateNames) || exprRefsState(expr, ctx.functionNames);
|
||||
return (
|
||||
exprRefsState(expr, ctx.stateNames) ||
|
||||
exprRefsState(expr, ctx.propNames) ||
|
||||
exprRefsState(expr, ctx.functionNames)
|
||||
);
|
||||
}
|
||||
|
||||
function viewHasEvents(nodes: ViewNode[]): boolean {
|
||||
@@ -1918,6 +1928,8 @@ function compileText(raw: string, ctx: CompCtx): string {
|
||||
// Loop variable (from data-for): leave a literal client mustache — the
|
||||
// list renderer fills it per item; it has no server-side value.
|
||||
out += escLit(`{${expr}}`);
|
||||
} else if (expr === "content") {
|
||||
out += `\${__wireRaw(${ctx.resolveExpr(expr)})}`;
|
||||
} else if (exprRefsComponentReactiveValue(expr, ctx)) {
|
||||
// State interpolation: bake the initial value AND keep it reactive via a
|
||||
// data-text span, so no-JS clients see the real value and hydration
|
||||
@@ -1926,8 +1938,6 @@ function compileText(raw: string, ctx: CompCtx): string {
|
||||
escLit(`<span data-text="${attrEscape(expr)}">`) +
|
||||
`\${__wireHtml(${ctx.resolveExpr(expr)})}` +
|
||||
escLit(`</span>`);
|
||||
} else if (expr === "content") {
|
||||
out += `\${__wireRaw(${ctx.resolveExpr(expr)})}`;
|
||||
} else {
|
||||
out += `\${__wireHtml(${ctx.resolveExpr(expr)})}`;
|
||||
}
|
||||
@@ -2346,6 +2356,7 @@ function generateComponent(ast: PageAst): string {
|
||||
};
|
||||
const ctx: CompCtx = {
|
||||
stateNames,
|
||||
propNames: new Set(effectiveProps.map((entry) => entry.name)),
|
||||
functionNames: new Set(
|
||||
ast.runtimeFunctions.filter((fn) => fn.runtime !== "server").map((fn) => fn.name),
|
||||
),
|
||||
@@ -2374,14 +2385,14 @@ function generateComponent(ast: PageAst): string {
|
||||
const styles = ast.styles.map((body) => body.trim()).filter(Boolean);
|
||||
const styleTag = escLit(localStyleTag(ast, styles));
|
||||
|
||||
// A component needs a reactive scope only when it has state or event handlers.
|
||||
// Prop-driven text/attributes are baked server-side, so static components ship
|
||||
// no JavaScript at all.
|
||||
// Props remain server-rendered and also become signals so a parent can drive
|
||||
// a mounted child after hydration.
|
||||
const behavior = componentBehavior(ast);
|
||||
|
||||
const needsScope =
|
||||
ast.runtime !== "server" &&
|
||||
(browserStates.length > 0 ||
|
||||
(effectiveProps.length > 0 ||
|
||||
browserStates.length > 0 ||
|
||||
ast.computed.length > 0 ||
|
||||
viewHasEvents(ast.view) ||
|
||||
behavior !== null);
|
||||
@@ -2407,7 +2418,7 @@ function generateComponent(ast: PageAst): string {
|
||||
);
|
||||
}
|
||||
decls.push(
|
||||
` const ${nameRefs.get(prop.name)}: ${prop.valueType ?? "any"} = __coerce(__p[${JSON.stringify(prop.name)}], (${resolveExpr(prop.default)}), ${JSON.stringify(runtimeTypeOf(prop.valueType))});`,
|
||||
` const ${nameRefs.get(prop.name)}: ${prop.valueType ?? "unknown"} = __coerce(__p[${JSON.stringify(prop.name)}], (${resolveExpr(prop.default)}), ${JSON.stringify(runtimeTypeOf(prop.valueType))}, ${JSON.stringify(prop.name)}) as ${prop.valueType ?? "unknown"};`,
|
||||
);
|
||||
}
|
||||
if (!effectiveProps.some((prop) => prop.name === "attrs")) {
|
||||
@@ -2497,7 +2508,7 @@ function generateComponent(ast: PageAst): string {
|
||||
);
|
||||
}
|
||||
|
||||
out.push(`function __coerce(v: any, def: any, declared: string = "unknown"): any {
|
||||
out.push(`function __coerce(v: unknown, def: unknown, declared: string = "unknown", propName: string = "prop"): unknown {
|
||||
if (v === undefined || v === null) {
|
||||
return def;
|
||||
}
|
||||
@@ -2522,14 +2533,14 @@ function generateComponent(ast: PageAst): string {
|
||||
if (typeof v === "string") {
|
||||
try {
|
||||
const parsed = JSON.parse(v);
|
||||
return Array.isArray(parsed) ? parsed : def;
|
||||
if (!Array.isArray(parsed)) throw new TypeError("Expected an array prop '" + propName + "'");
|
||||
return parsed;
|
||||
} catch {
|
||||
if (declared === "array") throw new TypeError("Expected an array prop");
|
||||
return def;
|
||||
throw new TypeError("Expected an array prop '" + propName + "'");
|
||||
}
|
||||
}
|
||||
|
||||
return def;
|
||||
throw new TypeError("Expected an array prop '" + propName + "'");
|
||||
}
|
||||
|
||||
if (declared === "object" || (def !== null && typeof def === "object")) {
|
||||
@@ -2545,23 +2556,19 @@ function generateComponent(ast: PageAst): string {
|
||||
try {
|
||||
const parsed = JSON.parse(v);
|
||||
|
||||
return (
|
||||
parsed !== null &&
|
||||
typeof parsed === "object" &&
|
||||
!Array.isArray(parsed)
|
||||
)
|
||||
? parsed
|
||||
: def;
|
||||
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
||||
throw new TypeError("Expected an object prop '" + propName + "'");
|
||||
}
|
||||
return parsed;
|
||||
} catch {
|
||||
if (declared === "object") throw new TypeError("Expected an object prop");
|
||||
return def;
|
||||
throw new TypeError("Expected an object prop '" + propName + "'");
|
||||
}
|
||||
}
|
||||
|
||||
return def;
|
||||
throw new TypeError("Expected an object prop '" + propName + "'");
|
||||
}
|
||||
|
||||
if (declared === "bigint") return BigInt(v);
|
||||
if (declared === "bigint") return BigInt(v as string | number | bigint | boolean);
|
||||
if (declared === "function" && typeof v !== "function") {
|
||||
throw new TypeError("Expected a function prop");
|
||||
}
|
||||
@@ -2569,15 +2576,15 @@ function generateComponent(ast: PageAst): string {
|
||||
}
|
||||
|
||||
function __restProps(
|
||||
props: Record<string, any>,
|
||||
props: Record<string, unknown>,
|
||||
declared: Set<string>,
|
||||
): Record<string, any> {
|
||||
): Record<string, unknown> {
|
||||
return Object.fromEntries(
|
||||
Object.entries(props).filter(([name]) => !declared.has(name)),
|
||||
);
|
||||
}
|
||||
|
||||
function __wireHtml(v: any): string {
|
||||
function __wireHtml(v: unknown): string {
|
||||
return String(v == null ? "" : v).replace(
|
||||
/[&<>]/g,
|
||||
(c) =>
|
||||
@@ -2589,7 +2596,7 @@ function __wireHtml(v: any): string {
|
||||
);
|
||||
}
|
||||
|
||||
function __wireAttr(v: any): string {
|
||||
function __wireAttr(v: unknown): string {
|
||||
return String(v == null ? "" : v).replace(
|
||||
/[&<>"]/g,
|
||||
(c) =>
|
||||
@@ -2603,7 +2610,7 @@ function __wireAttr(v: any): string {
|
||||
);
|
||||
}
|
||||
|
||||
function __wireBooleanAttr(name: string, value: any): string {
|
||||
function __wireBooleanAttr(name: string, value: unknown): string {
|
||||
return value === true ||
|
||||
value === "true" ||
|
||||
value === "" ||
|
||||
@@ -2614,7 +2621,7 @@ function __wireBooleanAttr(name: string, value: any): string {
|
||||
: "";
|
||||
}
|
||||
|
||||
function __wireSpreadAttrs(value: any): string {
|
||||
function __wireSpreadAttrs(value: unknown): string {
|
||||
if (value === null || typeof value !== "object" || Array.isArray(value)) return "";
|
||||
|
||||
const booleanAttributes = new Set(${JSON.stringify([...HTML_BOOLEAN_ATTRIBUTES])});
|
||||
@@ -2628,10 +2635,12 @@ function __wireSpreadAttrs(value: any): string {
|
||||
lowerName === "style" ||
|
||||
lowerName === "slot" ||
|
||||
lowerName === "data-component" ||
|
||||
// Internal markers must not leak through a spread -- except the
|
||||
// parent's output handlers, whose whole job is to ride from the mount
|
||||
// onto the view root so the mounting scope can bind them there.
|
||||
(lowerName.startsWith("data-wrn") && !lowerName.startsWith("data-wrn-out-"))
|
||||
// Internal markers must not leak through a spread. Parent-owned output
|
||||
// and prop bindings ride from the mount onto the
|
||||
// rendered child root so the mounting scope can bind them there.
|
||||
(lowerName.startsWith("data-wrn") &&
|
||||
!lowerName.startsWith("data-wrn-out-") &&
|
||||
!lowerName.startsWith("data-wrn-prop-bind-"))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
@@ -2648,7 +2657,7 @@ function __wireSpreadAttrs(value: any): string {
|
||||
return attributes.join("");
|
||||
}
|
||||
|
||||
function __wireProp(v: any): string {
|
||||
function __wireProp(v: unknown): string {
|
||||
const value =
|
||||
v !== null && typeof v === "object"
|
||||
? JSON.stringify(v)
|
||||
@@ -2657,18 +2666,18 @@ function __wireProp(v: any): string {
|
||||
return __wireAttr(value);
|
||||
}
|
||||
|
||||
function __wireRaw(v: any): string {
|
||||
function __wireRaw(v: unknown): string {
|
||||
return String(v == null ? "" : v);
|
||||
}`);
|
||||
|
||||
if (hasServerEach) {
|
||||
out.push(`function __wrnexusEncodeLoopLocals(value: Record<string, any>): string {
|
||||
out.push(`function __wrnexusEncodeLoopLocals(value: Record<string, unknown>): string {
|
||||
return __WrnexusBuffer.from(JSON.stringify(value), "utf8").toString("base64");
|
||||
}`);
|
||||
}
|
||||
|
||||
if (needsScope) {
|
||||
out.push(`function __wrnexusSerializeScopeValue(value: any): string {
|
||||
out.push(`function __wrnexusSerializeScopeValue(value: unknown): string {
|
||||
if (value === undefined) {
|
||||
return "undefined";
|
||||
}
|
||||
@@ -2702,7 +2711,7 @@ function __wireRaw(v: any): string {
|
||||
}
|
||||
}
|
||||
|
||||
function __wrnexusScopeDecl(obj: Record<string, any>): string {
|
||||
function __wrnexusScopeDecl(obj: Record<string, unknown>): string {
|
||||
return Object.keys(obj)
|
||||
.map(
|
||||
(key) =>
|
||||
@@ -2723,7 +2732,7 @@ function __wireRaw(v: any): string {
|
||||
const serverFunctionSource = serverFunctions ? `${serverFunctions}\n` : "";
|
||||
|
||||
out.push(
|
||||
`export function render(props: ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, any>"} = {} as ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, any>"}): string {\n` +
|
||||
`export function render(props: ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, unknown>"} = {} as ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, unknown>"}): string {\n` +
|
||||
` const __p = props || {};\n` +
|
||||
(decls.length > 0 ? decls.join("\n") + "\n" : "") +
|
||||
serverFunctionSource +
|
||||
@@ -2738,7 +2747,7 @@ function __wireRaw(v: any): string {
|
||||
return out.join("\n\n") + "\n";
|
||||
}
|
||||
|
||||
function __wireRaw(v: any): string {
|
||||
function __wireRaw(v: unknown): string {
|
||||
return String(v == null ? "" : v);
|
||||
}
|
||||
|
||||
@@ -2747,19 +2756,19 @@ function wholeAttributeExpression(value: string): string | null {
|
||||
|
||||
return match?.[1]?.trim() || null;
|
||||
}
|
||||
function __wireHtml(v: any): string {
|
||||
function __wireHtml(v: unknown): string {
|
||||
return String(v == null ? "" : v).replace(/[&<>]/g, (c) =>
|
||||
c === "&" ? "&" : c === "<" ? "<" : ">",
|
||||
);
|
||||
}
|
||||
|
||||
function __wireAttr(v: any): string {
|
||||
function __wireAttr(v: unknown): string {
|
||||
return String(v == null ? "" : v).replace(/[&<>"]/g, (c) =>
|
||||
c === "&" ? "&" : c === "<" ? "<" : c === ">" ? ">" : """,
|
||||
);
|
||||
}
|
||||
|
||||
function __wireProp(v: any): string {
|
||||
function __wireProp(v: unknown): string {
|
||||
const value =
|
||||
v !== null && typeof v === "object" ? JSON.stringify(v) : String(v == null ? "" : v);
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ export interface CounterOutputs {
|
||||
"change"(value: number): void;
|
||||
}
|
||||
|
||||
function __coerce(v: any, def: any, declared: string = "unknown"): any {
|
||||
function __coerce(v: unknown, def: unknown, declared: string = "unknown", propName: string = "prop"): unknown {
|
||||
if (v === undefined || v === null) {
|
||||
return def;
|
||||
}
|
||||
@@ -70,14 +70,14 @@ function __coerce(v: any, def: any, declared: string = "unknown"): any {
|
||||
if (typeof v === "string") {
|
||||
try {
|
||||
const parsed = JSON.parse(v);
|
||||
return Array.isArray(parsed) ? parsed : def;
|
||||
if (!Array.isArray(parsed)) throw new TypeError("Expected an array prop '" + propName + "'");
|
||||
return parsed;
|
||||
} catch {
|
||||
if (declared === "array") throw new TypeError("Expected an array prop");
|
||||
return def;
|
||||
throw new TypeError("Expected an array prop '" + propName + "'");
|
||||
}
|
||||
}
|
||||
|
||||
return def;
|
||||
throw new TypeError("Expected an array prop '" + propName + "'");
|
||||
}
|
||||
|
||||
if (declared === "object" || (def !== null && typeof def === "object")) {
|
||||
@@ -93,23 +93,19 @@ function __coerce(v: any, def: any, declared: string = "unknown"): any {
|
||||
try {
|
||||
const parsed = JSON.parse(v);
|
||||
|
||||
return (
|
||||
parsed !== null &&
|
||||
typeof parsed === "object" &&
|
||||
!Array.isArray(parsed)
|
||||
)
|
||||
? parsed
|
||||
: def;
|
||||
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
||||
throw new TypeError("Expected an object prop '" + propName + "'");
|
||||
}
|
||||
return parsed;
|
||||
} catch {
|
||||
if (declared === "object") throw new TypeError("Expected an object prop");
|
||||
return def;
|
||||
throw new TypeError("Expected an object prop '" + propName + "'");
|
||||
}
|
||||
}
|
||||
|
||||
return def;
|
||||
throw new TypeError("Expected an object prop '" + propName + "'");
|
||||
}
|
||||
|
||||
if (declared === "bigint") return BigInt(v);
|
||||
if (declared === "bigint") return BigInt(v as string | number | bigint | boolean);
|
||||
if (declared === "function" && typeof v !== "function") {
|
||||
throw new TypeError("Expected a function prop");
|
||||
}
|
||||
@@ -117,15 +113,15 @@ function __coerce(v: any, def: any, declared: string = "unknown"): any {
|
||||
}
|
||||
|
||||
function __restProps(
|
||||
props: Record<string, any>,
|
||||
props: Record<string, unknown>,
|
||||
declared: Set<string>,
|
||||
): Record<string, any> {
|
||||
): Record<string, unknown> {
|
||||
return Object.fromEntries(
|
||||
Object.entries(props).filter(([name]) => !declared.has(name)),
|
||||
);
|
||||
}
|
||||
|
||||
function __wireHtml(v: any): string {
|
||||
function __wireHtml(v: unknown): string {
|
||||
return String(v == null ? "" : v).replace(
|
||||
/[&<>]/g,
|
||||
(c) =>
|
||||
@@ -137,7 +133,7 @@ function __wireHtml(v: any): string {
|
||||
);
|
||||
}
|
||||
|
||||
function __wireAttr(v: any): string {
|
||||
function __wireAttr(v: unknown): string {
|
||||
return String(v == null ? "" : v).replace(
|
||||
/[&<>"]/g,
|
||||
(c) =>
|
||||
@@ -151,7 +147,7 @@ function __wireAttr(v: any): string {
|
||||
);
|
||||
}
|
||||
|
||||
function __wireBooleanAttr(name: string, value: any): string {
|
||||
function __wireBooleanAttr(name: string, value: unknown): string {
|
||||
return value === true ||
|
||||
value === "true" ||
|
||||
value === "" ||
|
||||
@@ -162,7 +158,7 @@ function __wireBooleanAttr(name: string, value: any): string {
|
||||
: "";
|
||||
}
|
||||
|
||||
function __wireSpreadAttrs(value: any): string {
|
||||
function __wireSpreadAttrs(value: unknown): string {
|
||||
if (value === null || typeof value !== "object" || Array.isArray(value)) return "";
|
||||
|
||||
const booleanAttributes = new Set(["allowfullscreen","async","autofocus","autoplay","checked","controls","default","defer","disabled","formnovalidate","hidden","inert","ismap","itemscope","loop","multiple","muted","nomodule","novalidate","open","playsinline","readonly","required","reversed","selected"]);
|
||||
@@ -176,10 +172,12 @@ function __wireSpreadAttrs(value: any): string {
|
||||
lowerName === "style" ||
|
||||
lowerName === "slot" ||
|
||||
lowerName === "data-component" ||
|
||||
// Internal markers must not leak through a spread -- except the
|
||||
// parent's output handlers, whose whole job is to ride from the mount
|
||||
// onto the view root so the mounting scope can bind them there.
|
||||
(lowerName.startsWith("data-wrn") && !lowerName.startsWith("data-wrn-out-"))
|
||||
// Internal markers must not leak through a spread. Parent-owned output
|
||||
// and prop bindings ride from the mount onto the
|
||||
// rendered child root so the mounting scope can bind them there.
|
||||
(lowerName.startsWith("data-wrn") &&
|
||||
!lowerName.startsWith("data-wrn-out-") &&
|
||||
!lowerName.startsWith("data-wrn-prop-bind-"))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
@@ -196,7 +194,7 @@ function __wireSpreadAttrs(value: any): string {
|
||||
return attributes.join("");
|
||||
}
|
||||
|
||||
function __wireProp(v: any): string {
|
||||
function __wireProp(v: unknown): string {
|
||||
const value =
|
||||
v !== null && typeof v === "object"
|
||||
? JSON.stringify(v)
|
||||
@@ -205,11 +203,11 @@ function __wireProp(v: any): string {
|
||||
return __wireAttr(value);
|
||||
}
|
||||
|
||||
function __wireRaw(v: any): string {
|
||||
function __wireRaw(v: unknown): string {
|
||||
return String(v == null ? "" : v);
|
||||
}
|
||||
|
||||
function __wrnexusSerializeScopeValue(value: any): string {
|
||||
function __wrnexusSerializeScopeValue(value: unknown): string {
|
||||
if (value === undefined) {
|
||||
return "undefined";
|
||||
}
|
||||
@@ -243,7 +241,7 @@ function __wrnexusSerializeScopeValue(value: any): string {
|
||||
}
|
||||
}
|
||||
|
||||
function __wrnexusScopeDecl(obj: Record<string, any>): string {
|
||||
function __wrnexusScopeDecl(obj: Record<string, unknown>): string {
|
||||
return Object.keys(obj)
|
||||
.map(
|
||||
(key) =>
|
||||
@@ -262,14 +260,14 @@ function __wrnexusSerializeScopeValue(value: any): string {
|
||||
|
||||
export function render(props: CounterProps = {} as CounterProps): string {
|
||||
const __p = props || {};
|
||||
const label: string = __coerce(__p["label"], ("Count"), "string");
|
||||
const label: string = __coerce(__p["label"], ("Count"), "string", "label") as string;
|
||||
const __attrs = __restProps(__p, new Set(["label"]));
|
||||
let count = (0);
|
||||
const __scopeState = { "label": label, "count": count };
|
||||
const __scope = __wrnexusScopeDecl(__scopeState);
|
||||
const __scopePayload = __WrnexusBuffer.from(JSON.stringify(__scopeState), "utf8").toString("base64");
|
||||
return \`<div data-scope="\${__scope}" data-wrn-scope="\${__scopePayload}" data-wrn-behavior="eyJmdW5jdGlvbnMiOiJmdW5jdGlvbiBpbmNyZW1lbnQoKXtcbiAgICAgIGNvdW50ID0gY291bnQgKyAxXG4gICAgICBvdXRwdXQuY2hhbmdlKGNvdW50KVxuICAgIH0iLCJvdXRwdXRzIjpbeyJuYW1lIjoiY2hhbmdlIiwicGF5bG9hZCI6eyJuYW1lIjoidmFsdWUiLCJ2YWx1ZVR5cGUiOiJudW1iZXIiLCJvcHRpb25hbCI6ZmFsc2V9fV0sImNvbXB1dGVkIjpbXSwiZWZmZWN0cyI6W10sImxpZmVjeWNsZSI6e30sIndhdGNoZXMiOltdfQ==" data-wrn-hydration="Counter:1skggk6" data-wrn-hydrate="load" data-wrn-runtime="universal" data-wrn-client-module="__WRNEXUS_CLIENT_MODULE__">
|
||||
<div data-component="Button"\${__wireSpreadAttrs(__attrs)} on:click="\${__wireProp(increment)}">\${__wireHtml(label)}: <span data-text="count">\${__wireHtml(count)}</span></div>
|
||||
<div data-component="Button"\${__wireSpreadAttrs(__attrs)} on:click="\${__wireProp(increment)}"><span data-text="label">\${__wireHtml(label)}</span>: <span data-text="count">\${__wireHtml(count)}</span></div>
|
||||
</div>\`;
|
||||
}
|
||||
|
||||
|
||||
@@ -200,9 +200,10 @@ test("typed props enforce required values and runtime-compatible input", async (
|
||||
}`);
|
||||
const render = component.render as (props?: Record<string, unknown>) => string;
|
||||
expect(() => render()).toThrow("TypedInput requires prop 'label' (string)");
|
||||
expect(render({ label: "Total", count: "4", enabled: "true" })).toContain(
|
||||
"<span>Total:4:true</span>",
|
||||
);
|
||||
const rendered = render({ label: "Total", count: "4", enabled: "true" });
|
||||
expect(rendered).toContain('<span data-text="label">Total</span>');
|
||||
expect(rendered).toContain('<span data-text="count">4</span>');
|
||||
expect(rendered).toContain('<span data-text="enabled">true</span>');
|
||||
expect(() => render({ label: "Total", count: "many" })).toThrow("Expected a finite number prop");
|
||||
expect(() => render({ label: "Total", enabled: "sometimes" })).toThrow("Expected a boolean prop");
|
||||
});
|
||||
@@ -238,13 +239,14 @@ test("dynamic HTML boolean attributes are omitted when false", async () => {
|
||||
const render = mod.render as (props?: Record<string, unknown>) => string;
|
||||
|
||||
const enabled = render();
|
||||
expect(enabled).not.toContain(" disabled");
|
||||
expect(enabled).not.toContain(" checked");
|
||||
expect(enabled).not.toContain(" required");
|
||||
expect(enabled).not.toContain("<button disabled");
|
||||
expect(enabled).not.toContain("<input checked");
|
||||
expect(enabled).not.toContain("<input required");
|
||||
|
||||
const disabled = render({ disabled: "true", checked: "true", required: "true" });
|
||||
expect(disabled).toContain("<button disabled>");
|
||||
expect(disabled).toContain("<input checked required>");
|
||||
expect(disabled).toContain("<button disabled");
|
||||
expect(disabled).toContain("<input checked");
|
||||
expect(disabled).toContain(" required");
|
||||
});
|
||||
|
||||
test("components safely forward undeclared HTML attributes to their root", async () => {
|
||||
@@ -282,7 +284,7 @@ test("components safely forward undeclared HTML attributes to their root", async
|
||||
expect(html).not.toContain("variant=");
|
||||
expect(html).not.toContain("onclick=");
|
||||
expect(html).not.toContain("style=");
|
||||
expect(html).not.toContain("data-wrn-bind");
|
||||
expect(html).not.toContain('data-wrn-bind-0="unsafe"');
|
||||
});
|
||||
|
||||
test("an explicit attrs spread overrides automatic root forwarding", async () => {
|
||||
@@ -300,12 +302,13 @@ test("an explicit attrs spread overrides automatic root forwarding", async () =>
|
||||
const render = mod.render as (props?: Record<string, unknown>) => string;
|
||||
const html = render({ label: "Save", class: "wrapper", formaction: "/save" });
|
||||
|
||||
expect(html).toContain('<span class="wrapper">');
|
||||
expect(html).toContain('<span class="wrapper"');
|
||||
expect(html).not.toContain('<span formaction="/save"');
|
||||
expect(html).toContain('<button formaction="/save">Save</button>');
|
||||
expect(html).toContain('<button formaction="/save">');
|
||||
expect(html).toContain('<span data-text="label">Save</span>');
|
||||
});
|
||||
|
||||
test("stateless component bakes props into server HTML (zero JS)", async () => {
|
||||
test("prop-driven component renders SSR content and exposes reactive prop signals", async () => {
|
||||
const mod = await compileAndImport(
|
||||
`component Button {\n props {\n label = "Button"\n variant = "default"\n class = ""\n }\n view { <button class="wire-btn wire-btn--{variant} {class}">{label}</button> }\n}`,
|
||||
);
|
||||
@@ -313,10 +316,11 @@ test("stateless component bakes props into server HTML (zero JS)", async () => {
|
||||
const out = render({ label: "Save <b>", variant: "primary", class: "mt-2" });
|
||||
expect(out).toContain('class="wire-btn wire-btn--primary mt-2"');
|
||||
expect(out).toContain("Save <b>"); // html-escaped
|
||||
expect(out).not.toContain("data-scope"); // no reactivity → no scope
|
||||
expect(out).toContain("data-scope");
|
||||
expect(out).toContain('data-text="label"');
|
||||
});
|
||||
|
||||
test("stateful component: state text baked into a reactive data-text span, prop text baked", async () => {
|
||||
test("stateful component keeps both prop and state text reactive", async () => {
|
||||
const mod = await compileAndImport(
|
||||
`component Counter {\n props {\n start = 0\n label = "Count"\n }\n state count = start\n view { <button @click="count++">{label}: {count}</button> }\n}`,
|
||||
);
|
||||
@@ -324,8 +328,7 @@ test("stateful component: state text baked into a reactive data-text span, prop
|
||||
const out = render({ start: "10", label: "Score" });
|
||||
expect(out).toContain('data-scope="start: 10, label: "Score", count: 10"');
|
||||
expect(out).toContain('data-on-click="count++"');
|
||||
// label baked as static text; count baked as its initial value AND kept live.
|
||||
expect(out).toContain('Score: <span data-text="count">10</span>');
|
||||
expect(out).toContain('<span data-text="label">Score</span>: <span data-text="count">10</span>');
|
||||
|
||||
expect(out).toContain('data-scope="');
|
||||
|
||||
@@ -521,6 +524,17 @@ component NativeProps {
|
||||
);
|
||||
});
|
||||
|
||||
test("malformed structured prop strings fail with the prop name", async () => {
|
||||
const mod = await compileAndImport(`component StructuredProps {
|
||||
props { items = [] options = {} }
|
||||
view { <div></div> }
|
||||
}`);
|
||||
const render = mod.render as (props?: Record<string, unknown>) => string;
|
||||
|
||||
expect(() => render({ items: "not-json" })).toThrow("Expected an array prop 'items'");
|
||||
expect(() => render({ options: "[]" })).toThrow("Expected an object prop 'options'");
|
||||
});
|
||||
|
||||
test("parses a layout block", () => {
|
||||
const ast = parse(`
|
||||
layout AppLayout {
|
||||
@@ -1083,6 +1097,19 @@ page Home {
|
||||
|
||||
expect(output).toContain("__wrnexusPropAttr([");
|
||||
});
|
||||
test("nested component props retain parent-owned reactive bindings", () => {
|
||||
const output = generate(
|
||||
parse(`component Parent {
|
||||
props { value = 0 }
|
||||
state count = value
|
||||
view { <Child value={count} /> }
|
||||
}`),
|
||||
);
|
||||
|
||||
expect(output).toContain("data-wrn-prop-bind-0");
|
||||
expect(output).toContain("["value","{count}"]");
|
||||
expect(output).toContain('lowerName.startsWith("data-wrn-prop-bind-")');
|
||||
});
|
||||
test("component functions are available during server rendering", () => {
|
||||
const output = generate(
|
||||
parse(`
|
||||
@@ -1470,3 +1497,24 @@ test("a boolean attribute bound to a loop variable binds on the client", () => {
|
||||
expect(code).not.toContain('__wireBooleanAttr("checked"');
|
||||
expect(code).toContain("data-wrn-bind-");
|
||||
});
|
||||
|
||||
test("block comments inside props report the authoring restriction", () => {
|
||||
expect(() =>
|
||||
compileWireFile(`component Example {
|
||||
props {
|
||||
/* use a line comment */
|
||||
label: string = "Example"
|
||||
}
|
||||
view { <span>{label}</span> }
|
||||
}`),
|
||||
).toThrow("Block comments are not allowed inside props {}; use // line comments instead");
|
||||
});
|
||||
|
||||
test("state page reports its collision with the page keyword", () => {
|
||||
expect(() =>
|
||||
compileWireFile(`page Example {
|
||||
state page = 1
|
||||
view { <span>{page}</span> }
|
||||
}`),
|
||||
).toThrow("State name 'page' collides with the WRN 'page' keyword");
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { gzipSync } from "bun";
|
||||
import { generateTargets, parse } from "../src/index.ts";
|
||||
|
||||
const ast = parse(`component ConfirmDialog {
|
||||
@@ -130,7 +131,7 @@ test("browser codegen binds peer client functions through the scoped function ta
|
||||
view { <button @click='run()'>{value}</button> }
|
||||
}`),
|
||||
);
|
||||
expect(targets.browser).toContain("context.functions");
|
||||
expect(targets.browser).toContain('implementations["increment"]');
|
||||
const executable = new Function(
|
||||
`${targets.browser.replace(/^export\s+/gm, "")}\nreturn { bindClientScope };`,
|
||||
)() as { bindClientScope: (context: Record<string, unknown>) => Record<string, () => void> };
|
||||
@@ -145,3 +146,82 @@ test("browser codegen binds peer client functions through the scoped function ta
|
||||
functions.run?.();
|
||||
expect(state.value).toBe(1);
|
||||
});
|
||||
|
||||
test("deferred timer state writes commit after the client function returns", async () => {
|
||||
const targets = generateTargets(
|
||||
parse(`component DeferredWrite {
|
||||
state { value: number = 0 }
|
||||
functions {
|
||||
client function later(): void { setTimeout(() => { value = 2 }, 0) }
|
||||
}
|
||||
view { <button @click='later()'>{value}</button> }
|
||||
}`),
|
||||
);
|
||||
const executable = new Function(
|
||||
`${targets.browser.replace(/^export\s+/gm, "")}\nreturn { bindClientScope };`,
|
||||
)() as { bindClientScope: (context: Record<string, unknown>) => Record<string, () => void> };
|
||||
let rendered = "0";
|
||||
const state = new Proxy(
|
||||
{ value: 0 },
|
||||
{
|
||||
set(target, property, value) {
|
||||
Reflect.set(target, property, value);
|
||||
rendered = String(value);
|
||||
return true;
|
||||
},
|
||||
},
|
||||
);
|
||||
const functions = executable.bindClientScope({
|
||||
state,
|
||||
props: {},
|
||||
output: {},
|
||||
server: {},
|
||||
refs: {},
|
||||
});
|
||||
|
||||
functions.later?.();
|
||||
expect(state.value).toBe(0);
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
expect(state.value).toBe(2);
|
||||
expect(rendered).toBe("2");
|
||||
});
|
||||
|
||||
test("the deferred commit helper does not shadow an authored commit function", () => {
|
||||
const targets = generateTargets(
|
||||
parse(`component AuthoredCommit {
|
||||
state { value: number = 0 }
|
||||
functions {
|
||||
client function commit(): void { value = 1 }
|
||||
client function save(): void { commit() }
|
||||
}
|
||||
view { <button @click='save()'>{value}</button> }
|
||||
}`),
|
||||
);
|
||||
|
||||
expect(() => new Function(targets.browser.replace(/^export\s+/gm, ""))).not.toThrow();
|
||||
expect(targets.browser).not.toContain("const commit = __wrnexusCommit");
|
||||
});
|
||||
|
||||
test("generated client modules stay below decoded-size and duplication ratchets", () => {
|
||||
const states = Array.from({ length: 12 }, (_, index) => `s${index}: number = ${index}`).join(" ");
|
||||
const functions = Array.from(
|
||||
{ length: 20 },
|
||||
(_, index) =>
|
||||
`client function f${index}(): void { s${index % 12} += 1 ${Array.from(
|
||||
{ length: 20 },
|
||||
(_unused, peer) => (peer === index ? "" : `if (false) f${peer}()`),
|
||||
).join(" ")} }`,
|
||||
).join("\n");
|
||||
const browser = generateTargets(
|
||||
parse(`component ClientSizeStress {
|
||||
state { ${states} }
|
||||
functions { ${functions} }
|
||||
view { <button></button> }
|
||||
}`),
|
||||
).browser;
|
||||
const decoded = new TextEncoder().encode(browser).length;
|
||||
const compressed = gzipSync(new TextEncoder().encode(browser)).length;
|
||||
|
||||
expect(decoded).toBeLessThanOrEqual(13_000);
|
||||
expect(decoded / compressed).toBeLessThanOrEqual(10);
|
||||
});
|
||||
|
||||
+127
-2
@@ -17,9 +17,134 @@ export { NAV_RUNTIME } from "./nav-runtime.ts";
|
||||
export { REALTIME_RUNTIME } from "./realtime-runtime.ts";
|
||||
export { ACTION_RUNTIME } from "./action-runtime.ts";
|
||||
|
||||
const CONTROLLER_SECTIONS = ["PRIMARY", "UI", "PIN"] as const;
|
||||
|
||||
function runtimeForMode(development: boolean): string {
|
||||
if (development) {
|
||||
return REACTIVE_RUNTIME.replace(/\/\*__WRNEXUS_DEV_START__\*\//g, "").replace(
|
||||
/\/\*__WRNEXUS_DEV_END__\*\//g,
|
||||
"",
|
||||
);
|
||||
}
|
||||
return REACTIVE_RUNTIME.replace(
|
||||
/\/\*__WRNEXUS_DEV_START__\*\/[\s\S]*?\/\*__WRNEXUS_DEV_END__\*\//g,
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
function controllerSection(source: string, name: (typeof CONTROLLER_SECTIONS)[number]): string {
|
||||
const pattern = new RegExp(
|
||||
`/\\*__WRNEXUS_CONTROLLERS_${name}_START__\\*/([\\s\\S]*?)/\\*__WRNEXUS_CONTROLLERS_${name}_END__\\*/`,
|
||||
);
|
||||
const match = pattern.exec(source);
|
||||
if (!match?.[1]) throw new Error(`Missing reactive runtime controller section: ${name}`);
|
||||
return match[1];
|
||||
}
|
||||
|
||||
function stripControllerSections(source: string): string {
|
||||
for (const name of CONTROLLER_SECTIONS) {
|
||||
source = source.replace(
|
||||
new RegExp(
|
||||
`/\\*__WRNEXUS_CONTROLLERS_${name}_START__\\*/[\\s\\S]*?/\\*__WRNEXUS_CONTROLLERS_${name}_END__\\*/`,
|
||||
),
|
||||
"",
|
||||
);
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
const CONTROLLER_LOADER = String.raw`
|
||||
var componentControllerSelector = "[data-wrn-anchored],[data-wrn-dialog],[data-wrn-roving],[data-wrn-scrollspy],[data-wrn-splitter],[data-wrn-navbar],[data-wrn-preferences],[data-wrn-select],[data-wrn-pin-input]";
|
||||
var componentControllerPromise = null;
|
||||
var componentControllerUrl = (document.currentScript && document.currentScript.src)
|
||||
? new URL("./controllers.js", document.currentScript.src).href
|
||||
: "/__wrnexus/controllers.js";
|
||||
function needsComponentControllers(root) {
|
||||
var host = root || document;
|
||||
return !!((host.matches && host.matches(componentControllerSelector)) ||
|
||||
(host.querySelector && host.querySelector(componentControllerSelector)));
|
||||
}
|
||||
function loadComponentControllers(root) {
|
||||
if (!needsComponentControllers(root)) return;
|
||||
if (window.__wrnexusComponentControllers) {
|
||||
window.__wrnexusComponentControllers.hydrate(root || document);
|
||||
return;
|
||||
}
|
||||
if (!componentControllerPromise) {
|
||||
componentControllerPromise = new Promise(function (resolve, reject) {
|
||||
var script = document.createElement("script");
|
||||
script.src = componentControllerUrl;
|
||||
script.defer = true;
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
componentControllerPromise.then(function () {
|
||||
if (window.__wrnexusComponentControllers) window.__wrnexusComponentControllers.hydrate(root || document);
|
||||
}).catch(function (error) { console.error("[wrnexus] failed to load component controllers", error); });
|
||||
}
|
||||
`;
|
||||
|
||||
/** The reactive runtime served at `/__wrnexus/reactive.js` (plain browser JS). */
|
||||
export function getReactiveRuntime(): string {
|
||||
return REACTIVE_RUNTIME;
|
||||
export function getReactiveRuntime(development = false): string {
|
||||
let source = stripControllerSections(runtimeForMode(development));
|
||||
source = source.replace(
|
||||
" function hydrateScopes(root) {",
|
||||
`${CONTROLLER_LOADER}\n function hydrateScopes(root) {`,
|
||||
);
|
||||
source = source.replace(
|
||||
" hydrateNavbarControllers(host);\n hydratePreferenceControllers(host);\n hydrateSelectControllers(host);\n hydratePinInputControllers(host);",
|
||||
" loadComponentControllers(host);",
|
||||
);
|
||||
source = source.replace(" startDocumentWatch();\n", "");
|
||||
source = source.replace(
|
||||
" setupAnchoredOverlays();\n setupModalDialogs();\n setupRovingFocus();\n setupScrollspy();\n setupSplitters();",
|
||||
" loadComponentControllers(document);",
|
||||
);
|
||||
source = source.replace(
|
||||
" window.__wrnexusRepositionAnchored = repositionAnchored;",
|
||||
" window.__wrnexusRepositionAnchored = function (root) { if (window.__wrnexusComponentControllers) window.__wrnexusComponentControllers.reposition(root); };",
|
||||
);
|
||||
source = source.replace(
|
||||
" window.__wrnexusMountClientRoots = mountClientRoots;",
|
||||
` window.__wrnexusControllerBridge = { invokeComponentOutput: invokeComponentOutput, callServerFunction: callServerFunction, dispatchComponentEvent: dispatchComponentEvent, emitPinInputEvent: emitPinInputEvent, parseScopeDecl: parseScopeDecl, warn: ${development ? "warnOnce" : "function () {}"} };\n window.__wrnexusMountClientRoots = mountClientRoots;`,
|
||||
);
|
||||
return source;
|
||||
}
|
||||
|
||||
/** Component-specific controllers, loaded only when their marker is present. */
|
||||
export function getComponentControllerRuntime(development = false): string {
|
||||
const source = runtimeForMode(development);
|
||||
const sections = CONTROLLER_SECTIONS.map((name) => controllerSection(source, name)).join("\n");
|
||||
return `
|
||||
(function () {
|
||||
"use strict";
|
||||
var bridge = window.__wrnexusControllerBridge || {};
|
||||
var invokeComponentOutput = bridge.invokeComponentOutput;
|
||||
var callServerFunction = bridge.callServerFunction;
|
||||
var dispatchComponentEvent = bridge.dispatchComponentEvent;
|
||||
var emitPinInputEvent = bridge.emitPinInputEvent;
|
||||
var parseScopeDecl = bridge.parseScopeDecl;
|
||||
var warnOnce = bridge.warn || function () {};
|
||||
${sections}
|
||||
function hydrate(root) {
|
||||
var host = root || document;
|
||||
hydrateNavbarControllers(host);
|
||||
hydratePreferenceControllers(host);
|
||||
hydrateSelectControllers(host);
|
||||
hydratePinInputControllers(host);
|
||||
syncRovingGroups(host);
|
||||
}
|
||||
setupAnchoredOverlays();
|
||||
setupModalDialogs();
|
||||
setupRovingFocus();
|
||||
setupScrollspy();
|
||||
setupSplitters();
|
||||
window.__wrnexusComponentControllers = { hydrate: hydrate, reposition: repositionAnchored };
|
||||
hydrate(document);
|
||||
})();
|
||||
`.trim();
|
||||
}
|
||||
|
||||
/** The client-side navigation runtime served at `/__wrnexus/nav.js`. */
|
||||
|
||||
@@ -191,6 +191,68 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
}
|
||||
}
|
||||
|
||||
/*__WRNEXUS_DEV_START__*/
|
||||
var developmentWarnings = Object.create(null);
|
||||
function warnOnce(code, message, element, detail) {
|
||||
var key = code + "\\n" + message;
|
||||
if (developmentWarnings[key]) return;
|
||||
developmentWarnings[key] = true;
|
||||
var payload = {
|
||||
code: code,
|
||||
message: message,
|
||||
hydrationId: element && element.getAttribute ? element.getAttribute("data-wrn-hydration") : null,
|
||||
detail: detail || null,
|
||||
};
|
||||
console.warn("[" + code + "] " + message, detail || "");
|
||||
try {
|
||||
window.dispatchEvent(new CustomEvent("wrnexus:diagnostic", { detail: payload }));
|
||||
} catch (_) {
|
||||
// CustomEvent can be unavailable in minimal DOM test environments.
|
||||
}
|
||||
}
|
||||
|
||||
function warnMissingBindingFunction(statement, resolve, element) {
|
||||
var match = /^\s*([A-Za-z_$][\w$]*)\s*\(/.exec(statement || "");
|
||||
if (!match || typeof resolve(match[1]) === "function") return;
|
||||
warnOnce(
|
||||
"WRN-DEV-BINDING-MISSING",
|
||||
"Component binding calls '" + match[1] + "', but that function does not exist in the parent scope.",
|
||||
element,
|
||||
{ binding: statement, functionName: match[1] },
|
||||
);
|
||||
}
|
||||
|
||||
function warnMissingThemeTokens() {
|
||||
var referenced = Object.create(null);
|
||||
function collect(rules) {
|
||||
Array.prototype.forEach.call(rules || [], function (rule) {
|
||||
var cssText = rule.cssText || "";
|
||||
var match;
|
||||
var pattern = /var\(\s*(--wire-[A-Za-z0-9_-]+)/g;
|
||||
while ((match = pattern.exec(cssText))) referenced[match[1]] = true;
|
||||
try {
|
||||
if (rule.cssRules) collect(rule.cssRules);
|
||||
} catch (_) {
|
||||
// Cross-origin and disabled stylesheets can deny CSSOM access.
|
||||
}
|
||||
});
|
||||
}
|
||||
Array.prototype.forEach.call(document.styleSheets || [], function (sheet) {
|
||||
try { collect(sheet.cssRules); } catch (_) {}
|
||||
});
|
||||
var rendered = window.getComputedStyle(document.documentElement);
|
||||
Object.keys(referenced).forEach(function (token) {
|
||||
if (rendered.getPropertyValue(token).trim()) return;
|
||||
warnOnce(
|
||||
"WRN-DEV-THEME-TOKEN-MISSING",
|
||||
"Theme token '" + token + "' is referenced by rendered CSS but is not defined.",
|
||||
document.documentElement,
|
||||
{ token: token },
|
||||
);
|
||||
});
|
||||
}
|
||||
/*__WRNEXUS_DEV_END__*/
|
||||
|
||||
function scheduleUpdateHook(element, callback) {
|
||||
pendingUpdateHooks.set(element, callback);
|
||||
if (updateHooksScheduled) return;
|
||||
@@ -2493,6 +2555,9 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
locals.event = undefined;
|
||||
locals.$event = undefined;
|
||||
try {
|
||||
/*__WRNEXUS_DEV_START__*/
|
||||
warnMissingBindingFunction(outStmt, function (name) { return peekScope(name); }, componentRoot);
|
||||
/*__WRNEXUS_DEV_END__*/
|
||||
return runStmt(outStmt, locals);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
@@ -2535,9 +2600,43 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
});
|
||||
});
|
||||
|
||||
if (behavior) {
|
||||
installBehaviorFunctions(behavior.functions);
|
||||
var publicScopeApi = {
|
||||
// Prop expressions belong to the parent that mounted the component. The
|
||||
// server forwards these markers onto the rendered child root; evaluate
|
||||
// them here and write changes into the child's prop signals.
|
||||
Array.prototype.slice
|
||||
.call(el.querySelectorAll("[data-scope], [data-wrn-scope]"))
|
||||
.forEach(function (node) {
|
||||
if (!node.parentNode || ownerScope(node.parentNode) !== el) return;
|
||||
Array.prototype.slice.call(node.attributes).forEach(function (attr) {
|
||||
if (attr.name.indexOf("data-wrn-prop-bind-") !== 0) return;
|
||||
var binding;
|
||||
try { binding = JSON.parse(attr.value); } catch (_) { return; }
|
||||
if (!binding || binding.length !== 2) return;
|
||||
var propName = binding[0];
|
||||
var template = binding[1];
|
||||
reactive(function () {
|
||||
var exact = /^\{([^{}]+)\}$/.exec(template);
|
||||
var value;
|
||||
try {
|
||||
value = exact
|
||||
? evalExpr(exact[1].trim(), decodeLoopLocals(node))
|
||||
: template.replace(/\{([^{}]+)\}/g, function (_, expression) {
|
||||
var part = evalExpr(expression.trim(), decodeLoopLocals(node));
|
||||
return part == null ? "" : String(part);
|
||||
});
|
||||
} catch (_) { return; }
|
||||
var apply = function () {
|
||||
if (node.__wrnexusScopeApi) node.__wrnexusScopeApi.set(propName, value);
|
||||
};
|
||||
if (node.__wrnexusScopeApi) apply();
|
||||
else queueMicrotask(apply);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Every hydrated scope exposes state writes. Prop-only components need the
|
||||
// same API even when they have no behavior block.
|
||||
var publicScopeApi = {
|
||||
get: peekScope,
|
||||
set: writeScope,
|
||||
call: function (name) {
|
||||
@@ -2545,11 +2644,14 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
if (typeof fn !== "function") return undefined;
|
||||
return fn.apply(null, Array.prototype.slice.call(arguments, 1));
|
||||
},
|
||||
};
|
||||
el.__wrnexusScopeApi = publicScopeApi;
|
||||
el.querySelectorAll("[data-wrn-select]").forEach(function (select) {
|
||||
select.__wrnexusScopeApi = publicScopeApi;
|
||||
});
|
||||
};
|
||||
el.__wrnexusScopeApi = publicScopeApi;
|
||||
el.querySelectorAll("[data-wrn-select]").forEach(function (select) {
|
||||
select.__wrnexusScopeApi = publicScopeApi;
|
||||
});
|
||||
|
||||
if (behavior) {
|
||||
installBehaviorFunctions(behavior.functions);
|
||||
|
||||
(behavior.effects || []).forEach(function (source) {
|
||||
if (typeof source !== "string" || !source.trim()) return;
|
||||
@@ -2762,6 +2864,7 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
* cleared, so repositioning is idempotent -- re-running it on a panel that
|
||||
* is already on screen changes nothing and cannot drift.
|
||||
*/
|
||||
/*__WRNEXUS_CONTROLLERS_PRIMARY_START__*/
|
||||
var ANCHORED_SELECTOR = "[data-wrn-anchored]";
|
||||
var ANCHOR_MARGIN = 8;
|
||||
var anchoredScheduled = false;
|
||||
@@ -3378,6 +3481,8 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
document.addEventListener("pointercancel", endDrag);
|
||||
}
|
||||
|
||||
/*__WRNEXUS_CONTROLLERS_PRIMARY_END__*/
|
||||
|
||||
function hydrateScopes(root) {
|
||||
var host = root || document;
|
||||
|
||||
@@ -3403,8 +3508,12 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
hydratePreferenceControllers(host);
|
||||
hydrateSelectControllers(host);
|
||||
hydratePinInputControllers(host);
|
||||
/*__WRNEXUS_DEV_START__*/
|
||||
warnMissingThemeTokens();
|
||||
/*__WRNEXUS_DEV_END__*/
|
||||
}
|
||||
|
||||
/*__WRNEXUS_CONTROLLERS_UI_START__*/
|
||||
var navbarOutsideClickBound = false;
|
||||
var preferenceOutsideClickBound = false;
|
||||
|
||||
@@ -3923,6 +4032,8 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
}
|
||||
}
|
||||
|
||||
/*__WRNEXUS_CONTROLLERS_UI_END__*/
|
||||
|
||||
function invokeComponentOutput(root, name, payload) {
|
||||
if (!root || !name) return undefined;
|
||||
var registry = root.__wrnexusOutputHandlers;
|
||||
@@ -3953,6 +4064,15 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
? Promise.all(values)
|
||||
: values[values.length - 1];
|
||||
}
|
||||
/*__WRNEXUS_DEV_START__*/
|
||||
warnOnce(
|
||||
"WRN-DEV-OUTPUT-UNHANDLED",
|
||||
"Output '" + name + "' has no parent binding. Registered bindings: " +
|
||||
(registry ? Object.keys(registry).join(", ") || "none" : "none") + ".",
|
||||
root,
|
||||
{ output: name, registeredBindings: registry ? Object.keys(registry) : [] },
|
||||
);
|
||||
/*__WRNEXUS_DEV_END__*/
|
||||
return dispatchComponentEvent(root, name, payload);
|
||||
}
|
||||
|
||||
@@ -4021,6 +4141,7 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
}
|
||||
}
|
||||
|
||||
/*__WRNEXUS_CONTROLLERS_PIN_START__*/
|
||||
function setupPinInputController(root) {
|
||||
if (!root || root.__wrnexusPinInputController) return;
|
||||
var cells = Array.prototype.slice.call(root.querySelectorAll("[data-pin-cell]"));
|
||||
@@ -4196,6 +4317,8 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
inputs.forEach(setupPinInputController);
|
||||
}
|
||||
|
||||
/*__WRNEXUS_CONTROLLERS_PIN_END__*/
|
||||
|
||||
function parseScopeDecl(decl) {
|
||||
var initial = {};
|
||||
splitTopLevel(decl, ",").forEach(function (part) {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { test, expect, beforeEach } from "bun:test";
|
||||
import { Window } from "happy-dom";
|
||||
import { REACTIVE_RUNTIME } from "../src/reactive-runtime.ts";
|
||||
import { getComponentControllerRuntime, getReactiveRuntime } from "../src/index.ts";
|
||||
import { mountHtml } from "@wrnexus/test";
|
||||
|
||||
// Fresh DOM per test, with the runtime's globals bound.
|
||||
function mount(html: string): Window {
|
||||
function mount(html: string, runtime = REACTIVE_RUNTIME, controllers = ""): Window {
|
||||
const win = new Window() as unknown as Window & Record<string, unknown>;
|
||||
win.document.body.innerHTML = `<div id="app">${html}</div>`;
|
||||
(globalThis as Record<string, unknown>).window = win;
|
||||
@@ -24,7 +25,8 @@ function mount(html: string): Window {
|
||||
(globalThis as Record<string, unknown>).CustomEvent = (
|
||||
win as unknown as { CustomEvent: unknown }
|
||||
).CustomEvent;
|
||||
(0, eval)(REACTIVE_RUNTIME);
|
||||
(0, eval)(runtime);
|
||||
if (controllers) (0, eval)(controllers);
|
||||
// Hydrate deterministically (auto-init waits on DOMContentLoaded, which the
|
||||
// test window may not fire). setupScope is idempotent, so this is safe.
|
||||
const w = win as unknown as { __wrnexusHydrateScopes?: (root: unknown) => void };
|
||||
@@ -40,6 +42,31 @@ beforeEach(() => {
|
||||
delete (globalThis as Record<string, unknown>).MutationObserver;
|
||||
});
|
||||
|
||||
test("split runtime hydrates a controller only from the controller asset", () => {
|
||||
const core = getReactiveRuntime(true);
|
||||
const controllers = getComponentControllerRuntime(true);
|
||||
expect(core).not.toContain("function setupRovingFocus");
|
||||
expect(controllers).toContain("function setupRovingFocus");
|
||||
|
||||
const win = mount(
|
||||
`<div data-wrn-roving="horizontal"><button data-wrn-roving-item>One</button><button data-wrn-roving-item>Two</button></div>`,
|
||||
core,
|
||||
controllers,
|
||||
);
|
||||
const buttons = win.document.querySelectorAll("button");
|
||||
(buttons[0] as unknown as HTMLButtonElement).focus();
|
||||
buttons[0]!.dispatchEvent(new win.KeyboardEvent("keydown", { key: "ArrowRight", bubbles: true }));
|
||||
expect(win.document.activeElement).toBe(buttons[1]);
|
||||
});
|
||||
|
||||
test("a page without controller markers does not request the controller asset", () => {
|
||||
const win = mount(
|
||||
`<main data-scope="count: 1"><span>{count}</span></main>`,
|
||||
getReactiveRuntime(true),
|
||||
);
|
||||
expect(win.document.querySelector('script[src$="controllers.js"]')).toBeNull();
|
||||
});
|
||||
|
||||
test("hydrates {expr} mustaches from data-scope", () => {
|
||||
const win = mount(`<div data-scope="count: 0"><span>{count}, {count * 2}</span></div>`);
|
||||
expect(win.document.querySelector("span")!.textContent).toBe("0, 0");
|
||||
@@ -361,6 +388,20 @@ test("reactive attribute bindings update input and accessibility attributes", ()
|
||||
expect(button.getAttribute("aria-label")).toBe("Hide password");
|
||||
});
|
||||
|
||||
test("parent state updates a mounted child's reactive prop", async () => {
|
||||
const win = mount(
|
||||
`<div data-scope="n: 1">` +
|
||||
`<button data-on-click="n++">increment</button>` +
|
||||
`<div data-scope="value: 1" data-wrn-prop-bind-0='["value","{n}"]'>` +
|
||||
`<span id="child-value" data-text="value">1</span>` +
|
||||
`</div></div>`,
|
||||
);
|
||||
await Promise.resolve();
|
||||
expect(win.document.querySelector("#child-value")?.textContent).toBe("1");
|
||||
(win.document.querySelector("button") as unknown as HTMLElement).click();
|
||||
expect(win.document.querySelector("#child-value")?.textContent).toBe("2");
|
||||
});
|
||||
|
||||
test("independent signals in one scope update correctly (dependency tracking)", () => {
|
||||
const win = mount(
|
||||
`<div data-scope="a: 0, b: 100">
|
||||
@@ -663,6 +704,122 @@ test("a camelCase output reaches a parent binding despite attribute lowercasing"
|
||||
expect(win.document.querySelector("#out")?.textContent).toBe("yes");
|
||||
});
|
||||
|
||||
test("development runtime warns once for an output with no parent binding", () => {
|
||||
const win = new Window() as unknown as Window & Record<string, unknown>;
|
||||
win.document.body.innerHTML = `<div data-scope="" data-wrn-events="complete"><button data-on-click="output.complete({})">go</button></div>`;
|
||||
(globalThis as Record<string, unknown>).window = win;
|
||||
(globalThis as Record<string, unknown>).document = win.document;
|
||||
(globalThis as Record<string, unknown>).location = win.location;
|
||||
(globalThis as Record<string, unknown>).NodeFilter = (
|
||||
win as unknown as { NodeFilter: unknown }
|
||||
).NodeFilter;
|
||||
(globalThis as Record<string, unknown>).MutationObserver = (
|
||||
win as unknown as { MutationObserver: unknown }
|
||||
).MutationObserver;
|
||||
(globalThis as Record<string, unknown>).CustomEvent = (
|
||||
win as unknown as { CustomEvent: unknown }
|
||||
).CustomEvent;
|
||||
const warnings: unknown[][] = [];
|
||||
const originalWarn = console.warn;
|
||||
console.warn = (...args: unknown[]) => warnings.push(args);
|
||||
try {
|
||||
(0, eval)(getReactiveRuntime(true));
|
||||
(
|
||||
win as unknown as { __wrnexusHydrateScopes?: (root: unknown) => void }
|
||||
).__wrnexusHydrateScopes?.(win.document);
|
||||
(win.document.querySelector("button") as unknown as HTMLElement).click();
|
||||
(win.document.querySelector("button") as unknown as HTMLElement).click();
|
||||
} finally {
|
||||
console.warn = originalWarn;
|
||||
}
|
||||
expect(warnings).toHaveLength(1);
|
||||
expect(String(warnings[0]?.[0])).toContain("WRN-DEV-OUTPUT-UNHANDLED");
|
||||
});
|
||||
|
||||
test("development runtime warns when a component binding names a missing function", () => {
|
||||
const win = new Window() as unknown as Window & Record<string, unknown>;
|
||||
win.document.body.innerHTML =
|
||||
`<div data-scope="">` +
|
||||
`<div data-scope="" data-wrn-hydration="Child:x">` +
|
||||
`<div data-wrn-events="save" data-wrn-out-save="missingSave(payload)"></div>` +
|
||||
`</div></div>`;
|
||||
(globalThis as Record<string, unknown>).window = win;
|
||||
(globalThis as Record<string, unknown>).document = win.document;
|
||||
(globalThis as Record<string, unknown>).location = win.location;
|
||||
(globalThis as Record<string, unknown>).NodeFilter = (
|
||||
win as unknown as { NodeFilter: unknown }
|
||||
).NodeFilter;
|
||||
(globalThis as Record<string, unknown>).MutationObserver = (
|
||||
win as unknown as { MutationObserver: unknown }
|
||||
).MutationObserver;
|
||||
(globalThis as Record<string, unknown>).CustomEvent = (
|
||||
win as unknown as { CustomEvent: unknown }
|
||||
).CustomEvent;
|
||||
const warnings: unknown[][] = [];
|
||||
const originalWarn = console.warn;
|
||||
const originalError = console.error;
|
||||
console.warn = (...args: unknown[]) => warnings.push(args);
|
||||
console.error = () => {};
|
||||
try {
|
||||
(0, eval)(getReactiveRuntime(true));
|
||||
(
|
||||
win as unknown as { __wrnexusHydrateScopes?: (root: unknown) => void }
|
||||
).__wrnexusHydrateScopes?.(win.document);
|
||||
const target = win.document.querySelector("[data-wrn-events]") as unknown as {
|
||||
__wrnexusOutputHandlers?: Record<string, Set<(payload: unknown) => unknown>>;
|
||||
};
|
||||
target.__wrnexusOutputHandlers?.save?.forEach((handler) => handler({ id: 1 }));
|
||||
target.__wrnexusOutputHandlers?.save?.forEach((handler) => handler({ id: 2 }));
|
||||
} finally {
|
||||
console.warn = originalWarn;
|
||||
console.error = originalError;
|
||||
}
|
||||
expect(warnings).toHaveLength(1);
|
||||
expect(String(warnings[0]?.[0])).toContain("WRN-DEV-BINDING-MISSING");
|
||||
expect(String(warnings[0]?.[0])).toContain("missingSave");
|
||||
});
|
||||
|
||||
test("development runtime warns for referenced theme tokens absent from rendered CSS", () => {
|
||||
const win = new Window() as unknown as Window & Record<string, unknown>;
|
||||
win.document.head.innerHTML =
|
||||
`<style>:root { --wire-present-test: red; } .probe { ` +
|
||||
`color: var(--wire-present-test); background: var(--wire-missing-test); }</style>`;
|
||||
win.document.body.innerHTML = `<div class="probe" data-scope=""></div>`;
|
||||
(globalThis as Record<string, unknown>).window = win;
|
||||
(globalThis as Record<string, unknown>).document = win.document;
|
||||
(globalThis as Record<string, unknown>).location = win.location;
|
||||
(globalThis as Record<string, unknown>).NodeFilter = (
|
||||
win as unknown as { NodeFilter: unknown }
|
||||
).NodeFilter;
|
||||
(globalThis as Record<string, unknown>).MutationObserver = (
|
||||
win as unknown as { MutationObserver: unknown }
|
||||
).MutationObserver;
|
||||
(globalThis as Record<string, unknown>).CustomEvent = (
|
||||
win as unknown as { CustomEvent: unknown }
|
||||
).CustomEvent;
|
||||
const warnings: unknown[][] = [];
|
||||
const originalWarn = console.warn;
|
||||
console.warn = (...args: unknown[]) => warnings.push(args);
|
||||
try {
|
||||
(0, eval)(getReactiveRuntime(true));
|
||||
(
|
||||
win as unknown as { __wrnexusHydrateScopes?: (root: unknown) => void }
|
||||
).__wrnexusHydrateScopes?.(win.document);
|
||||
} finally {
|
||||
console.warn = originalWarn;
|
||||
}
|
||||
expect(warnings).toHaveLength(1);
|
||||
expect(String(warnings[0]?.[0])).toContain("WRN-DEV-THEME-TOKEN-MISSING");
|
||||
expect(String(warnings[0]?.[0])).toContain("--wire-missing-test");
|
||||
});
|
||||
|
||||
test("production runtime strips development diagnostics", () => {
|
||||
const production = getReactiveRuntime();
|
||||
expect(production).not.toContain("WRN-DEV-");
|
||||
expect(production).not.toContain("warnOnce");
|
||||
expect(REACTIVE_RUNTIME).toContain("__WRNEXUS_DEV_START__");
|
||||
});
|
||||
|
||||
// --- browser globals + regex literals in client expressions ----------------
|
||||
// Client functions and inline handlers are interpreted by the runtime's own
|
||||
// eval-free expression engine (so a strict CSP needs no unsafe-eval). Anything
|
||||
|
||||
@@ -31,7 +31,8 @@ export function sqliteSessionStore(path = "sessions.db"): SessionBackend {
|
||||
if (!row) return undefined;
|
||||
try {
|
||||
return { data: JSON.parse(row.data) as Record<string, unknown>, expiresAt: row.expiresAt };
|
||||
} catch {
|
||||
} catch (error) {
|
||||
console.warn(`[wrnexus:db] discarded corrupt session '${id}'`, error);
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -12,12 +12,14 @@
|
||||
|
||||
import {
|
||||
getActionRuntime,
|
||||
getComponentControllerRuntime,
|
||||
getReactiveRuntime,
|
||||
getNavRuntime,
|
||||
getRealtimeRuntime,
|
||||
} from "@wrnexus/csr";
|
||||
import {
|
||||
renderStyles,
|
||||
renderActiveThemeCss,
|
||||
renderThemeCss,
|
||||
renderThemeRuntime,
|
||||
type ResolvedTheme,
|
||||
@@ -93,7 +95,9 @@ export function createDevAssetServer(
|
||||
if (pathname.startsWith("/__wrnexus/client/")) {
|
||||
return serveWrnBrowserArtifact(pathname) ?? new Response("Not Found", { status: 404 });
|
||||
}
|
||||
if (pathname === "/__wrnexus/reactive.js") return jsResponse(getReactiveRuntime());
|
||||
if (pathname === "/__wrnexus/reactive.js") return jsResponse(getReactiveRuntime(true));
|
||||
if (pathname === "/__wrnexus/controllers.js")
|
||||
return jsResponse(getComponentControllerRuntime(true));
|
||||
if (pathname === "/__wrnexus/nav.js") return jsResponse(getNavRuntime());
|
||||
if (pathname === "/__wrnexus/realtime.js") return jsResponse(getRealtimeRuntime());
|
||||
if (pathname === "/__wrnexus/actions.js") return jsResponse(getActionRuntime());
|
||||
@@ -119,6 +123,18 @@ export function createDevAssetServer(
|
||||
? cssResponse(renderThemeCss(theme))
|
||||
: new Response("Not Found", { status: 404 });
|
||||
}
|
||||
const activeThemeMatch = /^\/__wrnexus\/theme\/([^/]+)\/([^/]+)\.css$/.exec(pathname);
|
||||
if (activeThemeMatch && theme) {
|
||||
try {
|
||||
const themeName = decodeURIComponent(activeThemeMatch[1]!);
|
||||
const accentPart = decodeURIComponent(activeThemeMatch[2]!);
|
||||
return cssResponse(
|
||||
renderActiveThemeCss(theme, themeName, accentPart === "_" ? undefined : accentPart),
|
||||
);
|
||||
} catch {
|
||||
return new Response("Not Found", { status: 404 });
|
||||
}
|
||||
}
|
||||
if (pathname === "/__wrnexus/theme.js") {
|
||||
return theme
|
||||
? jsResponse(renderThemeRuntime(theme))
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* the running process while the HMR socket morphs fresh HTML into the browser.
|
||||
*/
|
||||
|
||||
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { resolve, dirname, isAbsolute, join } from "node:path";
|
||||
import type { Middleware, Mode, SecurityConfig, SeoConfig } from "@wrnexus/core";
|
||||
import { buildRouter, type Router } from "@wrnexus/router";
|
||||
@@ -272,7 +272,11 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
|
||||
|
||||
const pluginContributions = await pluginRunner.contributions();
|
||||
const virtualModules = new Map<string, string>();
|
||||
const virtualDir = join(appRoot, ".wrnexus", "virtual");
|
||||
// A live dev server and a test process can share an app root. Keeping the
|
||||
// generated artifacts in a process-specific directory prevents a test boot
|
||||
// from deleting the running server's client modules.
|
||||
const cacheDir = join(appRoot, `.wrnexus-${process.pid}`);
|
||||
const virtualDir = join(cacheDir, "virtual");
|
||||
mkdirSync(virtualDir, { recursive: true });
|
||||
for (const [index, module] of pluginContributions.virtualModules.entries()) {
|
||||
const output = join(virtualDir, `plugin-${index}.ts`);
|
||||
@@ -305,7 +309,12 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
|
||||
console.log(`[wrnexus:plugin] contributed routes: ${pluginContributions.routes.length}`);
|
||||
|
||||
const pluginToolbarPanels = await pluginRunner.devToolbarPanels();
|
||||
const componentDirs = [uiComponentsDir(), ...pluginContributions.componentDirs];
|
||||
const bundledUiDir = uiComponentsDir();
|
||||
const componentDirs = [
|
||||
bundledUiDir,
|
||||
join(bundledUiDir, "..", "styles"),
|
||||
...pluginContributions.componentDirs,
|
||||
];
|
||||
|
||||
const hmr = opts.hmr ?? mode === "development";
|
||||
const port = opts.port ?? 3000;
|
||||
@@ -327,12 +336,13 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
|
||||
|
||||
resetDevCache({
|
||||
rootDir: appRoot,
|
||||
cacheDir,
|
||||
enabled: process.env.WRNEXUS_PRESERVE_CACHE !== "1",
|
||||
});
|
||||
|
||||
// Compile every `.wrn` into ONE cache dir at the project root, instead of a
|
||||
// `.wrnexus/` next to each source file (and inside node_modules UI dirs).
|
||||
setCompileCacheDir(join(appRoot, ".wrnexus"));
|
||||
setCompileCacheDir(cacheDir);
|
||||
const theme = resolveThemeConfig(opts.theme);
|
||||
const uiStylesPath = uiCssPath();
|
||||
|
||||
@@ -739,6 +749,7 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
|
||||
unsubscribeDevToolbar?.();
|
||||
server.stop();
|
||||
void pluginRunner.hook("shutdown");
|
||||
rmSync(cacheDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -259,6 +259,7 @@ async function rewriteArtifactImportsAsync(
|
||||
}
|
||||
|
||||
export async function loadModule(file: string): Promise<Record<string, unknown>> {
|
||||
file = resolve(file);
|
||||
let mod = moduleCache.get(file);
|
||||
if (!mod) {
|
||||
mod = (async () => {
|
||||
@@ -631,6 +632,7 @@ export function serveWrnBrowserArtifact(pathname: string): Response | null {
|
||||
|
||||
/** Forget one module and force its next dynamic import to bypass Bun's import cache. */
|
||||
export function invalidateModule(file: string): void {
|
||||
file = resolve(file);
|
||||
moduleCache.delete(file);
|
||||
moduleVersions.set(file, (moduleVersions.get(file) ?? 0) + 1);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
} from "@wrnexus/router";
|
||||
import {
|
||||
getActionRuntime,
|
||||
getComponentControllerRuntime,
|
||||
getReactiveRuntime,
|
||||
getNavRuntime,
|
||||
getRealtimeRuntime,
|
||||
@@ -90,12 +91,18 @@ export interface ProdOptions {
|
||||
inlineStyles?: string;
|
||||
/** `stylesPath` contains theme + UI + app CSS in cascade order. */
|
||||
stylesIncludeFramework?: boolean;
|
||||
/** `stylesPath` already contains the shared Wire UI stylesheet. */
|
||||
stylesIncludeUi?: boolean;
|
||||
/** Absolute path to the pre-built reactive runtime. */
|
||||
reactivePath?: string;
|
||||
/** Absolute path to the on-demand component controller runtime. */
|
||||
controllersPath?: string;
|
||||
/** Absolute directory containing bundled per-WRN browser modules. */
|
||||
clientModulesDir?: string;
|
||||
/** Absolute path to the pre-built theme stylesheet (`theme.css`). */
|
||||
themePath?: string;
|
||||
/** Pre-built active theme/accent stylesheets, loaded on demand. */
|
||||
themeAssetsDir?: string;
|
||||
/** Absolute path to the pre-built theme runtime (`theme.js`). */
|
||||
themeJsPath?: string;
|
||||
/** Resolved theme config: enables `<html data-theme>` + `theme.css` link. */
|
||||
@@ -341,6 +348,10 @@ function createProdAssetServer(opts: ProdOptions): AssetServer {
|
||||
}
|
||||
return new Response(getReactiveRuntime(), { headers: JS_HEADERS });
|
||||
}
|
||||
if (pathname === "/__wrnexus/controllers.js") {
|
||||
if (opts.controllersPath) return serveFile(opts.controllersPath, JS_HEADERS);
|
||||
return new Response(getComponentControllerRuntime(), { headers: JS_HEADERS });
|
||||
}
|
||||
if (pathname === "/__wrnexus/nav.js")
|
||||
return new Response(getNavRuntime(), { headers: JS_HEADERS });
|
||||
if (pathname === "/__wrnexus/realtime.js")
|
||||
@@ -359,6 +370,28 @@ function createProdAssetServer(opts: ProdOptions): AssetServer {
|
||||
return new Response(opts.schemasJs ?? "window.__wireSchemas={};", { headers: JS_HEADERS });
|
||||
}
|
||||
if (pathname === "/__wrnexus/theme.css") return serveFile(opts.themePath, CSS_HEADERS);
|
||||
const activeThemeMatch = /^\/__wrnexus\/theme\/([^/]+)\/([^/]+)\.css$/.exec(pathname);
|
||||
if (activeThemeMatch && opts.theme && opts.themeAssetsDir) {
|
||||
try {
|
||||
const themeName = decodeURIComponent(activeThemeMatch[1]!);
|
||||
const accentPart = decodeURIComponent(activeThemeMatch[2]!);
|
||||
if (!opts.theme.names.includes(themeName))
|
||||
return new Response("Not Found", { status: 404 });
|
||||
if (accentPart !== "_" && !opts.theme.accentNames.some((name) => name === accentPart)) {
|
||||
return new Response("Not Found", { status: 404 });
|
||||
}
|
||||
return serveFile(
|
||||
join(
|
||||
opts.themeAssetsDir,
|
||||
encodeURIComponent(themeName),
|
||||
`${accentPart === "_" ? "_" : encodeURIComponent(accentPart)}.css`,
|
||||
),
|
||||
CSS_HEADERS,
|
||||
);
|
||||
} catch {
|
||||
return new Response("Not Found", { status: 404 });
|
||||
}
|
||||
}
|
||||
if (pathname === "/__wrnexus/theme.js") return serveFile(opts.themeJsPath, JS_HEADERS);
|
||||
if (pathname === "/__wrnexus/ui.css") return serveFile(opts.uiCssPath, CSS_HEADERS);
|
||||
if (pathname === "/__wrnexus/framework.css")
|
||||
@@ -470,6 +503,7 @@ export function createProductionHandlers(
|
||||
i18n: opts.i18n,
|
||||
inlineStyles: opts.inlineStyles,
|
||||
stylesIncludeFramework: opts.stylesIncludeFramework,
|
||||
stylesIncludeUi: opts.stylesIncludeUi,
|
||||
assetVersion: opts.assetVersion,
|
||||
clientRuntimes: opts.clientRuntimes,
|
||||
head: opts.head,
|
||||
|
||||
@@ -66,12 +66,12 @@ import type { StoreDefinition } from "@wrnexus/store";
|
||||
import type { ClientRuntimeDefinition } from "@wrnexus/plugin";
|
||||
import { CacheCoordinator } from "@wrnexus/cache";
|
||||
import { generateServiceWorker } from "@wrnexus/pwa";
|
||||
import { runtimeScriptsForMarkup } from "./plugin-assets.ts";
|
||||
import { collectScripts, usesMobileRuntime } from "./script-selection.ts";
|
||||
export { collectScripts, usesMobileRuntime } from "./script-selection.ts";
|
||||
import {
|
||||
ACCENT_COOKIE,
|
||||
THEME_COOKIE,
|
||||
THEME_CSS_HREF,
|
||||
THEME_JS_HREF,
|
||||
activeThemeCssHref,
|
||||
resolveAccentName,
|
||||
resolveThemeName,
|
||||
type MobileConfig,
|
||||
@@ -81,7 +81,6 @@ import {
|
||||
type TenancyConfig,
|
||||
} from "@wrnexus/styles";
|
||||
import {
|
||||
I18N_JS_HREF,
|
||||
renderI18nData,
|
||||
makeT,
|
||||
resolveLang,
|
||||
@@ -161,6 +160,7 @@ export interface RuntimeDeps {
|
||||
hasFrameworkStyles?: boolean;
|
||||
/** App stylesheet already contains theme + UI CSS and is the only CSS request needed. */
|
||||
stylesIncludeFramework?: boolean;
|
||||
stylesIncludeUi?: boolean;
|
||||
/** Resolved theme config: enables `/__wrnexus/theme.css` + `<html data-theme>`. */
|
||||
theme?: ResolvedTheme;
|
||||
/** Resolved i18n bundle: enables `ctx.t`, `<html lang>`, and `{t:key}` markers. */
|
||||
@@ -941,15 +941,12 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
|
||||
// Order: theme tokens, then Wire UI, then the app stylesheet — so the app's
|
||||
// own CSS (loaded last) can override both the tokens and the UI classes.
|
||||
const headParts: string[] = [];
|
||||
if (deps.hasFrameworkStyles && !deps.stylesIncludeFramework) {
|
||||
if (!deps.theme && deps.hasFrameworkStyles && !deps.stylesIncludeFramework) {
|
||||
headParts.push(
|
||||
`<link rel="stylesheet" href="${versionAssetUrl("/__wrnexus/framework.css", deps.assetVersion)}" />`,
|
||||
);
|
||||
} else if (deps.theme && !deps.stylesIncludeFramework) {
|
||||
const themeHref = versionAssetUrl(THEME_CSS_HREF, deps.assetVersion);
|
||||
headParts.push(`<link rel="stylesheet" href="${themeHref}" />`);
|
||||
}
|
||||
if (deps.hasUi && !deps.hasFrameworkStyles && !deps.stylesIncludeFramework) {
|
||||
if (deps.hasUi && !deps.stylesIncludeUi && !deps.stylesIncludeFramework) {
|
||||
headParts.push(
|
||||
`<link rel="stylesheet" href="${versionAssetUrl("/__wrnexus/ui.css", deps.assetVersion)}" />`,
|
||||
);
|
||||
@@ -1815,7 +1812,14 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
|
||||
if (isMobileRequest) body = revealMobileOnlyHtml(body);
|
||||
|
||||
// i18n: resolve `{t:key}` / `t:attr` markers against the request language.
|
||||
if (deps.i18n) body = translateHtml(body, ctx.t);
|
||||
if (deps.i18n) {
|
||||
body = translateHtml(body, ctx.t);
|
||||
// renderDocument prefers the complete document template when one was
|
||||
// rendered. Keep it in sync with the translated body; otherwise markers
|
||||
// owned by document/page layouts are replaced by the stale pre-translation
|
||||
// template even though page-only responses translate correctly.
|
||||
if (documentTemplate) documentTemplate = body;
|
||||
}
|
||||
|
||||
// Point 3: only ship the JS this page actually uses.
|
||||
const scripts = collectScripts(body, deps.clientRuntimes, deps.navigation).map((script) =>
|
||||
@@ -1832,6 +1836,7 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
|
||||
|
||||
// <html> attributes: no-flash theme (from cookie, validated) + active lang.
|
||||
const attrs: string[] = [];
|
||||
let activeThemeHead = "";
|
||||
|
||||
if (deps.theme) {
|
||||
const themeName = resolveThemeName(ctx.cookies.get(THEME_COOKIE), deps.theme);
|
||||
@@ -1843,6 +1848,13 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
|
||||
if (accentName) {
|
||||
attrs.push(`data-accent="${accentName}"`);
|
||||
}
|
||||
if (!deps.stylesIncludeFramework) {
|
||||
const themeHref = versionAssetUrl(
|
||||
activeThemeCssHref(themeName, accentName),
|
||||
deps.assetVersion,
|
||||
);
|
||||
activeThemeHead = `<link rel="stylesheet" data-wrnexus-theme href="${themeHref}" />`;
|
||||
}
|
||||
}
|
||||
attrs.push(`lang="${safeLanguageTag(language)}"`);
|
||||
if (deps.i18n) attrs.push(`dir="${deps.i18n.direction[language] ?? "ltr"}"`);
|
||||
@@ -1866,6 +1878,7 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
|
||||
pwaEnabled ? `<link rel="manifest" href="/site.webmanifest" />` : "",
|
||||
pwaEnabled ? `<meta name="mobile-web-app-capable" content="yes" />` : "",
|
||||
pwaEnabled ? `<meta name="apple-mobile-web-app-capable" content="yes" />` : "",
|
||||
activeThemeHead,
|
||||
extraHead,
|
||||
]
|
||||
.filter(Boolean)
|
||||
@@ -2436,72 +2449,6 @@ export function normalizeComponentName(name: string): string {
|
||||
return name.toLowerCase().replace(/[-_]/g, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide which framework scripts a rendered page needs. Components are already
|
||||
* server-rendered into the HTML; the only script is the reactive runtime, and
|
||||
* only when the page actually contains a scope or a browser-side API fetch.
|
||||
*/
|
||||
export function collectScripts(
|
||||
body: string,
|
||||
clientRuntimes: readonly ClientRuntimeDefinition[] = [],
|
||||
navigation: { mode?: "auto" | "client" | "document" } = {},
|
||||
): RenderScript[] {
|
||||
const scripts: RenderScript[] = [];
|
||||
if (
|
||||
/\bdata-scope=/.test(body) ||
|
||||
/\bdata-wrnexus-csr=/.test(body) ||
|
||||
/\bdata-wrn-client-template=/.test(body) ||
|
||||
/\bdata-wrn-async=/.test(body)
|
||||
) {
|
||||
scripts.push("/__wrnexus/reactive.js");
|
||||
}
|
||||
if (/\bdata-wrn-action=/.test(body)) scripts.push("/__wrnexus/actions.js");
|
||||
// The theme runtime is only needed when the page can switch themes.
|
||||
if (
|
||||
/\bdata-wire-theme-(toggle|set)\b/.test(body) ||
|
||||
/\bdata-wire-accent-(set|clear)\b/.test(body)
|
||||
) {
|
||||
scripts.push(THEME_JS_HREF);
|
||||
}
|
||||
// Validation: schema descriptors + the generic validator, only for pages with a form.
|
||||
if (/\bdata-schema="[A-Za-z0-9_-]+"/.test(body)) {
|
||||
scripts.push("/__wrnexus/schemas.js", "/__wrnexus/validate.js");
|
||||
}
|
||||
// Language switcher runtime, only when the page has one.
|
||||
if (/\bdata-wire-lang-set\b/.test(body) || /\bselect[^>]*\bdata-wire-lang\b/.test(body)) {
|
||||
scripts.push(I18N_JS_HREF);
|
||||
}
|
||||
// Realtime client, only when the page declares a room.
|
||||
if (/\bdata-room=/.test(body)) {
|
||||
scripts.push("/__wrnexus/realtime.js");
|
||||
}
|
||||
// File-upload runtime (drag-drop + progress), only when a page has an uploader.
|
||||
if (/\bdata-uploader\b/.test(body)) {
|
||||
scripts.push("/__wrnexus/uploader.js");
|
||||
}
|
||||
// Package runtimes are declarative. Components mark the rendered HTML with
|
||||
// `data-wrnexus-runtime="id"`; the corresponding package chunk is loaded
|
||||
// once, without requiring application-authored script tags or public copies.
|
||||
scripts.push(...runtimeScriptsForMarkup(body, clientRuntimes));
|
||||
|
||||
// `auto` is the performance-first default: a fully static page ships no
|
||||
// framework JavaScript and its links use native document navigation. Routes
|
||||
// that already need browser behavior also receive progressive navigation.
|
||||
// `client` preserves the explicit always-on behavior; `document` disables it.
|
||||
const mode = navigation.mode ?? "auto";
|
||||
if (mode === "client" || (mode === "auto" && scripts.length > 0)) {
|
||||
scripts.unshift("/__wrnexus/nav.js");
|
||||
}
|
||||
return scripts;
|
||||
}
|
||||
|
||||
/** Whether rendered markup needs the Capacitor/native browser bridge. */
|
||||
export function usesMobileRuntime(body: string): boolean {
|
||||
return /\b(?:data-mobile-[\w-]+|data-native-(?:browser|mobile|only|requires|unsupported|options)|data-on-wrnexus-(?:browser|mobile)-[\w-]+)\b/.test(
|
||||
body,
|
||||
);
|
||||
}
|
||||
|
||||
function safeLanguageTag(value: string): string {
|
||||
return /^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/.test(value) ? value : "en";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { I18N_JS_HREF } from "@wrnexus/i18n";
|
||||
import type { ClientRuntimeDefinition } from "@wrnexus/plugin";
|
||||
import { THEME_JS_HREF } from "@wrnexus/styles";
|
||||
import type { RenderScript } from "@wrnexus/ssr";
|
||||
import { runtimeScriptsForMarkup } from "./plugin-assets.ts";
|
||||
|
||||
/** Select browser runtimes from capabilities present in rendered markup. */
|
||||
export function collectScripts(
|
||||
body: string,
|
||||
clientRuntimes: readonly ClientRuntimeDefinition[] = [],
|
||||
navigation: { mode?: "auto" | "client" | "document" } = {},
|
||||
): RenderScript[] {
|
||||
const scripts: RenderScript[] = [];
|
||||
if (
|
||||
/\bdata-scope=/.test(body) ||
|
||||
/\bdata-wrnexus-csr=/.test(body) ||
|
||||
/\bdata-wrn-client-template=/.test(body) ||
|
||||
/\bdata-wrn-async=/.test(body)
|
||||
) {
|
||||
scripts.push("/__wrnexus/reactive.js");
|
||||
}
|
||||
if (/\bdata-wrn-action=/.test(body)) scripts.push("/__wrnexus/actions.js");
|
||||
if (
|
||||
/\bdata-wire-theme-(toggle|set)\b/.test(body) ||
|
||||
/\bdata-wire-accent-(set|clear)\b/.test(body)
|
||||
) {
|
||||
scripts.push(THEME_JS_HREF);
|
||||
}
|
||||
if (/\bdata-schema="[A-Za-z0-9_-]+"/.test(body)) {
|
||||
scripts.push("/__wrnexus/schemas.js", "/__wrnexus/validate.js");
|
||||
}
|
||||
if (/\bdata-wire-lang-set\b/.test(body) || /\bselect[^>]*\bdata-wire-lang\b/.test(body)) {
|
||||
scripts.push(I18N_JS_HREF);
|
||||
}
|
||||
if (/\bdata-room=/.test(body)) scripts.push("/__wrnexus/realtime.js");
|
||||
if (/\bdata-uploader\b/.test(body)) scripts.push("/__wrnexus/uploader.js");
|
||||
scripts.push(...runtimeScriptsForMarkup(body, clientRuntimes));
|
||||
|
||||
const mode = navigation.mode ?? "auto";
|
||||
if (mode === "client" || (mode === "auto" && scripts.length > 0)) {
|
||||
scripts.unshift("/__wrnexus/nav.js");
|
||||
}
|
||||
return scripts;
|
||||
}
|
||||
|
||||
/** Whether rendered markup needs the Capacitor/native browser bridge. */
|
||||
export function usesMobileRuntime(body: string): boolean {
|
||||
return /\b(?:data-mobile-[\w-]+|data-native-(?:browser|mobile|only|requires|unsupported|options)|data-on-wrnexus-(?:browser|mobile)-[\w-]+)\b/.test(
|
||||
body,
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { afterAll, describe, expect, test } from "bun:test";
|
||||
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { getAuthzCatalog, hasAuthzCatalog } from "@wrnexus/authz";
|
||||
import { startServer } from "../src/index.ts";
|
||||
@@ -63,9 +63,14 @@ export default defineAuthz({ permissions: { "post:read": { title: "View posts" }
|
||||
try {
|
||||
expect(hasAuthzCatalog()).toBe(true);
|
||||
expect(getAuthzCatalog().permissions.has("post:read")).toBe(true);
|
||||
const cacheDir = join(appDir, "..", `.wrnexus-${process.pid}`);
|
||||
mkdirSync(cacheDir, { recursive: true });
|
||||
writeFileSync(join(cacheDir, "generated.js"), "generated", "utf8");
|
||||
expect(existsSync(cacheDir)).toBe(true);
|
||||
} finally {
|
||||
server.stop();
|
||||
}
|
||||
expect(existsSync(join(appDir, "..", `.wrnexus-${process.pid}`))).toBe(false);
|
||||
});
|
||||
|
||||
test("an app with no app/authz declarations boots without throwing", async () => {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { join, resolve } from "node:path";
|
||||
|
||||
const root = resolve(import.meta.dir, "../../..");
|
||||
|
||||
test("per-process dev caches are excluded from root and example project tools", () => {
|
||||
const eslint = readFileSync(join(root, "eslint.config.js"), "utf8");
|
||||
const tsconfig = JSON.parse(readFileSync(join(root, "tsconfig.json"), "utf8")) as {
|
||||
exclude?: string[];
|
||||
};
|
||||
const prettier = readFileSync(join(root, ".prettierignore"), "utf8");
|
||||
|
||||
expect(eslint).toContain('"**/.wrnexus-*/**"');
|
||||
expect(tsconfig.exclude).toContain("**/.wrnexus-*");
|
||||
expect(prettier).toContain("**/.wrnexus-*/**");
|
||||
|
||||
const projects = ["basic-app", "inter-app-api-showcase"].map((name) =>
|
||||
join(root, "examples", name),
|
||||
);
|
||||
const caches = projects.map((project) => join(project, ".wrnexus-123"));
|
||||
|
||||
for (const project of projects) {
|
||||
const projectEslint = readFileSync(join(project, "eslint.config.js"), "utf8");
|
||||
const projectTsconfig = JSON.parse(readFileSync(join(project, "tsconfig.json"), "utf8")) as {
|
||||
exclude?: string[];
|
||||
};
|
||||
const projectPrettier = readFileSync(join(project, ".prettierignore"), "utf8");
|
||||
|
||||
expect(projectEslint).toContain('".wrnexus-*/**"');
|
||||
expect(projectEslint).toContain('"**/.wrnexus-*/**"');
|
||||
expect(projectTsconfig.exclude).toContain(".wrnexus-*");
|
||||
expect(projectTsconfig.exclude).toContain("**/.wrnexus-*");
|
||||
expect(projectPrettier).toContain(".wrnexus-*/");
|
||||
expect(projectPrettier).toContain("**/.wrnexus-*/");
|
||||
}
|
||||
|
||||
for (const cache of caches) {
|
||||
mkdirSync(cache, { recursive: true });
|
||||
writeFileSync(join(cache, "broken.client.mjs"), "const = ;\n", "utf8");
|
||||
writeFileSync(join(cache, "broken.d.ts"), "type = ;\n", "utf8");
|
||||
}
|
||||
try {
|
||||
const lint = Bun.spawnSync(
|
||||
[
|
||||
"bunx",
|
||||
"eslint",
|
||||
"--no-warn-ignored",
|
||||
...caches.flatMap((cache) => [
|
||||
join(cache, "broken.client.mjs"),
|
||||
join(cache, "broken.d.ts"),
|
||||
]),
|
||||
],
|
||||
{ cwd: root },
|
||||
);
|
||||
expect(lint.exitCode).toBe(0);
|
||||
} finally {
|
||||
for (const cache of caches) rmSync(cache, { recursive: true, force: true });
|
||||
}
|
||||
}, 15_000);
|
||||
@@ -0,0 +1,114 @@
|
||||
import { afterEach, expect, test } from "bun:test";
|
||||
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { basename, join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { buildRouter } from "@wrnexus/router";
|
||||
import { resolveI18n } from "@wrnexus/i18n";
|
||||
import { createHandlers, type RuntimeDeps } from "../src/runtime.ts";
|
||||
|
||||
const roots: string[] = [];
|
||||
afterEach(() => roots.splice(0).forEach((root) => rmSync(root, { recursive: true, force: true })));
|
||||
|
||||
test("a component nested in another component's slot keeps its boundary", async () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "wrnexus-component-slot-"));
|
||||
roots.push(root);
|
||||
const app = join(root, "app");
|
||||
mkdirSync(join(app, "pages"), { recursive: true });
|
||||
mkdirSync(join(app, "components"), { recursive: true });
|
||||
writeFileSync(join(app, "pages/index.ts"), "export default () => '';\n");
|
||||
writeFileSync(join(app, "components/Card.wrn"), "component Card { view { <slot /> } }\n");
|
||||
writeFileSync(join(app, "components/Badge.wrn"), "component Badge { view { <b></b> } }\n");
|
||||
|
||||
const handlers = createHandlers({
|
||||
mode: "development",
|
||||
hmr: false,
|
||||
router: buildRouter(app),
|
||||
loadModule: async (file) => {
|
||||
if (basename(file) === "Card.wrn") {
|
||||
return { render: () => '<article class="card"><slot /></article>' };
|
||||
}
|
||||
if (basename(file) === "Badge.wrn") {
|
||||
return { render: (props: Record<string, string>) => `<b class="badge">${props.label}</b>` };
|
||||
}
|
||||
return {
|
||||
default: () =>
|
||||
'<div data-component="Card"><div data-component="Badge" label="x"></div></div>',
|
||||
};
|
||||
},
|
||||
getMiddleware: async () => [],
|
||||
assets: { serve: async () => null },
|
||||
} satisfies RuntimeDeps);
|
||||
|
||||
const response = await handlers.fetch(new Request("https://example.test/"), {
|
||||
upgrade: () => false,
|
||||
});
|
||||
const html = await response!.text();
|
||||
expect(html).toContain('<article class="card">');
|
||||
expect(html).toContain('<b class="badge">x</b>');
|
||||
expect(html).not.toContain('data-component="Badge"');
|
||||
});
|
||||
|
||||
test("translated text is present in the raw server response", async () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "wrnexus-ssr-i18n-"));
|
||||
roots.push(root);
|
||||
const app = join(root, "app");
|
||||
mkdirSync(join(app, "pages"), { recursive: true });
|
||||
writeFileSync(join(app, "pages/index.ts"), "export default () => '';\n");
|
||||
|
||||
const handlers = createHandlers({
|
||||
mode: "development",
|
||||
hmr: false,
|
||||
router: buildRouter(app),
|
||||
i18n: resolveI18n({ en: { nav: { title: "Navigation" } } }, { default: "en" }),
|
||||
loadModule: async () => ({
|
||||
default: () => '<nav><span data-t="nav.title"></span></nav>',
|
||||
}),
|
||||
getMiddleware: async () => [],
|
||||
assets: { serve: async () => null },
|
||||
} satisfies RuntimeDeps);
|
||||
|
||||
const response = await handlers.fetch(new Request("https://example.test/"), {
|
||||
upgrade: () => false,
|
||||
});
|
||||
const html = await response!.text();
|
||||
expect(html).toContain('<span data-t="nav.title">Navigation</span>');
|
||||
});
|
||||
|
||||
test("translated layout text survives document-template rendering", async () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "wrnexus-layout-ssr-i18n-"));
|
||||
roots.push(root);
|
||||
const app = join(root, "app");
|
||||
mkdirSync(join(app, "pages"), { recursive: true });
|
||||
mkdirSync(join(app, "layouts"), { recursive: true });
|
||||
writeFileSync(join(app, "pages/index.ts"), "export default () => '';\n");
|
||||
writeFileSync(join(app, "layouts/public.wrn"), "layout Public { view { <slot /> } }\n");
|
||||
writeFileSync(
|
||||
join(app, "layouts/document.wrn"),
|
||||
"layout Document { view { <html><head></head><body><slot /></body></html> } }\n",
|
||||
);
|
||||
|
||||
const handlers = createHandlers({
|
||||
mode: "development",
|
||||
hmr: false,
|
||||
router: buildRouter(app),
|
||||
i18n: resolveI18n({ en: { nav: { home: "Home" } } }, { default: "en" }),
|
||||
loadModule: async (file) => {
|
||||
if (basename(file) === "public.wrn") {
|
||||
return { render: () => '<nav><span data-t="nav.home"></span></nav>' };
|
||||
}
|
||||
if (basename(file) === "document.wrn") {
|
||||
return { render: () => "<html><head></head><body><slot /></body></html>" };
|
||||
}
|
||||
return { default: () => "<main>Page</main>", layout: "public" };
|
||||
},
|
||||
getMiddleware: async () => [],
|
||||
assets: { serve: async () => null },
|
||||
} satisfies RuntimeDeps);
|
||||
|
||||
const response = await handlers.fetch(new Request("https://example.test/"), {
|
||||
upgrade: () => false,
|
||||
});
|
||||
const html = await response!.text();
|
||||
expect(html).toContain('<span data-t="nav.home">Home</span>');
|
||||
expect(html).not.toContain('<span data-t="nav.home"></span>');
|
||||
});
|
||||
@@ -83,7 +83,7 @@ test("invalidateModule loads changed server modules without restarting the proce
|
||||
}
|
||||
});
|
||||
|
||||
test("invalidateModule recompiles changed WRN files in-process", async () => {
|
||||
test("invalidateModule recompiles changed WRN files with watcher-normalized separators", async () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "wrnexus-hmr-"));
|
||||
const file = join(root, "page.wrn");
|
||||
setCompileCacheDir(join(root, ".wrnexus"));
|
||||
@@ -94,7 +94,7 @@ test("invalidateModule recompiles changed WRN files in-process", async () => {
|
||||
expect(String(first({}))).toContain("First");
|
||||
|
||||
writeFileSync(file, "page Home { view { <h1>Second</h1> } }\n");
|
||||
invalidateModule(file);
|
||||
invalidateModule(file.replace(/\\/g, "/"));
|
||||
const second = (await loadModule(file)).default as (ctx: unknown) => unknown;
|
||||
expect(String(second({}))).toContain("Second");
|
||||
} finally {
|
||||
|
||||
@@ -22,7 +22,7 @@ export function webVitalsClient(options: WebVitalsClientOptions = {}): string {
|
||||
function send(name, value) {
|
||||
var body = JSON.stringify({ name: name, value: value, rating: rating(name, value), route: location.pathname, navigationType: performance.getEntriesByType("navigation")[0]?.type });
|
||||
if (navigator.sendBeacon) navigator.sendBeacon(endpoint, new Blob([body], { type: "application/json" }));
|
||||
else fetch(endpoint, { method: "POST", headers: { "content-type": "application/json" }, body: body, keepalive: true }).catch(function(){});
|
||||
else fetch(endpoint, { method: "POST", headers: { "content-type": "application/json" }, body: body, keepalive: true }).catch(function(error){ console.warn("[wrnexus:observability] web-vitals delivery failed", error); });
|
||||
}
|
||||
function flush() {
|
||||
if (flushed) return;
|
||||
@@ -31,9 +31,13 @@ export function webVitalsClient(options: WebVitalsClientOptions = {}): string {
|
||||
if (inp) send("INP", inp);
|
||||
if (cls) send("CLS", cls);
|
||||
}
|
||||
try { new PerformanceObserver(function(list){ var entries=list.getEntries(); var last=entries[entries.length-1]; if(last) lcp = Math.max(lcp, last.startTime); }).observe({type:"largest-contentful-paint",buffered:true}); } catch(_) {}
|
||||
try { new PerformanceObserver(function(list){ list.getEntries().forEach(function(e){ if(!e.hadRecentInput) cls += e.value; }); }).observe({type:"layout-shift",buffered:true}); } catch(_) {}
|
||||
try { new PerformanceObserver(function(list){ list.getEntries().forEach(function(e){ inp=Math.max(inp,e.duration||0); }); }).observe({type:"event",durationThreshold:40,buffered:true}); } catch(_) {}
|
||||
function observe(type, callback, options) {
|
||||
try { new PerformanceObserver(callback).observe(options); }
|
||||
catch(error) { console.warn("[wrnexus:observability] PerformanceObserver unavailable for " + type, error); }
|
||||
}
|
||||
observe("largest-contentful-paint", function(list){ var entries=list.getEntries(); var last=entries[entries.length-1]; if(last) lcp = Math.max(lcp, last.startTime); }, {type:"largest-contentful-paint",buffered:true});
|
||||
observe("layout-shift", function(list){ list.getEntries().forEach(function(e){ if(!e.hadRecentInput) cls += e.value; }); }, {type:"layout-shift",buffered:true});
|
||||
observe("event", function(list){ list.getEntries().forEach(function(e){ inp=Math.max(inp,e.duration||0); }); }, {type:"event",durationThreshold:40,buffered:true});
|
||||
addEventListener("visibilitychange", function(){ if(document.visibilityState === "hidden") flush(); });
|
||||
addEventListener("pagehide", flush, { once: true });
|
||||
})();`;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import type { MetricPoint } from "./metrics.ts";
|
||||
import type { LogRecord } from "./logging.ts";
|
||||
import type { MetricExporter } from "./server.ts";
|
||||
import type { SpanExporter, SpanRecord, TraceContext } from "./trace.ts";
|
||||
import { reportObservabilityFailure, type ObservabilityDiagnostic } from "./diagnostics.ts";
|
||||
|
||||
export type FrameworkSpanKind =
|
||||
"database" | "cache" | "queue" | "realtime" | "server-action" | "application";
|
||||
@@ -26,6 +27,7 @@ export function createOperationTracer(
|
||||
context?: () => Partial<TraceContext>;
|
||||
now?: () => number;
|
||||
onExportError?: (error: unknown) => void;
|
||||
diagnostic?: ObservabilityDiagnostic;
|
||||
} = {},
|
||||
): OperationTracer {
|
||||
const now = options.now ?? Date.now;
|
||||
@@ -66,7 +68,9 @@ export function createOperationTracer(
|
||||
try {
|
||||
await options.exporter?.export([record]);
|
||||
} catch (error) {
|
||||
options.onExportError?.(error);
|
||||
if (options.onExportError) options.onExportError(error);
|
||||
else
|
||||
reportObservabilityFailure("operation span export failed", error, options.diagnostic);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createTracer, type Context, type Middleware } from "@wrnexus/core";
|
||||
import { reportObservabilityFailure, type ObservabilityDiagnostic } from "./diagnostics.ts";
|
||||
|
||||
export interface TraceContext {
|
||||
version: "00";
|
||||
@@ -63,6 +64,7 @@ export interface TraceMiddlewareOptions {
|
||||
routeName?: (ctx: Context) => string;
|
||||
serverTiming?: boolean;
|
||||
onExportError?: (error: unknown, span: SpanRecord) => void | Promise<void>;
|
||||
diagnostic?: ObservabilityDiagnostic;
|
||||
}
|
||||
|
||||
export function traceMiddleware(options: TraceMiddlewareOptions = {}): Middleware {
|
||||
@@ -131,7 +133,8 @@ export function traceMiddleware(options: TraceMiddlewareOptions = {}): Middlewar
|
||||
await options.onSpan?.(span);
|
||||
await options.exporter?.export([span]);
|
||||
} catch (error) {
|
||||
await options.onExportError?.(error, span);
|
||||
if (options.onExportError) await options.onExportError(error, span);
|
||||
else reportObservabilityFailure("trace export failed", error, options.diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,4 +93,14 @@ describe("observability integrations", () => {
|
||||
expect(await profile("render", () => "ok")).toBe("ok");
|
||||
expect(profiles[0]).toMatchObject({ name: "render", durationMs: 4 });
|
||||
});
|
||||
|
||||
test("operation exporter failures reach diagnostics without recursive telemetry", async () => {
|
||||
const diagnostics: string[] = [];
|
||||
const tracer = createOperationTracer({
|
||||
exporter: { export: () => Promise.reject(new Error("offline")) },
|
||||
diagnostic: (message) => diagnostics.push(message),
|
||||
});
|
||||
await expect(tracer.span("database", "query", async () => 1)).resolves.toBe(1);
|
||||
expect(diagnostics).toEqual(["operation span export failed"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,6 +8,8 @@ describe("@wrnexus/observability", () => {
|
||||
expect(client).toContain('addEventListener("pagehide", flush');
|
||||
expect(client).not.toContain('send("INP",max)');
|
||||
expect(client).not.toContain('send("LCP", last.startTime)');
|
||||
expect(client).toContain("web-vitals delivery failed");
|
||||
expect(client).toContain("PerformanceObserver unavailable for");
|
||||
});
|
||||
|
||||
test("records deterministic counters and histograms", () => {
|
||||
|
||||
@@ -81,6 +81,22 @@ describe("production observability operations", () => {
|
||||
expect(failures).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("reports exporter failures through the non-recursive diagnostic hook", async () => {
|
||||
const diagnostics: Array<{ message: string; error: unknown }> = [];
|
||||
const ctx = createContext(
|
||||
new Request("https://example.test/"),
|
||||
new URL("https://example.test/"),
|
||||
);
|
||||
const middleware = traceMiddleware({
|
||||
random: (target) => target.fill(8),
|
||||
exporter: { export: () => Promise.reject(new Error("offline")) },
|
||||
diagnostic: (message, error) => diagnostics.push({ message, error }),
|
||||
});
|
||||
expect((await middleware(ctx, () => new Response("ok"))).status).toBe(200);
|
||||
expect(diagnostics).toHaveLength(1);
|
||||
expect(diagnostics[0]?.message).toBe("trace export failed");
|
||||
});
|
||||
|
||||
test("exports OTLP JSON traces and metrics", async () => {
|
||||
const requests: unknown[] = [];
|
||||
const send = (async (_url: URL | RequestInfo, init?: RequestInit) => {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { createStoreContainer } from "./index.ts";
|
||||
|
||||
let globalContainer: ReturnType<typeof createStoreContainer> | undefined;
|
||||
const storeGlobal = globalThis as typeof globalThis & {
|
||||
__wrnexusStoreContainer?: ReturnType<typeof createStoreContainer>;
|
||||
};
|
||||
|
||||
export function browserStoreContainer(hydration: Record<string, unknown> = {}) {
|
||||
if (!globalContainer) {
|
||||
@@ -17,7 +20,7 @@ export function browserStoreContainer(hydration: Record<string, unknown> = {}) {
|
||||
}
|
||||
},
|
||||
});
|
||||
(globalThis as any).__wrnexusStoreContainer = globalContainer;
|
||||
storeGlobal.__wrnexusStoreContainer = globalContainer;
|
||||
}
|
||||
return globalContainer;
|
||||
}
|
||||
@@ -25,5 +28,5 @@ export function browserStoreContainer(hydration: Record<string, unknown> = {}) {
|
||||
export async function resetBrowserStores() {
|
||||
await globalContainer?.dispose();
|
||||
globalContainer = undefined;
|
||||
delete (globalThis as any).__wrnexusStoreContainer;
|
||||
delete storeGlobal.__wrnexusStoreContainer;
|
||||
}
|
||||
|
||||
+20
-13
@@ -27,7 +27,7 @@ function readonlySnapshot<S extends object>(state: S): Readonly<S> {
|
||||
return Object.freeze(clone(state));
|
||||
}
|
||||
|
||||
function storageFor(kind: StorePersistenceConfig<any>["storage"]): Storage | null {
|
||||
function storageFor(kind: StorePersistenceConfig<object>["storage"]): Storage | null {
|
||||
if (typeof window === "undefined") return null;
|
||||
if (kind === "local") return window.localStorage;
|
||||
if (kind === "session") return window.sessionStorage;
|
||||
@@ -91,7 +91,10 @@ export class StoreContainer {
|
||||
readonly runtime: "server" | "client";
|
||||
readonly request?: unknown;
|
||||
readonly routeId?: string;
|
||||
private readonly instances = new Map<string, StoreInstance<any, any, any>>();
|
||||
private readonly instances = new Map<
|
||||
string,
|
||||
StoreInstance<Record<string, unknown>, Record<string, unknown>, Record<string, StoreFunction>>
|
||||
>();
|
||||
private readonly hydration: Record<string, unknown>;
|
||||
private readonly onMutation?: (mutation: StoreMutation) => void;
|
||||
private readonly lastMutations = new Map<string, StoreMutation>();
|
||||
@@ -159,7 +162,7 @@ export class StoreContainer {
|
||||
Object.fromEntries(
|
||||
Reflect.ownKeys(instance.computed).map((key) => [
|
||||
String(key),
|
||||
(instance.computed as any)[key],
|
||||
Reflect.get(instance.computed, key),
|
||||
]),
|
||||
),
|
||||
),
|
||||
@@ -314,7 +317,7 @@ export function createStoreInstance<
|
||||
|
||||
const publicState = new Proxy({} as State, {
|
||||
get(_target, property) {
|
||||
return (mutableState as any)[property];
|
||||
return Reflect.get(mutableState, property);
|
||||
},
|
||||
set(_target, property) {
|
||||
throw new TypeError(
|
||||
@@ -334,7 +337,7 @@ export function createStoreInstance<
|
||||
return {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
value: (mutableState as any)[property],
|
||||
value: Reflect.get(mutableState, property),
|
||||
writable: false,
|
||||
};
|
||||
},
|
||||
@@ -360,7 +363,7 @@ export function createStoreInstance<
|
||||
mutate("$reset", () => {
|
||||
const next = createInitialState();
|
||||
for (const key of Object.keys(mutableState)) {
|
||||
if (!(key in next)) delete (mutableState as any)[key];
|
||||
if (!(key in next)) Reflect.deleteProperty(mutableState, key);
|
||||
}
|
||||
Object.assign(mutableState, next);
|
||||
});
|
||||
@@ -382,16 +385,20 @@ export function createStoreInstance<
|
||||
definitions.find((entry) => entry.runtime === "shared") ??
|
||||
definitions.find((entry) => entry.runtime === "legacy");
|
||||
if (!selected) continue;
|
||||
(actions as any)[name] = async (...args: unknown[]) => {
|
||||
Reflect.set(actions, name, async (...args: unknown[]) => {
|
||||
currentAction = name;
|
||||
internalMutation = true;
|
||||
try {
|
||||
return await (selected.handler as any)(context, ...args);
|
||||
const handler = selected.handler as unknown as (
|
||||
context: StoreActionContext<State>,
|
||||
...args: unknown[]
|
||||
) => unknown;
|
||||
return await handler(context, ...args);
|
||||
} finally {
|
||||
currentAction = "direct";
|
||||
internalMutation = false;
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const computed = new Proxy({} as C, {
|
||||
@@ -461,7 +468,7 @@ export function createStoreInstance<
|
||||
const result: Partial<State> = {};
|
||||
const serverKeys = new Set(Object.keys(definition.createServerState?.() ?? {}));
|
||||
for (const [key, value] of Object.entries(mutableState)) {
|
||||
if (!serverKeys.has(key)) (result as any)[key] = clone(value);
|
||||
if (!serverKeys.has(key)) Reflect.set(result, key, clone(value));
|
||||
}
|
||||
return result;
|
||||
},
|
||||
@@ -479,9 +486,9 @@ export function createStoreInstance<
|
||||
return new Proxy(core as StoreInstance<State, C, A>, {
|
||||
get(target, property, receiver) {
|
||||
if (Reflect.has(target, property)) return Reflect.get(target, property, receiver);
|
||||
if (property in actions) return (actions as any)[property];
|
||||
if (property in (definition.computed ?? {})) return (computed as any)[property];
|
||||
if (property in mutableState) return (publicState as any)[property];
|
||||
if (property in actions) return Reflect.get(actions, property);
|
||||
if (property in (definition.computed ?? {})) return Reflect.get(computed, property);
|
||||
if (property in mutableState) return Reflect.get(publicState, property);
|
||||
return undefined;
|
||||
},
|
||||
set(_target, property) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
export type StoreKind = "global" | "page";
|
||||
export type StoreRuntime = "shared" | "client" | "server" | "legacy";
|
||||
export type PersistenceStorage = "memory" | "session" | "local";
|
||||
export type StoreFunction = (...args: any[]) => any;
|
||||
/** Broad callable constraint that preserves each action's concrete parameters and return type. */
|
||||
export type StoreFunction = (...args: any[]) => unknown;
|
||||
|
||||
export type StoreCombinedState<
|
||||
S extends object,
|
||||
|
||||
@@ -61,10 +61,13 @@ export {
|
||||
THEME_PALETTE_NAMES,
|
||||
THEME_COOKIE,
|
||||
THEME_CSS_HREF,
|
||||
THEME_CSS_PREFIX,
|
||||
THEME_JS_HREF,
|
||||
activeThemeCssHref,
|
||||
resolveThemeConfig,
|
||||
resolveThemeName,
|
||||
renderThemeCss,
|
||||
renderActiveThemeCss,
|
||||
renderThemeRuntime,
|
||||
defineThemeTokens,
|
||||
themeVar,
|
||||
|
||||
@@ -121,6 +121,7 @@ export interface ResolvedTheme {
|
||||
export const THEME_COOKIE = "wire-theme";
|
||||
export const ACCENT_COOKIE = "wire-accent";
|
||||
export const THEME_CSS_HREF = "/__wrnexus/theme.css";
|
||||
export const THEME_CSS_PREFIX = "/__wrnexus/theme/";
|
||||
export const THEME_JS_HREF = "/__wrnexus/theme.js";
|
||||
|
||||
/**
|
||||
@@ -519,6 +520,44 @@ function selectorValue(value: string): string {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
function globalThemeCss(): string {
|
||||
return (
|
||||
'\nhtml,body{font-family:var(--wire-font-sans,"Plus Jakarta Sans",ui-sans-serif,system-ui,sans-serif);}\n' +
|
||||
":root{--wire-radius-sm:0.55rem;--wire-radius-md:0.9rem;--wire-radius-lg:1.35rem;" +
|
||||
"--wire-shadow-1:0 1px 2px color-mix(in srgb, black 22%, transparent)," +
|
||||
"0 10px 30px color-mix(in srgb, black 14%, transparent);}\n" +
|
||||
"wrn-slot{display:contents;}\n" +
|
||||
"[data-for]{display:none !important;}\n"
|
||||
);
|
||||
}
|
||||
|
||||
/** URL for the small stylesheet containing only one active theme/accent pair. */
|
||||
export function activeThemeCssHref(themeName: string, accentName?: string): string {
|
||||
return `${THEME_CSS_PREFIX}${encodeURIComponent(themeName)}/${accentName ? encodeURIComponent(accentName) : "_"}.css`;
|
||||
}
|
||||
|
||||
/** Render only the tokens needed for the current SSR-selected theme and accent. */
|
||||
export function renderActiveThemeCss(
|
||||
theme: ResolvedTheme,
|
||||
themeName: string,
|
||||
accentName?: string,
|
||||
): string {
|
||||
const selectedTheme = theme.themes[themeName];
|
||||
if (!selectedTheme) throw new Error(`Unknown theme '${themeName}'.`);
|
||||
const blocks = [`:root{${tokensToDeclarations(selectedTheme)}}`];
|
||||
if (accentName) {
|
||||
if (!theme.accentNames.includes(accentName as ThemePaletteName)) {
|
||||
throw new Error(`Unknown theme accent '${accentName}'.`);
|
||||
}
|
||||
blocks.push(
|
||||
`:root{${tokensToDeclarations(
|
||||
paletteTokens(THEME_PALETTES[accentName as ThemePaletteName], themeScheme(selectedTheme)),
|
||||
)}}`,
|
||||
);
|
||||
}
|
||||
return blocks.join("\n") + globalThemeCss();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the theme stylesheet.
|
||||
*
|
||||
@@ -559,36 +598,7 @@ export function renderThemeCss(theme: ResolvedTheme): string {
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
blocks.join("\n") +
|
||||
'\nhtml,body{font-family:var(--wire-font-sans,"Plus Jakarta Sans",ui-sans-serif,system-ui,sans-serif);}\n' +
|
||||
// <wrn-slot> wraps content spliced into a component's <slot> so the client
|
||||
// runtime can tell which scope authored it. It is a pure ownership marker
|
||||
// and must never introduce a box: an unknown element would otherwise
|
||||
// default to display:inline and break the component's own flex/grid layout.
|
||||
/*
|
||||
* Shape and elevation tokens the Wire UI stylesheet depends on.
|
||||
*
|
||||
* ui.css references --wire-radius-sm (51 times) and --wire-radius-md (21)
|
||||
* but nothing ever defined them, so every component using them fell back
|
||||
* to square corners in any app that did not declare them itself.
|
||||
* --wire-shadow-1 had the same problem: the showcase declared it
|
||||
* privately, so the library looked right there and flat everywhere else.
|
||||
* Defining them with the rest of the theme keeps a component looking the
|
||||
* same in every app; an app can still override them.
|
||||
*/
|
||||
":root{--wire-radius-sm:0.55rem;--wire-radius-md:0.9rem;--wire-radius-lg:1.35rem;" +
|
||||
"--wire-shadow-1:0 1px 2px color-mix(in srgb, black 22%, transparent)," +
|
||||
"0 10px 30px color-mix(in srgb, black 14%, transparent);}\n" +
|
||||
"wrn-slot{display:contents;}\n" +
|
||||
// A [data-for] element is the loop TEMPLATE, not a rendered row: the server
|
||||
// ships it with mustaches unresolved ({item.title}) and hydration replaces
|
||||
// it with a comment marker. Painted as-is it flashes one blank, literal
|
||||
// row on every page load before the runtime boots. Hiding it costs
|
||||
// nothing after hydration -- the runtime strips data-for from the clones
|
||||
// it renders, so this only ever matches the template itself.
|
||||
"[data-for]{display:none !important;}\n"
|
||||
);
|
||||
return blocks.join("\n") + globalThemeCss();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -603,6 +613,7 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
|
||||
const names = JSON.stringify(theme.names);
|
||||
const accentNames = JSON.stringify(theme.accentNames);
|
||||
const defaultAccent = JSON.stringify(theme.defaultAccent ?? null);
|
||||
const cssPrefix = JSON.stringify(THEME_CSS_PREFIX);
|
||||
|
||||
return `(function(){
|
||||
var THEME_COOKIE=${JSON.stringify(THEME_COOKIE)};
|
||||
@@ -611,6 +622,7 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
|
||||
var ACCENTS=${accentNames};
|
||||
var DEFAULT_THEME=${JSON.stringify(theme.default)};
|
||||
var DEFAULT_ACCENT=${defaultAccent};
|
||||
var THEME_CSS_PREFIX=${cssPrefix};
|
||||
var MAX_AGE=31536000;
|
||||
var el=document.documentElement;
|
||||
|
||||
@@ -637,6 +649,13 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
|
||||
document.cookie=encodeURIComponent(name)+"=;path=/;max-age=0;samesite=lax"+secure;
|
||||
}
|
||||
|
||||
function syncStylesheet(themeName,accentName){
|
||||
var link=document.querySelector("link[data-wrnexus-theme]");
|
||||
if(!link)return;
|
||||
var accent=accentName?encodeURIComponent(accentName):"_";
|
||||
link.href=THEME_CSS_PREFIX+encodeURIComponent(themeName)+"/"+accent+".css";
|
||||
}
|
||||
|
||||
function getTheme(){
|
||||
var current=el.getAttribute("data-theme")||readCookie(THEME_COOKIE)||DEFAULT_THEME;
|
||||
return THEMES.indexOf(current)>=0?current:DEFAULT_THEME;
|
||||
@@ -665,6 +684,7 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
|
||||
if(THEMES.indexOf(name)<0)return;
|
||||
el.setAttribute("data-theme",name);
|
||||
writeCookie(THEME_COOKIE,name);
|
||||
syncStylesheet(name,getAccent());
|
||||
syncThemeButtons(document);
|
||||
window.dispatchEvent(new CustomEvent("wrnexus:theme-changed",{detail:{theme:name}}));
|
||||
}
|
||||
@@ -678,6 +698,7 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
|
||||
if(ACCENTS.indexOf(name)<0)return;
|
||||
el.setAttribute("data-accent",name);
|
||||
writeCookie(ACCENT_COOKIE,name);
|
||||
syncStylesheet(getTheme(),name);
|
||||
syncAccentButtons(document);
|
||||
window.dispatchEvent(new CustomEvent("wrnexus:accent-changed",{detail:{accent:name}}));
|
||||
}
|
||||
@@ -685,6 +706,7 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
|
||||
function clearAccent(){
|
||||
el.removeAttribute("data-accent");
|
||||
deleteCookie(ACCENT_COOKIE);
|
||||
syncStylesheet(getTheme(),null);
|
||||
syncAccentButtons(document);
|
||||
window.dispatchEvent(new CustomEvent("wrnexus:accent-changed",{detail:{accent:null}}));
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
resolveThemeConfig,
|
||||
resolveThemeName,
|
||||
renderThemeCss,
|
||||
renderActiveThemeCss,
|
||||
activeThemeCssHref,
|
||||
renderThemeRuntime,
|
||||
} from "../src/index.ts";
|
||||
|
||||
@@ -85,6 +87,17 @@ test("renderThemeCss emits :root + per-theme blocks and --wire-* vars", () => {
|
||||
expect(css).toContain("font-family:var(--wire-font-sans");
|
||||
});
|
||||
|
||||
test("active theme CSS contains only the selected theme and accent", () => {
|
||||
const theme = resolveThemeConfig({ default: "dark", palette: "rose" });
|
||||
const full = renderThemeCss(theme);
|
||||
const active = renderActiveThemeCss(theme, "dark", "rose");
|
||||
expect(active).toContain(":root{");
|
||||
expect(active).not.toContain('[data-theme="light"]');
|
||||
expect(active.length).toBeLessThan(full.length / 4);
|
||||
expect(activeThemeCssHref("dark", "rose")).toBe("/__wrnexus/theme/dark/rose.css");
|
||||
expect(() => renderActiveThemeCss(theme, "missing")).toThrow("Unknown theme");
|
||||
});
|
||||
|
||||
test("renderThemeRuntime bakes the theme names for cycling", () => {
|
||||
const t = resolveThemeConfig();
|
||||
const js = renderThemeRuntime(t);
|
||||
|
||||
@@ -439,7 +439,14 @@ export function parse(source: string): PageAst {
|
||||
// props { name: Type = <default>; @event name = function }
|
||||
lx.next();
|
||||
expect("lbrace");
|
||||
while (lx.peek().type !== "rbrace") {
|
||||
while (true) {
|
||||
if (lx.startsWithBlockComment()) {
|
||||
throw new ParseError(
|
||||
"Block comments are not allowed inside props {}; use // line comments instead",
|
||||
"WRN-PROPS-BLOCK-COMMENT",
|
||||
);
|
||||
}
|
||||
if (lx.peek().type === "rbrace") break;
|
||||
const t = lx.peek();
|
||||
if (t.type === "eof") throw new ParseError("Unexpected end of input inside props");
|
||||
if (t.type === "at") {
|
||||
@@ -829,6 +836,13 @@ export function parse(source: string): PageAst {
|
||||
|
||||
const declaredStates = new Set(states.map((state) => state.name));
|
||||
|
||||
if (declaredStates.has("page")) {
|
||||
throw new ParseError(
|
||||
"State name 'page' collides with the WRN 'page' keyword; choose another state name",
|
||||
"WRN-STATE-RESERVED-NAME",
|
||||
);
|
||||
}
|
||||
|
||||
for (const watcher of watches) {
|
||||
if (!declaredStates.has(watcher.state)) {
|
||||
throw new ParseError(`Cannot watch undeclared state '${watcher.state}'`);
|
||||
|
||||
@@ -55,6 +55,12 @@ export class Lexer {
|
||||
}
|
||||
}
|
||||
|
||||
/** True when the next non-trivia characters open a block comment. */
|
||||
startsWithBlockComment(): boolean {
|
||||
this.skipTrivia();
|
||||
return this.src[this.pos] === "/" && this.src[this.pos + 1] === "*";
|
||||
}
|
||||
|
||||
/** Read and consume the next structural token. */
|
||||
next(): Token {
|
||||
this.skipTrivia();
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import {
|
||||
createSourceRange,
|
||||
diagnose,
|
||||
diagnosticSummary,
|
||||
sliceSource,
|
||||
supportsSyntaxFeature,
|
||||
} from "../src/index.ts";
|
||||
|
||||
describe("syntax contract and security diagnostics", () => {
|
||||
test("validates source ranges and summarizes diagnostic codes", () => {
|
||||
const range = createSourceRange(2, 6);
|
||||
expect(sliceSource("0123456789", range)).toBe("2345");
|
||||
expect(() => createSourceRange(-1, 2)).toThrow(RangeError);
|
||||
expect(() => createSourceRange(4, 3)).toThrow(RangeError);
|
||||
expect(supportsSyntaxFeature("runtime-markers")).toBe(true);
|
||||
expect(supportsSyntaxFeature("dynamic-eval")).toBe(false);
|
||||
|
||||
const summary = diagnosticSummary([
|
||||
{ code: "A", severity: "error", message: "a" },
|
||||
{ code: "A", severity: "warning", message: "b" },
|
||||
{ code: "B", severity: "info", message: "c" },
|
||||
]);
|
||||
expect(summary).toEqual({ errors: 1, warnings: 1, info: 1, codes: { A: 2, B: 1 } });
|
||||
});
|
||||
|
||||
test("rejects browser secret reads, executable sinks, and sensitive persistence", () => {
|
||||
const diagnostics = diagnose(`page store Unsafe {
|
||||
client state { apiToken: string = process.env.API_TOKEN }
|
||||
persist { storage = "local" include = ["apiToken"] version = 1 }
|
||||
functions {
|
||||
client function render(raw: string): void { document.write(raw); setTimeout("run()", 1) }
|
||||
}
|
||||
}`);
|
||||
const codes = diagnostics.map((diagnostic) => diagnostic.code);
|
||||
expect(codes).toContain("WRN-SEC-SERVER-SECRET-SOURCE");
|
||||
expect(codes).toContain("WRN-SEC-DOM-SINK");
|
||||
expect(codes).toContain("WRN-SEC-STRING-TIMER");
|
||||
expect(codes).toContain("WRN-PERSIST-SENSITIVE");
|
||||
});
|
||||
|
||||
test.each([
|
||||
[
|
||||
"server output call",
|
||||
`component Bad { outputs { save() } functions { server function run(): void { output.save() } } view { <div></div> } }`,
|
||||
"WRN-OUTPUT-SERVER-CALL",
|
||||
],
|
||||
[
|
||||
"unknown output call",
|
||||
`component Bad { functions { client function run(): void { output.missing() } } view { <button @click="run()">Run</button> } }`,
|
||||
"WRN-OUTPUT-UNKNOWN",
|
||||
],
|
||||
[
|
||||
"server API in client function",
|
||||
`component Bad { functions { client function run(): void { process.cwd() } } view { <button @click="run()">Run</button> } }`,
|
||||
"WRN-CLIENT-SERVER-API",
|
||||
],
|
||||
[
|
||||
"browser API in server function",
|
||||
`component Bad { functions { server function run(): void { document.title = "bad" } } view { <div></div> } }`,
|
||||
"WRN-SERVER-BROWSER-API",
|
||||
],
|
||||
[
|
||||
"non-serializable shared state",
|
||||
`global store Bad { state { values: unknown = new Map() } }`,
|
||||
"WRN-STATE-NON-SERIALIZABLE",
|
||||
],
|
||||
[
|
||||
"unknown persisted state",
|
||||
`global store Bad { state { value: string = "x" } persist { storage = "local" include = ["missing"] version = 1 } }`,
|
||||
"WRN-PERSIST-UNKNOWN-FIELD",
|
||||
],
|
||||
])("diagnoses %s", (_name, source, expectedCode) => {
|
||||
expect(diagnose(source).map((diagnostic) => diagnostic.code)).toContain(expectedCode);
|
||||
});
|
||||
});
|
||||
@@ -24,7 +24,7 @@ import { pathToFileURL } from "node:url";
|
||||
import { compileWireFile } from "@wrnexus/compiler";
|
||||
import { createContext } from "@wrnexus/core";
|
||||
import type { Context } from "@wrnexus/core";
|
||||
import { getReactiveRuntime } from "@wrnexus/csr";
|
||||
import { getComponentControllerRuntime, getReactiveRuntime } from "@wrnexus/csr";
|
||||
import { startServer } from "@wrnexus/dev-server";
|
||||
import {
|
||||
loadAppConfig,
|
||||
@@ -100,6 +100,7 @@ export function mountHtml(html: string): {
|
||||
g.HTMLSelectElement = win.HTMLSelectElement;
|
||||
g.HTMLTextAreaElement = win.HTMLTextAreaElement;
|
||||
(0, eval)(getReactiveRuntime());
|
||||
(0, eval)(getComponentControllerRuntime());
|
||||
(win as { __wrnexusHydrateScopes?: (root: unknown) => void }).__wrnexusHydrateScopes?.(
|
||||
win.document,
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# WRNexus UI component reference
|
||||
|
||||
This reference is generated from the 108 packaged `.wrn` component sources. Props marked required have no default; all others show their runtime default.
|
||||
This reference is generated from the 102 packaged `.wrn` component sources. Props marked required have no default; all others show their runtime default.
|
||||
|
||||
## Advanced-forms
|
||||
|
||||
@@ -22,15 +22,6 @@ Editable autocomplete combobox with local and remote suggestions.
|
||||
- Slots: None
|
||||
- Outputs: `search({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `select({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `change({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `clear({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `open({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `close({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `load({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `error({ error: Error | string; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | Error | string)`
|
||||
|
||||
### CopyMarkup
|
||||
|
||||
Theme-aware, responsive copy markup component.
|
||||
|
||||
- Mount: `data-component="CopyMarkup"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Copy Markup"`, `name: string = ""`, `value: string = ""`, `placeholder: string = ""`, `type: string = "text"`, `min: string = ""`, `max: string = ""`, `step: string = ""`, `disabled: boolean = false`, `required: boolean = false`, `class: string = ""`
|
||||
- Slots: None
|
||||
- Outputs: `copy({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])`, `success({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `error({ error: Error | string; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | Error | string)`
|
||||
|
||||
### InputNumber
|
||||
|
||||
Theme-aware, responsive input number component.
|
||||
@@ -303,23 +294,14 @@ Present responsive chronological activity, milestones, or workflow status with r
|
||||
- Slots: `default`
|
||||
- Outputs: `select({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`
|
||||
|
||||
### Toast
|
||||
|
||||
Theme-aware, responsive toast component.
|
||||
|
||||
- Mount: `data-component="Toast"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "Toast"`, `description: string = ""`, `items: unknown[] = []`, `variant: string = "default"`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Outputs: `dismiss({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `action({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`
|
||||
|
||||
### TreeView
|
||||
|
||||
Theme-aware, responsive tree view component.
|
||||
|
||||
- Mount: `data-component="TreeView"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "Tree View"`, `description: string = ""`, `items: unknown[] = []`, `variant: string = "default"`, `class: string = ""`
|
||||
- Props: `title: string = "Tree View"`, `description: string = ""`, `items: unknown[] = []`, `valueKey: string = "value"`, `labelKey: string = "label"`, `defaultExpanded: unknown[] = []`, `selected: string = ""`, `size: string = "default"`, `color: string = "primary"`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Outputs: `select({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `toggle({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `expand({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `collapse({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`
|
||||
- Outputs: `select({ item: object; value: string; sourceEvent?: Event })`, `toggle({ item: object; value: string; expanded: boolean; sourceEvent?: Event })`, `expand({ item: object; value: string; sourceEvent?: Event })`, `collapse({ item: object; value: string; sourceEvent?: Event })`
|
||||
|
||||
## Core
|
||||
|
||||
@@ -509,50 +491,32 @@ Theme-aware, responsive time picker component.
|
||||
|
||||
## Integrations
|
||||
|
||||
### AdvancedDatePicker
|
||||
|
||||
Theme-aware, responsive advanced date picker component.
|
||||
|
||||
- Mount: `data-component="AdvancedDatePicker"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "Advanced Date Picker"`, `description: string = ""`, `items: unknown[] = []`, `variant: string = "default"`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Outputs: `input({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `change({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `open({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `close({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `clear({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`
|
||||
|
||||
### AdvancedRangeSlider
|
||||
|
||||
Theme-aware, responsive advanced range slider component.
|
||||
|
||||
- Mount: `data-component="AdvancedRangeSlider"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "Advanced Range Slider"`, `description: string = ""`, `items: unknown[] = []`, `variant: string = "default"`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Outputs: `input({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `change({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `start({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `end({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`
|
||||
|
||||
### Chart
|
||||
|
||||
Theme-aware, responsive chart component.
|
||||
|
||||
- Mount: `data-component="Chart"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "Chart"`, `description: string = ""`, `items: unknown[] = []`, `variant: string = "default"`, `class: string = ""`
|
||||
- Props: `title: string = "Chart"`, `description: string = ""`, `items: unknown[] = []`, `valueKey: string = "value"`, `labelKey: string = "label"`, `height: number = 240`, `showLegend: boolean = true`, `showValues: boolean = true`, `size: string = "default"`, `color: string = "primary"`, `variant: string = "bar"`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Outputs: `select({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `dataPointClick({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])`, `legendToggle({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])`
|
||||
- Outputs: `select({ item: object; index: number; sourceEvent?: Event })`, `dataPointClick({ item: object; index: number; sourceEvent?: Event })`, `legendToggle({ item: object; index: number; hidden: boolean; sourceEvent?: Event })`
|
||||
|
||||
### Clipboard
|
||||
|
||||
Theme-aware, responsive clipboard component.
|
||||
|
||||
- Mount: `data-component="Clipboard"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "Clipboard"`, `description: string = ""`, `items: unknown[] = []`, `variant: string = "default"`, `class: string = ""`
|
||||
- Props: `value: string = ""`, `label: string = "Copy"`, `copiedLabel: string = "Copied"`, `errorLabel: string = "Copy failed"`, `title: string = "Clipboard"`, `description: string = ""`, `code: boolean = true`, `size: string = "default"`, `color: string = "primary"`, `disabled: boolean = false`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Outputs: `copy({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])`, `success({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `error({ error: Error | string; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | Error | string)`
|
||||
- Outputs: `copy({ value: string; sourceEvent?: Event })`, `success({ value: string; sourceEvent?: Event })`, `error({ error: Error | string; value: string; sourceEvent?: Event })`
|
||||
|
||||
### Confetti
|
||||
|
||||
Theme-aware, responsive confetti component.
|
||||
|
||||
- Mount: `data-component="Confetti"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "Confetti"`, `description: string = ""`, `items: unknown[] = []`, `variant: string = "default"`, `class: string = ""`
|
||||
- Props: `label: string = "Celebrate"`, `duration: number = 1200`, `count: number = 24`, `autoStart: boolean = false`, `disabled: boolean = false`, `size: string = "default"`, `color: string = "primary"`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Outputs: `start({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `complete({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`
|
||||
- Outputs: `start({ sourceEvent?: Event; duration: number })`, `complete({ sourceEvent?: Event; duration: number })`
|
||||
|
||||
### DataMap
|
||||
|
||||
@@ -572,15 +536,6 @@ Theme-aware, responsive drag and drop component.
|
||||
- Slots: `default`
|
||||
- Outputs: `dragStart({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `dragEnd({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `dragEnter({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `dragLeave({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `drop({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `change({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`
|
||||
|
||||
### FileUpload
|
||||
|
||||
Theme-aware, responsive file upload component.
|
||||
|
||||
- Mount: `data-component="FileUpload"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "File Upload"`, `description: string = ""`, `items: unknown[] = []`, `variant: string = "default"`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Outputs: `select({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `upload({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `progress({ progress: number; [key: string]: string | number | boolean | null | object } | number)`, `success({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `error({ error: Error | string; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | Error | string)`, `cancel({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `remove({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`
|
||||
|
||||
### Map
|
||||
|
||||
Present responsive location information and markers with map-ready metadata and movement or marker events.
|
||||
@@ -590,15 +545,6 @@ Present responsive location information and markers with map-ready metadata and
|
||||
- Slots: `default`
|
||||
- Outputs: `markerClick({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `select({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `zoom({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`
|
||||
|
||||
### ToastNotifications
|
||||
|
||||
Theme-aware, responsive toast notifications component.
|
||||
|
||||
- Mount: `data-component="ToastNotifications"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "Toast Notifications"`, `description: string = ""`, `items: unknown[] = []`, `variant: string = "default"`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Outputs: `add({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])`, `dismiss({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `clear({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `action({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`
|
||||
|
||||
### WysiwygEditor
|
||||
|
||||
Theme-aware, responsive wysiwyg editor component.
|
||||
|
||||
@@ -118,16 +118,12 @@ are `.wrn` files rendered server-side.
|
||||
The framework serves this stylesheet once at `/__wrnexus/ui.css`, so pages get all
|
||||
component styles from a single request.
|
||||
|
||||
### Tailwind and motion
|
||||
### Component styles and motion
|
||||
|
||||
Components use static Tailwind utility classes alongside the shared `.wire-*`
|
||||
layer. If the package is consumed by a separate Tailwind build, include its
|
||||
component sources so every utility is generated:
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@source "../node_modules/@wrnexus/ui/components/*.wrn";
|
||||
```
|
||||
Components own their BEM styles in local `style {}` blocks and do not require a
|
||||
Tailwind scan. `ui.css` supplies only global tokens, resets, and shared
|
||||
primitives. Applications can still use Tailwind independently in their own
|
||||
source files.
|
||||
|
||||
The shared stylesheet gives all component boundaries consistent, GPU-friendly
|
||||
entry and interaction motion. Override `--wire-motion-fast`,
|
||||
|
||||
@@ -6,16 +6,6 @@
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive accordion component."
|
||||
},
|
||||
{
|
||||
"name": "AdvancedDatePicker",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive advanced date picker component."
|
||||
},
|
||||
{
|
||||
"name": "AdvancedRangeSlider",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive advanced range slider component."
|
||||
},
|
||||
{
|
||||
"name": "AdvancedSelect",
|
||||
"category": "advanced-forms",
|
||||
@@ -151,11 +141,6 @@
|
||||
"category": "overlays",
|
||||
"purpose": "Open an accessible keyboard-aware action menu from pointer or keyboard context interactions."
|
||||
},
|
||||
{
|
||||
"name": "CopyMarkup",
|
||||
"category": "advanced-forms",
|
||||
"purpose": "Theme-aware, responsive copy markup component."
|
||||
},
|
||||
{
|
||||
"name": "CustomScrollbar",
|
||||
"category": "layout",
|
||||
@@ -221,11 +206,6 @@
|
||||
"category": "forms",
|
||||
"purpose": "Theme-aware, responsive file input component."
|
||||
},
|
||||
{
|
||||
"name": "FileUpload",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive file upload component."
|
||||
},
|
||||
{
|
||||
"name": "FileUploadProgress",
|
||||
"category": "base",
|
||||
@@ -496,16 +476,6 @@
|
||||
"category": "base",
|
||||
"purpose": "Present responsive chronological activity, milestones, or workflow status with rich item metadata."
|
||||
},
|
||||
{
|
||||
"name": "Toast",
|
||||
"category": "base",
|
||||
"purpose": "Theme-aware, responsive toast component."
|
||||
},
|
||||
{
|
||||
"name": "ToastNotifications",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive toast notifications component."
|
||||
},
|
||||
{
|
||||
"name": "Toaster",
|
||||
"category": "core",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user