Compare commits
103
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
52660cbb8e | ||
|
|
ca2f9451ab | ||
|
|
b3116de354 | ||
|
|
f6993e6cdb | ||
|
|
f1d1081b67 | ||
|
|
b4d3cb3695 | ||
|
|
124da548b8 | ||
|
|
f4960f2fc5 | ||
|
|
45ef035bae | ||
|
|
b67a5e43eb | ||
|
|
7ac6e08544 | ||
|
|
f94004648d | ||
|
|
e8e1a2623b | ||
|
|
ecb93c7116 | ||
|
|
f01a308287 | ||
|
|
1fb1a8d2d0 | ||
|
|
949cf78636 | ||
|
|
296728d51d | ||
|
|
98205daef6 | ||
|
|
3eec9fd8c6 | ||
|
|
6aaf21aa06 | ||
|
|
ce68803471 | ||
|
|
7c4b484d0a | ||
|
|
fcf4ed3039 | ||
|
|
e01915823a | ||
|
|
9bc0f48514 | ||
|
|
9f599e02e8 | ||
|
|
83c99cc3e5 | ||
|
|
1393a8a3b8 | ||
|
|
2257ee871e | ||
|
|
40625e98ed | ||
|
|
e0bd84247e | ||
|
|
34d5bbc810 | ||
|
|
e16903b286 | ||
|
|
21ea8a84a0 | ||
|
|
dd8354477d | ||
|
|
796b19d923 | ||
|
|
3e1d7db537 | ||
|
|
a4c7d7b298 | ||
|
|
e1fca3eddf | ||
|
|
63e6148cdb | ||
|
|
87d33a2ce1 | ||
|
|
2ab7b1e762 | ||
|
|
2e93124080 | ||
|
|
2c339bee15 | ||
|
|
41b6e2ed2b | ||
|
|
3867e7c183 | ||
|
|
a7255fa1bd | ||
|
|
fd5e2b7128 | ||
|
|
57097c8204 | ||
|
|
226217ecbf | ||
|
|
daea59cf5d | ||
|
|
bc5437063d | ||
|
|
b9098382b3 | ||
|
|
e5d0654d2a | ||
|
|
3ef353de83 | ||
|
|
726b8a7d24 | ||
|
|
91e5c6e0c5 | ||
|
|
2fbf059c00 | ||
|
|
1cc0b97a72 | ||
|
|
134c5fa4bc | ||
|
|
205f4e2d4c | ||
|
|
f19462dff0 | ||
|
|
3fa3fce5df | ||
|
|
218f5e2dd6 | ||
|
|
e136fbc56a | ||
|
|
41fb82b9e9 | ||
|
|
e7743cdbb5 | ||
|
|
703baa1ead | ||
|
|
e05ddc7aa5 | ||
|
|
6f3a53b9ff | ||
|
|
13859ce7dc | ||
|
|
e15422ed8d | ||
|
|
3f1fcd0d2d | ||
|
|
798f56734a | ||
|
|
77b9e49bf2 | ||
|
|
9e3624e584 | ||
|
|
b7f3507b59 | ||
|
|
cc8085bcfa | ||
|
|
984c6236d3 | ||
|
|
cd82bec414 | ||
|
|
d6a2d05407 | ||
|
|
ae37c9b57a | ||
|
|
86b3dc1e6a | ||
|
|
c499f136fd | ||
|
|
6d8b6daba9 | ||
|
|
d7509421c7 | ||
|
|
d609a41222 | ||
|
|
e710756baf | ||
|
|
ba83038d8d | ||
|
|
f033197850 | ||
|
|
dc0771308a | ||
|
|
83f2951035 | ||
|
|
4362d49770 | ||
|
|
a01b7bc99e | ||
|
|
9b6b970cae | ||
|
|
1849213ce4 | ||
|
|
d694dda320 | ||
|
|
212fdaa5b5 | ||
|
|
0ac648bc26 | ||
|
|
10da210b0a | ||
|
|
b209936f86 | ||
|
|
c64434a131 |
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": "0.0.1",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "component-showcase",
|
||||
"runtimeExecutable": "bun",
|
||||
"runtimeArgs": ["run", "--cwd", "examples/component-showcase", "dev"],
|
||||
"port": 3000
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
# Enforce LF in the working tree regardless of a contributor's core.autocrlf.
|
||||
# Without this, Git on Windows smudges every text file to CRLF on clone, stash
|
||||
# pop, or checkout, which fails `bun run format:check` (prettier endOfLine: lf).
|
||||
* text=auto eol=lf
|
||||
|
||||
# Binary assets Git must not touch.
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.webp binary
|
||||
*.ico binary
|
||||
*.pdf binary
|
||||
*.woff binary
|
||||
*.woff2 binary
|
||||
*.db binary
|
||||
@@ -19,3 +19,8 @@ bun.lockb
|
||||
# Local focused typecheck helpers must never enter the repository.
|
||||
focus-shims.d.ts
|
||||
tsconfig.focus.json
|
||||
|
||||
# Scratch dirs for tests that must dynamically import scaffolded files using
|
||||
# "@wrnexus/*" bare specifiers (resolved via the root tsconfig.json `paths`,
|
||||
# which requires the scaffold to live inside the repo tree).
|
||||
**/test/.tmp-*/
|
||||
|
||||
@@ -26,3 +26,6 @@ focus-shims.d.ts
|
||||
**/focus-shims.d.ts
|
||||
tsconfig.focus.json
|
||||
**/tsconfig.focus.json
|
||||
|
||||
# SDD scratch workspace (git-ignored controller artifacts)
|
||||
.superpowers/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ai",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -149,3 +149,156 @@ app.put(
|
||||
- **Bun-only** — like the rest of WrNexus, this package targets the Bun runtime; Node is not supported.
|
||||
- Works with [`@wrnexus/core`](../core) — the guards return `Middleware` and read the subject from `ctx.user` on the request `Context`. Both types are imported from `@wrnexus/core`.
|
||||
- Policy combinators (`any`, `all`) and `authorize` are async-aware, so policies may return a `Promise<boolean>` (e.g. for a database ownership check).
|
||||
|
||||
## Declaring permissions
|
||||
|
||||
The RBAC/PBAC/ABAC surface above is the low-level toolkit. On top of it sits a
|
||||
declarative **registry + catalog + store + engine**: permissions, roles, and
|
||||
policies are declared once in code, merged into a frozen catalog at boot, and
|
||||
resolved per-request against a pluggable `PermissionStore` that holds who has
|
||||
what.
|
||||
|
||||
Put declarations in `app/authz/<name>.ts`; they are discovered automatically
|
||||
and merged (conflicting declarations of the same permission/role/policy across
|
||||
files fail the boot loudly, naming both source files).
|
||||
|
||||
```ts
|
||||
import { defineAuthz, owner } from "@wrnexus/authz";
|
||||
|
||||
export default defineAuthz({
|
||||
permissions: {
|
||||
"post:read": { title: "View posts", public: true },
|
||||
"post:delete": { title: "Delete posts", risk: "high" },
|
||||
},
|
||||
// "post:*" is a namespace wildcard grant, valid inside a role's list — it is
|
||||
// not itself a registered permission, so it can only ever grant permissions
|
||||
// that ARE declared above (e.g. "post:read", "post:delete").
|
||||
roles: { editor: ["post:*"], admin: ["role:editor"] },
|
||||
policies: { ownsPost: owner("id", "authorId") },
|
||||
bindings: { "post:delete": ["ownsPost"] },
|
||||
});
|
||||
```
|
||||
|
||||
`public: true` means anonymous callers may hold the permission — but any
|
||||
policy bound to it still runs, and can still veto the anonymous caller (e.g. a
|
||||
`notBanned` policy on a public `post:preview` permission).
|
||||
|
||||
## Checking permissions
|
||||
|
||||
Register `authzMiddleware` once, in `app/middleware/`, with the merged
|
||||
catalog and a `PermissionStore`. Like every other `app/middleware/*.ts` file,
|
||||
the registration is an eager, module-scope call — the same shape as
|
||||
`authzMiddleware({ catalog, store })` requires — so it must run after the
|
||||
catalog has been populated. Both the dev server and `wrnexus build`'s
|
||||
generated production entry guarantee `getAuthzCatalog()` is populated before
|
||||
any app middleware module evaluates. Name the file so it sorts after whatever
|
||||
middleware sets `ctx.user` (middleware runs in alphabetical filename order —
|
||||
`authz.ts` after `auth.ts`, for instance).
|
||||
|
||||
```ts
|
||||
// app/middleware/authz.ts
|
||||
import { authzMiddleware, getAuthzCatalog } from "@wrnexus/authz";
|
||||
import { dbPermissionStore } from "@wrnexus/authz/db";
|
||||
import { getDb } from "@wrnexus/db";
|
||||
|
||||
export default authzMiddleware({ catalog: getAuthzCatalog(), store: dbPermissionStore(getDb()) });
|
||||
```
|
||||
|
||||
> **`subject.id` must be a non-empty string.** The engine denies (and logs to
|
||||
> stderr) whenever `ctx.user.id` is present but not a non-empty string — this
|
||||
> includes the common case of an integer primary key. Coerce it before it
|
||||
> reaches `ctx.user`, e.g. `user.id = String(row.id)`, or every request for
|
||||
> that user denies with "Invalid subject" instead of resolving normally.
|
||||
> `owner()` (the built-in ownership policy) compares subject and resource ids
|
||||
> with `Object.is`, so both sides must be the same type too — `owner()` on a
|
||||
> numeric `resource.authorId` against a stringified `subject.id` never
|
||||
> matches even when they represent "the same" id.
|
||||
|
||||
There is no per-route `middleware` export — `app/middleware/*.ts` is the only
|
||||
place middleware is registered. To gate part of the app, branch on the
|
||||
request the same way any other conditional middleware does (compare
|
||||
`app/middleware/captcha-login.ts` in the auth showcase, which branches on
|
||||
method + path the same way):
|
||||
|
||||
```ts
|
||||
// app/middleware/protect-posts.ts
|
||||
import type { Context, Next } from "@wrnexus/core";
|
||||
import { guardPermission } from "@wrnexus/authz";
|
||||
|
||||
const guardPostWrite = guardPermission("post:write");
|
||||
|
||||
export default function protectPosts(ctx: Context, next: Next) {
|
||||
return ctx.url.pathname.startsWith("/api/posts") && ctx.req.method !== "GET"
|
||||
? guardPostWrite(ctx, next)
|
||||
: next();
|
||||
}
|
||||
```
|
||||
|
||||
Or check inline inside a route handler with the free function `can()`:
|
||||
|
||||
```ts
|
||||
// app/api/posts/[id].ts
|
||||
import type { Context } from "@wrnexus/core";
|
||||
import { can } from "@wrnexus/authz";
|
||||
|
||||
export const DELETE = async (ctx: Context) => {
|
||||
const post = { id: "1", authorId: "alice" }; // load your own resource here
|
||||
if (!(await can(ctx, "post:delete", post))) {
|
||||
return Response.json({ ok: false, error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
return Response.json({ ok: true });
|
||||
};
|
||||
```
|
||||
|
||||
`can()` is a free function taking `ctx`, not `ctx.can` — `@wrnexus/core` must
|
||||
not depend on `@wrnexus/authz`, so the per-request resolver lives in
|
||||
`ctx.locals` instead, reached through `can()` / `decideFor()` /
|
||||
`guardPermission()` / `filterCan()`. Calling any of them before
|
||||
`authzMiddleware` has run for that request throws a `WRN-AUTHZ-SETUP` error
|
||||
naming the missing registration, rather than silently denying.
|
||||
|
||||
See `examples/auth-showcase/app/authz/showcase.ts` and
|
||||
`examples/auth-showcase/app/middleware/authz.ts` for a complete, runnable
|
||||
version of this wiring.
|
||||
|
||||
## Precedence
|
||||
|
||||
1. An explicit deny wins over everything, including `*` — and honours the
|
||||
same namespace-wildcard matching as grants (denying `post:*` blocks
|
||||
`post:comment:delete`, not just `post:*` itself).
|
||||
2. A bound policy can veto a permission a role grants, and runs even for a
|
||||
`public: true` permission — including for an anonymous caller.
|
||||
3. Otherwise the permission must be held via a role or an explicit grant.
|
||||
4. Default deny.
|
||||
|
||||
Every failure — an unknown permission (outside strict/dev mode), a store
|
||||
outage, a thrown policy — denies rather than throwing through to the caller.
|
||||
|
||||
`permissionsFor()` (on the resolver returned by `createAuthzResolver`) is a
|
||||
coarse hint for hiding UI (e.g. a menu section), **never authoritative**. A
|
||||
`Set<string>` cannot represent "granted `post:*` except `post:delete`", so a
|
||||
narrow deny beneath a broad grant is invisible to it — the set still contains
|
||||
`post:*` while `can()` / `decide()` correctly refuse `post:delete`. Gate real
|
||||
actions with `can()`, `decideFor()`, or `filterCan()`; never by matching
|
||||
against `permissionsFor()`'s result.
|
||||
|
||||
## CLI
|
||||
|
||||
```bash
|
||||
wrnexus authz list # every registered permission, role, and policy
|
||||
wrnexus authz generate # app/authz/permissions.gen.ts type unions
|
||||
wrnexus authz init # scaffold the assignment-table migration
|
||||
```
|
||||
|
||||
`wrnexus authz generate`'s output is a plain `Permission | Role` string-literal
|
||||
union — `can()`, `guardPermission()`, and `decideFor()` all take a bare
|
||||
`string` and nothing reads this file automatically, so import it to type your
|
||||
own helpers/constants against the registered catalog, e.g.:
|
||||
|
||||
```ts
|
||||
import type { Permission } from "app/authz/permissions.gen.ts";
|
||||
|
||||
function guard(permission: Permission) {
|
||||
return guardPermission(permission);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/authz",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/authz — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -34,8 +34,16 @@
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
},
|
||||
"./db": {
|
||||
"types": "./dist/db.d.ts",
|
||||
"import": "./dist/db.js"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.8.5",
|
||||
"@wrnexus/db": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md"
|
||||
|
||||
+18
-17
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/cli",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/cli — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -44,22 +44,23 @@
|
||||
"wrnexus": "./dist/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.8.4",
|
||||
"@wrnexus/router": "^0.8.4",
|
||||
"@wrnexus/csr": "^0.8.4",
|
||||
"@wrnexus/compiler": "^0.8.4",
|
||||
"@wrnexus/styles": "^0.8.4",
|
||||
"@wrnexus/dev-server": "^0.8.4",
|
||||
"@wrnexus/ui": "^0.8.4",
|
||||
"@wrnexus/validation": "^0.8.4",
|
||||
"@wrnexus/i18n": "^0.8.4",
|
||||
"@wrnexus/mcp": "^0.8.4",
|
||||
"@wrnexus/playground": "^0.8.4",
|
||||
"@wrnexus/db": "^0.8.4",
|
||||
"@wrnexus/plugin": "^0.8.4",
|
||||
"@wrnexus/syntax": "^0.8.4",
|
||||
"@wrnexus/typecheck": "^0.8.4",
|
||||
"@wrnexus/security": "^0.8.4",
|
||||
"@wrnexus/core": "^0.8.5",
|
||||
"@wrnexus/router": "^0.8.5",
|
||||
"@wrnexus/csr": "^0.8.5",
|
||||
"@wrnexus/compiler": "^0.8.5",
|
||||
"@wrnexus/styles": "^0.8.5",
|
||||
"@wrnexus/dev-server": "^0.8.5",
|
||||
"@wrnexus/ui": "^0.8.5",
|
||||
"@wrnexus/validation": "^0.8.5",
|
||||
"@wrnexus/i18n": "^0.8.5",
|
||||
"@wrnexus/mcp": "^0.8.5",
|
||||
"@wrnexus/playground": "^0.8.5",
|
||||
"@wrnexus/db": "^0.8.5",
|
||||
"@wrnexus/authz": "^0.8.5",
|
||||
"@wrnexus/plugin": "^0.8.5",
|
||||
"@wrnexus/syntax": "^0.8.5",
|
||||
"@wrnexus/typecheck": "^0.8.5",
|
||||
"@wrnexus/security": "^0.8.5",
|
||||
"selfsigned": "^5.5.0"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/compiler",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/compiler — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -37,10 +37,10 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/csr": "^0.8.4",
|
||||
"@wrnexus/syntax": "^0.8.4",
|
||||
"@wrnexus/store": "^0.8.4",
|
||||
"@wrnexus/validation": "^0.8.4"
|
||||
"@wrnexus/csr": "^0.8.5",
|
||||
"@wrnexus/syntax": "^0.8.5",
|
||||
"@wrnexus/store": "^0.8.5",
|
||||
"@wrnexus/validation": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/core",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/core — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/csr",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/csr — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -37,7 +37,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.8.4"
|
||||
"@wrnexus/core": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/db",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "Typed database drivers, migrations, instrumentation, repositories, pagination, and transaction helpers.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/dev-server",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/dev-server — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -41,25 +41,27 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.8.4",
|
||||
"@wrnexus/dev-toolbar": "^0.8.4",
|
||||
"@wrnexus/router": "^0.8.4",
|
||||
"@wrnexus/ssr": "^0.8.4",
|
||||
"@wrnexus/csr": "^0.8.4",
|
||||
"@wrnexus/compiler": "^0.8.4",
|
||||
"@wrnexus/styles": "^0.8.4",
|
||||
"@wrnexus/ui": "^0.8.4",
|
||||
"@wrnexus/validation": "^0.8.4",
|
||||
"@wrnexus/i18n": "^0.8.4",
|
||||
"@wrnexus/db": "^0.8.4",
|
||||
"@wrnexus/pubsub": "^0.8.4",
|
||||
"@wrnexus/uploader": "^0.8.4",
|
||||
"@wrnexus/plugin": "^0.8.4",
|
||||
"@wrnexus/store": "^0.8.4",
|
||||
"@wrnexus/security": "^0.8.4",
|
||||
"@wrnexus/observability": "^0.8.4",
|
||||
"@wrnexus/cache": "^0.8.4",
|
||||
"@wrnexus/pwa": "^0.8.4"
|
||||
"@wrnexus/authz": "^0.8.5",
|
||||
"@wrnexus/rpc": "^0.8.5",
|
||||
"@wrnexus/core": "^0.8.5",
|
||||
"@wrnexus/dev-toolbar": "^0.8.5",
|
||||
"@wrnexus/router": "^0.8.5",
|
||||
"@wrnexus/ssr": "^0.8.5",
|
||||
"@wrnexus/csr": "^0.8.5",
|
||||
"@wrnexus/compiler": "^0.8.5",
|
||||
"@wrnexus/styles": "^0.8.5",
|
||||
"@wrnexus/ui": "^0.8.5",
|
||||
"@wrnexus/validation": "^0.8.5",
|
||||
"@wrnexus/i18n": "^0.8.5",
|
||||
"@wrnexus/db": "^0.8.5",
|
||||
"@wrnexus/pubsub": "^0.8.5",
|
||||
"@wrnexus/uploader": "^0.8.5",
|
||||
"@wrnexus/plugin": "^0.8.5",
|
||||
"@wrnexus/store": "^0.8.5",
|
||||
"@wrnexus/security": "^0.8.5",
|
||||
"@wrnexus/observability": "^0.8.5",
|
||||
"@wrnexus/cache": "^0.8.5",
|
||||
"@wrnexus/pwa": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/dev-toolbar",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/dev-toolbar — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/encryption",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "Authenticated encryption, key rotation, hashing, and optional application-layer encrypted HTTP envelopes.",
|
||||
"license": "MIT",
|
||||
@@ -37,7 +37,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.8.4"
|
||||
"@wrnexus/core": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/helpers",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
|
||||
"license": "MIT",
|
||||
@@ -37,7 +37,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.8.4"
|
||||
"@wrnexus/core": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/i18n",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "Locale loading, fallback resolution, SSR/browser translations, formatters, and WRNexusJS language components.",
|
||||
"license": "MIT",
|
||||
@@ -42,9 +42,9 @@
|
||||
"./components/*": "./components/*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.8.4",
|
||||
"@wrnexus/plugin": "^0.8.4",
|
||||
"@wrnexus/ui": "^0.8.4"
|
||||
"@wrnexus/core": "^0.8.5",
|
||||
"@wrnexus/plugin": "^0.8.5",
|
||||
"@wrnexus/ui": "^0.8.5"
|
||||
},
|
||||
"wrnexus": {
|
||||
"plugin": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/jwt",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "HS256 JSON Web Tokens, key rotation, access/refresh helpers, scopes, cookies, and auth middleware.",
|
||||
"license": "MIT",
|
||||
@@ -37,7 +37,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.8.4"
|
||||
"@wrnexus/core": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/mobile",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/mobile — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -37,7 +37,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/native": "^0.8.4"
|
||||
"@wrnexus/native": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/native",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/native — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/oauth",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/oauth — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -37,7 +37,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/jwt": "^0.8.4"
|
||||
"@wrnexus/jwt": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/plugin",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/plugin — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -49,7 +49,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/syntax": "^0.8.4"
|
||||
"@wrnexus/syntax": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/pubsub",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/pubsub — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/queue",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/queue — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -37,7 +37,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.8.4"
|
||||
"@wrnexus/core": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/reactive",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/reactive — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/router",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/router — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -37,8 +37,8 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/compiler": "^0.8.4",
|
||||
"@wrnexus/core": "^0.8.4"
|
||||
"@wrnexus/compiler": "^0.8.5",
|
||||
"@wrnexus/core": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ssr",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/ssr — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -45,9 +45,9 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.8.4",
|
||||
"@wrnexus/store": "^0.8.4",
|
||||
"@wrnexus/security": "^0.8.4"
|
||||
"@wrnexus/core": "^0.8.5",
|
||||
"@wrnexus/store": "^0.8.5",
|
||||
"@wrnexus/security": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/styles",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/styles — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -37,9 +37,9 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/uploader": "^0.8.4",
|
||||
"@wrnexus/core": "^0.8.4",
|
||||
"@wrnexus/plugin": "^0.8.4"
|
||||
"@wrnexus/uploader": "^0.8.5",
|
||||
"@wrnexus/core": "^0.8.5",
|
||||
"@wrnexus/plugin": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/syntax",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/syntax — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/test",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/test — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/tracking",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/tracking — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
+16
-16
@@ -359,6 +359,15 @@ Reusable preference switcher component.
|
||||
- Slots: None
|
||||
- Outputs: `theme({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])`, `color({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])`, `language({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])`
|
||||
|
||||
### Toaster
|
||||
|
||||
Reusable toaster component.
|
||||
|
||||
- Mount: `data-component="Toaster"`
|
||||
- Props: `color: string = "info"`, `size: string = "default"`, `position: string = "bottom-right"`, `duration: number = 4500`, `max: number = 4`, `pauseOnHover: boolean = true`, `showIcon: boolean = true`, `successIcon: string = ""`, `dangerIcon: string = ""`, `warningIcon: string = ""`, `infoIcon: string = ""`, `closable: boolean = true`, `showProgress: boolean = true`, `closeLabel: string = "Dismiss notification"`, `class: string = ""`
|
||||
- Slots: None
|
||||
- Outputs: `show({ id: number; message: string; tone: string })`, `dismiss({ id: number; reason: string })`, `action({ id: number; sourceEvent: Event })`
|
||||
|
||||
## Data
|
||||
|
||||
### MetricCard
|
||||
@@ -554,15 +563,6 @@ Theme-aware, responsive data map component.
|
||||
- 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)`, `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)`
|
||||
|
||||
### DataTable
|
||||
|
||||
Theme-aware, responsive data table component.
|
||||
|
||||
- Mount: `data-component="DataTable"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `caption: string = "Data Table"`, `columns: unknown[] = []`, `rows: unknown[] = []`, `striped: boolean = true`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Outputs: `sort({ 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)`, `rowClick({ 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)`, `pageChange({ 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)`
|
||||
|
||||
### DragAndDrop
|
||||
|
||||
Theme-aware, responsive drag and drop component.
|
||||
@@ -945,7 +945,7 @@ Open an accessible keyboard-aware action menu from pointer or keyboard context i
|
||||
Present responsive modal side or bottom content with focus management, backdrop behavior, slots, and close events.
|
||||
|
||||
- Mount: `data-component="Drawer"`
|
||||
- Props: `open: boolean = false`, `defaultOpen: boolean = false`, `placement: string = "right"`, `size: string = "md"`, `color: string = "primary"`, `variant: string = "default"`, `title: string = "Drawer"`, `description: string = ""`, `icon: string = ""`, `label: string = "Drawer"`, `closeLabel: string = "Close drawer"`, `showClose: boolean = true`, `closeOnBackdrop: boolean = true`, `closeOnEscape: boolean = true`, `overlay: boolean = true`, `scrollable: boolean = true`, `triggerLabel: string = ""`, `triggerIcon: string = ""`, `class: string = ""`
|
||||
- Props: `open: boolean = false`, `defaultOpen: boolean = false`, `placement: string = "right"`, `size: string = "md"`, `color: string = "primary"`, `variant: string = "default"`, `title: string = "Drawer"`, `description: string = ""`, `icon: string = ""`, `label: string = "Drawer"`, `closeLabel: string = "Close drawer"`, `showClose: boolean = true`, `closeOnBackdrop: boolean = true`, `closeOnEscape: boolean = true`, `duration: number = 260`, `overlay: boolean = true`, `scrollable: boolean = true`, `triggerLabel: string = ""`, `triggerIcon: string = ""`, `class: string = ""`
|
||||
- Slots: `trigger`, `header`, `default`, `footer`
|
||||
- Outputs: `open({ placement: string; sourceEvent: Event })`, `close({ reason: string; placement: string; sourceEvent: Event })`, `cancel({ placement: string; sourceEvent: Event })`
|
||||
|
||||
@@ -963,7 +963,7 @@ Open an accessible anchored menu with keyboard navigation, item selection, actio
|
||||
Present an accessible modal dialog with focus management, confirmation, cancellation, slots, and responsive sizing.
|
||||
|
||||
- Mount: `data-component="Modal"`
|
||||
- Props: `open: boolean = false`, `defaultOpen: boolean = false`, `title: string = "Modal"`, `description: string = ""`, `icon: string = ""`, `label: string = "Modal dialog"`, `size: string = "md"`, `placement: string = "center"`, `color: string = "primary"`, `variant: string = "default"`, `showClose: boolean = true`, `closeLabel: string = "Close modal"`, `closeOnBackdrop: boolean = true`, `closeOnEscape: boolean = true`, `closeOnCancel: boolean = true`, `closeOnConfirm: boolean = false`, `showFooter: boolean = true`, `cancelLabel: string = "Cancel"`, `cancelIcon: string = ""`, `confirmLabel: string = "Confirm"`, `confirmIcon: string = ""`, `confirmDisabled: boolean = false`, `confirmLoading: boolean = false`, `destructive: boolean = false`, `triggerLabel: string = ""`, `triggerIcon: string = ""`, `scrollable: boolean = true`, `class: string = ""`
|
||||
- Props: `open: boolean = false`, `defaultOpen: boolean = false`, `title: string = "Modal"`, `description: string = ""`, `icon: string = ""`, `label: string = "Modal dialog"`, `size: string = "md"`, `placement: string = "center"`, `color: string = "primary"`, `variant: string = "default"`, `showClose: boolean = true`, `closeLabel: string = "Close modal"`, `closeOnBackdrop: boolean = true`, `closeOnEscape: boolean = true`, `closeOnCancel: boolean = true`, `closeOnConfirm: boolean = false`, `showFooter: boolean = true`, `cancelLabel: string = "Cancel"`, `cancelIcon: string = ""`, `confirmLabel: string = "Confirm"`, `confirmIcon: string = ""`, `confirmDisabled: boolean = false`, `confirmLoading: boolean = false`, `destructive: boolean = false`, `triggerLabel: string = ""`, `triggerIcon: string = ""`, `scrollable: boolean = true`, `scrollBehavior: string = "inside"`, `class: string = ""`
|
||||
- Slots: `trigger`, `header`, `default`, `footer`
|
||||
- Outputs: `open({ sourceEvent: Event })`, `close({ reason: string; sourceEvent: Event })`, `cancel({ sourceEvent: Event })`, `confirm({ sourceEvent: Event })`
|
||||
|
||||
@@ -987,11 +987,11 @@ Show concise accessible contextual help on hover, focus, click, or controlled op
|
||||
|
||||
## Tables
|
||||
|
||||
### Table
|
||||
### DataTable
|
||||
|
||||
Theme-aware, responsive table component.
|
||||
Sortable, filterable, paginated data table with row selection.
|
||||
|
||||
- Mount: `data-component="Table"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `caption: string = "Table"`, `columns: unknown[] = []`, `rows: unknown[] = []`, `striped: boolean = true`, `class: string = ""`
|
||||
- Mount: `data-component="DataTable"`
|
||||
- Props: `color: string = "primary"`, `size: string = "default"`, `columns: unknown[] = []`, `rows: unknown[] = []`, `rowKey: string = "id"`, `remote: boolean = false`, `loadingLabel: string = "Loading"`, `errorLabel: string = "Could not load this data"`, `retryLabel: string = "Try again"`, `caption: string = ""`, `description: string = ""`, `searchable: boolean = true`, `searchPlaceholder: string = "Search"`, `paginated: boolean = true`, `pageSize: number = 10`, `paginationStyle: string = "compact"`, `pageSizes: number[] = [10, 25, 50]`, `selectable: boolean = false`, `actions: unknown[] = []`, `striped: boolean = true`, `bordered: boolean = true`, `gridlines: string = "rows"`, `density: string = "default"`, `emptyLabel: string = "No records to show"`, `noResultsLabel: string = "No records match your search"`, `clearSearchLabel: string = "Clear search"`, `stickyFirstColumn: boolean = false`, `layout: string = "rows"`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Outputs: `sort({ 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)`, `rowClick({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`
|
||||
- Outputs: `sort({ key: string; direction: string })`, `search({ query: string })`, `pageChange({ page: number; pageSize: number })`, `select({ selected: Array<string | number>; all: boolean })`, `change({ page: number; pageSize: number; total: number; query: string; sortKey: string; sortDirection: string })`, `rowClick({ row: object; sourceEvent: Event })`, `action({ id: string; selected: Array<string | number>; rows: object[]; sourceEvent: Event })`, `request({ instanceId: number; page: number; pageSize: number; sortKey: string; sortDirection: string; query: string })`
|
||||
|
||||
@@ -168,8 +168,8 @@
|
||||
},
|
||||
{
|
||||
"name": "DataTable",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive data table component."
|
||||
"category": "tables",
|
||||
"purpose": "Sortable, filterable, paginated data table with row selection."
|
||||
},
|
||||
{
|
||||
"name": "DatePicker",
|
||||
@@ -471,11 +471,6 @@
|
||||
"category": "forms",
|
||||
"purpose": "Theme-aware, responsive switch component."
|
||||
},
|
||||
{
|
||||
"name": "Table",
|
||||
"category": "tables",
|
||||
"purpose": "Theme-aware, responsive table component."
|
||||
},
|
||||
{
|
||||
"name": "Tabs",
|
||||
"category": "navigation",
|
||||
@@ -511,6 +506,11 @@
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive toast notifications component."
|
||||
},
|
||||
{
|
||||
"name": "Toaster",
|
||||
"category": "core",
|
||||
"purpose": "Reusable toaster component."
|
||||
},
|
||||
{
|
||||
"name": "ToggleCount",
|
||||
"category": "advanced-forms",
|
||||
|
||||
@@ -4569,16 +4569,9 @@
|
||||
{
|
||||
"name": "DataTable",
|
||||
"mount": "DataTable",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive data table component.",
|
||||
"category": "tables",
|
||||
"purpose": "Sortable, filterable, paginated data table with row selection.",
|
||||
"props": [
|
||||
{
|
||||
"name": "size",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"default\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "color",
|
||||
"type": "string",
|
||||
@@ -4587,10 +4580,10 @@
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "caption",
|
||||
"name": "size",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"Data Table\"",
|
||||
"default": "\"default\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
@@ -4607,6 +4600,111 @@
|
||||
"default": "[]",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "rowKey",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"id\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "remote",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": "false",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "loadingLabel",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"Loading\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "errorLabel",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"Could not load this data\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "retryLabel",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"Try again\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "caption",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "searchable",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": "true",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "searchPlaceholder",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"Search\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "paginated",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": "true",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "pageSize",
|
||||
"type": "number",
|
||||
"required": false,
|
||||
"default": "10",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "paginationStyle",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"compact\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "pageSizes",
|
||||
"type": "number[]",
|
||||
"required": false,
|
||||
"default": "[10, 25, 50]",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "selectable",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": "false",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "actions",
|
||||
"type": "unknown[]",
|
||||
"required": false,
|
||||
"default": "[]",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "striped",
|
||||
"type": "boolean",
|
||||
@@ -4614,6 +4712,62 @@
|
||||
"default": "true",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "bordered",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": "true",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "gridlines",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"rows\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "density",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"default\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "emptyLabel",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"No records to show\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "noResultsLabel",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"No records match your search\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "clearSearchLabel",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"Clear search\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "stickyFirstColumn",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": "false",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "layout",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"rows\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "class",
|
||||
"type": "string",
|
||||
@@ -4626,26 +4780,47 @@
|
||||
"outputs": [
|
||||
{
|
||||
"name": "sort",
|
||||
"payloadType": "{ 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"
|
||||
"payloadType": "{ key: string; direction: string }"
|
||||
},
|
||||
{
|
||||
"name": "select",
|
||||
"payloadType": "{ 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"
|
||||
},
|
||||
{
|
||||
"name": "change",
|
||||
"payloadType": "{ 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"
|
||||
},
|
||||
{
|
||||
"name": "rowClick",
|
||||
"payloadType": "{ 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"
|
||||
"name": "search",
|
||||
"payloadType": "{ query: string }"
|
||||
},
|
||||
{
|
||||
"name": "pageChange",
|
||||
"payloadType": "{ 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"
|
||||
"payloadType": "{ page: number; pageSize: number }"
|
||||
},
|
||||
{
|
||||
"name": "select",
|
||||
"payloadType": "{ selected: Array<string | number>; all: boolean }"
|
||||
},
|
||||
{
|
||||
"name": "change",
|
||||
"payloadType": "{ page: number; pageSize: number; total: number; query: string; sortKey: string; sortDirection: string }"
|
||||
},
|
||||
{
|
||||
"name": "rowClick",
|
||||
"payloadType": "{ row: object; sourceEvent: Event }"
|
||||
},
|
||||
{
|
||||
"name": "action",
|
||||
"payloadType": "{ id: string; selected: Array<string | number>; rows: object[]; sourceEvent: Event }"
|
||||
},
|
||||
{
|
||||
"name": "request",
|
||||
"payloadType": "{ instanceId: number; page: number; pageSize: number; sortKey: string; sortDirection: string; query: string }"
|
||||
}
|
||||
],
|
||||
"events": ["sort", "select", "change", "rowClick", "pageChange"],
|
||||
"events": [
|
||||
"sort",
|
||||
"search",
|
||||
"pageChange",
|
||||
"select",
|
||||
"change",
|
||||
"rowClick",
|
||||
"action",
|
||||
"request"
|
||||
],
|
||||
"source": "components/DataTable.wrn"
|
||||
},
|
||||
{
|
||||
@@ -5220,6 +5395,13 @@
|
||||
"default": "true",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "duration",
|
||||
"type": "number",
|
||||
"required": false,
|
||||
"default": "260",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "overlay",
|
||||
"type": "boolean",
|
||||
@@ -8736,6 +8918,13 @@
|
||||
"default": "true",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "scrollBehavior",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"inside\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "class",
|
||||
"type": "string",
|
||||
@@ -12009,80 +12198,6 @@
|
||||
"events": ["input", "change", "focus", "blur"],
|
||||
"source": "components/Switch.wrn"
|
||||
},
|
||||
{
|
||||
"name": "Table",
|
||||
"mount": "Table",
|
||||
"category": "tables",
|
||||
"purpose": "Theme-aware, responsive table component.",
|
||||
"props": [
|
||||
{
|
||||
"name": "size",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"default\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "color",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"primary\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "caption",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"Table\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "columns",
|
||||
"type": "unknown[]",
|
||||
"required": false,
|
||||
"default": "[]",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "rows",
|
||||
"type": "unknown[]",
|
||||
"required": false,
|
||||
"default": "[]",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "striped",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": "true",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "class",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"\"",
|
||||
"options": []
|
||||
}
|
||||
],
|
||||
"slots": ["default"],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "sort",
|
||||
"payloadType": "{ 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"
|
||||
},
|
||||
{
|
||||
"name": "select",
|
||||
"payloadType": "{ 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"
|
||||
},
|
||||
{
|
||||
"name": "rowClick",
|
||||
"payloadType": "{ 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"
|
||||
}
|
||||
],
|
||||
"events": ["sort", "select", "rowClick"],
|
||||
"source": "components/table.wrn"
|
||||
},
|
||||
{
|
||||
"name": "Tabs",
|
||||
"mount": "Tabs",
|
||||
@@ -12893,6 +13008,136 @@
|
||||
"events": ["add", "dismiss", "clear", "action"],
|
||||
"source": "components/ToastNotifications.wrn"
|
||||
},
|
||||
{
|
||||
"name": "Toaster",
|
||||
"mount": "Toaster",
|
||||
"category": "core",
|
||||
"purpose": "Reusable toaster component.",
|
||||
"props": [
|
||||
{
|
||||
"name": "color",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"info\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "size",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"default\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "position",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"bottom-right\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "duration",
|
||||
"type": "number",
|
||||
"required": false,
|
||||
"default": "4500",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "max",
|
||||
"type": "number",
|
||||
"required": false,
|
||||
"default": "4",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "pauseOnHover",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": "true",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "showIcon",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": "true",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "successIcon",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "dangerIcon",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "warningIcon",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "infoIcon",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "closable",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": "true",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "showProgress",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": "true",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "closeLabel",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"Dismiss notification\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "class",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"\"",
|
||||
"options": []
|
||||
}
|
||||
],
|
||||
"slots": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "show",
|
||||
"payloadType": "{ id: number; message: string; tone: string }"
|
||||
},
|
||||
{
|
||||
"name": "dismiss",
|
||||
"payloadType": "{ id: number; reason: string }"
|
||||
},
|
||||
{
|
||||
"name": "action",
|
||||
"payloadType": "{ id: number; sourceEvent: Event }"
|
||||
}
|
||||
],
|
||||
"events": ["show", "dismiss", "action"],
|
||||
"source": "components/Toaster.wrn"
|
||||
},
|
||||
{
|
||||
"name": "ToggleCount",
|
||||
"mount": "ToggleCount",
|
||||
|
||||
@@ -45,8 +45,12 @@ component ContextMenu {
|
||||
sourceEvent.preventDefault()
|
||||
}
|
||||
if (placement === "pointer" && sourceEvent) {
|
||||
positionX = Math.max(12, Math.min(sourceEvent.clientX || 12, window.innerWidth - 340))
|
||||
positionY = Math.max(12, Math.min(sourceEvent.clientY || 12, window.innerHeight - 420))
|
||||
// Place the menu at the pointer and let the anchored clamp in the
|
||||
// runtime pull it back on screen once it has been laid out and can
|
||||
// actually be measured. Subtracting a guessed 340x420 here instead
|
||||
// pushed every menu that was not that size away from the pointer.
|
||||
positionX = Math.max(12, sourceEvent.clientX || 12)
|
||||
positionY = Math.max(12, sourceEvent.clientY || 12)
|
||||
}
|
||||
visible = true
|
||||
output.open({
|
||||
@@ -161,6 +165,7 @@ component ContextMenu {
|
||||
|
||||
<div
|
||||
class="wire-context-menu__panel"
|
||||
data-wrn-anchored="true"
|
||||
data-show='{open || visible}'
|
||||
role="menu"
|
||||
aria-label='{label}'
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,6 +20,8 @@ open: boolean = false
|
||||
showClose: boolean = true
|
||||
closeOnBackdrop: boolean = true
|
||||
closeOnEscape: boolean = true
|
||||
// Open/close animation length in ms. 0 disables the animation entirely.
|
||||
duration: number = 260
|
||||
overlay: boolean = true
|
||||
scrollable: boolean = true
|
||||
triggerLabel: string = ""
|
||||
@@ -79,7 +81,9 @@ open: boolean = false
|
||||
data-overlay='{overlay ? "true" : "false"}'
|
||||
data-scrollable='{scrollable ? "true" : "false"}'
|
||||
class='wire-drawer {class}'
|
||||
style='--drawer-duration: {duration}ms'
|
||||
>
|
||||
|
||||
{#if triggerLabel}
|
||||
<button
|
||||
type="button"
|
||||
@@ -101,7 +105,6 @@ open: boolean = false
|
||||
|
||||
<div
|
||||
class="wire-drawer__layer"
|
||||
data-show='{open || visible}'
|
||||
role="presentation"
|
||||
@keydown='handleKeydown(event)'
|
||||
>
|
||||
@@ -218,12 +221,32 @@ open: boolean = false
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/*
|
||||
* The layer stays in the layout and is revealed by [data-open]; it used to
|
||||
* be toggled with data-show, which sets display:none, and display cannot
|
||||
* be transitioned -- the drawer simply snapped in and out. visibility is
|
||||
* delayed by the duration on the way out so the panel can finish sliding
|
||||
* before the layer is taken out of the hit-testing tree.
|
||||
*/
|
||||
.wire-drawer__layer {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1200;
|
||||
display: flex;
|
||||
pointer-events: none;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition:
|
||||
opacity var(--drawer-duration, 260ms) ease,
|
||||
visibility 0s linear var(--drawer-duration, 260ms);
|
||||
}
|
||||
|
||||
.wire-drawer[data-open="true"] .wire-drawer__layer {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition:
|
||||
opacity var(--drawer-duration, 260ms) ease,
|
||||
visibility 0s linear 0s;
|
||||
}
|
||||
|
||||
.wire-drawer__backdrop {
|
||||
@@ -262,6 +285,38 @@ open: boolean = false
|
||||
box-shadow: -28px 0 80px color-mix(in srgb, black 28%, transparent);
|
||||
pointer-events: auto;
|
||||
overflow: hidden;
|
||||
/* Slides in from whichever edge the placement puts it on. */
|
||||
transform: translateX(100%);
|
||||
transition: transform var(--drawer-duration, 260ms) cubic-bezier(0.32, 0.72, 0, 1);
|
||||
}
|
||||
|
||||
.wire-drawer[data-open="true"] .wire-drawer__panel {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.wire-drawer[data-placement="left"] .wire-drawer__panel {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
.wire-drawer[data-placement="top"] .wire-drawer__panel {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
.wire-drawer[data-placement="bottom"] .wire-drawer__panel {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
|
||||
.wire-drawer[data-open="true"][data-placement="left"] .wire-drawer__panel,
|
||||
.wire-drawer[data-open="true"][data-placement="top"] .wire-drawer__panel,
|
||||
.wire-drawer[data-open="true"][data-placement="bottom"] .wire-drawer__panel {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.wire-drawer__layer,
|
||||
.wire-drawer__panel {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
.wire-drawer[data-size="sm"] .wire-drawer__panel {
|
||||
@@ -395,12 +450,19 @@ open: boolean = false
|
||||
color: color-mix(in srgb, currentColor 76%, transparent);
|
||||
}
|
||||
|
||||
/*
|
||||
* padding is reset explicitly: an app-level `button { padding: ... }` rule
|
||||
* outranks the browser default and leaves this fixed-size button with a
|
||||
* content box of a couple of pixels, which squeezes the icon to a sliver
|
||||
* and reads as "the close button has no icon". Same trap as Modal.
|
||||
*/
|
||||
.wire-drawer__close {
|
||||
appearance: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 0 0 auto;
|
||||
padding: 0;
|
||||
width: 2.35rem;
|
||||
height: 2.35rem;
|
||||
color: var(--wire-color-text-muted);
|
||||
@@ -417,10 +479,30 @@ open: boolean = false
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Never let the glyph be shrunk by the flex container. */
|
||||
.wire-drawer__close svg {
|
||||
flex: 0 0 auto;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
/*
|
||||
* Slot content is authored by the host app, so the app global stylesheet
|
||||
* styles it too. A bare element selector there (p { color: ... }) beats
|
||||
* anything the panel merely *inherits*, which is how modal body copy ended
|
||||
* up muted grey on a saturated background. State the colour explicitly;
|
||||
* :where() keeps the specificity low enough that any class the app puts on
|
||||
* its own slot content still wins.
|
||||
*/
|
||||
.wire-drawer__body {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
padding: 1.35rem;
|
||||
color: var(--wire-color-text);
|
||||
}
|
||||
|
||||
.wire-drawer__body :where(p, li, dd, dt, h1, h2, h3, h4, h5, h6, span, label, code) {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.wire-drawer[data-scrollable="true"] .wire-drawer__body {
|
||||
|
||||
@@ -171,6 +171,7 @@ items: unknown[] = []
|
||||
|
||||
<div
|
||||
class="wire-dropdown__panel"
|
||||
data-wrn-anchored="true"
|
||||
data-show='{open || visible}'
|
||||
role="menu"
|
||||
aria-label='{menuLabel}'
|
||||
|
||||
@@ -35,6 +35,7 @@ component Modal {
|
||||
triggerLabel: string = ""
|
||||
triggerIcon: string = ""
|
||||
scrollable: boolean = true
|
||||
scrollBehavior: string = "inside"
|
||||
class: string = ""
|
||||
}
|
||||
|
||||
@@ -50,6 +51,16 @@ component Modal {
|
||||
output.open({ sourceEvent: sourceEvent })
|
||||
}
|
||||
|
||||
// Slot content can close the modal it sits in by dispatching a bubbling
|
||||
// wrnexus:modal:close event, e.g. from a form success handler:
|
||||
//
|
||||
// event.target.dispatchEvent(
|
||||
// new CustomEvent("wrnexus:modal:close", { bubbles: true })
|
||||
// )
|
||||
//
|
||||
// The listener is on the modal root, so the event only ever closes the
|
||||
// modal the dispatching element is actually inside -- no ids to wire up
|
||||
// and no way to close somebody else's modal by accident.
|
||||
client function hideModal(reason, sourceEvent) {
|
||||
visible = false
|
||||
output.close({
|
||||
@@ -87,21 +98,23 @@ component Modal {
|
||||
<div
|
||||
{...attrs}
|
||||
data-ui-component="Modal"
|
||||
data-open='{open || visible ? "true" : "false"}'
|
||||
data-open='{isOpen() ? "true" : "false"}'
|
||||
data-size='{size}'
|
||||
data-placement='{placement}'
|
||||
data-color='{color}'
|
||||
data-variant='{variant}'
|
||||
data-scrollable='{scrollable ? "true" : "false"}'
|
||||
data-scroll='{scrollBehavior}'
|
||||
data-destructive='{destructive ? "true" : "false"}'
|
||||
class='wire-modal {class}'
|
||||
@wrnexus:modal:close='hideModal("api", event)'
|
||||
>
|
||||
{#if triggerLabel}
|
||||
<button
|
||||
type="button"
|
||||
class="wire-modal__trigger"
|
||||
aria-haspopup="dialog"
|
||||
aria-expanded='{open || visible ? "true" : "false"}'
|
||||
aria-expanded='{isOpen() ? "true" : "false"}'
|
||||
@click='showModal(event)'
|
||||
>
|
||||
{#if triggerIcon}
|
||||
@@ -127,7 +140,7 @@ component Modal {
|
||||
|
||||
<div
|
||||
class="wire-modal__layer"
|
||||
data-show='{open || visible}'
|
||||
data-show="isOpen()"
|
||||
role="presentation"
|
||||
@keydown='handleKeydown(event)'
|
||||
>
|
||||
@@ -183,11 +196,20 @@ component Modal {
|
||||
aria-label='{closeLabel}'
|
||||
@click='hideModal("close-button", event)'
|
||||
>
|
||||
<span
|
||||
class="icon-[lucide--x]"
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
</span>
|
||||
<path d="M18 6 6 18" />
|
||||
<path d="M6 6 18 18" />
|
||||
</svg>
|
||||
</button>
|
||||
{/if}
|
||||
</header>
|
||||
@@ -277,16 +299,26 @@ component Modal {
|
||||
--modal-contrast: var(--wire-color-secondary-contrast);
|
||||
}
|
||||
|
||||
/*
|
||||
* Fallbacks below (the second var() argument): the theme token generator
|
||||
* (packages/styles/src/theme.ts) only emits a real -contrast token for
|
||||
* primary and secondary. info, success, and danger have no contrast
|
||||
* token defined at all, so var(--wire-color-info-contrast) with no
|
||||
* fallback resolves to nothing, and --modal-contrast becomes invalid --
|
||||
* which made confirm-button and solid-panel text unreadable. White is a
|
||||
* safe default against these saturated colors until the theme package
|
||||
* defines real tokens for them.
|
||||
*/
|
||||
.wire-modal[data-color="info"] {
|
||||
--modal-accent: var(--wire-color-info);
|
||||
--modal-soft: var(--wire-color-info-soft);
|
||||
--modal-contrast: var(--wire-color-info-contrast);
|
||||
--modal-contrast: var(--wire-color-info-contrast, white);
|
||||
}
|
||||
|
||||
.wire-modal[data-color="success"] {
|
||||
--modal-accent: var(--wire-color-success);
|
||||
--modal-soft: var(--wire-color-success-soft);
|
||||
--modal-contrast: var(--wire-color-success-contrast);
|
||||
--modal-contrast: var(--wire-color-success-contrast, white);
|
||||
}
|
||||
|
||||
.wire-modal[data-color="warning"] {
|
||||
@@ -299,7 +331,7 @@ component Modal {
|
||||
.wire-modal[data-destructive="true"] {
|
||||
--modal-accent: var(--wire-color-danger);
|
||||
--modal-soft: var(--wire-color-danger-soft);
|
||||
--modal-contrast: var(--wire-color-on-danger);
|
||||
--modal-contrast: var(--wire-color-on-danger, white);
|
||||
}
|
||||
|
||||
.wire-modal__trigger,
|
||||
@@ -321,8 +353,35 @@ component Modal {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 650;
|
||||
cursor: pointer;
|
||||
transition: opacity 150ms ease, transform 150ms ease, box-shadow 150ms ease;
|
||||
}
|
||||
|
||||
.wire-modal__trigger:hover {
|
||||
opacity: 0.92;
|
||||
}
|
||||
|
||||
.wire-modal__trigger:active {
|
||||
transform: scale(0.97);
|
||||
}
|
||||
|
||||
.wire-modal__trigger:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--modal-accent) 40%, transparent);
|
||||
}
|
||||
|
||||
/*
|
||||
* Hidden by default so the SSR-rendered HTML never paints the layer before
|
||||
* hydration runs. data-open on the wire-modal root is evaluated and
|
||||
* serialized to a real true or false string at render time -- a plain
|
||||
* bind, not the raw-expression data-show directive -- so this selector
|
||||
* is correct on first paint with zero flash, with no dependency on
|
||||
* client JS having run yet. The data-show attribute and client directive
|
||||
* still run after hydration to keep things in sync for state changes,
|
||||
* but visibility itself is driven by CSS here. visibility (rather than
|
||||
* display) is used so the open and close transitions below can actually
|
||||
* animate -- a box that starts at display: none has no prior frame to
|
||||
* transition from.
|
||||
*/
|
||||
.wire-modal__layer {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
@@ -331,6 +390,15 @@ component Modal {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1rem;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: opacity 180ms ease, visibility 0s linear 180ms;
|
||||
}
|
||||
|
||||
.wire-modal[data-open="true"] .wire-modal__layer {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition: opacity 180ms ease, visibility 0s linear 0s;
|
||||
}
|
||||
|
||||
.wire-modal[data-placement="top"] .wire-modal__layer {
|
||||
@@ -338,6 +406,12 @@ component Modal {
|
||||
padding-top: clamp(1rem, 8vh, 5rem);
|
||||
}
|
||||
|
||||
.wire-modal[data-scroll="page"] .wire-modal__layer {
|
||||
align-items: flex-start;
|
||||
overflow-y: auto;
|
||||
padding: 2.5rem 1rem;
|
||||
}
|
||||
|
||||
.wire-modal__backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
@@ -370,6 +444,18 @@ component Modal {
|
||||
0 1px 0 color-mix(in srgb, white 5%, transparent) inset,
|
||||
0 40px 110px color-mix(in srgb, black 38%, transparent);
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
transform: scale(0.96) translateY(10px);
|
||||
transition: opacity 180ms ease, transform 220ms cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
|
||||
.wire-modal[data-open="true"] .wire-modal__panel {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.wire-modal[data-size="xs"] .wire-modal__panel {
|
||||
width: min(19rem, calc(100vw - 2rem));
|
||||
}
|
||||
|
||||
.wire-modal[data-size="sm"] .wire-modal__panel {
|
||||
@@ -390,6 +476,10 @@ component Modal {
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.wire-modal[data-scroll="page"] .wire-modal__panel {
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.wire-modal[data-variant="soft"] .wire-modal__panel {
|
||||
background:
|
||||
linear-gradient(145deg, var(--modal-soft), transparent 68%),
|
||||
@@ -447,6 +537,7 @@ component Modal {
|
||||
}
|
||||
|
||||
.wire-modal__heading-copy h2 {
|
||||
color: inherit;
|
||||
font-size: 1.08rem;
|
||||
font-weight: 650;
|
||||
line-height: 1.3;
|
||||
@@ -462,32 +553,100 @@ component Modal {
|
||||
color: color-mix(in srgb, currentColor 76%, transparent);
|
||||
}
|
||||
|
||||
/*
|
||||
* padding is reset explicitly: an app-level `button { padding: … }` rule
|
||||
* outranks the browser default, and 1rem of horizontal padding left this
|
||||
* 2.15rem button with a ~2px content box -- which squeezed the icon to
|
||||
* 0.4px wide and read as "the close button has no icon".
|
||||
*/
|
||||
.wire-modal__close {
|
||||
appearance: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 0 0 auto;
|
||||
width: 2.35rem;
|
||||
height: 2.35rem;
|
||||
padding: 0;
|
||||
width: 2.15rem;
|
||||
height: 2.15rem;
|
||||
color: var(--wire-color-text-muted);
|
||||
background: var(--wire-color-surface-soft);
|
||||
border: 1px solid var(--wire-color-border);
|
||||
border-radius: 0.75rem;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 9999px;
|
||||
cursor: pointer;
|
||||
transition: background 150ms ease, color 150ms ease, border-color 150ms ease, transform 150ms ease;
|
||||
}
|
||||
|
||||
/*
|
||||
* On a solid panel the background is the accent color, so the muted-grey
|
||||
* default is close to invisible -- the dismiss affordance reads as
|
||||
* missing rather than subtle. Derive it from the panel contrast color
|
||||
* instead, and give it a faint ring so it is unmistakably a control.
|
||||
*/
|
||||
.wire-modal[data-variant="solid"] .wire-modal__close {
|
||||
color: color-mix(in srgb, currentColor 82%, transparent);
|
||||
border-color: color-mix(in srgb, currentColor 35%, transparent);
|
||||
}
|
||||
|
||||
.wire-modal[data-variant="solid"] .wire-modal__close:hover {
|
||||
color: currentColor;
|
||||
background: color-mix(in srgb, black 18%, transparent);
|
||||
border-color: color-mix(in srgb, currentColor 55%, transparent);
|
||||
}
|
||||
|
||||
.wire-modal__close:hover {
|
||||
color: var(--wire-color-text);
|
||||
background: var(--wire-color-surface-soft);
|
||||
}
|
||||
|
||||
.wire-modal__close:hover,
|
||||
.wire-modal__close:focus-visible {
|
||||
color: var(--modal-accent);
|
||||
border-color: color-mix(in srgb, var(--modal-accent) 34%, var(--wire-color-border));
|
||||
border-color: color-mix(in srgb, var(--modal-accent) 45%, transparent);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.wire-modal__close:active {
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
/* Never let the glyph be shrunk by the flex container. */
|
||||
.wire-modal__close svg {
|
||||
flex: 0 0 auto;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
/*
|
||||
* Slot content is authored by the host app, so the app global stylesheet
|
||||
* styles it too. A bare element selector there (p { color: ... }) beats
|
||||
* anything the panel merely *inherits*, which is how solid-variant modals
|
||||
* ended up with muted grey body copy on a saturated accent background --
|
||||
* unreadable, and worst exactly where contrast matters most (the
|
||||
* destructive confirm). Setting the color on the body makes the panel
|
||||
* choice explicit instead of leaving it to inheritance.
|
||||
*
|
||||
* NOTE: apostrophes are avoided in .wrn style comments on purpose -- the
|
||||
* block scanner treats a quote as a string delimiter while it counts
|
||||
* braces, so a stray one breaks parsing of the whole component.
|
||||
*/
|
||||
.wire-modal__body {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
padding: 1.4rem;
|
||||
color: var(--wire-color-text);
|
||||
}
|
||||
|
||||
/*
|
||||
* :where() keeps this at the specificity of .wire-modal__body alone, so it
|
||||
* outranks a global element selector but still yields to any class the app
|
||||
* puts on its own slot content (an error message, a muted caption). A
|
||||
* plain .wire-modal__body p list would have quietly overridden those.
|
||||
*/
|
||||
.wire-modal__body :where(p, li, dd, dt, h1, h2, h3, h4, h5, h6, span, label, code) {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.wire-modal[data-variant="solid"] .wire-modal__body {
|
||||
color: var(--modal-contrast);
|
||||
}
|
||||
|
||||
.wire-modal[data-scrollable="true"] .wire-modal__body {
|
||||
@@ -548,6 +707,18 @@ component Modal {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
/*
|
||||
* On a solid-variant panel the panel background is also --modal-accent,
|
||||
* so a plain primary button (same color) has no visible edge against it.
|
||||
* Darken the fill slightly and add a light border so the button still
|
||||
* reads as a distinct, clickable pill instead of blending into the panel.
|
||||
*/
|
||||
.wire-modal[data-variant="solid"] .wire-modal__button--primary {
|
||||
background: color-mix(in srgb, black 18%, var(--modal-accent));
|
||||
border-color: color-mix(in srgb, white 32%, transparent);
|
||||
box-shadow: 0 1px 0 color-mix(in srgb, white 12%, transparent) inset;
|
||||
}
|
||||
|
||||
.wire-modal__button:disabled {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
@@ -575,6 +746,7 @@ component Modal {
|
||||
}
|
||||
|
||||
.wire-modal__panel,
|
||||
.wire-modal[data-size="xs"] .wire-modal__panel,
|
||||
.wire-modal[data-size="sm"] .wire-modal__panel,
|
||||
.wire-modal[data-size="lg"] .wire-modal__panel,
|
||||
.wire-modal[data-size="xl"] .wire-modal__panel {
|
||||
@@ -599,10 +771,17 @@ component Modal {
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.wire-modal__button,
|
||||
.wire-modal__spinner {
|
||||
.wire-modal__spinner,
|
||||
.wire-modal__layer,
|
||||
.wire-modal__panel,
|
||||
.wire-modal__close {
|
||||
animation: none;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.wire-modal__panel {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,12 +123,13 @@ component Popover {
|
||||
|
||||
<section
|
||||
class="wire-popover__panel"
|
||||
data-wrn-anchored="true"
|
||||
data-show='{open || visible}'
|
||||
role="dialog"
|
||||
aria-label='{title || triggerLabel}'
|
||||
>
|
||||
{#if showArrow}
|
||||
<span class="wire-popover__arrow" aria-hidden="true"></span>
|
||||
<span class="wire-popover__arrow" data-wrn-anchor-arrow="true" aria-hidden="true"></span>
|
||||
{/if}
|
||||
|
||||
{#if title || description || icon || showClose}
|
||||
@@ -471,7 +472,13 @@ component Popover {
|
||||
color: color-mix(in srgb, currentColor 76%, transparent);
|
||||
}
|
||||
|
||||
/*
|
||||
* padding is reset explicitly: an app-level `button { padding: ... }` rule
|
||||
* outranks the browser default and crushes the icon inside this
|
||||
* fixed-size button. Same trap as Modal and Drawer.
|
||||
*/
|
||||
.wire-popover__close {
|
||||
padding: 0;
|
||||
appearance: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
component Table {
|
||||
outputs {
|
||||
sort(payload: { 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(payload: { 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)
|
||||
rowClick(payload: { 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)
|
||||
}
|
||||
|
||||
props {
|
||||
size: string = "default"
|
||||
color: string = "primary"
|
||||
caption: string = "Table"
|
||||
columns: unknown[] = []
|
||||
rows: unknown[] = []
|
||||
striped: boolean = true
|
||||
class: string = ""
|
||||
}
|
||||
view {
|
||||
<div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--table {class}"><table><caption>{caption}</caption><thead><tr>{#each columns as column}<th>{column.label}</th>{/each}</tr></thead><tbody>{#each rows as row}<tr>{#each columns as column}<td>{row[column.key]}</td>{/each}</tr>{/each}</tbody></table><slot /></div>
|
||||
}
|
||||
}
|
||||
@@ -97,11 +97,12 @@ id: string = ""
|
||||
<span
|
||||
id='{id}'
|
||||
class="wire-tooltip__content"
|
||||
data-wrn-anchored="true"
|
||||
data-show='{open || visible}'
|
||||
role="tooltip"
|
||||
>
|
||||
{#if showArrow}
|
||||
<span class="wire-tooltip__arrow" aria-hidden="true"></span>
|
||||
<span class="wire-tooltip__arrow" data-wrn-anchor-arrow="true" aria-hidden="true"></span>
|
||||
{/if}
|
||||
|
||||
{#if title}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ui",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/ui — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -35,6 +35,10 @@
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
},
|
||||
"./registry": {
|
||||
"types": "./dist/registry.d.ts",
|
||||
"import": "./dist/registry.js"
|
||||
},
|
||||
"./components/*": "./components/*",
|
||||
"./component-catalog.json": "./component-catalog.json",
|
||||
"./component-migrations.json": "./component-migrations.json",
|
||||
@@ -42,7 +46,7 @@
|
||||
"./ui.css": "./ui.css"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.8.4"
|
||||
"@wrnexus/core": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -8037,28 +8037,6 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.wire-next--table {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
border: 1px solid var(--wire-color-border);
|
||||
border-radius: var(--wire-radius-md);
|
||||
}
|
||||
.wire-next--table table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.wire-next--table caption,
|
||||
.wire-next--table th,
|
||||
.wire-next--table td {
|
||||
padding: 0.75rem;
|
||||
border-bottom: 1px solid var(--wire-color-border);
|
||||
text-align: left;
|
||||
}
|
||||
.wire-next--table caption,
|
||||
.wire-next--table th {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
@keyframes wire-shimmer {
|
||||
to {
|
||||
background-position: -200% 0;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/uploader",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "Secure upload drivers, policies, client runtime, helper functions, and reusable upload components.",
|
||||
"license": "MIT",
|
||||
@@ -42,9 +42,9 @@
|
||||
"./components/*": "./components/*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.8.4",
|
||||
"@wrnexus/plugin": "^0.8.4",
|
||||
"@wrnexus/ui": "^0.8.4"
|
||||
"@wrnexus/core": "^0.8.5",
|
||||
"@wrnexus/plugin": "^0.8.5",
|
||||
"@wrnexus/ui": "^0.8.5"
|
||||
},
|
||||
"wrnexus": {
|
||||
"plugin": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/validation",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "Shared server/browser schemas, validation helpers, form runtime, and reusable error components.",
|
||||
"license": "MIT",
|
||||
@@ -42,9 +42,9 @@
|
||||
"./components/*": "./components/*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.8.4",
|
||||
"@wrnexus/plugin": "^0.8.4",
|
||||
"@wrnexus/ui": "^0.8.4"
|
||||
"@wrnexus/core": "^0.8.5",
|
||||
"@wrnexus/plugin": "^0.8.5",
|
||||
"@wrnexus/ui": "^0.8.5"
|
||||
},
|
||||
"wrnexus": {
|
||||
"plugin": {
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "wrnexus",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^5.0.9",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@types/bun": "^1.3.14",
|
||||
@@ -20,8 +23,10 @@
|
||||
"version": "0.8.0",
|
||||
"dependencies": {
|
||||
"@wrnexus/auth": "workspace:*",
|
||||
"@wrnexus/authz": "workspace:*",
|
||||
"@wrnexus/captcha": "workspace:*",
|
||||
"@wrnexus/core": "workspace:*",
|
||||
"@wrnexus/rpc": "workspace:*",
|
||||
"@wrnexus/validation": "workspace:*",
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -102,11 +107,11 @@
|
||||
},
|
||||
"packages/ai": {
|
||||
"name": "@wrnexus/ai",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
},
|
||||
"packages/auth": {
|
||||
"name": "@wrnexus/auth",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/authz": "workspace:*",
|
||||
"@wrnexus/captcha": "workspace:*",
|
||||
@@ -128,22 +133,26 @@
|
||||
},
|
||||
"packages/authz": {
|
||||
"name": "@wrnexus/authz",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "workspace:*",
|
||||
"@wrnexus/db": "workspace:*",
|
||||
},
|
||||
},
|
||||
"packages/benchmark": {
|
||||
"name": "@wrnexus/benchmark",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
},
|
||||
"packages/cache": {
|
||||
"name": "@wrnexus/cache",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "workspace:*",
|
||||
},
|
||||
},
|
||||
"packages/captcha": {
|
||||
"name": "@wrnexus/captcha",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "workspace:*",
|
||||
"@wrnexus/plugin": "workspace:*",
|
||||
@@ -158,11 +167,12 @@
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "@wrnexus/cli",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"bin": {
|
||||
"wrnexus": "src/index.ts",
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/authz": "workspace:*",
|
||||
"@wrnexus/compiler": "workspace:*",
|
||||
"@wrnexus/core": "workspace:*",
|
||||
"@wrnexus/csr": "workspace:*",
|
||||
@@ -184,7 +194,7 @@
|
||||
},
|
||||
"packages/compiler": {
|
||||
"name": "@wrnexus/compiler",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/csr": "workspace:*",
|
||||
"@wrnexus/store": "workspace:*",
|
||||
@@ -194,7 +204,7 @@
|
||||
},
|
||||
"packages/content": {
|
||||
"name": "@wrnexus/content",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.3.14",
|
||||
"typescript": "^5.9.2",
|
||||
@@ -202,18 +212,18 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@wrnexus/core",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
},
|
||||
"packages/csr": {
|
||||
"name": "@wrnexus/csr",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "workspace:*",
|
||||
},
|
||||
},
|
||||
"packages/db": {
|
||||
"name": "@wrnexus/db",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.3.14",
|
||||
"typescript": "^5.9.2",
|
||||
@@ -221,8 +231,9 @@
|
||||
},
|
||||
"packages/dev-server": {
|
||||
"name": "@wrnexus/dev-server",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/authz": "workspace:*",
|
||||
"@wrnexus/cache": "workspace:*",
|
||||
"@wrnexus/compiler": "workspace:*",
|
||||
"@wrnexus/core": "workspace:*",
|
||||
@@ -235,6 +246,7 @@
|
||||
"@wrnexus/pubsub": "workspace:*",
|
||||
"@wrnexus/pwa": "workspace:*",
|
||||
"@wrnexus/router": "workspace:*",
|
||||
"@wrnexus/rpc": "workspace:*",
|
||||
"@wrnexus/security": "workspace:*",
|
||||
"@wrnexus/ssr": "workspace:*",
|
||||
"@wrnexus/store": "workspace:*",
|
||||
@@ -246,7 +258,7 @@
|
||||
},
|
||||
"packages/dev-toolbar": {
|
||||
"name": "@wrnexus/dev-toolbar",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.3.14",
|
||||
"typescript": "^5.9.2",
|
||||
@@ -254,7 +266,7 @@
|
||||
},
|
||||
"packages/encryption": {
|
||||
"name": "@wrnexus/encryption",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "workspace:*",
|
||||
},
|
||||
@@ -265,21 +277,21 @@
|
||||
},
|
||||
"packages/graphql": {
|
||||
"name": "@wrnexus/graphql",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/plugin": "workspace:*",
|
||||
},
|
||||
},
|
||||
"packages/helpers": {
|
||||
"name": "@wrnexus/helpers",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "workspace:*",
|
||||
},
|
||||
},
|
||||
"packages/i18n": {
|
||||
"name": "@wrnexus/i18n",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "workspace:*",
|
||||
"@wrnexus/plugin": "workspace:*",
|
||||
@@ -293,7 +305,7 @@
|
||||
},
|
||||
"packages/identity": {
|
||||
"name": "@wrnexus/identity",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/auth": "workspace:*",
|
||||
"@wrnexus/authz": "workspace:*",
|
||||
@@ -302,7 +314,7 @@
|
||||
},
|
||||
"packages/image": {
|
||||
"name": "@wrnexus/image",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/plugin": "workspace:*",
|
||||
"@wrnexus/security": "workspace:*",
|
||||
@@ -322,7 +334,7 @@
|
||||
},
|
||||
"packages/jwt": {
|
||||
"name": "@wrnexus/jwt",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "workspace:*",
|
||||
},
|
||||
@@ -333,7 +345,7 @@
|
||||
},
|
||||
"packages/language-server": {
|
||||
"name": "@wrnexus/language-server",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"bin": {
|
||||
"wrnexus-language-server": "src/server.ts",
|
||||
},
|
||||
@@ -344,39 +356,39 @@
|
||||
},
|
||||
"packages/mcp": {
|
||||
"name": "@wrnexus/mcp",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"bin": {
|
||||
"wrnexus-mcp": "src/stdio.ts",
|
||||
},
|
||||
},
|
||||
"packages/mobile": {
|
||||
"name": "@wrnexus/mobile",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/native": "workspace:*",
|
||||
},
|
||||
},
|
||||
"packages/native": {
|
||||
"name": "@wrnexus/native",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
},
|
||||
"packages/oauth": {
|
||||
"name": "@wrnexus/oauth",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/jwt": "workspace:*",
|
||||
},
|
||||
},
|
||||
"packages/observability": {
|
||||
"name": "@wrnexus/observability",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "workspace:*",
|
||||
},
|
||||
},
|
||||
"packages/playground": {
|
||||
"name": "@wrnexus/playground",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/compiler": "workspace:*",
|
||||
"@wrnexus/syntax": "workspace:*",
|
||||
@@ -384,18 +396,18 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@wrnexus/plugin",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/syntax": "workspace:*",
|
||||
},
|
||||
},
|
||||
"packages/pubsub": {
|
||||
"name": "@wrnexus/pubsub",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
},
|
||||
"packages/pwa": {
|
||||
"name": "@wrnexus/pwa",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.3.14",
|
||||
"typescript": "^5.9.2",
|
||||
@@ -403,18 +415,18 @@
|
||||
},
|
||||
"packages/queue": {
|
||||
"name": "@wrnexus/queue",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "workspace:*",
|
||||
},
|
||||
},
|
||||
"packages/reactive": {
|
||||
"name": "@wrnexus/reactive",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
},
|
||||
"packages/realtime": {
|
||||
"name": "@wrnexus/realtime",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "workspace:*",
|
||||
"@wrnexus/plugin": "workspace:*",
|
||||
@@ -428,22 +440,37 @@
|
||||
},
|
||||
"packages/router": {
|
||||
"name": "@wrnexus/router",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/compiler": "workspace:*",
|
||||
"@wrnexus/core": "workspace:*",
|
||||
},
|
||||
},
|
||||
"packages/rpc": {
|
||||
"name": "@wrnexus/rpc",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/authz": "workspace:*",
|
||||
"@wrnexus/core": "workspace:*",
|
||||
"@wrnexus/helpers": "workspace:*",
|
||||
"@wrnexus/jwt": "workspace:*",
|
||||
"@wrnexus/validation": "workspace:*",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.3.14",
|
||||
"typescript": "^5.9.2",
|
||||
},
|
||||
},
|
||||
"packages/security": {
|
||||
"name": "@wrnexus/security",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "workspace:*",
|
||||
},
|
||||
},
|
||||
"packages/ssr": {
|
||||
"name": "@wrnexus/ssr",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "workspace:*",
|
||||
"@wrnexus/security": "workspace:*",
|
||||
@@ -452,11 +479,11 @@
|
||||
},
|
||||
"packages/store": {
|
||||
"name": "@wrnexus/store",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
},
|
||||
"packages/styles": {
|
||||
"name": "@wrnexus/styles",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "workspace:*",
|
||||
"@wrnexus/plugin": "workspace:*",
|
||||
@@ -465,19 +492,19 @@
|
||||
},
|
||||
"packages/syntax": {
|
||||
"name": "@wrnexus/syntax",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
},
|
||||
"packages/test": {
|
||||
"name": "@wrnexus/test",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
},
|
||||
"packages/tracking": {
|
||||
"name": "@wrnexus/tracking",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
},
|
||||
"packages/typecheck": {
|
||||
"name": "@wrnexus/typecheck",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/syntax": "workspace:*",
|
||||
"typescript": "^5.5.0",
|
||||
@@ -485,14 +512,14 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@wrnexus/ui",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "workspace:*",
|
||||
},
|
||||
},
|
||||
"packages/uploader": {
|
||||
"name": "@wrnexus/uploader",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "workspace:*",
|
||||
"@wrnexus/plugin": "workspace:*",
|
||||
@@ -506,7 +533,7 @@
|
||||
},
|
||||
"packages/validation": {
|
||||
"name": "@wrnexus/validation",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "workspace:*",
|
||||
"@wrnexus/plugin": "workspace:*",
|
||||
@@ -531,7 +558,7 @@
|
||||
},
|
||||
},
|
||||
"overrides": {
|
||||
"brace-expansion": "5.0.8",
|
||||
"brace-expansion": "5.0.9",
|
||||
"esbuild": "0.28.1",
|
||||
},
|
||||
"packages": {
|
||||
@@ -877,6 +904,8 @@
|
||||
|
||||
"@wrnexus/router": ["@wrnexus/router@workspace:packages/router"],
|
||||
|
||||
"@wrnexus/rpc": ["@wrnexus/rpc@workspace:packages/rpc"],
|
||||
|
||||
"@wrnexus/security": ["@wrnexus/security@workspace:packages/security"],
|
||||
|
||||
"@wrnexus/ssr": ["@wrnexus/ssr@workspace:packages/ssr"],
|
||||
@@ -917,7 +946,7 @@
|
||||
|
||||
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@5.0.8", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg=="],
|
||||
"brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="],
|
||||
|
||||
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||
|
||||
|
||||
@@ -0,0 +1,500 @@
|
||||
# WRNexusJS — Security & Power Improvement Plan
|
||||
|
||||
Prepared for WorkRoot · covers `E:\WireJS` (framework v0.8.4)
|
||||
|
||||
This plan is scoped and sequenced the way the repo's own roadmap docs are (`ROADMAP-V1.md`,
|
||||
`IMPLEMENTATION-ROADMAP-0.8.md`): version-gated phases, one package/file set per item, with
|
||||
implementation, tests, and doc updates called out per item so each phase can ship as a real release
|
||||
with a `bun run validate:0.X` gate, like 0.7 and 0.8 did.
|
||||
|
||||
Every proposed API below follows conventions already in the repo (workspace `package.json` shape,
|
||||
`Middleware`/`Context` typing from `@wrnexus/core`, ASVS row format in `docs/SECURITY-ASVS-5.md`,
|
||||
audit-sink pattern from `packages/authz/src/audit.ts`) rather than inventing new patterns.
|
||||
|
||||
---
|
||||
|
||||
## Phasing overview
|
||||
|
||||
| Phase | Version | Theme | New packages | Est. effort |
|
||||
| ----- | ------------- | ----------------------------------------------- | ---------------------------------------------- | ----------- |
|
||||
| 1 | 0.8.5 (patch) | Security default fixes, no breaking changes | none | 1–2 weeks |
|
||||
| 2 | 0.9 | Distributed rate limiting + live security audit | `@wrnexus/ratelimit-redis` | 2–3 weeks |
|
||||
| 3 | 0.10 | Product-critical DX packages | `@wrnexus/mail`, `@wrnexus/flags` | 4–6 weeks |
|
||||
| 4 | 0.11 | Search + AI pairing | `@wrnexus/search` | 3–4 weeks |
|
||||
| 5 | 0.12 | Enterprise/government readiness | SAML in `@wrnexus/auth`, `@wrnexus/compliance` | 5–7 weeks |
|
||||
| 6 | 1.0 | Monetization + ecosystem | `@wrnexus/billing`, public release strategy | 6–10 weeks |
|
||||
|
||||
Total: roughly 6–8 months at a small-team pace, phased so each release is independently shippable
|
||||
and dogfoodable on `workroot.in` / `wrnexusjs.dev` / the WRNexus SaaS itself before the next phase
|
||||
starts.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — v0.8.5: Security default fixes
|
||||
|
||||
No new packages. Pure hardening of existing code, all changes are config-default flips, so they are
|
||||
non-breaking for anyone who already sets these fields explicitly and only change behavior for people
|
||||
relying on the current default.
|
||||
|
||||
### 1.1 Trusted Types default allowlist
|
||||
|
||||
**File:** `packages/core/src/headers.ts`, function `applyTrustedTypesDirectives`
|
||||
|
||||
**Problem:** `policyNames` defaults to `["*"]` in production, so any script — including an injected
|
||||
one — can register a Trusted Types policy. This defeats the XSS mitigation Trusted Types exists for.
|
||||
|
||||
**Change:**
|
||||
|
||||
```ts
|
||||
// Before
|
||||
const policyNames =
|
||||
typeof trustedTypes === "object" && trustedTypes.policyNames?.length
|
||||
? trustedTypes.policyNames
|
||||
: ["*"];
|
||||
|
||||
// After
|
||||
const policyNames =
|
||||
typeof trustedTypes === "object" && trustedTypes.policyNames?.length
|
||||
? trustedTypes.policyNames
|
||||
: ["wrnexus", "default"];
|
||||
```
|
||||
|
||||
Update the `TrustedTypesConfig.policyNames` doc comment to explain the new default and how to opt
|
||||
back into `["*"]` for apps with third-party extensions that need it.
|
||||
|
||||
**Tests:** update `packages/core/test/headers.test.ts` assertions that currently expect `*`.
|
||||
|
||||
**Docs:** update the "Production configuration baseline" block in `docs/SECURITY-PERFORMANCE-0.7.md`
|
||||
and add a migration note to `docs/UPGRADE-0.8.3.md`-style upgrade doc for 0.8.5.
|
||||
|
||||
### 1.2 HSTS `preload` default
|
||||
|
||||
**File:** `packages/core/src/headers.ts`, function `serializeHsts`
|
||||
|
||||
**Problem:** `preload: true` is on by default whenever `mode === "production"`. Preload-list
|
||||
submission is a long-lived commitment (removal takes months across browsers); defaulting it on for
|
||||
every production build is a footgun for teams not ready to guarantee HTTPS on every subdomain
|
||||
permanently.
|
||||
|
||||
**Change:**
|
||||
|
||||
```ts
|
||||
function serializeHsts(config: HstsConfig): string {
|
||||
const parts = [`max-age=${config.maxAge ?? 31536000}`];
|
||||
if (config.includeSubDomains !== false) parts.push("includeSubDomains");
|
||||
if (config.preload === true) parts.push("preload"); // was: !== false
|
||||
return parts.join("; ");
|
||||
}
|
||||
```
|
||||
|
||||
`includeSubDomains` can stay default-on (safe, reversible); only `preload` flips to opt-in.
|
||||
|
||||
**Tests:** update `packages/core/test/headers.test.ts`.
|
||||
|
||||
**Docs:** update `SECURITY-ASVS-5.md` row for `v5.0.0-3.4.1` evidence note and the production config
|
||||
baseline example (explicitly show `hsts: { preload: true }` as something apps opt into, with a
|
||||
one-line warning comment).
|
||||
|
||||
### 1.3 Live security-header verification (`--url` mode)
|
||||
|
||||
**File:** `packages/cli/src/security-command.ts`
|
||||
|
||||
**Problem:** `securityAudit()` already exists and is solid — it loads the local app config, builds a
|
||||
synthetic request/response, and runs `withSecurityHeaders` to check what headers _would_ be emitted.
|
||||
It never checks what a _deployed_ site is actually serving, so a misconfigured reverse proxy,
|
||||
missing env var, or config drift between local and production is invisible to `wrnexus security
|
||||
audit` today.
|
||||
|
||||
**Change:** add a second code path that takes a URL instead of an app root:
|
||||
|
||||
```ts
|
||||
export interface SecurityAuditOptions {
|
||||
appRoot?: string;
|
||||
/** Fetch a live deployment and audit its actual response headers instead of a local config. */
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export async function securityAudit(options: SecurityAuditOptions): Promise<SecurityAuditReport> {
|
||||
const headers = options.url
|
||||
? await fetchLiveHeaders(options.url)
|
||||
: await securityHeaders(resolve(options.appRoot ?? "."));
|
||||
// same checks[] logic runs against either header source
|
||||
...
|
||||
}
|
||||
|
||||
async function fetchLiveHeaders(url: string): Promise<Record<string, string>> {
|
||||
const res = await fetch(url, { method: "HEAD", redirect: "manual" });
|
||||
return Object.fromEntries(res.headers.entries());
|
||||
}
|
||||
```
|
||||
|
||||
CLI surface: `wrnexus security audit --url=https://workroot.in` — same `SecurityAuditCheck[]` table
|
||||
output as the local mode, so it's a drop-in mental model for anyone who's already used the local
|
||||
version.
|
||||
|
||||
**Tests:** `packages/cli/test/security-command.test.ts` — mock `fetch`, assert the same check IDs run
|
||||
against a header map built from a fake `Response`.
|
||||
|
||||
**Docs:** update `SECURITY-ASVS-5.md`'s intro line ("Run `bun run security:asvs` ... `wrnexus
|
||||
security audit`") to mention the `--url` mode explicitly.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — v0.9: Distributed rate limiting
|
||||
|
||||
### 2.1 `@wrnexus/ratelimit-redis`
|
||||
|
||||
**Problem:** `packages/core/src/ratelimit.ts` is honestly documented as process-local by default,
|
||||
with a clean `RateLimitStore` interface for swapping in a shared store — but no first-party
|
||||
implementation ships. Every team running more than one instance has to write their own Redis
|
||||
`INCR`/`PEXPIRE` bucket store before rate limiting actually works in production.
|
||||
|
||||
**New package layout** (mirrors `packages/captcha`'s `stores/redis.ts` pattern, which already
|
||||
exists for CAPTCHA — this is literally copying a pattern you've already built once):
|
||||
|
||||
```
|
||||
packages/ratelimit-redis/
|
||||
package.json
|
||||
src/
|
||||
index.ts # createRedisRateLimitStore()
|
||||
client.ts # thin ioredis/bun-redis wrapper, injectable client
|
||||
test/
|
||||
store.test.ts # against a real or mocked Redis
|
||||
README.md
|
||||
```
|
||||
|
||||
**API:**
|
||||
|
||||
```ts
|
||||
import { createRedisRateLimitStore } from "@wrnexus/ratelimit-redis";
|
||||
import { rateLimit } from "@wrnexus/core";
|
||||
|
||||
const store = createRedisRateLimitStore({ url: process.env.REDIS_URL! });
|
||||
app.use(rateLimit({ store, max: 100, windowMs: 60_000 }));
|
||||
```
|
||||
|
||||
Implementation: one atomic Lua script (`INCR` + conditional `PEXPIRE`) to avoid a race between the
|
||||
increment and the expiry set — same correctness bar as the in-memory store's atomicity within a
|
||||
single process.
|
||||
|
||||
**package.json:**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "@wrnexus/ratelimit-redis",
|
||||
"version": "0.9.0",
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
"exports": { ".": "./src/index.ts" },
|
||||
"dependencies": { "@wrnexus/core": "workspace:*" }
|
||||
}
|
||||
```
|
||||
|
||||
**Tests:** `store.test.ts` covering window rollover, concurrent-hit correctness (fire N parallel
|
||||
`hit()` calls, assert exact count), and store failure fallback behavior (Redis down → fail open with
|
||||
a warning log, documented explicitly so nobody is surprised).
|
||||
|
||||
**Docs:** add a row to `SECURITY-SUPPORT-MATRIX.md` under a new "Rate limiting" area, and link it from
|
||||
the `RateLimitStore` doc comment in `packages/core/src/ratelimit.ts`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — v0.10: Product-critical DX packages
|
||||
|
||||
### 3.1 `@wrnexus/mail`
|
||||
|
||||
**Problem:** there's no first-party way to actually send email. The queue example
|
||||
(`app/queues/welcome-email.ts`) shows _scheduling_ an email job but nothing implements delivery.
|
||||
|
||||
**Package layout:**
|
||||
|
||||
```
|
||||
packages/mail/
|
||||
package.json
|
||||
src/
|
||||
index.ts
|
||||
send.ts # sendMail(), core envelope type
|
||||
providers/
|
||||
resend.ts
|
||||
ses.ts
|
||||
postmark.ts
|
||||
smtp.ts
|
||||
dev-inbox.ts # captures mail in dev instead of sending; wrnexus dev shows it in DevToolbar
|
||||
components/ # optional .wrn email-template partials, reusing the compiler
|
||||
test/
|
||||
send.test.ts
|
||||
dev-inbox.test.ts
|
||||
README.md
|
||||
SECURITY.md
|
||||
```
|
||||
|
||||
**API (mirrors the `SafeUrlPolicy`/provider-adapter shape from `@wrnexus/captcha`'s providers):**
|
||||
|
||||
```ts
|
||||
export interface MailProvider {
|
||||
send(message: MailMessage): Promise<MailResult>;
|
||||
}
|
||||
|
||||
export interface MailMessage {
|
||||
to: string | string[];
|
||||
from: string;
|
||||
subject: string;
|
||||
html?: string;
|
||||
text?: string;
|
||||
replyTo?: string;
|
||||
headers?: Record<string, string>;
|
||||
}
|
||||
|
||||
export function createMailer(provider: MailProvider): { send(m: MailMessage): Promise<MailResult> };
|
||||
|
||||
// providers/resend.ts
|
||||
export function resendProvider(opts: { apiKey: string }): MailProvider;
|
||||
```
|
||||
|
||||
**Dev-mode behavior:** when `mode !== "production"`, `createMailer` wraps any provider with
|
||||
`devInboxProvider()`, which stores messages in memory and surfaces them in the DevToolbar (new panel,
|
||||
same pattern as the existing SQL/queue/realtime DevToolbar providers listed in
|
||||
`SECURITY-PERFORMANCE-0.7.md` §18) instead of actually sending — this is the single highest-value DX
|
||||
win in this package, since it removes the "did my email actually work" debugging loop entirely.
|
||||
|
||||
**Security notes for `SECURITY-SUPPORT-MATRIX.md`:** framework guarantees provider-secret handling
|
||||
stays server-side and never serializes into hydration payloads (reuse the existing serialization
|
||||
redaction from `@wrnexus/security/serialization.ts`); app/operator responsibility covers SPF/DKIM/DMARC
|
||||
DNS records and provider account reputation.
|
||||
|
||||
**Effort:** ~2–3 weeks for `resend` + `smtp` providers, dev inbox, and DevToolbar panel; `ses` and
|
||||
`postmark` can follow as a fast-follow since they share the same `MailProvider` interface.
|
||||
|
||||
### 3.2 `@wrnexus/flags`
|
||||
|
||||
**Problem:** no feature-flag primitive. Every team building past MVP eventually needs gradual
|
||||
rollout, and right now they'd bolt on a third-party SDK with no integration into WRNexusJS's
|
||||
`.wrn` reactivity or SSR model.
|
||||
|
||||
**API:**
|
||||
|
||||
```ts
|
||||
export interface FlagsStore {
|
||||
get(key: string, ctx: FlagContext): Promise<boolean | string | number>;
|
||||
}
|
||||
|
||||
export function createFlags(store: FlagsStore): {
|
||||
isEnabled(key: string, ctx: FlagContext): Promise<boolean>;
|
||||
variant(key: string, ctx: FlagContext): Promise<string | undefined>;
|
||||
};
|
||||
|
||||
// stores/memory.ts, stores/db.ts (reuses @wrnexus/db), stores/percentage.ts (deterministic hash rollout)
|
||||
```
|
||||
|
||||
`.wrn` integration: expose `load server { const enabled = await flags.isEnabled("new-dashboard",
|
||||
ctx) }` so flags flow into `props`/`state` the same way `load server` results already do — no new
|
||||
compiler syntax needed, just a helper package.
|
||||
|
||||
**Effort:** ~1–2 weeks; the DB-backed store reuses `@wrnexus/db` migration patterns already in the
|
||||
repo, so most of the work is the percentage-rollout hashing and the DevToolbar panel.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — v0.11: Search, paired with `@wrnexus/ai`
|
||||
|
||||
### 4.1 `@wrnexus/search`
|
||||
|
||||
**Problem:** no first-party search story, despite already having `@wrnexus/ai` for embeddings/LLM
|
||||
calls — search and RAG are the two things most SaaS apps need `@wrnexus/ai` _for_, so this is the
|
||||
natural pairing package.
|
||||
|
||||
**Package layout:**
|
||||
|
||||
```
|
||||
packages/search/
|
||||
src/
|
||||
index.ts
|
||||
adapters/
|
||||
postgres-fts.ts # tsvector + GIN index helpers, generated migration
|
||||
sqlite-fts5.ts
|
||||
pgvector.ts # embeddings via @wrnexus/ai, cosine-distance query helper
|
||||
typesense.ts # optional hosted adapter
|
||||
test/
|
||||
```
|
||||
|
||||
**API:**
|
||||
|
||||
```ts
|
||||
export function createSearchIndex(adapter: SearchAdapter, table: string, columns: string[]);
|
||||
const results = await search.query("customer onboarding", { limit: 10 });
|
||||
|
||||
// pairs directly with @wrnexus/ai
|
||||
import { embed } from "@wrnexus/ai";
|
||||
const vector = await embed(text);
|
||||
await search.upsertVector(id, vector);
|
||||
```
|
||||
|
||||
**Effort:** ~3–4 weeks — Postgres FTS and SQLite FTS5 adapters first (no new infra dependency,
|
||||
reuses `@wrnexus/db`'s existing driver abstraction), pgvector and Typesense as fast-follows.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5 — v0.12: Enterprise & government readiness
|
||||
|
||||
This phase is prioritized specifically because `workroot.in` markets to "enterprises & governments" —
|
||||
these two items are the actual procurement blockers for that buyer, more than any generic framework
|
||||
feature would be.
|
||||
|
||||
### 5.1 SAML support in `@wrnexus/auth`
|
||||
|
||||
**Problem:** OAuth/OIDC is implemented (per `SECURITY-SUPPORT-MATRIX.md`), but large-enterprise and
|
||||
government IT departments frequently mandate SAML 2.0 specifically for SSO procurement, regardless of
|
||||
OIDC's technical merits.
|
||||
|
||||
**Location:** `packages/auth/src/saml/` — new subdirectory alongside the existing OAuth/OIDC code,
|
||||
same `AuthProvider` interface shape so it plugs into the existing account/session engine
|
||||
(`packages/auth/src/engine.ts`) without a parallel auth system.
|
||||
|
||||
**Scope:** SP-initiated SSO, signed assertion validation, configurable IdP metadata (Okta, Azure AD,
|
||||
Google Workspace, ADFS as the four IdPs to certify against first — that covers the large majority of
|
||||
enterprise/government IT estates).
|
||||
|
||||
**Security notes:** reuse the existing `AUTH_SECURITY_EVENT_TYPES` vocabulary for SAML-specific
|
||||
events (assertion replay attempt, signature validation failure, clock-skew rejection) so they flow
|
||||
into the same audit pipeline as every other auth event — no parallel logging system.
|
||||
|
||||
**Effort:** ~4–5 weeks; SAML assertion validation is fiddly (XML canonicalization, signature
|
||||
wrapping attacks) and deserves a dedicated security review pass before release, not just unit tests.
|
||||
|
||||
### 5.2 `@wrnexus/compliance`
|
||||
|
||||
**Problem:** `packages/authz/src/audit.ts` already has a well-built `AuthzAuditSink` interface with
|
||||
log-injection-safe formatting (`logSafe()`) and memory/console sinks — but no durable, exportable
|
||||
store. For SOC 2-style evidence or India's DPDP Act data-processing records, teams need retained,
|
||||
queryable, exportable audit trails, not console lines.
|
||||
|
||||
**Package layout:**
|
||||
|
||||
```
|
||||
packages/compliance/
|
||||
src/
|
||||
index.ts
|
||||
sinks/
|
||||
db.ts # durable AuthzAuditSink + auth security-event sink, reusing @wrnexus/db
|
||||
export.ts # CSV/JSON export with retention-window filtering
|
||||
retention.ts # configurable retention policy + scheduled purge
|
||||
test/
|
||||
```
|
||||
|
||||
**API:**
|
||||
|
||||
```ts
|
||||
import { dbAuditSink } from "@wrnexus/compliance";
|
||||
authz.configure({ auditSink: dbAuditSink({ retentionDays: 365 }) });
|
||||
|
||||
const report = await compliance.exportAuditTrail({ from, to, format: "csv" });
|
||||
```
|
||||
|
||||
This is the package I'd actually build _first_ internally for the WRNexus SaaS itself, since any
|
||||
enterprise/government customer of WRNexus will ask WorkRoot for exactly this evidence during their
|
||||
own procurement review — dogfooding it validates the design before it ships to other developers.
|
||||
|
||||
**Effort:** ~2 weeks on top of the existing audit-sink groundwork, since most of the hard part
|
||||
(safe event formatting, sink interface) is already done.
|
||||
|
||||
---
|
||||
|
||||
## Phase 6 — v1.0: Monetization and ecosystem
|
||||
|
||||
### 6.1 `@wrnexus/billing`
|
||||
|
||||
**Problem:** no payments package, despite WRNexus itself being a billed SaaS product — this is the
|
||||
package where dogfooding value is highest.
|
||||
|
||||
**API:**
|
||||
|
||||
```ts
|
||||
export interface BillingProvider {
|
||||
createCheckoutSession(params): Promise<{ url: string }>;
|
||||
verifyWebhook(req: Request): Promise<BillingEvent>;
|
||||
getSubscription(customerId: string): Promise<Subscription>;
|
||||
}
|
||||
|
||||
// providers/stripe.ts, providers/razorpay.ts (India-relevant)
|
||||
```
|
||||
|
||||
Webhook verification reuses `@wrnexus/security`'s constant-time comparison helpers (same primitive
|
||||
already used in `packages/core/src/csrf.ts`'s `timingSafeEqual`) for signature checks. Usage metering
|
||||
hooks into `@wrnexus/observability`'s existing counters/gauges rather than a new metrics system.
|
||||
|
||||
**Effort:** ~5–6 weeks for Stripe + Razorpay, subscription lifecycle, and webhook handling with
|
||||
proper idempotency-key handling (a real source of billing bugs if skipped).
|
||||
|
||||
### 6.2 Public release strategy — open-core
|
||||
|
||||
**Problem:** `@wrnexus/*` is currently private, so nobody outside WorkRoot can `bun install` any of
|
||||
it. This is the actual ceiling on "developer power," not any single missing feature.
|
||||
|
||||
**Recommended split:**
|
||||
|
||||
| Tier | Packages | License |
|
||||
| ----------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------ |
|
||||
| Open (public npm) | `core`, `ssr`, `compiler`, `syntax`, `router`, `store`, `reactive`, `security`, `ui`, `cli`, `dev-server`, `dev-toolbar` | MIT or Apache-2.0 |
|
||||
| Paid/enterprise | `billing`, `compliance`, SAML in `auth`, `authz` advanced policy engine | Commercial license, distributed via the private registry you already run |
|
||||
|
||||
**Rollout steps:**
|
||||
|
||||
1. Audit the `open` tier packages for any WorkRoot-specific secrets/config baked in (`scripts/`
|
||||
already has `generate-sbom.mjs` and `check-public-api.mjs` — extend `check-public-api.mjs` to also
|
||||
flag internal-only references before a package is promoted to the public tier).
|
||||
2. Publish under the `@wrnexus` npm org with the existing `PUBLISHING.md` process, starting with
|
||||
`core` + `cli` (the minimum to `bunx @wrnexus/cli create my-app` publicly).
|
||||
3. Public GitHub repo for the open tier only (mirrored from the monorepo via the existing
|
||||
`stage:packages` script's publish pipeline, not a manual copy).
|
||||
4. Keep `docs/ROADMAP.md`-style public roadmap visible so early external adopters see what's coming.
|
||||
|
||||
**Effort:** ~4–6 weeks of packaging/licensing/CI work, separate from any new feature work above — this
|
||||
can run in parallel with Phase 5.
|
||||
|
||||
### 6.3 Lean into `@wrnexus/ai` + `@wrnexus/mcp`
|
||||
|
||||
**Problem:** these packages already exist and are ahead of most frameworks, but aren't positioned as
|
||||
a headline feature anywhere in the marketing (`wrnexusjs.dev` homepage doesn't mention AI/MCP at all
|
||||
per the live screenshot taken earlier).
|
||||
|
||||
**Recommendation:** ship an official MCP server (`packages/mcp/src/index.ts` already has a `stdio.ts`
|
||||
transport — check whether it currently exposes framework introspection, e.g. route listing, `.wrn`
|
||||
component schema, or `wrnexus doctor` output as MCP tools) so agentic coding tools (Claude Code,
|
||||
Cursor, etc.) can scaffold and modify WRNexusJS apps with structured tool calls instead of guessing at
|
||||
the `.wrn` syntax from grepped examples. This is a low-cost, high-differentiation move given how much
|
||||
of the ecosystem is moving toward agent-built apps.
|
||||
|
||||
**Effort:** ~2–3 weeks to wrap existing CLI commands (`inspect`, `doctor`, `routes`, `generate`) as
|
||||
MCP tools, since the underlying logic already exists in `packages/cli/src`.
|
||||
|
||||
---
|
||||
|
||||
## Cross-cutting requirements for every phase
|
||||
|
||||
- Every new/changed package needs a `SECURITY-SUPPORT-MATRIX.md` row (framework guarantee vs.
|
||||
app/operator responsibility) before release, matching the existing table format.
|
||||
- Every new package needs an ASVS evidence row in `SECURITY-ASVS-5.md` if it touches auth, secrets,
|
||||
network requests, or user input — same two-column (implementation evidence / verification evidence)
|
||||
format already used.
|
||||
- `bun run validate:0.X` release gate (`scripts/validate-0.8.mjs`-style) should get a new
|
||||
`validate-0.9.mjs` etc. per phase, following the existing per-minor-version validation script
|
||||
pattern rather than one growing script.
|
||||
- `CHANGELOG.md` entries per release, same format as the 0.8.3/0.8.0 entries already there.
|
||||
- Each phase should ship an example page/route in `examples/basic-app` (mirroring
|
||||
`auth-showcase`, `captcha-showcase`, `i18n-showcase`) — e.g. `examples/mail-showcase`,
|
||||
`examples/billing-showcase` — so the roadmap's own "executable examples" discipline
|
||||
(`ROADMAP-COMPLETION-REPORT.md`) continues.
|
||||
|
||||
---
|
||||
|
||||
## Suggested sequencing rationale
|
||||
|
||||
Phase 1 ships first because it's the only phase with zero new surface area — pure default fixes that
|
||||
improve every existing deployment (including `workroot.in` and `wrnexusjs.dev` themselves) without
|
||||
anyone changing their code. Phases 2–4 build developer-facing power in the order teams actually hit
|
||||
the wall (rate limiting under real traffic → needing email → needing flags → needing search once an
|
||||
app has enough data to search). Phase 5 is prioritized ahead of Phase 6 despite being harder, because
|
||||
it directly unblocks revenue-relevant deals in WorkRoot's actual pipeline (enterprise/government
|
||||
procurement). Phase 6's public release is last on purpose — it should launch once there's a stronger
|
||||
package lineup behind it, so the first public impression of `@wrnexus/*` is "batteries-included," not
|
||||
"promising but thin."
|
||||
@@ -0,0 +1,288 @@
|
||||
# Permissions system design (`@wrnexus/authz`)
|
||||
|
||||
Date: 2026-08-04
|
||||
Status: approved, not yet implemented
|
||||
Supersedes: nothing — extends the existing `@wrnexus/authz` package
|
||||
|
||||
## Problem
|
||||
|
||||
`@wrnexus/authz` today evaluates authorization but does not **describe** it. `defineRbac()`
|
||||
takes a literal object of roles, policies are anonymous closures, and nothing records which
|
||||
permissions exist. The consequences:
|
||||
|
||||
- No discoverability. Nothing can answer "what permissions does this system have?"
|
||||
- No runtime assignment. Changing who is an admin requires a redeploy.
|
||||
- No tenant awareness, despite `core/src/tenant.ts` already defining
|
||||
`TenantMembership { tenantId, userId, roles[] }` that `authz` never reads.
|
||||
- No audit trail.
|
||||
- Typos in permission strings fail silently as `false`.
|
||||
|
||||
The evaluation primitives are sound and stay: `AuthorizationDecision`, `DecisionPolicy`,
|
||||
`owner`, `anyDecision`, `allDecisions`, `filterAuthorized`, and the guard middleware.
|
||||
|
||||
## Approach
|
||||
|
||||
Separate **declaration** (what permissions, roles, policies and attributes exist — static,
|
||||
typed, in code) from **assignment** (who holds what — dynamic, in a store). The existing
|
||||
package becomes the evaluation layer beneath both.
|
||||
|
||||
Rejected alternatives:
|
||||
|
||||
- **Extend `defineRbac` in place.** Half the work, but leaves discoverability, codegen,
|
||||
cross-app catalog and audit with nowhere to live.
|
||||
- **Adapter for OpenFGA / Cedar / SpiceDB.** Better at relationship-heavy authorization,
|
||||
but puts a network dependency and a sidecar in the request path of a zero-dependency
|
||||
framework.
|
||||
|
||||
## Module layout
|
||||
|
||||
```
|
||||
@wrnexus/authz
|
||||
index.ts existing surface (unchanged exports)
|
||||
advanced.ts existing decision primitives (unchanged exports)
|
||||
registry.ts defineAuthz() — permissions, roles, policies, attributes
|
||||
catalog.ts discovery, merge, conflict detection; frozen at boot
|
||||
store.ts PermissionStore interface, memory adapter, cachedPermissionStore()
|
||||
db.ts dbPermissionStore(getDb()) — subpath export @wrnexus/authz/db
|
||||
engine.ts subject -> effective permissions -> Decision
|
||||
guards.ts route middleware, extended for resources
|
||||
audit.ts AuthzAuditSink
|
||||
view.ts can() exposed to .wrn `{#if}` expressions
|
||||
```
|
||||
|
||||
## Declaration
|
||||
|
||||
Declarations live in `app/authz/*.ts`, discovered the same way `app/schemas/*.ts` already is
|
||||
(`packages/router/src/index.ts` scans and populates `router.schemas`; this adds `router.authz`).
|
||||
|
||||
```ts
|
||||
// app/authz/blog.ts
|
||||
import { defineAuthz, owner } from "@wrnexus/authz";
|
||||
|
||||
export default defineAuthz({
|
||||
permissions: {
|
||||
"post:read": { title: "View posts", public: true },
|
||||
"post:write": { title: "Create and edit posts" },
|
||||
"post:delete": { title: "Delete posts", risk: "high" },
|
||||
},
|
||||
roles: {
|
||||
editor: ["post:*"],
|
||||
moderator: ["post:comment:*"],
|
||||
admin: ["role:editor", "role:moderator"],
|
||||
},
|
||||
policies: {
|
||||
ownsPost: owner("id", "authorId"),
|
||||
},
|
||||
attributes: {
|
||||
department: { description: "Subject's department, from the identity provider" },
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
A permission declared `public: true` is granted to anonymous subjects. Every other permission
|
||||
denies when there is no authenticated user.
|
||||
|
||||
## Catalog and cross-app scope
|
||||
|
||||
Declarations are static code, so sharing them across workspace apps needs no runtime
|
||||
distribution: the `defineAuthz` blocks live in the workspace's shared package
|
||||
(`packages/shared`, already scaffolded by `wrnexus workspace`) and every app imports them.
|
||||
They are identical by construction.
|
||||
|
||||
What is genuinely shared at runtime is **assignments**, and those live in the shared database
|
||||
behind `PermissionStore`.
|
||||
|
||||
`wrnexus authz list` is therefore introspection, not distribution: it walks every app in the
|
||||
workspace, merges catalogs, and reports the full permission/role/policy surface plus conflicts.
|
||||
|
||||
Merge rules:
|
||||
|
||||
- Two declarations of the same permission id with deep-equal metadata: no-op (lets shared
|
||||
packages re-declare freely).
|
||||
- Two declarations of the same permission id whose metadata is not deep-equal: boot error
|
||||
naming both source files.
|
||||
- The catalog is frozen after boot. Registration is not possible at request time.
|
||||
|
||||
## Assignment store
|
||||
|
||||
```ts
|
||||
interface AuthzScope {
|
||||
tenantId?: string;
|
||||
}
|
||||
|
||||
interface SubjectAssignments {
|
||||
roles: string[];
|
||||
grants: string[]; // explicit allows, bypassing roles
|
||||
denies: string[]; // explicit denies, win over everything
|
||||
}
|
||||
|
||||
interface PermissionStore {
|
||||
assignmentsFor(subjectId: string, scope?: AuthzScope): Promise<SubjectAssignments>;
|
||||
assignRole(subjectId: string, role: string, scope?: AuthzScope): Promise<void>;
|
||||
revokeRole(subjectId: string, role: string, scope?: AuthzScope): Promise<void>;
|
||||
grant(
|
||||
subjectId: string,
|
||||
permission: string,
|
||||
effect: "allow" | "deny",
|
||||
scope?: AuthzScope,
|
||||
): Promise<void>;
|
||||
revokeGrant(subjectId: string, permission: string, scope?: AuthzScope): Promise<void>;
|
||||
listSubjects(scope?: AuthzScope): Promise<string[]>;
|
||||
}
|
||||
```
|
||||
|
||||
`scope.tenantId` is how this meets the existing `TenantMembership`. An assignment with no
|
||||
scope is global; a scoped assignment applies only within that tenant. Both are unioned for a
|
||||
request whose `ctx.tenant` is set.
|
||||
|
||||
Adapters:
|
||||
|
||||
- `memoryPermissionStore()` — tests and single-process development.
|
||||
- `dbPermissionStore(getDb())` — default; tables below.
|
||||
- `cachedPermissionStore(inner, { ttlMs, max })` — decorator exposing
|
||||
`invalidate(subjectId, scope)`. Role changes must invalidate explicitly rather than wait
|
||||
out a TTL.
|
||||
|
||||
### Tables
|
||||
|
||||
```
|
||||
wrn_authz_assignment
|
||||
id, subject_id, scope, role, granted_by, created_at
|
||||
unique(subject_id, scope, role)
|
||||
|
||||
wrn_authz_grant
|
||||
id, subject_id, scope, permission, effect, granted_by, created_at
|
||||
unique(subject_id, scope, permission)
|
||||
```
|
||||
|
||||
`scope` stores the tenant id, or the empty string for global. Migrations are scaffolded by
|
||||
`wrnexus authz init`, following the existing `db/src/migrate.ts` conventions.
|
||||
|
||||
## Evaluation
|
||||
|
||||
### How `can()` reaches a request
|
||||
|
||||
`can` is **not** added to the `Context` interface. `@wrnexus/core` must not depend on
|
||||
`@wrnexus/authz` — the same constraint that keeps `getDb()` off `Context` rather than
|
||||
introducing a `core -> db` cycle. Instead:
|
||||
|
||||
```ts
|
||||
app.use(authzMiddleware({ store, catalog })); // stashes a resolver in ctx.locals
|
||||
const allowed = await can(ctx, "post:delete", post); // imported from @wrnexus/authz
|
||||
```
|
||||
|
||||
`authzMiddleware` puts the per-request resolver (with its memo table) into
|
||||
`ctx.locals._authz`; `can(ctx, ...)` reads it and throws a clear setup error if the
|
||||
middleware was not registered. Views get the bound form described under view integration.
|
||||
|
||||
### Per request
|
||||
|
||||
1. Subject is `ctx.user`; scope is `ctx.tenant`.
|
||||
2. `store.assignmentsFor(subjectId, scope)`.
|
||||
3. Registry expands roles into a permission set — wildcards at every depth
|
||||
(`post:*` and `post:comment:*` both grant `post:comment:delete`), `role:` inheritance,
|
||||
cycle-safe.
|
||||
4. `can(ctx, permission, resource?)` checks the set, then runs any policy bound to that
|
||||
permission with the resource.
|
||||
5. Result is an `AuthorizationDecision`; denials go to the audit sink.
|
||||
|
||||
Memoised per request. Precedence, highest first:
|
||||
|
||||
1. Explicit deny (store `denies`) — beats everything including `*`.
|
||||
2. Policy denial.
|
||||
3. Explicit grant or role-derived permission.
|
||||
4. Default deny.
|
||||
|
||||
## Failure behaviour
|
||||
|
||||
Every failure path denies.
|
||||
|
||||
| Condition | Behaviour |
|
||||
| ------------------------------ | ------------------------------------------------------ |
|
||||
| Permission not in the registry | Throws in development, denies and audits in production |
|
||||
| Store throws | Deny, audit, log. Never fail open. |
|
||||
| Policy throws | Treated as a denial, logged with the policy name |
|
||||
| No authenticated user | Deny, unless the permission is declared `public` |
|
||||
|
||||
## Audit
|
||||
|
||||
```ts
|
||||
interface AuthzAuditEvent {
|
||||
subjectId?: string;
|
||||
scope?: AuthzScope;
|
||||
permission: string;
|
||||
allowed: boolean;
|
||||
reason?: string;
|
||||
policy?: string;
|
||||
at: number;
|
||||
}
|
||||
interface AuthzAuditSink {
|
||||
record(event: AuthzAuditEvent): void | Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
Default sink is a no-op. Records denials only unless configured otherwise, to bound write
|
||||
volume on hot paths. Sink errors are logged and swallowed — auditing must never break a
|
||||
request.
|
||||
|
||||
## Tooling
|
||||
|
||||
- `wrnexus authz list` — merged catalog across the workspace, with conflicts.
|
||||
- `wrnexus authz generate` — emits `app/authz/permissions.gen.ts` exporting
|
||||
`type Permission = "post:read" | "post:write" | ...`. `can()`, `guardPermission()`,
|
||||
and `decideFor()` all take a bare `string` and nothing consumes this union
|
||||
automatically — it exists to type your own helpers/constants against the
|
||||
registered catalog. Runs automatically in `build.ts`, mirroring `regenerateQueries`.
|
||||
- `wrnexus authz init` — scaffolds the migration and a seed helper for default roles.
|
||||
- Admin UI: `.wrn` components for listing subjects and assigning roles, shipped in
|
||||
`@wrnexus/ui` behind the existing eject mechanism.
|
||||
|
||||
## Inter-app seam
|
||||
|
||||
Reserved for the inter-app communication system, specified but not built here:
|
||||
|
||||
```ts
|
||||
exportSubjectContext(ctx): string // signed, compact: subject id, scope, roles
|
||||
importSubjectContext(token): Subject // verified on the receiving app
|
||||
```
|
||||
|
||||
An app calling another on a user's behalf propagates identity and roles rather than
|
||||
re-querying the store. The signing key and transport are the comms system's concern.
|
||||
|
||||
## Security fix folded into this work
|
||||
|
||||
`authorizeDecision` currently returns the internal `reason` and `policy` name in the 403 body,
|
||||
disclosing policy structure to unauthenticated callers. This becomes opt-in via
|
||||
`authorizeDecision(evaluate, { exposeReason: true })`, defaulting to a bare
|
||||
`{ ok: false, error: "Forbidden" }`.
|
||||
|
||||
## Testing
|
||||
|
||||
- **Store conformance suite** — one shared set of tests run against both the memory and DB
|
||||
adapters so they cannot drift.
|
||||
- **Unit** — wildcard expansion at depth, deny precedence, role-cycle termination, catalog
|
||||
merge conflicts, public-permission handling.
|
||||
- **Integration** — guards return 403 for API and redirect for pages; `{#if can(...)}` omits
|
||||
markup server-side rather than hiding it with CSS.
|
||||
- **Security regression** — unregistered permission denies in production; 403 body does not
|
||||
leak policy names unless opted in; store failure denies rather than allows.
|
||||
|
||||
## Build order
|
||||
|
||||
1. `registry.ts`, `catalog.ts`, `store.ts` (memory), `engine.ts`, extended `guards.ts`.
|
||||
2. Resource-level policies wired to `filterAuthorized`; `audit.ts`.
|
||||
3. `db.ts` adapter, migrations, `wrnexus authz init`, codegen, `wrnexus authz list`.
|
||||
4. `.wrn` view integration (`can()` inside `{#if}`).
|
||||
5. Admin UI components.
|
||||
|
||||
Phase 4 is the only one whose shape is uncertain. The compiler supports `{#if}` at page level
|
||||
and nested, but exposing `can()` into that scope touches codegen
|
||||
(`packages/compiler/src/codegen.ts`). If it proves invasive, phases 1–3 ship on their own and
|
||||
view integration returns as its own design.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Relationship-based authorization ("can edit because they're in the team that owns the doc").
|
||||
Role and policy checks cover the intended cases; revisit if hierarchical resources appear.
|
||||
- Permission delegation and time-bounded grants.
|
||||
- Cross-workspace federation.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,80 @@
|
||||
# Authz follow-ups
|
||||
|
||||
Findings from the reviews on branch `security/0.8.4-audit-and-authz-design` that were
|
||||
adjudicated as non-blocking. None is an authorization bypass. Recorded here because the
|
||||
review workspace is scratch and git history does not carry the reasoning.
|
||||
|
||||
## Worth a ticket
|
||||
|
||||
**`guardPermission` turns a 403 into a 500 when the middleware is missing.**
|
||||
`packages/authz/src/middleware.ts` — the `getResource` catch now calls `readAuthz(ctx)` to
|
||||
reach the audit sink, and `readAuthz` throws `WRN-AUTHZ-SETUP` when `authzMiddleware` was
|
||||
never registered. That configuration is already broken, and a throw denies rather than
|
||||
grants, but it converts a clean denial into a framework 500. Read the sink defensively
|
||||
instead of destructuring `readAuthz`.
|
||||
|
||||
**`deniedBy()` fails open in isolation.** `packages/authz/src/engine.ts` returns `false` for
|
||||
a non-array argument. Safe for the one in-repo caller, which pre-validates, but `deniedBy`
|
||||
is on the public surface and an external caller passing a string gets a silent `false`.
|
||||
Throwing a `TypeError` would make the guard self-contained.
|
||||
|
||||
**`permissionsFor()` still reads `denies` unguarded.** Same shape the engine's `decide()`
|
||||
was hardened against: a store omitting `denies` throws a raw `TypeError`. Not fail-open,
|
||||
and the doc comment already says never to gate on this result, but it is inconsistent with
|
||||
the fix applied next to it.
|
||||
|
||||
## Behaviour to carry into release notes
|
||||
|
||||
**`authorizeDecision`'s 403 body no longer contains `reason` or `policy`.** Approved
|
||||
breaking change — policy names describe internal authorization structure. Opt back in with
|
||||
`{ exposeReason: true }`. No in-repo caller relied on the old shape.
|
||||
|
||||
**RBAC namespace wildcards now match at every depth.** `post:comment:*` previously did not
|
||||
grant `post:comment:delete`. The fix is correct, but it _widens_ access for any app that
|
||||
relied on the old first-segment-only behaviour.
|
||||
|
||||
**`Router` gained a required `authz` field.** Compile-time break for anything constructing a
|
||||
`Router` object literal — custom deployment adapters, test fixtures. Consider making it
|
||||
optional.
|
||||
|
||||
**`subject.id` must be a non-empty string.** Integer primary keys deny every request and log
|
||||
to stderr. Documented in the authz README; worth a release-note line too.
|
||||
|
||||
## Known gaps, deliberately accepted
|
||||
|
||||
**`listSubjects` and `assignmentsFor` disagree about "in this tenant".** Reads union global
|
||||
and tenant scope; `listSubjects` matches the scope key exactly. An admin UI built on
|
||||
`listSubjects` omits globally-granted superusers. Both adapters agree with each other, so
|
||||
this is a model choice, not drift — but it is on the public `PermissionStore` interface.
|
||||
|
||||
**DNS pinning has no real-TLS test.** Every test in `ssrf-regression.test.ts` stubs
|
||||
`globalThis.fetch`, so `tls: { serverName }` is only asserted as an object property. If a
|
||||
runtime ever validates the certificate against the dialed IP rather than `serverName`,
|
||||
every HTTPS `safeFetch` breaks by default and no test would notice. One live-network smoke
|
||||
test closes this.
|
||||
|
||||
**`safeFetch` re-attaches credentials on a→b→a.** Credentials return to the intended origin,
|
||||
but the path is attacker-chosen. Browsers do not re-add after leaving the origin. Track a
|
||||
`hasLeftOrigin` latch.
|
||||
|
||||
**Gateway basic-auth: the username compare short-circuits.** `packages/dev-server/src/gateway.ts`
|
||||
— `&&` skips the password compare when the username misses, giving a measured 2.1x timing
|
||||
signal (39.8ms vs 83.6ms over 200k iterations). Username enumeration. The password compare
|
||||
itself is constant-time. Evaluate both, then combine.
|
||||
|
||||
**`safeFetch` buffers the whole body before checking `maxResponseBytes`.** Pre-existing, not
|
||||
introduced by this branch: with no `content-length`, `await response.arrayBuffer()` buffers
|
||||
everything first. Verified 8MB buffered against a 1KB limit.
|
||||
|
||||
**`packages/router` does not declare `@wrnexus/ui`.** Pre-existing. Passes every in-repo gate
|
||||
because bare `@wrnexus/*` specifiers resolve through the root tsconfig `paths` map, not
|
||||
`node_modules` — the same class of defect that would have shipped a broken published CLI.
|
||||
Worth auditing every package's declared-vs-imported dependencies once.
|
||||
|
||||
**The generated `Permission` union has no consumer.** `can`, `guardPermission` and
|
||||
`decideFor` take bare `string`. The docstrings and design doc were corrected to stop
|
||||
promising compile-time checking; wiring a type parameter is a real option if wanted.
|
||||
|
||||
**Catalog conflict origin tracking drifts.** A later re-declaration overwrites the recorded
|
||||
source file, so a conflict message can name the wrong original. The conflict is still
|
||||
detected; only the diagnostic is affected.
|
||||
@@ -0,0 +1,223 @@
|
||||
# Inter-app communication design (`@wrnexus/rpc`)
|
||||
|
||||
Date: 2026-08-05
|
||||
Status: phase 1 implemented (2026-08-05); phases 2–4 remain deferred
|
||||
Depends on: the permissions system (`@wrnexus/authz`), merged 2026-08-05
|
||||
|
||||
## Problem
|
||||
|
||||
Workspace apps are separate OS processes behind a Host-routed gateway — full isolation, own
|
||||
database registry, own memory. They can currently reach each other in exactly two ways:
|
||||
|
||||
- `@wrnexus/helpers` — `appOrigin(name)` / `appUrl(name, path)`, string builders over the
|
||||
`WRNEXUS_WORKSPACE_ORIGINS` environment map.
|
||||
- `@wrnexus/pubsub` — fire-and-forget events, cross-process via the Redis, NATS or Kafka
|
||||
drivers.
|
||||
|
||||
There is no request/response layer. An app that needs an answer from another hand-writes
|
||||
`fetch(appUrl("billing", "/api/invoice"))`: no shared contract, no type safety, no timeout,
|
||||
no retry policy, and no way for the callee to learn who the end user is. The permissions
|
||||
system reserved a seam for that last part — `exportSubjectContext` / `importSubjectContext` —
|
||||
and left it unbuilt.
|
||||
|
||||
## Scope
|
||||
|
||||
Of the four cross-app call shapes, three already have implementations:
|
||||
|
||||
| Shape | Status |
|
||||
| ---------------------- | ----------------------------------------------------------------- |
|
||||
| Request/response | Missing. This is the gap. |
|
||||
| Fire-and-forget events | `@wrnexus/pubsub` |
|
||||
| Scheduled / deferred | `@wrnexus/queue` — `defineJob`, durable store, workflows, cron |
|
||||
| Streaming | `@wrnexus/realtime` covers browser↔server; app↔app is not covered |
|
||||
|
||||
This design builds request/response and app-to-app streaming. It does **not** wrap `pubsub`
|
||||
or `queue` in a new facade: they work, and an abstraction over working code leaks and needs
|
||||
keeping in sync. They instead gain identity propagation, and all four shapes are documented
|
||||
in one place so a developer knows what to reach for.
|
||||
|
||||
## Approach
|
||||
|
||||
Service contracts live in the workspace's shared package and are imported by both sides. The
|
||||
callee `implement`s a contract; the caller gets a typed proxy client. Types flow through a
|
||||
normal TypeScript import — no generator, no generated file to go stale, no build step. This
|
||||
matches the idiom the framework already uses: `defineAuthz`, `defineJob`, `defineRbac`,
|
||||
`defineWorkflow`.
|
||||
|
||||
Rejected alternatives:
|
||||
|
||||
- **Callee exports types, caller imports them.** No new abstraction, but the callee still
|
||||
hand-writes and hand-mounts every route and nothing keeps the route path and the type in
|
||||
agreement.
|
||||
- **Generate a client from the callee's `app/api` routes.** Codegen over hand-written routes
|
||||
is brittle, and the permissions work has already demonstrated that a generated file with no
|
||||
consumer becomes dead weight.
|
||||
|
||||
## Module layout
|
||||
|
||||
```
|
||||
@wrnexus/rpc
|
||||
contract.ts defineService(), the procedure builder
|
||||
server.ts implement(); routes discovered from app/services/*.ts
|
||||
client.ts serviceClient(contract, { as: ctx }) -> typed proxy
|
||||
transport.ts Transport interface, httpTransport(), inProcessTransport()
|
||||
identity.ts exportSubjectContext / importSubjectContext
|
||||
errors.ts ServiceError and retryability classification
|
||||
```
|
||||
|
||||
## Contracts
|
||||
|
||||
```ts
|
||||
// packages/shared/src/services/billing.ts — imported by both apps
|
||||
import { defineService, procedure } from "@wrnexus/rpc";
|
||||
import { v } from "@wrnexus/validation";
|
||||
|
||||
export const billing = defineService({
|
||||
name: "billing",
|
||||
procedures: {
|
||||
createInvoice: procedure
|
||||
.input(v.object({ userId: v.string(), amountCents: v.number().integer() }))
|
||||
.output<{ invoiceId: string }>()
|
||||
.permission("invoice:create"),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Input validation reuses `@wrnexus/validation`'s `v`, which already returns
|
||||
`{ ok, value, errors }`. No new validator is introduced.
|
||||
|
||||
`.permission(id)` is **enforced**, not merely declarative: `implement` checks it against the
|
||||
callee's own catalog before invoking the handler, and a denial never reaches the handler. A
|
||||
handler may additionally perform finer-grained, resource-level checks — `.permission()` is
|
||||
the coarse gate, not the whole authorization story. Declaring it on the contract also means
|
||||
the caller can see, at the type level, what the call requires.
|
||||
|
||||
## Server side
|
||||
|
||||
```ts
|
||||
// app/services/billing.ts
|
||||
export default implement(billing, {
|
||||
async createInvoice({ userId, amountCents }, ctx) {
|
||||
/* ... */
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
`buildRouter` discovers `app/services/*.ts` exactly as it discovers `app/authz` and
|
||||
`app/schemas`, skipping `*.gen.ts`. Procedures mount at
|
||||
`/__wrnexus/rpc/<service>/<procedure>`.
|
||||
|
||||
## Client side
|
||||
|
||||
```ts
|
||||
const client = serviceClient(billing, { as: ctx });
|
||||
const { invoiceId } = await client.createInvoice({ userId, amountCents });
|
||||
```
|
||||
|
||||
`{ as: ctx }` carries identity: it calls `exportSubjectContext(ctx)` to mint a short-lived
|
||||
signed token.
|
||||
|
||||
## Identity
|
||||
|
||||
The token carries **`sub` and `tenant` only — never roles.** Every app shares the
|
||||
`PermissionStore`, so the callee resolves roles itself. A stale or forged roles claim is
|
||||
therefore impossible by construction, the token stays small, and there is no path to
|
||||
injecting privileges through a claim.
|
||||
|
||||
Built on `@wrnexus/jwt`: `alg` pinned to HS256, signature verified before the payload is
|
||||
parsed, `exp`/`nbf`/`aud`/`iss` enforced.
|
||||
|
||||
| Claim | Purpose |
|
||||
| -------- | ------------------------------------------------------------------------------ |
|
||||
| `iss` | calling app name |
|
||||
| `aud` | target app name, so B cannot replay A's token to C |
|
||||
| `sub` | subject id — a non-empty string, matching the permissions system's requirement |
|
||||
| `tenant` | tenant id, absent for global |
|
||||
| `exp` | 60 seconds |
|
||||
|
||||
**The token authenticates; it never authorizes.** The callee runs its own permission checks
|
||||
against its own catalog. An app cannot grant access the callee's catalog does not allow.
|
||||
|
||||
### Trust boundary
|
||||
|
||||
With a workspace-wide signing secret, any app can mint a token claiming to be any user. All
|
||||
workspace apps are therefore one trust boundary: compromising the lowest-privilege app
|
||||
compromises user identity across all of them. This is the standard trade-off and is
|
||||
acceptable for apps in one repository under one operator, but it must be documented rather
|
||||
than discovered. Per-app signing keys are the hardening path if apps ever run at differing
|
||||
trust levels.
|
||||
|
||||
The RPC secret MUST NOT be the session secret. Reusing it would make a leaked RPC token a
|
||||
session-forgery primitive.
|
||||
|
||||
### The RPC path is a new attack surface
|
||||
|
||||
`/__wrnexus/rpc/*` must be unreachable from the public internet — blocked at the gateway
|
||||
**and** verified at the app, because either alone is a single point of failure. If that
|
||||
endpoint is reachable with a forged token, every permission check in the workspace is
|
||||
bypassable. This is the single most important property in the design.
|
||||
|
||||
## Transport
|
||||
|
||||
```ts
|
||||
interface Transport {
|
||||
call(
|
||||
target: { app: string; service: string; procedure: string },
|
||||
payload: unknown,
|
||||
options: { signal: AbortSignal; identity?: string },
|
||||
): Promise<ServiceResult>;
|
||||
}
|
||||
```
|
||||
|
||||
`httpTransport()` is the default and the only production transport in v1. A gRPC transport,
|
||||
if ever needed, implements this interface and nothing else changes.
|
||||
|
||||
`inProcessTransport()` implements the same interface by calling the implementation directly
|
||||
with no network. It exists for tests.
|
||||
|
||||
## Errors and retries
|
||||
|
||||
Errors classify into three kinds, because the classification decides whether a retry is safe:
|
||||
|
||||
| Kind | Retryable |
|
||||
| --------------------------------------------------------------------- | --------- |
|
||||
| Transport failure — connection refused, timeout, 5xx | Yes |
|
||||
| Application error — the procedure threw or returned a failure | No |
|
||||
| Rejected — validation failed, authorization denied, unknown procedure | No |
|
||||
|
||||
Only procedures explicitly marked `.idempotent()` are retried at all. Retrying a
|
||||
non-idempotent `createInvoice` because a response was slow is how a customer gets billed
|
||||
twice: the default is no retry, and opting in is a deliberate act recorded in the contract.
|
||||
|
||||
`ServiceError` crossing an app boundary is **opaque by default** — code and message only, no
|
||||
stack, no internal detail. An error that explains the callee's internals is an information
|
||||
leak, the same defect class as `authorizeDecision`'s original 403 body.
|
||||
|
||||
## Testing
|
||||
|
||||
- **In-process transport.** Contract, identity round-trip and error classification are all
|
||||
testable without standing up two servers.
|
||||
- **Contract conformance suite.** An implementation must satisfy its contract at runtime, not
|
||||
only at compile time.
|
||||
- **Security regressions**, each proven by reverting the guard and confirming the test fails:
|
||||
the RPC path rejects an external request; a forged signature is rejected; a token with the
|
||||
wrong `aud` is rejected; an expired token is rejected; a missing token is rejected.
|
||||
|
||||
## Build order
|
||||
|
||||
1. Contract, server discovery, typed client, HTTP transport, identity — the usable core.
|
||||
2. Failure handling: timeouts, idempotent retry, circuit breaking.
|
||||
3. App-to-app streaming.
|
||||
4. Identity propagation for `pubsub` and `queue`, and the unified "how apps talk" guide.
|
||||
|
||||
Phase 1 is what makes the system usable; everything after is additive.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- gRPC and Protocol Buffers. The `Transport` seam exists so this stays additive. gRPC's value
|
||||
is cross-language interop and streaming; workspace apps are all TypeScript in one
|
||||
repository sharing types through workspace packages, so a typed HTTP transport delivers
|
||||
end-to-end type safety without a code generator.
|
||||
- Wrapping `pubsub` or `queue` behind a unified facade.
|
||||
- Service discovery beyond the existing `WRNEXUS_WORKSPACE_ORIGINS` map.
|
||||
- Cross-workspace federation.
|
||||
File diff suppressed because it is too large
Load Diff
+100
-2
@@ -457,11 +457,31 @@
|
||||
},
|
||||
"@wrnexus/authz": {
|
||||
".": [
|
||||
"AUTHZ_LOCALS_KEY",
|
||||
"AttributeMeta",
|
||||
"AuthorizationDecision",
|
||||
"AuthorizeDecisionOptions",
|
||||
"AuthzAuditEvent",
|
||||
"AuthzAuditSink",
|
||||
"AuthzCatalog",
|
||||
"AuthzModule",
|
||||
"AuthzResolver",
|
||||
"AuthzResolverOptions",
|
||||
"AuthzScope",
|
||||
"CacheOptions",
|
||||
"CachedPermissionStore",
|
||||
"CatalogSource",
|
||||
"DecideInput",
|
||||
"DecisionPolicy",
|
||||
"GrantEffect",
|
||||
"GuardOptions",
|
||||
"MemoryAuditSink",
|
||||
"PermissionMeta",
|
||||
"PermissionStore",
|
||||
"Policy",
|
||||
"Rbac",
|
||||
"Subject",
|
||||
"SubjectAssignments",
|
||||
"all",
|
||||
"allDecisions",
|
||||
"allow",
|
||||
@@ -470,14 +490,41 @@
|
||||
"attr",
|
||||
"authorize",
|
||||
"authorizeDecision",
|
||||
"authzMiddleware",
|
||||
"cachedPermissionStore",
|
||||
"can",
|
||||
"consoleAuditSink",
|
||||
"createAuthzResolver",
|
||||
"decideFor",
|
||||
"decision",
|
||||
"defineAuthz",
|
||||
"defineRbac",
|
||||
"deniedBy",
|
||||
"deny",
|
||||
"emptyCatalog",
|
||||
"expandRoles",
|
||||
"filterAuthorized",
|
||||
"filterCan",
|
||||
"generatePermissionTypes",
|
||||
"getAuthzCatalog",
|
||||
"guardPermission",
|
||||
"hasAuthzCatalog",
|
||||
"hasRole",
|
||||
"memoryAuditSink",
|
||||
"memoryPermissionStore",
|
||||
"mergeCatalogs",
|
||||
"owner",
|
||||
"permissionMatches",
|
||||
"requirePermission",
|
||||
"requireRole"
|
||||
"requireRole",
|
||||
"safeRecord",
|
||||
"scopeKey",
|
||||
"setAuthzCatalog"
|
||||
],
|
||||
"./db": [
|
||||
"authzMigrationSql",
|
||||
"dbPermissionStore",
|
||||
"ensureAuthzTables"
|
||||
]
|
||||
},
|
||||
"@wrnexus/benchmark": {
|
||||
@@ -1376,6 +1423,7 @@
|
||||
"@wrnexus/dev-server": {
|
||||
".": [
|
||||
"AssetServer",
|
||||
"AuthzManifestEntry",
|
||||
"FetchHandler",
|
||||
"GatewayApp",
|
||||
"GatewayAuth",
|
||||
@@ -1388,6 +1436,7 @@
|
||||
"ServeOptions",
|
||||
"WrnCompileMetrics",
|
||||
"WsData",
|
||||
"applyAuthzManifestEarly",
|
||||
"createHandlers",
|
||||
"createProductionHandlers",
|
||||
"createProductionServer",
|
||||
@@ -2323,6 +2372,54 @@
|
||||
"sortRoutes"
|
||||
]
|
||||
},
|
||||
"@wrnexus/rpc": {
|
||||
".": [
|
||||
"AnyProcedures",
|
||||
"CallOptions",
|
||||
"ExportOptions",
|
||||
"HandlerContext",
|
||||
"HttpTransportOptions",
|
||||
"ImplementOptions",
|
||||
"ImportOptions",
|
||||
"InProcessHandler",
|
||||
"InferInput",
|
||||
"InferProcedureInput",
|
||||
"InferProcedureOutput",
|
||||
"InputSchema",
|
||||
"ProcedureBuilder",
|
||||
"ProcedureDef",
|
||||
"RPC_ERROR_CODES",
|
||||
"RPC_IDENTITY_HEADER",
|
||||
"RPC_INTERNAL_HEADER",
|
||||
"RPC_PATH_PREFIX",
|
||||
"RpcErrorCode",
|
||||
"RpcTarget",
|
||||
"ServiceClient",
|
||||
"ServiceClientOptions",
|
||||
"ServiceContract",
|
||||
"ServiceError",
|
||||
"ServiceHandlers",
|
||||
"ServiceImplementation",
|
||||
"ServiceResult",
|
||||
"SubjectContext",
|
||||
"ToResultOptions",
|
||||
"Transport",
|
||||
"defineService",
|
||||
"exportSubjectContext",
|
||||
"failure",
|
||||
"httpTransport",
|
||||
"implement",
|
||||
"importSubjectContext",
|
||||
"inProcessTransport",
|
||||
"isRetryableStatus",
|
||||
"procedure",
|
||||
"resolveAppOrigin",
|
||||
"rpcPath",
|
||||
"rpcSecret",
|
||||
"serviceClient",
|
||||
"success"
|
||||
]
|
||||
},
|
||||
"@wrnexus/security": {
|
||||
".": [
|
||||
"RequestHardeningOptions",
|
||||
@@ -2761,7 +2858,8 @@
|
||||
]
|
||||
},
|
||||
"@wrnexus/ui": {
|
||||
".": [
|
||||
".": [],
|
||||
"./registry": [
|
||||
"UiComponentMetadata",
|
||||
"UiComponentReference",
|
||||
"auditUiComponents",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,184 @@
|
||||
# Navigation component group — design
|
||||
|
||||
Date: 2026-08-07
|
||||
Status: approved, not yet implemented
|
||||
|
||||
## Problem
|
||||
|
||||
The navigation group has nine components. Five of them — Nav, MegaMenu,
|
||||
Scrollspy, Pagination, Stepper — are byte-identical scaffold stubs that differ
|
||||
only in one CSS class name. Each renders `{#each items}<a href>{label}</a>{/each}`
|
||||
and nothing else. MegaMenu has no panel, Scrollspy never observes scroll,
|
||||
Stepper has no steps or progress, Pagination has no pages.
|
||||
|
||||
Of the remaining four, Breadcrumb is genuinely built (323 lines, own style
|
||||
block). Navbar and Sidebar render but have no keyboard handling and keep their
|
||||
styles in `ui.css`. Tabs works but is off-pattern in three ways: it is the only
|
||||
component in the library using Tailwind utility classes rather than `wire-*` +
|
||||
a local style block, it has no `outputs` block and fires raw `CustomEvent`s via
|
||||
`dispatchEvent`, and it sets a roving `tabindex` with no `@keydown` handler at
|
||||
all — which is worse than having no keyboard support, because the roving
|
||||
tabindex makes every inactive tab unreachable by Tab while arrows do nothing.
|
||||
|
||||
## Scope
|
||||
|
||||
All nine components, delivered in three phases.
|
||||
|
||||
## Decisions
|
||||
|
||||
### Styling
|
||||
|
||||
Every navigation component gets a local `style {}` block using `wire-*`
|
||||
classes. This matches DataTable, Toaster, Modal, Drawer, ContextMenu and
|
||||
Breadcrumb — the 22 of 108 components that carry local styles are exactly the
|
||||
recently built ones. Navbar and Sidebar styles move out of `ui.css` as part of
|
||||
this work.
|
||||
|
||||
### Dropdowns: Nav vs MegaMenu
|
||||
|
||||
Both get dropdowns, of deliberately different kinds.
|
||||
|
||||
- **Nav** gets multi-level cascading submenus. It is the link bar, so nested
|
||||
submenus are its job.
|
||||
- **MegaMenu** gets a single-level rich panel: columns of grouped links with
|
||||
headings, descriptions and icons. This is deliberate, not a shortcut. A mega
|
||||
menu exists to show breadth flat so everything is one click away; nesting
|
||||
inside the panel buries content behind hover-within-hover and is close to
|
||||
unusable by keyboard and touch.
|
||||
|
||||
Neither reuses the existing Dropdown component: Dropdown is click-triggered and
|
||||
has no nesting support.
|
||||
|
||||
**Depth limit.** There is no recursive-component precedent in this library, so
|
||||
multi-level means fixed depth via nested `{#each}` loops — the pattern Navbar
|
||||
(4 loops) and Sidebar (2 loops) already use. Depth is **3 levels**. Unlimited
|
||||
nesting would require proving out component self-reference, which is out of
|
||||
scope here.
|
||||
|
||||
### Roving focus lives in the runtime
|
||||
|
||||
Arrow-key roving focus is identical logic for Tabs, Nav, MegaMenu, Sidebar and
|
||||
Stepper. It goes in the reactive runtime as a declarative attribute rather than
|
||||
five near-identical client functions:
|
||||
|
||||
- `data-wrn-roving="horizontal|vertical|both"` on the container
|
||||
- `[data-wrn-roving-item]` on each focusable child
|
||||
|
||||
The runtime owns arrow keys, Home/End, wrap-around, skip-disabled, and
|
||||
maintenance of the roving `tabindex`. Components declare intent only.
|
||||
|
||||
This follows the modal-dialog focus work already in the runtime, and for the
|
||||
same reason: a client function cannot hold focus state across callbacks,
|
||||
because state written after the function returns is dropped.
|
||||
|
||||
### Scrollspy needs runtime support too
|
||||
|
||||
`data-wrn-scrollspy` backed by `IntersectionObserver`. It cannot be a client
|
||||
function — the observer callback fires long after the function returns, and
|
||||
that state write would be lost.
|
||||
|
||||
### Runtime budget
|
||||
|
||||
Roving focus is roughly 3–4k, scrollspy roughly 1.5k. The runtime is at 167k
|
||||
against the 175k ceiling raised on 2026-08-07. This fits, but leaves little
|
||||
room. The group after this one forces the decision about splitting the runtime
|
||||
into loadable chunks so pages pay only for behaviour they use.
|
||||
|
||||
### Tabs URL mode
|
||||
|
||||
`mode="client" | "url"`. URL mode uses a query parameter (`?tab=value`) driven
|
||||
by `history.pushState`, so content swaps with no page load and the back button
|
||||
works.
|
||||
|
||||
Query parameter rather than hash: it is shareable, survives reload, and does
|
||||
not collide with in-page anchors or with Scrollspy, which wants the hash.
|
||||
|
||||
Tabs also gets a panel transition on change.
|
||||
|
||||
### Sidebar composes Drawer
|
||||
|
||||
Sidebar does not reimplement off-canvas behaviour. Desktop renders a static
|
||||
rail; mobile renders inside Drawer. This inherits Drawer's focus trap and
|
||||
scroll lock rather than duplicating them.
|
||||
|
||||
### Cross-cutting requirements
|
||||
|
||||
- Icons on every menu item, tab, and step
|
||||
- Dropdown arrows that animate on open
|
||||
- Responsive behaviour per component: Nav collapses to a toggle, MegaMenu
|
||||
stacks its panel, Tabs becomes a scrollable strip, Stepper goes vertical,
|
||||
Pagination goes compact
|
||||
- Full ARIA Authoring Practices patterns for each role, including correct
|
||||
`aria-current` / `aria-selected` / `aria-expanded`
|
||||
|
||||
## Components
|
||||
|
||||
| Component | Work |
|
||||
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Nav | Build. Flat or multi-level (3 deep), horizontal/vertical, `items[{label,href,value,icon,badge,disabled,items}]`, `aria-current="page"`, roving focus, animated arrows, collapses on mobile. `outputs: select` |
|
||||
| Tabs | Rewrite off Tailwind onto `wire-*` + local styles. Real `outputs {change, select}` replacing raw `dispatchEvent`. Roving + Home/End. `mode="client"\|"url"`. Panel transition. Fix the `<slot>` that renders regardless of active tab |
|
||||
| Pagination | Build standalone. `page`/`pageSize`/`total`, compact + numbered styles, windowed page numbers. `outputs: change, previous, next` |
|
||||
| Stepper | Build. `<ol>`/`<li>`, status derived from `active` index, horizontal/vertical, optionally clickable, `aria-current="step"`. Indexed named slots (`data-slot="step-0"`, `step-1`, …) for custom per-step content, falling back to built-in rendering |
|
||||
| MegaMenu | Build. Trigger plus a single-level panel of link columns. Panel marked `data-wrn-anchored` so the existing clamp handles viewport containment. Hover and focus open, Escape closes, click-outside closes |
|
||||
| Scrollspy | Build. Observes section ids, moves `aria-current` to the matching link |
|
||||
| Navbar | Audit. Styles move to a local block, roving focus on the link group, keyboard for the mobile toggle |
|
||||
| Sidebar | Audit and extend. Local styles, composes Drawer for mobile, single items / labelled groups / multi-level (3 deep), vertical roving |
|
||||
| Breadcrumb | Audit only — already the strongest of the nine |
|
||||
|
||||
Component count stays at 108; all nine files already exist.
|
||||
|
||||
## Data flow
|
||||
|
||||
Props in, outputs out, no global state. Every component takes its data as props
|
||||
and reports interaction through declared `outputs`.
|
||||
|
||||
## Error handling
|
||||
|
||||
`items` arrives as an HTML attribute and is frequently a JSON string, so:
|
||||
|
||||
- A non-array `items` renders empty rather than throwing
|
||||
- Out-of-range `active` / `page` clamps to bounds
|
||||
|
||||
## Testing
|
||||
|
||||
- Per-component entries in `packages/ui/test/ui.test.ts` covering names,
|
||||
declared outputs and props
|
||||
- Showcase profiles for each so every component gets live demos
|
||||
- Regenerate component reference, catalog, showcase, and the UI visual contract
|
||||
- Each phase ends with `bun run check:production` green
|
||||
|
||||
## Phases
|
||||
|
||||
Each phase ends green, committed and pushed.
|
||||
|
||||
1. **Runtime and primitives** — roving-focus runtime, Nav, Pagination, Stepper
|
||||
2. **Composed** — Tabs (rewrite, URL sync, animation), Sidebar (on Drawer),
|
||||
MegaMenu
|
||||
3. **Audit and polish** — Navbar, Breadcrumb, Scrollspy, responsive pass,
|
||||
showcase generation
|
||||
|
||||
## Risks
|
||||
|
||||
- **Scrollspy and MegaMenu** are the only two needing new runtime behaviour.
|
||||
MegaMenu leans on the anchored clamp, which could not be verified
|
||||
interactively on 2026-08-07 because the browser pane was degraded
|
||||
(screenshots timing out, `scrollIntoView` inert). Verify the pane works
|
||||
before starting phase 2.
|
||||
- **Tabs is a breaking change.** Replacing raw `CustomEvent`s with declared
|
||||
outputs changes its public contract; anything listening for the old events
|
||||
breaks. 0.8.5 shipped on 2026-08-07, so this needs a migration entry in
|
||||
`packages/cli/src/update.ts`.
|
||||
|
||||
## Codebase constraints to respect
|
||||
|
||||
Hazards this codebase has already hit:
|
||||
|
||||
- No apostrophes in `.wrn` comments — the brace scanner breaks on them
|
||||
- `/* */` only inside style blocks; `//` is not a CSS comment and silently eats
|
||||
the following rule
|
||||
- No deferred state writes in client functions; state written after the
|
||||
function returns is dropped
|
||||
- Never call a peer function after an application callback — the wrapper
|
||||
flushes the entry-time snapshot
|
||||
- No boolean attributes bound to loop variables
|
||||
- Package components need explicit imports
|
||||
@@ -13,7 +13,7 @@
|
||||
"packages/ui/components/BackToTop.wrn": "b12c56ec8b7aabad68cbdc47f4b3237e2c21a61398f0aa51f9add6229b7357cd",
|
||||
"packages/ui/components/Badge.wrn": "6b55b4d85100d605cf168857aec1dc57da62c1f6a0a5cd6dd877023f24b189cc",
|
||||
"packages/ui/components/Blockquote.wrn": "6016dd4450de7cbf5c3cb413599baa2e502321e8eedef54439a0df6d0aae8bcb",
|
||||
"packages/ui/components/Breadcrumb.wrn": "8bf6239b95b382ce68f8411aac0d364f67a1c278dfbbf08f1501cd0482751e30",
|
||||
"packages/ui/components/Breadcrumb.wrn": "df27101d41cf018d55b6909e0399286a4661637140e341c8624615051d485142",
|
||||
"packages/ui/components/ButtonGroup.wrn": "0d97fbeed531d1d8ef4b59531923b47b34100d0de71d7bf524b02c668920b3d8",
|
||||
"packages/ui/components/CTASection.wrn": "2d2241d4f1018cfd2f8ea1ddd5fa8c8fcb8d1bbb2d4e4498cb9f39ff47a3801c",
|
||||
"packages/ui/components/Card.wrn": "4370df341235819b8a968a3f812209c2e6c29e4471d06578aaa5b577fb6e2dc8",
|
||||
@@ -27,18 +27,18 @@
|
||||
"packages/ui/components/Combobox.wrn": "62a7d861b0e536ceb088cea3552c59acbafaea5184101e3651dc8f4976b99dbc",
|
||||
"packages/ui/components/Confetti.wrn": "22d687beefd5047055f65e806b4e39f81e18887532ec18d439ac364a044b230a",
|
||||
"packages/ui/components/Container.wrn": "be8fce140043eced8b78c8dd14ff85ace4ed2d91671251641ef83e0fe4da8f2a",
|
||||
"packages/ui/components/ContextMenu.wrn": "2b5153f3acec3b11e5f20d7ea219f4e7b92569340fac09279e6a51bff0e7b235",
|
||||
"packages/ui/components/ContextMenu.wrn": "2e011e9cb1c09a3a2344ed3fa29dcc74331cd39f838535d8aee4b249dceef0ce",
|
||||
"packages/ui/components/CopyMarkup.wrn": "8d57a7d72e02c126f855a181fc36e1b969edb90f683dce6b6ffe546d49d2b29b",
|
||||
"packages/ui/components/CustomScrollbar.wrn": "4c1f7758b9cd47e20ecf922403e12b5b15b8127a2744383c6bd280ee20e5fdd1",
|
||||
"packages/ui/components/DataMap.wrn": "65552d73ecd1a148427dbb10d611f9352ca36bd490f595ab1e1a47e173445ba1",
|
||||
"packages/ui/components/DataTable.wrn": "a147aef9840ac1ed9e97921b8ff81b4f8a5afe66dd6ccb6d26947b8f7ffc760f",
|
||||
"packages/ui/components/DataTable.wrn": "5a76e50e8559c724aaf01ec954958ac640c99758d44f1eef8e6eb1cb6dc4f862",
|
||||
"packages/ui/components/DatePicker.wrn": "18b07e59c27c2720bf960e9c04ad70d9e422ddcf0817fede74e71899811bb387",
|
||||
"packages/ui/components/DeviceFrame.wrn": "a881cf2cc1f859b43cc3c7a1e1ca0fb40cea7a9e530ab5bdcb985aa9592d11c0",
|
||||
"packages/ui/components/Divider.wrn": "e377f8005249cf4f6c51dd4d04ff9c6a70a3f4e3a79d2c02a8ab601754c8553a",
|
||||
"packages/ui/components/DragAndDrop.wrn": "9a403ce9ed20d36211d8916911871875c9c7d0952a7de1a21a433eaa0b3c3324",
|
||||
"packages/ui/components/Drawer.wrn": "7ecfc54494402e8758eb3f648418df70b6cde598f38836a620a1fa179d1bb1bd",
|
||||
"packages/ui/components/Dropdown.wrn": "703c354dfb304ad640a600398317b0a5eefeea326df8a1fc51c87e0ed3c26ebc",
|
||||
"packages/ui/components/FeatureCard.wrn": "cf122df37d5de72a9556fea501b13b8bfad49ee4a5e88daec2a32d204ee9e784",
|
||||
"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",
|
||||
@@ -61,44 +61,45 @@
|
||||
"packages/ui/components/Map.wrn": "7b8a0d5412a464fd7cab8977d816542c506d8c5dab8230a3c984c2caee407c91",
|
||||
"packages/ui/components/MarketingSectionHeader.wrn": "7d6ffcc01a9331474475ea57ca58598be757abd9428b66bdfaf6f3603c5b145b",
|
||||
"packages/ui/components/Marquee.wrn": "b2b35eedf3297ba12ab3776385a9f3eab001027648d87a2a1968f576eee6c025",
|
||||
"packages/ui/components/MegaMenu.wrn": "4a084eaf6aae77bb9023d2f3589bc6b80119b9be63982a90a80f9d280cc9c0a5",
|
||||
"packages/ui/components/MegaMenu.wrn": "48f4ced485df0e8de5217630c8bdcec0b2317bf04e6b279399f2c93d98d56995",
|
||||
"packages/ui/components/MetricCard.wrn": "6451182739298691908f68258c0250cce2a78b0dc27c97115579ece30d7d9f92",
|
||||
"packages/ui/components/MetricGrid.wrn": "6018a98c10628ed240ed236ed916c0ff60994d192c3aadafd10bc876be0f9364",
|
||||
"packages/ui/components/Modal.wrn": "1821428492e510403dd029c4766e71272899299f65141ae4fdaf09c4a26719e5",
|
||||
"packages/ui/components/Nav.wrn": "78f215c94caf68e0968449a23e23bd3409a689e0c52a6c1770aa8373c767d080",
|
||||
"packages/ui/components/Navbar.wrn": "e68f9d3643e500e43124e9c7a6d4c7f6722657377ea7313f3e3cf5093a81b360",
|
||||
"packages/ui/components/PageHeader.wrn": "adb3bed81ce040044405e35a52d0304f49d5a0d162f923fbd54a4242a25c7362",
|
||||
"packages/ui/components/Pagination.wrn": "9169e724f89992dacd10e9492a95438c8016affb39c0fd17a4f6fe26bd21d8b4",
|
||||
"packages/ui/components/Modal.wrn": "59d4ae9d6dd2700692d9edecfe53ee868e9f864363a4885961d1813cb2bee51f",
|
||||
"packages/ui/components/Nav.wrn": "7dc456b879e9248bde267b986cdcb138b8f3127d98b51e27bc9708129953f5e2",
|
||||
"packages/ui/components/Navbar.wrn": "cf28cf4cdd23c85821d3302e6881115c2c14977e7cb7142d306cf559fea2a518",
|
||||
"packages/ui/components/PageHeader.wrn": "0761235a4924eec09877be40b292d27b70db22d210be7d8e989493165c20df86",
|
||||
"packages/ui/components/Pagination.wrn": "9ceb74745b159150b015bbbb1974c68e14a7d4b92bd03491cb23b8855aa07983",
|
||||
"packages/ui/components/PinInput.wrn": "5196f584de8d548a5dfa03688c3c95da3c948cead2662b05e927386ccea74299",
|
||||
"packages/ui/components/Popover.wrn": "fd9982f60e37e500586f788470f41a8510d6b96d3a85c80d2859fa01cac6731e",
|
||||
"packages/ui/components/Popover.wrn": "167f6c476cf3114ac5062ecf7739bddd9b5a81b1d209396a3675067dad1577b2",
|
||||
"packages/ui/components/PortalDashboard.wrn": "037d4300b59c7d60543abc7d4aba5c738efc143e9b61abc48aad0ddfcfe6845b",
|
||||
"packages/ui/components/PreferenceSwitcher.wrn": "2cc186d4dcb6580b4b152e3a265d9ed5ad210d3fdc76330b9092db21894465f0",
|
||||
"packages/ui/components/PublicPageShell.wrn": "507baad0e83dc05c24db42b8af8bd45b18f4e32e418843d77ec906428c687199",
|
||||
"packages/ui/components/Radio.wrn": "0b2d38a5aee3e859280e4590fa1d509d789cc2239082054872fa342817735f1b",
|
||||
"packages/ui/components/RangeSlider.wrn": "21eb7a5df0ee2cb5e78f628eb920265998eb9f1a4933d4e5c57db0323fd66b41",
|
||||
"packages/ui/components/Rating.wrn": "a2d74dc748fc7d98892684c760518b87fa65411b1102e3611252b87245b3ffcb",
|
||||
"packages/ui/components/Scrollspy.wrn": "bbe0788f63c2bc8850649c1dee78391d485a1c93876015bc536d838273d5fae4",
|
||||
"packages/ui/components/Scrollspy.wrn": "76d1c17580c8d460f1222ce97585bdcac9779141f2f30d5460c84ff9f75413d8",
|
||||
"packages/ui/components/SearchBox.wrn": "315f54f1feaaa47a815d1e2d7a35b492f09fbfadfc6d37228e1009e19e0652ad",
|
||||
"packages/ui/components/Section.wrn": "21485de07d8bb986837a65c61c5dd01ba6c2f3d4e37d58c154a584c7b58de0ed",
|
||||
"packages/ui/components/SectionHeader.wrn": "512cb96636ee1f0540a0f4b4c75603fc0fe7536e7ed7ed7dc82173a16f48a4d3",
|
||||
"packages/ui/components/Select.wrn": "7f046bb11b7c2470dae26d91a4b2261c66a40ae054d0e04c69b6748912d1e204",
|
||||
"packages/ui/components/Sidebar.wrn": "05f4bd216ae8bfcee2460fe638e431174d5f3c8e10003f346644e5a2939e7ce5",
|
||||
"packages/ui/components/Sidebar.wrn": "03f1bbce75ca6d50ed940e62df39c539610e89eadd2064fe24abc7d5470f98bf",
|
||||
"packages/ui/components/SplitHero.wrn": "70e843565ff869bcf413b11b6d9830b2e2bd229863a00904596f71e2dff0e76d",
|
||||
"packages/ui/components/StatsBar.wrn": "c7d1df3895f25b6d3a2e1012a18b97592c7f33e65418b880d486f20c2d490686",
|
||||
"packages/ui/components/Stepper.wrn": "4fec0c91a9006319ec27f3198d1176acdfd93abf62d76159aa55c0879bb081d9",
|
||||
"packages/ui/components/Stepper.wrn": "09f216a44f888421091bd1bce760a076927500a573550d5b2b10c4f27f608e92",
|
||||
"packages/ui/components/StrongPassword.wrn": "c7c5ef26ece6170dd7db9882f0dc98cb2e4607f1e5d43eb3fd39e5d15bbd3a2e",
|
||||
"packages/ui/components/StyledIcon.wrn": "4a4504e357dee9dd0418edbe85fc90b824ccdb3752123a2125da95b77bf0b336",
|
||||
"packages/ui/components/Switch.wrn": "ccb74599fab72b0d68b09a7f1f90b7732cb2f9cbed67a84219e897575ce30c28",
|
||||
"packages/ui/components/Tabs.wrn": "8a1b98c7395b27b09f16f0ebdc14d3d84473b06023ec2b3ed0af495cc2f076bb",
|
||||
"packages/ui/components/Tabs.wrn": "4fa0c0854700532960043290700d7cf99663ec41692068101f0aae4c2b1a1181",
|
||||
"packages/ui/components/TextLink.wrn": "30782039293eb36d63b7b3a4f32a71a47177a3a68c7e90184be7cf7b4385eb19",
|
||||
"packages/ui/components/Textarea.wrn": "ddf0b4f124b2cf0c0ab3d820d3ac0085f7c20466e977231949be264cd0cee8cf",
|
||||
"packages/ui/components/TimePicker.wrn": "2e8e7a90f6b6069a07e7ffd2725ba1e1031e84d55a4f1254025befbb314fa695",
|
||||
"packages/ui/components/Timeline.wrn": "5708c656eefd12f31c93490075ee482bf527059028844bd58cdcddc88461e2b5",
|
||||
"packages/ui/components/Toast.wrn": "f37c584d1c1a66401deb53d915c8ee0c70aaf7baa1d3c297fa74c5bdb914dd1d",
|
||||
"packages/ui/components/ToastNotifications.wrn": "33ff76b2a0a129ff896baea8979b4be97f7ec4471a92a2da970403a5c46e8b03",
|
||||
"packages/ui/components/Toaster.wrn": "7481ecddde9ed5bf1f448d45a78da7ee0009f84963814bf681735db4e5ab75f9",
|
||||
"packages/ui/components/ToggleCount.wrn": "70a75b2bdcc89103f8ca300ee6d21cd61baf8c6a4aeade9b68d1dc529f77064b",
|
||||
"packages/ui/components/TogglePassword.wrn": "405a85cbfa3d0ff0185b52d2805fba88497a5f25501f01b2438ed3a28591a38d",
|
||||
"packages/ui/components/Tooltip.wrn": "fe663c153e5239f37a298273662b16a8a77928be9376a823cd6f4e62fbaf2ee5",
|
||||
"packages/ui/components/Tooltip.wrn": "e6c8c14a75062d04a2e64245df1a40e67785240573c73bae4b0b32e76ef822bc",
|
||||
"packages/ui/components/TreeView.wrn": "1791a10135595b17b5c746af9e0476e8520d516ba46d64c79dc4efe0af2b500d",
|
||||
"packages/ui/components/Typography.wrn": "a667e800e11d23da74b00047bd9c560a65a5920e5394f58f66a6e0b32572ce8e",
|
||||
"packages/ui/components/WysiwygEditor.wrn": "636e60b9f9be7a5807cca7ad20e0b0f370ee1ec6c1ecba5d0a577726cc86bc98",
|
||||
@@ -109,7 +110,6 @@
|
||||
"packages/ui/components/progress.wrn": "ba6f4dfcc00f04f34e9533be675bb4a6200c3cdcd7d03fc597377e48520fdec7",
|
||||
"packages/ui/components/skeleton.wrn": "ceec8af147b8be08155500e378393ac7c72d819da61b62191c076c80e943d5a9",
|
||||
"packages/ui/components/spinner.wrn": "abc4ee3ede2e019289257e9009eee012c880a85839cc220fac6dbe1b780caf52",
|
||||
"packages/ui/components/table.wrn": "96126772e82fbd401787600aba7a095ef097282d9bdb4df22f3e06016c677210",
|
||||
"packages/ui/ui.css": "a72e839e6cccbe01483507f9d9ba913962aca2b9f866bb6e136f023d6ea2013d"
|
||||
"packages/ui/ui.css": "eee7da4d1088703a61db8e78c43eb6ff7f1b6a02fd8082d8d722beb65bf78382"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "wrnexus",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "wrnexus",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
"dependencies": {
|
||||
"vscode-languageclient": "^10.1.0"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "wrnexus",
|
||||
"displayName": "WRNexus Language Support",
|
||||
"description": "Complete WRNexus v0.8.3 language support for typed imports, props, state, outputs, runtime functions, stores, diagnostics, formatting, navigation, and migration assistance.",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"publisher": "wrnexus",
|
||||
"private": true,
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use strict";
|
||||
// Generated by scripts/build-editor-compiler.mjs. Do not edit directly.
|
||||
// WRN editor compiler source hash: 1d2e0d1e1c38a4513b0fce3631bae003f7c28069920c5386ecc68cbe728bf57b
|
||||
// WRN editor compiler source hash: 451706f47a7734fd5808cdeeb5786e1b97a183afec98db99fc041369eb16a205
|
||||
// WRN editor compiler generator hash: c71e7fe4258c97b73b384ff14b321f0cf0b30cc2ed0322f5f84b04e757159b18
|
||||
// Generated with TypeScript: 5.9.3
|
||||
const __nodeRequire = require;
|
||||
@@ -560,24 +560,44 @@ function browserModuleRequired(ast) {
|
||||
}
|
||||
function functionEntry(ast, fn, availableFunctions) {
|
||||
const parameterNames = new Set(fn.parameters.map((parameter) => parameter.name));
|
||||
/*
|
||||
* Names the function body declares for itself.
|
||||
*
|
||||
* Props and state are destructured into the SAME scope as the body, so a
|
||||
* body that declares `var size` when `size` is also a prop produced
|
||||
* "Identifier 'size' has already been declared" and the entire module
|
||||
* failed to parse -- taking every function in the component down with it,
|
||||
* with nothing to point at the one line responsible. Skipping the alias for
|
||||
* a shadowed name is also what plain JavaScript does: inside that function
|
||||
* the local wins.
|
||||
*/
|
||||
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)) {
|
||||
const name = match[1] ?? match[2];
|
||||
if (name)
|
||||
declaredLocals.add(name);
|
||||
}
|
||||
const stateNames = ast.states
|
||||
.filter((state) => state.runtime !== "server" &&
|
||||
safeIdentifier(state.name) &&
|
||||
!RUNTIME_BINDINGS.has(state.name) &&
|
||||
!parameterNames.has(state.name))
|
||||
!parameterNames.has(state.name) &&
|
||||
!declaredLocals.has(state.name))
|
||||
.map((state) => state.name);
|
||||
const stateSet = new Set(stateNames);
|
||||
const propNames = ast.props
|
||||
.filter((prop) => safeIdentifier(prop.name) &&
|
||||
!RUNTIME_BINDINGS.has(prop.name) &&
|
||||
!parameterNames.has(prop.name) &&
|
||||
!stateSet.has(prop.name))
|
||||
!stateSet.has(prop.name) &&
|
||||
!declaredLocals.has(prop.name))
|
||||
.map((prop) => prop.name);
|
||||
const functionAliases = availableFunctions.filter((name) => safeIdentifier(name) &&
|
||||
!RUNTIME_BINDINGS.has(name) &&
|
||||
!parameterNames.has(name) &&
|
||||
!stateSet.has(name) &&
|
||||
!propNames.includes(name));
|
||||
!propNames.includes(name) &&
|
||||
!declaredLocals.has(name));
|
||||
const parameters = fn.parameters.map((parameter) => parameter.name).join(", ");
|
||||
const initialStateSnapshot = stateNames.length
|
||||
? `const __wrnexusInitialState = { ${stateNames.map((name) => `${JSON.stringify(name)}: context.state.${name}`).join(", ")} };`
|
||||
@@ -805,6 +825,31 @@ function eventAttribute(name) {
|
||||
}
|
||||
return `data-on-${name}`;
|
||||
}
|
||||
/**
|
||||
* Event attribute for a handler written on a *component tag*
|
||||
* (`<Modal @confirm="save()">`).
|
||||
*
|
||||
* These need their own attribute name. The mount's attributes are forwarded
|
||||
* into the component and land on its view root, i.e. inside the component's
|
||||
* own `data-scope` -- but the statement (`save()`) belongs to the parent that
|
||||
* wrote the tag. Emitting `data-on-confirm` makes the child's runtime bind it
|
||||
* against the child's scope, where the parent's functions and state do not
|
||||
* exist, so the handler silently does nothing. `data-wrn-out-*` is ignored by
|
||||
* the child and claimed by the mounting scope instead.
|
||||
*
|
||||
* `window:`/`document:` (and the browser/mobile bridges) keep the plain
|
||||
* `data-on-*` form: those bind to a global target rather than to the element,
|
||||
* and the runtime has no component-output path for them.
|
||||
*/
|
||||
function componentEventAttribute(name) {
|
||||
if (name.startsWith("window:") ||
|
||||
name.startsWith("document:") ||
|
||||
name.startsWith("browser-") ||
|
||||
name.startsWith("mobile-")) {
|
||||
return eventAttribute(name);
|
||||
}
|
||||
return `data-wrn-out-${name}`;
|
||||
}
|
||||
function reactiveAttrValue(raw, reactive) {
|
||||
let found = false;
|
||||
const value = raw.replace(/\{([^{}]+)\}/g, (whole, inner) => {
|
||||
@@ -962,7 +1007,11 @@ function renderLoopBody(node) {
|
||||
const attrs = node.attrs
|
||||
.filter((attr) => attr.name !== "data-component")
|
||||
.map((attr) => {
|
||||
const name = attr.event ? eventAttribute(attr.name) : attr.name;
|
||||
const name = attr.event
|
||||
? componentTag
|
||||
? componentEventAttribute(attr.name)
|
||||
: eventAttribute(attr.name)
|
||||
: attr.name;
|
||||
if (attr.boolean) {
|
||||
return escLit(` ${name}`);
|
||||
}
|
||||
@@ -1221,7 +1270,9 @@ function renderNestedComponentInvocation(node, ctx) {
|
||||
return `\${__wireSpreadAttrs(${ctx.resolveExpr(spread[1])})}`;
|
||||
}
|
||||
if (attr.event) {
|
||||
return (escLit(` ${eventAttribute(attr.name)}="`) + escLit(attrEscape(attr.value)) + escLit(`"`));
|
||||
return (escLit(` ${componentEventAttribute(attr.name)}="`) +
|
||||
escLit(attrEscape(attr.value)) +
|
||||
escLit(`"`));
|
||||
}
|
||||
if (attr.boolean) {
|
||||
return ` ${attr.name}`;
|
||||
@@ -2398,6 +2449,18 @@ function renderComponentNode(node, ctx) {
|
||||
const marker = referencesState || referencesLoopVariable || referencesServerLocal
|
||||
? ` data-wrn-bind-${bindIndex++}="${escLit(attrEscape(JSON.stringify([a.name, a.value])))}"`
|
||||
: "";
|
||||
/*
|
||||
* A boolean attribute whose expression names a loop variable cannot
|
||||
* be resolved on the server: __wireBooleanAttr runs at render time,
|
||||
* where `row` or `item` simply does not exist, and the emitted
|
||||
* module blew up. Leave the attribute off the server output and let
|
||||
* the client bind set it -- the runtime toggles boolean attributes
|
||||
* rather than stringifying them, so `checked={isSelected(row)}`
|
||||
* behaves correctly once hydrated.
|
||||
*/
|
||||
if (referencesLoopVariable) {
|
||||
return ` data-wrn-bind-${bindIndex++}="${escLit(attrEscape(JSON.stringify([a.name, a.value])))}"`;
|
||||
}
|
||||
return `\${__wireBooleanAttr(${JSON.stringify(a.name)}, ${elementContext.resolveExpr(expression)})}${marker}`;
|
||||
}
|
||||
if (a.value === "false")
|
||||
@@ -2406,6 +2469,20 @@ function renderComponentNode(node, ctx) {
|
||||
return ` ${a.name}`;
|
||||
}
|
||||
const wholeExpression = wholeAttributeExpression(a.value);
|
||||
/*
|
||||
* data-show carries an EXPRESSION, not a value. The client re-evaluates
|
||||
* whatever string it finds in the attribute on every state change, so
|
||||
* interpolating `{open || visible}` down to the literal "false" at
|
||||
* render time froze the directive: the element could never be shown
|
||||
* again, no matter what the state did. A data-wrn-bind marker did not
|
||||
* save it either -- the bind rewrites the same attribute the directive
|
||||
* reads, and the directive had already captured "false" as its
|
||||
* expression. Emitting the expression verbatim (the form Modal uses,
|
||||
* data-show="isOpen()") makes both authoring styles behave the same.
|
||||
*/
|
||||
if (a.name === "data-show" && wholeExpression) {
|
||||
return ` data-show="${escLit(attrEscape(wholeExpression))}"`;
|
||||
}
|
||||
const compiledValue = isExplicitComponentMount && wholeExpression
|
||||
? `\${__wireProp(${elementContext.resolveExpr(wholeExpression)})}`
|
||||
: compileAttrValue(a.value, elementContext);
|
||||
@@ -2751,7 +2828,10 @@ function __wireSpreadAttrs(value: any): string {
|
||||
lowerName === "style" ||
|
||||
lowerName === "slot" ||
|
||||
lowerName === "data-component" ||
|
||||
lowerName.startsWith("data-wrn")
|
||||
// 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-"))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
@@ -2867,7 +2947,7 @@ function __wireProp(v) {
|
||||
}
|
||||
function renderPageComponentAttr(attr, dynamicExpressions) {
|
||||
if (attr.event) {
|
||||
return ` ${eventAttribute(attr.name)}="${attrEscape(attr.value)}"`;
|
||||
return ` ${componentEventAttribute(attr.name)}="${attrEscape(attr.value)}"`;
|
||||
}
|
||||
if (attr.boolean) {
|
||||
return ` ${attr.name}`;
|
||||
@@ -6703,6 +6783,18 @@ class Lexer {
|
||||
* Read a `{ ... }` block and return its INNER text (no outer braces), with
|
||||
* brace counting that respects string and template literals so a `}` inside a
|
||||
* string doesn't end the block early.
|
||||
*
|
||||
* Comments are skipped as well. Without that, an apostrophe in ordinary
|
||||
* prose — `/* the panel's color *\/`, `// the Input's slot` — opened a
|
||||
* string that ran to the next apostrophe, swallowing every brace in between
|
||||
* and failing the whole component with "Unbalanced braces" pointing at the
|
||||
* block's opening line. Comments are where apostrophes actually occur, so
|
||||
* that error was almost always a false alarm.
|
||||
*
|
||||
* A `//` line comment is only recognised at the start of a line (after
|
||||
* whitespace), which is where every comment in a `.wrn` file is written.
|
||||
* Recognising it mid-line would break the far more common case of a bare
|
||||
* URL in view text, where `https://…` is not inside quotes.
|
||||
*/
|
||||
readBalancedBraces() {
|
||||
this.skipTrivia();
|
||||
@@ -6714,6 +6806,8 @@ class Lexer {
|
||||
let depth = 0;
|
||||
let i = this.pos;
|
||||
let str = null;
|
||||
/** True while only whitespace has been seen since the last newline. */
|
||||
let atLineStart = false;
|
||||
for (; i < src.length; i++) {
|
||||
const c = src[i];
|
||||
if (str) {
|
||||
@@ -6725,6 +6819,27 @@ class Lexer {
|
||||
str = null;
|
||||
continue;
|
||||
}
|
||||
if (c === "\n") {
|
||||
atLineStart = true;
|
||||
continue;
|
||||
}
|
||||
if (c === "/" && src[i + 1] === "*") {
|
||||
const close = src.indexOf("*/", i + 2);
|
||||
if (close === -1)
|
||||
break; // unterminated: fall through to the error
|
||||
i = close + 1;
|
||||
atLineStart = false;
|
||||
continue;
|
||||
}
|
||||
if (atLineStart && c === "/" && src[i + 1] === "/") {
|
||||
const newline = src.indexOf("\n", i + 2);
|
||||
if (newline === -1)
|
||||
break;
|
||||
i = newline - 1; // let the loop's own increment land on the newline
|
||||
continue;
|
||||
}
|
||||
if (c !== " " && c !== "\t" && c !== "\r")
|
||||
atLineStart = false;
|
||||
if (c === '"' || c === "'" || c === "`") {
|
||||
str = c;
|
||||
continue;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// WRN editor extension source hash: 825c406dea4b196976b1b6e53b3c71c3263bd63f403acdaf2fe7e5ad174275da
|
||||
// WRN editor extension source hash: 6afaf7958331407986761b491ad81d56458dcb162f56369a4cc29f86ff95d981
|
||||
// WRN editor extension generator hash: 456d1d614e44e5fb1f19b784176c09cf2ade9b64ef73a17934c2698150b62728
|
||||
"use strict";
|
||||
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
||||
@@ -23204,10 +23204,16 @@ ${(0, codegen_ts_1.generate)(ast)}`,
|
||||
}
|
||||
function functionEntry(ast, fn, availableFunctions) {
|
||||
const parameterNames = new Set(fn.parameters.map((parameter) => parameter.name));
|
||||
const stateNames = ast.states.filter((state) => state.runtime !== "server" && safeIdentifier(state.name) && !RUNTIME_BINDINGS.has(state.name) && !parameterNames.has(state.name)).map((state) => state.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)) {
|
||||
const name = match[1] ?? match[2];
|
||||
if (name)
|
||||
declaredLocals.add(name);
|
||||
}
|
||||
const stateNames = ast.states.filter((state) => state.runtime !== "server" && safeIdentifier(state.name) && !RUNTIME_BINDINGS.has(state.name) && !parameterNames.has(state.name) && !declaredLocals.has(state.name)).map((state) => state.name);
|
||||
const stateSet = new Set(stateNames);
|
||||
const propNames = ast.props.filter((prop) => safeIdentifier(prop.name) && !RUNTIME_BINDINGS.has(prop.name) && !parameterNames.has(prop.name) && !stateSet.has(prop.name)).map((prop) => prop.name);
|
||||
const functionAliases = availableFunctions.filter((name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name) && !parameterNames.has(name) && !stateSet.has(name) && !propNames.includes(name));
|
||||
const propNames = ast.props.filter((prop) => safeIdentifier(prop.name) && !RUNTIME_BINDINGS.has(prop.name) && !parameterNames.has(prop.name) && !stateSet.has(prop.name) && !declaredLocals.has(prop.name)).map((prop) => prop.name);
|
||||
const functionAliases = availableFunctions.filter((name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name) && !parameterNames.has(name) && !stateSet.has(name) && !propNames.includes(name) && !declaredLocals.has(name));
|
||||
const parameters = fn.parameters.map((parameter) => parameter.name).join(", ");
|
||||
const initialStateSnapshot = stateNames.length ? `const __wrnexusInitialState = { ${stateNames.map((name) => `${JSON.stringify(name)}: context.state.${name}`).join(", ")} };` : "";
|
||||
const stateAliases = stateNames.length ? `let { ${stateNames.join(", ")} } = context.state;` : "";
|
||||
@@ -23404,6 +23410,12 @@ export function bindClientScope(context) {
|
||||
}
|
||||
return `data-on-${name}`;
|
||||
}
|
||||
function componentEventAttribute(name) {
|
||||
if (name.startsWith("window:") || name.startsWith("document:") || name.startsWith("browser-") || name.startsWith("mobile-")) {
|
||||
return eventAttribute(name);
|
||||
}
|
||||
return `data-wrn-out-${name}`;
|
||||
}
|
||||
function reactiveAttrValue(raw, reactive) {
|
||||
let found = false;
|
||||
const value = raw.replace(/\{([^{}]+)\}/g, (whole, inner) => {
|
||||
@@ -23527,7 +23539,7 @@ export function bindClientScope(context) {
|
||||
}
|
||||
const componentTag = isComponentTag(node.tag);
|
||||
const attrs = node.attrs.filter((attr) => attr.name !== "data-component").map((attr) => {
|
||||
const name = attr.event ? eventAttribute(attr.name) : attr.name;
|
||||
const name = attr.event ? componentTag ? componentEventAttribute(attr.name) : eventAttribute(attr.name) : attr.name;
|
||||
if (attr.boolean) {
|
||||
return escLit(` ${name}`);
|
||||
}
|
||||
@@ -23699,7 +23711,7 @@ export function bindClientScope(context) {
|
||||
return `\${__wireSpreadAttrs(${ctx.resolveExpr(spread[1])})}`;
|
||||
}
|
||||
if (attr.event) {
|
||||
return escLit(` ${eventAttribute(attr.name)}="`) + escLit(attrEscape(attr.value)) + escLit(`"`);
|
||||
return escLit(` ${componentEventAttribute(attr.name)}="`) + escLit(attrEscape(attr.value)) + escLit(`"`);
|
||||
}
|
||||
if (attr.boolean) {
|
||||
return ` ${attr.name}`;
|
||||
@@ -24751,6 +24763,9 @@ ${handlers.join(`
|
||||
const referencesLoopVariable2 = elementContext.loopVars ? exprRefsState(a.value, elementContext.loopVars) : false;
|
||||
const referencesServerLocal2 = ctx.serverLocals ? exprRefsState(a.value, ctx.serverLocals) : false;
|
||||
const marker2 = referencesState2 || referencesLoopVariable2 || referencesServerLocal2 ? ` data-wrn-bind-${bindIndex++}="${escLit(attrEscape(JSON.stringify([a.name, a.value])))}"` : "";
|
||||
if (referencesLoopVariable2) {
|
||||
return ` data-wrn-bind-${bindIndex++}="${escLit(attrEscape(JSON.stringify([a.name, a.value])))}"`;
|
||||
}
|
||||
return `\${__wireBooleanAttr(${JSON.stringify(a.name)}, ${elementContext.resolveExpr(expression)})}${marker2}`;
|
||||
}
|
||||
if (a.value === "false")
|
||||
@@ -24759,6 +24774,9 @@ ${handlers.join(`
|
||||
return ` ${a.name}`;
|
||||
}
|
||||
const wholeExpression = wholeAttributeExpression(a.value);
|
||||
if (a.name === "data-show" && wholeExpression) {
|
||||
return ` data-show="${escLit(attrEscape(wholeExpression))}"`;
|
||||
}
|
||||
const compiledValue = isExplicitComponentMount && wholeExpression ? `\${__wireProp(${elementContext.resolveExpr(wholeExpression)})}` : compileAttrValue(a.value, elementContext);
|
||||
const rendered = ` ${a.name}="${compiledValue}"`;
|
||||
const referencesState = exprRefsComponentReactiveValue(a.value, ctx);
|
||||
@@ -25054,7 +25072,10 @@ function __wireSpreadAttrs(value: any): string {
|
||||
lowerName === "style" ||
|
||||
lowerName === "slot" ||
|
||||
lowerName === "data-component" ||
|
||||
lowerName.startsWith("data-wrn")
|
||||
// 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-"))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
@@ -25173,7 +25194,7 @@ function __wireRaw(v: any): string {
|
||||
}
|
||||
function renderPageComponentAttr(attr, dynamicExpressions) {
|
||||
if (attr.event) {
|
||||
return ` ${eventAttribute(attr.name)}="${attrEscape(attr.value)}"`;
|
||||
return ` ${componentEventAttribute(attr.name)}="${attrEscape(attr.value)}"`;
|
||||
}
|
||||
if (attr.boolean) {
|
||||
return ` ${attr.name}`;
|
||||
@@ -28961,6 +28982,7 @@ ${serverFunctions}
|
||||
let depth = 0;
|
||||
let i = this.pos;
|
||||
let str = null;
|
||||
let atLineStart = false;
|
||||
for (;i < src.length; i++) {
|
||||
const c = src[i];
|
||||
if (str) {
|
||||
@@ -28972,6 +28994,29 @@ ${serverFunctions}
|
||||
str = null;
|
||||
continue;
|
||||
}
|
||||
if (c === `
|
||||
`) {
|
||||
atLineStart = true;
|
||||
continue;
|
||||
}
|
||||
if (c === "/" && src[i + 1] === "*") {
|
||||
const close = src.indexOf("*/", i + 2);
|
||||
if (close === -1)
|
||||
break;
|
||||
i = close + 1;
|
||||
atLineStart = false;
|
||||
continue;
|
||||
}
|
||||
if (atLineStart && c === "/" && src[i + 1] === "/") {
|
||||
const newline = src.indexOf(`
|
||||
`, i + 2);
|
||||
if (newline === -1)
|
||||
break;
|
||||
i = newline - 1;
|
||||
continue;
|
||||
}
|
||||
if (c !== " " && c !== "\t" && c !== "\r")
|
||||
atLineStart = false;
|
||||
if (c === '"' || c === "'" || c === "`") {
|
||||
str = c;
|
||||
continue;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
// WRN editor language server source hash: 5397c7912894dd5ab330f6fafc0f4093adbb94d95c23366ba7883605a724a7ae
|
||||
// WRN editor language server source hash: c4362f7edf117e0c403c9bce9435bc02ae0febd963edb4af9a18d2d74d87eb47
|
||||
// WRN editor language server generator hash: f593a44aaf05495b789ce7a3086bee1eebb951b884d41c0e017bbcfe5f547e72
|
||||
// @bun @bun-cjs
|
||||
(function(exports, require, module, __filename, __dirname) {var __create = Object.create;
|
||||
@@ -169020,6 +169020,7 @@ class Lexer {
|
||||
let depth = 0;
|
||||
let i = this.pos;
|
||||
let str = null;
|
||||
let atLineStart = false;
|
||||
for (;i < src.length; i++) {
|
||||
const c = src[i];
|
||||
if (str) {
|
||||
@@ -169031,6 +169032,29 @@ class Lexer {
|
||||
str = null;
|
||||
continue;
|
||||
}
|
||||
if (c === `
|
||||
`) {
|
||||
atLineStart = true;
|
||||
continue;
|
||||
}
|
||||
if (c === "/" && src[i + 1] === "*") {
|
||||
const close = src.indexOf("*/", i + 2);
|
||||
if (close === -1)
|
||||
break;
|
||||
i = close + 1;
|
||||
atLineStart = false;
|
||||
continue;
|
||||
}
|
||||
if (atLineStart && c === "/" && src[i + 1] === "/") {
|
||||
const newline = src.indexOf(`
|
||||
`, i + 2);
|
||||
if (newline === -1)
|
||||
break;
|
||||
i = newline - 1;
|
||||
continue;
|
||||
}
|
||||
if (c !== " " && c !== "\t" && c !== "\r")
|
||||
atLineStart = false;
|
||||
if (c === '"' || c === "'" || c === "`") {
|
||||
str = c;
|
||||
continue;
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { defineAuthz } from "@wrnexus/authz";
|
||||
|
||||
/**
|
||||
* `app/authz/<name>.ts` declarations are discovered automatically and merged
|
||||
* into the process-wide catalog at boot (see `app/middleware/authz.ts`, which
|
||||
* registers the middleware that resolves against it).
|
||||
*/
|
||||
export default defineAuthz({
|
||||
permissions: {
|
||||
"post:read": { title: "View posts", public: true },
|
||||
"post:write": { title: "Create and edit posts" },
|
||||
"post:delete": { title: "Delete posts", risk: "high" },
|
||||
"admin:access": { title: "Reach the admin area", risk: "high" },
|
||||
},
|
||||
roles: {
|
||||
viewer: ["post:read"],
|
||||
editor: ["role:viewer", "post:write"],
|
||||
admin: ["role:editor", "post:delete", "admin:access"],
|
||||
},
|
||||
policies: {
|
||||
ownsPost: async (
|
||||
subject: { id?: string },
|
||||
resource?: { authorId?: string },
|
||||
): Promise<{ allowed: boolean; reason?: string }> =>
|
||||
resource?.authorId === subject?.id
|
||||
? { allowed: true }
|
||||
: { allowed: false, reason: "You are not the author" },
|
||||
},
|
||||
bindings: { "post:delete": ["ownsPost"] },
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import { authzMiddleware, getAuthzCatalog, memoryPermissionStore } from "@wrnexus/authz";
|
||||
|
||||
/**
|
||||
* Registers the per-request authorization resolver against the catalog merged
|
||||
* from `app/authz/*.ts` (see `showcase.ts`). This is an eager, module-scope
|
||||
* call — the same shape `authzMiddleware({...})` requires — so it must run
|
||||
* after `getAuthzCatalog()` has been populated. Both the dev server and
|
||||
* `wrnexus build`'s generated production entry guarantee that happens before
|
||||
* any app middleware module evaluates.
|
||||
*
|
||||
* Middleware runs in alphabetical filename order, so `authz.ts` runs after
|
||||
* `auth.ts`, which hydrates `ctx.user` from the session. Route handlers and
|
||||
* pages can then call `can(ctx, "post:write")` or guard a route with
|
||||
* `guardPermission("post:delete")`.
|
||||
*
|
||||
* A real deployment would swap `memoryPermissionStore()` for
|
||||
* `dbPermissionStore(getDb())` from `@wrnexus/authz/db` so role and grant
|
||||
* assignments survive a restart; the showcase keeps everything in memory so
|
||||
* it stays dependency-free.
|
||||
*/
|
||||
export default authzMiddleware({
|
||||
catalog: getAuthzCatalog(),
|
||||
store: memoryPermissionStore(),
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import { httpTransport, serviceClient } from "@wrnexus/rpc";
|
||||
import { greeter } from "./greeter.ts";
|
||||
|
||||
/** A caller-side helper using the shared service contract. */
|
||||
export function greeterClient() {
|
||||
return serviceClient(greeter, { app: "auth-showcase", transport: httpTransport() });
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { defineService, implement, procedure } from "@wrnexus/rpc";
|
||||
import { v } from "@wrnexus/validation";
|
||||
|
||||
export const greeter = defineService({
|
||||
name: "greeter",
|
||||
procedures: {
|
||||
greet: procedure
|
||||
.input(v.object({ name: v.string() }))
|
||||
.output<{ message: string; subject: string }>()
|
||||
.build(),
|
||||
},
|
||||
});
|
||||
|
||||
export default implement(
|
||||
greeter,
|
||||
{
|
||||
greet: async ({ name }, ctx) => ({
|
||||
message: `Hello, ${name}`,
|
||||
subject: ctx.subject?.subjectId ?? "anonymous",
|
||||
}),
|
||||
},
|
||||
{ selfApp: "auth-showcase" },
|
||||
);
|
||||
@@ -12,6 +12,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/auth": "workspace:*",
|
||||
"@wrnexus/authz": "workspace:*",
|
||||
"@wrnexus/rpc": "workspace:*",
|
||||
"@wrnexus/captcha": "workspace:*",
|
||||
"@wrnexus/core": "workspace:*",
|
||||
"@wrnexus/validation": "workspace:*"
|
||||
|
||||
@@ -91,3 +91,23 @@ test("package auth schemas are shared by browser forms and API handlers", () =>
|
||||
expect(forgotPassword).toContain("data-schema='{schema}'");
|
||||
expect(forgotPassword).toContain("novalidate");
|
||||
});
|
||||
|
||||
test("authz is wired with a real declaration and a registered middleware, not a dangling file", () => {
|
||||
expect(existsSync(join(root, "app", "authz", "showcase.ts"))).toBe(true);
|
||||
expect(existsSync(join(root, "app", "middleware", "authz.ts"))).toBe(true);
|
||||
|
||||
const declaration = read(root, "app", "authz", "showcase.ts");
|
||||
expect(declaration).toContain("defineAuthz");
|
||||
expect(declaration).toContain('"post:read": { title: "View posts", public: true }');
|
||||
expect(declaration).toContain("bindings:");
|
||||
|
||||
const middleware = read(root, "app", "middleware", "authz.ts");
|
||||
expect(middleware).toContain("authzMiddleware");
|
||||
expect(middleware).toContain("getAuthzCatalog()");
|
||||
expect(middleware).toContain("memoryPermissionStore()");
|
||||
|
||||
const manifest = JSON.parse(read(root, "package.json")) as {
|
||||
dependencies?: Record<string, string>;
|
||||
};
|
||||
expect(manifest.dependencies?.["@wrnexus/authz"]).toBe("workspace:*");
|
||||
});
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
// API route: GET /api/accounts — a paged, sorted, filtered slice of a dataset.
|
||||
//
|
||||
// This is the server half of the DataTable `load` contract. The table sends
|
||||
// page, pageSize, sortKey, sortDirection and query, and expects `rows` for
|
||||
// that page plus `total` for the whole matching set. The total has to come
|
||||
// from here: the table only ever sees one page and cannot count the rest.
|
||||
import type { Context } from "@wrnexus/core";
|
||||
|
||||
interface Account {
|
||||
id: number;
|
||||
name: string;
|
||||
plan: string;
|
||||
owner: string;
|
||||
seats: number;
|
||||
renewsOn: string;
|
||||
}
|
||||
|
||||
const PLANS = ["Scale", "Team", "Enterprise", "Starter"];
|
||||
const OWNERS = ["A. Okafor", "R. Silva", "M. Chen", "J. Dubois", "P. Novak"];
|
||||
const NAMES = [
|
||||
"Northwind",
|
||||
"Acme Industrial",
|
||||
"Globex",
|
||||
"Initech",
|
||||
"Umbrella",
|
||||
"Stark Labs",
|
||||
"Wayne Foods",
|
||||
"Soylent",
|
||||
"Hooli",
|
||||
"Vehement",
|
||||
"Massive Dynamic",
|
||||
"Cyberdyne",
|
||||
"Tyrell",
|
||||
"Aperture",
|
||||
"Black Mesa",
|
||||
"Oceanic",
|
||||
"Weyland",
|
||||
"Gringotts",
|
||||
"Duff Brewing",
|
||||
"Prestige Worldwide",
|
||||
"Bluth Company",
|
||||
"Pied Piper",
|
||||
];
|
||||
|
||||
// Deterministic so paging is stable across requests.
|
||||
const ACCOUNTS: Account[] = NAMES.map((name, index) => ({
|
||||
id: index + 1,
|
||||
name,
|
||||
plan: PLANS[index % PLANS.length]!,
|
||||
owner: OWNERS[index % OWNERS.length]!,
|
||||
seats: ((index * 13) % 240) + 4,
|
||||
renewsOn: new Date(Date.UTC(2026, index % 12, ((index * 5) % 27) + 1)).toISOString().slice(0, 10),
|
||||
}));
|
||||
|
||||
export function GET(ctx: Context): Response {
|
||||
const params = ctx.url.searchParams;
|
||||
const page = Math.max(1, Number(params.get("page")) || 1);
|
||||
const pageSize = Math.min(100, Math.max(1, Number(params.get("pageSize")) || 10));
|
||||
const query = (params.get("query") ?? "").trim().toLowerCase();
|
||||
const sortKey = params.get("sortKey") ?? "";
|
||||
const descending = params.get("sortDirection") === "desc";
|
||||
|
||||
let matched = ACCOUNTS;
|
||||
if (query) {
|
||||
matched = matched.filter((account) =>
|
||||
[account.name, account.plan, account.owner, account.renewsOn].some((field) =>
|
||||
field.toLowerCase().includes(query),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (sortKey) {
|
||||
const direction = descending ? -1 : 1;
|
||||
matched = [...matched].sort((left, right) => {
|
||||
const a = left[sortKey as keyof Account];
|
||||
const b = right[sortKey as keyof Account];
|
||||
if (typeof a === "number" && typeof b === "number") return (a - b) * direction;
|
||||
return String(a).localeCompare(String(b)) * direction;
|
||||
});
|
||||
}
|
||||
|
||||
const start = (page - 1) * pageSize;
|
||||
|
||||
return Response.json({
|
||||
rows: matched.slice(start, start + pageSize),
|
||||
total: matched.length,
|
||||
page,
|
||||
pageSize,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// API route: POST /api/invite. Validates the body with the SAME schema the
|
||||
// modal's invite form uses in the browser, so a request that bypasses the
|
||||
// client (curl, a replayed fetch, a stale page) is held to identical rules.
|
||||
//
|
||||
// There is no mail provider wired up in the example, so a successful parse
|
||||
// just echoes the invite back. The point being demonstrated is the shared
|
||||
// schema and the client/server round trip, not delivery.
|
||||
import { verifyCsrf, type Context } from "@wrnexus/core";
|
||||
import { parseBody } from "@wrnexus/validation";
|
||||
import invite from "../schemas/invite.ts";
|
||||
|
||||
export async function POST(ctx: Context): Promise<Response> {
|
||||
if (!verifyCsrf(ctx)) return new Response("Invalid CSRF token", { status: 403 });
|
||||
|
||||
const result = await parseBody(invite, ctx.req);
|
||||
if (!result.ok) return result.response; // 400 { ok:false, errors } — rendered per field
|
||||
|
||||
const { email, message } = result.value as { email: string; message?: string };
|
||||
|
||||
return Response.json({
|
||||
ok: true,
|
||||
email,
|
||||
message: message ?? "",
|
||||
sentAt: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
component Modal {
|
||||
props {
|
||||
@event onConfirmed = function
|
||||
}
|
||||
|
||||
state isOpen = false
|
||||
|
||||
functions {
|
||||
function confirmed() {
|
||||
isOpen = false;
|
||||
if(onConfirmed) {
|
||||
onConfirmed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
view {
|
||||
<main
|
||||
class="flex min-h-screen items-center justify-center bg-[var(--wire-color-background)] p-6"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-xl bg-[var(--wire-color-primary)] px-5 py-3 font-semibold text-white shadow-lg transition hover:opacity-90"
|
||||
@click="isOpen = true"
|
||||
>
|
||||
Open Transparent Modal
|
||||
</button>
|
||||
|
||||
<div
|
||||
class:hidden="isOpen == false"
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/20 p-4 backdrop-blur-sm"
|
||||
>
|
||||
<div
|
||||
class="relative w-full max-w-lg overflow-hidden rounded-3xl border border-white/20 bg-white/10 p-6 shadow-2xl backdrop-blur-2xl dark:border-white/10 dark:bg-black/20"
|
||||
>
|
||||
<div
|
||||
class="pointer-events-none absolute -right-20 -top-20 h-48 w-48 rounded-full bg-[var(--wire-color-primary)]/30 blur-3xl"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="pointer-events-none absolute -bottom-20 -left-20 h-48 w-48 rounded-full bg-cyan-500/20 blur-3xl"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="relative z-10"
|
||||
>
|
||||
<div
|
||||
class="mb-6 flex items-start justify-between gap-4"
|
||||
>
|
||||
<div>
|
||||
<p
|
||||
class="mb-2 text-sm font-semibold uppercase tracking-wider text-[var(--wire-color-primary)]"
|
||||
>
|
||||
Transparent Modal
|
||||
</p>
|
||||
|
||||
<h2
|
||||
class="text-2xl font-bold text-[var(--wire-color-text)]"
|
||||
>
|
||||
Welcome to WRNexusJS
|
||||
</h2>
|
||||
|
||||
<p
|
||||
class="mt-2 text-sm leading-6 text-[var(--wire-color-text-muted)]"
|
||||
>
|
||||
This modal uses a transparent glass background with blur,
|
||||
soft borders, and theme-aware colors.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Close modal"
|
||||
class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full border border-white/20 bg-white/10 text-xl text-[var(--wire-color-text)] transition hover:bg-white/20 dark:border-white/10 dark:bg-black/20 dark:hover:bg-black/30"
|
||||
@click="isOpen = false"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="rounded-2xl border border-white/20 bg-white/10 p-4 backdrop-blur-lg dark:border-white/10 dark:bg-black/10"
|
||||
>
|
||||
<p
|
||||
class="text-sm text-[var(--wire-color-text-muted)]"
|
||||
>
|
||||
You can place forms, confirmation messages, account details,
|
||||
images, or any other component inside this modal.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="mt-6 flex flex-col-reverse gap-3 sm:flex-row sm:justify-end"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-xl border border-white/20 bg-white/10 px-5 py-2.5 font-medium text-[var(--wire-color-text)] transition hover:bg-white/20 dark:border-white/10 dark:bg-black/20 dark:hover:bg-black/30"
|
||||
@click="isOpen = false"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-xl bg-[var(--wire-color-primary)] px-5 py-2.5 font-semibold text-white shadow-lg transition hover:opacity-90"
|
||||
@click="confirmed()"
|
||||
>
|
||||
Continue
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
}
|
||||
|
||||
style {
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,438 @@
|
||||
// Modal demo page. Route: /modal
|
||||
//
|
||||
// Five real-world use cases for the shared @wrnexus/ui Modal component — each
|
||||
// one exercises a different combination of props (color, variant, size,
|
||||
// scrollable, destructive, showFooter, confirmDisabled) instead of just
|
||||
// showing the same modal five times.
|
||||
//
|
||||
// Notifications go through the shared <Toaster /> mounted at the bottom of
|
||||
// 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"
|
||||
state deletedNote = ""
|
||||
|
||||
functions {
|
||||
function onConfirmed() {
|
||||
alert("User Confirmed")
|
||||
function onSaveConfirmed() {
|
||||
toast.success("Changes saved", { title: "Draft published" })
|
||||
}
|
||||
|
||||
function onDeleteConfirmed() {
|
||||
toast.error("Account deleted", { title: "Gone for good", duration: 6000 })
|
||||
}
|
||||
|
||||
// Toast actions and component state.
|
||||
//
|
||||
// An action callback runs LONG AFTER the function that created it
|
||||
// returned, and a client function only flushes its state when its body
|
||||
// ends -- so assigning noteText straight from the callback would write to
|
||||
// a dead local and vanish. The callback dispatches an event instead, and
|
||||
// the declarative @window binding on the actions row below handles it as
|
||||
// a fresh invocation with live state. Anything that is not component
|
||||
// state (calling toast(), fetch, navigation) works from the callback
|
||||
// directly.
|
||||
function onDeleteNote() {
|
||||
deletedNote = noteText
|
||||
noteText = ""
|
||||
toast("Note deleted", {
|
||||
title: "Deleted",
|
||||
actionLabel: "Undo",
|
||||
onAction: function () {
|
||||
window.dispatchEvent(new CustomEvent("demo:restore-note"))
|
||||
},
|
||||
duration: 8000
|
||||
})
|
||||
}
|
||||
|
||||
function restoreNote() {
|
||||
if (!deletedNote) {
|
||||
return
|
||||
}
|
||||
noteText = deletedNote
|
||||
deletedNote = ""
|
||||
toast.success("Note restored")
|
||||
}
|
||||
|
||||
// Two actions. The second is styled destructive and keeps the toast open
|
||||
// (dismiss: false) so the choice stays on screen until it is resolved.
|
||||
function onConflict() {
|
||||
toast.warning("This note changed in another tab", {
|
||||
title: "Conflict",
|
||||
actions: [
|
||||
{
|
||||
label: "Keep mine",
|
||||
onClick: function () { toast.success("Kept your version") }
|
||||
},
|
||||
{
|
||||
label: "Discard",
|
||||
tone: "danger",
|
||||
dismiss: false,
|
||||
onClick: function () {
|
||||
window.dispatchEvent(new CustomEvent("demo:discard-note"))
|
||||
}
|
||||
}
|
||||
],
|
||||
duration: 0
|
||||
})
|
||||
}
|
||||
|
||||
function discardNote() {
|
||||
deletedNote = noteText
|
||||
noteText = ""
|
||||
toast.error("Your changes were discarded")
|
||||
}
|
||||
|
||||
// Tones carry their own icon and colour, but any toast can override it.
|
||||
// The class lives in this page source, so the app Tailwind/iconify build
|
||||
// sees it and emits the rule -- a class from inside @wrnexus/ui would not
|
||||
// be scanned, which is why the packaged defaults are inline SVG.
|
||||
function onTrialNotice() {
|
||||
toast.warning("Your trial ends in 3 days", {
|
||||
title: "Heads up",
|
||||
icon: "icon-[lucide--hourglass]",
|
||||
actionLabel: "Upgrade",
|
||||
duration: 6000
|
||||
})
|
||||
}
|
||||
|
||||
function onTermsConfirmed() {
|
||||
toast.success("Thanks, you are all set")
|
||||
}
|
||||
|
||||
// The invite form is a real <form data-schema="invite">, so
|
||||
// @wrnexus/validation owns the rules: it validates on input/blur/submit
|
||||
// against app/schemas/invite.ts, writes each message into the
|
||||
// [data-error] slot the Input renders, and only then POSTs to /api/invite — which parses
|
||||
// the very same schema server-side. These two handlers just react to the
|
||||
// outcome events the validator emits.
|
||||
function onInviteSent(event) {
|
||||
toast.success("Invite sent to " + event.detail.email, { title: "Invitation" })
|
||||
// Close the modal the form lives in. The event bubbles from the form up
|
||||
// to the Modal root, which listens for it -- so the page never needs a
|
||||
// handle on the modal or a way to reach its internal state.
|
||||
event.target.dispatchEvent(
|
||||
new CustomEvent("wrnexus:modal:close", { bubbles: true })
|
||||
)
|
||||
}
|
||||
|
||||
function onInviteFailed(event) {
|
||||
toast.error(event.detail.message || "Could not send the invite", {
|
||||
title: "Invite failed"
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
style {
|
||||
.modal-demo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.modal-demo-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.6rem;
|
||||
padding: 1.1rem;
|
||||
background: var(--wire-color-surface-raised);
|
||||
border: 1px solid var(--wire-color-border);
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
/*
|
||||
* Direct children only. A modal mounts *inside* its card, and its panel is
|
||||
* position: fixed but still a DOM descendant -- so a plain
|
||||
* `.modal-demo-card p` also styles the copy inside the open modal, which
|
||||
* repainted the destructive modal body muted grey on its red panel. The
|
||||
* card is describing its own blurb here, not everything a component it
|
||||
* hosts happens to render.
|
||||
*/
|
||||
.modal-demo-card > h2 {
|
||||
margin: 0;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.modal-demo-card > p {
|
||||
margin: 0;
|
||||
color: var(--wire-color-text-muted);
|
||||
font-size: 0.82rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.modal-demo-form {
|
||||
display: grid;
|
||||
gap: 0.9rem;
|
||||
}
|
||||
|
||||
.modal-demo-form-error {
|
||||
margin: 0;
|
||||
color: var(--wire-color-danger);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.modal-demo-form-error:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.modal-demo-submit {
|
||||
appearance: none;
|
||||
min-height: 2.55rem;
|
||||
padding: 0.65rem 1rem;
|
||||
color: var(--wire-color-secondary-contrast);
|
||||
background: var(--wire-color-secondary);
|
||||
border: 0;
|
||||
border-radius: 0.78rem;
|
||||
font: inherit;
|
||||
font-size: 0.83rem;
|
||||
font-weight: 650;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal-demo-toast-demos {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
margin-top: 1.75rem;
|
||||
color: var(--wire-color-text-muted);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.modal-demo-note {
|
||||
color: var(--wire-color-text-muted);
|
||||
}
|
||||
|
||||
.modal-demo-note em {
|
||||
color: var(--wire-color-text);
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.modal-demo-toast-demos button {
|
||||
appearance: none;
|
||||
padding: 0.4rem 0.7rem;
|
||||
color: var(--wire-color-text);
|
||||
background: var(--wire-color-surface-raised);
|
||||
border: 1px solid var(--wire-color-border);
|
||||
border-radius: 0.6rem;
|
||||
font: inherit;
|
||||
font-size: 0.78rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal-demo-submit:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.modal-demo-terms {
|
||||
max-height: 12rem;
|
||||
overflow-y: auto;
|
||||
padding-right: 0.5rem;
|
||||
color: var(--wire-color-text-muted);
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
|
||||
view {
|
||||
<h1>Modal</h1>
|
||||
<p>
|
||||
Five common use cases for the shared <code>@wrnexus/ui</code>
|
||||
<code>Modal</code> component — themed, accessible, and responsive out
|
||||
of the box.
|
||||
</p>
|
||||
|
||||
<div class="modal-demo-grid">
|
||||
|
||||
<!-- 1. Info-only modal: no footer, dismiss via close button/backdrop/Escape -->
|
||||
<div class="modal-demo-card">
|
||||
<h2>Info modal</h2>
|
||||
<p>No footer, no actions — just content you dismiss.</p>
|
||||
<Modal
|
||||
onConfirmed={onConfirmed}
|
||||
title="What's new"
|
||||
description="Release notes for this build."
|
||||
triggerLabel="View release notes"
|
||||
color="info"
|
||||
variant="soft"
|
||||
size="sm"
|
||||
showFooter={false}
|
||||
>
|
||||
<p>
|
||||
This release adds the shared <code>@wrnexus/ui</code>
|
||||
<code>Modal</code> component to every app, fully themed to your
|
||||
palette and dark/light mode.
|
||||
</p>
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
<!-- 2. Standard confirm/cancel modal -->
|
||||
<div class="modal-demo-card">
|
||||
<h2>Confirmation</h2>
|
||||
<p>Cancel/confirm with a primary action.</p>
|
||||
<Modal
|
||||
title="Save changes?"
|
||||
description="You have unsaved edits on this page."
|
||||
triggerLabel="Save changes"
|
||||
color="primary"
|
||||
variant="soft"
|
||||
size="md"
|
||||
cancelLabel="Discard"
|
||||
confirmLabel="Save"
|
||||
closeOnConfirm={true}
|
||||
@confirm="onSaveConfirmed()"
|
||||
>
|
||||
<p>Saving will overwrite the previously published version.</p>
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
<!-- 3. Destructive confirmation -->
|
||||
<div class="modal-demo-card">
|
||||
<h2>Destructive action</h2>
|
||||
<p>Danger styling for irreversible actions.</p>
|
||||
<Modal
|
||||
title="Delete account"
|
||||
description="This action cannot be undone."
|
||||
triggerLabel="Delete account"
|
||||
color="danger"
|
||||
variant="solid"
|
||||
destructive={true}
|
||||
cancelLabel="Keep account"
|
||||
confirmLabel="Delete account"
|
||||
closeOnConfirm={true}
|
||||
@confirm="onDeleteConfirmed()"
|
||||
>
|
||||
<p>
|
||||
All of your data, projects, and billing history will be
|
||||
permanently removed.
|
||||
</p>
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
<!-- 4. Form modal. The form owns its own submit button, so the Modal
|
||||
footer is turned off: @wrnexus/validation binds to form[data-schema]
|
||||
and drives the whole flow (validate on input/blur, block submit
|
||||
while invalid, POST to /api/invite, emit wire:success or
|
||||
wire:error). Nothing here re-implements a rule that
|
||||
app/schemas/invite.ts already states. -->
|
||||
<div class="modal-demo-card">
|
||||
<h2>Form modal</h2>
|
||||
<p>Real Input fields, validated by the shared schema on both sides.</p>
|
||||
<Modal
|
||||
title="Invite a teammate"
|
||||
description="They will get an email with a link to join."
|
||||
triggerLabel="Invite teammate"
|
||||
color="secondary"
|
||||
variant="soft"
|
||||
size="md"
|
||||
showFooter={false}
|
||||
>
|
||||
<form
|
||||
class="modal-demo-form"
|
||||
data-schema="invite"
|
||||
method="post"
|
||||
action="/api/invite"
|
||||
@wire:success="onInviteSent(event)"
|
||||
@wire:error="onInviteFailed(event)"
|
||||
>
|
||||
<Input
|
||||
name="email"
|
||||
type="email"
|
||||
label="Email address"
|
||||
placeholder="teammate@company.com"
|
||||
autocomplete="email"
|
||||
icon="icon-[lucide--mail]"
|
||||
required={true}
|
||||
helperText="They will get a one-time link to join your workspace."
|
||||
/>
|
||||
<Input
|
||||
name="message"
|
||||
label="Note (optional)"
|
||||
placeholder="Looking forward to working with you"
|
||||
maxlength="140"
|
||||
icon="icon-[lucide--message-square]"
|
||||
cornerHint="Optional"
|
||||
/>
|
||||
<p class="modal-demo-form-error" data-error="_form"></p>
|
||||
<button type="submit" class="modal-demo-submit">Send invite</button>
|
||||
</form>
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
<!-- 5. Large scrollable content, backdrop-click disabled -->
|
||||
<div class="modal-demo-card">
|
||||
<h2>Scrollable content</h2>
|
||||
<p>Larger size, scrollable body, no accidental backdrop dismiss.</p>
|
||||
<Modal
|
||||
title="Terms of service"
|
||||
description="Please review before continuing."
|
||||
triggerLabel="Read terms"
|
||||
color="primary"
|
||||
variant="outline"
|
||||
size="lg"
|
||||
scrollable={true}
|
||||
closeOnBackdrop={false}
|
||||
cancelLabel=""
|
||||
confirmLabel="I agree"
|
||||
closeOnConfirm={true}
|
||||
@confirm="onTermsConfirmed()"
|
||||
>
|
||||
<div class="modal-demo-terms">
|
||||
<p>
|
||||
This is placeholder terms-of-service copy used to demonstrate a
|
||||
tall, scrollable modal body. In a real app this slot would hold
|
||||
your actual legal text.
|
||||
</p>
|
||||
<p>
|
||||
1. You agree to use this framework responsibly. 2. Components
|
||||
are provided as-is, themed to your app's palette. 3. Scrollable
|
||||
modals keep the header and footer fixed while the body scrolls
|
||||
independently, so long content never breaks the layout.
|
||||
</p>
|
||||
<p>
|
||||
4. Clicking the backdrop will not close this particular modal —
|
||||
that is controlled by the <code>closeOnBackdrop</code> prop,
|
||||
set to <code>false</code> here on purpose so an explicit choice
|
||||
is required. 5. Pressing Escape still works, since
|
||||
<code>closeOnEscape</code> defaults to <code>true</code>.
|
||||
</p>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<p class="modal-demo-toast-demos">
|
||||
Toast tones:
|
||||
<button type="button" @click='toast.success("Everything went through", { title: "Success" })'>success</button>
|
||||
<button type="button" @click='toast.error("That did not work", { title: "Error" })'>danger</button>
|
||||
<button type="button" @click='onTrialNotice()'>warning + custom icon</button>
|
||||
<button type="button" @click='toast.info("Just so you know")'>info</button>
|
||||
</p>
|
||||
|
||||
<p
|
||||
class="modal-demo-toast-demos"
|
||||
@window:demo:restore-note="restoreNote()"
|
||||
@window:demo:discard-note="discardNote()"
|
||||
>
|
||||
Toast actions:
|
||||
<button type="button" @click="onDeleteNote()">delete note (undo)</button>
|
||||
<button type="button" @click="onConflict()">conflict (two actions)</button>
|
||||
<span class="modal-demo-note">Note: <em data-show="noteText">{noteText}</em><em data-show="!noteText">(deleted)</em></span>
|
||||
</p>
|
||||
|
||||
<!-- One host per page (a real app puts this in the layout). Every
|
||||
toast() call anywhere on the page lands here. -->
|
||||
<Toaster position="bottom-right" duration={4500} max={4} />
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
// DataTable demo. Route: /table
|
||||
//
|
||||
// Two tables: one over a local array, one backed by /api/accounts.
|
||||
//
|
||||
// The remote table does not receive a fetch function. Component props travel
|
||||
// as HTML attributes, so a function passed that way arrives as its own source
|
||||
// text rather than something callable. Instead the table emits a `request`
|
||||
// output and we answer it by dispatching the rows back with the same id.
|
||||
page TablePage {
|
||||
|
||||
state localRows = [
|
||||
{ id: 1, name: "Northwind", plan: "Scale", seats: 42, renewsOn: "2026-02-09" },
|
||||
{ id: 2, name: "Acme Industrial", plan: "Team", seats: 8, renewsOn: "2026-01-10" },
|
||||
{ id: 3, name: "Globex", plan: "Enterprise", seats: 210, renewsOn: "2025-12-24" },
|
||||
{ id: 4, name: "Initech", plan: "Starter", seats: 5, renewsOn: "2026-03-02" },
|
||||
{ id: 5, name: "Umbrella", plan: "Scale", seats: 96, renewsOn: "2026-01-31" }
|
||||
]
|
||||
|
||||
state localColumns = [
|
||||
{ key: "name", label: "Account", sortable: true },
|
||||
{ key: "plan", label: "Plan", sortable: true },
|
||||
{ key: "seats", label: "Seats", align: "end", sortable: true, type: "number" },
|
||||
{ key: "renewsOn", label: "Renews", align: "end", sortable: true, type: "date" }
|
||||
]
|
||||
|
||||
state remoteColumns = [
|
||||
{ key: "name", label: "Account", sortable: true },
|
||||
{ key: "owner", label: "Owner", sortable: true },
|
||||
{ key: "plan", label: "Plan", align: "center" },
|
||||
{ key: "seats", label: "Seats", align: "end", sortable: true, type: "number" },
|
||||
{ key: "renewsOn", label: "Renews", align: "end", sortable: true, type: "date" }
|
||||
]
|
||||
|
||||
state tableActions = []
|
||||
state lastEvent = "none yet"
|
||||
|
||||
functions {
|
||||
// The table asks for rows through its `request` output; we answer by
|
||||
// dispatching the result back with the same instanceId. It cannot simply
|
||||
// call a function we hand it: component props travel as HTML attributes,
|
||||
// so a function would arrive as its own source text.
|
||||
function loadAccounts(payload) {
|
||||
var id = payload.instanceId
|
||||
var search = new URLSearchParams({
|
||||
page: String(payload.page),
|
||||
pageSize: String(payload.pageSize),
|
||||
query: payload.query || "",
|
||||
sortKey: payload.sortKey || "",
|
||||
sortDirection: payload.sortDirection || "asc"
|
||||
})
|
||||
|
||||
fetch("/api/accounts?" + search.toString())
|
||||
.then(function (response) {
|
||||
if (!response.ok) {
|
||||
throw new Error("Request failed with status " + response.status)
|
||||
}
|
||||
return response.json()
|
||||
})
|
||||
.then(function (data) {
|
||||
window.dispatchEvent(new CustomEvent("wrnexus:datatable:rows", {
|
||||
detail: { instanceId: id, rows: data.rows, total: data.total }
|
||||
}))
|
||||
})
|
||||
.catch(function (error) {
|
||||
window.dispatchEvent(new CustomEvent("wrnexus:datatable:error", {
|
||||
detail: { instanceId: id, message: error.message }
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
function onTableChange(payload) {
|
||||
lastEvent =
|
||||
"page " + payload.page + " of " + Math.max(1, Math.ceil(payload.total / payload.pageSize)) +
|
||||
" · " + payload.total + " records" +
|
||||
(payload.sortKey ? " · sorted by " + payload.sortKey + " " + payload.sortDirection : "")
|
||||
}
|
||||
|
||||
function onTableAction(payload) {
|
||||
toast.info(payload.id + " on " + payload.rows.length + " record(s)")
|
||||
}
|
||||
}
|
||||
|
||||
style {
|
||||
.table-demo-section {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.table-demo-note {
|
||||
margin: 0;
|
||||
color: var(--wire-color-text-muted);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.table-demo-note code {
|
||||
color: var(--wire-color-text);
|
||||
}
|
||||
}
|
||||
|
||||
view {
|
||||
<h1>Data table</h1>
|
||||
<p>
|
||||
Sorting, filtering, paging and selection over a local array, and the same
|
||||
component driven by an API through its <code>request</code> output.
|
||||
</p>
|
||||
|
||||
<section class="table-demo-section">
|
||||
<h2>Local array</h2>
|
||||
<p class="table-demo-note">
|
||||
Everything is derived in the browser. The Renews column declares
|
||||
<code>type: "date"</code>, so it sorts chronologically rather than
|
||||
alphabetically.
|
||||
</p>
|
||||
|
||||
<DataTable
|
||||
columns={localColumns}
|
||||
rows={localRows}
|
||||
actions={tableActions}
|
||||
caption="Accounts"
|
||||
pageSize={5}
|
||||
selectable={true}
|
||||
gridlines="grid"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section class="table-demo-section">
|
||||
<h2>Loaded from an API</h2>
|
||||
<p class="table-demo-note">
|
||||
With <code>remote</code> the table emits a <code>request</code> for each
|
||||
view change and waits to be handed rows back. Sorting and searching are
|
||||
done by <code>/api/accounts</code> — the table never sees the other pages.
|
||||
</p>
|
||||
|
||||
<DataTable
|
||||
columns={remoteColumns}
|
||||
remote={true}
|
||||
@request="loadAccounts(payload)"
|
||||
caption="Accounts (server side)"
|
||||
description="22 records, paged by the API."
|
||||
pageSize={6}
|
||||
pageSizes={[6, 12, 22]}
|
||||
paginationStyle="numbered"
|
||||
searchPlaceholder="Search accounts"
|
||||
stickyFirstColumn={true}
|
||||
selectable={true}
|
||||
@change="onTableChange(payload)"
|
||||
@action="onTableAction(payload)"
|
||||
/>
|
||||
|
||||
<p class="table-demo-note">Last change event: <strong>{lastEvent}</strong></p>
|
||||
</section>
|
||||
|
||||
<Toaster position="bottom-right" />
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ export interface Routes {
|
||||
"/platform-showcase": Record<string, never>;
|
||||
"/reactive": Record<string, never>;
|
||||
"/server-actions": Record<string, never>;
|
||||
"/table": Record<string, never>;
|
||||
"/test": Record<string, never>;
|
||||
"/ui": Record<string, never>;
|
||||
}
|
||||
@@ -35,6 +36,7 @@ export interface RouteNames {
|
||||
"platform.showcase": "/platform-showcase";
|
||||
"reactive": "/reactive";
|
||||
"server.actions": "/server-actions";
|
||||
"table": "/table";
|
||||
"test": "/test";
|
||||
"ui": "/ui";
|
||||
}
|
||||
@@ -123,6 +125,7 @@ export function route<N extends RouteName>(
|
||||
"platform.showcase": "/platform-showcase",
|
||||
"reactive": "/reactive",
|
||||
"server.actions": "/server-actions",
|
||||
"table": "/table",
|
||||
"test": "/test",
|
||||
"ui": "/ui"
|
||||
} as Record<RouteName, RoutePath>;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// One schema, two consumers: the browser validates the invite form against
|
||||
// the descriptor emitted from this file, and /api/invite parses the same
|
||||
// schema server-side. Keeping a single definition is the point — a rule added
|
||||
// here tightens both sides at once, and the client can never drift into
|
||||
// accepting something the server rejects.
|
||||
import { v } from "@wrnexus/validation";
|
||||
|
||||
export default v.object({
|
||||
email: v.string().trim().email("Enter a valid email address"),
|
||||
message: v.string().trim().max(140, "Keep the note under 140 characters").optional(),
|
||||
});
|
||||
@@ -39,7 +39,7 @@ layout Showcase {
|
||||
</section>
|
||||
<section class="docs-directory-group">
|
||||
<a class="docs-directory-heading" href="/core">Core</a>
|
||||
<div><a data-docs-component-link href="/components/auth-form">Auth Form</a><a data-docs-component-link href="/components/auth-split-layout">Auth Split Layout</a><a data-docs-component-link href="/components/portal-dashboard">Portal Dashboard</a><a data-docs-component-link href="/components/preference-switcher">Preference Switcher</a></div>
|
||||
<div><a data-docs-component-link href="/components/auth-form">Auth Form</a><a data-docs-component-link href="/components/auth-split-layout">Auth Split Layout</a><a data-docs-component-link href="/components/portal-dashboard">Portal Dashboard</a><a data-docs-component-link href="/components/preference-switcher">Preference Switcher</a><a data-docs-component-link href="/components/toaster">Toaster</a></div>
|
||||
</section>
|
||||
<section class="docs-directory-group">
|
||||
<a class="docs-directory-heading" href="/data">Data</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/data-table">Data Table</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/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>
|
||||
</section>
|
||||
<section class="docs-directory-group">
|
||||
<a class="docs-directory-heading" href="/layout">Layout</a>
|
||||
@@ -71,7 +71,7 @@ layout Showcase {
|
||||
</section>
|
||||
<section class="docs-directory-group">
|
||||
<a class="docs-directory-heading" href="/tables">Tables</a>
|
||||
<div><a data-docs-component-link href="/components/table">Table</a></div>
|
||||
<div><a data-docs-component-link href="/components/data-table">Data Table</a></div>
|
||||
</section>
|
||||
</nav>
|
||||
<p class="docs-directory-empty" data-docs-empty>No components found.</p>
|
||||
|
||||
@@ -45,7 +45,7 @@ page AccordionDetail {
|
||||
<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><Accordion
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "accordion-0-1",
|
||||
"key": "accordion-0-1",
|
||||
"value": "primary",
|
||||
@@ -74,31 +74,31 @@ page AccordionDetail {
|
||||
"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": "accordion-0-2",
|
||||
"key": "accordion-0-2",
|
||||
"value": "secondary",
|
||||
@@ -127,33 +127,33 @@ page AccordionDetail {
|
||||
"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>
|
||||
]'
|
||||
defaultOpen='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "accordion-0-1",
|
||||
"key": "accordion-0-1",
|
||||
"value": "primary",
|
||||
@@ -182,31 +182,31 @@ page AccordionDetail {
|
||||
"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": "accordion-0-2",
|
||||
"key": "accordion-0-2",
|
||||
"value": "secondary",
|
||||
@@ -235,30 +235,30 @@ page AccordionDetail {
|
||||
"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>
|
||||
@@ -503,7 +503,7 @@ page AccordionDetail {
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
<pre><code><Accordion
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "accordion-0-1",
|
||||
"key": "accordion-0-1",
|
||||
"value": "primary",
|
||||
@@ -532,31 +532,31 @@ page AccordionDetail {
|
||||
"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": "accordion-0-2",
|
||||
"key": "accordion-0-2",
|
||||
"value": "secondary",
|
||||
@@ -585,33 +585,33 @@ page AccordionDetail {
|
||||
"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>
|
||||
]'
|
||||
defaultOpen='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "accordion-0-1",
|
||||
"key": "accordion-0-1",
|
||||
"value": "primary",
|
||||
@@ -640,31 +640,31 @@ page AccordionDetail {
|
||||
"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": "accordion-0-2",
|
||||
"key": "accordion-0-2",
|
||||
"value": "secondary",
|
||||
@@ -693,30 +693,30 @@ page AccordionDetail {
|
||||
"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>
|
||||
@@ -739,7 +739,7 @@ page AccordionDetail {
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "accordion-1-1",
|
||||
"key": "accordion-1-1",
|
||||
"value": "primary",
|
||||
@@ -768,31 +768,31 @@ page AccordionDetail {
|
||||
"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": "accordion-1-2",
|
||||
"key": "accordion-1-2",
|
||||
"value": "secondary",
|
||||
@@ -821,33 +821,33 @@ page AccordionDetail {
|
||||
"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>
|
||||
]'
|
||||
defaultOpen='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "accordion-1-1",
|
||||
"key": "accordion-1-1",
|
||||
"value": "primary",
|
||||
@@ -876,31 +876,31 @@ page AccordionDetail {
|
||||
"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": "accordion-1-2",
|
||||
"key": "accordion-1-2",
|
||||
"value": "secondary",
|
||||
@@ -929,30 +929,30 @@ page AccordionDetail {
|
||||
"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>
|
||||
]'
|
||||
/></code></pre>
|
||||
</section>
|
||||
@@ -975,7 +975,7 @@ page AccordionDetail {
|
||||
size="lg"
|
||||
variant="success"
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "accordion-2-1",
|
||||
"key": "accordion-2-1",
|
||||
"value": "primary",
|
||||
@@ -1004,31 +1004,31 @@ page AccordionDetail {
|
||||
"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": "accordion-2-2",
|
||||
"key": "accordion-2-2",
|
||||
"value": "secondary",
|
||||
@@ -1057,33 +1057,33 @@ page AccordionDetail {
|
||||
"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>
|
||||
]'
|
||||
defaultOpen='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "accordion-2-1",
|
||||
"key": "accordion-2-1",
|
||||
"value": "primary",
|
||||
@@ -1112,31 +1112,31 @@ page AccordionDetail {
|
||||
"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": "accordion-2-2",
|
||||
"key": "accordion-2-2",
|
||||
"value": "secondary",
|
||||
@@ -1165,30 +1165,30 @@ page AccordionDetail {
|
||||
"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>
|
||||
]'
|
||||
/></code></pre>
|
||||
</section>
|
||||
@@ -1198,21 +1198,21 @@ page AccordionDetail {
|
||||
@change='console.log(payload)'
|
||||
@open='console.log(payload)'
|
||||
@close='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 { registerOutputHandler } from "@wrnexus/csr/outputs"
|
||||
/></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=\"Accordion\"], [data-component=\"Accordion\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "change", (payload) => {
|
||||
if (component) registerOutputHandler(component, "change", (payload) => <span>{</span>
|
||||
console.log("change", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "open", (payload) => {
|
||||
if (component) registerOutputHandler(component, "open", (payload) => <span>{</span>
|
||||
console.log("open", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "close", (payload) => {
|
||||
if (component) registerOutputHandler(component, "close", (payload) => <span>{</span>
|
||||
console.log("close", payload)
|
||||
})</code></pre></section></div></div></section>
|
||||
<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>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><tr><td><code>id</code></td><td>string</td><td><code>"accordion"</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>defaultOpen</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>multiple</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>alwaysOpen</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>indicator</code></td><td>string</td><td><code>"plus"</code></td><td>No</td></tr><tr><td><code>indicatorPosition</code></td><td>string</td><td><code>"start"</code></td><td>No</td></tr><tr><td><code>showIndicator</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>bordered</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>separated</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>flush</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>contentItalic</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
</main>
|
||||
<aside class="detail-aside">
|
||||
|
||||
@@ -37,7 +37,7 @@ page AdvancedDatePickerDetail {
|
||||
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",
|
||||
@@ -66,31 +66,31 @@ page AdvancedDatePickerDetail {
|
||||
"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",
|
||||
@@ -119,30 +119,30 @@ page AdvancedDatePickerDetail {
|
||||
"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>
|
||||
@@ -282,7 +282,7 @@ page AdvancedDatePickerDetail {
|
||||
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",
|
||||
@@ -311,31 +311,31 @@ page AdvancedDatePickerDetail {
|
||||
"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",
|
||||
@@ -364,30 +364,30 @@ page AdvancedDatePickerDetail {
|
||||
"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>
|
||||
@@ -411,7 +411,7 @@ page AdvancedDatePickerDetail {
|
||||
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",
|
||||
@@ -440,31 +440,31 @@ page AdvancedDatePickerDetail {
|
||||
"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",
|
||||
@@ -493,30 +493,30 @@ page AdvancedDatePickerDetail {
|
||||
"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>
|
||||
@@ -541,7 +541,7 @@ page AdvancedDatePickerDetail {
|
||||
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",
|
||||
@@ -570,31 +570,31 @@ page AdvancedDatePickerDetail {
|
||||
"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",
|
||||
@@ -623,30 +623,30 @@ page AdvancedDatePickerDetail {
|
||||
"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>
|
||||
@@ -659,29 +659,29 @@ page AdvancedDatePickerDetail {
|
||||
@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 { registerOutputHandler } from "@wrnexus/csr/outputs"
|
||||
/></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) => {
|
||||
if (component) registerOutputHandler(component, "input", (payload) => <span>{</span>
|
||||
console.log("input", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "change", (payload) => {
|
||||
if (component) registerOutputHandler(component, "change", (payload) => <span>{</span>
|
||||
console.log("change", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "open", (payload) => {
|
||||
if (component) registerOutputHandler(component, "open", (payload) => <span>{</span>
|
||||
console.log("open", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "close", (payload) => {
|
||||
if (component) registerOutputHandler(component, "close", (payload) => <span>{</span>
|
||||
console.log("close", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "clear", (payload) => {
|
||||
if (component) registerOutputHandler(component, "clear", (payload) => <span>{</span>
|
||||
console.log("clear", payload)
|
||||
})</code></pre></section></div></div></section>
|
||||
<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">
|
||||
|
||||
@@ -37,7 +37,7 @@ page AdvancedRangeSliderDetail {
|
||||
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",
|
||||
@@ -66,31 +66,31 @@ page AdvancedRangeSliderDetail {
|
||||
"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",
|
||||
@@ -119,30 +119,30 @@ page AdvancedRangeSliderDetail {
|
||||
"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>
|
||||
@@ -282,7 +282,7 @@ page AdvancedRangeSliderDetail {
|
||||
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",
|
||||
@@ -311,31 +311,31 @@ page AdvancedRangeSliderDetail {
|
||||
"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",
|
||||
@@ -364,30 +364,30 @@ page AdvancedRangeSliderDetail {
|
||||
"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>
|
||||
@@ -411,7 +411,7 @@ page AdvancedRangeSliderDetail {
|
||||
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",
|
||||
@@ -440,31 +440,31 @@ page AdvancedRangeSliderDetail {
|
||||
"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",
|
||||
@@ -493,30 +493,30 @@ page AdvancedRangeSliderDetail {
|
||||
"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>
|
||||
@@ -541,7 +541,7 @@ page AdvancedRangeSliderDetail {
|
||||
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",
|
||||
@@ -570,31 +570,31 @@ page AdvancedRangeSliderDetail {
|
||||
"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",
|
||||
@@ -623,30 +623,30 @@ page AdvancedRangeSliderDetail {
|
||||
"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>
|
||||
@@ -658,25 +658,25 @@ page AdvancedRangeSliderDetail {
|
||||
@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 { registerOutputHandler } from "@wrnexus/csr/outputs"
|
||||
/></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) => {
|
||||
if (component) registerOutputHandler(component, "input", (payload) => <span>{</span>
|
||||
console.log("input", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "change", (payload) => {
|
||||
if (component) registerOutputHandler(component, "change", (payload) => <span>{</span>
|
||||
console.log("change", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "start", (payload) => {
|
||||
if (component) registerOutputHandler(component, "start", (payload) => <span>{</span>
|
||||
console.log("start", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "end", (payload) => {
|
||||
if (component) registerOutputHandler(component, "end", (payload) => <span>{</span>
|
||||
console.log("end", payload)
|
||||
})</code></pre></section></div></div></section>
|
||||
<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">
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -51,7 +51,7 @@ page AlertDetail {
|
||||
title="Alert example"
|
||||
description="A polished default alert for a modern application."
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "alert-0-1",
|
||||
"key": "alert-0-1",
|
||||
"value": "primary",
|
||||
@@ -80,31 +80,31 @@ page AlertDetail {
|
||||
"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": "alert-0-2",
|
||||
"key": "alert-0-2",
|
||||
"value": "secondary",
|
||||
@@ -133,33 +133,33 @@ page AlertDetail {
|
||||
"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>
|
||||
]'
|
||||
actions='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "alert-0-1",
|
||||
"key": "alert-0-1",
|
||||
"value": "primary",
|
||||
@@ -188,31 +188,31 @@ page AlertDetail {
|
||||
"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": "alert-0-2",
|
||||
"key": "alert-0-2",
|
||||
"value": "secondary",
|
||||
@@ -241,30 +241,30 @@ page AlertDetail {
|
||||
"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>
|
||||
]'
|
||||
showIcon="true"
|
||||
dismissible="true"
|
||||
@@ -518,7 +518,7 @@ page AlertDetail {
|
||||
title="Alert example"
|
||||
description="A polished default alert for a modern application."
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "alert-0-1",
|
||||
"key": "alert-0-1",
|
||||
"value": "primary",
|
||||
@@ -547,31 +547,31 @@ page AlertDetail {
|
||||
"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": "alert-0-2",
|
||||
"key": "alert-0-2",
|
||||
"value": "secondary",
|
||||
@@ -600,33 +600,33 @@ page AlertDetail {
|
||||
"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>
|
||||
]'
|
||||
actions='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "alert-0-1",
|
||||
"key": "alert-0-1",
|
||||
"value": "primary",
|
||||
@@ -655,31 +655,31 @@ page AlertDetail {
|
||||
"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": "alert-0-2",
|
||||
"key": "alert-0-2",
|
||||
"value": "secondary",
|
||||
@@ -708,30 +708,30 @@ page AlertDetail {
|
||||
"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>
|
||||
]'
|
||||
showIcon="true"
|
||||
dismissible="true"
|
||||
@@ -763,7 +763,7 @@ page AlertDetail {
|
||||
title="Compact Alert"
|
||||
description="A compact configuration for dashboards and dense product surfaces."
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "alert-1-1",
|
||||
"key": "alert-1-1",
|
||||
"value": "primary",
|
||||
@@ -792,31 +792,31 @@ page AlertDetail {
|
||||
"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": "alert-1-2",
|
||||
"key": "alert-1-2",
|
||||
"value": "secondary",
|
||||
@@ -845,33 +845,33 @@ page AlertDetail {
|
||||
"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>
|
||||
]'
|
||||
actions='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "alert-1-1",
|
||||
"key": "alert-1-1",
|
||||
"value": "primary",
|
||||
@@ -900,31 +900,31 @@ page AlertDetail {
|
||||
"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": "alert-1-2",
|
||||
"key": "alert-1-2",
|
||||
"value": "secondary",
|
||||
@@ -953,30 +953,30 @@ page AlertDetail {
|
||||
"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>
|
||||
]'
|
||||
showIcon="true"
|
||||
icon="icon-[lucide--arrow-right]"
|
||||
@@ -1010,7 +1010,7 @@ page AlertDetail {
|
||||
title="Advanced Alert"
|
||||
description="A richer configuration demonstrating additional content and hierarchy."
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "alert-2-1",
|
||||
"key": "alert-2-1",
|
||||
"value": "primary",
|
||||
@@ -1039,31 +1039,31 @@ page AlertDetail {
|
||||
"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": "alert-2-2",
|
||||
"key": "alert-2-2",
|
||||
"value": "secondary",
|
||||
@@ -1092,33 +1092,33 @@ page AlertDetail {
|
||||
"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>
|
||||
]'
|
||||
actions='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "alert-2-1",
|
||||
"key": "alert-2-1",
|
||||
"value": "primary",
|
||||
@@ -1147,31 +1147,31 @@ page AlertDetail {
|
||||
"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": "alert-2-2",
|
||||
"key": "alert-2-2",
|
||||
"value": "secondary",
|
||||
@@ -1200,30 +1200,30 @@ page AlertDetail {
|
||||
"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>
|
||||
]'
|
||||
showIcon="true"
|
||||
icon="icon-[lucide--trash-2]"
|
||||
@@ -1240,17 +1240,17 @@ page AlertDetail {
|
||||
<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><Alert
|
||||
@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 { registerOutputHandler } from "@wrnexus/csr/outputs"
|
||||
/></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=\"Alert\"], [data-component=\"Alert\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "dismiss", (payload) => {
|
||||
if (component) registerOutputHandler(component, "dismiss", (payload) => <span>{</span>
|
||||
console.log("dismiss", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "action", (payload) => {
|
||||
if (component) registerOutputHandler(component, "action", (payload) => <span>{</span>
|
||||
console.log("action", payload)
|
||||
})</code></pre></section></div></div></section>
|
||||
<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>"info"</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"soft"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>radius</code></td><td>string</td><td><code>"md"</code></td><td>No</td></tr><tr><td><code>shadow</code></td><td>string</td><td><code>"sm"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Alert"</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>actions</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>showIcon</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>icon</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>dismissible</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>dismissLabel</code></td><td>string</td><td><code>"Dismiss alert"</code></td><td>No</td></tr><tr><td><code>role</code></td><td>string</td><td><code>"alert"</code></td><td>No</td></tr><tr><td><code>live</code></td><td>string</td><td><code>"polite"</code></td><td>No</td></tr><tr><td><code>linkLabel</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>linkHref</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>actionLabel</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>actionHref</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>compact</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
</main>
|
||||
<aside class="detail-aside">
|
||||
|
||||
@@ -154,13 +154,13 @@ page AnnouncementBarDetail {
|
||||
</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><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><AnnouncementBar
|
||||
@dismiss='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 { registerOutputHandler } from "@wrnexus/csr/outputs"
|
||||
/></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=\"AnnouncementBar\"], [data-component=\"AnnouncementBar\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "dismiss", (payload) => {
|
||||
if (component) registerOutputHandler(component, "dismiss", (payload) => <span>{</span>
|
||||
console.log("dismiss", payload)
|
||||
})</code></pre></section></div></div></section>
|
||||
<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>badge</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>badgeIcon</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>message</code></td><td>string</td><td><code>"Announcement"</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>icon</code></td><td>string</td><td><code>"icon-[lucide--megaphone]"</code></td><td>No</td></tr><tr><td><code>actionLabel</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>actionHref</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>actionIcon</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>dismissible</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>dismissLabel</code></td><td>string</td><td><code>"Dismiss announcement"</code></td><td>No</td></tr><tr><td><code>sticky</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>compact</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>width</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>"soft"</code></td><td>No</td></tr><tr><td><code>role</code></td><td>string</td><td><code>"status"</code></td><td>No</td></tr><tr><td><code>live</code></td><td>string</td><td><code>"polite"</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">
|
||||
|
||||
@@ -132,29 +132,29 @@ page AuthFormDetail {
|
||||
@input='console.log(payload)'
|
||||
@focus='console.log(payload)'
|
||||
@blur='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 { registerOutputHandler } from "@wrnexus/csr/outputs"
|
||||
/></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=\"AuthForm\"], [data-component=\"AuthForm\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "submit", (payload) => {
|
||||
if (component) registerOutputHandler(component, "submit", (payload) => <span>{</span>
|
||||
console.log("submit", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "change", (payload) => {
|
||||
if (component) registerOutputHandler(component, "change", (payload) => <span>{</span>
|
||||
console.log("change", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "input", (payload) => {
|
||||
if (component) registerOutputHandler(component, "input", (payload) => <span>{</span>
|
||||
console.log("input", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "focus", (payload) => {
|
||||
if (component) registerOutputHandler(component, "focus", (payload) => <span>{</span>
|
||||
console.log("focus", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "blur", (payload) => {
|
||||
if (component) registerOutputHandler(component, "blur", (payload) => <span>{</span>
|
||||
console.log("blur", payload)
|
||||
})</code></pre></section></div></div></section>
|
||||
<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>mode</code></td><td>string</td><td><code>"sign-in"</code></td><td>No</td></tr><tr><td><code>action</code></td><td>string</td><td><code>"/api/auth/login"</code></td><td>No</td></tr><tr><td><code>method</code></td><td>string</td><td><code>"post"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Sign in"</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>returnTo</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>schema</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>showRemember</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>showName</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>submitLabel</code></td><td>string</td><td><code>"Continue"</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">
|
||||
|
||||
@@ -38,30 +38,30 @@ page AuthSplitLayoutDetail {
|
||||
title="Auth Split Layout example"
|
||||
description="A polished default auth split layout for a modern application."
|
||||
features='[
|
||||
{
|
||||
<span>{</span>
|
||||
"label": "Team members",
|
||||
"values": [
|
||||
"5",
|
||||
"Unlimited",
|
||||
"Unlimited"
|
||||
]
|
||||
},
|
||||
{
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Support",
|
||||
"values": [
|
||||
"Email",
|
||||
"Priority",
|
||||
"Dedicated"
|
||||
]
|
||||
},
|
||||
{
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Audit history",
|
||||
"values": [
|
||||
"7 days",
|
||||
"90 days",
|
||||
"Custom"
|
||||
]
|
||||
}
|
||||
<span>}</span>
|
||||
]'>
|
||||
<div data-slot="aside-extra">
|
||||
<div class="showcase-slot">aside-extra slot</div>
|
||||
@@ -125,30 +125,30 @@ page AuthSplitLayoutDetail {
|
||||
title="Auth Split Layout example"
|
||||
description="A polished default auth split layout for a modern application."
|
||||
features='[
|
||||
{
|
||||
<span>{</span>
|
||||
"label": "Team members",
|
||||
"values": [
|
||||
"5",
|
||||
"Unlimited",
|
||||
"Unlimited"
|
||||
]
|
||||
},
|
||||
{
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Support",
|
||||
"values": [
|
||||
"Email",
|
||||
"Priority",
|
||||
"Dedicated"
|
||||
]
|
||||
},
|
||||
{
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Audit history",
|
||||
"values": [
|
||||
"7 days",
|
||||
"90 days",
|
||||
"Custom"
|
||||
]
|
||||
}
|
||||
<span>}</span>
|
||||
]'>
|
||||
<div data-slot="aside-extra">
|
||||
<div class="showcase-slot">aside-extra slot</div>
|
||||
@@ -178,30 +178,30 @@ page AuthSplitLayoutDetail {
|
||||
title="Compact Auth Split Layout"
|
||||
description="A compact configuration for dashboards and dense product surfaces."
|
||||
features='[
|
||||
{
|
||||
<span>{</span>
|
||||
"label": "Team members",
|
||||
"values": [
|
||||
"5",
|
||||
"Unlimited",
|
||||
"Unlimited"
|
||||
]
|
||||
},
|
||||
{
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Support",
|
||||
"values": [
|
||||
"Email",
|
||||
"Priority",
|
||||
"Dedicated"
|
||||
]
|
||||
},
|
||||
{
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Audit history",
|
||||
"values": [
|
||||
"7 days",
|
||||
"90 days",
|
||||
"Custom"
|
||||
]
|
||||
}
|
||||
<span>}</span>
|
||||
]'>
|
||||
<div data-slot="aside-extra">
|
||||
<div class="showcase-slot">aside-extra slot</div>
|
||||
@@ -231,30 +231,30 @@ page AuthSplitLayoutDetail {
|
||||
title="Advanced Auth Split Layout"
|
||||
description="A richer configuration demonstrating additional content and hierarchy."
|
||||
features='[
|
||||
{
|
||||
<span>{</span>
|
||||
"label": "Team members",
|
||||
"values": [
|
||||
"5",
|
||||
"Unlimited",
|
||||
"Unlimited"
|
||||
]
|
||||
},
|
||||
{
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Support",
|
||||
"values": [
|
||||
"Email",
|
||||
"Priority",
|
||||
"Dedicated"
|
||||
]
|
||||
},
|
||||
{
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Audit history",
|
||||
"values": [
|
||||
"7 days",
|
||||
"90 days",
|
||||
"Custom"
|
||||
]
|
||||
}
|
||||
<span>}</span>
|
||||
]'>
|
||||
<div data-slot="aside-extra">
|
||||
<div class="showcase-slot">aside-extra slot</div>
|
||||
|
||||
@@ -40,7 +40,7 @@ page AvatarGroupDetail {
|
||||
<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><AvatarGroup
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "avatar-group-0-1",
|
||||
"key": "avatar-group-0-1",
|
||||
"value": "primary",
|
||||
@@ -69,31 +69,31 @@ page AvatarGroupDetail {
|
||||
"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": "avatar-group-0-2",
|
||||
"key": "avatar-group-0-2",
|
||||
"value": "secondary",
|
||||
@@ -122,30 +122,30 @@ page AvatarGroupDetail {
|
||||
"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>
|
||||
]'
|
||||
overflowLabel="Avatar Group"
|
||||
/></code></pre>
|
||||
@@ -284,7 +284,7 @@ page AvatarGroupDetail {
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
<pre><code><AvatarGroup
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "avatar-group-0-1",
|
||||
"key": "avatar-group-0-1",
|
||||
"value": "primary",
|
||||
@@ -313,31 +313,31 @@ page AvatarGroupDetail {
|
||||
"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": "avatar-group-0-2",
|
||||
"key": "avatar-group-0-2",
|
||||
"value": "secondary",
|
||||
@@ -366,30 +366,30 @@ page AvatarGroupDetail {
|
||||
"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>
|
||||
]'
|
||||
overflowLabel="Avatar Group"
|
||||
/></code></pre>
|
||||
@@ -411,7 +411,7 @@ page AvatarGroupDetail {
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
<pre><code><AvatarGroup
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "avatar-group-1-1",
|
||||
"key": "avatar-group-1-1",
|
||||
"value": "primary",
|
||||
@@ -440,31 +440,31 @@ page AvatarGroupDetail {
|
||||
"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": "avatar-group-1-2",
|
||||
"key": "avatar-group-1-2",
|
||||
"value": "secondary",
|
||||
@@ -493,30 +493,30 @@ page AvatarGroupDetail {
|
||||
"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"
|
||||
variant="secondary"
|
||||
@@ -540,7 +540,7 @@ page AvatarGroupDetail {
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
<pre><code><AvatarGroup
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "avatar-group-2-1",
|
||||
"key": "avatar-group-2-1",
|
||||
"value": "primary",
|
||||
@@ -569,31 +569,31 @@ page AvatarGroupDetail {
|
||||
"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": "avatar-group-2-2",
|
||||
"key": "avatar-group-2-2",
|
||||
"value": "secondary",
|
||||
@@ -622,30 +622,30 @@ page AvatarGroupDetail {
|
||||
"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"
|
||||
variant="success"
|
||||
@@ -656,13 +656,13 @@ page AvatarGroupDetail {
|
||||
</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>@overflow</code><span>Fires when the component emits the overflow 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><AvatarGroup
|
||||
@overflow='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 { registerOutputHandler } from "@wrnexus/csr/outputs"
|
||||
/></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=\"AvatarGroup\"], [data-component=\"AvatarGroup\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "overflow", (payload) => {
|
||||
if (component) registerOutputHandler(component, "overflow", (payload) => <span>{</span>
|
||||
console.log("overflow", payload)
|
||||
})</code></pre></section></div></div></section>
|
||||
<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>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>size</code></td><td>string</td><td><code>"md"</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>"solid"</code></td><td>No</td></tr><tr><td><code>shape</code></td><td>string</td><td><code>"circle"</code></td><td>No</td></tr><tr><td><code>layout</code></td><td>string</td><td><code>"stack"</code></td><td>No</td></tr><tr><td><code>maxVisible</code></td><td>number</td><td><code>4</code></td><td>No</td></tr><tr><td><code>columns</code></td><td>number</td><td><code>3</code></td><td>No</td></tr><tr><td><code>borderColor</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>showTooltips</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>overflowLabel</code></td><td>string</td><td><code>"Show remaining members"</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">
|
||||
|
||||
@@ -133,21 +133,21 @@ page AvatarDetail {
|
||||
@load='console.log(payload)'
|
||||
@error='console.log(payload)'
|
||||
@click='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 { registerOutputHandler } from "@wrnexus/csr/outputs"
|
||||
/></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=\"Avatar\"], [data-component=\"Avatar\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "load", (payload) => {
|
||||
if (component) registerOutputHandler(component, "load", (payload) => <span>{</span>
|
||||
console.log("load", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "error", (payload) => {
|
||||
if (component) registerOutputHandler(component, "error", (payload) => <span>{</span>
|
||||
console.log("error", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "click", (payload) => {
|
||||
if (component) registerOutputHandler(component, "click", (payload) => <span>{</span>
|
||||
console.log("click", payload)
|
||||
})</code></pre></section></div></div></section>
|
||||
<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>src</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>alt</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>initials</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>size</code></td><td>string</td><td><code>"md"</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>"solid"</code></td><td>No</td></tr><tr><td><code>shape</code></td><td>string</td><td><code>"circle"</code></td><td>No</td></tr><tr><td><code>status</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>statusLabel</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>statusPosition</code></td><td>string</td><td><code>"bottom"</code></td><td>No</td></tr><tr><td><code>badge</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>badgeIcon</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>badgeLabel</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>tooltip</code></td><td>string</td><td><code>""</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>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>loading</code></td><td>string</td><td><code>"lazy"</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">
|
||||
|
||||
@@ -143,13 +143,13 @@ page BadgeDetail {
|
||||
</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><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><Badge
|
||||
@dismiss='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 { registerOutputHandler } from "@wrnexus/csr/outputs"
|
||||
/></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=\"Badge\"], [data-component=\"Badge\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "dismiss", (payload) => {
|
||||
if (component) registerOutputHandler(component, "dismiss", (payload) => <span>{</span>
|
||||
console.log("dismiss", payload)
|
||||
})</code></pre></section></div></div></section>
|
||||
<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>label</code></td><td>string</td><td><code>"Badge"</code></td><td>No</td></tr><tr><td><code>size</code></td><td>string</td><td><code>"md"</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>"solid"</code></td><td>No</td></tr><tr><td><code>shape</code></td><td>string</td><td><code>"pill"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>icon</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>iconPosition</code></td><td>string</td><td><code>"start"</code></td><td>No</td></tr><tr><td><code>dot</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>dotOnly</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>dotLabel</code></td><td>string</td><td><code>"Status"</code></td><td>No</td></tr><tr><td><code>animated</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>avatarSrc</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>avatarAlt</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>dismissible</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>dismissLabel</code></td><td>string</td><td><code>"Remove badge"</code></td><td>No</td></tr><tr><td><code>truncate</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>maxWidth</code></td><td>string</td><td><code>"12rem"</code></td><td>No</td></tr><tr><td><code>anchorLabel</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>anchorIcon</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>placement</code></td><td>string</td><td><code>"inline"</code></td><td>No</td></tr><tr><td><code>anchorLabelText</code></td><td>string</td><td><code>"Badge anchor"</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
</main>
|
||||
<aside class="detail-aside">
|
||||
|
||||
@@ -40,7 +40,7 @@ page BreadcrumbDetail {
|
||||
<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><Breadcrumb
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "breadcrumb-0-1",
|
||||
"key": "breadcrumb-0-1",
|
||||
"value": "primary",
|
||||
@@ -69,31 +69,31 @@ page BreadcrumbDetail {
|
||||
"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": "breadcrumb-0-2",
|
||||
"key": "breadcrumb-0-2",
|
||||
"value": "secondary",
|
||||
@@ -122,30 +122,30 @@ page BreadcrumbDetail {
|
||||
"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>
|
||||
]'
|
||||
showHome="true"
|
||||
homeLabel="Breadcrumb"
|
||||
@@ -286,19 +286,19 @@ page BreadcrumbDetail {
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
<pre><code><Breadcrumb
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"label": "Home",
|
||||
"href": "/",
|
||||
"icon": "icon-[lucide--house]"
|
||||
},
|
||||
{
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Components",
|
||||
"href": "/base"
|
||||
},
|
||||
{
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Page header",
|
||||
"current": true
|
||||
}
|
||||
<span>}</span>
|
||||
]'
|
||||
showHome="true"
|
||||
homeLabel="Breadcrumb"
|
||||
@@ -324,19 +324,19 @@ page BreadcrumbDetail {
|
||||
<pre><code><Breadcrumb
|
||||
label="Compact example"
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"label": "Home",
|
||||
"href": "/",
|
||||
"icon": "icon-[lucide--house]"
|
||||
},
|
||||
{
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Components",
|
||||
"href": "/base"
|
||||
},
|
||||
{
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Page header",
|
||||
"current": true
|
||||
}
|
||||
<span>}</span>
|
||||
]'
|
||||
showHome="true"
|
||||
homeLabel="Compact example"
|
||||
@@ -363,19 +363,19 @@ page BreadcrumbDetail {
|
||||
<pre><code><Breadcrumb
|
||||
label="Advanced example"
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"label": "Home",
|
||||
"href": "/",
|
||||
"icon": "icon-[lucide--house]"
|
||||
},
|
||||
{
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Components",
|
||||
"href": "/base"
|
||||
},
|
||||
{
|
||||
<span>}</span>,
|
||||
<span>{</span>
|
||||
"label": "Page header",
|
||||
"current": true
|
||||
}
|
||||
<span>}</span>
|
||||
]'
|
||||
separator="slash"
|
||||
showHome="true"
|
||||
@@ -389,13 +389,13 @@ page BreadcrumbDetail {
|
||||
</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><Breadcrumb
|
||||
@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 { registerOutputHandler } from "@wrnexus/csr/outputs"
|
||||
/></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=\"Breadcrumb\"], [data-component=\"Breadcrumb\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "select", (payload) => {
|
||||
if (component) registerOutputHandler(component, "select", (payload) => <span>{</span>
|
||||
console.log("select", payload)
|
||||
})</code></pre></section></div></div></section>
|
||||
<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>label</code></td><td>string</td><td><code>"Breadcrumb"</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>active</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>separator</code></td><td>string</td><td><code>"chevron"</code></td><td>No</td></tr><tr><td><code>showHome</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>homeLabel</code></td><td>string</td><td><code>"Home"</code></td><td>No</td></tr><tr><td><code>homeHref</code></td><td>string</td><td><code>"/"</code></td><td>No</td></tr><tr><td><code>homeIcon</code></td><td>string</td><td><code>"icon-[lucide--house]"</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>"minimal"</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">
|
||||
|
||||
@@ -41,7 +41,7 @@ page ButtonGroupDetail {
|
||||
<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><ButtonGroup
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "button-group-0-1",
|
||||
"key": "button-group-0-1",
|
||||
"value": "primary",
|
||||
@@ -70,31 +70,31 @@ page ButtonGroupDetail {
|
||||
"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": "button-group-0-2",
|
||||
"key": "button-group-0-2",
|
||||
"value": "secondary",
|
||||
@@ -123,30 +123,30 @@ page ButtonGroupDetail {
|
||||
"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>
|
||||
]'
|
||||
value="sample-1"
|
||||
ariaLabel="Button Group 1 example"
|
||||
@@ -286,7 +286,7 @@ page ButtonGroupDetail {
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
<pre><code><ButtonGroup
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "button-group-0-1",
|
||||
"key": "button-group-0-1",
|
||||
"value": "primary",
|
||||
@@ -315,31 +315,31 @@ page ButtonGroupDetail {
|
||||
"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": "button-group-0-2",
|
||||
"key": "button-group-0-2",
|
||||
"value": "secondary",
|
||||
@@ -368,30 +368,30 @@ page ButtonGroupDetail {
|
||||
"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>
|
||||
]'
|
||||
value="sample-1"
|
||||
ariaLabel="Button Group 1 example"
|
||||
@@ -414,7 +414,7 @@ page ButtonGroupDetail {
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
<pre><code><ButtonGroup
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "button-group-1-1",
|
||||
"key": "button-group-1-1",
|
||||
"value": "primary",
|
||||
@@ -443,31 +443,31 @@ page ButtonGroupDetail {
|
||||
"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": "button-group-1-2",
|
||||
"key": "button-group-1-2",
|
||||
"value": "secondary",
|
||||
@@ -496,30 +496,30 @@ page ButtonGroupDetail {
|
||||
"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>
|
||||
]'
|
||||
value="sample-2"
|
||||
size="sm"
|
||||
@@ -544,7 +544,7 @@ page ButtonGroupDetail {
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
<pre><code><ButtonGroup
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "button-group-2-1",
|
||||
"key": "button-group-2-1",
|
||||
"value": "primary",
|
||||
@@ -573,31 +573,31 @@ page ButtonGroupDetail {
|
||||
"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": "button-group-2-2",
|
||||
"key": "button-group-2-2",
|
||||
"value": "secondary",
|
||||
@@ -626,30 +626,30 @@ page ButtonGroupDetail {
|
||||
"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>
|
||||
]'
|
||||
value="sample-3"
|
||||
size="lg"
|
||||
@@ -663,21 +663,21 @@ page ButtonGroupDetail {
|
||||
@click='console.log(payload)'
|
||||
@select='console.log(payload)'
|
||||
@change='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 { registerOutputHandler } from "@wrnexus/csr/outputs"
|
||||
/></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=\"ButtonGroup\"], [data-component=\"ButtonGroup\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "click", (payload) => {
|
||||
if (component) registerOutputHandler(component, "click", (payload) => <span>{</span>
|
||||
console.log("click", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "select", (payload) => {
|
||||
if (component) registerOutputHandler(component, "select", (payload) => <span>{</span>
|
||||
console.log("select", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "change", (payload) => {
|
||||
if (component) registerOutputHandler(component, "change", (payload) => <span>{</span>
|
||||
console.log("change", payload)
|
||||
})</code></pre></section></div></div></section>
|
||||
<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>items</code></td><td>unknown[]</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>size</code></td><td>string</td><td><code>"md"</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>"default"</code></td><td>No</td></tr><tr><td><code>orientation</code></td><td>string</td><td><code>"horizontal"</code></td><td>No</td></tr><tr><td><code>responsive</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>attached</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>selectable</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>toolbar</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>ariaLabel</code></td><td>string</td><td><code>"Button group"</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">
|
||||
|
||||
@@ -254,21 +254,21 @@ page ButtonDetail {
|
||||
@click='console.log(payload)'
|
||||
@focus='console.log(payload)'
|
||||
@blur='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 { registerOutputHandler } from "@wrnexus/csr/outputs"
|
||||
/></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=\"Button\"], [data-component=\"Button\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "click", (payload) => {
|
||||
if (component) registerOutputHandler(component, "click", (payload) => <span>{</span>
|
||||
console.log("click", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "focus", (payload) => {
|
||||
if (component) registerOutputHandler(component, "focus", (payload) => <span>{</span>
|
||||
console.log("focus", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "blur", (payload) => {
|
||||
if (component) registerOutputHandler(component, "blur", (payload) => <span>{</span>
|
||||
console.log("blur", payload)
|
||||
})</code></pre></section></div></div></section>
|
||||
<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>label</code></td><td>string</td><td><code>"Button"</code></td><td>No</td></tr><tr><td><code>loadingLabel</code></td><td>string</td><td><code>"Loading…"</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>as</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>href</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>target</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>rel</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>type</code></td><td>string</td><td><code>"button"</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>color</code></td><td>string</td><td><code>"primary"</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>disabled</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>loading</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>pill</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>fullWidth</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>icon</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>iconPosition</code></td><td>string</td><td><code>"start"</code></td><td>No</td></tr><tr><td><code>ariaLabel</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>ariaPressed</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>ariaExpanded</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>ariaControls</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>autofocus</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>controlClass</code></td><td>string</td><td><code>""</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">
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -52,7 +52,7 @@ page CarouselDetail {
|
||||
title="Carousel example"
|
||||
description="A polished default carousel for a modern application."
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "carousel-0-1",
|
||||
"key": "carousel-0-1",
|
||||
"value": "primary",
|
||||
@@ -81,31 +81,31 @@ page CarouselDetail {
|
||||
"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": "carousel-0-2",
|
||||
"key": "carousel-0-2",
|
||||
"value": "secondary",
|
||||
@@ -134,30 +134,30 @@ page CarouselDetail {
|
||||
"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>
|
||||
]'
|
||||
showPagination="true"
|
||||
showCounter="true"
|
||||
@@ -300,7 +300,7 @@ page CarouselDetail {
|
||||
title="Carousel example"
|
||||
description="A polished default carousel for a modern application."
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "carousel-0-1",
|
||||
"key": "carousel-0-1",
|
||||
"value": "primary",
|
||||
@@ -329,31 +329,31 @@ page CarouselDetail {
|
||||
"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": "carousel-0-2",
|
||||
"key": "carousel-0-2",
|
||||
"value": "secondary",
|
||||
@@ -382,30 +382,30 @@ page CarouselDetail {
|
||||
"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>
|
||||
]'
|
||||
showPagination="true"
|
||||
showCounter="true"
|
||||
@@ -432,7 +432,7 @@ page CarouselDetail {
|
||||
title="Compact Carousel"
|
||||
description="A compact configuration for dashboards and dense product surfaces."
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "carousel-1-1",
|
||||
"key": "carousel-1-1",
|
||||
"value": "primary",
|
||||
@@ -461,31 +461,31 @@ page CarouselDetail {
|
||||
"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": "carousel-1-2",
|
||||
"key": "carousel-1-2",
|
||||
"value": "secondary",
|
||||
@@ -514,30 +514,30 @@ page CarouselDetail {
|
||||
"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>
|
||||
]'
|
||||
showPagination="true"
|
||||
showCounter="true"
|
||||
@@ -565,7 +565,7 @@ page CarouselDetail {
|
||||
title="Advanced Carousel"
|
||||
description="A richer configuration demonstrating additional content and hierarchy."
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "carousel-2-1",
|
||||
"key": "carousel-2-1",
|
||||
"value": "primary",
|
||||
@@ -594,31 +594,31 @@ page CarouselDetail {
|
||||
"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": "carousel-2-2",
|
||||
"key": "carousel-2-2",
|
||||
"value": "secondary",
|
||||
@@ -647,30 +647,30 @@ page CarouselDetail {
|
||||
"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>
|
||||
]'
|
||||
showPagination="true"
|
||||
showCounter="true"
|
||||
@@ -691,49 +691,49 @@ page CarouselDetail {
|
||||
@reachEnd='console.log(payload)'
|
||||
@dragStart='console.log(payload)'
|
||||
@dragEnd='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 { registerOutputHandler } from "@wrnexus/csr/outputs"
|
||||
/></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=\"Carousel\"], [data-component=\"Carousel\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "initialize", (payload) => {
|
||||
if (component) registerOutputHandler(component, "initialize", (payload) => <span>{</span>
|
||||
console.log("initialize", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "change", (payload) => {
|
||||
if (component) registerOutputHandler(component, "change", (payload) => <span>{</span>
|
||||
console.log("change", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "previous", (payload) => {
|
||||
if (component) registerOutputHandler(component, "previous", (payload) => <span>{</span>
|
||||
console.log("previous", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "next", (payload) => {
|
||||
if (component) registerOutputHandler(component, "next", (payload) => <span>{</span>
|
||||
console.log("next", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "play", (payload) => {
|
||||
if (component) registerOutputHandler(component, "play", (payload) => <span>{</span>
|
||||
console.log("play", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "pause", (payload) => {
|
||||
if (component) registerOutputHandler(component, "pause", (payload) => <span>{</span>
|
||||
console.log("pause", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "reachStart", (payload) => {
|
||||
if (component) registerOutputHandler(component, "reachStart", (payload) => <span>{</span>
|
||||
console.log("reachStart", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "reachEnd", (payload) => {
|
||||
if (component) registerOutputHandler(component, "reachEnd", (payload) => <span>{</span>
|
||||
console.log("reachEnd", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "dragStart", (payload) => {
|
||||
if (component) registerOutputHandler(component, "dragStart", (payload) => <span>{</span>
|
||||
console.log("dragStart", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "dragEnd", (payload) => {
|
||||
if (component) registerOutputHandler(component, "dragEnd", (payload) => <span>{</span>
|
||||
console.log("dragEnd", payload)
|
||||
})</code></pre></section></div></div></section>
|
||||
<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>""</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>activeIndex</code></td><td>number</td><td><code>0</code></td><td>No</td></tr><tr><td><code>slidesPerView</code></td><td>number</td><td><code>1</code></td><td>No</td></tr><tr><td><code>gap</code></td><td>string</td><td><code>"0.75rem"</code></td><td>No</td></tr><tr><td><code>showPagination</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>isAutoPlay</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>autoplayInterval</code></td><td>number</td><td><code>4000</code></td><td>No</td></tr><tr><td><code>isInfiniteLoop</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>isRTL</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>isCentered</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>isDraggable</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>isAutoHeight</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>isSnap</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>showCounter</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>thumbnails</code></td><td>string</td><td><code>"none"</code></td><td>No</td></tr><tr><td><code>ariaLabel</code></td><td>string</td><td><code>"Content carousel"</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">
|
||||
|
||||
@@ -37,7 +37,7 @@ page ChartDetail {
|
||||
title="Chart example"
|
||||
description="A polished default chart for a modern application."
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "chart-0-1",
|
||||
"key": "chart-0-1",
|
||||
"value": "primary",
|
||||
@@ -66,31 +66,31 @@ page ChartDetail {
|
||||
"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": "chart-0-2",
|
||||
"key": "chart-0-2",
|
||||
"value": "secondary",
|
||||
@@ -119,30 +119,30 @@ page ChartDetail {
|
||||
"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>
|
||||
@@ -282,7 +282,7 @@ page ChartDetail {
|
||||
title="Chart example"
|
||||
description="A polished default chart for a modern application."
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "chart-0-1",
|
||||
"key": "chart-0-1",
|
||||
"value": "primary",
|
||||
@@ -311,31 +311,31 @@ page ChartDetail {
|
||||
"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": "chart-0-2",
|
||||
"key": "chart-0-2",
|
||||
"value": "secondary",
|
||||
@@ -364,30 +364,30 @@ page ChartDetail {
|
||||
"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>
|
||||
@@ -411,7 +411,7 @@ page ChartDetail {
|
||||
title="Compact Chart"
|
||||
description="A compact configuration for dashboards and dense product surfaces."
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "chart-1-1",
|
||||
"key": "chart-1-1",
|
||||
"value": "primary",
|
||||
@@ -440,31 +440,31 @@ page ChartDetail {
|
||||
"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": "chart-1-2",
|
||||
"key": "chart-1-2",
|
||||
"value": "secondary",
|
||||
@@ -493,30 +493,30 @@ page ChartDetail {
|
||||
"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>
|
||||
@@ -541,7 +541,7 @@ page ChartDetail {
|
||||
title="Advanced Chart"
|
||||
description="A richer configuration demonstrating additional content and hierarchy."
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "chart-2-1",
|
||||
"key": "chart-2-1",
|
||||
"value": "primary",
|
||||
@@ -570,31 +570,31 @@ page ChartDetail {
|
||||
"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": "chart-2-2",
|
||||
"key": "chart-2-2",
|
||||
"value": "secondary",
|
||||
@@ -623,30 +623,30 @@ page ChartDetail {
|
||||
"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>
|
||||
@@ -657,21 +657,21 @@ page ChartDetail {
|
||||
@select='console.log(payload)'
|
||||
@dataPointClick='console.log(payload)'
|
||||
@legendToggle='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 { registerOutputHandler } from "@wrnexus/csr/outputs"
|
||||
/></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=\"Chart\"], [data-component=\"Chart\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "select", (payload) => {
|
||||
if (component) registerOutputHandler(component, "select", (payload) => <span>{</span>
|
||||
console.log("select", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "dataPointClick", (payload) => {
|
||||
if (component) registerOutputHandler(component, "dataPointClick", (payload) => <span>{</span>
|
||||
console.log("dataPointClick", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "legendToggle", (payload) => {
|
||||
if (component) registerOutputHandler(component, "legendToggle", (payload) => <span>{</span>
|
||||
console.log("legendToggle", payload)
|
||||
})</code></pre></section></div></div></section>
|
||||
<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>
|
||||
</main>
|
||||
<aside class="detail-aside">
|
||||
|
||||
@@ -41,7 +41,7 @@ page ChatBubbleDetail {
|
||||
title="Chat Bubble example"
|
||||
description="A polished default chat bubble for a modern application."
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "chat-bubble-0-1",
|
||||
"key": "chat-bubble-0-1",
|
||||
"value": "primary",
|
||||
@@ -70,31 +70,31 @@ page ChatBubbleDetail {
|
||||
"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": "chat-bubble-0-2",
|
||||
"key": "chat-bubble-0-2",
|
||||
"value": "secondary",
|
||||
@@ -123,30 +123,30 @@ page ChatBubbleDetail {
|
||||
"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>
|
||||
]'
|
||||
showAvatars="true"
|
||||
showMetadata="true"
|
||||
@@ -289,7 +289,7 @@ page ChatBubbleDetail {
|
||||
title="Chat Bubble example"
|
||||
description="A polished default chat bubble for a modern application."
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "chat-bubble-0-1",
|
||||
"key": "chat-bubble-0-1",
|
||||
"value": "primary",
|
||||
@@ -318,31 +318,31 @@ page ChatBubbleDetail {
|
||||
"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": "chat-bubble-0-2",
|
||||
"key": "chat-bubble-0-2",
|
||||
"value": "secondary",
|
||||
@@ -371,30 +371,30 @@ page ChatBubbleDetail {
|
||||
"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>
|
||||
]'
|
||||
showAvatars="true"
|
||||
showMetadata="true"
|
||||
@@ -421,7 +421,7 @@ page ChatBubbleDetail {
|
||||
title="Compact Chat Bubble"
|
||||
description="A compact configuration for dashboards and dense product surfaces."
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "chat-bubble-1-1",
|
||||
"key": "chat-bubble-1-1",
|
||||
"value": "primary",
|
||||
@@ -450,31 +450,31 @@ page ChatBubbleDetail {
|
||||
"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": "chat-bubble-1-2",
|
||||
"key": "chat-bubble-1-2",
|
||||
"value": "secondary",
|
||||
@@ -503,30 +503,30 @@ page ChatBubbleDetail {
|
||||
"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>
|
||||
]'
|
||||
showAvatars="true"
|
||||
showMetadata="true"
|
||||
@@ -554,7 +554,7 @@ page ChatBubbleDetail {
|
||||
title="Advanced Chat Bubble"
|
||||
description="A richer configuration demonstrating additional content and hierarchy."
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "chat-bubble-2-1",
|
||||
"key": "chat-bubble-2-1",
|
||||
"value": "primary",
|
||||
@@ -583,31 +583,31 @@ page ChatBubbleDetail {
|
||||
"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": "chat-bubble-2-2",
|
||||
"key": "chat-bubble-2-2",
|
||||
"value": "secondary",
|
||||
@@ -636,30 +636,30 @@ page ChatBubbleDetail {
|
||||
"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>
|
||||
]'
|
||||
showAvatars="true"
|
||||
showMetadata="true"
|
||||
@@ -674,25 +674,25 @@ page ChatBubbleDetail {
|
||||
@messageClick='console.log(payload)'
|
||||
@avatarClick='console.log(payload)'
|
||||
@linkClick='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 { registerOutputHandler } from "@wrnexus/csr/outputs"
|
||||
/></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=\"ChatBubble\"], [data-component=\"ChatBubble\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "action", (payload) => {
|
||||
if (component) registerOutputHandler(component, "action", (payload) => <span>{</span>
|
||||
console.log("action", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "messageClick", (payload) => {
|
||||
if (component) registerOutputHandler(component, "messageClick", (payload) => <span>{</span>
|
||||
console.log("messageClick", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "avatarClick", (payload) => {
|
||||
if (component) registerOutputHandler(component, "avatarClick", (payload) => <span>{</span>
|
||||
console.log("avatarClick", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "linkClick", (payload) => {
|
||||
if (component) registerOutputHandler(component, "linkClick", (payload) => <span>{</span>
|
||||
console.log("linkClick", payload)
|
||||
})</code></pre></section></div></div></section>
|
||||
<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>""</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>oneSided</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>showAvatars</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>showMetadata</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>ariaLabel</code></td><td>string</td><td><code>"Conversation"</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">
|
||||
|
||||
@@ -57,7 +57,7 @@ page CheckboxDetail {
|
||||
placeholder="Enter a sample value"
|
||||
value="sample-1"
|
||||
values='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "checkbox-0-1",
|
||||
"key": "checkbox-0-1",
|
||||
"value": "primary",
|
||||
@@ -86,31 +86,31 @@ page CheckboxDetail {
|
||||
"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": "checkbox-0-2",
|
||||
"key": "checkbox-0-2",
|
||||
"value": "secondary",
|
||||
@@ -139,33 +139,33 @@ page CheckboxDetail {
|
||||
"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>
|
||||
]'
|
||||
options='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "checkbox-0-1",
|
||||
"key": "checkbox-0-1",
|
||||
"value": "primary",
|
||||
@@ -194,31 +194,31 @@ page CheckboxDetail {
|
||||
"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": "checkbox-0-2",
|
||||
"key": "checkbox-0-2",
|
||||
"value": "secondary",
|
||||
@@ -247,30 +247,30 @@ page CheckboxDetail {
|
||||
"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>
|
||||
@@ -517,7 +517,7 @@ page CheckboxDetail {
|
||||
placeholder="Enter a sample value"
|
||||
value="sample-1"
|
||||
values='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "checkbox-0-1",
|
||||
"key": "checkbox-0-1",
|
||||
"value": "primary",
|
||||
@@ -546,31 +546,31 @@ page CheckboxDetail {
|
||||
"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": "checkbox-0-2",
|
||||
"key": "checkbox-0-2",
|
||||
"value": "secondary",
|
||||
@@ -599,33 +599,33 @@ page CheckboxDetail {
|
||||
"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>
|
||||
]'
|
||||
options='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "checkbox-0-1",
|
||||
"key": "checkbox-0-1",
|
||||
"value": "primary",
|
||||
@@ -654,31 +654,31 @@ page CheckboxDetail {
|
||||
"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": "checkbox-0-2",
|
||||
"key": "checkbox-0-2",
|
||||
"value": "secondary",
|
||||
@@ -707,30 +707,30 @@ page CheckboxDetail {
|
||||
"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>
|
||||
@@ -757,7 +757,7 @@ page CheckboxDetail {
|
||||
icon="icon-[lucide--arrow-right]"
|
||||
value="sample-2"
|
||||
values='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "checkbox-1-1",
|
||||
"key": "checkbox-1-1",
|
||||
"value": "primary",
|
||||
@@ -786,31 +786,31 @@ page CheckboxDetail {
|
||||
"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": "checkbox-1-2",
|
||||
"key": "checkbox-1-2",
|
||||
"value": "secondary",
|
||||
@@ -839,33 +839,33 @@ page CheckboxDetail {
|
||||
"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>
|
||||
]'
|
||||
options='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "checkbox-1-1",
|
||||
"key": "checkbox-1-1",
|
||||
"value": "primary",
|
||||
@@ -894,31 +894,31 @@ page CheckboxDetail {
|
||||
"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": "checkbox-1-2",
|
||||
"key": "checkbox-1-2",
|
||||
"value": "secondary",
|
||||
@@ -947,30 +947,30 @@ page CheckboxDetail {
|
||||
"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>
|
||||
]'
|
||||
/></code></pre>
|
||||
</section>
|
||||
@@ -997,7 +997,7 @@ page CheckboxDetail {
|
||||
icon="icon-[lucide--trash-2]"
|
||||
value="sample-3"
|
||||
values='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "checkbox-2-1",
|
||||
"key": "checkbox-2-1",
|
||||
"value": "primary",
|
||||
@@ -1026,31 +1026,31 @@ page CheckboxDetail {
|
||||
"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": "checkbox-2-2",
|
||||
"key": "checkbox-2-2",
|
||||
"value": "secondary",
|
||||
@@ -1079,33 +1079,33 @@ page CheckboxDetail {
|
||||
"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>
|
||||
]'
|
||||
options='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "checkbox-2-1",
|
||||
"key": "checkbox-2-1",
|
||||
"value": "primary",
|
||||
@@ -1134,31 +1134,31 @@ page CheckboxDetail {
|
||||
"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": "checkbox-2-2",
|
||||
"key": "checkbox-2-2",
|
||||
"value": "secondary",
|
||||
@@ -1187,30 +1187,30 @@ page CheckboxDetail {
|
||||
"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>
|
||||
]'
|
||||
/></code></pre>
|
||||
</section>
|
||||
@@ -1222,29 +1222,29 @@ page CheckboxDetail {
|
||||
@focus='console.log(payload)'
|
||||
@blur='console.log(payload)'
|
||||
@invalid='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 { registerOutputHandler } from "@wrnexus/csr/outputs"
|
||||
/></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=\"Checkbox\"], [data-component=\"Checkbox\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "input", (payload) => {
|
||||
if (component) registerOutputHandler(component, "input", (payload) => <span>{</span>
|
||||
console.log("input", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "change", (payload) => {
|
||||
if (component) registerOutputHandler(component, "change", (payload) => <span>{</span>
|
||||
console.log("change", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "focus", (payload) => {
|
||||
if (component) registerOutputHandler(component, "focus", (payload) => <span>{</span>
|
||||
console.log("focus", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "blur", (payload) => {
|
||||
if (component) registerOutputHandler(component, "blur", (payload) => <span>{</span>
|
||||
console.log("blur", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "invalid", (payload) => {
|
||||
if (component) registerOutputHandler(component, "invalid", (payload) => <span>{</span>
|
||||
console.log("invalid", payload)
|
||||
})</code></pre></section></div></div></section>
|
||||
<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>id</code></td><td>string</td><td><code>""</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>label</code></td><td>string</td><td><code>"Checkbox"</code></td><td>No</td></tr><tr><td><code>hiddenLabel</code></td><td>boolean</td><td><code>false</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>variant</code></td><td>string</td><td><code>"normal"</code></td><td>No</td></tr><tr><td><code>icon</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>iconPosition</code></td><td>string</td><td><code>"start"</code></td><td>No</td></tr><tr><td><code>value</code></td><td>string</td><td><code>"on"</code></td><td>No</td></tr><tr><td><code>values</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>options</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>checked</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>indeterminate</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>orientation</code></td><td>string</td><td><code>"vertical"</code></td><td>No</td></tr><tr><td><code>card</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>rightAligned</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>list</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>helperText</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>cornerHint</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>error</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>inline</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>readonly</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>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">
|
||||
|
||||
@@ -37,7 +37,7 @@ page ClipboardDetail {
|
||||
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",
|
||||
@@ -66,31 +66,31 @@ page ClipboardDetail {
|
||||
"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",
|
||||
@@ -119,30 +119,30 @@ page ClipboardDetail {
|
||||
"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>
|
||||
@@ -282,7 +282,7 @@ page ClipboardDetail {
|
||||
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",
|
||||
@@ -311,31 +311,31 @@ page ClipboardDetail {
|
||||
"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",
|
||||
@@ -364,30 +364,30 @@ page ClipboardDetail {
|
||||
"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>
|
||||
@@ -411,7 +411,7 @@ page ClipboardDetail {
|
||||
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",
|
||||
@@ -440,31 +440,31 @@ page ClipboardDetail {
|
||||
"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",
|
||||
@@ -493,30 +493,30 @@ page ClipboardDetail {
|
||||
"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>
|
||||
@@ -541,7 +541,7 @@ page ClipboardDetail {
|
||||
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",
|
||||
@@ -570,31 +570,31 @@ page ClipboardDetail {
|
||||
"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",
|
||||
@@ -623,30 +623,30 @@ page ClipboardDetail {
|
||||
"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>
|
||||
@@ -657,21 +657,21 @@ page ClipboardDetail {
|
||||
@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 { registerOutputHandler } from "@wrnexus/csr/outputs"
|
||||
/></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=\"Clipboard\"], [data-component=\"Clipboard\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "copy", (payload) => {
|
||||
if (component) registerOutputHandler(component, "copy", (payload) => <span>{</span>
|
||||
console.log("copy", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "success", (payload) => {
|
||||
if (component) registerOutputHandler(component, "success", (payload) => <span>{</span>
|
||||
console.log("success", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "error", (payload) => {
|
||||
if (component) registerOutputHandler(component, "error", (payload) => <span>{</span>
|
||||
console.log("error", payload)
|
||||
})</code></pre></section></div></div></section>
|
||||
<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>
|
||||
</main>
|
||||
<aside class="detail-aside">
|
||||
|
||||
@@ -36,7 +36,7 @@ page CollapseDetail {
|
||||
<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><Collapse
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "collapse-0-1",
|
||||
"key": "collapse-0-1",
|
||||
"value": "primary",
|
||||
@@ -65,31 +65,31 @@ page CollapseDetail {
|
||||
"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": "collapse-0-2",
|
||||
"key": "collapse-0-2",
|
||||
"value": "secondary",
|
||||
@@ -118,33 +118,33 @@ page CollapseDetail {
|
||||
"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>
|
||||
]'
|
||||
initialOpenIndexes='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "collapse-0-1",
|
||||
"key": "collapse-0-1",
|
||||
"value": "primary",
|
||||
@@ -173,31 +173,31 @@ page CollapseDetail {
|
||||
"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": "collapse-0-2",
|
||||
"key": "collapse-0-2",
|
||||
"value": "secondary",
|
||||
@@ -226,30 +226,30 @@ page CollapseDetail {
|
||||
"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>
|
||||
]'
|
||||
ariaLabel="Collapse 1 example"
|
||||
/></code></pre>
|
||||
@@ -495,7 +495,7 @@ page CollapseDetail {
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
<pre><code><Collapse
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "collapse-0-1",
|
||||
"key": "collapse-0-1",
|
||||
"value": "primary",
|
||||
@@ -524,31 +524,31 @@ page CollapseDetail {
|
||||
"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": "collapse-0-2",
|
||||
"key": "collapse-0-2",
|
||||
"value": "secondary",
|
||||
@@ -577,33 +577,33 @@ page CollapseDetail {
|
||||
"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>
|
||||
]'
|
||||
initialOpenIndexes='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "collapse-0-1",
|
||||
"key": "collapse-0-1",
|
||||
"value": "primary",
|
||||
@@ -632,31 +632,31 @@ page CollapseDetail {
|
||||
"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": "collapse-0-2",
|
||||
"key": "collapse-0-2",
|
||||
"value": "secondary",
|
||||
@@ -685,30 +685,30 @@ page CollapseDetail {
|
||||
"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>
|
||||
]'
|
||||
ariaLabel="Collapse 1 example"
|
||||
/></code></pre>
|
||||
@@ -731,7 +731,7 @@ page CollapseDetail {
|
||||
<pre><code><Collapse
|
||||
size="sm"
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "collapse-1-1",
|
||||
"key": "collapse-1-1",
|
||||
"value": "primary",
|
||||
@@ -760,31 +760,31 @@ page CollapseDetail {
|
||||
"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": "collapse-1-2",
|
||||
"key": "collapse-1-2",
|
||||
"value": "secondary",
|
||||
@@ -813,33 +813,33 @@ page CollapseDetail {
|
||||
"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>
|
||||
]'
|
||||
initialOpenIndexes='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "collapse-1-1",
|
||||
"key": "collapse-1-1",
|
||||
"value": "primary",
|
||||
@@ -868,31 +868,31 @@ page CollapseDetail {
|
||||
"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": "collapse-1-2",
|
||||
"key": "collapse-1-2",
|
||||
"value": "secondary",
|
||||
@@ -921,30 +921,30 @@ page CollapseDetail {
|
||||
"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>
|
||||
]'
|
||||
ariaLabel="Collapse 2 example"
|
||||
/></code></pre>
|
||||
@@ -967,7 +967,7 @@ page CollapseDetail {
|
||||
<pre><code><Collapse
|
||||
size="lg"
|
||||
items='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "collapse-2-1",
|
||||
"key": "collapse-2-1",
|
||||
"value": "primary",
|
||||
@@ -996,31 +996,31 @@ page CollapseDetail {
|
||||
"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": "collapse-2-2",
|
||||
"key": "collapse-2-2",
|
||||
"value": "secondary",
|
||||
@@ -1049,33 +1049,33 @@ page CollapseDetail {
|
||||
"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>
|
||||
]'
|
||||
initialOpenIndexes='[
|
||||
{
|
||||
<span>{</span>
|
||||
"id": "collapse-2-1",
|
||||
"key": "collapse-2-1",
|
||||
"value": "primary",
|
||||
@@ -1104,31 +1104,31 @@ page CollapseDetail {
|
||||
"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": "collapse-2-2",
|
||||
"key": "collapse-2-2",
|
||||
"value": "secondary",
|
||||
@@ -1157,30 +1157,30 @@ page CollapseDetail {
|
||||
"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>
|
||||
]'
|
||||
ariaLabel="Collapse 3 example"
|
||||
/></code></pre>
|
||||
@@ -1191,21 +1191,21 @@ page CollapseDetail {
|
||||
@toggle='console.log(payload)'
|
||||
@open='console.log(payload)'
|
||||
@close='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 { registerOutputHandler } from "@wrnexus/csr/outputs"
|
||||
/></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=\"Collapse\"], [data-component=\"Collapse\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "toggle", (payload) => {
|
||||
if (component) registerOutputHandler(component, "toggle", (payload) => <span>{</span>
|
||||
console.log("toggle", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "open", (payload) => {
|
||||
if (component) registerOutputHandler(component, "open", (payload) => <span>{</span>
|
||||
console.log("open", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "close", (payload) => {
|
||||
if (component) registerOutputHandler(component, "close", (payload) => <span>{</span>
|
||||
console.log("close", payload)
|
||||
})</code></pre></section></div></div></section>
|
||||
<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>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>multiple</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>mode</code></td><td>string</td><td><code>"panel"</code></td><td>No</td></tr><tr><td><code>initialOpenIndexes</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>ariaLabel</code></td><td>string</td><td><code>"Collapsible content"</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">
|
||||
|
||||
@@ -137,25 +137,25 @@ page ColorPickerDetail {
|
||||
@change='console.log(payload)'
|
||||
@focus='console.log(payload)'
|
||||
@blur='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 { registerOutputHandler } from "@wrnexus/csr/outputs"
|
||||
/></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=\"ColorPicker\"], [data-component=\"ColorPicker\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "input", (payload) => {
|
||||
if (component) registerOutputHandler(component, "input", (payload) => <span>{</span>
|
||||
console.log("input", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "change", (payload) => {
|
||||
if (component) registerOutputHandler(component, "change", (payload) => <span>{</span>
|
||||
console.log("change", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "focus", (payload) => {
|
||||
if (component) registerOutputHandler(component, "focus", (payload) => <span>{</span>
|
||||
console.log("focus", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "blur", (payload) => {
|
||||
if (component) registerOutputHandler(component, "blur", (payload) => <span>{</span>
|
||||
console.log("blur", payload)
|
||||
})</code></pre></section></div></div></section>
|
||||
<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>id</code></td><td>string</td><td><code>""</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>label</code></td><td>string</td><td><code>"Color"</code></td><td>No</td></tr><tr><td><code>hiddenLabel</code></td><td>boolean</td><td><code>false</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>value</code></td><td>string</td><td><code>"#2563eb"</code></td><td>No</td></tr><tr><td><code>icon</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>iconPosition</code></td><td>string</td><td><code>"start"</code></td><td>No</td></tr><tr><td><code>helperText</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>cornerHint</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>error</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>inline</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"normal"</code></td><td>No</td></tr><tr><td><code>readonly</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>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">
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -37,7 +37,7 @@ page ConfettiDetail {
|
||||
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",
|
||||
@@ -66,31 +66,31 @@ page ConfettiDetail {
|
||||
"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",
|
||||
@@ -119,30 +119,30 @@ page ConfettiDetail {
|
||||
"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>
|
||||
@@ -282,7 +282,7 @@ page ConfettiDetail {
|
||||
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",
|
||||
@@ -311,31 +311,31 @@ page ConfettiDetail {
|
||||
"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",
|
||||
@@ -364,30 +364,30 @@ page ConfettiDetail {
|
||||
"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>
|
||||
@@ -411,7 +411,7 @@ page ConfettiDetail {
|
||||
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",
|
||||
@@ -440,31 +440,31 @@ page ConfettiDetail {
|
||||
"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",
|
||||
@@ -493,30 +493,30 @@ page ConfettiDetail {
|
||||
"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>
|
||||
@@ -541,7 +541,7 @@ page ConfettiDetail {
|
||||
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",
|
||||
@@ -570,31 +570,31 @@ page ConfettiDetail {
|
||||
"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",
|
||||
@@ -623,30 +623,30 @@ page ConfettiDetail {
|
||||
"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>
|
||||
@@ -656,17 +656,17 @@ page ConfettiDetail {
|
||||
<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>@start</code><span>Fires when the component emits the start event.</span></div><div><code>@complete</code><span>Fires when the component emits the complete 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><Confetti
|
||||
@start='console.log(payload)'
|
||||
@complete='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 { registerOutputHandler } from "@wrnexus/csr/outputs"
|
||||
/></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=\"Confetti\"], [data-component=\"Confetti\"]")
|
||||
|
||||
if (component) registerOutputHandler(component, "start", (payload) => {
|
||||
if (component) registerOutputHandler(component, "start", (payload) => <span>{</span>
|
||||
console.log("start", payload)
|
||||
})
|
||||
<span>}</span>)
|
||||
|
||||
if (component) registerOutputHandler(component, "complete", (payload) => {
|
||||
if (component) registerOutputHandler(component, "complete", (payload) => <span>{</span>
|
||||
console.log("complete", payload)
|
||||
})</code></pre></section></div></div></section>
|
||||
<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>
|
||||
</main>
|
||||
<aside class="detail-aside">
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user