Compare commits

...
87 Commits
Author SHA1 Message Date
Clintchiz cc98bccd6c @
Quality / quality (ubuntu-latest) (push) Failing after 12m30s
Quality / quality (windows-latest) (push) Canceled after 0s
feat(ui): add DataTable and Toaster, drop the legacy Table, fix overlay dialogs

DataTable replaces the 20-line Table scaffold entirely: columns, sorting,
filtering, pagination, selection, bulk actions, comparison layout, sticky
first column, custom HTML cells, and a remote source driven by a `request`
output rather than a function prop (props travel as HTML attributes, so a
function arrives as its own source text).

Toaster replaces the hand-rolled status div: tone icons, actions, hover
pause/resume and a progress bar.

Overlays audit -- Modal and Drawer declared aria-modal="true" but nothing
ever moved focus into the panel, so the @keydown handler on their root
never ran and closeOnEscape did nothing. Focus, focus restore, a Tab trap
and a body scroll lock now live in the reactive runtime, shared by both.

ContextMenu placed pointer menus by subtracting a guessed 340x420 from the
viewport, which pushed every menu that was not that size away from the
pointer; it now positions at the pointer and lets the anchored clamp pull
it back once it can be measured.

The reactive runtime size budget moves 150k -> 175k to cover anchored
overlays, dialog behaviour, the toaster and the DataTable client half.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@
2026-08-07 14:57:17 +05:30
Clintchiz 296728d51d merge: inter-app RPC 2026-08-05 21:40:30 +05:30
Clintchiz 98205daef6 fix(rpc): isolate integration test from cross-suite fetch pollution
packages/csr's actions.test.ts and reactive.test.ts both leave
globalThis.fetch mutated across bun test files (reactive.test.ts's
'cache invalidation refetches...' test replaces it and never restores
it). Since bun test runs files sequentially rather than importing all
of them up front, a module-level capture of fetch in this file would
already observe csr's leftover mock (csr sorts before rpc).

Route the real-socket assertion through a small node:http-backed fetch
implementation instead of relying on globalThis.fetch at all, keeping
the test's actual target - httpTransport()'s default resolveOrigin -
unaffected by any other suite's global mutation.
2026-08-05 20:57:08 +05:30
ClintchizandClaude Opus 5 3eec9fd8c6 fix(rpc): close the four final-review blockers on inter-app RPC
- Resolve RPC call origins via a new WRNEXUS_INTERNAL_ORIGINS map (loopback
  origins the gateway hands each child before spawning it), falling back to
  the public appOrigin only when it is absent. Calls previously always went
  to the public gateway origin, which the gateway unconditionally 404s on
  the RPC prefix by design — every real cross-app call failed.
- Stop loadServices() from running ahead of routing and stop memoizing a
  rejected load: one bad file under app/services/ no longer permanently
  breaks every route in the app. A failed load logs loudly, is retried on
  the next RPC request, and the RPC path gets a structured RPC_UNKNOWN
  instead of an unhandled throw.
- Reject a service whose contract.name does not match the filename it is
  mounted under, naming both, instead of silently mounting under the
  filename while the typed client calls by contract name.
- Let ServiceError accept an explicit retryable and have the client pass the
  wire value through, instead of recomputing (and silently flipping) it from
  the error code alone.
- Document the gateway/X-Forwarded-* deployment requirement in the RPC
  README.

Each of the three code blockers has a new/extended test that was verified to
fail when its fix was reverted (rpc/test/integration.test.ts,
dev-server/test/rpc-services-loading.test.ts).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 20:36:31 +05:30
ClintchizandClaude Opus 5 6aaf21aa06 feat(rpc): add the caller-side example and close remaining coverage gaps
Adds examples/auth-showcase/app/services/greeter-client.ts so the showcase
demonstrates both halves - the review noted the example was callee-only, so a
developer had no working reference for making a call.

Raises integration coverage to the planned 3 tests and adds the missing
rpc-endpoint cases. Also wires the prod build path for services.

304 tests pass across rpc/router/dev-server/cli; typecheck, lint, format and
check:public-api all clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 20:21:41 +05:30
ClintchizandClaude Opus 5 ce68803471 fix(rpc): close the service-collision fail-open and the fix-wave gaps
Critical:
- router: fail loudly (WRN-SERVICE-COLLISION) when two app/services files
  scan to the same service name, instead of silently letting directory-walk
  order pick a winner.

Important:
- server.ts: wrap a throwing input schema so its raw message cannot escape
  invoke(); returns RPC_INVALID and logs server-side instead.
- client.ts: race timeoutMs against transport.call so a stalled transport
  cannot hang the caller; rejects with a ServiceError(RPC_TRANSPORT).
- client.ts: the proxy returns undefined for undeclared properties (incl.
  then/catch/finally) instead of a function that throws, closing the
  await-client thenable trap.
- gateway.ts / rpc-dispatch.ts: import RPC_PATH_PREFIX / RPC_INTERNAL_HEADER
  from @wrnexus/rpc instead of hardcoding local copies.
- gateway.test.ts: cover the RPC-prefix edge block and internal-header
  stripping across casing variants.
- http.test.ts / client.test.ts: cover anonymous-call header omission, the
  internal marker, the retryable-status sweep, network/malformed/HTML
  failures, AbortSignal propagation, the timeout path, and timer cleanup.

Minor:
- transport.ts: Object.hasOwn for handler lookup; note the entry-only abort
  check.
- client.ts: wrap a missing/invalid WRNEXUS_RPC_SECRET as a ServiceError
  (RPC_IDENTITY) instead of a bare Error.
- rpc/package.json: drop the unused @wrnexus/authz dependency.
- server.ts: implement() now throws at construction time if a declared
  procedure has no own handler.

Verified: reverting the service-collision check and the client timeout race
each make their new test fail, then restore green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 20:10:20 +05:30
ClintchizandClaude Opus 5 7c4b484d0a fix(rpc): close prototype-chain permission bypass, add server/client/transport tests
C1 CRITICAL: implement() looked up procedures/handlers with plain property
indexing, so any Object.prototype member name (constructor, toString, etc.)
resolved truthy and skipped the permission gate entirely. Fixed with
Object.hasOwn checks in packages/rpc/src/server.ts. Defense-in-depth guard
added in packages/dev-server/src/rpc-dispatch.ts constraining URL path
segments to a safe charset before they reach service/procedure lookups.

Added missing direct test coverage for packages/rpc/src/transport.ts,
server.ts and client.ts (previously untested), including a prototype-name
sweep in both server.test.ts and dev-server's rpc-endpoint.test.ts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 19:46:59 +05:30
ClintchizandClaude Opus 5 fcf4ed3039 docs: close a prototype-chain authorization bypass in the plan
server.ts looked procedures up with plain property indexing, so every
Object.prototype member resolved as truthy. A prototype member carries no
`permission`, so the permission gate was skipped entirely.

Verified: with a contract whose only procedure declares a permission and a
checkPermission that always denies, invoke("add") correctly returns
RPC_DENIED, while invoke("constructor") returns {"ok":true,"value":{"a":2}}
and the gate never runs.

Reachable over the wire as POST /__wrnexus/rpc/<service>/constructor by
anything that clears the internal-caller check - i.e. any workspace app.

Fixed at both layers: Object.hasOwn for the procedure and handler lookups,
and a character-class guard on the path segments before they are used as
lookup keys.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 19:42:49 +05:30
ClintchizandClaude Opus 5 e01915823a feat(rpc): transport, server, client, http, mounting, docs (Tasks 5-11)
Brings the uncommitted body of work under version control so it cannot be
lost. Gates are green: 152 tests pass across rpc/router/dev-server,
typecheck, lint, format and check:public-api all clean.

NOT YET REVIEWED. None of Tasks 5-11 has had an independent task review, and
Task 4's second fix round was never re-reviewed either.

Known gaps against the plan, recorded here rather than discovered later:
- packages/rpc/test/{transport,server,client}.test.ts are ABSENT. The plan
  required a test file for each. server.ts holds the fail-closed identity and
  permission checks and currently has no direct coverage at all.
- rpc-endpoint.test.ts has 3 tests where the plan specified 9. Missing:
  unknown service, non-POST, malformed body, non-rpc passthrough, and the
  isInternalCaller sweep. This is the task where a reachable
  /__wrnexus/rpc/* makes every permission check in the workspace bypassable.
- http.test.ts has 3 of 7; integration.test.ts 2 of 3;
  services-discovery.test.ts 1 of 4.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 19:38:04 +05:30
ClintchizandClaude Opus 5 9bc0f48514 fix(rpc): close the iat fail-open and tighten the identity guards
verifyJwt gates its maxAge check on iat being a number, so a token forged
without iat was honoured at any maxAgeSeconds - the same shape as the
audience and exp fail-opens closed in the previous round. A future-dated iat
did the same via a negative age. Both refused now.

The import side never checked aud was a single string, and verifyJwt compares
with includes(), so a multi-audience token verified at several apps. The
mint-side guard's invariant now holds where it is enforced.

ctx.tenant present with a null id minted an authenticated credential with no
tenant claim, which the callee reads as global. Absent ctx.tenant means
untenanted; a present tenant with an unusable id is an error.

Adds six tests pinning behaviours that mutation testing showed were free to
delete without any test noticing: no-exp, no-iat, the 300s default max age,
an array audience on import, a non-string tenant claim on import, and a null
tenant id at mint.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 18:32:41 +05:30
ClintchizandClaude Opus 5 9f599e02e8 docs: close the iat fail-open and tighten the Task 4 identity guards
The round-1 fix required exp and passed maxAge, but verifyJwt gates its age
check on iat being a number - the identical shape to the two fail-opens that
round closed. A token minted without iat defeats the age bound at ANY
maxAgeSeconds, and a future-dated iat yields a negative age and does the
same. Both refused now, so maxAge means what ImportOptions says it means.

The mint side refused an array targetApp, but the import side never checked
that aud was a single string, and verifyJwt compares with includes(). So a
multi-audience token still verified at several apps - the invariant was true
only where it was not enforced. Now checked at the callee.

ctx.tenant present with a null id was treated as untenanted, silently
widening scope to global while still issuing an authenticated credential.
Absent ctx.tenant means global; a present tenant with an unusable id is an
error.

Also exports ImportOptions, which the append snippet omitted although the
Produces line names it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 14:36:05 +05:30
ClintchizandClaude Opus 5 83c99cc3e5 fix(rpc): close identity-token fail-open and validation gaps
- importSubjectContext now rejects a non-string/empty selfApp before
  verifying. verifyJwt skips the audience check entirely when audience
  is undefined, so an unvalidated selfApp (the natural shape of
  currentAppName(): string | undefined) accepted every token from every
  app for every audience.
- exportSubjectContext now rejects a non-string/empty targetApp, so an
  array can no longer mint one token valid at multiple apps.
- Both directions now reject a present-but-non-string tenant id instead
  of silently dropping it (was: callee reads missing tenantId as
  global/unscoped -> cross-tenant exposure).
- importSubjectContext now requires exp to be present and independently
  bounds accepted token age via a new maxAge/ImportOptions.maxAgeSeconds
  (default 300s), so a caller cannot mint a long-lived token via a huge
  ttlSeconds and have it honoured indefinitely.
- SubjectContext.callerApp doc now states it is self-asserted (the
  signing secret is workspace-wide) and must never be an authz input.
- index.ts also exports the new ImportOptions type.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 14:26:14 +05:30
ClintchizandClaude Opus 5 1393a8a3b8 docs: close a fail-open and three gaps in the Task 4 identity plan
CRITICAL: importSubjectContext never validated selfApp, and verifyJwt skips
the audience check entirely when audience is undefined. So an undefined
selfApp disabled the only cross-app binding in the system and accepted every
token from every app for every audience. Not hypothetical - the natural feed
is helpers' currentAppName(), which returns string | undefined. The mint side
already hard-fails on a missing app name; the import side did not.

A non-string tenant id was silently dropped at both ends. A numeric tenant id
is the common DB-backed case, and a callee reading a missing tenantId as
"global" is a cross-tenant exposure. Now refused, symmetric with the subject
check.

Token lifetime was unbounded: verifyJwt only checks exp when present, so a
token minted without one never expired, and a caller passing a large
ttlSeconds produced a long-lived impersonation credential the callee
honoured. exp is now required and age is bounded by maxAge independently.

targetApp was unvalidated, so passing an array minted one token valid at
several apps - exactly what the audience binding exists to prevent.

Also documents callerApp as self-asserted rather than authenticated
provenance, since the signing secret is workspace-wide.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 14:21:38 +05:30
ClintchizandClaude Opus 5 2257ee871e feat(rpc): add the signed subject-context token
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 14:12:42 +05:30
Clintchiz 40625e98ed fix(rpc): deep-freeze procedures in defineService, not just the map 2026-08-05 14:03:40 +05:30
ClintchizandClaude Opus 5 e0bd84247e docs: deep-freeze procedures in the Task 3 plan snippet
defineService froze the procedures map but not each procedure inside it, so a
ProcedureDef built by hand rather than through procedure.build() stayed
mutable: svc.procedures.foo.permission = 'hacked' silently succeeded. The
contract is shared between two apps as a single source of truth, and the
guarantee rested on every call site remembering to use the builder.

Same class as the authz catalog's frozenMap, which froze the Map's mutators
but not the values it handed out.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 14:01:52 +05:30
ClintchizandClaude Opus 5 34d5bbc810 docs: add the missing cast to .input() in the Task 3 plan snippet
The builder's .input() did not typecheck as written (TS2345). The phantom
__input/__output markers make ProcedureDef invariant, which is exactly why
.output<T>() already carried a cast - .input() needed the analogous one and
did not have it.

Caught by the Task 3 implementer, who also verified via @ts-expect-error that
InferProcedureInput/InferProcedureOutput genuinely reject wrong shapes, so
the phantom markers are carrying real type information rather than silently
widening.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 13:54:23 +05:30
Clintchiz e16903b286 feat(rpc): add defineService and the immutable procedure builder 2026-08-05 13:53:14 +05:30
ClintchizandClaude Opus 5 21ea8a84a0 fix(rpc): bound retryable status range and add malformed-response code
- isRetryableStatus now fails closed for out-of-range values (600+, negative,
  NaN) by bounding the 5xx check on both sides (>= 500 && <= 599), instead of
  an unbounded >= 500 that classified garbage statuses like 1000 as retryable.
- 408 Request Timeout is now retryable, matching the RPC_TRANSPORT doc
  comment (connection, timeout, 5xx) — a timeout surfaced as 408 is no longer
  treated differently from the same timeout surfaced as 504.
- Add RPC_MALFORMED: the callee answered, but not with a ServiceResult (HTML
  error page, truncated body, unexpected shape). Distinct from RPC_TRANSPORT
  since something DID respond; non-retryable via the existing retryableFor,
  no new branch needed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 13:45:37 +05:30
ClintchizandClaude Opus 5 dd8354477d docs: close three retryability gaps in the Task 2 plan snippet
isRetryableStatus used an unbounded status >= 500, so a garbage status like
1000 landed in the retryable bucket. This function is the sole gate the
client and HTTP transport trust for retry safety, and an out-of-range value
must fail closed. Bounded on both sides.

408 Request Timeout was non-retryable while the same file documented
transport as covering "connection, timeout, 5xx" - a genuine timeout
surfaced as 408 was classified differently from the identical timeout
surfaced as 504. Now retryable.

There was no code for "the callee answered but not with a ServiceResult" - a
proxy's HTML error page, a truncated body. Task 8 was already papering over
it by hand-setting retryable: false beside a transport code that
retryableFor says is always retryable, which is exactly how the two drift
apart. Added RPC_MALFORMED and made that path use failure() so retryability
is derived from the code rather than written next to it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 13:43:39 +05:30
ClintchizandClaude Opus 5 796b19d923 feat(rpc): add service errors and retryability classification
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 13:37:52 +05:30
Clintchiz 3e1d7db537 fix(rpc): resolve lint warnings from review follow-up
- Drop the redundant eslint-disable on AnyProcedures; no-explicit-any
  is off repo-wide so the directive itself was the warning. Doc
  comment now explains why none is needed.
- Rename test's schema binding to _schema per the lint config's
  underscore-prefix rule for read-only-as-type bindings.
2026-08-05 09:59:23 +05:30
ClintchizandClaude Opus 5 a4c7d7b298 docs: drop a redundant eslint directive and note a Bun test quirk
no-explicit-any is off repo-wide in eslint.config.js, so the disable comment
the plan mandated is itself an unused-directive warning. The test's schema
binding also needs the _ prefix the lint config requires for a value read
only via typeof.

Separately: bun test strips type-only imports before resolution, so the
red-first step does not reproduce for type-only tests. Recorded so later
implementers do not chase it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 09:54:08 +05:30
Clintchiz e1fca3eddf feat(rpc): scaffold the package and shared contract types 2026-08-05 09:52:48 +05:30
ClintchizandClaude Opus 5 63e6148cdb docs: implementation plan for the inter-app communication system
Eleven TDD tasks covering phase 1: contract and immutable procedure builder,
error classification, the signed subject-context token, the Transport seam
with an in-process transport for tests, implement() with fail-closed identity
and permission checks, the typed client proxy, the HTTP transport, router
discovery of app/services, and the mounted endpoint with its two independent
external-access guards.

Phases 2-4 (retry and circuit breaking, app-to-app streaming, identity for
pubsub and queue) are documented as deferred with the reason each needs its
own design pass.

Task 10 is called out as the highest-risk: if /__wrnexus/rpc/* is reachable
from the public internet, every permission check in the workspace is
bypassable, so the plan requires the gateway block and the app-side check to
be verified as working independently of each other.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 09:37:39 +05:30
ClintchizandClaude Opus 5 87d33a2ce1 docs: design for the inter-app communication system
Typed request/response between workspace apps over HTTP, behind a Transport
seam so gRPC stays additive rather than a rewrite. Contracts live in the
workspace's shared package and are imported by both sides, so types flow
through a normal import with no code generator.

Consumes the exportSubjectContext/importSubjectContext seam the permissions
system reserved, with one improvement on what that seam implied: 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 becomes impossible by construction and there is no path to
injecting privileges through a claim. The token authenticates; it never
authorizes.

Records two properties that are easy to get wrong and expensive to discover:
/__wrnexus/rpc/* must be unreachable from the public internet, blocked at the
gateway AND verified at the app, or every permission check in the workspace
is bypassable; and only procedures explicitly marked idempotent may be
retried, because retrying a slow createInvoice is how a customer gets billed
twice.

Deliberately does not wrap pubsub or queue - they work, and an abstraction
over working code leaks and needs keeping in sync. They gain identity
propagation instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 09:27:43 +05:30
ClintchizandClaude Opus 5 2ab7b1e762 chore: sync the lockfile with the authz workspace dependencies
packages/authz gained @wrnexus/core and @wrnexus/db, and
examples/auth-showcase gained @wrnexus/authz, but no install ran afterwards
so bun.lock never recorded them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 08:41:22 +05:30
ClintchizandClaude Opus 5 2e93124080 merge: 0.8.4 security audit fixes and the permissions system
Two bodies of work, both reviewed before merge.

SECURITY AUDIT of 0.8.4. The repo's own gates were already green, so every
finding came from manual review and each was reproduced before being claimed:
safeFetch re-attached credentials after a cross-origin redirect; its
private-network guard was advisory only and defeated by DNS rebinding; three
IPv6 forms bypassed the private-address check; sanitizeUrl returned
protocol-relative input verbatim (open redirect); the gateway threw on
malformed Basic credentials, truncated passwords at the first colon, and
leaked password length by timing; RBAC namespace wildcards matched only the
first segment; the brace-expansion override was pinned to the exact
vulnerable version.

PERMISSIONS SYSTEM in @wrnexus/authz. Declaration catalog discovered from
app/authz, a pluggable PermissionStore with memory and sqlite adapters held
to one 24-test conformance suite, a resolution engine with deny-wins
precedence and fail-closed error handling, request middleware, an audit sink,
type codegen, a wrnexus authz CLI, and dev/prod boot wiring.

Behaviour changes needing release notes: authorizeDecision's 403 body no
longer carries reason or policy (opt back in with exposeReason); RBAC
wildcards now match at every depth, which widens access for anyone relying on
the old behaviour; Router gained a required authz field; subject.id must be a
non-empty string. See docs/plans/2026-08-05-authz-follow-ups.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 08:37:55 +05:30
ClintchizandClaude Opus 5 2c339bee15 docs: record the adjudicated non-blocking authz findings
Findings from the task and whole-branch reviews that were ruled non-blocking,
plus the behaviour changes that need release notes. None is an authorization
bypass. Recorded in the repo because the review workspace is scratch and git
history does not carry the reasoning.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 02:20:39 +05:30
ClintchizandClaude Opus 5 41b6e2ed2b fix(authz): freeze catalog values after boot; correct compile-time-check claims
frozenMap only blocked the Map's own mutators, so
catalog.roles.get("editor").push("*") escalated a role to a full wildcard
past an error string claiming the catalog is frozen after boot; the same
applied to permission/attribute metadata objects and binding arrays.
mergeCatalogs now stores frozen copies of each, so the original declaring
module's objects are never mutated either.

Also corrects two docstrings (codegen.ts, the design doc) that claimed
`wrnexus authz generate`'s output makes a permission typo a type error —
can(), guardPermission(), and decideFor() all take a bare string and nothing
consumes the generated union automatically. Documents what it actually is:
a Permission/Role union to type your own helpers/constants against. Also
adds a README note on the subject.id contract (must be a non-empty string;
owner() compares with Object.is).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 02:10:24 +05:30
ClintchizandClaude Opus 5 3867e7c183 fix(authz): audit getResource denials; fail closed on a malformed denies shape
guardPermission's getResource catch returned 403 directly, never reaching
decideFor -> decide -> finish, so the audit sink never saw it — an attacker
probing ids that make the resource loader throw got a clean 403 stream
invisible to the audit trail. The audit sink is now stashed on the
per-request RequestAuthz object (authzMiddleware already receives it via
AuthzResolverOptions), and the catch records an "allowed: false" event with
an opaque reason before returning the 403.

Also: the explicit-deny check sat outside decide()'s try/catch, and
deniedBy() guarded on denies.length rather than Array.isArray(denies). A
store returning denies as a bare string let new Set(denies) iterate
characters instead of the permission, so the deny matched nothing and was
silently discarded; a store omitting denies entirely threw straight out of
decide(). Both are now validated and handled inside the try, denying via the
same "Authorization store unavailable" path as any other store failure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 02:10:11 +05:30
ClintchizandClaude Opus 5 a7255fa1bd fix(dev-server): don't clobber a caller-set authz catalog; drop dead RuntimeDeps.authz
createProductionHandlers called setAuthzCatalog unconditionally, so a caller
using client.ts's documented escape hatch (setAuthzCatalog(catalog) before
importing anything that reads it) had that catalog silently wiped to empty
whenever opts.authz was omitted. Now only sets when opts.authz has entries to
contribute, or when nothing has been set yet; a non-empty opts.authz still
always sets and still throws on a genuine conflict.

Also removes RuntimeDeps.authz: nothing read it, and its doc comment
described a consumer that doesn't exist. The real wiring is
getAuthzCatalog()/setAuthzCatalog(), including the HMR hot-update path, which
is untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 02:10:00 +05:30
Clintchiz fd5e2b7128 test(authz): end-to-end integration coverage, worked example, and docs
Task 15 of the authz permissions plan: proves db store + cache + catalog +
middleware + audit compose correctly, wires a real (non-dangling) example
into auth-showcase, and documents the declaration/registration/precedence
surface in the package README.
2026-08-05 01:22:13 +05:30
Clintchiz 57097c8204 fix(authz): fix prod boot-order (C1), dev HMR staleness (I2), add prod coverage (I4)
Fix round 2 for Task 14, addressing a critical review finding reproduced on
a real built server.

C1 (critical): the generated production entry set the authz catalog inside
createProductionServer's BODY, but ES modules evaluate every static import
(including app middleware, emitted as a static import) before the importing
module's body runs. Middleware reading getAuthzCatalog() at module scope —
the same eager shape authzMiddleware({ catalog, ... }) itself requires, and
the pattern app/middleware/logger.ts's `export default requestLogger({...})`
already uses — saw an unset catalog and crashed the whole process at import
time, after every other gate (typecheck/lint/tests/a plain `bun run build`)
stayed green.

Fix: packages/cli/src/build.ts now emits a small side-effecting
`.authz-setup.ts` module containing the static imports of every
app/authz/*.ts declaration plus a call to the new
applyAuthzManifestEarly(entries) (packages/dev-server/src/prod.ts), and
imports THAT MODULE FIRST in the generated entry — before pages, api,
realtime, middleware, components, and layouts. applyAuthzManifestEarly is
deliberately silent (no missing-default-export warnings, though a genuine
conflict still throws and fails the boot at import time); createProductionHandlers
keeps its own unconditional merge+set as an idempotent, always-warning second
pass, so an adapter that bypasses the generated entry and calls it directly
still gets a correctly merged, validated catalog, and so the function stays
independently testable.

I3: corrected packages/authz/src/client.ts's WRN-AUTHZ-SETUP message, which
claimed prod always sets the catalog before middleware runs — true again for
the generated entry after the C1 fix, but not for a custom entry that calls
createProductionHandlers directly.

I2: dev HMR editing app/authz/*.ts reloaded the page while the OLD catalog
stayed authoritative (watch.ts classifies any non-CSS change as "server";
hotUpdate had no authz/ branch) — a false security signal, since tightening
or removing a permission looked like it took effect but didn't until a
restart. Added the branch (packages/dev-server/src/index.ts), and gave
loadAppAuthzCatalog (authz-boot.ts) an injectable importer: a raw import()
would have silently no-op'd on the re-import (Bun caches local TS/JS modules
by filesystem path and ignores query strings), so the hot path routes through
loadModule (pipeline.ts) instead, which copies the edited file to a versioned
sibling specifically to defeat that cache.

I4: added direct createProductionHandlers/applyAuthzManifestEarly tests
(packages/dev-server/test/authz-prod.test.ts: conflict throws naming both
files, missing default export warns and skips, empty array yields an empty
catalog, a second call re-validates rather than trusting a stale singleton)
and the regression test that matters most
(packages/cli/test/authz-prod-coldstart.test.ts): a real `runBuild` + a real
`bun dist/server.js` boot, with a middleware module reading
getAuthzCatalog() at module scope, asserting it actually serves a request.

M5: startServer built its own router once, then loadAppAuthzCatalog built a
second one from scratch on every dev boot and every authz/ hot reload.
loadAppAuthzCatalog now accepts either an appDir (still used standalone, e.g.
by the test suite) or an already-built Router, and both call sites in
index.ts now pass the router they already have.

Every fix in this round was verified non-vacuous by sabotaging it and
confirming the corresponding test fails, then reverting.
2026-08-04 23:16:41 +05:30
Clintchiz 226217ecbf feat(authz): reach the merged catalog from boot via a process-wide singleton
Fix round 1 for Task 14 — closes the gap flagged in the last report:
loadAppAuthzCatalog existed but nothing called it.

- packages/authz/src/client.ts (new): setAuthzCatalog/getAuthzCatalog/
  hasAuthzCatalog, mirroring @wrnexus/db's client.ts. App middleware runs
  at module-eval time and needs the catalog then, so ctx cannot carry it;
  getAuthzCatalog() throws a setup error naming the fix, like getDb() does.
  Exported from packages/authz/src/index.ts.
- packages/dev-server/src/index.ts: startServer calls loadAppAuthzCatalog +
  setAuthzCatalog before middleware is resolved (schemasJs precedent),
  and populates the new RuntimeDeps.authz field.
- packages/dev-server/src/runtime.ts: RuntimeDeps gains authz?: AuthzCatalog.
- packages/cli/src/build.ts: emits static imports of each app/authz/*.ts
  file into the generated entry (components/layouts precedent) and passes
  { source, module } pairs through ProdOptions.authz — the catalog holds
  policy functions, so it cannot be JSON-baked like schemasJs.
- packages/dev-server/src/prod.ts: createProductionHandlers merges those
  declarations and calls setAuthzCatalog before the server accepts
  traffic, so a conflict fails the boot instead of surfacing on the first
  request. Runs for every deployment adapter, not only Bun.serve.

The framework never installs authzMiddleware itself; the app still
registers it with its own store.

Verified end-to-end: added a temporary app/authz declaration to
examples/basic-app, ran `bun run build`, inspected the generated entry's
static import + authz array, and booted dist/server.js to confirm the
merge/setAuthzCatalog call succeeds against real bundled code (reverted
before commit).
2026-08-04 22:40:42 +05:30
Clintchiz daea59cf5d feat(dev-server): load the authz catalog at boot
Adds loadAppAuthzCatalog(appDir) to @wrnexus/dev-server: discovers
app/authz/*.ts declarations via buildRouter, imports and merges them
into an AuthzCatalog, returning an empty catalog when the app has no
declarations. A declaration with no default export is skipped with a
warning; a genuine conflict between two declarations throws
WRN-AUTHZ-CONFLICT naming both source files.

Declared the missing @wrnexus/authz workspace dependency in
dev-server's package.json.
2026-08-04 22:03:28 +05:30
Clintchiz bc5437063d fix(cli): declare @wrnexus/authz dependency, exit cleanly on bad authz input
Round-1 review fixes for Task 13:

- packages/cli/package.json was missing @wrnexus/authz, and
  packages/authz/package.json was missing @wrnexus/core despite importing
  its types in index.ts/middleware.ts/advanced.ts. Both only worked
  in-repo because bare "@wrnexus/*" specifiers resolve through the root
  tsconfig.json paths map; a standalone install of @wrnexus/cli or
  @wrnexus/authz would fail at runtime.
- authz.ts's unknown/missing-subcommand and bad --dialect paths now
  console.error + process.exit(1), matching db.ts's convention, instead
  of throwing — index.ts's top-level catch previously printed those as a
  raw stack trace. Added a subprocess-level test that spawns the real CLI
  and asserts stderr has the usage line with no stack frame.
- nextMigrationNumber now extracts the leading-digit run the same way
  db/migrate.ts's nextNumber does, instead of a fixed slice(0, 4) that
  would have undercounted once a migration number passed 9999.
2026-08-04 21:51:38 +05:30
Clintchiz b9098382b3 feat(cli): add wrnexus authz list/generate/init
Introspects the merged authz catalog, emits app/authz/permissions.gen.ts
type unions, and scaffolds the assignment-table migration. init validates
--dialect explicitly (unrecognised values reject rather than silently
falling back to sqlite) and joins authzMigrationSql's up/down statement
lists with terminators instead of interpolating the arrays.

Test scaffolding for dynamically-imported app/authz declarations must
live inside the repo tree (not os.tmpdir()) for the "@wrnexus/*" bare
specifier to resolve via tsconfig paths; .gitignore excludes the scratch
dirs this produces.
2026-08-04 21:34:56 +05:30
ClintchizandClaude Opus 5 e5d0654d2a docs: join the DDL statement lists in the Task 13 init command
authzMigrationSql was changed in Task 11 to return statement arrays rather
than one blob, but Task 13's init still interpolated them straight into the
migration file, which would comma-join two CREATE TABLE statements into one
unparseable line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 21:26:10 +05:30
ClintchizandClaude Opus 5 3ef353de83 docs: use JSON.stringify for codegen escaping in the Task 12 plan
The plan's union helper hand-rolled escaping for backslash and double quote
only. Role names reach the emitter through the raw mergeCatalogs path, which
does not apply the registry's permission-id regex, so a value containing a
newline was emitted verbatim and the generated file failed to compile with
TS1002 Unterminated string literal.

Caught by the Task 12 implementer actually running tsc over the generated
output rather than eyeballing the string.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 21:20:11 +05:30
Clintchiz 726b8a7d24 feat(authz): generate Permission and Role union types
Emits sorted TS unions from the merged catalog so a typo in
can(ctx, "post:wrtie") is a compile-time error. Uses JSON.stringify
for string-literal escaping (not manual backslash/quote replace) so
role names containing raw newlines still produce valid TypeScript;
role names are not regex-validated like permission ids, so this
matters for the raw mergeCatalogs path.
2026-08-04 21:19:04 +05:30
ClintchizandClaude Opus 5 91e5c6e0c5 fix(authz): guard scopeKey's tenantId type, add deterministic C1/C2 guard
N1: scopeKey guarded the empty-string VALUE but not the TYPE. A
non-string tenantId (null, 0, false, an object) flowed through
un-normalised, and the adapters disagreed about the result - db
rejects null on NOT NULL, memory accepts it as an unreachable row; 0
and false stringify differently and could collide. Now
`typeof tenantId !== "string" || tenantId === ""` is refused with the
same WRN-AUTHZ-SCOPE error. Added a conformance case covering
null/0/false/{}.

N2: nothing failed if grant() were re-wrapped in db.tx, reintroducing
the shared-connection rollback from C1/C2 - timing-based tests can't
reliably prove a transaction is never opened. Added
db-no-transaction.test.ts: a fake Db with a spied driver.transaction
and statement-recording all/exec, driving every PermissionStore method
and asserting zero transaction calls and no "BEGIN" in any recorded
statement. Verified it fails when grant() is temporarily re-wrapped in
db.tx, then restored.

Also documents two things in db.ts as comments only: the UNIQUE
constraints are now load-bearing for ON CONFLICT/ON DUPLICATE KEY
target inference, and MySQL's VALUES(effect) upsert syntax is
deprecated since 8.0.20 (no MySQL server in CI to catch its removal).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 21:06:11 +05:30
ClintchizandClaude Opus 5 2fbf059c00 docs: type-guard tenantId and add a no-transaction regression guard
Two gaps the Task 11 re-review left open.

scopeKey guarded the empty-string tenantId but not its type, so null, 0,
false or an object flowed through un-normalised and the adapters diverged -
the db rejects on NOT NULL while memory accepts an unreachable row. The whole
premise of the empty-string guard was a caller who controls the tenant id,
and that caller can just as easily hand over a null from a JSON body.

The vacuous concurrency test was removed for good reason, but that left
nothing failing if someone re-wraps grant() in db.tx and reintroduces the
shared-connection rollback. A spy over driver.transaction discriminates that
deterministically, with no timing dependency.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 21:01:26 +05:30
ClintchizandClaude Opus 5 1cc0b97a72 fix(authz): replace vacuous concurrency test, validate effect in memory store
The fix-round-1 test "a concurrent write is not lost to another
method's failure" was vacuous: a single-process Promise.all cannot
reliably land a bare write inside another method's open transaction,
so it passed against both the fixed and the (previously) defective
grant() implementation. The shared-connection rollback hazard it was
meant to catch is real (confirmed separately by forcing the
transaction open before the write), but this specific test could
never reach that state and gave false assurance either way.

Replaced it with "a rejected write leaves unrelated state intact",
which asserts a grant() call with an invalid effect is refused without
disturbing the subject's existing roles/grants, plus a NOTE
documenting that the rollback hazard is now prevented structurally (no
transactions) rather than by a dedicated concurrency test.

memoryPermissionStore.grant() had no effect validation, so it failed
the new test; added a guard mirroring the db adapter's CHECK
constraint so both adapters agree on rejecting anything other than
"allow"/"deny".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 20:52:18 +05:30
ClintchizandClaude Opus 5 134c5fa4bc docs: replace a vacuous conformance test with an honest one
I added "a concurrent write is not lost to another method's failure" to the
conformance suite to guard the fail-open the Task 11 review demonstrated. The
implementer reported they could not make it fail against the reverted code,
across 600 stress iterations. They were right.

I reproduced the underlying defect directly - forcing the transaction to open
before the bare write gives "revoke resolved without error: true" with the
role still present - so the mechanism is real. But the test cannot reach it:
Promise.all in one process does not reliably land the bare write inside the
open transaction, and grant() never fails on its own. The test passed against
the defective implementation, which is exactly the false assurance this suite
exists to prevent.

Replaced with a property that is actually guaranteed and adapter-agnostic: a
rejected write leaves unrelated state intact. The rollback hazard itself is
prevented structurally, by the store using no transactions, and that is now
stated in a comment rather than pretended to be under test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 20:48:38 +05:30
ClintchizandClaude Opus 5 205f4e2d4c fix(authz): close fail-open db store defects from review round 1
C1/C2: grant() wrapped its delete+insert in db.tx on a shared,
unserialized sqlite connection, so a concurrent bare write from another
method (e.g. revokeRole) got swept into the open transaction and
discarded on rollback - a revoke could report success while the
privilege survived. Also broke concurrent grants on distinct keys
("cannot start a transaction within a transaction"). Replaced with
single-statement upserts (ON CONFLICT / ON DUPLICATE KEY UPDATE),
atomic without a transaction.

I1: assignRole's check-then-act SELECT lost 19/20 concurrent identical
calls to a UNIQUE violation; switched to ON CONFLICT DO NOTHING.

I2: an unrecognised `effect` value was dropped from both the grant and
deny buckets on read. Added a CHECK constraint and made anything not
literally "allow" count as a deny (fail closed).

I3: ensureAuthzTables defaulted to sqlite instead of the Db's own
dialect. I4: scopeKey now refuses an explicitly empty tenantId rather
than treating it as global (shared with the memory adapter). I5: added
migrations.test.ts asserting the generated DDL per dialect, including
MySQL's binary collation on identity columns. M1: DDL is now a
statement list instead of a blob split on a formatting-dependent
separator. M3: declared @wrnexus/db as a workspace dependency.

Extends the conformance suite with four concurrency/empty-scope tests
(23 total, up from 19) that all three adapters now pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 20:46:48 +05:30
ClintchizandClaude Opus 5 f19462dff0 docs: fix fail-open concurrency and effect handling in the Task 11 plan
The database store review found two Critical defects and several Important
ones, all reachable in production.

grant() was wrapped in db.tx for atomicity. The sqlite driver runs a bare
BEGIN on one shared connection with no serialization, so an open transaction
swallows any concurrent write from another method and discards it on
rollback. Demonstrated: revokeRole resolved with no error while the role
survived - a security-critical revoke reporting success with the privilege
retained. Concurrent grants also rejected outright with "cannot start a
transaction within a transaction". Replaced with single-statement upserts,
which are atomic without a transaction; assignRole likewise drops its
check-then-act SELECT for ON CONFLICT DO NOTHING, which was rejecting 19 of
20 concurrent identical calls.

effect had no CHECK constraint and assignmentsFor classified by exact
equality, so a mis-cased or corrupted value was dropped from BOTH buckets -
a deny row that silently stopped denying. Added the constraint and made
anything that is not literally "allow" count as a deny.

scopeKey now refuses an explicitly empty tenantId rather than treating it as
global, which otherwise let a caller who controls the tenant id read and
write global assignments.

Also: ensureAuthzTables takes the dialect from db.driver.dialect instead of
defaulting to sqlite; the DDL is a list of statements rather than a blob
split on a formatting-dependent separator; MySQL identity columns get a
binary collation so tenant "T1" cannot match "t1"; and postgres placeholders
are numbered.

Adds four conformance tests for the concurrency and empty-scope cases. The
suite was entirely sequential and structurally could not catch any of this.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 20:33:45 +05:30
Clintchiz 3fa3fce5df feat(authz): add database-backed PermissionStore
Adds dbPermissionStore/ensureAuthzTables/authzMigrationSql, backed by
_wrn_authz_assignment and _wrn_authz_grant tables, plus a ./db subpath
export. Passes the identical 19-test store-conformance suite the memory
adapter passes, including tenant-scope isolation.
2026-08-04 20:22:31 +05:30
ClintchizandClaude Opus 5 218f5e2dd6 chore: add .gitattributes enforcing LF
The repo had none, and core.autocrlf=true is the usual Git-on-Windows
setting, so a clone, checkout, or stash pop silently rewrites every text file
to CRLF. That fails format:check against prettier's endOfLine: lf - it
already turned the gate red once mid-branch, after a stash round-trip
reintroduced CRLF into files that had been committed clean.

Verified: no tracked file currently carries a CR byte at HEAD.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 20:17:39 +05:30
ClintchizandClaude Opus 5 e136fbc56a fix(router): quietly skip permissions.gen.{ts,js} in authz scan
Task 10 fix round 1: the coordinator's plan doc (41fb82b9) recorded that
generated authz type files should be skipped before the isSafeIslandName
check, but the code change never landed. isSafeIslandName rejects the dot
in the stripped basename "permissions.gen", so every app running Task 12's
codegen would warn on every boot.

Add a quiet skip for *.gen.ts / *.gen.js immediately after the extension
guard, before the name check. Add tests: a .gen.ts file is skipped without
a console.warn (spied), and a .gen.js file is skipped the same way while a
legitimately named .js declaration is still discovered.

Also corrects the scanDir extraExtensions doc comment, which incorrectly
implied app/schemas passes it too (only app/authz does).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 20:09:16 +05:30
ClintchizandClaude Opus 5 41fb82b9e9 docs: skip generated type files in the Task 10 authz scan
The brief asserted permissions.gen.ts would be discovered as an entry named
permissions.gen and filtered by a later task. It is not: isSafeIslandName
rejects the dot in the stripped basename, so it takes the warn-and-skip path
and would print a warning on every boot of any app that ran the codegen,
while Task 14's name-based filter for it was dead code.

The scan now skips *.gen.ts / *.gen.js quietly, before the name check. Also
records the extraExtensions argument the implementer added to scanDir, which
keeps .js out of the route-scanning allow-list where it would otherwise leak
into generated route URLs via fileToRoute.

Caught by the Task 10 implementer testing the claim rather than trusting it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 19:55:33 +05:30
Clintchiz e7743cdbb5 feat(router): discover app/authz declarations
Scan app/authz/<name>.{ts,js} the same way app/schemas is scanned,
exposing Router.authz: ComponentRef[]. Also update the two other
literal Router construction sites (prod runtime, dev-server test
fixture) that now need the new required field.

scanDir gains an optional extraExtensions parameter (default []) so
the authz scan can accept .js files without widening the extension
allow-list used by route scanning (app/pages, app/api, app/realtime),
which would otherwise leak .js into generated route URLs via
fileToRoute.
2026-08-04 19:52:37 +05:30
Clintchiz 703baa1ead fix(authz): strengthen permissionMatches warning, complete export coverage
Move the "don't gate on permissionsFor() with permissionMatches" warning
onto permissionMatches itself so it's visible via autocomplete, not just
on AuthzResolver.permissionsFor. Round out exports.test.ts to cover
scopeKey, safeRecord, and AUTHZ_LOCALS_KEY, closing the gap where
dropping either export from index.ts would not fail the test.
2026-08-04 19:38:07 +05:30
ClintchizandClaude Opus 5 e05ddc7aa5 docs: warn against the permissionMatches + permissionsFor composition
permissionsFor carries a caveat that its Set cannot represent a narrow deny
under a broad grant, so callers must gate with decide(). Now that
permissionMatches is also public, the wrong composition is directly reachable
and looks idiomatic - and the warning lived only on the other half of it.
Adds the pointer to permissionMatches, and covers scopeKey and safeRecord in
the exports test, which the brief omitted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 19:36:24 +05:30
Clintchiz 6f3a53b9ff feat(authz): export registry, store, engine, and middleware surface
Appends the Task 1-8 modules (defineAuthz, catalog merge helpers,
permission stores, audit sinks, resolver engine, and authzMiddleware/
can/guards) to the public @wrnexus/authz surface, and regenerates the
public-api-0.8.json baseline to match.
2026-08-04 19:29:08 +05:30
ClintchizandClaude Opus 5 13859ce7dc docs: add deniedBy to the Task 9 export list
deniedBy was introduced in Task 6's fix round to make wildcard denies work,
but the plan's export block and its exports test were never updated, so
Task 9 would have shipped it module-private.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 19:25:40 +05:30
Clintchiz e15422ed8d fix(authz): stop authorizeDecision leaking policy names in 403 bodies 2026-08-04 19:17:48 +05:30
Clintchiz 3f1fcd0d2d fix(authz): stop encodeURI from double-encoding a percent-escaped redirectTo
Fix round 3 for Task 7 (N5, minor-to-important): fix round 2's
encodeURI(options.redirectTo) fixed the non-ASCII crash but broke the
most common real use of redirectTo -- a return-path query param that's
already percent-encoded (e.g. /login?next=%2Fdash) -- because encodeURI
also escapes "%", double-encoding it to %252Fdash. Replaced with
headerSafePath(), a codepoint loop that encodes only codepoints above
0x7f (matching isLocalPath's style: no regex, no source escapes) and
leaves "%" alone.

Added tests: an already-percent-encoded target round-trips unchanged;
a non-ASCII target still 303s without throwing and the location is
ASCII-only; a plain ASCII target passes through byte-identical.
2026-08-04 19:10:09 +05:30
ClintchizandClaude Opus 5 798f56734a docs: stop double-encoding redirectTo in the Task 7 plan snippet
The previous fix used encodeURI to keep a non-ASCII redirect target from
throwing inside new Response. But encodeURI also escapes "%", so an
already-percent-encoded target is corrupted: /login?next=%2Fdash becomes
/login?next=%252Fdash, which single-decodes to the literal "%2Fdash" rather
than the intended path. That is the most common real use of redirectTo -
"send them to login, then bounce back".

Replaced with headerSafePath, a codepoint loop that encodes only what cannot
be sent in a Latin-1 header and leaves existing escapes and reserved ASCII
untouched. My prescription, my defect.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 19:07:15 +05:30
Clintchiz 77b9e49bf2 fix(authz): fold subject into the memo key, fix symbol/-0 and redirect issues
Fix round 2 for Task 7 (plan amendment 9e3624e5):

- N1 (Important): the memo key carried scope and permission but not the
  subject, so a request that reassigns ctx.user mid-flight (impersonation,
  step-up auth, session revocation, or an authz-before-auth middleware
  ordering mistake) could be served the previous principal's cached
  verdict. subjectId (typeof + String, matching the existing scope/value
  encoding style) is now folded into every memo key.
- N2 (Minor): the primitive-value memo key used String(resource), which
  collapses distinct Symbol("row") values into one slot and maps -0 onto
  0's slot. Added a dedicated bySymbol identity memo (WeakMap-style, but a
  plain Map since symbols aren't valid WeakMap keys pre-registry symbols
  and the memo is request-scoped anyway) and special-cased Object.is(x,-0)
  to render as "-0".
- N3 (Minor): the rejected-redirect console.error interpolated
  redirectTo directly, exactly the value most likely to carry CR/LF in
  that branch. Switched to JSON.stringify(redirectTo) for the log line.
- N4 (Minor): a non-ASCII (but otherwise valid, local) redirectTo passed
  isLocalPath and then threw inside `new Response` building the Location
  header. Wrapped it in encodeURI().

Added 5 regression tests: subject swap re-evaluates, clearing ctx.user
denies, two same-description symbols get separate verdicts, 0 vs -0 get
separate verdicts, non-ASCII redirectTo 303s with an encoded location
instead of throwing. N1 revert-checked: temporarily restored the
two-element (no-subject) key and confirmed both subject-swap tests fail
against it before restoring the fix.
2026-08-04 18:59:56 +05:30
ClintchizandClaude Opus 5 9e3624e584 docs: put the subject in the memo key in the Task 7 plan snippet
The re-review closed all six earlier findings but surfaced the same bug class
one level over: the memo key carried the scope but not the subject, so
reassigning ctx.user mid-request served the previous principal's verdict.
Demonstrated - u1 allowed, then ctx.user = u2 still returned true, and
clearing ctx.user entirely revoked nothing. Triggered by impersonation or
"view as user" middleware, step-up auth, session revocation mid-request, or
simply registering an auth middleware after authzMiddleware.

Also: symbols now memo by identity (String() collapsed two distinct symbols
sharing a description into one slot), -0 stays distinct from 0, the
rejected-redirect log no longer echoes CR/LF verbatim into the log stream,
and a non-ASCII redirect target is encodeURI'd rather than throwing out of
the Response constructor and 500ing on a denial path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 18:54:30 +05:30
Clintchiz b7f3507b59 fix(authz): close memo cross-authorization and guard hardening gaps
Fix round 1 for Task 7, addressing review findings against the brief's
own memoKey design (now superseded per plan amendment cc8085bc):

- C1: memoKey's String(id) + JSON.stringify-with-catch cross-authorized
  distinct resources whenever their ids stringified the same (numeric
  vs string ids, object-shaped ids) or whenever JSON.stringify threw
  (circular references, BigInt fields, throwing getters all shared one
  "<unserialisable>" bucket, so the first verdict computed for any of
  them became the cached verdict for all of them in that request).
- C2: filterCan inherited the same bypass, returning rows the subject
  could not act on.
- Replaced serialisation-based memoization with identity-based
  memoization: object resources are memoised in a WeakMap keyed by the
  resource reference itself (never serialised), primitives/absent
  resources in a Map keyed by [scope, permission, typeof, String(value)]
  so 7 and "7" can never collide.
- I1: scope is now read from ctx.tenant at decision time (currentScope),
  not captured once at middleware-install time, so a tenant switch
  mid-request is honoured on the next check.
- I2/M1: guardPermission's redirectTo now only fires for non-JSON/API
  requests (replicated wantsJson check, since authz may only import
  core as types) and only for a validated local path (isLocalPath),
  closing an open-redirect and a JSON-caller-follows-303 gap.
- I3: getResource is now wrapped in try/catch; a throw denies with the
  standard opaque 403 body instead of propagating the loader's error
  (e.g. a SQL string) to the client.
- Added cache-control: private, no-store to both the 303 and 403
  responses.

Added 11 regression tests. C1/C2 revert-checked: temporarily restored
the old memoKey design and confirmed the four collision tests fail
against it before restoring the fix.
2026-08-04 18:41:18 +05:30
ClintchizandClaude Opus 5 cc8085bcfa docs: fix memo-key cross-authorization in the Task 7 plan snippet
The middleware's per-request memo keyed resources by String(resource.id) with
an unserialisable fallback that shared one bucket. Six demonstrated cases
cross-authorized: {id:1} vs the primitive 1; {id:7} vs {id:"7"}; object ids;
and every circular / BigInt / throwing-getter row collapsing together so the
first verdict in a request became the verdict for all of them. filterCan
returned 3 of 3 rows where 1 was permitted - it leaked, rather than denied.

Object resources now memo by identity through a WeakMap; primitives key on
JSON-encoded [scope, permission, typeof, value] so 7 and "7" stay distinct
and a tenant id containing the separator cannot collide.

Scope is also read at decision time rather than frozen when the middleware
runs, and is part of the memo key, so switching tenant mid-request no longer
returns the previous tenant's verdict.

guardPermission additionally: denies instead of 500ing when getResource
throws (and no longer leaks the loader's message), skips redirectTo for API
requests using the same rule requireAuth applies, refuses a non-local
redirect target, and sets cache-control: private, no-store.

Adds eleven regression tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 18:35:05 +05:30
Clintchiz 984c6236d3 feat(authz): add request middleware, can(), and guardPermission
Installs a per-request authz resolver via authzMiddleware and exposes
can()/decideFor()/guardPermission()/filterCan() as free functions (not
Context members, so @wrnexus/core stays free of an authz dependency).
All four route through resolver.decide(), never permissionsFor(), so
resource-scoped policy denials can't be bypassed via the coarse
permission set. Per-request results are memoised keyed on (permission,
resource) to avoid re-hitting the store within a request without
leaking one resource's verdict onto another.
2026-08-04 18:24:40 +05:30
ClintchizandClaude Opus 5 cd82bec414 fix(authz): fix perf, doc, and fail-open gaps found in second review
Re-review of Task 6's fix round 1 (plan amendment d6a2d054) found three
items in that diff plus one adjacent pre-existing issue that C1 made
reachable:

- Important (perf): permissionsFor() rebuilt the deny Set on every
  entry in the granted set (O(grants x denies) allocations on a
  per-request path). Hoisted to build the Set once. Measured
  4000x4000: 665.92ms before, 3.90ms after.
- Important (contract accuracy): permissionsFor() only half-agrees
  with decide() — a narrow deny under a broad grant (e.g. role editor's
  "post:*" plus a deny on "post:delete") can't be represented in a flat
  Set, so the set still contains "post:*" while decide() correctly
  refuses "post:delete". Documented as NOT authoritative on the
  AuthzResolver interface, and pinned with a regression test asserting
  the divergence is deliberate.
- Minor: subject.id === "" was audited as subjectId: "" instead of
  omitted, so consoleAuditSink printed a blank subject= rather than
  subject=anonymous. Reused the same non-empty-string guard as the
  decide() path.
- Important (adjacent, advanced.ts): owner() compared subject[key] to
  resource[key] with Object.is without checking either side was
  present, so two absent ids (Object.is(undefined, undefined) ===
  true) satisfied ownership. Unreachable before this task, but C1 now
  runs bound policies for anonymous/empty subjects, putting this on a
  live path. Fixed to deny whenever either side is undefined or null.

Every fix's regression test was verified by reverting the fix and
confirming the test fails against the pre-fix code before restoring.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 18:15:34 +05:30
ClintchizandClaude Opus 5 d6a2d05407 docs: hoist the deny set and document permissionsFor's limits
Two issues the Task 6 re-review raised against the fix diff.

permissionsFor rebuilt the deny Set inside its loop over granted entries,
making it O(grants x denies) allocations on a per-request path. Measured
632ms at 4000x4000, ~100% of it in repeated Set construction. Hoisted.

permissionsFor also only half-delivers on "the obvious composition agrees
with decide()". A narrow deny beneath a broad grant is not representable in
a Set of strings - the set keeps post:* while decide() correctly refuses
post:delete - so callers that match against the set would offer actions the
server rejects. Documented the limit on the interface and pointed callers at
decide()/can()/filterCan() for per-action gating.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 18:09:34 +05:30
ClintchizandClaude Opus 5 ae37c9b57a fix(authz): close fail-open engine gaps found in review
Coordinator review of Task 6's resolution engine (plan amendment
86b3dc1e) found two critical and four important defects, all inherited
from the brief's original engine snippet:

- C1: anonymous callers on a public permission returned allow before
  running bound policies, so the least-trusted caller got the weakest
  evaluation. Policies now run for anonymous subjects too.
- C2: the policy verdict check was a truthiness test (`!verdict.allowed`),
  so a policy returning `{allowed: "yes"}` granted access. Now requires
  `verdict?.allowed === true` exactly, and no longer spreads the raw
  verdict into the decision (which leaked arbitrary policy fields).
- I1: a binding naming a policy the catalog doesn't have was silently
  `continue`d, granting whatever the policy was meant to guard. Now
  denies with "Policy unavailable".
- I3: denies were checked by exact string equality, so a wildcard deny
  (e.g. "post:*") was accepted and silently did nothing. Denies now go
  through the same depth-aware wildcard matching as grants, via the new
  exported `deniedBy()`.
- I2: `permissionsFor` now subtracts denied entries so it agrees with
  `decide()` — needed for Task 7's UI gating to compose correctly.
- I4: non-string/empty `subject.id` (0, "", 123, {}) no longer silently
  falls back to anonymous; it denies with "Invalid subject". `subject:
  null` (no subject at all) remains genuinely anonymous.

Added six regression tests, each verified by reverting its fix and
confirming the test fails against the old code before restoring.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 17:58:28 +05:30
ClintchizandClaude Opus 5 86b3dc1e6a docs: close two auth bypasses and four fail-open paths in the Task 6 engine snippet
The plan's engine had a genuine authorization bypass and several fail-open
branches. Task 7 builds can() on this, so the source of truth is fixed before
that lands.

CRITICAL - anonymous callers bypassed every bound policy on a public:true
permission: the anonymous branch returned allow before the policy loop. A
permission marked "public, but not when embargoed" was fully open to
unauthenticated traffic, and the least-trusted caller got the weakest
evaluation. Policies now run on the anonymous path too; public relaxes the
identity requirement, never the policy requirement.

CRITICAL - the policy verdict check was truthiness-based, not an identity
check, so a policy returning {allowed: "yes"} or {allowed: 1} granted access.
It now compares against true.

A binding naming a policy the catalog lacks was skipped, granting whatever
the policy guarded; it now denies. Falsy and non-string subject ids fell
through to the anonymous path - {id: 0} became anonymous and {id: 123} reached
the store as a lookup key; only a non-empty string now identifies a subject.

Two design forks, ruled by the human: denies honour wildcards, so denying
"post:*" blocks post:delete instead of being accepted and doing nothing; and
permissionsFor subtracts denies, so composing it with permissionMatches
agrees with decide() rather than silently losing deny precedence.

Adds deniedBy() and six regression tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 17:52:32 +05:30
ClintchizandClaude Opus 5 c499f136fd docs: fix self-contradictory audit test in the Task 6 plan snippet
The 'denials are audited' test assigned role editor, which holds post:*, so
decide(post:delete) was legitimately an ALLOW under the wildcard rule the
same task specifies. The test then asserted one audited denial and got zero.
Switched to moderator (post:comment:*), which genuinely lacks post:delete.

Caught by the Task 6 implementer running the transcribed test against the
transcribed implementation. Plan-origin defect, fixed under standing
authority.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 17:41:11 +05:30
Clintchiz 6d8b6daba9 feat(authz): add resolution engine with deny-wins precedence and fail-closed errors 2026-08-04 17:40:19 +05:30
Clintchiz d7509421c7 fix(authz): widen logSafe to strip NEL and Unicode line separators
U+0085 (NEL), U+2028 (LINE SEPARATOR), and U+2029 (PARAGRAPH SEPARATOR)
are treated as line terminators by some log shippers and by JS's own
lexical grammar (and are not escaped by JSON.stringify by default), so
they could still be used to forge audit log entries even after the
initial C0/DEL fix. logSafe now strips all five categories.
2026-08-04 17:29:46 +05:30
ClintchizandClaude Opus 5 d609a41222 docs: widen logSafe to Unicode line separators in the Task 5 plan snippet
The re-review confirmed the log-injection fix works for C0 and DEL, but
U+0085 (NEL) and U+2028/U+2029 pass through. Those are line terminators to
some log shippers and to JavaScript's own lexical grammar, so they can still
split a record downstream.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 17:26:02 +05:30
Clintchiz e710756baf fix(authz): sanitize control characters in console audit sink
Prevents audit log injection: subjectId, tenantId, and reason trace back
to request input, so an unsanitized newline could forge a second,
fully-formed audit line indistinguishable from a real entry. Adds
logSafe() to strip control characters before interpolation and logs the
previously-missing policy field.
2026-08-04 17:21:09 +05:30
ClintchizandClaude Opus 5 ba83038d8d docs: fix audit-log injection in the Task 5 plan snippet
consoleAuditSink interpolated subjectId, tenantId and reason straight into
the log line. A newline in any of them forges a second entry that reads as a
genuine audit record - the reviewer produced a fake
'[wrnexus:authz] allow admin:everything subject=root' line. Those values
trace back to request input.

Interpolated fields now go through logSafe(), which replaces control
characters. Adds the missing coverage the review flagged: consoleAuditSink
injection, malformed-sink handling, and memoryAuditSink.clear().

Plan-origin defect, fixed under standing authority to amend the plan.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 17:19:05 +05:30
Clintchiz f033197850 feat(authz): add pluggable authorization audit sink 2026-08-04 17:10:03 +05:30
Clintchiz dc0771308a fix(authz): eliminate cache-key collision in cachedPermissionStore
The scope-prefix concatenation cacheKey used a bare U+FFFD separator with
no escaping, so an adversarial subject/tenant id containing that character
could collide with a different subject/tenant pair and leak cached roles
across tenants. Switch to JSON.stringify([scopeKey, subjectId]) for an
unambiguous key.

Also replace the untested key.endsWith() substring sweep used to
invalidate a subject across all tenants on a global write with an
explicit bySubject index, and add test coverage for both the collision
and the cross-tenant invalidation sweep.
2026-08-04 17:04:19 +05:30
ClintchizandClaude Opus 5 83f2951035 docs: fix cache-key collision in the Task 4 plan snippet
The plan's cachedPermissionStore used scopeKey + U+FFFD + subjectId as a
cache key with no escaping, so ('a', 'b<sep>c') and ('a<sep>b', 'c') collide
and one subject is served another's permissions. Subject and tenant ids are
unconstrained strings, so nothing prevented it.

Key is now JSON-encoded, and the global-write sweep tracks keys per subject
instead of substring-matching. Adds the two regression tests that were
missing: cross-tenant invalidation on a global write, and key collision.

Ruled by the human as plan-mandated; source of truth amended so a re-run of
the plan does not reintroduce the defect.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 17:00:35 +05:30
Clintchiz 4362d49770 feat(authz): add caching decorator for PermissionStore 2026-08-04 16:47:50 +05:30
Clintchiz a01b7bc99e fix(authz): cover grant/deny scope isolation and revoke scope-isolation in conformance suite 2026-08-04 16:43:08 +05:30
ClintchizandClaude Opus 5 9b6b970cae chore: exclude the SDD scratch workspace from prettier
.superpowers/ holds git-ignored controller artifacts (briefs, reports,
review packages). Prettier still walked it, so format:check — and with it
check:production — failed on scratch markdown.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 16:38:24 +05:30
Clintchiz 1849213ce4 feat(authz): add PermissionStore contract with memory adapter and conformance suite 2026-08-04 16:36:57 +05:30
Clintchiz d694dda320 feat(authz): merge declaration modules into a frozen catalog 2026-08-04 16:30:41 +05:30
Clintchiz 212fdaa5b5 feat(authz): add defineAuthz declaration registry 2026-08-04 16:26:17 +05:30
ClintchizandClaude Opus 5 0ac648bc26 docs: resolve two pre-flight conflicts in the authz plan
- Global Constraints said the change was additive while Task 8 changed
  authorizeDecision's 403 body. Ruled: the security fix governs; the
  constraint now names it as the one approved exception.
- Task 6 defined permissionsFor and then re-implemented it inline in
  decide. Both now call a single loadEffective helper.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 16:24:07 +05:30
ClintchizandClaude Opus 5 10da210b0a docs: implementation plan for the authz permissions system
Fifteen TDD tasks covering phases 1-3 of the approved design: registry,
catalog merge, PermissionStore with a shared conformance suite, caching
decorator, audit sink, resolution engine, request middleware and guards,
router discovery, database adapter, codegen, and the wrnexus authz CLI.

Phases 4 (.wrn view can()) and 5 (admin UI) are documented as deferred with
the reason each needs its own design pass.

Also folds in the authorizeDecision disclosure fix as Task 8, since the new
guards share its 403 shape.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 16:10:37 +05:30
ClintchizandClaude Opus 5 b209936f86 docs: design for the authz permissions system
Separates declaration (what permissions, roles, policies and attributes exist)
from assignment (who holds what), building on the decision primitives already
in advanced.ts rather than replacing them.

Covers the registry and app/authz discovery, the PermissionStore interface
with memory and db adapters, tenant-scoped assignments meeting the existing
TenantMembership, deny-wins precedence, fail-closed behaviour, the audit sink,
codegen and CLI introspection, and the seam for propagating subject context to
the inter-app communication system.

Records two decisions worth keeping: cross-app sharing needs no runtime
catalog distribution (declarations are static code in the shared package;
only assignments are shared, via the database), and can() stays off Context
to avoid a core -> authz dependency cycle.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 15:57:22 +05:30
ClintchizandClaude Opus 5 c64434a131 fix(security): close SSRF, credential-leak, and auth bypass findings in 0.8.4
Audit of 0.8.4 found the repo's own gates green, so these came from manual
review; each is covered by a new regression test.

security/fetch.ts
- safeFetch re-attached Authorization/Cookie on a same-origin redirect that
  followed a cross-origin hop (a -> b -> b), handing credentials to the second
  host. Compare against the origin the caller trusted, not the previous hop.
- The private-network guard resolved the host, approved it, then let fetch
  resolve again, so a low-TTL record could answer public for the check and
  private for the connection. Pin the connection to the validated address,
  preserving Host and TLS serverName. Opt out with pinDns: false.
- 0:0:0:0:0:ffff:127.0.0.1, ::ffff:7f00:1 and fec0::1 were not treated as
  private. Add uncompressed IPv4-mapped forms, site-local IPv6, 198.18/15
  and 192.0.0/24.

security/url.ts
- sanitizeUrl returned "//evil.com" verbatim via the relative-path fast path,
  bypassing the host checks it had just run; in an href that navigates
  cross-origin. Resolve protocol-relative input instead.

dev-server/gateway.ts
- Malformed base64 in an Authorization header threw out of checkAuth on an
  unauthenticated path. Fail closed.
- split(":", 2) truncated passwords at the first colon, so a password
  containing ":" could never authenticate.
- The credential compare short-circuited on length mismatch, leaking length
  by timing. Extracted as verifyBasicAuth so it is testable.

authz/index.ts
- Namespace wildcards only matched the first segment, so "post:comment:*"
  did not grant "post:comment:delete". Match at every depth.

uploader/operations.ts
- Validate transcoder dimensions and bitrate rather than trusting the declared
  type, and reject ".." path segments.

package.json
- The brace-expansion override pinned 5.0.8, which is inside the advisory
  range >=4.0.0 <5.0.9. Bump to 5.0.9; bun audit is now clean.

Verified: check:production passes (typecheck, lint, 1033 tests, format,
ASVS, public-API baseline, editor checks).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 15:57:22 +05:30
245 changed files with 29199 additions and 9217 deletions
+16
View File
@@ -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
+5
View File
@@ -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-*/
+3
View File
@@ -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/
+31 -2
View File
@@ -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": {
@@ -129,6 +134,10 @@
"packages/authz": {
"name": "@wrnexus/authz",
"version": "0.8.4",
"dependencies": {
"@wrnexus/core": "workspace:*",
"@wrnexus/db": "workspace:*",
},
},
"packages/benchmark": {
"name": "@wrnexus/benchmark",
@@ -163,6 +172,7 @@
"wrnexus": "src/index.ts",
},
"dependencies": {
"@wrnexus/authz": "workspace:*",
"@wrnexus/compiler": "workspace:*",
"@wrnexus/core": "workspace:*",
"@wrnexus/csr": "workspace:*",
@@ -223,6 +233,7 @@
"name": "@wrnexus/dev-server",
"version": "0.8.4",
"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:*",
@@ -434,6 +446,21 @@
"@wrnexus/core": "workspace:*",
},
},
"packages/rpc": {
"name": "@wrnexus/rpc",
"version": "0.8.4",
"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",
@@ -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=="],
+500
View File
@@ -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 | 12 weeks |
| 2 | 0.9 | Distributed rate limiting + live security audit | `@wrnexus/ratelimit-redis` | 23 weeks |
| 3 | 0.10 | Product-critical DX packages | `@wrnexus/mail`, `@wrnexus/flags` | 46 weeks |
| 4 | 0.11 | Search + AI pairing | `@wrnexus/search` | 34 weeks |
| 5 | 0.12 | Enterprise/government readiness | SAML in `@wrnexus/auth`, `@wrnexus/compliance` | 57 weeks |
| 6 | 1.0 | Monetization + ecosystem | `@wrnexus/billing`, public release strategy | 610 weeks |
Total: roughly 68 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:** ~23 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:** ~12 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:** ~34 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:** ~45 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:** ~56 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:** ~46 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:** ~23 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 24 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 13 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
+80
View File
@@ -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 24 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
View File
@@ -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",
+11 -11
View File
@@ -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",
@@ -64,13 +64,13 @@
"packages/ui/components/MegaMenu.wrn": "4a084eaf6aae77bb9023d2f3589bc6b80119b9be63982a90a80f9d280cc9c0a5",
"packages/ui/components/MetricCard.wrn": "6451182739298691908f68258c0250cce2a78b0dc27c97115579ece30d7d9f92",
"packages/ui/components/MetricGrid.wrn": "6018a98c10628ed240ed236ed916c0ff60994d192c3aadafd10bc876be0f9364",
"packages/ui/components/Modal.wrn": "1821428492e510403dd029c4766e71272899299f65141ae4fdaf09c4a26719e5",
"packages/ui/components/Modal.wrn": "59d4ae9d6dd2700692d9edecfe53ee868e9f864363a4885961d1813cb2bee51f",
"packages/ui/components/Nav.wrn": "78f215c94caf68e0968449a23e23bd3409a689e0c52a6c1770aa8373c767d080",
"packages/ui/components/Navbar.wrn": "e68f9d3643e500e43124e9c7a6d4c7f6722657377ea7313f3e3cf5093a81b360",
"packages/ui/components/PageHeader.wrn": "adb3bed81ce040044405e35a52d0304f49d5a0d162f923fbd54a4242a25c7362",
"packages/ui/components/PageHeader.wrn": "0761235a4924eec09877be40b292d27b70db22d210be7d8e989493165c20df86",
"packages/ui/components/Pagination.wrn": "9169e724f89992dacd10e9492a95438c8016affb39c0fd17a4f6fe26bd21d8b4",
"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",
@@ -96,9 +96,10 @@
"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"
}
}
+123 -8
View File
@@ -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;
+53 -8
View File
@@ -1,4 +1,4 @@
// WRN editor extension source hash: 825c406dea4b196976b1b6e53b3c71c3263bd63f403acdaf2fe7e5ad174275da
// WRN editor extension source hash: 174fee91d6ec09c86f71f1204aeb46a20e5a563cc8af1484051d68dde01c8e10
// 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;
+25 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env node
// WRN editor language server source hash: 5397c7912894dd5ab330f6fafc0f4093adbb94d95c23366ba7883605a724a7ae
// WRN editor language server source hash: de46b5767d808fb99bb470ac9ccd0bc9504aa9ab40f0cf8ad9ec4e945303200d
// 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" },
);
+2
View File
@@ -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:*");
});
+90
View File
@@ -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,
});
}
+26
View File
@@ -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(),
});
}
-121
View File
@@ -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 {
}
}
+427 -3
View File
@@ -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&#39;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} />
}
}
+156
View File
@@ -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" />
}
}
+3
View File
@@ -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>;
+11
View File
@@ -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>&lt;Accordion
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
defaultOpen='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</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>&lt;Accordion
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
defaultOpen='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -739,7 +739,7 @@ page AccordionDetail {
size="sm"
variant="secondary"
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
defaultOpen='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -975,7 +975,7 @@ page AccordionDetail {
size="lg"
variant="success"
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
defaultOpen='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -1198,21 +1198,21 @@ page AccordionDetail {
@change='console.log(payload)'
@open='console.log(payload)'
@close='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Accordion\"], [data-component=\"Accordion\"]")
if (component) registerOutputHandler(component, "change", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "open", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "open", (payload) =&gt; <span>&#123;</span>
console.log("open", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "close", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "close", (payload) =&gt; <span>&#123;</span>
console.log("close", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>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='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</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='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</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='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
variant="secondary"
/&gt;</code></pre>
@@ -541,7 +541,7 @@ page AdvancedDatePickerDetail {
title="Advanced Advanced Date Picker"
description="A richer configuration demonstrating additional content and hierarchy."
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
variant="success"
/&gt;</code></pre>
@@ -659,29 +659,29 @@ page AdvancedDatePickerDetail {
@open='console.log(payload)'
@close='console.log(payload)'
@clear='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"AdvancedDatePicker\"], [data-component=\"AdvancedDatePicker\"]")
if (component) registerOutputHandler(component, "input", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "input", (payload) =&gt; <span>&#123;</span>
console.log("input", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "change", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "open", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "open", (payload) =&gt; <span>&#123;</span>
console.log("open", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "close", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "close", (payload) =&gt; <span>&#123;</span>
console.log("close", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "clear", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "clear", (payload) =&gt; <span>&#123;</span>
console.log("clear", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"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='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</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='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</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='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
variant="secondary"
/&gt;</code></pre>
@@ -541,7 +541,7 @@ page AdvancedRangeSliderDetail {
title="Advanced Advanced Range Slider"
description="A richer configuration demonstrating additional content and hierarchy."
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
variant="success"
/&gt;</code></pre>
@@ -658,25 +658,25 @@ page AdvancedRangeSliderDetail {
@change='console.log(payload)'
@start='console.log(payload)'
@end='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"AdvancedRangeSlider\"], [data-component=\"AdvancedRangeSlider\"]")
if (component) registerOutputHandler(component, "input", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "input", (payload) =&gt; <span>&#123;</span>
console.log("input", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "change", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "start", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "start", (payload) =&gt; <span>&#123;</span>
console.log("start", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "end", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "end", (payload) =&gt; <span>&#123;</span>
console.log("end", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"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='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
actions='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
showIcon="true"
dismissible="true"
@@ -518,7 +518,7 @@ page AlertDetail {
title="Alert example"
description="A polished default alert for a modern application."
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
actions='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</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='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
actions='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</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='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
actions='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</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>&lt;Alert
@dismiss='console.log(payload)'
@action='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Alert\"], [data-component=\"Alert\"]")
if (component) registerOutputHandler(component, "dismiss", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "dismiss", (payload) =&gt; <span>&#123;</span>
console.log("dismiss", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "action", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "action", (payload) =&gt; <span>&#123;</span>
console.log("action", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"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>&lt;AnnouncementBar
@dismiss='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"AnnouncementBar\"], [data-component=\"AnnouncementBar\"]")
if (component) registerOutputHandler(component, "dismiss", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "dismiss", (payload) =&gt; <span>&#123;</span>
console.log("dismiss", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>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)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"AuthForm\"], [data-component=\"AuthForm\"]")
if (component) registerOutputHandler(component, "submit", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "submit", (payload) =&gt; <span>&#123;</span>
console.log("submit", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "change", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "input", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "input", (payload) =&gt; <span>&#123;</span>
console.log("input", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "focus", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "focus", (payload) =&gt; <span>&#123;</span>
console.log("focus", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "blur", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "blur", (payload) =&gt; <span>&#123;</span>
console.log("blur", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>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='[
&#123;
<span>&#123;</span>
"label": "Team members",
"values": [
"5",
"Unlimited",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Support",
"values": [
"Email",
"Priority",
"Dedicated"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Audit history",
"values": [
"7 days",
"90 days",
"Custom"
]
&#125;
<span>&#125;</span>
]'&gt;
&lt;div data-slot="aside-extra"&gt;
&lt;div class="showcase-slot"&gt;aside-extra slot&lt;/div&gt;
@@ -125,30 +125,30 @@ page AuthSplitLayoutDetail {
title="Auth Split Layout example"
description="A polished default auth split layout for a modern application."
features='[
&#123;
<span>&#123;</span>
"label": "Team members",
"values": [
"5",
"Unlimited",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Support",
"values": [
"Email",
"Priority",
"Dedicated"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Audit history",
"values": [
"7 days",
"90 days",
"Custom"
]
&#125;
<span>&#125;</span>
]'&gt;
&lt;div data-slot="aside-extra"&gt;
&lt;div class="showcase-slot"&gt;aside-extra slot&lt;/div&gt;
@@ -178,30 +178,30 @@ page AuthSplitLayoutDetail {
title="Compact Auth Split Layout"
description="A compact configuration for dashboards and dense product surfaces."
features='[
&#123;
<span>&#123;</span>
"label": "Team members",
"values": [
"5",
"Unlimited",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Support",
"values": [
"Email",
"Priority",
"Dedicated"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Audit history",
"values": [
"7 days",
"90 days",
"Custom"
]
&#125;
<span>&#125;</span>
]'&gt;
&lt;div data-slot="aside-extra"&gt;
&lt;div class="showcase-slot"&gt;aside-extra slot&lt;/div&gt;
@@ -231,30 +231,30 @@ page AuthSplitLayoutDetail {
title="Advanced Auth Split Layout"
description="A richer configuration demonstrating additional content and hierarchy."
features='[
&#123;
<span>&#123;</span>
"label": "Team members",
"values": [
"5",
"Unlimited",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Support",
"values": [
"Email",
"Priority",
"Dedicated"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Audit history",
"values": [
"7 days",
"90 days",
"Custom"
]
&#125;
<span>&#125;</span>
]'&gt;
&lt;div data-slot="aside-extra"&gt;
&lt;div class="showcase-slot"&gt;aside-extra slot&lt;/div&gt;
@@ -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>&lt;AvatarGroup
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
overflowLabel="Avatar Group"
/&gt;</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>&lt;AvatarGroup
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
overflowLabel="Avatar Group"
/&gt;</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>&lt;AvatarGroup
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</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>&lt;AvatarGroup
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</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>&lt;AvatarGroup
@overflow='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"AvatarGroup\"], [data-component=\"AvatarGroup\"]")
if (component) registerOutputHandler(component, "overflow", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "overflow", (payload) =&gt; <span>&#123;</span>
console.log("overflow", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>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)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Avatar\"], [data-component=\"Avatar\"]")
if (component) registerOutputHandler(component, "load", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "load", (payload) =&gt; <span>&#123;</span>
console.log("load", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "error", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "error", (payload) =&gt; <span>&#123;</span>
console.log("error", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "click", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "click", (payload) =&gt; <span>&#123;</span>
console.log("click", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>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>&lt;Badge
@dismiss='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Badge\"], [data-component=\"Badge\"]")
if (component) registerOutputHandler(component, "dismiss", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "dismiss", (payload) =&gt; <span>&#123;</span>
console.log("dismiss", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>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>&lt;Breadcrumb
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</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>&lt;Breadcrumb
items='[
&#123;
<span>&#123;</span>
"label": "Home",
"href": "/",
"icon": "icon-[lucide--house]"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Components",
"href": "/base"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Page header",
"current": true
&#125;
<span>&#125;</span>
]'
showHome="true"
homeLabel="Breadcrumb"
@@ -324,19 +324,19 @@ page BreadcrumbDetail {
<pre><code>&lt;Breadcrumb
label="Compact example"
items='[
&#123;
<span>&#123;</span>
"label": "Home",
"href": "/",
"icon": "icon-[lucide--house]"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Components",
"href": "/base"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Page header",
"current": true
&#125;
<span>&#125;</span>
]'
showHome="true"
homeLabel="Compact example"
@@ -363,19 +363,19 @@ page BreadcrumbDetail {
<pre><code>&lt;Breadcrumb
label="Advanced example"
items='[
&#123;
<span>&#123;</span>
"label": "Home",
"href": "/",
"icon": "icon-[lucide--house]"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Components",
"href": "/base"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Page header",
"current": true
&#125;
<span>&#125;</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>&lt;Breadcrumb
@select='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Breadcrumb\"], [data-component=\"Breadcrumb\"]")
if (component) registerOutputHandler(component, "select", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span>
console.log("select", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>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>&lt;ButtonGroup
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</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>&lt;ButtonGroup
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</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>&lt;ButtonGroup
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</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>&lt;ButtonGroup
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
value="sample-3"
size="lg"
@@ -663,21 +663,21 @@ page ButtonGroupDetail {
@click='console.log(payload)'
@select='console.log(payload)'
@change='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"ButtonGroup\"], [data-component=\"ButtonGroup\"]")
if (component) registerOutputHandler(component, "click", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "click", (payload) =&gt; <span>&#123;</span>
console.log("click", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "select", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span>
console.log("select", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "change", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>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)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Button\"], [data-component=\"Button\"]")
if (component) registerOutputHandler(component, "click", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "click", (payload) =&gt; <span>&#123;</span>
console.log("click", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "focus", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "focus", (payload) =&gt; <span>&#123;</span>
console.log("focus", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "blur", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "blur", (payload) =&gt; <span>&#123;</span>
console.log("blur", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>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='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
showPagination="true"
showCounter="true"
@@ -300,7 +300,7 @@ page CarouselDetail {
title="Carousel example"
description="A polished default carousel for a modern application."
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</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='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
showPagination="true"
showCounter="true"
@@ -565,7 +565,7 @@ page CarouselDetail {
title="Advanced Carousel"
description="A richer configuration demonstrating additional content and hierarchy."
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
showPagination="true"
showCounter="true"
@@ -691,49 +691,49 @@ page CarouselDetail {
@reachEnd='console.log(payload)'
@dragStart='console.log(payload)'
@dragEnd='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Carousel\"], [data-component=\"Carousel\"]")
if (component) registerOutputHandler(component, "initialize", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "initialize", (payload) =&gt; <span>&#123;</span>
console.log("initialize", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "change", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "previous", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "previous", (payload) =&gt; <span>&#123;</span>
console.log("previous", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "next", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "next", (payload) =&gt; <span>&#123;</span>
console.log("next", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "play", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "play", (payload) =&gt; <span>&#123;</span>
console.log("play", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "pause", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "pause", (payload) =&gt; <span>&#123;</span>
console.log("pause", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "reachStart", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "reachStart", (payload) =&gt; <span>&#123;</span>
console.log("reachStart", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "reachEnd", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "reachEnd", (payload) =&gt; <span>&#123;</span>
console.log("reachEnd", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "dragStart", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "dragStart", (payload) =&gt; <span>&#123;</span>
console.log("dragStart", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "dragEnd", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "dragEnd", (payload) =&gt; <span>&#123;</span>
console.log("dragEnd", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>""</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='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -282,7 +282,7 @@ page ChartDetail {
title="Chart example"
description="A polished default chart for a modern application."
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -411,7 +411,7 @@ page ChartDetail {
title="Compact Chart"
description="A compact configuration for dashboards and dense product surfaces."
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
variant="secondary"
/&gt;</code></pre>
@@ -541,7 +541,7 @@ page ChartDetail {
title="Advanced Chart"
description="A richer configuration demonstrating additional content and hierarchy."
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
variant="success"
/&gt;</code></pre>
@@ -657,21 +657,21 @@ page ChartDetail {
@select='console.log(payload)'
@dataPointClick='console.log(payload)'
@legendToggle='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Chart\"], [data-component=\"Chart\"]")
if (component) registerOutputHandler(component, "select", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span>
console.log("select", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "dataPointClick", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "dataPointClick", (payload) =&gt; <span>&#123;</span>
console.log("dataPointClick", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "legendToggle", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "legendToggle", (payload) =&gt; <span>&#123;</span>
console.log("legendToggle", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Chart"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</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='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</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='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</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='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</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='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
showAvatars="true"
showMetadata="true"
@@ -674,25 +674,25 @@ page ChatBubbleDetail {
@messageClick='console.log(payload)'
@avatarClick='console.log(payload)'
@linkClick='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"ChatBubble\"], [data-component=\"ChatBubble\"]")
if (component) registerOutputHandler(component, "action", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "action", (payload) =&gt; <span>&#123;</span>
console.log("action", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "messageClick", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "messageClick", (payload) =&gt; <span>&#123;</span>
console.log("messageClick", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "avatarClick", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "avatarClick", (payload) =&gt; <span>&#123;</span>
console.log("avatarClick", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "linkClick", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "linkClick", (payload) =&gt; <span>&#123;</span>
console.log("linkClick", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>""</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='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
options='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -517,7 +517,7 @@ page CheckboxDetail {
placeholder="Enter a sample value"
value="sample-1"
values='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
options='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -757,7 +757,7 @@ page CheckboxDetail {
icon="icon-[lucide--arrow-right]"
value="sample-2"
values='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
options='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -997,7 +997,7 @@ page CheckboxDetail {
icon="icon-[lucide--trash-2]"
value="sample-3"
values='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
options='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -1222,29 +1222,29 @@ page CheckboxDetail {
@focus='console.log(payload)'
@blur='console.log(payload)'
@invalid='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Checkbox\"], [data-component=\"Checkbox\"]")
if (component) registerOutputHandler(component, "input", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "input", (payload) =&gt; <span>&#123;</span>
console.log("input", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "change", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "focus", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "focus", (payload) =&gt; <span>&#123;</span>
console.log("focus", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "blur", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "blur", (payload) =&gt; <span>&#123;</span>
console.log("blur", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "invalid", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "invalid", (payload) =&gt; <span>&#123;</span>
console.log("invalid", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>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='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -282,7 +282,7 @@ page ClipboardDetail {
title="Clipboard example"
description="A polished default clipboard for a modern application."
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -411,7 +411,7 @@ page ClipboardDetail {
title="Compact Clipboard"
description="A compact configuration for dashboards and dense product surfaces."
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
variant="secondary"
/&gt;</code></pre>
@@ -541,7 +541,7 @@ page ClipboardDetail {
title="Advanced Clipboard"
description="A richer configuration demonstrating additional content and hierarchy."
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
variant="success"
/&gt;</code></pre>
@@ -657,21 +657,21 @@ page ClipboardDetail {
@copy='console.log(payload)'
@success='console.log(payload)'
@error='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Clipboard\"], [data-component=\"Clipboard\"]")
if (component) registerOutputHandler(component, "copy", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "copy", (payload) =&gt; <span>&#123;</span>
console.log("copy", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "success", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "success", (payload) =&gt; <span>&#123;</span>
console.log("success", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "error", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "error", (payload) =&gt; <span>&#123;</span>
console.log("error", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Clipboard"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</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>&lt;Collapse
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
initialOpenIndexes='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
ariaLabel="Collapse 1 example"
/&gt;</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>&lt;Collapse
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
initialOpenIndexes='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
ariaLabel="Collapse 1 example"
/&gt;</code></pre>
@@ -731,7 +731,7 @@ page CollapseDetail {
<pre><code>&lt;Collapse
size="sm"
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
initialOpenIndexes='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
ariaLabel="Collapse 2 example"
/&gt;</code></pre>
@@ -967,7 +967,7 @@ page CollapseDetail {
<pre><code>&lt;Collapse
size="lg"
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
initialOpenIndexes='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
ariaLabel="Collapse 3 example"
/&gt;</code></pre>
@@ -1191,21 +1191,21 @@ page CollapseDetail {
@toggle='console.log(payload)'
@open='console.log(payload)'
@close='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Collapse\"], [data-component=\"Collapse\"]")
if (component) registerOutputHandler(component, "toggle", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "toggle", (payload) =&gt; <span>&#123;</span>
console.log("toggle", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "open", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "open", (payload) =&gt; <span>&#123;</span>
console.log("open", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "close", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "close", (payload) =&gt; <span>&#123;</span>
console.log("close", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>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)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"ColorPicker\"], [data-component=\"ColorPicker\"]")
if (component) registerOutputHandler(component, "input", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "input", (payload) =&gt; <span>&#123;</span>
console.log("input", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "change", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "focus", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "focus", (payload) =&gt; <span>&#123;</span>
console.log("focus", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "blur", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "blur", (payload) =&gt; <span>&#123;</span>
console.log("blur", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>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='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -282,7 +282,7 @@ page ConfettiDetail {
title="Confetti example"
description="A polished default confetti for a modern application."
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -411,7 +411,7 @@ page ConfettiDetail {
title="Compact Confetti"
description="A compact configuration for dashboards and dense product surfaces."
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
variant="secondary"
/&gt;</code></pre>
@@ -541,7 +541,7 @@ page ConfettiDetail {
title="Advanced Confetti"
description="A richer configuration demonstrating additional content and hierarchy."
items='[
&#123;
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</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": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
variant="success"
/&gt;</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>&lt;Confetti
@start='console.log(payload)'
@complete='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Confetti\"], [data-component=\"Confetti\"]")
if (component) registerOutputHandler(component, "start", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "start", (payload) =&gt; <span>&#123;</span>
console.log("start", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "complete", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "complete", (payload) =&gt; <span>&#123;</span>
console.log("complete", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Confetti"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main>
<aside class="detail-aside">
@@ -46,7 +46,7 @@ page ContextMenuDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;ContextMenu
items='[
&#123;
<span>&#123;</span>
"id": "context-menu-0-1",
"key": "context-menu-0-1",
"value": "primary",
@@ -75,31 +75,31 @@ page ContextMenuDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "context-menu-0-2",
"key": "context-menu-0-2",
"value": "secondary",
@@ -128,30 +128,30 @@ page ContextMenuDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
title="Context Menu example"
description="A polished default context menu for a modern application."
@@ -216,27 +216,27 @@ page ContextMenuDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;ContextMenu
items='[
&#123;
<span>&#123;</span>
"label": "Open dashboard",
"description": "View the operational workspace",
"value": "dashboard",
"icon": "icon-[lucide--layout-dashboard]"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Share record",
"value": "share",
"icon": "icon-[lucide--share-2]",
"shortcut": "Ctrl S"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"type": "divider"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Delete record",
"value": "delete",
"icon": "icon-[lucide--trash-2]",
"danger": true
&#125;
<span>&#125;</span>
]'
title="Record actions"
description="Right-click the target surface"
@@ -269,27 +269,27 @@ page ContextMenuDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;ContextMenu
items='[
&#123;
<span>&#123;</span>
"label": "Open dashboard",
"description": "View the operational workspace",
"value": "dashboard",
"icon": "icon-[lucide--layout-dashboard]"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Share record",
"value": "share",
"icon": "icon-[lucide--share-2]",
"shortcut": "Ctrl S"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"type": "divider"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Delete record",
"value": "delete",
"icon": "icon-[lucide--trash-2]",
"danger": true
&#125;
<span>&#125;</span>
]'
trigger="click"
align="center"
@@ -327,27 +327,27 @@ page ContextMenuDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;ContextMenu
items='[
&#123;
<span>&#123;</span>
"label": "Open dashboard",
"description": "View the operational workspace",
"value": "dashboard",
"icon": "icon-[lucide--layout-dashboard]"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Share record",
"value": "share",
"icon": "icon-[lucide--share-2]",
"shortcut": "Ctrl S"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"type": "divider"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Delete record",
"value": "delete",
"icon": "icon-[lucide--trash-2]",
"danger": true
&#125;
<span>&#125;</span>
]'
trigger="both"
placement="bottom-start"
@@ -375,25 +375,25 @@ page ContextMenuDetail {
@close='console.log(payload)'
@select='console.log(payload)'
@action='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"ContextMenu\"], [data-component=\"ContextMenu\"]")
if (component) registerOutputHandler(component, "open", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "open", (payload) =&gt; <span>&#123;</span>
console.log("open", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "close", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "close", (payload) =&gt; <span>&#123;</span>
console.log("close", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "select", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span>
console.log("select", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "action", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "action", (payload) =&gt; <span>&#123;</span>
console.log("action", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>open</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>defaultOpen</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>trigger</code></td><td>string</td><td><code>"contextmenu"</code></td><td>No</td></tr><tr><td><code>placement</code></td><td>string</td><td><code>"pointer"</code></td><td>No</td></tr><tr><td><code>align</code></td><td>string</td><td><code>"start"</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>"raised"</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>label</code></td><td>string</td><td><code>"Context menu"</code></td><td>No</td></tr><tr><td><code>closeOnSelect</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>closeOnOutside</code></td><td>boolean</td><td><code>true</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>minWidth</code></td><td>string</td><td><code>"14rem"</code></td><td>No</td></tr><tr><td><code>maxWidth</code></td><td>string</td><td><code>"20rem"</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">
@@ -124,21 +124,21 @@ page CopyMarkupDetail {
@copy='console.log(payload)'
@success='console.log(payload)'
@error='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"CopyMarkup\"], [data-component=\"CopyMarkup\"]")
if (component) registerOutputHandler(component, "copy", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "copy", (payload) =&gt; <span>&#123;</span>
console.log("copy", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "success", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "success", (payload) =&gt; <span>&#123;</span>
console.log("success", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "error", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "error", (payload) =&gt; <span>&#123;</span>
console.log("error", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Copy Markup"</code></td><td>No</td></tr><tr><td><code>name</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>value</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>placeholder</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>type</code></td><td>string</td><td><code>"text"</code></td><td>No</td></tr><tr><td><code>min</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>max</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>step</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>disabled</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>required</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main>
<aside class="detail-aside">
@@ -67,7 +67,7 @@ page CTASectionDetail {
visualTitle="CTASection example"
visualDescription="A polished default ctasection for a modern application."
visualItems='[
&#123;
<span>&#123;</span>
"id": "ctasection-0-1",
"key": "ctasection-0-1",
"value": "primary",
@@ -96,31 +96,31 @@ page CTASectionDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "ctasection-0-2",
"key": "ctasection-0-2",
"value": "secondary",
@@ -149,30 +149,30 @@ page CTASectionDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'&gt;
&lt;div data-slot="actions"&gt;
&lt;div class="showcase-slot"&gt;actions slot&lt;/div&gt;
@@ -338,7 +338,7 @@ page CTASectionDetail {
visualTitle="CTASection example"
visualDescription="A polished default ctasection for a modern application."
visualItems='[
&#123;
<span>&#123;</span>
"id": "ctasection-0-1",
"key": "ctasection-0-1",
"value": "primary",
@@ -367,31 +367,31 @@ page CTASectionDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "ctasection-0-2",
"key": "ctasection-0-2",
"value": "secondary",
@@ -420,30 +420,30 @@ page CTASectionDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
maxWidth="full"&gt;
&lt;div data-slot="actions"&gt;
@@ -489,7 +489,7 @@ page CTASectionDetail {
visualTitle="Compact CTASection"
visualDescription="A compact configuration for dashboards and dense product surfaces."
visualItems='[
&#123;
<span>&#123;</span>
"id": "ctasection-1-1",
"key": "ctasection-1-1",
"value": "primary",
@@ -518,31 +518,31 @@ page CTASectionDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "ctasection-1-2",
"key": "ctasection-1-2",
"value": "secondary",
@@ -571,30 +571,30 @@ page CTASectionDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'&gt;
&lt;div data-slot="actions"&gt;
&lt;div class="showcase-slot"&gt;actions slot&lt;/div&gt;
@@ -637,7 +637,7 @@ page CTASectionDetail {
visualTitle="Secure public access"
visualDescription="One account for requests, certificates, updates, and notifications."
visualItems='[
&#123;
<span>&#123;</span>
"id": "ctasection-2-1",
"key": "ctasection-2-1",
"value": "primary",
@@ -666,31 +666,31 @@ page CTASectionDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "ctasection-2-2",
"key": "ctasection-2-2",
"value": "secondary",
@@ -719,30 +719,30 @@ page CTASectionDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
maxWidth="full"
fullBleed="true"&gt;
@@ -103,13 +103,13 @@ page CustomScrollbarDetail {
</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>@scroll</code><span>Fires when the component emits the scroll event.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;CustomScrollbar
@scroll='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"CustomScrollbar\"], [data-component=\"CustomScrollbar\"]")
if (component) registerOutputHandler(component, "scroll", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "scroll", (payload) =&gt; <span>&#123;</span>
console.log("scroll", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>columns</code></td><td>number</td><td><code>2</code></td><td>No</td></tr><tr><td><code>gap</code></td><td>string</td><td><code>"md"</code></td><td>No</td></tr><tr><td><code>maxWidth</code></td><td>string</td><td><code>"xl"</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 DataMapDetail {
title="Data Map example"
description="A polished default data map for a modern application."
items='[
&#123;
<span>&#123;</span>
"id": "data-map-0-1",
"key": "data-map-0-1",
"value": "primary",
@@ -66,31 +66,31 @@ page DataMapDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "data-map-0-2",
"key": "data-map-0-2",
"value": "secondary",
@@ -119,30 +119,30 @@ page DataMapDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -282,7 +282,7 @@ page DataMapDetail {
title="Data Map example"
description="A polished default data map for a modern application."
items='[
&#123;
<span>&#123;</span>
"id": "data-map-0-1",
"key": "data-map-0-1",
"value": "primary",
@@ -311,31 +311,31 @@ page DataMapDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "data-map-0-2",
"key": "data-map-0-2",
"value": "secondary",
@@ -364,30 +364,30 @@ page DataMapDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -411,7 +411,7 @@ page DataMapDetail {
title="Compact Data Map"
description="A compact configuration for dashboards and dense product surfaces."
items='[
&#123;
<span>&#123;</span>
"id": "data-map-1-1",
"key": "data-map-1-1",
"value": "primary",
@@ -440,31 +440,31 @@ page DataMapDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "data-map-1-2",
"key": "data-map-1-2",
"value": "secondary",
@@ -493,30 +493,30 @@ page DataMapDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
variant="secondary"
/&gt;</code></pre>
@@ -541,7 +541,7 @@ page DataMapDetail {
title="Advanced Data Map"
description="A richer configuration demonstrating additional content and hierarchy."
items='[
&#123;
<span>&#123;</span>
"id": "data-map-2-1",
"key": "data-map-2-1",
"value": "primary",
@@ -570,31 +570,31 @@ page DataMapDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "data-map-2-2",
"key": "data-map-2-2",
"value": "secondary",
@@ -623,30 +623,30 @@ page DataMapDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
variant="success"
/&gt;</code></pre>
@@ -656,17 +656,17 @@ page DataMapDetail {
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@select</code><span>Fires when an item, option, card, or result is selected.</span></div><div><code>@change</code><span>Fires when the component's committed value or selection changes.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;DataMap
@select='console.log(payload)'
@change='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"DataMap\"], [data-component=\"DataMap\"]")
if (component) registerOutputHandler(component, "select", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span>
console.log("select", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "change", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Data Map"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main>
<aside class="detail-aside">
@@ -675,7 +675,7 @@ if (component) registerOutputHandler(component, "change", (payload) =&gt; &#123;
</div>
<nav class="detail-pagination">
<a href="/components/confetti"><small>Previous</small><strong>← Confetti</strong></a>
<a href="/components/data-table"><small>Next</small><strong>Data Table →</strong></a>
<a href="/components/drag-and-drop"><small>Next</small><strong>Drag And Drop →</strong></a>
</nav>
}
}
File diff suppressed because one or more lines are too long
@@ -142,38 +142,38 @@ page DatePickerDetail {
@focus='console.log(payload)'
@blur='console.log(payload)'
@invalid='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"DatePicker\"], [data-component=\"DatePicker\"]")
if (component) registerOutputHandler(component, "input", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "input", (payload) =&gt; <span>&#123;</span>
console.log("input", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "change", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "open", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "open", (payload) =&gt; <span>&#123;</span>
console.log("open", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "close", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "close", (payload) =&gt; <span>&#123;</span>
console.log("close", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "focus", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "focus", (payload) =&gt; <span>&#123;</span>
console.log("focus", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "blur", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "blur", (payload) =&gt; <span>&#123;</span>
console.log("blur", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "invalid", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "invalid", (payload) =&gt; <span>&#123;</span>
console.log("invalid", payload)
&#125;)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Date Picker"</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>value</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>placeholder</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>type</code></td><td>string</td><td><code>"date"</code></td><td>No</td></tr><tr><td><code>locale</code></td><td>string</td><td><code>"en-US"</code></td><td>No</td></tr><tr><td><code>firstDayOfWeek</code></td><td>number</td><td><code>0</code></td><td>No</td></tr><tr><td><code>months</code></td><td>string</td><td><code>[&#123; label: "Jan", value: "01" &#125;, &#123; label: "Feb", value: "02" &#125;, &#123; label: "Mar", value: "03" &#125;, &#123; label: "Apr", value: "04" &#125;, &#123; label: "May", value: "05" &#125;, &#123; label: "Jun", value: "06" &#125;, &#123; label: "Jul", value: "07" &#125;, &#123; label: "Aug", value: "08" &#125;, &#123; label: "Sep", value: "09" &#125;, &#123; label: "Oct", value: "10" &#125;, &#123; label: "Nov", value: "11" &#125;, &#123; label: "Dec", value: "12" &#125;]</code></td><td>No</td></tr><tr><td><code>days</code></td><td>string</td><td><code>[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]</code></td><td>No</td></tr><tr><td><code>years</code></td><td>string</td><td><code>[2024, 2025, 2026, 2027, 2028, 2029, 2030]</code></td><td>No</td></tr><tr><td><code>min</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>max</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>step</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>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>variant</code></td><td>string</td><td><code>"normal"</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>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Date Picker"</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>value</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>placeholder</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>type</code></td><td>string</td><td><code>"date"</code></td><td>No</td></tr><tr><td><code>locale</code></td><td>string</td><td><code>"en-US"</code></td><td>No</td></tr><tr><td><code>firstDayOfWeek</code></td><td>number</td><td><code>0</code></td><td>No</td></tr><tr><td><code>months</code></td><td>string</td><td><code>[<span>&#123;</span> label: "Jan", value: "01" <span>&#125;</span>, <span>&#123;</span> label: "Feb", value: "02" <span>&#125;</span>, <span>&#123;</span> label: "Mar", value: "03" <span>&#125;</span>, <span>&#123;</span> label: "Apr", value: "04" <span>&#125;</span>, <span>&#123;</span> label: "May", value: "05" <span>&#125;</span>, <span>&#123;</span> label: "Jun", value: "06" <span>&#125;</span>, <span>&#123;</span> label: "Jul", value: "07" <span>&#125;</span>, <span>&#123;</span> label: "Aug", value: "08" <span>&#125;</span>, <span>&#123;</span> label: "Sep", value: "09" <span>&#125;</span>, <span>&#123;</span> label: "Oct", value: "10" <span>&#125;</span>, <span>&#123;</span> label: "Nov", value: "11" <span>&#125;</span>, <span>&#123;</span> label: "Dec", value: "12" <span>&#125;</span>]</code></td><td>No</td></tr><tr><td><code>days</code></td><td>string</td><td><code>[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]</code></td><td>No</td></tr><tr><td><code>years</code></td><td>string</td><td><code>[2024, 2025, 2026, 2027, 2028, 2029, 2030]</code></td><td>No</td></tr><tr><td><code>min</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>max</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>step</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>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>variant</code></td><td>string</td><td><code>"normal"</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">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#date-picker-demo-1">Production default</a><a href="#date-picker-demo-2">Compact application</a><a href="#date-picker-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
@@ -44,7 +44,7 @@ page DeviceFrameDetail {
title="Device Frame example"
description="A polished default device frame for a modern application."
items='[
&#123;
<span>&#123;</span>
"id": "device-frame-0-1",
"key": "device-frame-0-1",
"value": "primary",
@@ -73,31 +73,31 @@ page DeviceFrameDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "device-frame-0-2",
"key": "device-frame-0-2",
"value": "secondary",
@@ -126,30 +126,30 @@ page DeviceFrameDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
frameTitle="Device Frame example"
/&gt;</code></pre>
@@ -290,7 +290,7 @@ page DeviceFrameDetail {
title="Device Frame example"
description="A polished default device frame for a modern application."
items='[
&#123;
<span>&#123;</span>
"id": "device-frame-0-1",
"key": "device-frame-0-1",
"value": "primary",
@@ -319,31 +319,31 @@ page DeviceFrameDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "device-frame-0-2",
"key": "device-frame-0-2",
"value": "secondary",
@@ -372,30 +372,30 @@ page DeviceFrameDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
frameTitle="Device Frame example"
/&gt;</code></pre>
@@ -420,7 +420,7 @@ page DeviceFrameDetail {
title="Compact Device Frame"
description="A compact configuration for dashboards and dense product surfaces."
items='[
&#123;
<span>&#123;</span>
"id": "device-frame-1-1",
"key": "device-frame-1-1",
"value": "primary",
@@ -449,31 +449,31 @@ page DeviceFrameDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "device-frame-1-2",
"key": "device-frame-1-2",
"value": "secondary",
@@ -502,30 +502,30 @@ page DeviceFrameDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
variant="secondary"
frameTitle="Compact Device Frame"
@@ -551,7 +551,7 @@ page DeviceFrameDetail {
title="Advanced Device Frame"
description="A richer configuration demonstrating additional content and hierarchy."
items='[
&#123;
<span>&#123;</span>
"id": "device-frame-2-1",
"key": "device-frame-2-1",
"value": "primary",
@@ -580,31 +580,31 @@ page DeviceFrameDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "device-frame-2-2",
"key": "device-frame-2-2",
"value": "secondary",
@@ -633,30 +633,30 @@ page DeviceFrameDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
variant="success"
frameTitle="Advanced Device Frame"
@@ -667,17 +667,17 @@ page DeviceFrameDetail {
<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>@change</code><span>Fires when the component's committed value or selection changes.</span></div><div><code>@rotate</code><span>Fires when the component emits the rotate event.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;DeviceFrame
@change='console.log(payload)'
@rotate='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"DeviceFrame\"], [data-component=\"DeviceFrame\"]")
if (component) registerOutputHandler(component, "change", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "rotate", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "rotate", (payload) =&gt; <span>&#123;</span>
console.log("rotate", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Device Frame"</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>device</code></td><td>string</td><td><code>"phone"</code></td><td>No</td></tr><tr><td><code>orientation</code></td><td>string</td><td><code>"portrait"</code></td><td>No</td></tr><tr><td><code>src</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>srcdoc</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>frameTitle</code></td><td>string</td><td><code>"Device preview"</code></td><td>No</td></tr><tr><td><code>showToolbar</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>allow</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">
@@ -37,7 +37,7 @@ page DragAndDropDetail {
title="Drag And Drop example"
description="A polished default drag and drop for a modern application."
items='[
&#123;
<span>&#123;</span>
"id": "drag-and-drop-0-1",
"key": "drag-and-drop-0-1",
"value": "primary",
@@ -66,31 +66,31 @@ page DragAndDropDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "drag-and-drop-0-2",
"key": "drag-and-drop-0-2",
"value": "secondary",
@@ -119,30 +119,30 @@ page DragAndDropDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -282,7 +282,7 @@ page DragAndDropDetail {
title="Drag And Drop example"
description="A polished default drag and drop for a modern application."
items='[
&#123;
<span>&#123;</span>
"id": "drag-and-drop-0-1",
"key": "drag-and-drop-0-1",
"value": "primary",
@@ -311,31 +311,31 @@ page DragAndDropDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "drag-and-drop-0-2",
"key": "drag-and-drop-0-2",
"value": "secondary",
@@ -364,30 +364,30 @@ page DragAndDropDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -411,7 +411,7 @@ page DragAndDropDetail {
title="Compact Drag And Drop"
description="A compact configuration for dashboards and dense product surfaces."
items='[
&#123;
<span>&#123;</span>
"id": "drag-and-drop-1-1",
"key": "drag-and-drop-1-1",
"value": "primary",
@@ -440,31 +440,31 @@ page DragAndDropDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "drag-and-drop-1-2",
"key": "drag-and-drop-1-2",
"value": "secondary",
@@ -493,30 +493,30 @@ page DragAndDropDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
variant="secondary"
/&gt;</code></pre>
@@ -541,7 +541,7 @@ page DragAndDropDetail {
title="Advanced Drag And Drop"
description="A richer configuration demonstrating additional content and hierarchy."
items='[
&#123;
<span>&#123;</span>
"id": "drag-and-drop-2-1",
"key": "drag-and-drop-2-1",
"value": "primary",
@@ -570,31 +570,31 @@ page DragAndDropDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "drag-and-drop-2-2",
"key": "drag-and-drop-2-2",
"value": "secondary",
@@ -623,30 +623,30 @@ page DragAndDropDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
variant="success"
/&gt;</code></pre>
@@ -660,33 +660,33 @@ page DragAndDropDetail {
@dragLeave='console.log(payload)'
@drop='console.log(payload)'
@change='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"DragAndDrop\"], [data-component=\"DragAndDrop\"]")
if (component) registerOutputHandler(component, "dragStart", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "dragStart", (payload) =&gt; <span>&#123;</span>
console.log("dragStart", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "dragEnd", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "dragEnd", (payload) =&gt; <span>&#123;</span>
console.log("dragEnd", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "dragEnter", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "dragEnter", (payload) =&gt; <span>&#123;</span>
console.log("dragEnter", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "dragLeave", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "dragLeave", (payload) =&gt; <span>&#123;</span>
console.log("dragLeave", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "drop", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "drop", (payload) =&gt; <span>&#123;</span>
console.log("drop", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "change", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Drag And Drop"</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">
@@ -694,7 +694,7 @@ if (component) registerOutputHandler(component, "change", (payload) =&gt; &#123;
</aside>
</div>
<nav class="detail-pagination">
<a href="/components/data-table"><small>Previous</small><strong>← Data Table</strong></a>
<a href="/components/data-map"><small>Previous</small><strong>← Data Map</strong></a>
<a href="/components/file-upload"><small>Next</small><strong>File Upload →</strong></a>
</nav>
}
File diff suppressed because one or more lines are too long
@@ -45,7 +45,7 @@ page DropdownDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;Dropdown
items='[
&#123;
<span>&#123;</span>
"id": "dropdown-0-1",
"key": "dropdown-0-1",
"value": "primary",
@@ -74,31 +74,31 @@ page DropdownDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "dropdown-0-2",
"key": "dropdown-0-2",
"value": "secondary",
@@ -127,30 +127,30 @@ page DropdownDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
label="Dropdown"
menuLabel="Dropdown"
@@ -215,27 +215,27 @@ page DropdownDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Dropdown
items='[
&#123;
<span>&#123;</span>
"label": "Open dashboard",
"description": "View the operational workspace",
"value": "dashboard",
"icon": "icon-[lucide--layout-dashboard]"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Share record",
"value": "share",
"icon": "icon-[lucide--share-2]",
"shortcut": "Ctrl S"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"type": "divider"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Delete record",
"value": "delete",
"icon": "icon-[lucide--trash-2]",
"danger": true
&#125;
<span>&#125;</span>
]'
label="Open actions"
icon="icon-[lucide--menu]"
@@ -270,27 +270,27 @@ page DropdownDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Dropdown
items='[
&#123;
<span>&#123;</span>
"label": "Open dashboard",
"description": "View the operational workspace",
"value": "dashboard",
"icon": "icon-[lucide--layout-dashboard]"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Share record",
"value": "share",
"icon": "icon-[lucide--share-2]",
"shortcut": "Ctrl S"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"type": "divider"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Delete record",
"value": "delete",
"icon": "icon-[lucide--trash-2]",
"danger": true
&#125;
<span>&#125;</span>
]'
label="More actions"
icon="icon-[lucide--ellipsis]"
@@ -328,27 +328,27 @@ page DropdownDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Dropdown
items='[
&#123;
<span>&#123;</span>
"label": "Open dashboard",
"description": "View the operational workspace",
"value": "dashboard",
"icon": "icon-[lucide--layout-dashboard]"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Share record",
"value": "share",
"icon": "icon-[lucide--share-2]",
"shortcut": "Ctrl S"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"type": "divider"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Delete record",
"value": "delete",
"icon": "icon-[lucide--trash-2]",
"danger": true
&#125;
<span>&#125;</span>
]'
label="Review detailed menu"
icon="icon-[lucide--trash-2]"
@@ -376,29 +376,29 @@ page DropdownDetail {
@close='console.log(payload)'
@select='console.log(payload)'
@action='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Dropdown\"], [data-component=\"Dropdown\"]")
if (component) registerOutputHandler(component, "toggle", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "toggle", (payload) =&gt; <span>&#123;</span>
console.log("toggle", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "open", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "open", (payload) =&gt; <span>&#123;</span>
console.log("open", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "close", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "close", (payload) =&gt; <span>&#123;</span>
console.log("close", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "select", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span>
console.log("select", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "action", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "action", (payload) =&gt; <span>&#123;</span>
console.log("action", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>open</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>defaultOpen</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Open menu"</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>showChevron</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>menuLabel</code></td><td>string</td><td><code>"Dropdown menu"</code></td><td>No</td></tr><tr><td><code>placement</code></td><td>string</td><td><code>"bottom-start"</code></td><td>No</td></tr><tr><td><code>width</code></td><td>string</td><td><code>"md"</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>"raised"</code></td><td>No</td></tr><tr><td><code>closeOnSelect</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>closeOnOutside</code></td><td>boolean</td><td><code>true</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>emptyLabel</code></td><td>string</td><td><code>"No menu items"</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">
@@ -42,7 +42,7 @@ page FeatureGridDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;FeatureGrid
items='[
&#123;
<span>&#123;</span>
"id": "feature-grid-0-1",
"key": "feature-grid-0-1",
"value": "primary",
@@ -71,31 +71,31 @@ page FeatureGridDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "feature-grid-0-2",
"key": "feature-grid-0-2",
"value": "secondary",
@@ -124,30 +124,30 @@ page FeatureGridDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
align="start"
label="Feature Grid"
@@ -217,7 +217,7 @@ page FeatureGridDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;FeatureGrid
items='[
&#123;
<span>&#123;</span>
"icon": "icon-[lucide--shield-check]",
"eyebrow": "Citizen support",
"title": "Citizen services",
@@ -228,8 +228,8 @@ page FeatureGridDetail {
"color": "primary",
"variant": "default",
"hover": "lift"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"icon": "icon-[lucide--map-pin]",
"eyebrow": "Directory",
"title": "Station directory",
@@ -240,8 +240,8 @@ page FeatureGridDetail {
"color": "info",
"variant": "soft",
"hover": "border"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"icon": "icon-[lucide--radio-tower]",
"eyebrow": "Verified",
"title": "Public updates",
@@ -252,7 +252,7 @@ page FeatureGridDetail {
"color": "success",
"variant": "gradient",
"hover": "glow"
&#125;
<span>&#125;</span>
]'
gap="lg"
align="start"
@@ -277,7 +277,7 @@ page FeatureGridDetail {
<pre><code>&lt;FeatureGrid
size="sm"
items='[
&#123;
<span>&#123;</span>
"icon": "icon-[lucide--shield-check]",
"eyebrow": "Citizen support",
"title": "Citizen services",
@@ -288,8 +288,8 @@ page FeatureGridDetail {
"color": "primary",
"variant": "default",
"hover": "lift"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"icon": "icon-[lucide--map-pin]",
"eyebrow": "Directory",
"title": "Station directory",
@@ -300,8 +300,8 @@ page FeatureGridDetail {
"color": "info",
"variant": "soft",
"hover": "border"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"icon": "icon-[lucide--radio-tower]",
"eyebrow": "Verified",
"title": "Public updates",
@@ -312,14 +312,14 @@ page FeatureGridDetail {
"color": "success",
"variant": "gradient",
"hover": "glow"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"icon": "icon-[lucide--file-check-2]",
"title": "Certificates",
"description": "Request and track certificates online.",
"href": "#certificates",
"color": "warning"
&#125;
<span>&#125;</span>
]'
columns="4"
gap="sm"
@@ -346,7 +346,7 @@ page FeatureGridDetail {
<pre><code>&lt;FeatureGrid
size="lg"
items='[
&#123;
<span>&#123;</span>
"icon": "icon-[lucide--shield-check]",
"eyebrow": "Citizen support",
"title": "Citizen services",
@@ -357,8 +357,8 @@ page FeatureGridDetail {
"color": "primary",
"variant": "default",
"hover": "lift"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"icon": "icon-[lucide--map-pin]",
"eyebrow": "Directory",
"title": "Station directory",
@@ -369,8 +369,8 @@ page FeatureGridDetail {
"color": "info",
"variant": "soft",
"hover": "border"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"icon": "icon-[lucide--radio-tower]",
"eyebrow": "Verified",
"title": "Public updates",
@@ -381,7 +381,7 @@ page FeatureGridDetail {
"color": "success",
"variant": "gradient",
"hover": "glow"
&#125;
<span>&#125;</span>
]'
gap="xl"
minItemWidth="18rem"
@@ -142,37 +142,37 @@ page FileInputDetail {
@select='console.log(payload)'
@clear='console.log(payload)'
@invalid='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"FileInput\"], [data-component=\"FileInput\"]")
if (component) registerOutputHandler(component, "input", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "input", (payload) =&gt; <span>&#123;</span>
console.log("input", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "change", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "focus", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "focus", (payload) =&gt; <span>&#123;</span>
console.log("focus", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "blur", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "blur", (payload) =&gt; <span>&#123;</span>
console.log("blur", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "select", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span>
console.log("select", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "clear", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "clear", (payload) =&gt; <span>&#123;</span>
console.log("clear", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "invalid", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "invalid", (payload) =&gt; <span>&#123;</span>
console.log("invalid", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>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>"File"</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>"Choose a file"</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>icon</code></td><td>string</td><td><code>"icon-[lucide--upload]"</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>accept</code></td><td>string</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>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">
@@ -130,21 +130,21 @@ page FileUploadProgressDetail {
@cancel='console.log(payload)'
@retry='console.log(payload)'
@complete='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"FileUploadProgress\"], [data-component=\"FileUploadProgress\"]")
if (component) registerOutputHandler(component, "cancel", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "cancel", (payload) =&gt; <span>&#123;</span>
console.log("cancel", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "retry", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "retry", (payload) =&gt; <span>&#123;</span>
console.log("retry", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "complete", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "complete", (payload) =&gt; <span>&#123;</span>
console.log("complete", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Progress"</code></td><td>No</td></tr><tr><td><code>value</code></td><td>number</td><td><code>50</code></td><td>No</td></tr><tr><td><code>max</code></td><td>number</td><td><code>100</code></td><td>No</td></tr><tr><td><code>showValue</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>fileName</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>fileSize</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>uploadedSize</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>status</code></td><td>string</td><td><code>"uploading"</code></td><td>No</td></tr><tr><td><code>cancelLabel</code></td><td>string</td><td><code>"Cancel upload"</code></td><td>No</td></tr><tr><td><code>retryLabel</code></td><td>string</td><td><code>"Retry upload"</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 FileUploadDetail {
title="File Upload example"
description="A polished default file upload for a modern application."
items='[
&#123;
<span>&#123;</span>
"id": "file-upload-0-1",
"key": "file-upload-0-1",
"value": "primary",
@@ -66,31 +66,31 @@ page FileUploadDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "file-upload-0-2",
"key": "file-upload-0-2",
"value": "secondary",
@@ -119,30 +119,30 @@ page FileUploadDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -282,7 +282,7 @@ page FileUploadDetail {
title="File Upload example"
description="A polished default file upload for a modern application."
items='[
&#123;
<span>&#123;</span>
"id": "file-upload-0-1",
"key": "file-upload-0-1",
"value": "primary",
@@ -311,31 +311,31 @@ page FileUploadDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "file-upload-0-2",
"key": "file-upload-0-2",
"value": "secondary",
@@ -364,30 +364,30 @@ page FileUploadDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -411,7 +411,7 @@ page FileUploadDetail {
title="Compact File Upload"
description="A compact configuration for dashboards and dense product surfaces."
items='[
&#123;
<span>&#123;</span>
"id": "file-upload-1-1",
"key": "file-upload-1-1",
"value": "primary",
@@ -440,31 +440,31 @@ page FileUploadDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "file-upload-1-2",
"key": "file-upload-1-2",
"value": "secondary",
@@ -493,30 +493,30 @@ page FileUploadDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
variant="secondary"
/&gt;</code></pre>
@@ -541,7 +541,7 @@ page FileUploadDetail {
title="Advanced File Upload"
description="A richer configuration demonstrating additional content and hierarchy."
items='[
&#123;
<span>&#123;</span>
"id": "file-upload-2-1",
"key": "file-upload-2-1",
"value": "primary",
@@ -570,31 +570,31 @@ page FileUploadDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "file-upload-2-2",
"key": "file-upload-2-2",
"value": "secondary",
@@ -623,30 +623,30 @@ page FileUploadDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
variant="success"
/&gt;</code></pre>
@@ -661,37 +661,37 @@ page FileUploadDetail {
@error='console.log(payload)'
@cancel='console.log(payload)'
@remove='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"FileUpload\"], [data-component=\"FileUpload\"]")
if (component) registerOutputHandler(component, "select", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span>
console.log("select", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "upload", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "upload", (payload) =&gt; <span>&#123;</span>
console.log("upload", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "progress", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "progress", (payload) =&gt; <span>&#123;</span>
console.log("progress", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "success", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "success", (payload) =&gt; <span>&#123;</span>
console.log("success", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "error", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "error", (payload) =&gt; <span>&#123;</span>
console.log("error", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "cancel", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "cancel", (payload) =&gt; <span>&#123;</span>
console.log("cancel", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "remove", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "remove", (payload) =&gt; <span>&#123;</span>
console.log("remove", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"File Upload"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main>
<aside class="detail-aside">
@@ -37,7 +37,7 @@ page FooterDetail {
<pre><code data-playground-code>&lt;Footer
label="Footer"
items='[
&#123;
<span>&#123;</span>
"id": "footer-0-1",
"key": "footer-0-1",
"value": "primary",
@@ -66,31 +66,31 @@ page FooterDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "footer-0-2",
"key": "footer-0-2",
"value": "secondary",
@@ -119,30 +119,30 @@ page FooterDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'&gt;
&lt;div data-slot="pre-footer"&gt;
&lt;div class="showcase-slot"&gt;pre-footer slot&lt;/div&gt;
@@ -258,77 +258,77 @@ page FooterDetail {
<pre><code>&lt;Footer
label="Footer"
items='[
&#123;
<span>&#123;</span>
"type": "header",
"label": "Product",
"description": "Build consistent interfaces with WRNexusJS.",
"items": [
&#123;
<span>&#123;</span>
"label": "Components",
"href": "/base"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Themes",
"href": "/themes"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Examples",
"href": "/blocks"
&#125;
<span>&#125;</span>
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"type": "header",
"label": "Developers",
"items": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "/docs"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Installation",
"href": "/installation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Framework guides",
"href": "/framework-guides"
&#125;
<span>&#125;</span>
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"type": "header",
"label": "Resources",
"items": [
&#123;
<span>&#123;</span>
"label": "Accessibility",
"href": "/accessibility"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Design tokens",
"href": "/colors"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Typography",
"href": "/fonts"
&#125;
<span>&#125;</span>
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"type": "header",
"label": "Company",
"items": [
&#123;
<span>&#123;</span>
"label": "WorkRoot",
"href": "https://workroot.in",
"external": true
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "WRNexusJS",
"href": "https://wrnexusjs.dev",
"external": true
&#125;
<span>&#125;</span>
]
&#125;
<span>&#125;</span>
]'
columns="4"
maxWidth="wide"
@@ -366,61 +366,61 @@ page FooterDetail {
size="compact"
label="Compact example"
items='[
&#123;
<span>&#123;</span>
"type": "header",
"label": "Product",
"description": "Build consistent interfaces with WRNexusJS.",
"items": [
&#123;
<span>&#123;</span>
"label": "Components",
"href": "/base"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Themes",
"href": "/themes"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Examples",
"href": "/blocks"
&#125;
<span>&#125;</span>
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"type": "header",
"label": "Developers",
"items": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "/docs"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Installation",
"href": "/installation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Framework guides",
"href": "/framework-guides"
&#125;
<span>&#125;</span>
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"type": "header",
"label": "Resources",
"items": [
&#123;
<span>&#123;</span>
"label": "Accessibility",
"href": "/accessibility"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Design tokens",
"href": "/colors"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Typography",
"href": "/fonts"
&#125;
<span>&#125;</span>
]
&#125;
<span>&#125;</span>
]'
copyright="WRNexusJS component showcase"&gt;
&lt;div data-slot="pre-footer"&gt;
@@ -456,77 +456,77 @@ page FooterDetail {
size="spacious"
label="Advanced example"
items='[
&#123;
<span>&#123;</span>
"type": "header",
"label": "Product",
"description": "Build consistent interfaces with WRNexusJS.",
"items": [
&#123;
<span>&#123;</span>
"label": "Components",
"href": "/base"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Themes",
"href": "/themes"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Examples",
"href": "/blocks"
&#125;
<span>&#125;</span>
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"type": "header",
"label": "Developers",
"items": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "/docs"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Installation",
"href": "/installation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Framework guides",
"href": "/framework-guides"
&#125;
<span>&#125;</span>
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"type": "header",
"label": "Resources",
"items": [
&#123;
<span>&#123;</span>
"label": "Accessibility",
"href": "/accessibility"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Design tokens",
"href": "/colors"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Typography",
"href": "/fonts"
&#125;
<span>&#125;</span>
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"type": "header",
"label": "Company",
"items": [
&#123;
<span>&#123;</span>
"label": "WorkRoot",
"href": "https://workroot.in",
"external": true
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "WRNexusJS",
"href": "https://wrnexusjs.dev",
"external": true
&#125;
<span>&#125;</span>
]
&#125;
<span>&#125;</span>
]'
columns="4"
maxWidth="full"
@@ -550,17 +550,17 @@ page FooterDetail {
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@select</code><span>Fires when an item, option, card, or result is selected.</span></div><div><code>@action</code><span>Fires when the component's primary or item action is activated.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;Footer
@select='console.log(payload)'
@action='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Footer\"], [data-component=\"Footer\"]")
if (component) registerOutputHandler(component, "select", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span>
console.log("select", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "action", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "action", (payload) =&gt; <span>&#123;</span>
console.log("action", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Footer navigation"</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>columns</code></td><td>number</td><td><code>3</code></td><td>No</td></tr><tr><td><code>maxWidth</code></td><td>string</td><td><code>"compact"</code></td><td>No</td></tr><tr><td><code>copyright</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">
@@ -36,7 +36,7 @@ page HeroActionsDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;HeroActions
actions='[
&#123;
<span>&#123;</span>
"id": "hero-actions-0-1",
"key": "hero-actions-0-1",
"value": "primary",
@@ -65,31 +65,31 @@ page HeroActionsDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "hero-actions-0-2",
"key": "hero-actions-0-2",
"value": "secondary",
@@ -118,30 +118,30 @@ page HeroActionsDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
align="start"
/&gt;</code></pre>
@@ -186,18 +186,18 @@ page HeroActionsDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;HeroActions
actions='[
&#123;
<span>&#123;</span>
"label": "Browse components",
"href": "#components",
"icon": "icon-[lucide--blocks]",
"variant": "primary"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "View metrics",
"href": "#metrics",
"icon": "icon-[lucide--chart-no-axes-column-increasing]",
"variant": "outline"
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -218,18 +218,18 @@ page HeroActionsDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;HeroActions
actions='[
&#123;
<span>&#123;</span>
"label": "Browse components",
"href": "#components",
"icon": "icon-[lucide--blocks]",
"variant": "primary"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "View metrics",
"href": "#metrics",
"icon": "icon-[lucide--chart-no-axes-column-increasing]",
"variant": "outline"
&#125;
<span>&#125;</span>
]'
align="center"
size="sm"
@@ -252,24 +252,24 @@ page HeroActionsDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;HeroActions
actions='[
&#123;
<span>&#123;</span>
"label": "Browse components",
"href": "#components",
"icon": "icon-[lucide--blocks]",
"variant": "primary"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "View metrics",
"href": "#metrics",
"icon": "icon-[lucide--chart-no-axes-column-increasing]",
"variant": "outline"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Contact support",
"href": "#support",
"icon": "icon-[lucide--life-buoy]",
"variant": "ghost"
&#125;
<span>&#125;</span>
]'
align="end"
orientation="vertical"
@@ -76,7 +76,7 @@ page HeroDetail {
visualTitle="Hero example"
visualDescription="A polished default hero for a modern application."
visualItems='[
&#123;
<span>&#123;</span>
"id": "hero-0-1",
"key": "hero-0-1",
"value": "primary",
@@ -105,31 +105,31 @@ page HeroDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "hero-0-2",
"key": "hero-0-2",
"value": "secondary",
@@ -158,30 +158,30 @@ page HeroDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
primaryLabel="Hero"
primaryHref="#hero-demo"
@@ -190,7 +190,7 @@ page HeroDetail {
tertiaryLabel="Hero"
tertiaryHref="#hero-demo"
badges='[
&#123;
<span>&#123;</span>
"id": "hero-0-1",
"key": "hero-0-1",
"value": "primary",
@@ -219,31 +219,31 @@ page HeroDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "hero-0-2",
"key": "hero-0-2",
"value": "secondary",
@@ -272,33 +272,33 @@ page HeroDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
trustItems='[
&#123;
<span>&#123;</span>
"id": "hero-0-1",
"key": "hero-0-1",
"value": "primary",
@@ -327,31 +327,31 @@ page HeroDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "hero-0-2",
"key": "hero-0-2",
"value": "secondary",
@@ -380,30 +380,30 @@ page HeroDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'&gt;
&lt;div data-slot="eyebrow"&gt;
&lt;div class="showcase-slot"&gt;eyebrow slot&lt;/div&gt;
@@ -785,7 +785,7 @@ page HeroDetail {
visualTitle="Hero example"
visualDescription="A polished default hero for a modern application."
visualItems='[
&#123;
<span>&#123;</span>
"id": "hero-0-1",
"key": "hero-0-1",
"value": "primary",
@@ -814,31 +814,31 @@ page HeroDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "hero-0-2",
"key": "hero-0-2",
"value": "secondary",
@@ -867,30 +867,30 @@ page HeroDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
primaryLabel="Explore components"
primaryHref="#components"
@@ -901,7 +901,7 @@ page HeroDetail {
tertiaryLabel="Hero"
tertiaryHref="#hero-demo"
badges='[
&#123;
<span>&#123;</span>
"id": "hero-0-1",
"key": "hero-0-1",
"value": "primary",
@@ -930,31 +930,31 @@ page HeroDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "hero-0-2",
"key": "hero-0-2",
"value": "secondary",
@@ -983,33 +983,33 @@ page HeroDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
trustItems='[
&#123;
<span>&#123;</span>
"id": "hero-0-1",
"key": "hero-0-1",
"value": "primary",
@@ -1038,31 +1038,31 @@ page HeroDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "hero-0-2",
"key": "hero-0-2",
"value": "secondary",
@@ -1091,30 +1091,30 @@ page HeroDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
maxWidth="wide"&gt;
&lt;div data-slot="eyebrow"&gt;
@@ -1164,7 +1164,7 @@ page HeroDetail {
visualTitle="Compact Hero"
visualDescription="A compact configuration for dashboards and dense product surfaces."
visualItems='[
&#123;
<span>&#123;</span>
"id": "hero-1-1",
"key": "hero-1-1",
"value": "primary",
@@ -1193,31 +1193,31 @@ page HeroDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "hero-1-2",
"key": "hero-1-2",
"value": "secondary",
@@ -1246,30 +1246,30 @@ page HeroDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
primaryLabel="Browse services"
primaryHref="#services"
@@ -1278,7 +1278,7 @@ page HeroDetail {
tertiaryLabel="Compact example"
tertiaryHref="#hero-demo"
badges='[
&#123;
<span>&#123;</span>
"id": "hero-1-1",
"key": "hero-1-1",
"value": "primary",
@@ -1307,31 +1307,31 @@ page HeroDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "hero-1-2",
"key": "hero-1-2",
"value": "secondary",
@@ -1360,33 +1360,33 @@ page HeroDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
trustItems='[
&#123;
<span>&#123;</span>
"id": "hero-1-1",
"key": "hero-1-1",
"value": "primary",
@@ -1415,31 +1415,31 @@ page HeroDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "hero-1-2",
"key": "hero-1-2",
"value": "secondary",
@@ -1468,30 +1468,30 @@ page HeroDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'&gt;
&lt;div data-slot="eyebrow"&gt;
&lt;div class="showcase-slot"&gt;eyebrow slot&lt;/div&gt;
@@ -1544,7 +1544,7 @@ page HeroDetail {
visualTitle="Advanced Hero"
visualDescription="A richer configuration demonstrating additional content and hierarchy."
visualItems='[
&#123;
<span>&#123;</span>
"id": "hero-2-1",
"key": "hero-2-1",
"value": "primary",
@@ -1573,31 +1573,31 @@ page HeroDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "hero-2-2",
"key": "hero-2-2",
"value": "secondary",
@@ -1626,30 +1626,30 @@ page HeroDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
primaryLabel="Advanced example"
primaryHref="#hero-demo"
@@ -147,33 +147,33 @@ page InputGroupDetail {
@blur='console.log(payload)'
@submit='console.log(payload)'
@action='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"InputGroup\"], [data-component=\"InputGroup\"]")
if (component) registerOutputHandler(component, "input", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "input", (payload) =&gt; <span>&#123;</span>
console.log("input", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "change", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "focus", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "focus", (payload) =&gt; <span>&#123;</span>
console.log("focus", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "blur", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "blur", (payload) =&gt; <span>&#123;</span>
console.log("blur", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "submit", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "submit", (payload) =&gt; <span>&#123;</span>
console.log("submit", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "action", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "action", (payload) =&gt; <span>&#123;</span>
console.log("action", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>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>"Input group"</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>value</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>placeholder</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>type</code></td><td>string</td><td><code>"text"</code></td><td>No</td></tr><tr><td><code>startText</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>endText</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>actionLabel</code></td><td>string</td><td><code>""</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">
@@ -373,25 +373,25 @@ page InputNumberDetail {
@change='console.log(payload)'
@increment='console.log(payload)'
@decrement='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"InputNumber\"], [data-component=\"InputNumber\"]")
if (component) registerOutputHandler(component, "input", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "input", (payload) =&gt; <span>&#123;</span>
console.log("input", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "change", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "increment", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "increment", (payload) =&gt; <span>&#123;</span>
console.log("increment", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "decrement", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "decrement", (payload) =&gt; <span>&#123;</span>
console.log("decrement", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>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>""</code></td><td>No</td></tr><tr><td><code>name</code></td><td>string</td><td><code>"quantity"</code></td><td>No</td></tr><tr><td><code>value</code></td><td>number</td><td><code>0</code></td><td>No</td></tr><tr><td><code>min</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>max</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>step</code></td><td>number</td><td><code>1</code></td><td>No</td></tr><tr><td><code>precision</code></td><td>string</td><td><code>"auto"</code></td><td>No</td></tr><tr><td><code>label</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>helpText</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>invalid</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>prefix</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>suffix</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>placeholder</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>autocomplete</code></td><td>string</td><td><code>"off"</code></td><td>No</td></tr><tr><td><code>inputMode</code></td><td>string</td><td><code>"decimal"</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>required</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>inputDisabled</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>buttonsDisabled</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>allowInput</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>keyboard</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>wheel</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>clamp</code></td><td>boolean</td><td><code>true</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>showButtons</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>showValidationMessage</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>decrementLabel</code></td><td>string</td><td><code>"Decrease value"</code></td><td>No</td></tr><tr><td><code>incrementLabel</code></td><td>string</td><td><code>"Increase value"</code></td><td>No</td></tr><tr><td><code>controlsLabel</code></td><td>string</td><td><code>"Quantity controls"</code></td><td>No</td></tr><tr><td><code>requiredMessage</code></td><td>string</td><td><code>"A value is required."</code></td><td>No</td></tr><tr><td><code>minMessage</code></td><td>string</td><td><code>"Value is below the minimum."</code></td><td>No</td></tr><tr><td><code>maxMessage</code></td><td>string</td><td><code>"Value is above the maximum."</code></td><td>No</td></tr></tbody></table></div></section>
</main>
<aside class="detail-aside">
@@ -147,37 +147,37 @@ page InputDetail {
@invalid='console.log(payload)'
@keydown='console.log(payload)'
@keyup='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Input\"], [data-component=\"Input\"]")
if (component) registerOutputHandler(component, "input", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "input", (payload) =&gt; <span>&#123;</span>
console.log("input", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "change", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "focus", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "focus", (payload) =&gt; <span>&#123;</span>
console.log("focus", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "blur", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "blur", (payload) =&gt; <span>&#123;</span>
console.log("blur", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "invalid", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "invalid", (payload) =&gt; <span>&#123;</span>
console.log("invalid", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "keydown", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "keydown", (payload) =&gt; <span>&#123;</span>
console.log("keydown", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "keyup", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "keyup", (payload) =&gt; <span>&#123;</span>
console.log("keyup", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>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>"Input"</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>""</code></td><td>No</td></tr><tr><td><code>type</code></td><td>string</td><td><code>"text"</code></td><td>No</td></tr><tr><td><code>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>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>autocomplete</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>inputmode</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>minlength</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>maxlength</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>pattern</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>min</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>max</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>step</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main>
<aside class="detail-aside">
@@ -105,21 +105,21 @@ page LayoutSplitterDetail {
@resizeStart='console.log(payload)'
@resize='console.log(payload)'
@resizeEnd='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"LayoutSplitter\"], [data-component=\"LayoutSplitter\"]")
if (component) registerOutputHandler(component, "resizeStart", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "resizeStart", (payload) =&gt; <span>&#123;</span>
console.log("resizeStart", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "resize", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "resize", (payload) =&gt; <span>&#123;</span>
console.log("resize", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "resizeEnd", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "resizeEnd", (payload) =&gt; <span>&#123;</span>
console.log("resizeEnd", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>columns</code></td><td>number</td><td><code>2</code></td><td>No</td></tr><tr><td><code>gap</code></td><td>string</td><td><code>"md"</code></td><td>No</td></tr><tr><td><code>maxWidth</code></td><td>string</td><td><code>"xl"</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 LegendIndicatorDetail {
title="Legend Indicator example"
description="A polished default legend indicator for a modern application."
items='[
&#123;
<span>&#123;</span>
"id": "legend-indicator-0-1",
"key": "legend-indicator-0-1",
"value": "primary",
@@ -66,31 +66,31 @@ page LegendIndicatorDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "legend-indicator-0-2",
"key": "legend-indicator-0-2",
"value": "secondary",
@@ -119,30 +119,30 @@ page LegendIndicatorDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -282,7 +282,7 @@ page LegendIndicatorDetail {
title="Legend Indicator example"
description="A polished default legend indicator for a modern application."
items='[
&#123;
<span>&#123;</span>
"id": "legend-indicator-0-1",
"key": "legend-indicator-0-1",
"value": "primary",
@@ -311,31 +311,31 @@ page LegendIndicatorDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "legend-indicator-0-2",
"key": "legend-indicator-0-2",
"value": "secondary",
@@ -364,30 +364,30 @@ page LegendIndicatorDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -411,7 +411,7 @@ page LegendIndicatorDetail {
title="Compact Legend Indicator"
description="A compact configuration for dashboards and dense product surfaces."
items='[
&#123;
<span>&#123;</span>
"id": "legend-indicator-1-1",
"key": "legend-indicator-1-1",
"value": "primary",
@@ -440,31 +440,31 @@ page LegendIndicatorDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "legend-indicator-1-2",
"key": "legend-indicator-1-2",
"value": "secondary",
@@ -493,30 +493,30 @@ page LegendIndicatorDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
variant="secondary"
/&gt;</code></pre>
@@ -541,7 +541,7 @@ page LegendIndicatorDetail {
title="Advanced Legend Indicator"
description="A richer configuration demonstrating additional content and hierarchy."
items='[
&#123;
<span>&#123;</span>
"id": "legend-indicator-2-1",
"key": "legend-indicator-2-1",
"value": "primary",
@@ -570,31 +570,31 @@ page LegendIndicatorDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "legend-indicator-2-2",
"key": "legend-indicator-2-2",
"value": "secondary",
@@ -623,30 +623,30 @@ page LegendIndicatorDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
variant="success"
/&gt;</code></pre>
@@ -656,17 +656,17 @@ page LegendIndicatorDetail {
<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>@toggle</code><span>Fires when the component changes between open and closed or on and off states.</span></div><div><code>@select</code><span>Fires when an item, option, card, or result is selected.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;LegendIndicator
@toggle='console.log(payload)'
@select='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"LegendIndicator\"], [data-component=\"LegendIndicator\"]")
if (component) registerOutputHandler(component, "toggle", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "toggle", (payload) =&gt; <span>&#123;</span>
console.log("toggle", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "select", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span>
console.log("select", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Legend Indicator"</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 ListGroupDetail {
title="List Group example"
description="A polished default list group for a modern application."
items='[
&#123;
<span>&#123;</span>
"id": "list-group-0-1",
"key": "list-group-0-1",
"value": "primary",
@@ -66,31 +66,31 @@ page ListGroupDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "list-group-0-2",
"key": "list-group-0-2",
"value": "secondary",
@@ -119,30 +119,30 @@ page ListGroupDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -282,7 +282,7 @@ page ListGroupDetail {
title="List Group example"
description="A polished default list group for a modern application."
items='[
&#123;
<span>&#123;</span>
"id": "list-group-0-1",
"key": "list-group-0-1",
"value": "primary",
@@ -311,31 +311,31 @@ page ListGroupDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "list-group-0-2",
"key": "list-group-0-2",
"value": "secondary",
@@ -364,30 +364,30 @@ page ListGroupDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -411,7 +411,7 @@ page ListGroupDetail {
title="Compact List Group"
description="A compact configuration for dashboards and dense product surfaces."
items='[
&#123;
<span>&#123;</span>
"id": "list-group-1-1",
"key": "list-group-1-1",
"value": "primary",
@@ -440,31 +440,31 @@ page ListGroupDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "list-group-1-2",
"key": "list-group-1-2",
"value": "secondary",
@@ -493,30 +493,30 @@ page ListGroupDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
variant="secondary"
/&gt;</code></pre>
@@ -541,7 +541,7 @@ page ListGroupDetail {
title="Advanced List Group"
description="A richer configuration demonstrating additional content and hierarchy."
items='[
&#123;
<span>&#123;</span>
"id": "list-group-2-1",
"key": "list-group-2-1",
"value": "primary",
@@ -570,31 +570,31 @@ page ListGroupDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "list-group-2-2",
"key": "list-group-2-2",
"value": "secondary",
@@ -623,30 +623,30 @@ page ListGroupDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
variant="success"
/&gt;</code></pre>
@@ -656,17 +656,17 @@ page ListGroupDetail {
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@select</code><span>Fires when an item, option, card, or result is selected.</span></div><div><code>@change</code><span>Fires when the component's committed value or selection changes.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;ListGroup
@select='console.log(payload)'
@change='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"ListGroup\"], [data-component=\"ListGroup\"]")
if (component) registerOutputHandler(component, "select", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span>
console.log("select", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "change", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"List Group"</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 ListDetail {
title="List example"
description="A polished default list for a modern application."
items='[
&#123;
<span>&#123;</span>
"id": "list-0-1",
"key": "list-0-1",
"value": "primary",
@@ -66,31 +66,31 @@ page ListDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "list-0-2",
"key": "list-0-2",
"value": "secondary",
@@ -119,30 +119,30 @@ page ListDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -282,21 +282,21 @@ page ListDetail {
title="Citizen resources"
description="Commonly requested public information."
items='[
&#123;
<span>&#123;</span>
"label": "Report crime",
"href": "#report",
"icon": "icon-[lucide--shield-alert]"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Track complaint",
"href": "#track",
"icon": "icon-[lucide--scan-search]"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Certificates",
"href": "#certificates",
"icon": "icon-[lucide--file-check-2]"
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -320,18 +320,18 @@ page ListDetail {
title="Current status"
description="A compact configuration for dashboards and dense product surfaces."
items='[
&#123;
<span>&#123;</span>
"label": "Application received",
"status": "Complete"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Verification",
"status": "In progress"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Decision",
"status": "Pending"
&#125;
<span>&#125;</span>
]'
variant="soft"
/&gt;</code></pre>
@@ -356,18 +356,18 @@ page ListDetail {
title="Service requests"
description="Recent requests and their current status."
items='[
&#123;
<span>&#123;</span>
"label": "Address verification",
"description": "Submitted yesterday",
"status": "Review",
"href": "#request-1"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Character certificate",
"description": "Submitted 3 days ago",
"status": "Approved",
"href": "#request-2"
&#125;
<span>&#125;</span>
]'
variant="raised"
/&gt;</code></pre>
@@ -37,7 +37,7 @@ page MapDetail {
title="Map example"
description="A polished default map for a modern application."
items='[
&#123;
<span>&#123;</span>
"id": "map-0-1",
"key": "map-0-1",
"value": "primary",
@@ -66,31 +66,31 @@ page MapDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "map-0-2",
"key": "map-0-2",
"value": "secondary",
@@ -119,30 +119,30 @@ page MapDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -282,18 +282,18 @@ page MapDetail {
title="Nearby police stations"
description="Select a station to view contact details and jurisdiction information."
items='[
&#123;
<span>&#123;</span>
"title": "Central station",
"description": "Main Road",
"latitude": 18.5204,
"longitude": 73.8567
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"title": "North station",
"description": "University Road",
"latitude": 18.559,
"longitude": 73.807
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -317,10 +317,10 @@ page MapDetail {
title="Service location"
description="Public service office"
items='[
&#123;
<span>&#123;</span>
"title": "Public office",
"description": "City centre"
&#125;
<span>&#125;</span>
]'
variant="soft"
/&gt;</code></pre>
@@ -346,15 +346,15 @@ page MapDetail {
title="Jurisdiction overview"
description="Stations, service areas, and public facilities."
items='[
&#123;
<span>&#123;</span>
"title": "Station A"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"title": "Station B"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"title": "Control room"
&#125;
<span>&#125;</span>
]'
variant="raised"
/&gt;</code></pre>
@@ -37,7 +37,7 @@ page MarqueeDetail {
title="Marquee example"
description="A polished default marquee for a modern application."
items='[
&#123;
<span>&#123;</span>
"id": "marquee-0-1",
"key": "marquee-0-1",
"value": "primary",
@@ -66,31 +66,31 @@ page MarqueeDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "marquee-0-2",
"key": "marquee-0-2",
"value": "secondary",
@@ -119,30 +119,30 @@ page MarqueeDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -282,21 +282,21 @@ page MarqueeDetail {
title="Supported capabilities"
description="A polished default marquee for a modern application."
items='[
&#123;
<span>&#123;</span>
"label": "SSR"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "CSR"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Themes"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Accessibility"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Responsive UI"
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -320,15 +320,15 @@ page MarqueeDetail {
title="Compact Marquee"
description="A compact configuration for dashboards and dense product surfaces."
items='[
&#123;
<span>&#123;</span>
"label": "All services operational"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "New components published"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Documentation updated"
&#125;
<span>&#125;</span>
]'
variant="soft"
/&gt;</code></pre>
@@ -354,18 +354,18 @@ page MarqueeDetail {
title="Built for complete digital platforms"
description="A richer configuration demonstrating additional content and hierarchy."
items='[
&#123;
<span>&#123;</span>
"label": "Public portals",
"icon": "icon-[lucide--landmark]"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Enterprise systems",
"icon": "icon-[lucide--building-2]"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Developer tools",
"icon": "icon-[lucide--code-2]"
&#125;
<span>&#125;</span>
]'
variant="raised"
/&gt;</code></pre>
@@ -35,7 +35,7 @@ page MegaMenuDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;MegaMenu
items='[
&#123;
<span>&#123;</span>
"id": "mega-menu-0-1",
"key": "mega-menu-0-1",
"value": "primary",
@@ -64,31 +64,31 @@ page MegaMenuDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "mega-menu-0-2",
"key": "mega-menu-0-2",
"value": "secondary",
@@ -117,30 +117,30 @@ page MegaMenuDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -278,7 +278,7 @@ page MegaMenuDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;MegaMenu
items='[
&#123;
<span>&#123;</span>
"id": "mega-menu-0-1",
"key": "mega-menu-0-1",
"value": "primary",
@@ -307,31 +307,31 @@ page MegaMenuDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "mega-menu-0-2",
"key": "mega-menu-0-2",
"value": "secondary",
@@ -360,30 +360,30 @@ page MegaMenuDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -406,7 +406,7 @@ page MegaMenuDetail {
size="sm"
label="Compact example"
items='[
&#123;
<span>&#123;</span>
"id": "mega-menu-1-1",
"key": "mega-menu-1-1",
"value": "primary",
@@ -435,31 +435,31 @@ page MegaMenuDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "mega-menu-1-2",
"key": "mega-menu-1-2",
"value": "secondary",
@@ -488,30 +488,30 @@ page MegaMenuDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -534,7 +534,7 @@ page MegaMenuDetail {
size="lg"
label="Advanced example"
items='[
&#123;
<span>&#123;</span>
"id": "mega-menu-2-1",
"key": "mega-menu-2-1",
"value": "primary",
@@ -563,31 +563,31 @@ page MegaMenuDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "mega-menu-2-2",
"key": "mega-menu-2-2",
"value": "secondary",
@@ -616,30 +616,30 @@ page MegaMenuDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -649,21 +649,21 @@ page MegaMenuDetail {
@open='console.log(payload)'
@close='console.log(payload)'
@select='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"MegaMenu\"], [data-component=\"MegaMenu\"]")
if (component) registerOutputHandler(component, "open", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "open", (payload) =&gt; <span>&#123;</span>
console.log("open", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "close", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "close", (payload) =&gt; <span>&#123;</span>
console.log("close", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "select", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span>
console.log("select", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Mega Menu"</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>orientation</code></td><td>string</td><td><code>"horizontal"</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">
@@ -188,17 +188,17 @@ page MetricCardDetail {
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@select</code><span>Fires when an item, option, card, or result is selected.</span></div><div><code>@action</code><span>Fires when the component's primary or item action is activated.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;MetricCard
@select='console.log(payload)'
@action='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"MetricCard\"], [data-component=\"MetricCard\"]")
if (component) registerOutputHandler(component, "select", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span>
console.log("select", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "action", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "action", (payload) =&gt; <span>&#123;</span>
console.log("action", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>label</code></td><td>string</td><td><code>"Metric"</code></td><td>No</td></tr><tr><td><code>value</code></td><td>string</td><td><code>"0"</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>""</code></td><td>No</td></tr><tr><td><code>iconStyle</code></td><td>string</td><td><code>"soft"</code></td><td>No</td></tr><tr><td><code>prefix</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>suffix</code></td><td>string</td><td><code>""</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>trend</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>trendLabel</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>trendDirection</code></td><td>string</td><td><code>"neutral"</code></td><td>No</td></tr><tr><td><code>progress</code></td><td>number</td><td><code>-1</code></td><td>No</td></tr><tr><td><code>progressLabel</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>external</code></td><td>boolean</td><td><code>false</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>actionIcon</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>showArrow</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>disabled</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>hover</code></td><td>string</td><td><code>"lift"</code></td><td>No</td></tr><tr><td><code>align</code></td><td>string</td><td><code>"left"</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 MetricGridDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;MetricGrid
items='[
&#123;
<span>&#123;</span>
"id": "metric-grid-0-1",
"key": "metric-grid-0-1",
"value": "primary",
@@ -70,31 +70,31 @@ page MetricGridDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "metric-grid-0-2",
"key": "metric-grid-0-2",
"value": "secondary",
@@ -123,30 +123,30 @@ page MetricGridDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -208,7 +208,7 @@ page MetricGridDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;MetricGrid
items='[
&#123;
<span>&#123;</span>
"label": "Public services",
"value": "24",
"description": "Citizen-facing services available online.",
@@ -217,8 +217,8 @@ page MetricGridDetail {
"trendLabel": "this release",
"trendDirection": "up",
"color": "primary"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Police stations",
"value": "126",
"description": "Searchable station and jurisdiction entries.",
@@ -226,8 +226,8 @@ page MetricGridDetail {
"trend": "Updated",
"trendLabel": "today",
"color": "info"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Availability",
"value": "99.9",
"suffix": "%",
@@ -237,7 +237,7 @@ page MetricGridDetail {
"trendLabel": "30 days",
"trendDirection": "positive",
"color": "success"
&#125;
<span>&#125;</span>
]'
columns="3"
gap="lg"
@@ -260,7 +260,7 @@ page MetricGridDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;MetricGrid
items='[
&#123;
<span>&#123;</span>
"label": "Public services",
"value": "24",
"description": "Citizen-facing services available online.",
@@ -269,8 +269,8 @@ page MetricGridDetail {
"trendLabel": "this release",
"trendDirection": "up",
"color": "primary"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Police stations",
"value": "126",
"description": "Searchable station and jurisdiction entries.",
@@ -278,8 +278,8 @@ page MetricGridDetail {
"trend": "Updated",
"trendLabel": "today",
"color": "info"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Availability",
"value": "99.9",
"suffix": "%",
@@ -289,7 +289,7 @@ page MetricGridDetail {
"trendLabel": "30 days",
"trendDirection": "positive",
"color": "success"
&#125;
<span>&#125;</span>
]'
columns="3"
gap="none"
@@ -316,7 +316,7 @@ page MetricGridDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;MetricGrid
items='[
&#123;
<span>&#123;</span>
"label": "Public services",
"value": "24",
"description": "Citizen-facing services available online.",
@@ -325,8 +325,8 @@ page MetricGridDetail {
"trendLabel": "this release",
"trendDirection": "up",
"color": "primary"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Police stations",
"value": "126",
"description": "Searchable station and jurisdiction entries.",
@@ -334,8 +334,8 @@ page MetricGridDetail {
"trend": "Updated",
"trendLabel": "today",
"color": "info"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Availability",
"value": "99.9",
"suffix": "%",
@@ -345,8 +345,8 @@ page MetricGridDetail {
"trendLabel": "30 days",
"trendDirection": "positive",
"color": "success"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Response time",
"value": "1.8",
"suffix": "s",
@@ -355,7 +355,7 @@ page MetricGridDetail {
"color": "warning",
"progress": 72,
"progressLabel": "Target"
&#125;
<span>&#125;</span>
]'
size="lg"
variant="soft"
@@ -367,17 +367,17 @@ page MetricGridDetail {
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@select</code><span>Fires when an item, option, card, or result is selected.</span></div><div><code>@action</code><span>Fires when the component's primary or item action is activated.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;MetricGrid
@select='console.log(payload)'
@action='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"MetricGrid\"], [data-component=\"MetricGrid\"]")
if (component) registerOutputHandler(component, "select", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span>
console.log("select", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "action", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "action", (payload) =&gt; <span>&#123;</span>
console.log("action", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>columns</code></td><td>number</td><td><code>4</code></td><td>No</td></tr><tr><td><code>tabletColumns</code></td><td>number</td><td><code>2</code></td><td>No</td></tr><tr><td><code>mobileColumns</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>"md"</code></td><td>No</td></tr><tr><td><code>equalHeight</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>dividers</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>maxWidth</code></td><td>string</td><td><code>"full"</code></td><td>No</td></tr><tr><td><code>minItemWidth</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 one or more lines are too long
@@ -35,7 +35,7 @@ page NavDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;Nav
items='[
&#123;
<span>&#123;</span>
"id": "nav-0-1",
"key": "nav-0-1",
"value": "primary",
@@ -64,31 +64,31 @@ page NavDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "nav-0-2",
"key": "nav-0-2",
"value": "secondary",
@@ -117,30 +117,30 @@ page NavDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -278,7 +278,7 @@ page NavDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Nav
items='[
&#123;
<span>&#123;</span>
"id": "nav-0-1",
"key": "nav-0-1",
"value": "primary",
@@ -307,31 +307,31 @@ page NavDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "nav-0-2",
"key": "nav-0-2",
"value": "secondary",
@@ -360,30 +360,30 @@ page NavDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -406,7 +406,7 @@ page NavDetail {
size="sm"
label="Compact example"
items='[
&#123;
<span>&#123;</span>
"id": "nav-1-1",
"key": "nav-1-1",
"value": "primary",
@@ -435,31 +435,31 @@ page NavDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "nav-1-2",
"key": "nav-1-2",
"value": "secondary",
@@ -488,30 +488,30 @@ page NavDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -534,7 +534,7 @@ page NavDetail {
size="lg"
label="Advanced example"
items='[
&#123;
<span>&#123;</span>
"id": "nav-2-1",
"key": "nav-2-1",
"value": "primary",
@@ -563,31 +563,31 @@ page NavDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "nav-2-2",
"key": "nav-2-2",
"value": "secondary",
@@ -616,30 +616,30 @@ page NavDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -648,17 +648,17 @@ page NavDetail {
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@select</code><span>Fires when an item, option, card, or result is selected.</span></div><div><code>@change</code><span>Fires when the component's committed value or selection changes.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;Nav
@select='console.log(payload)'
@change='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Nav\"], [data-component=\"Nav\"]")
if (component) registerOutputHandler(component, "select", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span>
console.log("select", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "change", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Nav"</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>orientation</code></td><td>string</td><td><code>"horizontal"</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">
@@ -42,7 +42,7 @@ page NavbarDetail {
<pre><code data-playground-code>&lt;Navbar
label="Navbar"
topbarLabel="Navbar"
brand='&#123;
brand='<span>&#123;</span>
"id": "navbar-0-1",
"key": "navbar-0-1",
"value": "primary",
@@ -71,32 +71,32 @@ page NavbarDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;'
<span>&#125;</span>'
items='[
&#123;
<span>&#123;</span>
"id": "navbar-0-1",
"key": "navbar-0-1",
"value": "primary",
@@ -125,31 +125,31 @@ page NavbarDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "navbar-0-2",
"key": "navbar-0-2",
"value": "secondary",
@@ -178,33 +178,33 @@ page NavbarDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
actions='[
&#123;
<span>&#123;</span>
"id": "navbar-0-1",
"key": "navbar-0-1",
"value": "primary",
@@ -233,31 +233,31 @@ page NavbarDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "navbar-0-2",
"key": "navbar-0-2",
"value": "secondary",
@@ -286,30 +286,30 @@ page NavbarDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
mobileLabel="Navbar"&gt;
&lt;div data-slot="topbar"&gt;
@@ -614,7 +614,7 @@ page NavbarDetail {
<pre><code>&lt;Navbar
label="Navbar"
topbarLabel="Navbar"
brand='&#123;
brand='<span>&#123;</span>
"id": "navbar-0-1",
"key": "navbar-0-1",
"value": "primary",
@@ -643,32 +643,32 @@ page NavbarDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;'
<span>&#125;</span>'
items='[
&#123;
<span>&#123;</span>
"id": "navbar-0-1",
"key": "navbar-0-1",
"value": "primary",
@@ -697,31 +697,31 @@ page NavbarDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "navbar-0-2",
"key": "navbar-0-2",
"value": "secondary",
@@ -750,33 +750,33 @@ page NavbarDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
actions='[
&#123;
<span>&#123;</span>
"id": "navbar-0-1",
"key": "navbar-0-1",
"value": "primary",
@@ -805,31 +805,31 @@ page NavbarDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "navbar-0-2",
"key": "navbar-0-2",
"value": "secondary",
@@ -858,30 +858,30 @@ page NavbarDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
mobileLabel="Navbar"&gt;
&lt;div data-slot="topbar"&gt;
@@ -911,7 +911,7 @@ page NavbarDetail {
size="sm"
label="Compact example"
topbarLabel="Compact example"
brand='&#123;
brand='<span>&#123;</span>
"id": "navbar-1-1",
"key": "navbar-1-1",
"value": "primary",
@@ -940,32 +940,32 @@ page NavbarDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;'
<span>&#125;</span>'
items='[
&#123;
<span>&#123;</span>
"id": "navbar-1-1",
"key": "navbar-1-1",
"value": "primary",
@@ -994,31 +994,31 @@ page NavbarDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "navbar-1-2",
"key": "navbar-1-2",
"value": "secondary",
@@ -1047,33 +1047,33 @@ page NavbarDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
actions='[
&#123;
<span>&#123;</span>
"id": "navbar-1-1",
"key": "navbar-1-1",
"value": "primary",
@@ -1102,31 +1102,31 @@ page NavbarDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "navbar-1-2",
"key": "navbar-1-2",
"value": "secondary",
@@ -1155,30 +1155,30 @@ page NavbarDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
mobileLabel="Compact example"&gt;
&lt;div data-slot="topbar"&gt;
@@ -1208,7 +1208,7 @@ page NavbarDetail {
size="lg"
label="Advanced example"
topbarLabel="Advanced example"
brand='&#123;
brand='<span>&#123;</span>
"id": "navbar-2-1",
"key": "navbar-2-1",
"value": "primary",
@@ -1237,32 +1237,32 @@ page NavbarDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;'
<span>&#125;</span>'
items='[
&#123;
<span>&#123;</span>
"id": "navbar-2-1",
"key": "navbar-2-1",
"value": "primary",
@@ -1291,31 +1291,31 @@ page NavbarDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "navbar-2-2",
"key": "navbar-2-2",
"value": "secondary",
@@ -1344,33 +1344,33 @@ page NavbarDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
actions='[
&#123;
<span>&#123;</span>
"id": "navbar-2-1",
"key": "navbar-2-1",
"value": "primary",
@@ -1399,31 +1399,31 @@ page NavbarDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "navbar-2-2",
"key": "navbar-2-2",
"value": "secondary",
@@ -1452,30 +1452,30 @@ page NavbarDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
mobileLabel="Advanced example"&gt;
&lt;div data-slot="topbar"&gt;
@@ -1494,30 +1494,30 @@ page NavbarDetail {
@close='console.log(payload)'
@select='console.log(payload)'
@action='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Navbar\"], [data-component=\"Navbar\"]")
if (component) registerOutputHandler(component, "toggle", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "toggle", (payload) =&gt; <span>&#123;</span>
console.log("toggle", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "open", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "open", (payload) =&gt; <span>&#123;</span>
console.log("open", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "close", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "close", (payload) =&gt; <span>&#123;</span>
console.log("close", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "select", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span>
console.log("select", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "action", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "action", (payload) =&gt; <span>&#123;</span>
console.log("action", payload)
&#125;)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Primary navigation"</code></td><td>No</td></tr><tr><td><code>topbarLabel</code></td><td>string</td><td><code>"Utility navigation"</code></td><td>No</td></tr><tr><td><code>brand</code></td><td>Record&lt;string, unknown&gt;</td><td><code>&#123;&#125;</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>active</code></td><td>string</td><td><code>""</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>openOnHover</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>"full"</code></td><td>No</td></tr><tr><td><code>mobileLabel</code></td><td>string</td><td><code>"Toggle navigation"</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>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Primary navigation"</code></td><td>No</td></tr><tr><td><code>topbarLabel</code></td><td>string</td><td><code>"Utility navigation"</code></td><td>No</td></tr><tr><td><code>brand</code></td><td>Record&lt;string, unknown&gt;</td><td><code><span>&#123;</span><span>&#125;</span></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>active</code></td><td>string</td><td><code>""</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>openOnHover</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>"full"</code></td><td>No</td></tr><tr><td><code>mobileLabel</code></td><td>string</td><td><code>"Toggle navigation"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main>
<aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#navbar-demo-1">Production default</a><a href="#navbar-demo-2">Compact application</a><a href="#navbar-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
@@ -57,7 +57,7 @@ page PageHeaderDetail {
align="start"
showBreadcrumbs="true"
breadcrumbs='[
&#123;
<span>&#123;</span>
"id": "page-header-0-1",
"key": "page-header-0-1",
"value": "primary",
@@ -86,31 +86,31 @@ page PageHeaderDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "page-header-0-2",
"key": "page-header-0-2",
"value": "secondary",
@@ -139,30 +139,30 @@ page PageHeaderDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
breadcrumbParentHref="#page-header-demo"
primaryLabel="Page Header"
@@ -225,19 +225,19 @@ page PageHeaderDetail {
maxWidth="full"
showBreadcrumbs="true"
breadcrumbs='[
&#123;
<span>&#123;</span>
"label": "Home",
"href": "/",
"icon": "icon-[lucide--house]"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Components",
"href": "/base"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Page header",
"current": true
&#125;
<span>&#125;</span>
]'
breadcrumbParentHref="#page-header-demo"
primaryLabel="Primary action"
@@ -279,7 +279,7 @@ page PageHeaderDetail {
size="sm"
showBreadcrumbs="true"
breadcrumbs='[
&#123;
<span>&#123;</span>
"id": "page-header-1-1",
"key": "page-header-1-1",
"value": "primary",
@@ -308,31 +308,31 @@ page PageHeaderDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "page-header-1-2",
"key": "page-header-1-2",
"value": "secondary",
@@ -361,30 +361,30 @@ page PageHeaderDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
breadcrumbParentHref="#page-header-demo"
primaryLabel="Track status"
@@ -426,7 +426,7 @@ page PageHeaderDetail {
maxWidth="wide"
showBreadcrumbs="true"
breadcrumbs='[
&#123;
<span>&#123;</span>
"id": "page-header-2-1",
"key": "page-header-2-1",
"value": "primary",
@@ -455,31 +455,31 @@ page PageHeaderDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "page-header-2-2",
"key": "page-header-2-2",
"value": "secondary",
@@ -508,30 +508,30 @@ page PageHeaderDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
breadcrumbParentHref="#page-header-demo"
primaryLabel="Advanced example"
@@ -35,7 +35,7 @@ page PaginationDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;Pagination
items='[
&#123;
<span>&#123;</span>
"id": "pagination-0-1",
"key": "pagination-0-1",
"value": "primary",
@@ -64,31 +64,31 @@ page PaginationDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "pagination-0-2",
"key": "pagination-0-2",
"value": "secondary",
@@ -117,30 +117,30 @@ page PaginationDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -278,7 +278,7 @@ page PaginationDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Pagination
items='[
&#123;
<span>&#123;</span>
"id": "pagination-0-1",
"key": "pagination-0-1",
"value": "primary",
@@ -307,31 +307,31 @@ page PaginationDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "pagination-0-2",
"key": "pagination-0-2",
"value": "secondary",
@@ -360,30 +360,30 @@ page PaginationDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -406,7 +406,7 @@ page PaginationDetail {
size="sm"
label="Compact example"
items='[
&#123;
<span>&#123;</span>
"id": "pagination-1-1",
"key": "pagination-1-1",
"value": "primary",
@@ -435,31 +435,31 @@ page PaginationDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "pagination-1-2",
"key": "pagination-1-2",
"value": "secondary",
@@ -488,30 +488,30 @@ page PaginationDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -534,7 +534,7 @@ page PaginationDetail {
size="lg"
label="Advanced example"
items='[
&#123;
<span>&#123;</span>
"id": "pagination-2-1",
"key": "pagination-2-1",
"value": "primary",
@@ -563,31 +563,31 @@ page PaginationDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "pagination-2-2",
"key": "pagination-2-2",
"value": "secondary",
@@ -616,30 +616,30 @@ page PaginationDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -649,21 +649,21 @@ page PaginationDetail {
@change='console.log(payload)'
@previous='console.log(payload)'
@next='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Pagination\"], [data-component=\"Pagination\"]")
if (component) registerOutputHandler(component, "change", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "previous", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "previous", (payload) =&gt; <span>&#123;</span>
console.log("previous", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "next", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "next", (payload) =&gt; <span>&#123;</span>
console.log("next", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Pagination"</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>orientation</code></td><td>string</td><td><code>"horizontal"</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">
@@ -436,33 +436,33 @@ page PinInputDetail {
@paste='console.log(payload)'
@clear='console.log(payload)'
@error='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"PinInput\"], [data-component=\"PinInput\"]")
if (component) registerOutputHandler(component, "input", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "input", (payload) =&gt; <span>&#123;</span>
console.log("input", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "change", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "complete", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "complete", (payload) =&gt; <span>&#123;</span>
console.log("complete", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "paste", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "paste", (payload) =&gt; <span>&#123;</span>
console.log("paste", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "clear", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "clear", (payload) =&gt; <span>&#123;</span>
console.log("clear", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "error", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "error", (payload) =&gt; <span>&#123;</span>
console.log("error", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Verification code"</code></td><td>No</td></tr><tr><td><code>name</code></td><td>string</td><td><code>"pin"</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>length</code></td><td>number</td><td><code>4</code></td><td>No</td></tr><tr><td><code>pattern</code></td><td>string</td><td><code>"[0-9]"</code></td><td>No</td></tr><tr><td><code>type</code></td><td>string</td><td><code>"text"</code></td><td>No</td></tr><tr><td><code>inputMode</code></td><td>string</td><td><code>"numeric"</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>autocomplete</code></td><td>string</td><td><code>"one-time-code"</code></td><td>No</td></tr><tr><td><code>masked</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>readonly</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>autoFocus</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>autoSubmit</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>allowPaste</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>clearable</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>clearLabel</code></td><td>string</td><td><code>"Clear code"</code></td><td>No</td></tr><tr><td><code>separator</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>groupSize</code></td><td>number</td><td><code>0</code></td><td>No</td></tr><tr><td><code>helpText</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>invalid</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>validationMessage</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">
@@ -203,25 +203,25 @@ page PopoverDetail {
@open='console.log(payload)'
@close='console.log(payload)'
@action='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Popover\"], [data-component=\"Popover\"]")
if (component) registerOutputHandler(component, "toggle", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "toggle", (payload) =&gt; <span>&#123;</span>
console.log("toggle", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "open", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "open", (payload) =&gt; <span>&#123;</span>
console.log("open", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "close", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "close", (payload) =&gt; <span>&#123;</span>
console.log("close", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "action", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "action", (payload) =&gt; <span>&#123;</span>
console.log("action", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>open</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>defaultOpen</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>triggerLabel</code></td><td>string</td><td><code>"Open popover"</code></td><td>No</td></tr><tr><td><code>triggerIcon</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>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>""</code></td><td>No</td></tr><tr><td><code>placement</code></td><td>string</td><td><code>"bottom-start"</code></td><td>No</td></tr><tr><td><code>width</code></td><td>string</td><td><code>"md"</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>"raised"</code></td><td>No</td></tr><tr><td><code>showArrow</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>showClose</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>closeLabel</code></td><td>string</td><td><code>"Close popover"</code></td><td>No</td></tr><tr><td><code>closeOnOutside</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>closeOnEscape</code></td><td>boolean</td><td><code>true</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>closeOnAction</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>disabled</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main>
<aside class="detail-aside">
File diff suppressed because it is too large Load Diff
@@ -125,21 +125,21 @@ page PreferenceSwitcherDetail {
@theme='console.log(payload)'
@color='console.log(payload)'
@language='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"PreferenceSwitcher\"], [data-component=\"PreferenceSwitcher\"]")
if (component) registerOutputHandler(component, "theme", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "theme", (payload) =&gt; <span>&#123;</span>
console.log("theme", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "color", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "color", (payload) =&gt; <span>&#123;</span>
console.log("color", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "language", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "language", (payload) =&gt; <span>&#123;</span>
console.log("language", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>themeLabel</code></td><td>string</td><td><code>"Theme"</code></td><td>No</td></tr><tr><td><code>colorLabel</code></td><td>string</td><td><code>"Accent color"</code></td><td>No</td></tr><tr><td><code>languageLabel</code></td><td>string</td><td><code>"Language"</code></td><td>No</td></tr><tr><td><code>languages</code></td><td>string</td><td><code>[</code></td><td>No</td></tr><tr><td><code>colors</code></td><td>string</td><td><code>[</code></td><td>No</td></tr><tr><td><code>compact</code></td><td>boolean</td><td><code>true</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">
@@ -148,7 +148,7 @@ if (component) registerOutputHandler(component, "language", (payload) =&gt; &#12
</div>
<nav class="detail-pagination">
<a href="/components/portal-dashboard"><small>Previous</small><strong>← Portal Dashboard</strong></a>
<span></span>
<a href="/components/toaster"><small>Next</small><strong>Toaster →</strong></a>
</nav>
}
}
@@ -55,7 +55,7 @@ page RadioDetail {
placeholder="Enter a sample value"
value="sample-1"
options='[
&#123;
<span>&#123;</span>
"id": "radio-0-1",
"key": "radio-0-1",
"value": "primary",
@@ -84,31 +84,31 @@ page RadioDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "radio-0-2",
"key": "radio-0-2",
"value": "secondary",
@@ -137,30 +137,30 @@ page RadioDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -300,7 +300,7 @@ page RadioDetail {
placeholder="Enter a sample value"
value="sample-1"
options='[
&#123;
<span>&#123;</span>
"id": "radio-0-1",
"key": "radio-0-1",
"value": "primary",
@@ -329,31 +329,31 @@ page RadioDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "radio-0-2",
"key": "radio-0-2",
"value": "secondary",
@@ -382,30 +382,30 @@ page RadioDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -432,7 +432,7 @@ page RadioDetail {
icon="icon-[lucide--arrow-right]"
value="sample-2"
options='[
&#123;
<span>&#123;</span>
"id": "radio-1-1",
"key": "radio-1-1",
"value": "primary",
@@ -461,31 +461,31 @@ page RadioDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "radio-1-2",
"key": "radio-1-2",
"value": "secondary",
@@ -514,30 +514,30 @@ page RadioDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -564,7 +564,7 @@ page RadioDetail {
icon="icon-[lucide--trash-2]"
value="sample-3"
options='[
&#123;
<span>&#123;</span>
"id": "radio-2-1",
"key": "radio-2-1",
"value": "primary",
@@ -593,31 +593,31 @@ page RadioDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "radio-2-2",
"key": "radio-2-2",
"value": "secondary",
@@ -646,30 +646,30 @@ page RadioDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -681,29 +681,29 @@ page RadioDetail {
@focus='console.log(payload)'
@blur='console.log(payload)'
@invalid='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Radio\"], [data-component=\"Radio\"]")
if (component) registerOutputHandler(component, "input", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "input", (payload) =&gt; <span>&#123;</span>
console.log("input", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "change", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "focus", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "focus", (payload) =&gt; <span>&#123;</span>
console.log("focus", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "blur", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "blur", (payload) =&gt; <span>&#123;</span>
console.log("blur", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "invalid", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "invalid", (payload) =&gt; <span>&#123;</span>
console.log("invalid", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>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>"Radio"</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>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>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">
@@ -58,7 +58,7 @@ page RangeSliderDetail {
value="36"
showSteps="true"
marks='[
&#123;
<span>&#123;</span>
"id": "range-slider-0-1",
"key": "range-slider-0-1",
"value": "primary",
@@ -87,31 +87,31 @@ page RangeSliderDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "range-slider-0-2",
"key": "range-slider-0-2",
"value": "secondary",
@@ -140,30 +140,30 @@ page RangeSliderDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -305,7 +305,7 @@ page RangeSliderDetail {
value="36"
showSteps="true"
marks='[
&#123;
<span>&#123;</span>
"id": "range-slider-0-1",
"key": "range-slider-0-1",
"value": "primary",
@@ -334,31 +334,31 @@ page RangeSliderDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "range-slider-0-2",
"key": "range-slider-0-2",
"value": "secondary",
@@ -387,30 +387,30 @@ page RangeSliderDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -438,7 +438,7 @@ page RangeSliderDetail {
value="60"
showSteps="true"
marks='[
&#123;
<span>&#123;</span>
"id": "range-slider-1-1",
"key": "range-slider-1-1",
"value": "primary",
@@ -467,31 +467,31 @@ page RangeSliderDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "range-slider-1-2",
"key": "range-slider-1-2",
"value": "secondary",
@@ -520,30 +520,30 @@ page RangeSliderDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -571,7 +571,7 @@ page RangeSliderDetail {
value="84"
showSteps="true"
marks='[
&#123;
<span>&#123;</span>
"id": "range-slider-2-1",
"key": "range-slider-2-1",
"value": "primary",
@@ -600,31 +600,31 @@ page RangeSliderDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "range-slider-2-2",
"key": "range-slider-2-2",
"value": "secondary",
@@ -653,30 +653,30 @@ page RangeSliderDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -687,25 +687,25 @@ page RangeSliderDetail {
@change='console.log(payload)'
@focus='console.log(payload)'
@blur='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"RangeSlider\"], [data-component=\"RangeSlider\"]")
if (component) registerOutputHandler(component, "input", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "input", (payload) =&gt; <span>&#123;</span>
console.log("input", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "change", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "focus", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "focus", (payload) =&gt; <span>&#123;</span>
console.log("focus", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "blur", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "blur", (payload) =&gt; <span>&#123;</span>
console.log("blur", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>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>"Range"</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>number</td><td><code>50</code></td><td>No</td></tr><tr><td><code>min</code></td><td>number</td><td><code>0</code></td><td>No</td></tr><tr><td><code>max</code></td><td>number</td><td><code>100</code></td><td>No</td></tr><tr><td><code>step</code></td><td>number</td><td><code>1</code></td><td>No</td></tr><tr><td><code>showValue</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>showBounds</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>showSteps</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>marks</code></td><td>unknown[]</td><td><code>[]</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 RatingDetail {
title="Rating example"
description="A polished default rating for a modern application."
items='[
&#123;
<span>&#123;</span>
"id": "rating-0-1",
"key": "rating-0-1",
"value": "primary",
@@ -66,31 +66,31 @@ page RatingDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "rating-0-2",
"key": "rating-0-2",
"value": "secondary",
@@ -119,30 +119,30 @@ page RatingDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -282,7 +282,7 @@ page RatingDetail {
title="Rating example"
description="A polished default rating for a modern application."
items='[
&#123;
<span>&#123;</span>
"id": "rating-0-1",
"key": "rating-0-1",
"value": "primary",
@@ -311,31 +311,31 @@ page RatingDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "rating-0-2",
"key": "rating-0-2",
"value": "secondary",
@@ -364,30 +364,30 @@ page RatingDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -411,7 +411,7 @@ page RatingDetail {
title="Compact Rating"
description="A compact configuration for dashboards and dense product surfaces."
items='[
&#123;
<span>&#123;</span>
"id": "rating-1-1",
"key": "rating-1-1",
"value": "primary",
@@ -440,31 +440,31 @@ page RatingDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "rating-1-2",
"key": "rating-1-2",
"value": "secondary",
@@ -493,30 +493,30 @@ page RatingDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
variant="secondary"
/&gt;</code></pre>
@@ -541,7 +541,7 @@ page RatingDetail {
title="Advanced Rating"
description="A richer configuration demonstrating additional content and hierarchy."
items='[
&#123;
<span>&#123;</span>
"id": "rating-2-1",
"key": "rating-2-1",
"value": "primary",
@@ -570,31 +570,31 @@ page RatingDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "rating-2-2",
"key": "rating-2-2",
"value": "secondary",
@@ -623,30 +623,30 @@ page RatingDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
variant="success"
/&gt;</code></pre>
@@ -656,17 +656,17 @@ page RatingDetail {
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@input</code><span>Fires as the editable value changes.</span></div><div><code>@change</code><span>Fires when the component's committed value or selection changes.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;Rating
@input='console.log(payload)'
@change='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Rating\"], [data-component=\"Rating\"]")
if (component) registerOutputHandler(component, "input", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "input", (payload) =&gt; <span>&#123;</span>
console.log("input", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "change", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Rating"</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">
@@ -35,7 +35,7 @@ page ScrollspyDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;Scrollspy
items='[
&#123;
<span>&#123;</span>
"id": "scrollspy-0-1",
"key": "scrollspy-0-1",
"value": "primary",
@@ -64,31 +64,31 @@ page ScrollspyDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "scrollspy-0-2",
"key": "scrollspy-0-2",
"value": "secondary",
@@ -117,30 +117,30 @@ page ScrollspyDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -278,7 +278,7 @@ page ScrollspyDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Scrollspy
items='[
&#123;
<span>&#123;</span>
"id": "scrollspy-0-1",
"key": "scrollspy-0-1",
"value": "primary",
@@ -307,31 +307,31 @@ page ScrollspyDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "scrollspy-0-2",
"key": "scrollspy-0-2",
"value": "secondary",
@@ -360,30 +360,30 @@ page ScrollspyDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -406,7 +406,7 @@ page ScrollspyDetail {
size="sm"
label="Compact example"
items='[
&#123;
<span>&#123;</span>
"id": "scrollspy-1-1",
"key": "scrollspy-1-1",
"value": "primary",
@@ -435,31 +435,31 @@ page ScrollspyDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "scrollspy-1-2",
"key": "scrollspy-1-2",
"value": "secondary",
@@ -488,30 +488,30 @@ page ScrollspyDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -534,7 +534,7 @@ page ScrollspyDetail {
size="lg"
label="Advanced example"
items='[
&#123;
<span>&#123;</span>
"id": "scrollspy-2-1",
"key": "scrollspy-2-1",
"value": "primary",
@@ -563,31 +563,31 @@ page ScrollspyDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "scrollspy-2-2",
"key": "scrollspy-2-2",
"value": "secondary",
@@ -616,30 +616,30 @@ page ScrollspyDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -647,13 +647,13 @@ page ScrollspyDetail {
</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>@change</code><span>Fires when the component's committed value or selection changes.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;Scrollspy
@change='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Scrollspy\"], [data-component=\"Scrollspy\"]")
if (component) registerOutputHandler(component, "change", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Scrollspy"</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>orientation</code></td><td>string</td><td><code>"horizontal"</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">
@@ -51,7 +51,7 @@ page SelectDetail {
<pre><code data-playground-code>&lt;Select
value="sample-1"
values='[
&#123;
<span>&#123;</span>
"id": "select-0-1",
"key": "select-0-1",
"value": "primary",
@@ -80,31 +80,31 @@ page SelectDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "select-0-2",
"key": "select-0-2",
"value": "secondary",
@@ -133,33 +133,33 @@ page SelectDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
options='[
&#123;
<span>&#123;</span>
"id": "select-0-1",
"key": "select-0-1",
"value": "primary",
@@ -188,31 +188,31 @@ page SelectDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "select-0-2",
"key": "select-0-2",
"value": "secondary",
@@ -241,30 +241,30 @@ page SelectDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
placeholder="Enter a sample value"
/&gt;</code></pre>
@@ -511,7 +511,7 @@ page SelectDetail {
<pre><code>&lt;Select
value="sample-1"
values='[
&#123;
<span>&#123;</span>
"id": "select-0-1",
"key": "select-0-1",
"value": "primary",
@@ -540,31 +540,31 @@ page SelectDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "select-0-2",
"key": "select-0-2",
"value": "secondary",
@@ -593,33 +593,33 @@ page SelectDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
options='[
&#123;
<span>&#123;</span>
"id": "select-0-1",
"key": "select-0-1",
"value": "primary",
@@ -648,31 +648,31 @@ page SelectDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "select-0-2",
"key": "select-0-2",
"value": "secondary",
@@ -701,30 +701,30 @@ page SelectDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
placeholder="Enter a sample value"
/&gt;</code></pre>
@@ -749,7 +749,7 @@ page SelectDetail {
label="Compact example"
value="sample-2"
values='[
&#123;
<span>&#123;</span>
"id": "select-1-1",
"key": "select-1-1",
"value": "primary",
@@ -778,31 +778,31 @@ page SelectDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "select-1-2",
"key": "select-1-2",
"value": "secondary",
@@ -831,33 +831,33 @@ page SelectDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
options='[
&#123;
<span>&#123;</span>
"id": "select-1-1",
"key": "select-1-1",
"value": "primary",
@@ -886,31 +886,31 @@ page SelectDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "select-1-2",
"key": "select-1-2",
"value": "secondary",
@@ -939,30 +939,30 @@ page SelectDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
placeholder="Enter a sample value"
variant="secondary"
@@ -989,7 +989,7 @@ page SelectDetail {
label="Advanced example"
value="sample-3"
values='[
&#123;
<span>&#123;</span>
"id": "select-2-1",
"key": "select-2-1",
"value": "primary",
@@ -1018,31 +1018,31 @@ page SelectDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "select-2-2",
"key": "select-2-2",
"value": "secondary",
@@ -1071,33 +1071,33 @@ page SelectDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
options='[
&#123;
<span>&#123;</span>
"id": "select-2-1",
"key": "select-2-1",
"value": "primary",
@@ -1126,31 +1126,31 @@ page SelectDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "select-2-2",
"key": "select-2-2",
"value": "secondary",
@@ -1179,30 +1179,30 @@ page SelectDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
placeholder="Search by name, email, or identifier"
variant="success"
@@ -1219,37 +1219,37 @@ page SelectDetail {
@open='console.log(payload)'
@close='console.log(payload)'
@invalid='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Select\"], [data-component=\"Select\"]")
if (component) registerOutputHandler(component, "input", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "input", (payload) =&gt; <span>&#123;</span>
console.log("input", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "change", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "focus", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "focus", (payload) =&gt; <span>&#123;</span>
console.log("focus", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "blur", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "blur", (payload) =&gt; <span>&#123;</span>
console.log("blur", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "open", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "open", (payload) =&gt; <span>&#123;</span>
console.log("open", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "close", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "close", (payload) =&gt; <span>&#123;</span>
console.log("close", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "invalid", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "invalid", (payload) =&gt; <span>&#123;</span>
console.log("invalid", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>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>"Select"</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>value</code></td><td>string</td><td><code>""</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>placeholder</code></td><td>string</td><td><code>"Select an option"</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>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>multiple</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">
@@ -36,7 +36,7 @@ page SidebarDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;Sidebar
items='[
&#123;
<span>&#123;</span>
"id": "sidebar-0-1",
"key": "sidebar-0-1",
"value": "primary",
@@ -65,31 +65,31 @@ page SidebarDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "sidebar-0-2",
"key": "sidebar-0-2",
"value": "secondary",
@@ -118,30 +118,30 @@ page SidebarDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
mobileLabel="Sidebar"
/&gt;</code></pre>
@@ -280,7 +280,7 @@ page SidebarDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Sidebar
items='[
&#123;
<span>&#123;</span>
"id": "sidebar-0-1",
"key": "sidebar-0-1",
"value": "primary",
@@ -309,31 +309,31 @@ page SidebarDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "sidebar-0-2",
"key": "sidebar-0-2",
"value": "secondary",
@@ -362,30 +362,30 @@ page SidebarDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
mobileLabel="Sidebar"
/&gt;</code></pre>
@@ -409,7 +409,7 @@ page SidebarDetail {
size="sm"
label="Compact example"
items='[
&#123;
<span>&#123;</span>
"id": "sidebar-1-1",
"key": "sidebar-1-1",
"value": "primary",
@@ -438,31 +438,31 @@ page SidebarDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "sidebar-1-2",
"key": "sidebar-1-2",
"value": "secondary",
@@ -491,30 +491,30 @@ page SidebarDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
mobileLabel="Compact example"
/&gt;</code></pre>
@@ -538,7 +538,7 @@ page SidebarDetail {
size="lg"
label="Advanced example"
items='[
&#123;
<span>&#123;</span>
"id": "sidebar-2-1",
"key": "sidebar-2-1",
"value": "primary",
@@ -567,31 +567,31 @@ page SidebarDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "sidebar-2-2",
"key": "sidebar-2-2",
"value": "secondary",
@@ -620,30 +620,30 @@ page SidebarDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'
mobileLabel="Advanced example"
/&gt;</code></pre>
@@ -655,25 +655,25 @@ page SidebarDetail {
@open='console.log(payload)'
@close='console.log(payload)'
@select='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import &#123; registerOutputHandler &#125; from "@wrnexus/csr/outputs"
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"Sidebar\"], [data-component=\"Sidebar\"]")
if (component) registerOutputHandler(component, "toggle", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "toggle", (payload) =&gt; <span>&#123;</span>
console.log("toggle", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "open", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "open", (payload) =&gt; <span>&#123;</span>
console.log("open", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "close", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "close", (payload) =&gt; <span>&#123;</span>
console.log("close", payload)
&#125;)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "select", (payload) =&gt; &#123;
if (component) registerOutputHandler(component, "select", (payload) =&gt; <span>&#123;</span>
console.log("select", payload)
&#125;)</code></pre></section></div></div></section>
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Sidebar"</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>orientation</code></td><td>string</td><td><code>"horizontal"</code></td><td>No</td></tr><tr><td><code>mobileLabel</code></td><td>string</td><td><code>"Open navigation"</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">
@@ -73,7 +73,7 @@ page SplitHeroDetail {
visualTitle="Split Hero example"
visualDescription="A polished default split hero for a modern application."
visualItems='[
&#123;
<span>&#123;</span>
"id": "split-hero-0-1",
"key": "split-hero-0-1",
"value": "primary",
@@ -102,31 +102,31 @@ page SplitHeroDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "split-hero-0-2",
"key": "split-hero-0-2",
"value": "secondary",
@@ -155,33 +155,33 @@ page SplitHeroDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
trustItems='[
&#123;
<span>&#123;</span>
"id": "split-hero-0-1",
"key": "split-hero-0-1",
"value": "primary",
@@ -210,31 +210,31 @@ page SplitHeroDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "split-hero-0-2",
"key": "split-hero-0-2",
"value": "secondary",
@@ -263,30 +263,30 @@ page SplitHeroDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'&gt;
&lt;div data-slot="actions"&gt;
&lt;div class="showcase-slot"&gt;actions slot&lt;/div&gt;
@@ -559,7 +559,7 @@ page SplitHeroDetail {
visualTitle="Split Hero example"
visualDescription="A polished default split hero for a modern application."
visualItems='[
&#123;
<span>&#123;</span>
"id": "split-hero-0-1",
"key": "split-hero-0-1",
"value": "primary",
@@ -588,31 +588,31 @@ page SplitHeroDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "split-hero-0-2",
"key": "split-hero-0-2",
"value": "secondary",
@@ -641,33 +641,33 @@ page SplitHeroDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
trustItems='[
&#123;
<span>&#123;</span>
"id": "split-hero-0-1",
"key": "split-hero-0-1",
"value": "primary",
@@ -696,31 +696,31 @@ page SplitHeroDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "split-hero-0-2",
"key": "split-hero-0-2",
"value": "secondary",
@@ -749,30 +749,30 @@ page SplitHeroDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'&gt;
&lt;div data-slot="actions"&gt;
&lt;div class="showcase-slot"&gt;actions slot&lt;/div&gt;
@@ -826,7 +826,7 @@ page SplitHeroDetail {
visualTitle="Compact Split Hero"
visualDescription="A compact configuration for dashboards and dense product surfaces."
visualItems='[
&#123;
<span>&#123;</span>
"id": "split-hero-1-1",
"key": "split-hero-1-1",
"value": "primary",
@@ -855,31 +855,31 @@ page SplitHeroDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "split-hero-1-2",
"key": "split-hero-1-2",
"value": "secondary",
@@ -908,33 +908,33 @@ page SplitHeroDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
trustItems='[
&#123;
<span>&#123;</span>
"id": "split-hero-1-1",
"key": "split-hero-1-1",
"value": "primary",
@@ -963,31 +963,31 @@ page SplitHeroDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "split-hero-1-2",
"key": "split-hero-1-2",
"value": "secondary",
@@ -1016,30 +1016,30 @@ page SplitHeroDetail {
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'&gt;
&lt;div data-slot="actions"&gt;
&lt;div class="showcase-slot"&gt;actions slot&lt;/div&gt;
@@ -1086,24 +1086,24 @@ page SplitHeroDetail {
visualTitle="Service highlights"
visualDescription="A consistent, responsive supporting panel."
visualItems='[
&#123;
<span>&#123;</span>
"label": "Online access",
"value": "24/7",
"icon": "icon-[lucide--globe-2]"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Response tracking",
"value": "Live",
"icon": "icon-[lucide--activity]"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Languages",
"value": "3",
"icon": "icon-[lucide--languages]"
&#125;
<span>&#125;</span>
]'
trustItems='[
&#123;
<span>&#123;</span>
"id": "split-hero-2-1",
"key": "split-hero-2-1",
"value": "primary",
@@ -1132,31 +1132,31 @@ page SplitHeroDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "split-hero-2-2",
"key": "split-hero-2-2",
"value": "secondary",
@@ -1185,30 +1185,30 @@ page SplitHeroDetail {
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
&#125;
<span>&#125;</span>
]'&gt;
&lt;div data-slot="actions"&gt;
&lt;div class="showcase-slot"&gt;actions slot&lt;/div&gt;
@@ -38,7 +38,7 @@ page StatsBarDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;StatsBar
items='[
&#123;
<span>&#123;</span>
"id": "stats-bar-0-1",
"key": "stats-bar-0-1",
"value": "primary",
@@ -67,31 +67,31 @@ page StatsBarDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"id": "stats-bar-0-2",
"key": "stats-bar-0-2",
"value": "secondary",
@@ -120,30 +120,30 @@ page StatsBarDetail {
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
&#125;
<span>&#125;</span>
],
"links": [
&#123;
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
&#125;
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
&#125;
<span>&#125;</span>
]'
compact="false"
/&gt;</code></pre>
@@ -204,34 +204,34 @@ page StatsBarDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;StatsBar
items='[
&#123;
<span>&#123;</span>
"label": "Composite components",
"value": "18",
"description": "Reusable public-page building blocks",
"icon": "icon-[lucide--blocks]",
"color": "primary"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Rendering",
"value": "SSR",
"description": "Server-first WRNexusJS rendering",
"icon": "icon-[lucide--server]",
"color": "info"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Responsive",
"value": "Yes",
"description": "Mobile, tablet, and desktop layouts",
"icon": "icon-[lucide--monitor-smartphone]",
"color": "success"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Theme aware",
"value": "Yes",
"description": "Active theme and accent palettes",
"icon": "icon-[lucide--palette]",
"color": "warning"
&#125;
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
@@ -252,27 +252,27 @@ page StatsBarDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;StatsBar
items='[
&#123;
<span>&#123;</span>
"label": "Composite components",
"value": "18",
"description": "Reusable public-page building blocks",
"icon": "icon-[lucide--blocks]",
"color": "primary"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Rendering",
"value": "SSR",
"description": "Server-first WRNexusJS rendering",
"icon": "icon-[lucide--server]",
"color": "info"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Responsive",
"value": "Yes",
"description": "Mobile, tablet, and desktop layouts",
"icon": "icon-[lucide--monitor-smartphone]",
"color": "success"
&#125;
<span>&#125;</span>
]'
columns="3"
icons="false"
@@ -298,34 +298,34 @@ page StatsBarDetail {
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;StatsBar
items='[
&#123;
<span>&#123;</span>
"label": "Composite components",
"value": "18",
"description": "Reusable public-page building blocks",
"icon": "icon-[lucide--blocks]",
"color": "primary"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Rendering",
"value": "SSR",
"description": "Server-first WRNexusJS rendering",
"icon": "icon-[lucide--server]",
"color": "info"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Responsive",
"value": "Yes",
"description": "Mobile, tablet, and desktop layouts",
"icon": "icon-[lucide--monitor-smartphone]",
"color": "success"
&#125;,
&#123;
<span>&#125;</span>,
<span>&#123;</span>
"label": "Theme aware",
"value": "Yes",
"description": "Active theme and accent palettes",
"icon": "icon-[lucide--palette]",
"color": "warning"
&#125;
<span>&#125;</span>
]'
compact="false"
dividers="false"

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