Compare commits

..
Author SHA1 Message Date
Clintchiz bca9549f3a finish CSS delivery and generated type remediation
Quality / quality (ubuntu-latest) (push) Failing after 12m54s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-10 00:17:25 +05:30
Clintchiz 232d8e6734 complete performance and reliability follow-ups 2026-08-09 23:04:36 +05:30
Clintchiz 8f19a5eb2b complete UI CSS ownership remediation 2026-08-09 21:42:30 +05:30
Clintchiz 937ccb9e8e stabilize cache ignore integration test 2026-08-09 21:11:01 +05:30
Clintchiz 6859b5c4ff localize legacy UI component styles 2026-08-09 21:07:10 +05:30
Clintchiz 3f5ec8696f ignore dev caches in example tool configs 2026-08-09 20:22:55 +05:30
Clintchiz 9fd80b7cef ignore per-process caches across repository tools 2026-08-09 19:48:54 +05:30
Clintchiz 7d87200e31 fix layout SSR translations and remediation follow-ups 2026-08-09 18:49:16 +05:30
Clintchiz 51286d0fa3 complete framework remediation validation 2026-08-09 14:39:22 +05:30
Clintchiz 905cbce0c1 remove hazardous legacy scripts and gate repository writes 2026-08-09 14:24:35 +05:30
Clintchiz 09ebd44b6c fix compiler authoring traps and showcase generation order 2026-08-09 14:22:12 +05:30
Clintchiz 2e5c0cc953 feat(ui): build remaining component scaffolds 2026-08-09 14:18:43 +05:30
Clintchiz 1660044ed2 perf(csr): load component controllers on demand 2026-08-09 14:10:15 +05:30
Clintchiz 324928cfca refactor(ui): finish style-only component migration 2026-08-09 14:01:01 +05:30
Clintchiz 5c2c6a0a8f refactor(ui): replace Tailwind utilities with local styles 2026-08-09 13:58:13 +05:30
Clintchiz ff62918dbb refactor(ui): remove superseded component scaffolds 2026-08-09 13:49:55 +05:30
Clintchiz 54a93c1d14 refactor(ui): localize component styles 2026-08-09 13:47:01 +05:30
Clintchiz b6bd5cfcb7 refactor(ui): localize progress and loading styles 2026-08-09 13:42:09 +05:30
Clintchiz fdf6282aed perf(compiler): share client function state closures 2026-08-09 13:38:55 +05:30
Clintchiz ca8aabf640 fix(compiler): avoid deferred helper name collisions 2026-08-09 13:34:17 +05:30
Clintchiz a90d3683d3 fix(dev-server): normalize hot reload module paths 2026-08-09 13:32:00 +05:30
Clintchiz d85be6c456 test(perf): tighten reactive runtime size budget 2026-08-09 13:25:16 +05:30
Clintchiz 8ef6233ef3 fix(compiler): reject malformed structured props 2026-08-09 13:24:18 +05:30
Clintchiz 91e8b2ab05 test(dev-server): guard server-rendered translations 2026-08-09 13:22:18 +05:30
Clintchiz b2e83bc941 feat(compiler): commit deferred client state writes 2026-08-09 13:21:34 +05:30
Clintchiz 3e77a621f5 test(dev-server): guard nested component slot rendering 2026-08-09 13:18:32 +05:30
Clintchiz a1f671ed5d feat(framework): make component props reactive 2026-08-09 13:17:36 +05:30
Clintchiz 7c584c1d2e feat(csr): diagnose missing rendered theme tokens 2026-08-09 13:07:54 +05:30
Clintchiz 709a38feb4 feat(csr): diagnose missing output binding functions 2026-08-09 13:06:56 +05:30
Clintchiz 35cd28aad4 revert(compiler): preserve inline structured component props 2026-08-09 13:00:18 +05:30
Clintchiz de8d792e37 fix(compiler): diagnose inline object component props 2026-08-09 12:58:02 +05:30
Clintchiz 64bb0e366a perf(compiler): deduplicate client peer state synchronization 2026-08-09 12:57:37 +05:30
Clintchiz e4502f2437 fix(dev-server): isolate generated cache per process 2026-08-09 12:56:11 +05:30
Clintchiz 504d065003 feat(csr): add stripped dev output diagnostics 2026-08-09 11:25:48 +05:30
Clintchiz f09341fcfa test(scripts): make UI generator newline check deterministic 2026-08-09 11:24:20 +05:30
Clintchiz 535ad5af6d chore(scripts): remove destructive UI catalog generator 2026-08-09 11:23:59 +05:30
ClintchizandClaude Opus 5 247f360ae7 docs: rank the destructive generator as item 0
Quality / quality (ubuntu-latest) (push) Failing after 19m36s
Quality / quality (windows-latest) (push) Canceled after 0s
It was written up in 4.7 but never made the work order, which is exactly how it
stayed dangerous in the first place.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 11:18:45 +05:30
ClintchizandClaude Opus 5 790b81330a fix: repair main after an unreviewed commit, and record the cause
Quality / quality (ubuntu-latest) (push) Failing after 11m9s
Quality / quality (windows-latest) (push) Canceled after 0s
Three separate problems, all traceable to `git add -A` sweeping up a working
tree I had not inspected.

Commit 69020b25 ("docs: make the component sections executable") committed far
more than docs: 79 files of a half-scaffolded inter-app example, and four of
those files were truncated mid-statement. That broke `bun run typecheck` on
main. The example is reverted to its last green six-file form. The truncated
fragments and the fuller working copy are NOT in this commit -- if any of that
workspace was wanted, it needs to be reconstructed deliberately and committed on
its own, not as a side effect of a docs change.

Separately, `scripts/generate-ui-complete-catalog.mjs` was run while checking
which helper scripts still work. It rewrites components in place, so it
flattened six of them to stubs, deleted 24 more and lower-cased four filenames
before crashing. Contents were restored from HEAD, but the renames survived
that restore: Windows is case-insensitive, so `git status` reported clean while
Card, Container, Divider and Grid sat on disk under the wrong names. The index
now tracks the capitalised names, which is what the components declare and what
ui-redesign-contract.test.ts reads -- that test would have failed on any
case-sensitive checkout.

Documented both as 4.7 and 4.8 in the remediation plan, with the general rule:
no script that rewrites packages/ui/components/ may write in place. Also fixes
the heading level on 4.6, which was rendering outside section 4.

bun run check is green: 1,433 pass, 0 fail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 11:11:57 +05:30
ClintchizandClaude Opus 5 69020b2555 docs: make the component sections executable in one pass
Quality / quality (ubuntu-latest) (push) Failing after 10m7s
Quality / quality (windows-latest) (push) Canceled after 0s
Expands 3.1 and 3.2 so the work can be done without re-deriving anything.

3.1 now records what 0.8.6 already fixed, separated into the ten components
that were miswired and the five that gained outputs they had been firing
undeclared, with the caveat that Map's three were converted but never confirmed
in a browser. For the 22 that remain it adds the finding that changes the
decision: all nine are pure scaffolds with no state, functions or handlers, and
five of them duplicate a component that already works -- FileUpload against
FileInput and FileUploadProgress, Toast and ToastNotifications against Toaster,
AdvancedDatePicker against DatePicker, AdvancedRangeSlider against RangeSlider.
Superseding those is a migration entry rather than new code, and leaves Chart,
TreeView, Confetti and CopyMarkup as the only ones needing to be built.

3.2 corrects the scaffold count from 23 to 28; the earlier figure used a looser
rule. Nine of the 28 are the 3.1 components, so the two items must be planned
together, and several of the rest are primitives that need only their styles
moved out of ui.css rather than any behaviour.

Also corrects the dead-output component count from 11 to 9 in both documents.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 10:34:22 +05:30
ClintchizandClaude Opus 5 9ed896d2b9 docs: correct the dead-output component count from 11 to 9
Quality / quality (ubuntu-latest) (push) Failing after 12m8s
Quality / quality (windows-latest) (push) Canceled after 0s
Counted from source: the 22 remaining outputs sit in 9 components, not 11.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 10:14:28 +05:30
ClintchizandClaude Opus 5 8389d9674e docs: rank the two size items in the work order
Quality / quality (ubuntu-latest) (push) Failing after 13m48s
Quality / quality (windows-latest) (push) Canceled after 0s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 10:03:19 +05:30
ClintchizandClaude Opus 5 5112cc1a62 docs: measure the runtime and the generated client modules
Quality / quality (ubuntu-latest) (push) Failing after 13m21s
Quality / quality (windows-latest) (push) Canceled after 0s
Adds a per-subsystem measurement of reactive.js, made by minifying it
repeatedly with one subsystem removed rather than counting source bytes.

This corrects the earlier audit on both figures and on the conclusion drawn
from them. Component controllers are 23,722 bytes minified / 6,660 gzipped --
30.6% of transfer, not the "about 18%" previously claimed -- and splitting them
out saves 6.6 kB gzipped on a typical page, not "3-4 kB". Measured against the
example app, / and /login use none of the ten controllers and /layout uses one,
so most pages download and parse the lot for nothing.

The larger finding is that the runtime is not where the weight is. One page
parses 490,212 decoded bytes across 11 generated client modules while
transferring 21,026, and the largest module is 89.8% duplicated lines: the
state-restore prologue appears 162 times because client-codegen.ts inlines the
sync into every peer alias of every client function. Gzip hides it on the wire,
but parse cost follows decoded bytes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 10:02:36 +05:30
ClintchizandClaude Opus 5 30d1632252 docs: a remediation plan for the framework
Quality / quality (ubuntu-latest) (push) Failing after 12m14s
Quality / quality (windows-latest) (push) Canceled after 0s
Collects what this session exposed into one actionable document: the five bugs
fixed in 0.8.6 and the guard protecting each, the places the model is
incomplete, the smaller defects, and the delivery and dev-loop problems.

Every item states the issue, the evidence, the change and a test that fails
before it. Where a cause is not proven -- the dev server not picking up
packages/ui edits -- the item says so and makes diagnosis step one rather than
asserting a fix.

Two standing conventions are written down at the top because the rest is
written against them: a component owns its markup, behaviour and styles in its
own .wrn file, and ui.css carries global styles only; and a test that still
passes with the fix removed is measuring nothing, which is how the 0.8.5 focus
trap shipped with no coverage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 09:50:45 +05:30
ClintchizandClaude Opus 5 7a2b58652a chore(release): prepare 0.8.6
Quality / quality (ubuntu-latest) (push) Failing after 11m2s
Quality / quality (windows-latest) (push) Canceled after 0s
Bumps all 47 packages, the root manifest and the VS Code extension to 0.8.6,
and rebuilds the editor compiler, language server and extension bundles that
embed the version.

The release carries the output delivery fix: camelCase outputs now reach
parent bindings, and 18 components emit through output.* instead of
hand-built CustomEvents. See the 0.8.6 migration entry for what changes for
consumers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 01:54:44 +05:30
ClintchizandClaude Opus 5 5e65627305 fix(csr,ui): deliver component outputs to parent bindings
Quality / quality (ubuntu-latest) (push) Failing after 13m39s
Quality / quality (windows-latest) (push) Canceled after 0s
An output only reaches a parent @binding when the component calls
output.<name>(). Two separate faults meant most of the library never got
there, and both failed silently at each end.

HTML lowercases attribute names, so a parent's @sizeChange registered under
"sizechange" while the component emitted "sizeChange". The lookup missed, fell
through to a DOM dispatch, and the binding was never invoked. That made all 17
camelCase outputs undeliverable -- DataTable.pageChange and .rowClick,
Map.markerClick, ChatBubble.messageClick, LayoutSplitter.sizeChange and the
rest. invokeComponentOutput now falls back to a case-insensitive lookup, and a
csr test fails without it.

Separately, 18 components dispatched hand-built CustomEvents rather than
calling output.*. A bubbling event on the component's own root never reaches a
binding, because parent handlers live in a registry only the output proxy
reads. Card, Footer, Breadcrumb, Accordion, alert, Badge, AnnouncementBar,
AvatarGroup, ToggleCount and InputNumber now emit properly; Marquee, Map,
Timeline, List and SearchBox additionally declare the outputs they were
already firing. Dispatches on window are left alone -- that is how Toaster,
Modal and DataTable signal across component boundaries.

Verified in a browser both ways before and after: an AnnouncementBar
dispatching its own bubbling "dismiss" never reached a page-level @dismiss,
and reached it immediately once it called output.dismiss().

This corrects the audit, which called the LayoutSplitter failure "narrow and
unexplained" and read 32 dead outputs as 16 components needing a rebuild.
"Outputs work elsewhere" was an assumption; the components that worked
happened to use lowercase names and output.*. The dead-output ratchet drops
from 32 to 22, and a new test forbids the raw-CustomEvent pattern outright.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 01:40:39 +05:30
ClintchizandClaude Opus 5 84dc4e06a2 docs: audit the ui library and record the 0.8.6 migration
Quality / quality (ubuntu-latest) (push) Failing after 12m40s
Quality / quality (windows-latest) (push) Canceled after 0s
A measured pass rather than a bulk rewrite. Numbers come from the source and
from a browser.

The migration entry covers what has accumulated since 0.8.5 and would
otherwise reach upgraders unannounced: the Tabs output contract, the Sidebar
BEM rename, the layout components leaving Tailwind so their rendered class
lists changed, LayoutSplitter and CustomScrollbar changing props and outputs,
the ui.css families that were removed, and the theme tokens that now paint
where they previously resolved to nothing.

The audit records what is still wrong, with counts: 32 outputs across 16
components that nothing emits, 23 components still on the scaffold pattern, 66
without a local style block and therefore dependent on ui.css, and 10 still
using Tailwind. A test pins the dead-output count at 32 as a ceiling that only
moves down, so rebuilding a component tightens it and no new one can be added
quietly.

It also records what is not worth doing. Splitting the runtime saves 3 to 4 kB
gzipped on a first visit to a file cached for a year, and hydration costs
1.5 ms for 21 scopes across 4325 elements, so neither is a real problem.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 17:16:57 +05:30
ClintchizandClaude Opus 5 c7ab605e85 feat(ui): migrate the page and section components, add the layout tour
Quality / quality (ubuntu-latest) (push) Failing after 12m21s
Quality / quality (windows-latest) (push) Canceled after 0s
Section, SectionHeader and PublicPageShell move onto wire-* classes with
local style blocks, variants as data attributes. SectionHeader loses 38
utility lines, and Section stops spending one line per variant and colour
pairing: tinted and solid now select on two attributes. PageHeader was already
on the convention and only needed the audit.

examples/basic-app/app/pages/layout.wrn composes the whole set into one page.

Building it surfaced a library-wide bug. Ten custom properties were referenced
by components and defined by nothing: --wire-color-focus, --wire-color-surface-soft,
--wire-color-on-danger, --wire-color-surface-subtle and the input-* family,
plus hover and contrast for every semantic colour except primary and
secondary. An undefined custom property does not warn, it resolves to nothing,
so focus rings drew with no colour and every soft surface rendered
transparent -- 27 components referenced surface-soft alone. They are derived
in the theme now, and a test checks every token a component references against
the rendered theme CSS rather than the source, since most are generated.

The semantic spread also had to move ahead of the primary and secondary
entries so the palette keeps winning for those two.

Known and unresolved: LayoutSplitter emits its sizeChange output and the
component does fire it, but a parent binding on the tag is not invoked. The
tour page therefore points at the handle aria-valuenow rather than wiring a
handler that would never update. Outputs work elsewhere, so this is narrower
than an outputs-are-broken problem and needs its own investigation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 15:50:33 +05:30
ClintchizandClaude Opus 5 8aa28205e0 refactor(ui): migrate the layout components off Tailwind utilities
Quality / quality (ubuntu-latest) (push) Failing after 12m47s
Quality / quality (windows-latest) (push) Canceled after 0s
Container, Columns, Grid, Divider, Image, Link, Typography and Kbd were built
from utility classes and class: conditionals. That works only where Tailwind
is present, and every variant cost a dozen conditional lines -- Divider spent
eleven of them saying which token to paint the rule.

They now carry wire-* classes with a local style block, and variants are data
attributes the style block selects on. Divider went from eleven conditionals
to five rules, and Typography lost thirteen.

Behaviour is preserved rather than improved on. Container keeps columns and
gap even though a container is not really a grid, because applications depend
on them, and its columns default stays 2: the redesign contract test caught
that changing it would silently reflow every Container already published.

Additive only: Grid gains minItemWidth for an auto-fit track, Divider gains
dashed and dotted variants, Image gains fit, and Link gains underline.

Verified in a browser rather than by eye, since the pane cannot screenshot:
track counts match the declared columns at desktop and collapse correctly
below each breakpoint.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 11:33:59 +05:30
ClintchizandClaude Opus 5 b56cb8cba5 feat(ui): build LayoutSplitter and CustomScrollbar for real
Quality / quality (ubuntu-latest) (push) Failing after 6m8s
Quality / quality (windows-latest) (push) Canceled after 0s
Both advertised behaviour they did not have. LayoutSplitter declared
resizeStart, resize and resizeEnd with no pointer handling whatsoever, so a
caller wired up @resize and received nothing, for ever, with no error, and its
props were columns, gap and maxWidth copied from a grid scaffold.
CustomScrollbar was the same shape with a scroll output.

The splitter now resizes. Dragging lives in the reactive runtime behind
data-wrn-splitter, because a pointermove fires far too often to route through
a client function and a state write made in that callback is dropped; the
resolved size is held on the container as a --wrn-split custom property and
the component grids from it. The handle is a real separator: arrow keys step
it, Home and End go to the bounds rather than to nothing, and it carries
aria-valuenow, aria-valuemin and aria-valuemax. minSize fixes both bounds so
neither pane can be dragged away and left unrecoverable.

CustomScrollbar is CSS rather than script -- scrollbar-width and
scrollbar-color with webkit rules for the engines that still need them -- and
its fake scroll output is removed rather than left unimplemented, since a
caller can listen for a plain scroll event.

The test harness needed a fix too: mount did not bind the window CustomEvent,
so the runtime built events from the host global and happy-dom listeners never
matched them, which made anything dispatched look silently lost.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 11:22:45 +05:30
ClintchizandClaude Opus 5 b3a4d80df3 docs: layout and page-structure component group design
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 11:09:50 +05:30
ClintchizandClaude Opus 5 7bd4574b08 fix(example): theme the basic-app palette and surface the navigation tour
Quality / quality (ubuntu-latest) (push) Failing after 10m43s
Quality / quality (windows-latest) (push) Canceled after 0s
global.css defined its own fixed palette -- bg 0b1020, text e7ecff -- while
Wire UI surfaces follow the theme tokens. Switching to light turned the cards
light and left this text light with them, so the sign-in form rendered at a
contrast of about 1.1 and could not be read. The palette now derives from the
wire tokens, and the body wash is tinted from the primary token rather than a
fixed blue. Measured on the login form: light goes from 1.1 to 17.7, dark
stays at 18.2.

Anything an application hardcodes has to be themed as well, or it only ever
looks right in one mode.

The navigation tour is also reachable now: a Navigation entry in the site nav,
translated in both locales, and the page adopts the public layout so there is
a way back out of it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 10:19:12 +05:30
ClintchizandClaude Opus 5 e32d83933e docs(example): one page wiring the whole navigation group together
Quality / quality (ubuntu-latest) (push) Failing after 13m3s
Quality / quality (windows-latest) (push) Canceled after 0s
examples/basic-app/app/pages/navigation.wrn puts all nine navigation
components in a single console shell instead of showing each alone: Navbar
with a nested dropdown, MegaMenu beside it, Breadcrumb, Sidebar as a rail that
becomes a Drawer, Tabs backed by a query parameter, a Stepper wizard,
Pagination, Scrollspy following the article, and Nav in the footer.

Three framework limits shaped the layout and are written into the page rather
than hidden:

  - object props are held in state and bound, because a brace at the start of
    an attribute is read as an interpolation
  - a shared function on a page is compiled standalone and cannot see page
    state by name, so state is passed as arguments
  - component props and slot content render once and do not track page state,
    so anything that has to react lives in page scope; Tabs reports the
    selection and the page owns what is shown

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 09:57:53 +05:30
ClintchizandClaude Opus 5 5ce2771718 fix(ui): release the scroll lock on closed drawers, add stepper wizard controls, slide tabs
Quality / quality (ubuntu-latest) (push) Failing after 12m29s
Quality / quality (windows-latest) (push) Canceled after 0s
The scroll lock was mine, and it broke every page carrying a Drawer or Modal.
Making dialog visibility testable, I replaced a size check with a data-show
check -- but a Drawer animates open, so its panel cannot be hidden with
data-show at all: display:none is not transitionable. Every closed Drawer
therefore looked open, took the body scroll lock and never released it, and
the page could not be scrolled. Both components publish data-open, which is
the signal that actually means open, and that is what is read now.

Stepper gains the wizard surface: showPanel renders each step body and shows
only the active one, the same contract Tabs uses, and controls adds Back,
Skip and Next, which becomes Finish on the last step. nextDisabled lets a form
hold the step; the component never validates anything itself, since the page
owns the form.

Stepper also gets a single root. The panels and controls were siblings of the
list, so the component had several roots and anything scoped to
data-ui-component missed most of it.

Tabs panels now slide in the direction of travel rather than fading upward.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 23:41:18 +05:30
ClintchizandClaude Opus 5 68c7b96a9f fix(showcase): render object props, bridge the mega menu gap, make scrollspy testable
Quality / quality (ubuntu-latest) (push) Failing after 12m38s
Quality / quality (windows-latest) (push) Canceled after 0s
Object props never worked in generated demos, and my two earlier attempts each
traded one failure for another:

  - a bare {...} attribute is read by the compiler as an interpolation, so it
    parsed JSON as JavaScript and the page 500ed
  - parenthesising it compiled, but prop coercion runs JSON.parse on the raw
    attribute, so ({...}) threw and every demo rendered empty and silent
  - entity-escaping the braces did not help either: the compiler hands the
    attribute over without decoding, so JSON.parse still failed

They are now hoisted into page state and bound, which is what the playground
has always done. The state initialiser uses JSON.parse rather than an object
literal because the parser reads a leading brace as the start of a block.

Navbar gains a real profile: a brand, links, a two-column dropdown panel and
calls to action, instead of the generic scaffold samples that made every demo
look identical and showed no dropdown at all.

MegaMenu closed while the pointer travelled to it. The panel sits below the
trigger and that offset belongs to neither element, so crossing it fired
mouseleave on the root. A descendant now covers the gap.

Scrollspy could not be exercised at all: its links pointed at ids that did not
exist on the page. The demo now ships real sections, in page flow because the
runtime observes against the viewport.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 23:12:37 +05:30
ClintchizandClaude Opus 5 6a9c48a207 perf(ui): remove dead runtime controllers and unused stylesheet families
Quality / quality (ubuntu-latest) (push) Failing after 6m17s
Quality / quality (windows-latest) (push) Canceled after 0s
Two orphaned controllers in the reactive runtime targeted markup nothing
emits any more: hydrateSidebarControllers looked for .wire-sidebar-shell and
friends, which the Sidebar rewrite replaced with BEM classes earlier today,
and hydrateDropdownControllers looked for [data-wrn-dropdown], which no
component or compiler output has ever produced.

ui.css loses the matching legacy sidebar rules, the wire-mega-menu family
left behind when the MegaMenu scaffold was replaced, and a set of
self-contained application-pattern families that nothing references.

Utility layers are deliberately kept even where an individual member is not
name-checked anywhere. wire-bg-primary is documented and tested while
wire-bg-secondary is not, but they are one public family and splitting them
would be incoherent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 21:46:42 +05:30
ClintchizandClaude Opus 5 9a03b7c8d4 merge: runtime observer consolidation and a shipped-size budget
Quality / quality (ubuntu-latest) (push) Failing after 11m0s
Quality / quality (windows-latest) (push) Canceled after 0s
One document observer with subscribers instead of four, runtime budgets
measured on minified output rather than raw source, Navbar styles moved into
the component, and two bugs fixed: object props broke showcase pages with a
500, and the runtime evaluated JSON sitting in a textarea.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 21:20:34 +05:30
439 changed files with 24674 additions and 20307 deletions
+20 -2
View File
@@ -1,11 +1,29 @@
{ {
"version": "0.0.1", "version": "0.0.1",
"configurations": [ "configurations": [
{
"name": "basic-app",
"runtimeExecutable": "bun",
"runtimeArgs": [
"run",
"packages/cli/src/index.ts",
"dev",
"examples/basic-app",
"--port=3520"
],
"port": 3520
},
{ {
"name": "component-showcase", "name": "component-showcase",
"runtimeExecutable": "bun", "runtimeExecutable": "bun",
"runtimeArgs": ["run", "--cwd", "examples/component-showcase", "dev"], "runtimeArgs": [
"port": 3000 "run",
"packages/cli/src/index.ts",
"dev",
"examples/component-showcase",
"--port=3400"
],
"port": 3400
} }
] ]
} }
+1
View File
@@ -1,6 +1,7 @@
node_modules/ node_modules/
dist/ dist/
.wrnexus/ .wrnexus/
.wrnexus-*/
*.log *.log
*.db *.db
*.db-shm *.db-shm
+2
View File
@@ -1,6 +1,8 @@
node_modules/ node_modules/
dist/ dist/
**/dist/ **/dist/
**/.wrnexus/**
**/.wrnexus-*/**
.publish/ .publish/
**/.wirefw/ **/.wirefw/
coverage/ coverage/
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ai", "name": "@wrnexus/ai",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.", "description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
"license": "MIT", "license": "MIT",
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/authz", "name": "@wrnexus/authz",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "@wrnexus/authz — part of the WrNexus framework.", "description": "@wrnexus/authz — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -41,8 +41,8 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.5", "@wrnexus/core": "^0.8.6",
"@wrnexus/db": "^0.8.5" "@wrnexus/db": "^0.8.6"
}, },
"files": [ "files": [
"dist", "dist",
+18 -18
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/cli", "name": "@wrnexus/cli",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "@wrnexus/cli — part of the WrNexus framework.", "description": "@wrnexus/cli — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -44,23 +44,23 @@
"wrnexus": "./dist/index.js" "wrnexus": "./dist/index.js"
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.5", "@wrnexus/core": "^0.8.6",
"@wrnexus/router": "^0.8.5", "@wrnexus/router": "^0.8.6",
"@wrnexus/csr": "^0.8.5", "@wrnexus/csr": "^0.8.6",
"@wrnexus/compiler": "^0.8.5", "@wrnexus/compiler": "^0.8.6",
"@wrnexus/styles": "^0.8.5", "@wrnexus/styles": "^0.8.6",
"@wrnexus/dev-server": "^0.8.5", "@wrnexus/dev-server": "^0.8.6",
"@wrnexus/ui": "^0.8.5", "@wrnexus/ui": "^0.8.6",
"@wrnexus/validation": "^0.8.5", "@wrnexus/validation": "^0.8.6",
"@wrnexus/i18n": "^0.8.5", "@wrnexus/i18n": "^0.8.6",
"@wrnexus/mcp": "^0.8.5", "@wrnexus/mcp": "^0.8.6",
"@wrnexus/playground": "^0.8.5", "@wrnexus/playground": "^0.8.6",
"@wrnexus/db": "^0.8.5", "@wrnexus/db": "^0.8.6",
"@wrnexus/authz": "^0.8.5", "@wrnexus/authz": "^0.8.6",
"@wrnexus/plugin": "^0.8.5", "@wrnexus/plugin": "^0.8.6",
"@wrnexus/syntax": "^0.8.5", "@wrnexus/syntax": "^0.8.6",
"@wrnexus/typecheck": "^0.8.5", "@wrnexus/typecheck": "^0.8.6",
"@wrnexus/security": "^0.8.5", "@wrnexus/security": "^0.8.6",
"selfsigned": "^5.5.0" "selfsigned": "^5.5.0"
}, },
"files": [ "files": [
+5 -5
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/compiler", "name": "@wrnexus/compiler",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "@wrnexus/compiler — part of the WrNexus framework.", "description": "@wrnexus/compiler — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -37,10 +37,10 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/csr": "^0.8.5", "@wrnexus/csr": "^0.8.6",
"@wrnexus/syntax": "^0.8.5", "@wrnexus/syntax": "^0.8.6",
"@wrnexus/store": "^0.8.5", "@wrnexus/store": "^0.8.6",
"@wrnexus/validation": "^0.8.5" "@wrnexus/validation": "^0.8.6"
}, },
"files": [ "files": [
"dist", "dist",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/core", "name": "@wrnexus/core",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "@wrnexus/core — part of the WrNexus framework.", "description": "@wrnexus/core — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/csr", "name": "@wrnexus/csr",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "@wrnexus/csr — part of the WrNexus framework.", "description": "@wrnexus/csr — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -37,7 +37,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.5" "@wrnexus/core": "^0.8.6"
}, },
"files": [ "files": [
"dist", "dist",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/db", "name": "@wrnexus/db",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "Typed database drivers, migrations, instrumentation, repositories, pagination, and transaction helpers.", "description": "Typed database drivers, migrations, instrumentation, repositories, pagination, and transaction helpers.",
"license": "MIT", "license": "MIT",
+22 -22
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/dev-server", "name": "@wrnexus/dev-server",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "@wrnexus/dev-server — part of the WrNexus framework.", "description": "@wrnexus/dev-server — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -41,27 +41,27 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/authz": "^0.8.5", "@wrnexus/authz": "^0.8.6",
"@wrnexus/rpc": "^0.8.5", "@wrnexus/rpc": "^0.8.6",
"@wrnexus/core": "^0.8.5", "@wrnexus/core": "^0.8.6",
"@wrnexus/dev-toolbar": "^0.8.5", "@wrnexus/dev-toolbar": "^0.8.6",
"@wrnexus/router": "^0.8.5", "@wrnexus/router": "^0.8.6",
"@wrnexus/ssr": "^0.8.5", "@wrnexus/ssr": "^0.8.6",
"@wrnexus/csr": "^0.8.5", "@wrnexus/csr": "^0.8.6",
"@wrnexus/compiler": "^0.8.5", "@wrnexus/compiler": "^0.8.6",
"@wrnexus/styles": "^0.8.5", "@wrnexus/styles": "^0.8.6",
"@wrnexus/ui": "^0.8.5", "@wrnexus/ui": "^0.8.6",
"@wrnexus/validation": "^0.8.5", "@wrnexus/validation": "^0.8.6",
"@wrnexus/i18n": "^0.8.5", "@wrnexus/i18n": "^0.8.6",
"@wrnexus/db": "^0.8.5", "@wrnexus/db": "^0.8.6",
"@wrnexus/pubsub": "^0.8.5", "@wrnexus/pubsub": "^0.8.6",
"@wrnexus/uploader": "^0.8.5", "@wrnexus/uploader": "^0.8.6",
"@wrnexus/plugin": "^0.8.5", "@wrnexus/plugin": "^0.8.6",
"@wrnexus/store": "^0.8.5", "@wrnexus/store": "^0.8.6",
"@wrnexus/security": "^0.8.5", "@wrnexus/security": "^0.8.6",
"@wrnexus/observability": "^0.8.5", "@wrnexus/observability": "^0.8.6",
"@wrnexus/cache": "^0.8.5", "@wrnexus/cache": "^0.8.6",
"@wrnexus/pwa": "^0.8.5" "@wrnexus/pwa": "^0.8.6"
}, },
"files": [ "files": [
"dist", "dist",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/dev-toolbar", "name": "@wrnexus/dev-toolbar",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "@wrnexus/dev-toolbar — part of the WrNexus framework.", "description": "@wrnexus/dev-toolbar — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/encryption", "name": "@wrnexus/encryption",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "Authenticated encryption, key rotation, hashing, and optional application-layer encrypted HTTP envelopes.", "description": "Authenticated encryption, key rotation, hashing, and optional application-layer encrypted HTTP envelopes.",
"license": "MIT", "license": "MIT",
@@ -37,7 +37,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.5" "@wrnexus/core": "^0.8.6"
}, },
"files": [ "files": [
"dist", "dist",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/helpers", "name": "@wrnexus/helpers",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.", "description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
"license": "MIT", "license": "MIT",
@@ -37,7 +37,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.5" "@wrnexus/core": "^0.8.6"
}, },
"files": [ "files": [
"dist", "dist",
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/i18n", "name": "@wrnexus/i18n",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "Locale loading, fallback resolution, SSR/browser translations, formatters, and WRNexusJS language components.", "description": "Locale loading, fallback resolution, SSR/browser translations, formatters, and WRNexusJS language components.",
"license": "MIT", "license": "MIT",
@@ -42,9 +42,9 @@
"./components/*": "./components/*" "./components/*": "./components/*"
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.5", "@wrnexus/core": "^0.8.6",
"@wrnexus/plugin": "^0.8.5", "@wrnexus/plugin": "^0.8.6",
"@wrnexus/ui": "^0.8.5" "@wrnexus/ui": "^0.8.6"
}, },
"wrnexus": { "wrnexus": {
"plugin": { "plugin": {
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/jwt", "name": "@wrnexus/jwt",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "HS256 JSON Web Tokens, key rotation, access/refresh helpers, scopes, cookies, and auth middleware.", "description": "HS256 JSON Web Tokens, key rotation, access/refresh helpers, scopes, cookies, and auth middleware.",
"license": "MIT", "license": "MIT",
@@ -37,7 +37,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.5" "@wrnexus/core": "^0.8.6"
}, },
"files": [ "files": [
"dist", "dist",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/mobile", "name": "@wrnexus/mobile",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "@wrnexus/mobile — part of the WrNexus framework.", "description": "@wrnexus/mobile — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -37,7 +37,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/native": "^0.8.5" "@wrnexus/native": "^0.8.6"
}, },
"files": [ "files": [
"dist", "dist",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/native", "name": "@wrnexus/native",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "@wrnexus/native — part of the WrNexus framework.", "description": "@wrnexus/native — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/oauth", "name": "@wrnexus/oauth",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "@wrnexus/oauth — part of the WrNexus framework.", "description": "@wrnexus/oauth — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -37,7 +37,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/jwt": "^0.8.5" "@wrnexus/jwt": "^0.8.6"
}, },
"files": [ "files": [
"dist", "dist",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/plugin", "name": "@wrnexus/plugin",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "@wrnexus/plugin — part of the WrNexus framework.", "description": "@wrnexus/plugin — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -49,7 +49,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/syntax": "^0.8.5" "@wrnexus/syntax": "^0.8.6"
}, },
"files": [ "files": [
"dist", "dist",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/pubsub", "name": "@wrnexus/pubsub",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "@wrnexus/pubsub — part of the WrNexus framework.", "description": "@wrnexus/pubsub — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/queue", "name": "@wrnexus/queue",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "@wrnexus/queue — part of the WrNexus framework.", "description": "@wrnexus/queue — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -37,7 +37,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.5" "@wrnexus/core": "^0.8.6"
}, },
"files": [ "files": [
"dist", "dist",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/reactive", "name": "@wrnexus/reactive",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "@wrnexus/reactive — part of the WrNexus framework.", "description": "@wrnexus/reactive — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/router", "name": "@wrnexus/router",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "@wrnexus/router — part of the WrNexus framework.", "description": "@wrnexus/router — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -37,8 +37,8 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/compiler": "^0.8.5", "@wrnexus/compiler": "^0.8.6",
"@wrnexus/core": "^0.8.5" "@wrnexus/core": "^0.8.6"
}, },
"files": [ "files": [
"dist", "dist",
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ssr", "name": "@wrnexus/ssr",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "@wrnexus/ssr — part of the WrNexus framework.", "description": "@wrnexus/ssr — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -45,9 +45,9 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.5", "@wrnexus/core": "^0.8.6",
"@wrnexus/store": "^0.8.5", "@wrnexus/store": "^0.8.6",
"@wrnexus/security": "^0.8.5" "@wrnexus/security": "^0.8.6"
}, },
"files": [ "files": [
"dist", "dist",
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/styles", "name": "@wrnexus/styles",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "@wrnexus/styles — part of the WrNexus framework.", "description": "@wrnexus/styles — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -37,9 +37,9 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/uploader": "^0.8.5", "@wrnexus/uploader": "^0.8.6",
"@wrnexus/core": "^0.8.5", "@wrnexus/core": "^0.8.6",
"@wrnexus/plugin": "^0.8.5" "@wrnexus/plugin": "^0.8.6"
}, },
"files": [ "files": [
"dist", "dist",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/syntax", "name": "@wrnexus/syntax",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "@wrnexus/syntax — part of the WrNexus framework.", "description": "@wrnexus/syntax — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/test", "name": "@wrnexus/test",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "@wrnexus/test — part of the WrNexus framework.", "description": "@wrnexus/test — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/tracking", "name": "@wrnexus/tracking",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "@wrnexus/tracking — part of the WrNexus framework.", "description": "@wrnexus/tracking — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+33 -33
View File
@@ -229,7 +229,7 @@ Present structured responsive linked or status items with icons, descriptions, a
- Mount: `data-component="List"` - Mount: `data-component="List"`
- Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "List"`, `description: string = ""`, `items: unknown[] = []`, `variant: string = "default"`, `class: string = ""` - Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "List"`, `description: string = ""`, `items: unknown[] = []`, `variant: string = "default"`, `class: string = ""`
- Slots: `default` - Slots: `default`
- Outputs: None - Outputs: `select({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`
### ListGroup ### ListGroup
@@ -247,7 +247,7 @@ Continuously present responsive labels, partners, notices, or capabilities with
- Mount: `data-component="Marquee"` - Mount: `data-component="Marquee"`
- Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "Marquee"`, `description: string = ""`, `items: unknown[] = []`, `variant: string = "default"`, `class: string = ""` - Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "Marquee"`, `description: string = ""`, `items: unknown[] = []`, `variant: string = "default"`, `class: string = ""`
- Slots: `default` - Slots: `default`
- Outputs: None - Outputs: `pause({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `resume({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`
### Progress ### Progress
@@ -301,7 +301,7 @@ Present responsive chronological activity, milestones, or workflow status with r
- Mount: `data-component="Timeline"` - Mount: `data-component="Timeline"`
- Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "Timeline"`, `description: string = ""`, `items: unknown[] = []`, `variant: string = "default"`, `class: string = ""` - Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "Timeline"`, `description: string = ""`, `items: unknown[] = []`, `variant: string = "default"`, `class: string = ""`
- Slots: `default` - Slots: `default`
- Outputs: None - Outputs: `select({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`
### Toast ### Toast
@@ -469,7 +469,7 @@ Provide an accessible responsive search field with labels, validation states, si
- Mount: `data-component="SearchBox"` - Mount: `data-component="SearchBox"`
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Search Box"`, `name: string = ""`, `value: string = ""`, `placeholder: string = ""`, `type: string = "search"`, `min: string = ""`, `max: string = ""`, `step: string = ""`, `disabled: boolean = false`, `required: boolean = false`, `class: string = ""` - Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Search Box"`, `name: string = ""`, `value: string = ""`, `placeholder: string = ""`, `type: string = "search"`, `min: string = ""`, `max: string = ""`, `step: string = ""`, `disabled: boolean = false`, `required: boolean = false`, `class: string = ""`
- Slots: None - Slots: None
- Outputs: None - Outputs: `search({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `clear({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`
### Select ### Select
@@ -588,7 +588,7 @@ Present responsive location information and markers with map-ready metadata and
- Mount: `data-component="Map"` - Mount: `data-component="Map"`
- Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "Map"`, `description: string = ""`, `items: unknown[] = []`, `variant: string = "default"`, `class: string = ""` - Props: `size: string = "default"`, `color: string = "primary"`, `title: string = "Map"`, `description: string = ""`, `items: unknown[] = []`, `variant: string = "default"`, `class: string = ""`
- Slots: `default` - Slots: `default`
- Outputs: None - Outputs: `markerClick({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `select({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `zoom({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`
### ToastNotifications ### ToastNotifications
@@ -624,7 +624,7 @@ Create responsive balanced content columns with configurable count, gap, density
Constrain and align page content with responsive gutters and compact, wide, or full width options. Constrain and align page content with responsive gutters and compact, wide, or full width options.
- Mount: `data-component="Container"` - Mount: `data-component="Container"`
- Props: `size: string = "default"`, `color: string = "primary"`, `columns: number = 2`, `gap: string = "md"`, `maxWidth: string = "xl"`, `class: string = ""` - Props: `size: string = "default"`, `color: string = "primary"`, `columns: number = 2`, `gap: string = "md"`, `maxWidth: string = "xl"`, `centered: boolean = true`, `class: string = ""`
- Slots: `default` - Slots: `default`
- Outputs: None - Outputs: None
@@ -633,16 +633,16 @@ Constrain and align page content with responsive gutters and compact, wide, or f
Theme-aware, responsive custom scrollbar component. Theme-aware, responsive custom scrollbar component.
- Mount: `data-component="CustomScrollbar"` - Mount: `data-component="CustomScrollbar"`
- Props: `size: string = "default"`, `color: string = "primary"`, `columns: number = 2`, `gap: string = "md"`, `maxWidth: string = "xl"`, `class: string = ""` - Props: `color: string = "primary"`, `size: string = "default"`, `axis: string = "vertical"`, `thickness: number = 8`, `maxHeight: string = "20rem"`, `radius: string = "999px"`, `class: string = ""`
- Slots: `default` - Slots: `default`
- Outputs: `scroll({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])` - Outputs: None
### Divider ### Divider
Separate related horizontal or vertical content with optional labels, sizes, and semantic colors. Separate related horizontal or vertical content with optional labels, sizes, and semantic colors.
- Mount: `data-component="Divider"` - Mount: `data-component="Divider"`
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = ""`, `orientation: string = "horizontal"`, `class: string = ""` - Props: `size: string = "default"`, `color: string = "primary"`, `label: string = ""`, `orientation: string = "horizontal"`, `variant: string = "solid"`, `class: string = ""`
- Slots: None - Slots: None
- Outputs: None - Outputs: None
@@ -669,7 +669,7 @@ Render structured responsive footer navigation, pre and post content, copyright
Arrange arbitrary content in a responsive configurable CSS grid with stable columns, gaps, alignment, and width. Arrange arbitrary content in a responsive configurable CSS grid with stable columns, gaps, alignment, and width.
- Mount: `data-component="Grid"` - Mount: `data-component="Grid"`
- Props: `size: string = "default"`, `color: string = "primary"`, `columns: number = 2`, `gap: string = "md"`, `maxWidth: string = "xl"`, `class: string = ""` - Props: `size: string = "default"`, `color: string = "primary"`, `columns: number = 2`, `gap: string = "md"`, `maxWidth: string = "xl"`, `minItemWidth: string = ""`, `class: string = ""`
- Slots: `default` - Slots: `default`
- Outputs: None - Outputs: None
@@ -678,7 +678,7 @@ Arrange arbitrary content in a responsive configurable CSS grid with stable colu
Render a responsive image with explicit dimensions, loading behavior, alternative text, sizing, and rounded treatment. Render a responsive image with explicit dimensions, loading behavior, alternative text, sizing, and rounded treatment.
- Mount: `data-component="Image"` - Mount: `data-component="Image"`
- Props: `size: string = "default"`, `color: string = "primary"`, `src: string = ""`, `alt: string = ""`, `width: string = ""`, `height: string = ""`, `loading: string = "lazy"`, `rounded: boolean = false`, `class: string = ""` - Props: `size: string = "default"`, `color: string = "primary"`, `src: string = ""`, `alt: string = ""`, `width: string = ""`, `height: string = ""`, `loading: string = "lazy"`, `fit: string = "cover"`, `rounded: boolean = false`, `class: string = ""`
- Slots: `default` - Slots: `default`
- Outputs: None - Outputs: None
@@ -687,7 +687,7 @@ Render a responsive image with explicit dimensions, loading behavior, alternativ
Theme-aware, responsive kbd component. Theme-aware, responsive kbd component.
- Mount: `data-component="Kbd"` - Mount: `data-component="Kbd"`
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "K"`, `class: string = ""` - Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "K"`, `class: string = ""`
- Slots: `default` - Slots: `default`
- Outputs: None - Outputs: None
@@ -696,16 +696,16 @@ Theme-aware, responsive kbd component.
Theme-aware, responsive layout splitter component. Theme-aware, responsive layout splitter component.
- Mount: `data-component="LayoutSplitter"` - Mount: `data-component="LayoutSplitter"`
- Props: `size: string = "default"`, `color: string = "primary"`, `columns: number = 2`, `gap: string = "md"`, `maxWidth: string = "xl"`, `class: string = ""` - Props: `color: string = "primary"`, `size: number = 50`, `orientation: string = "horizontal"`, `minSize: number = 15`, `step: number = 5`, `label: string = "Resize panels"`, `class: string = ""`
- Slots: `default` - Slots: `start`, `end`, `default`
- Outputs: `resizeStart({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])`, `resize({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])`, `resizeEnd({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])` - Outputs: `sizeChange({ size: number })`
### Link ### Link
Render an accessible internal or external link with target, relation, size, color, and public focus or click events. Render an accessible internal or external link with target, relation, size, color, and public focus or click events.
- Mount: `data-component="Link"` - Mount: `data-component="Link"`
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Link"`, `href: string = "#"`, `target: string = ""`, `rel: string = ""`, `external: boolean = false`, `class: string = ""` - Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Link"`, `href: string = "#"`, `target: string = ""`, `rel: string = ""`, `underline: string = "hover"`, `external: boolean = false`, `class: string = ""`
- Slots: `default` - Slots: `default`
- Outputs: None - Outputs: None
@@ -862,18 +862,18 @@ Show responsive hierarchical navigation with home support, separators, current-p
Theme-aware, responsive mega menu component. Theme-aware, responsive mega menu component.
- Mount: `data-component="MegaMenu"` - Mount: `data-component="MegaMenu"`
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Mega Menu"`, `items: unknown[] = []`, `active: string = ""`, `orientation: string = "horizontal"`, `class: string = ""` - Props: `color: string = "primary"`, `size: string = "default"`, `label: string = "Menu"`, `icon: string = ""`, `columns: unknown[] = []`, `footer: string = ""`, `defaultOpen: boolean = false`, `class: string = ""`
- Slots: `default` - Slots: `default`
- Outputs: `open({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `close({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `select({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)` - Outputs: `open({ sourceEvent: Event })`, `close({ reason: string })`, `select({ item: object; value: string })`
### Nav ### Nav
Theme-aware, responsive nav component. Theme-aware, responsive nav component.
- Mount: `data-component="Nav"` - Mount: `data-component="Nav"`
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Nav"`, `items: unknown[] = []`, `active: string = ""`, `orientation: string = "horizontal"`, `class: string = ""` - Props: `color: string = "primary"`, `size: string = "default"`, `items: unknown[] = []`, `active: string = ""`, `orientation: string = "horizontal"`, `label: string = "Main"`, `collapsible: boolean = true`, `toggleLabel: string = "Menu"`, `class: string = ""`
- Slots: `default` - Slots: `default`
- Outputs: `select({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `change({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)` - Outputs: `select({ item: object; value: string })`
### Navbar ### Navbar
@@ -889,45 +889,45 @@ Theme-aware, responsive navbar component.
Theme-aware, responsive pagination component. Theme-aware, responsive pagination component.
- Mount: `data-component="Pagination"` - Mount: `data-component="Pagination"`
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Pagination"`, `items: unknown[] = []`, `active: string = ""`, `orientation: string = "horizontal"`, `class: string = ""` - Props: `color: string = "primary"`, `size: string = "default"`, `page: number = 1`, `pageSize: number = 10`, `total: number = 0`, `variant: string = "compact"`, `siblingCount: number = 1`, `showSummary: boolean = true`, `label: string = "Pagination"`, `previousLabel: string = "Previous"`, `nextLabel: string = "Next"`, `class: string = ""`
- Slots: `default` - Slots: `default`
- Outputs: `change({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `previous({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `next({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })` - Outputs: `change({ page: number; pageSize: number })`, `previous({ page: number })`, `next({ page: number })`
### Scrollspy ### Scrollspy
Theme-aware, responsive scrollspy component. Theme-aware, responsive scrollspy component.
- Mount: `data-component="Scrollspy"` - Mount: `data-component="Scrollspy"`
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Scrollspy"`, `items: unknown[] = []`, `active: string = ""`, `orientation: string = "horizontal"`, `class: string = ""` - Props: `color: string = "primary"`, `size: string = "default"`, `items: unknown[] = []`, `active: string = ""`, `label: string = "On this page"`, `heading: string = ""`, `class: string = ""`
- Slots: `default` - Slots: `default`
- Outputs: `change({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)` - Outputs: `change({ href: string; label: string })`
### Sidebar ### Sidebar
Theme-aware, responsive sidebar component. Theme-aware, responsive sidebar component.
- Mount: `data-component="Sidebar"` - Mount: `data-component="Sidebar"`
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Sidebar"`, `items: unknown[] = []`, `active: string = ""`, `orientation: string = "horizontal"`, `mobileLabel: string = "Open navigation"`, `class: string = ""` - Props: `color: string = "primary"`, `size: string = "default"`, `label: string = "Sidebar"`, `items: unknown[] = []`, `active: string = ""`, `mobileLabel: string = "Open navigation"`, `drawerTitle: string = "Navigation"`, `class: string = ""`
- Slots: `default` - Slots: `default`, `drawer`
- Outputs: `toggle({ open: boolean })`, `open({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `close({ source: string })`, `select({ item: string | number | boolean | null | object; value: string | number | boolean; level: string })` - Outputs: `toggle({ open: boolean })`, `open({ sourceEvent: Event })`, `close({ source: string })`, `select({ item: object; value: string; level: number })`
### Stepper ### Stepper
Theme-aware, responsive stepper component. Theme-aware, responsive stepper component.
- Mount: `data-component="Stepper"` - Mount: `data-component="Stepper"`
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Stepper"`, `items: unknown[] = []`, `active: string = ""`, `orientation: string = "horizontal"`, `class: string = ""` - Props: `color: string = "primary"`, `size: string = "default"`, `steps: unknown[] = []`, `active: number = 0`, `orientation: string = "horizontal"`, `clickable: boolean = false`, `label: string = "Progress"`, `showPanel: boolean = false`, `controls: boolean = false`, `allowSkip: boolean = false`, `nextDisabled: boolean = false`, `backLabel: string = "Back"`, `nextLabel: string = "Next"`, `skipLabel: string = "Skip"`, `finishLabel: string = "Finish"`, `class: string = ""`
- Slots: `default` - Slots: `step-{index}`, `panel-{index}`, `default`
- Outputs: `change({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `previous({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `next({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `complete({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })` - Outputs: `change({ index: number; step: object })`, `back({ index: number; step: object })`, `next({ index: number; step: object })`, `skip({ index: number; step: object })`, `finish({ index: number; step: object })`
### Tabs ### Tabs
Switch between related responsive content panels with horizontal or vertical orientation and selection events. Switch between related responsive content panels with horizontal or vertical orientation and selection events.
- Mount: `data-component="Tabs"` - Mount: `data-component="Tabs"`
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Tabs"`, `items: unknown[] = []`, `active: string = ""`, `orientation: string = "horizontal"`, `class: string = ""` - Props: `color: string = "primary"`, `size: string = "default"`, `items: unknown[] = []`, `active: string = ""`, `orientation: string = "horizontal"`, `mode: string = "client"`, `param: string = "tab"`, `label: string = "Tabs"`, `class: string = ""`
- Slots: `default` - Slots: `panel-{valueOf(item, index)}`, `default`
- Outputs: None - Outputs: `change({ value: string; item: object; index: number })`, `select({ value: string; item: object; index: number })`
## Overlays ## Overlays
+389 -170
View File
@@ -4153,6 +4153,13 @@
"default": "\"xl\"", "default": "\"xl\"",
"options": [] "options": []
}, },
{
"name": "centered",
"type": "boolean",
"required": false,
"default": "true",
"options": []
},
{ {
"name": "class", "name": "class",
"type": "string", "type": "string",
@@ -4443,13 +4450,6 @@
"category": "layout", "category": "layout",
"purpose": "Theme-aware, responsive custom scrollbar component.", "purpose": "Theme-aware, responsive custom scrollbar component.",
"props": [ "props": [
{
"name": "size",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{ {
"name": "color", "name": "color",
"type": "string", "type": "string",
@@ -4458,24 +4458,38 @@
"options": [] "options": []
}, },
{ {
"name": "columns", "name": "size",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{
"name": "axis",
"type": "string",
"required": false,
"default": "\"vertical\"",
"options": []
},
{
"name": "thickness",
"type": "number", "type": "number",
"required": false, "required": false,
"default": "2", "default": "8",
"options": [] "options": []
}, },
{ {
"name": "gap", "name": "maxHeight",
"type": "string", "type": "string",
"required": false, "required": false,
"default": "\"md\"", "default": "\"20rem\"",
"options": [] "options": []
}, },
{ {
"name": "maxWidth", "name": "radius",
"type": "string", "type": "string",
"required": false, "required": false,
"default": "\"xl\"", "default": "\"999px\"",
"options": [] "options": []
}, },
{ {
@@ -4487,13 +4501,8 @@
} }
], ],
"slots": ["default"], "slots": ["default"],
"outputs": [ "outputs": [],
{ "events": [],
"name": "scroll",
"payloadType": "{ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[]"
}
],
"events": ["scroll"],
"source": "components/CustomScrollbar.wrn" "source": "components/CustomScrollbar.wrn"
}, },
{ {
@@ -5192,6 +5201,13 @@
"default": "\"horizontal\"", "default": "\"horizontal\"",
"options": [] "options": []
}, },
{
"name": "variant",
"type": "string",
"required": false,
"default": "\"solid\"",
"options": []
},
{ {
"name": "class", "name": "class",
"type": "string", "type": "string",
@@ -6548,6 +6564,13 @@
"default": "\"xl\"", "default": "\"xl\"",
"options": [] "options": []
}, },
{
"name": "minItemWidth",
"type": "string",
"required": false,
"default": "\"\"",
"options": []
},
{ {
"name": "class", "name": "class",
"type": "string", "type": "string",
@@ -6962,6 +6985,13 @@
"default": "\"lazy\"", "default": "\"lazy\"",
"options": [] "options": []
}, },
{
"name": "fit",
"type": "string",
"required": false,
"default": "\"cover\"",
"options": []
},
{ {
"name": "rounded", "name": "rounded",
"type": "boolean", "type": "boolean",
@@ -7750,7 +7780,7 @@
"name": "label", "name": "label",
"type": "string", "type": "string",
"required": false, "required": false,
"default": "\"K\"", "default": "\"K\"",
"options": [] "options": []
}, },
{ {
@@ -7772,13 +7802,6 @@
"category": "layout", "category": "layout",
"purpose": "Theme-aware, responsive layout splitter component.", "purpose": "Theme-aware, responsive layout splitter component.",
"props": [ "props": [
{
"name": "size",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{ {
"name": "color", "name": "color",
"type": "string", "type": "string",
@@ -7787,24 +7810,38 @@
"options": [] "options": []
}, },
{ {
"name": "columns", "name": "size",
"type": "number", "type": "number",
"required": false, "required": false,
"default": "2", "default": "50",
"options": [] "options": []
}, },
{ {
"name": "gap", "name": "orientation",
"type": "string", "type": "string",
"required": false, "required": false,
"default": "\"md\"", "default": "\"horizontal\"",
"options": [] "options": []
}, },
{ {
"name": "maxWidth", "name": "minSize",
"type": "number",
"required": false,
"default": "15",
"options": []
},
{
"name": "step",
"type": "number",
"required": false,
"default": "5",
"options": []
},
{
"name": "label",
"type": "string", "type": "string",
"required": false, "required": false,
"default": "\"xl\"", "default": "\"Resize panels\"",
"options": [] "options": []
}, },
{ {
@@ -7815,22 +7852,14 @@
"options": [] "options": []
} }
], ],
"slots": ["default"], "slots": ["start", "end", "default"],
"outputs": [ "outputs": [
{ {
"name": "resizeStart", "name": "sizeChange",
"payloadType": "{ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[]" "payloadType": "{ size: number }"
},
{
"name": "resize",
"payloadType": "{ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[]"
},
{
"name": "resizeEnd",
"payloadType": "{ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[]"
} }
], ],
"events": ["resizeStart", "resize", "resizeEnd"], "events": ["sizeChange"],
"source": "components/LayoutSplitter.wrn" "source": "components/LayoutSplitter.wrn"
}, },
{ {
@@ -7951,6 +7980,13 @@
"default": "\"\"", "default": "\"\"",
"options": [] "options": []
}, },
{
"name": "underline",
"type": "string",
"required": false,
"default": "\"hover\"",
"options": []
},
{ {
"name": "external", "name": "external",
"type": "boolean", "type": "boolean",
@@ -8028,8 +8064,13 @@
} }
], ],
"slots": ["default"], "slots": ["default"],
"outputs": [], "outputs": [
"events": [], {
"name": "select",
"payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }"
}
],
"events": ["select"],
"source": "components/List.wrn" "source": "components/List.wrn"
}, },
{ {
@@ -8159,8 +8200,21 @@
} }
], ],
"slots": ["default"], "slots": ["default"],
"outputs": [], "outputs": [
"events": [], {
"name": "markerClick",
"payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }"
},
{
"name": "select",
"payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }"
},
{
"name": "zoom",
"payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }"
}
],
"events": ["markerClick", "select", "zoom"],
"source": "components/Map.wrn" "source": "components/Map.wrn"
}, },
{ {
@@ -8316,8 +8370,17 @@
} }
], ],
"slots": ["default"], "slots": ["default"],
"outputs": [], "outputs": [
"events": [], {
"name": "pause",
"payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }"
},
{
"name": "resume",
"payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }"
}
],
"events": ["pause", "resume"],
"source": "components/Marquee.wrn" "source": "components/Marquee.wrn"
}, },
{ {
@@ -8326,13 +8389,6 @@
"category": "navigation", "category": "navigation",
"purpose": "Theme-aware, responsive mega menu component.", "purpose": "Theme-aware, responsive mega menu component.",
"props": [ "props": [
{
"name": "size",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{ {
"name": "color", "name": "color",
"type": "string", "type": "string",
@@ -8340,32 +8396,46 @@
"default": "\"primary\"", "default": "\"primary\"",
"options": [] "options": []
}, },
{
"name": "size",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{ {
"name": "label", "name": "label",
"type": "string", "type": "string",
"required": false, "required": false,
"default": "\"Mega Menu\"", "default": "\"Menu\"",
"options": [] "options": []
}, },
{ {
"name": "items", "name": "icon",
"type": "unknown[]",
"required": false,
"default": "[]",
"options": []
},
{
"name": "active",
"type": "string", "type": "string",
"required": false, "required": false,
"default": "\"\"", "default": "\"\"",
"options": [] "options": []
}, },
{ {
"name": "orientation", "name": "columns",
"type": "unknown[]",
"required": false,
"default": "[]",
"options": []
},
{
"name": "footer",
"type": "string", "type": "string",
"required": false, "required": false,
"default": "\"horizontal\"", "default": "\"\"",
"options": []
},
{
"name": "defaultOpen",
"type": "boolean",
"required": false,
"default": "false",
"options": [] "options": []
}, },
{ {
@@ -8380,15 +8450,15 @@
"outputs": [ "outputs": [
{ {
"name": "open", "name": "open",
"payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }" "payloadType": "{ sourceEvent: Event }"
}, },
{ {
"name": "close", "name": "close",
"payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }" "payloadType": "{ reason: string }"
}, },
{ {
"name": "select", "name": "select",
"payloadType": "{ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null" "payloadType": "{ item: object; value: string }"
} }
], ],
"events": ["open", "close", "select"], "events": ["open", "close", "select"],
@@ -8961,13 +9031,6 @@
"category": "navigation", "category": "navigation",
"purpose": "Theme-aware, responsive nav component.", "purpose": "Theme-aware, responsive nav component.",
"props": [ "props": [
{
"name": "size",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{ {
"name": "color", "name": "color",
"type": "string", "type": "string",
@@ -8976,10 +9039,10 @@
"options": [] "options": []
}, },
{ {
"name": "label", "name": "size",
"type": "string", "type": "string",
"required": false, "required": false,
"default": "\"Nav\"", "default": "\"default\"",
"options": [] "options": []
}, },
{ {
@@ -9003,6 +9066,27 @@
"default": "\"horizontal\"", "default": "\"horizontal\"",
"options": [] "options": []
}, },
{
"name": "label",
"type": "string",
"required": false,
"default": "\"Main\"",
"options": []
},
{
"name": "collapsible",
"type": "boolean",
"required": false,
"default": "true",
"options": []
},
{
"name": "toggleLabel",
"type": "string",
"required": false,
"default": "\"Menu\"",
"options": []
},
{ {
"name": "class", "name": "class",
"type": "string", "type": "string",
@@ -9015,14 +9099,10 @@
"outputs": [ "outputs": [
{ {
"name": "select", "name": "select",
"payloadType": "{ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null" "payloadType": "{ item: object; value: string }"
},
{
"name": "change",
"payloadType": "{ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null"
} }
], ],
"events": ["select", "change"], "events": ["select"],
"source": "components/Nav.wrn" "source": "components/Nav.wrn"
}, },
{ {
@@ -9342,6 +9422,13 @@
"category": "navigation", "category": "navigation",
"purpose": "Theme-aware, responsive pagination component.", "purpose": "Theme-aware, responsive pagination component.",
"props": [ "props": [
{
"name": "color",
"type": "string",
"required": false,
"default": "\"primary\"",
"options": []
},
{ {
"name": "size", "name": "size",
"type": "string", "type": "string",
@@ -9350,10 +9437,45 @@
"options": [] "options": []
}, },
{ {
"name": "color", "name": "page",
"type": "number",
"required": false,
"default": "1",
"options": []
},
{
"name": "pageSize",
"type": "number",
"required": false,
"default": "10",
"options": []
},
{
"name": "total",
"type": "number",
"required": false,
"default": "0",
"options": []
},
{
"name": "variant",
"type": "string", "type": "string",
"required": false, "required": false,
"default": "\"primary\"", "default": "\"compact\"",
"options": []
},
{
"name": "siblingCount",
"type": "number",
"required": false,
"default": "1",
"options": []
},
{
"name": "showSummary",
"type": "boolean",
"required": false,
"default": "true",
"options": [] "options": []
}, },
{ {
@@ -9364,24 +9486,17 @@
"options": [] "options": []
}, },
{ {
"name": "items", "name": "previousLabel",
"type": "unknown[]", "type": "string",
"required": false, "required": false,
"default": "[]", "default": "\"Previous\"",
"options": [] "options": []
}, },
{ {
"name": "active", "name": "nextLabel",
"type": "string", "type": "string",
"required": false, "required": false,
"default": "\"\"", "default": "\"Next\"",
"options": []
},
{
"name": "orientation",
"type": "string",
"required": false,
"default": "\"horizontal\"",
"options": [] "options": []
}, },
{ {
@@ -9396,15 +9511,15 @@
"outputs": [ "outputs": [
{ {
"name": "change", "name": "change",
"payloadType": "{ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null" "payloadType": "{ page: number; pageSize: number }"
}, },
{ {
"name": "previous", "name": "previous",
"payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }" "payloadType": "{ page: number }"
}, },
{ {
"name": "next", "name": "next",
"payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }" "payloadType": "{ page: number }"
} }
], ],
"events": ["change", "previous", "next"], "events": ["change", "previous", "next"],
@@ -10664,13 +10779,6 @@
"category": "navigation", "category": "navigation",
"purpose": "Theme-aware, responsive scrollspy component.", "purpose": "Theme-aware, responsive scrollspy component.",
"props": [ "props": [
{
"name": "size",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{ {
"name": "color", "name": "color",
"type": "string", "type": "string",
@@ -10679,10 +10787,10 @@
"options": [] "options": []
}, },
{ {
"name": "label", "name": "size",
"type": "string", "type": "string",
"required": false, "required": false,
"default": "\"Scrollspy\"", "default": "\"default\"",
"options": [] "options": []
}, },
{ {
@@ -10700,10 +10808,17 @@
"options": [] "options": []
}, },
{ {
"name": "orientation", "name": "label",
"type": "string", "type": "string",
"required": false, "required": false,
"default": "\"horizontal\"", "default": "\"On this page\"",
"options": []
},
{
"name": "heading",
"type": "string",
"required": false,
"default": "\"\"",
"options": [] "options": []
}, },
{ {
@@ -10718,7 +10833,7 @@
"outputs": [ "outputs": [
{ {
"name": "change", "name": "change",
"payloadType": "{ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null" "payloadType": "{ href: string; label: string }"
} }
], ],
"events": ["change"], "events": ["change"],
@@ -10823,8 +10938,17 @@
} }
], ],
"slots": [], "slots": [],
"outputs": [], "outputs": [
"events": [], {
"name": "search",
"payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }"
},
{
"name": "clear",
"payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }"
}
],
"events": ["search", "clear"],
"source": "components/SearchBox.wrn" "source": "components/SearchBox.wrn"
}, },
{ {
@@ -11192,13 +11316,6 @@
"category": "navigation", "category": "navigation",
"purpose": "Theme-aware, responsive sidebar component.", "purpose": "Theme-aware, responsive sidebar component.",
"props": [ "props": [
{
"name": "size",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{ {
"name": "color", "name": "color",
"type": "string", "type": "string",
@@ -11206,6 +11323,13 @@
"default": "\"primary\"", "default": "\"primary\"",
"options": [] "options": []
}, },
{
"name": "size",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{ {
"name": "label", "name": "label",
"type": "string", "type": "string",
@@ -11227,13 +11351,6 @@
"default": "\"\"", "default": "\"\"",
"options": [] "options": []
}, },
{
"name": "orientation",
"type": "string",
"required": false,
"default": "\"horizontal\"",
"options": []
},
{ {
"name": "mobileLabel", "name": "mobileLabel",
"type": "string", "type": "string",
@@ -11241,6 +11358,13 @@
"default": "\"Open navigation\"", "default": "\"Open navigation\"",
"options": [] "options": []
}, },
{
"name": "drawerTitle",
"type": "string",
"required": false,
"default": "\"Navigation\"",
"options": []
},
{ {
"name": "class", "name": "class",
"type": "string", "type": "string",
@@ -11249,7 +11373,7 @@
"options": [] "options": []
} }
], ],
"slots": ["default"], "slots": ["default", "drawer"],
"outputs": [ "outputs": [
{ {
"name": "toggle", "name": "toggle",
@@ -11257,7 +11381,7 @@
}, },
{ {
"name": "open", "name": "open",
"payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }" "payloadType": "{ sourceEvent: Event }"
}, },
{ {
"name": "close", "name": "close",
@@ -11265,7 +11389,7 @@
}, },
{ {
"name": "select", "name": "select",
"payloadType": "{ item: string | number | boolean | null | object; value: string | number | boolean; level: string }" "payloadType": "{ item: object; value: string; level: number }"
} }
], ],
"events": ["toggle", "open", "close", "select"], "events": ["toggle", "open", "close", "select"],
@@ -11682,13 +11806,6 @@
"category": "navigation", "category": "navigation",
"purpose": "Theme-aware, responsive stepper component.", "purpose": "Theme-aware, responsive stepper component.",
"props": [ "props": [
{
"name": "size",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{ {
"name": "color", "name": "color",
"type": "string", "type": "string",
@@ -11697,14 +11814,14 @@
"options": [] "options": []
}, },
{ {
"name": "label", "name": "size",
"type": "string", "type": "string",
"required": false, "required": false,
"default": "\"Stepper\"", "default": "\"default\"",
"options": [] "options": []
}, },
{ {
"name": "items", "name": "steps",
"type": "unknown[]", "type": "unknown[]",
"required": false, "required": false,
"default": "[]", "default": "[]",
@@ -11712,9 +11829,9 @@
}, },
{ {
"name": "active", "name": "active",
"type": "string", "type": "number",
"required": false, "required": false,
"default": "\"\"", "default": "0",
"options": [] "options": []
}, },
{ {
@@ -11724,6 +11841,76 @@
"default": "\"horizontal\"", "default": "\"horizontal\"",
"options": [] "options": []
}, },
{
"name": "clickable",
"type": "boolean",
"required": false,
"default": "false",
"options": []
},
{
"name": "label",
"type": "string",
"required": false,
"default": "\"Progress\"",
"options": []
},
{
"name": "showPanel",
"type": "boolean",
"required": false,
"default": "false",
"options": []
},
{
"name": "controls",
"type": "boolean",
"required": false,
"default": "false",
"options": []
},
{
"name": "allowSkip",
"type": "boolean",
"required": false,
"default": "false",
"options": []
},
{
"name": "nextDisabled",
"type": "boolean",
"required": false,
"default": "false",
"options": []
},
{
"name": "backLabel",
"type": "string",
"required": false,
"default": "\"Back\"",
"options": []
},
{
"name": "nextLabel",
"type": "string",
"required": false,
"default": "\"Next\"",
"options": []
},
{
"name": "skipLabel",
"type": "string",
"required": false,
"default": "\"Skip\"",
"options": []
},
{
"name": "finishLabel",
"type": "string",
"required": false,
"default": "\"Finish\"",
"options": []
},
{ {
"name": "class", "name": "class",
"type": "string", "type": "string",
@@ -11732,26 +11919,30 @@
"options": [] "options": []
} }
], ],
"slots": ["default"], "slots": ["step-{index}", "panel-{index}", "default"],
"outputs": [ "outputs": [
{ {
"name": "change", "name": "change",
"payloadType": "{ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null" "payloadType": "{ index: number; step: object }"
}, },
{ {
"name": "previous", "name": "back",
"payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }" "payloadType": "{ index: number; step: object }"
}, },
{ {
"name": "next", "name": "next",
"payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }" "payloadType": "{ index: number; step: object }"
}, },
{ {
"name": "complete", "name": "skip",
"payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }" "payloadType": "{ index: number; step: object }"
},
{
"name": "finish",
"payloadType": "{ index: number; step: object }"
} }
], ],
"events": ["change", "previous", "next", "complete"], "events": ["change", "back", "next", "skip", "finish"],
"source": "components/Stepper.wrn" "source": "components/Stepper.wrn"
}, },
{ {
@@ -12204,13 +12395,6 @@
"category": "navigation", "category": "navigation",
"purpose": "Switch between related responsive content panels with horizontal or vertical orientation and selection events.", "purpose": "Switch between related responsive content panels with horizontal or vertical orientation and selection events.",
"props": [ "props": [
{
"name": "size",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{ {
"name": "color", "name": "color",
"type": "string", "type": "string",
@@ -12219,10 +12403,10 @@
"options": [] "options": []
}, },
{ {
"name": "label", "name": "size",
"type": "string", "type": "string",
"required": false, "required": false,
"default": "\"Tabs\"", "default": "\"default\"",
"options": [] "options": []
}, },
{ {
@@ -12246,6 +12430,27 @@
"default": "\"horizontal\"", "default": "\"horizontal\"",
"options": [] "options": []
}, },
{
"name": "mode",
"type": "string",
"required": false,
"default": "\"client\"",
"options": []
},
{
"name": "param",
"type": "string",
"required": false,
"default": "\"tab\"",
"options": []
},
{
"name": "label",
"type": "string",
"required": false,
"default": "\"Tabs\"",
"options": []
},
{ {
"name": "class", "name": "class",
"type": "string", "type": "string",
@@ -12254,9 +12459,18 @@
"options": [] "options": []
} }
], ],
"slots": ["default"], "slots": ["panel-{valueOf(item, index)}", "default"],
"outputs": [], "outputs": [
"events": [], {
"name": "change",
"payloadType": "{ value: string; item: object; index: number }"
},
{
"name": "select",
"payloadType": "{ value: string; item: object; index: number }"
}
],
"events": ["change", "select"],
"source": "components/Tabs.wrn" "source": "components/Tabs.wrn"
}, },
{ {
@@ -12856,8 +13070,13 @@
} }
], ],
"slots": ["default"], "slots": ["default"],
"outputs": [], "outputs": [
"events": [], {
"name": "select",
"payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }"
}
],
"events": ["select"],
"source": "components/Timeline.wrn" "source": "components/Timeline.wrn"
}, },
{ {
+13 -11
View File
@@ -49,22 +49,24 @@ component Accordion {
return multiple || alwaysOpen return multiple || alwaysOpen
} }
client function dispatchAccordionEvent(sourceEvent, eventName, value, item, root, customEvent) { // Named rather than computed: an output is resolved as a property name, so
root = sourceEvent.currentTarget.closest("[data-wrn-accordion]") // output[eventName] would not reach a parent binding.
client function dispatchAccordionEvent(sourceEvent, eventName, value, item, payload) {
if (!root) { payload = {
return
}
customEvent = document.createEvent("CustomEvent")
customEvent.initCustomEvent(eventName, true, false, {
component: "Accordion", component: "Accordion",
value: value, value: value,
item: item, item: item,
open: isOpen(value), open: isOpen(value),
openValues: openValues openValues: openValues
}) }
root.dispatchEvent(customEvent)
if (eventName === "open") {
output.open(payload)
} else if (eventName === "close") {
output.close(payload)
} else {
output.change(payload)
}
} }
client function toggleItem(sourceEvent, value, item, wasOpen) { client function toggleItem(sourceEvent, value, item, wasOpen) {
+2 -6
View File
@@ -30,17 +30,13 @@ component AvatarGroup {
return items.slice(Number(maxVisible)) return items.slice(Number(maxVisible))
} }
client function toggleOverflow(sourceEvent, root, customEvent) { client function toggleOverflow() {
overflowOpen = !overflowOpen overflowOpen = !overflowOpen
root = sourceEvent.currentTarget.closest("[data-wrn-avatar-group]") output.overflow({
customEvent = document.createEvent("CustomEvent")
customEvent.initCustomEvent("overflow", true, false, {
component: "AvatarGroup", component: "AvatarGroup",
open: overflowOpen, open: overflowOpen,
hiddenCount: hiddenMembers().length hiddenCount: hiddenMembers().length
}) })
root.dispatchEvent(customEvent)
} }
} }
+3 -3
View File
@@ -34,7 +34,7 @@ label: string = "Breadcrumb"
<a <a
href='{homeHref || "/"}' href='{homeHref || "/"}'
class="wire-breadcrumb__link wire-breadcrumb__home" class="wire-breadcrumb__link wire-breadcrumb__home"
@click='event.currentTarget.dispatchEvent(new CustomEvent("select", { bubbles: true, detail: { item: { label: homeLabel, href: homeHref || "/", value: "home" }, itemIndex: -1 } }))' @click='output.select({ item: { label: homeLabel, href: homeHref || "/", value: "home" }, itemIndex: -1 })'
> >
{#if homeIcon === "icon-[lucide--house]"} {#if homeIcon === "icon-[lucide--house]"}
<span class="icon-[lucide--house] wire-breadcrumb__icon" aria-hidden="true"></span> <span class="icon-[lucide--house] wire-breadcrumb__icon" aria-hidden="true"></span>
@@ -72,7 +72,7 @@ label: string = "Breadcrumb"
target='{item.target || ""}' target='{item.target || ""}'
rel='{item.external ? "noopener noreferrer" : (item.rel || "")}' rel='{item.external ? "noopener noreferrer" : (item.rel || "")}'
class="wire-breadcrumb__link" class="wire-breadcrumb__link"
@click='event.currentTarget.dispatchEvent(new CustomEvent("select", { bubbles: true, detail: { item: item, itemIndex: itemIndex } }))' @click='output.select({ item: item, itemIndex: itemIndex })'
> >
{#if item.icon} {#if item.icon}
<span class='{item.icon} wire-breadcrumb__icon' aria-hidden="true"></span> <span class='{item.icon} wire-breadcrumb__icon' aria-hidden="true"></span>
@@ -87,7 +87,7 @@ label: string = "Breadcrumb"
{:else} {:else}
<span <span
class="wire-breadcrumb__current" class="wire-breadcrumb__current"
aria-current='{item.active || item.current || (active && active === (item.value || item.label || item.title)) || (!active && itemIndex === items.length - 1) ? "page" : ""}' aria-current='{item.active || item.current || (active && active === (item.value || item.label || item.title)) || (!active && itemIndex === items.length - 1) ? "page" : "false"}'
aria-disabled='{item.disabled ? "true" : "false"}' aria-disabled='{item.disabled ? "true" : "false"}'
> >
{#if item.icon} {#if item.icon}
+88 -14
View File
@@ -1,3 +1,13 @@
// Columns -- a simple multi-column split for page content.
//
// <Columns columns={2} gap="lg">...</Columns>
//
// Columns and Grid overlap deliberately: Columns is the coarse two or three
// way split of a page, and its gaps are wider for that reason. Grid is the one
// to reach for when the items are cards and the count matters.
//
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
// and silently swallows the rule that follows it.
component Columns { component Columns {
props { props {
size: string = "default" size: string = "default"
@@ -8,28 +18,92 @@ component Columns {
class: string = "" class: string = ""
} }
functions {
shared function columnCount() {
var value = Number(columns)
if (!value || value < 1) {
return 1
}
return Math.min(4, value)
}
}
view { view {
<div <div
{...attrs}
data-ui-component="Columns" data-ui-component="Columns"
class='wire-columns {class}'
data-size='{size}' data-size='{size}'
data-color='{color}' data-color='{color}'
data-columns='{columns}'
data-gap='{gap}' data-gap='{gap}'
class='grid w-full grid-cols-1 items-start {class}' data-max-width='{maxWidth}'
class:max-w-3xl='maxWidth === "md"' data-columns='{columnCount()}'
class:max-w-5xl='maxWidth === "lg"'
class:max-w-7xl='maxWidth === "xl"'
class:max-w-screen-2xl='maxWidth === "2xl"'
class:max-w-none='maxWidth === "full"'
class:md:grid-cols-2='columns >= 2'
class:lg:grid-cols-3='columns === 3'
class:lg:grid-cols-4='columns === 4'
class:gap-3='gap === "sm"'
class:gap-6='gap === "md"'
class:gap-10='gap === "lg"'
class:gap-14='gap === "xl"'
> >
<slot></slot> <slot></slot>
</div> </div>
} }
style {
.wire-columns {
--columns-gap: 1.5rem;
display: grid;
width: 100%;
min-width: 0;
gap: var(--columns-gap);
grid-template-columns: 1fr;
align-items: start;
}
.wire-columns[data-max-width="md"] {
max-width: 48rem;
}
.wire-columns[data-max-width="lg"] {
max-width: 64rem;
}
.wire-columns[data-max-width="xl"] {
max-width: 80rem;
}
.wire-columns[data-max-width="2xl"] {
max-width: 96rem;
}
.wire-columns[data-max-width="full"] {
max-width: none;
}
.wire-columns[data-gap="sm"] {
--columns-gap: 0.75rem;
}
.wire-columns[data-gap="lg"] {
--columns-gap: 2.5rem;
}
.wire-columns[data-gap="xl"] {
--columns-gap: 3.5rem;
}
/*
* Splits at the tablet breakpoint rather than the phone one: a page split
* is worth keeping single-column for longer than a card deck is.
*/
@media (min-width: 768px) {
.wire-columns:not([data-columns="1"]) {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (min-width: 1024px) {
.wire-columns[data-columns="3"] {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.wire-columns[data-columns="4"] {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
}
}
} }
+114 -9
View File
@@ -1,17 +1,122 @@
// CustomScrollbar -- a scrolling region with a themed scrollbar.
//
// <CustomScrollbar maxHeight="20rem">...long content...</CustomScrollbar>
//
// Scrollbar appearance is CSS, not script: scrollbar-width and scrollbar-color
// are the standard properties, and the ::-webkit-scrollbar rules cover the
// browsers that still need them.
//
// This component used to declare a scroll output it never emitted, and its
// props were columns, gap and maxWidth copied from a grid scaffold. The output
// is removed rather than left unimplemented -- a caller can listen for a plain
// scroll event on the element, which is what it would have been anyway.
//
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
// and silently swallows the rule that follows it.
component CustomScrollbar { component CustomScrollbar {
outputs {
scroll(payload: { sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])
}
props { props {
size: string = "default"
color: string = "primary" color: string = "primary"
columns: number = 2 size: string = "default"
gap: string = "md" axis: string = "vertical"
maxWidth: string = "xl" // Track thickness in pixels. Clamped, because it arrives as an attribute
// and a scrollbar wider than the content is not useful to anyone.
thickness: number = 8
maxHeight: string = "20rem"
radius: string = "999px"
class: string = "" class: string = ""
} }
functions {
shared function trackSize() {
var value = Number(thickness)
if (!value || value < 2) {
return 8
}
return Math.min(24, value)
}
}
view { view {
<div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--custom-scrollbar wire-next--gap-{gap} wire-next--columns-{columns} wire-next--max-{maxWidth} {class}"><slot /></div> <div
{...attrs}
data-ui-component="CustomScrollbar"
class='wire-scrollbar {class}'
data-color='{color}'
data-size='{size}'
data-axis='{axis}'
style='--scrollbar-thickness:{trackSize()}px;--scrollbar-radius:{radius};max-height:{maxHeight};'
>
<slot />
</div>
}
style {
.wire-scrollbar {
--scrollbar-accent: var(--wire-color-primary);
min-width: 0;
overflow: auto;
/*
* The standard properties. Firefox and current Chrome honour these; the
* webkit rules below are only for the engines that still ignore them.
*/
scrollbar-width: thin;
scrollbar-color: color-mix(in srgb, var(--scrollbar-accent) 55%, transparent) transparent;
overscroll-behavior: contain;
}
.wire-scrollbar[data-color="secondary"] {
--scrollbar-accent: var(--wire-color-secondary);
}
.wire-scrollbar[data-color="success"] {
--scrollbar-accent: var(--wire-color-success);
}
.wire-scrollbar[data-color="danger"] {
--scrollbar-accent: var(--wire-color-danger);
}
.wire-scrollbar[data-color="info"] {
--scrollbar-accent: var(--wire-color-info);
}
.wire-scrollbar[data-axis="vertical"] {
overflow-x: hidden;
overflow-y: auto;
}
.wire-scrollbar[data-axis="horizontal"] {
overflow-x: auto;
overflow-y: hidden;
}
.wire-scrollbar::-webkit-scrollbar {
width: var(--scrollbar-thickness, 8px);
height: var(--scrollbar-thickness, 8px);
}
.wire-scrollbar::-webkit-scrollbar-track {
background: var(--wire-color-surface-soft);
border-radius: var(--scrollbar-radius, 999px);
}
.wire-scrollbar::-webkit-scrollbar-thumb {
background: color-mix(in srgb, var(--scrollbar-accent) 45%, transparent);
border-radius: var(--scrollbar-radius, 999px);
}
.wire-scrollbar::-webkit-scrollbar-thumb:hover {
background: var(--scrollbar-accent);
}
/*
* A pointer-less device paints its own overlay scrollbar and ignores the
* width above, so the region keeps its own padding instead.
*/
@media (hover: none) {
.wire-scrollbar {
scrollbar-width: auto;
}
}
} }
} }
+105 -25
View File
@@ -1,3 +1,13 @@
// Image -- a framed image with a reserved aspect ratio.
//
// <Image src="/hero.jpg" alt="Dashboard" size="video" rounded={true} />
//
// The frame keeps its ratio whether or not the image has loaded, so the page
// does not jump when it arrives. Without a src it renders a labelled
// placeholder of the same shape rather than collapsing.
//
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
// and silently swallows the rule that follows it.
component Image { component Image {
props { props {
size: string = "default" size: string = "default"
@@ -7,41 +17,111 @@ component Image {
width: string = "" width: string = ""
height: string = "" height: string = ""
loading: string = "lazy" loading: string = "lazy"
fit: string = "cover"
rounded: boolean = false rounded: boolean = false
class: string = "" class: string = ""
} }
view { view {
<figure <figure
{...attrs}
data-ui-component="Image" data-ui-component="Image"
class='relative m-0 overflow-hidden bg-[var(--wire-color-surface-soft)] {class}' class='wire-image {class}'
class:rounded-2xl='rounded' data-size='{size}'
data-color='{color}'
data-fit='{fit}'
data-rounded='{rounded}'
> >
{#if src} <img
<img class="wire-image__img"
src='{src}' data-show="src"
alt='{alt}' src='{src}'
width='{width}' alt='{alt}'
height='{height}' width='{width}'
loading='{loading}' height='{height}'
decoding="async" loading='{loading}'
class="block h-auto w-full object-cover transition duration-300" decoding="async"
class:aspect-square='size === "square"' />
class:aspect-video='size === "video"'
class:aspect-[4/3]='size === "landscape"' <div
class:aspect-[3/4]='size === "portrait"' class="wire-image__placeholder"
/> data-show="!src"
{:else} role="img"
<div aria-label='{alt || "Image placeholder"}'
class="flex min-h-48 w-full items-center justify-center text-[var(--wire-color-text-muted)]" >
role="img" <span class="icon-[lucide--image] wire-image__placeholder-icon" aria-hidden="true"></span>
aria-label='{alt || "Image placeholder"}' </div>
>
<span class="icon-[lucide--image] size-8" aria-hidden="true"></span>
</div>
{/if}
<slot></slot> <slot></slot>
</figure> </figure>
} }
style {
.wire-image {
position: relative;
margin: 0;
overflow: hidden;
width: 100%;
min-width: 0;
background: var(--wire-color-surface-soft);
}
.wire-image[data-rounded="true"] {
border-radius: var(--wire-radius-lg);
}
/*
* The ratio sits on the frame rather than the image, so the space is
* reserved before the file arrives and the page does not jump.
*/
.wire-image[data-size="square"] {
aspect-ratio: 1 / 1;
}
.wire-image[data-size="video"] {
aspect-ratio: 16 / 9;
}
.wire-image[data-size="landscape"] {
aspect-ratio: 4 / 3;
}
.wire-image[data-size="portrait"] {
aspect-ratio: 3 / 4;
}
.wire-image__img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
.wire-image[data-fit="contain"] .wire-image__img {
object-fit: contain;
}
.wire-image[data-fit="fill"] .wire-image__img {
object-fit: fill;
}
.wire-image[data-size="default"] .wire-image__img {
height: auto;
}
.wire-image__placeholder {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
min-height: 12rem;
height: 100%;
color: var(--wire-color-text-muted);
}
.wire-image__placeholder-icon {
width: 2rem;
height: 2rem;
}
}
} }
+16 -16
View File
@@ -232,26 +232,17 @@ component InputNumber {
) )
} }
// Outputs are resolved by name, so each one is written out. Raw
// CustomEvents dispatched on the root -- what this did before -- never
// reach a parent @binding.
client function dispatchInputNumberEvent( client function dispatchInputNumberEvent(
sourceEvent, sourceEvent,
eventName, eventName,
action, action,
previousValue, previousValue,
root, payload
customEvent
) { ) {
root = sourceEvent.currentTarget.closest("[data-wrn-input-number]") payload = {
if (!root && sourceEvent.target) {
root = sourceEvent.target.closest("[data-wrn-input-number]")
}
if (!root) {
return
}
customEvent = document.createEvent("CustomEvent")
customEvent.initCustomEvent(eventName, true, false, {
component: "InputNumber", component: "InputNumber",
name: name, name: name,
value: currentValue, value: currentValue,
@@ -261,8 +252,17 @@ component InputNumber {
max: hasMax() ? Number(max) : null, max: hasMax() ? Number(max) : null,
step: normalizedStep(), step: normalizedStep(),
valid: !isInvalid() valid: !isInvalid()
}) }
root.dispatchEvent(customEvent)
if (eventName === "input") {
output.input(payload)
} else if (eventName === "change") {
output.change(payload)
} else if (eventName === "increment") {
output.increment(payload)
} else if (eventName === "decrement") {
output.decrement(payload)
}
} }
client function applyControlValue(nextValue, action, sourceEvent, previousValue) { client function applyControlValue(nextValue, action, sourceEvent, previousValue) {
+48 -2
View File
@@ -1,9 +1,55 @@
// Kbd -- a keyboard key.
//
// <Kbd label="Ctrl" />
//
// Deliberately small. A kbd element is a kbd element; the work here is making
// it look like a key and follow the theme.
//
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
// and silently swallows the rule that follows it.
component Kbd { component Kbd {
props { props {
size: string = "default" size: string = "default"
color: string = "primary" color: string = "primary"
label: string = "K" label: string = "K"
class: string = "" class: string = ""
} }
view { <kbd class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--kbd {class}"><slot>{label}</slot></kbd> }
view {
<kbd {...attrs} class='wire-kbd {class}' data-size='{size}' data-color='{color}'>
<slot>{label}</slot>
</kbd>
}
style {
.wire-kbd {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 1.6rem;
padding: 0.15rem 0.4rem;
border: 1px solid var(--wire-color-border);
/* The lower edge is what reads as a physical key. */
border-bottom-width: 2px;
border-radius: var(--wire-radius-sm);
background: var(--wire-color-surface-soft);
color: var(--wire-color-text);
font-family: inherit;
font-size: 0.78rem;
font-weight: 600;
line-height: 1;
}
.wire-kbd[data-size="sm"] {
min-width: 1.4rem;
padding: 0.1rem 0.3rem;
font-size: 0.7rem;
}
.wire-kbd[data-size="lg"] {
min-width: 2rem;
padding: 0.3rem 0.55rem;
font-size: 0.9rem;
}
}
} }
+214 -8
View File
@@ -1,19 +1,225 @@
// LayoutSplitter -- two panes with a divider the reader can move.
//
// <LayoutSplitter size={40} minSize={20}>
// <div data-slot="start">...</div>
// <div data-slot="end">...</div>
// </LayoutSplitter>
//
// The dragging lives in the reactive runtime behind data-wrn-splitter. Pointer
// moves fire far too often to route through a client function, and a state
// write made inside a pointermove callback is dropped, so the resolved size is
// held in the DOM as the --wrn-split custom property and these styles read it.
//
// This component previously declared resizeStart, resize and resizeEnd with no
// pointer handling whatsoever: a caller wired up @resize and received nothing,
// for ever, with no error.
//
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
// and silently swallows the rule that follows it.
component LayoutSplitter { component LayoutSplitter {
outputs { outputs {
resizeStart(payload: { sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[]) // Named sizeChange rather than resize so it cannot be confused with the
resize(payload: { sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[]) // native window event a caller may already be listening for. An earlier
resizeEnd(payload: { sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[]) // comment here claimed a natively-named output could never reach a parent
// binding; that was wrong, and the rename was never what fixed anything.
sizeChange(payload: { size: number })
} }
props { props {
size: string = "default"
color: string = "primary" color: string = "primary"
columns: number = 2 size: number = 50
gap: string = "md" orientation: string = "horizontal"
maxWidth: string = "xl" // Smallest share either pane may take, as a percentage. It also fixes the
// upper bound at 100 - minSize, so neither pane can be dragged away to
// nothing and left unrecoverable by pointer.
minSize: number = 15
step: number = 5
label: string = "Resize panels"
class: string = "" class: string = ""
} }
functions {
// The runtime resolves the size and announces it; this turns that into the
// declared output so a parent @resize binding receives it.
client function reportResize(sourceEvent) {
var detail = sourceEvent ? sourceEvent.detail : null
if (!detail) {
return
}
output.sizeChange({ size: detail.size })
}
shared function isVertical() {
return orientation === "vertical"
}
shared function lowerBound() {
var value = Number(minSize)
if (!value || value < 0) {
return 15
}
return Math.min(45, value)
}
shared function upperBound() {
return 100 - lowerBound()
}
// Sizes arrive as HTML attributes, so a value outside the bounds is
// routine rather than exceptional. Clamp instead of rendering something
// the reader cannot undo.
shared function currentSize() {
var value = Number(size)
if (!value || value < 0) {
return 50
}
return Math.min(upperBound(), Math.max(lowerBound(), value))
}
shared function stepSize() {
var value = Number(step)
return value && value > 0 ? value : 5
}
}
view { view {
<div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--layout-splitter wire-next--gap-{gap} wire-next--columns-{columns} wire-next--max-{maxWidth} {class}"><slot /></div> <div
{...attrs}
data-ui-component="LayoutSplitter"
class='wire-splitter {class}'
data-color='{color}'
data-orientation='{orientation}'
data-wrn-splitter='{isVertical() ? "vertical" : "horizontal"}'
data-wrn-splitter-min='{lowerBound()}'
data-wrn-splitter-step='{stepSize()}'
style='--wrn-split:{currentSize()}%;'
@wrnexus:splitter:resize='reportResize(event)'
>
<div class="wire-splitter__pane wire-splitter__pane--start">
<slot name="start"></slot>
</div>
<div
class="wire-splitter__handle"
data-wrn-splitter-handle="true"
role="separator"
tabindex="0"
aria-label='{label}'
aria-orientation='{isVertical() ? "horizontal" : "vertical"}'
aria-valuenow='{currentSize()}'
aria-valuemin='{lowerBound()}'
aria-valuemax='{upperBound()}'
>
<span class="wire-splitter__grip" aria-hidden="true"></span>
</div>
<div class="wire-splitter__pane wire-splitter__pane--end">
<slot name="end"></slot>
</div>
<slot />
</div>
}
style {
.wire-splitter {
--splitter-accent: var(--wire-color-primary);
display: grid;
/* The first track follows the size the runtime resolves. */
grid-template-columns: var(--wrn-split, 50%) auto minmax(0, 1fr);
align-items: stretch;
width: 100%;
min-width: 0;
}
.wire-splitter[data-color="secondary"] {
--splitter-accent: var(--wire-color-secondary);
}
.wire-splitter[data-color="success"] {
--splitter-accent: var(--wire-color-success);
}
.wire-splitter[data-color="danger"] {
--splitter-accent: var(--wire-color-danger);
}
.wire-splitter[data-color="info"] {
--splitter-accent: var(--wire-color-info);
}
.wire-splitter[data-orientation="vertical"] {
grid-template-columns: minmax(0, 1fr);
grid-template-rows: var(--wrn-split, 50%) auto minmax(0, 1fr);
}
.wire-splitter__pane {
min-width: 0;
min-height: 0;
overflow: auto;
}
.wire-splitter__handle {
display: flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
padding: 0 0.25rem;
border: 0;
background: transparent;
cursor: col-resize;
/* Without this the pointer drag selects the text in both panes. */
touch-action: none;
user-select: none;
}
.wire-splitter[data-orientation="vertical"] .wire-splitter__handle {
padding: 0.25rem 0;
cursor: row-resize;
}
.wire-splitter__grip {
display: block;
width: 2px;
height: 100%;
min-height: 1.5rem;
border-radius: 999px;
background: var(--wire-color-border);
transition: background 140ms ease;
}
.wire-splitter[data-orientation="vertical"] .wire-splitter__grip {
width: 100%;
min-width: 1.5rem;
height: 2px;
}
.wire-splitter__handle:hover .wire-splitter__grip,
.wire-splitter[data-wrn-splitter-dragging="true"] .wire-splitter__grip {
background: var(--splitter-accent);
}
.wire-splitter__handle:focus-visible {
outline: 2px solid var(--splitter-accent);
outline-offset: -2px;
border-radius: var(--wire-radius-sm);
}
/*
* Two panes side by side stop making sense on a phone. Stacking them keeps
* both readable, and the divider stops being draggable because the grid
* no longer has a second track to trade against.
*/
@media (max-width: 639px) {
.wire-splitter,
.wire-splitter[data-orientation="vertical"] {
grid-template-columns: minmax(0, 1fr);
grid-template-rows: auto auto auto;
}
.wire-splitter__handle {
cursor: default;
}
}
} }
} }
+114 -24
View File
@@ -1,3 +1,13 @@
// Link -- a text link that follows the theme.
//
// <Link href="/docs" label="Documentation" />
// <Link href="https://example.com" label="Spec" external={true} />
//
// external adds rel="noopener noreferrer" and an outbound icon, so the reader
// is told the link leaves the site before they follow it.
//
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
// and silently swallows the rule that follows it.
component Link { component Link {
props { props {
size: string = "default" size: string = "default"
@@ -6,42 +16,122 @@ component Link {
href: string = "#" href: string = "#"
target: string = "" target: string = ""
rel: string = "" rel: string = ""
underline: string = "hover"
external: boolean = false external: boolean = false
class: string = "" class: string = ""
} }
view { view {
<a <a
{...attrs}
data-ui-component="Link" data-ui-component="Link"
class='wire-link {class}'
href='{href}' href='{href}'
target='{target}' target='{target}'
rel='{external ? (rel || "noopener noreferrer") : rel}' rel='{external ? (rel || "noopener noreferrer") : rel}'
class='inline-flex min-w-0 items-center gap-1.5 rounded-md font-semibold underline-offset-4 outline-none transition-colors focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--wire-color-background)] {class}' data-size='{size}'
class:text-xs='size === "xs"' data-color='{color}'
class:text-sm='size === "sm" || size === "default"' data-underline='{underline}'
class:text-base='size === "md"' data-external='{external}'
class:text-lg='size === "lg"'
class:text-[var(--wire-color-primary)]='color === "primary"'
class:hover:text-[var(--wire-color-primary-hover)]='color === "primary"'
class:text-[var(--wire-color-secondary)]='color === "secondary"'
class:hover:text-[var(--wire-color-secondary-hover)]='color === "secondary"'
class:text-[var(--wire-color-success)]='color === "success"'
class:text-[var(--wire-color-warning-text)]='color === "warning"'
class:text-[var(--wire-color-danger)]='color === "danger"'
class:text-[var(--wire-color-info)]='color === "info"'
class:text-[var(--wire-color-text)]='color === "neutral"'
class:hover:underline='external === false'
> >
<span class="truncate">{label}</span> <span class="wire-link__label">{label}</span>
<span
{#if external} class="icon-[lucide--external-link] wire-link__icon"
<span data-show="external"
class="icon-[lucide--external-link] size-3.5 shrink-0" aria-hidden="true"
aria-hidden="true" ></span>
></span>
{/if}
<slot></slot> <slot></slot>
</a> </a>
} }
style {
.wire-link {
--link-tone: var(--wire-color-primary);
--link-tone-hover: var(--wire-color-primary-hover);
display: inline-flex;
align-items: center;
gap: 0.35rem;
min-width: 0;
border-radius: var(--wire-radius-sm);
color: var(--link-tone);
font-size: 0.875rem;
font-weight: 600;
text-decoration: none;
text-underline-offset: 4px;
transition: color 140ms ease;
}
.wire-link[data-color="secondary"] {
--link-tone: var(--wire-color-secondary);
--link-tone-hover: var(--wire-color-secondary-hover);
}
.wire-link[data-color="success"] {
--link-tone: var(--wire-color-success);
--link-tone-hover: var(--wire-color-success);
}
.wire-link[data-color="warning"] {
--link-tone: var(--wire-color-warning-text);
--link-tone-hover: var(--wire-color-warning-text);
}
.wire-link[data-color="danger"] {
--link-tone: var(--wire-color-danger);
--link-tone-hover: var(--wire-color-danger);
}
.wire-link[data-color="info"] {
--link-tone: var(--wire-color-info);
--link-tone-hover: var(--wire-color-info);
}
.wire-link[data-color="neutral"] {
--link-tone: var(--wire-color-text);
--link-tone-hover: var(--wire-color-text);
}
.wire-link[data-size="xs"] {
font-size: 0.75rem;
}
.wire-link[data-size="md"] {
font-size: 1rem;
}
.wire-link[data-size="lg"] {
font-size: 1.125rem;
}
.wire-link[data-underline="always"] {
text-decoration: underline;
}
.wire-link[data-underline="hover"]:hover {
text-decoration: underline;
}
.wire-link:hover {
color: var(--link-tone-hover);
}
.wire-link:focus-visible {
outline: 2px solid var(--wire-color-focus);
outline-offset: 2px;
}
/* The label truncates; the outbound icon must not shrink with it. */
.wire-link__label {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.wire-link__icon {
flex: 0 0 auto;
width: 0.875rem;
height: 0.875rem;
}
}
} }
+5 -1
View File
@@ -1,4 +1,8 @@
component List { component List {
outputs {
select(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
}
props { props {
size: string = "default" size: string = "default"
color: string = "primary" color: string = "primary"
@@ -52,7 +56,7 @@ component List {
class:py-2.5='size === "sm"' class:py-2.5='size === "sm"'
class:px-5='size === "lg"' class:px-5='size === "lg"'
class:py-4='size === "lg"' class:py-4='size === "lg"'
@click='event.currentTarget.dispatchEvent(new CustomEvent("select", { bubbles: true, detail: { item: item, index: index } }))' @click='output.select({ item: item, index: index })'
> >
{#if item.icon} {#if item.icon}
<span class="flex size-10 shrink-0 items-center justify-center rounded-xl bg-[var(--wire-color-primary-soft)] text-[var(--wire-color-primary)]"> <span class="flex size-10 shrink-0 items-center justify-center rounded-xl bg-[var(--wire-color-primary-soft)] text-[var(--wire-color-primary)]">
+11 -3
View File
@@ -1,4 +1,12 @@
component Map { component Map {
outputs {
// markerClick and select both fire for a marker press; select is the
// generic name callers reach for, markerClick the explicit one.
markerClick(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
select(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
zoom(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
}
props { props {
size: string = "default" size: string = "default"
color: string = "primary" color: string = "primary"
@@ -54,7 +62,7 @@ component Map {
aria-label='{item.label || item.title || "Map marker"}' aria-label='{item.label || item.title || "Map marker"}'
class="absolute inline-flex size-10 items-center justify-center rounded-full border-4 border-[var(--wire-color-surface-raised)] bg-[var(--wire-color-primary)] text-[var(--wire-color-on-primary)] shadow-lg transition hover:scale-110 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]" class="absolute inline-flex size-10 items-center justify-center rounded-full border-4 border-[var(--wire-color-surface-raised)] bg-[var(--wire-color-primary)] text-[var(--wire-color-on-primary)] shadow-lg transition hover:scale-110 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]"
style='left: {item.x || (20 + index * 12)}%; top: {item.y || (30 + (index % 3) * 18)}%;' style='left: {item.x || (20 + index * 12)}%; top: {item.y || (30 + (index % 3) * 18)}%;'
@click='event.currentTarget.dispatchEvent(new CustomEvent("markerClick", { bubbles: true, detail: item })); event.currentTarget.dispatchEvent(new CustomEvent("select", { bubbles: true, detail: item }))' @click='output.markerClick(item); output.select(item)'
> >
<span class='{item.icon || "icon-[lucide--map-pin]"}' aria-hidden="true"></span> <span class='{item.icon || "icon-[lucide--map-pin]"}' aria-hidden="true"></span>
</button> </button>
@@ -66,7 +74,7 @@ component Map {
type="button" type="button"
aria-label="Zoom in" aria-label="Zoom in"
class="inline-flex size-10 items-center justify-center rounded-xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] text-[var(--wire-color-text)] shadow-sm transition hover:bg-[var(--wire-color-surface-soft)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]" class="inline-flex size-10 items-center justify-center rounded-xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] text-[var(--wire-color-text)] shadow-sm transition hover:bg-[var(--wire-color-surface-soft)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]"
@click='event.currentTarget.dispatchEvent(new CustomEvent("zoom", { bubbles: true, detail: { direction: "in" } }))' @click='output.zoom({ direction: "in" })'
> >
<span class="icon-[lucide--plus] size-4" aria-hidden="true"></span> <span class="icon-[lucide--plus] size-4" aria-hidden="true"></span>
</button> </button>
@@ -74,7 +82,7 @@ component Map {
type="button" type="button"
aria-label="Zoom out" aria-label="Zoom out"
class="inline-flex size-10 items-center justify-center rounded-xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] text-[var(--wire-color-text)] shadow-sm transition hover:bg-[var(--wire-color-surface-soft)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]" class="inline-flex size-10 items-center justify-center rounded-xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] text-[var(--wire-color-text)] shadow-sm transition hover:bg-[var(--wire-color-surface-soft)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]"
@click='event.currentTarget.dispatchEvent(new CustomEvent("zoom", { bubbles: true, detail: { direction: "out" } }))' @click='output.zoom({ direction: "out" })'
> >
<span class="icon-[lucide--minus] size-4" aria-hidden="true"></span> <span class="icon-[lucide--minus] size-4" aria-hidden="true"></span>
</button> </button>
+9 -3
View File
@@ -1,4 +1,10 @@
component Marquee { component Marquee {
outputs {
// Fired when the reader pauses the scroll, by hover, focus or the button.
pause(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
resume(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
}
props { props {
size: string = "default" size: string = "default"
color: string = "primary" color: string = "primary"
@@ -27,8 +33,8 @@ component Marquee {
<div <div
class="group relative min-w-0 flex-1 overflow-hidden" class="group relative min-w-0 flex-1 overflow-hidden"
@mouseenter='paused = true; event.currentTarget.dispatchEvent(new CustomEvent("pause", { bubbles: true }))' @mouseenter='paused = true; output.pause({})'
@mouseleave='paused = false; event.currentTarget.dispatchEvent(new CustomEvent("resume", { bubbles: true }))' @mouseleave='paused = false; output.resume({})'
@focusin='paused = true' @focusin='paused = true'
@focusout='paused = false' @focusout='paused = false'
> >
@@ -77,7 +83,7 @@ component Marquee {
type="button" type="button"
aria-label='{paused ? "Resume announcements" : "Pause announcements"}' aria-label='{paused ? "Resume announcements" : "Pause announcements"}'
class="flex shrink-0 items-center justify-center border-l border-[var(--wire-color-border)] px-4 text-[var(--wire-color-text-muted)] transition hover:bg-[var(--wire-color-surface-soft)] hover:text-[var(--wire-color-text)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--wire-color-focus)]" class="flex shrink-0 items-center justify-center border-l border-[var(--wire-color-border)] px-4 text-[var(--wire-color-text-muted)] transition hover:bg-[var(--wire-color-surface-soft)] hover:text-[var(--wire-color-text)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--wire-color-focus)]"
@click='paused = !paused; event.currentTarget.dispatchEvent(new CustomEvent(paused ? "pause" : "resume", { bubbles: true }))' @click='paused = !paused; paused ? output.pause({}) : output.resume({})'
> >
<span class="icon-[lucide--pause] size-4" data-show='!paused' aria-hidden="true"></span> <span class="icon-[lucide--pause] size-4" data-show='!paused' aria-hidden="true"></span>
<span class="icon-[lucide--play] size-4" data-show='paused' aria-hidden="true"></span> <span class="icon-[lucide--play] size-4" data-show='paused' aria-hidden="true"></span>
+333 -12
View File
@@ -1,23 +1,344 @@
// MegaMenu -- a trigger and a wide panel of grouped links.
//
// <MegaMenu label="Products" columns='[{"heading":"Platform",
// "items":[{"label":"Runtime","href":"/runtime"}]}]' />
//
// Deliberately one level deep. A mega menu exists to show breadth flat, so
// everything is one click away; nesting inside the panel buries content behind
// hover-within-hover and is close to unusable by keyboard and touch. Use Nav
// when you actually want cascading submenus.
//
// The panel carries data-wrn-anchored so the runtime clamp keeps it inside the
// viewport instead of hanging off the edge of a wide layout.
//
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
// and silently swallows the rule that follows it.
component MegaMenu { component MegaMenu {
outputs { outputs {
open(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object }) open(payload: { sourceEvent: Event })
close(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object }) close(payload: { reason: string })
select(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null) select(payload: { item: object; value: string })
} }
props { props {
size: string = "default"
color: string = "primary" color: string = "primary"
label: string = "Mega Menu" size: string = "default"
items: unknown[] = [] label: string = "Menu"
active: string = "" icon: string = ""
orientation: string = "horizontal" columns: unknown[] = []
footer: string = ""
defaultOpen: boolean = false
class: string = "" class: string = ""
} }
state visible = defaultOpen
functions {
shared function columnList() {
return Array.isArray(columns) ? columns : []
}
shared function itemsOf(column) {
return column && Array.isArray(column.items) ? column.items : []
}
client function showPanel(sourceEvent) {
visible = true
output.open({ sourceEvent: sourceEvent })
}
client function hidePanel(reason) {
visible = false
output.close({ reason: reason })
}
client function togglePanel(sourceEvent) {
if (visible) {
hidePanel("toggle")
} else {
showPanel(sourceEvent)
}
}
client function handleKeydown(sourceEvent) {
if (sourceEvent.key === "Escape" && visible) {
sourceEvent.preventDefault()
hidePanel("escape")
}
}
client function choose(item) {
if (item.disabled) {
return
}
output.select({ item: item, value: item.value || item.label || "" })
hidePanel("select")
}
}
view { view {
<nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--mega-menu wire-next--{orientation} {class}" aria-label="{label}"> <div
{#each items as item}<a href="{item.href}" aria-current="{item.value === active ? 'page' : ''}">{item.label}</a>{/each} {...attrs}
<slot /> data-ui-component="MegaMenu"
</nav> class='wire-mega {class}'
data-color='{color}'
data-size='{size}'
data-open='{visible}'
@keydown='handleKeydown(event)'
@mouseleave='hidePanel("pointer-leave")'
>
<button
type="button"
class="wire-mega__trigger"
aria-haspopup="true"
aria-expanded='{visible}'
@click='togglePanel(event)'
@mouseenter='showPanel(event)'
@focus='showPanel(event)'
>
<span class='wire-mega__trigger-icon {icon}' data-show="icon" aria-hidden="true"></span>
<span class="wire-mega__trigger-label">{label}</span>
<span class="wire-mega__arrow" aria-hidden="true">&#8250;</span>
</button>
<div
class="wire-mega__panel"
data-wrn-anchored="true"
data-show="visible"
data-wrn-roving="both"
>
<div class="wire-mega__columns">
{#each columnList() as column}
<section class="wire-mega__column">
<h3 class="wire-mega__heading" data-show="column.heading">{column.heading}</h3>
<ul class="wire-mega__list">
{#each itemsOf(column) as item}
<li>
<a
class="wire-mega__link"
href='{item.href || "#"}'
data-wrn-roving-item="true"
aria-disabled='{item.disabled ? "true" : "false"}'
@click='choose(item)'
>
<span
class='wire-mega__link-icon {item.icon}'
data-show="item.icon"
aria-hidden="true"
></span>
<span class="wire-mega__link-body">
<span class="wire-mega__link-label">{item.label}</span>
<span class="wire-mega__link-description" data-show="item.description">
{item.description}
</span>
</span>
</a>
</li>
{/each}
</ul>
</section>
{/each}
</div>
<p class="wire-mega__footer" data-show="footer">{footer}</p>
<slot />
</div>
</div>
}
style {
.wire-mega {
--mega-accent: var(--wire-color-primary);
position: relative;
display: inline-block;
max-width: 100%;
}
.wire-mega[data-color="secondary"] {
--mega-accent: var(--wire-color-secondary);
}
.wire-mega[data-color="success"] {
--mega-accent: var(--wire-color-success);
}
.wire-mega[data-color="danger"] {
--mega-accent: var(--wire-color-danger);
}
.wire-mega[data-color="info"] {
--mega-accent: var(--wire-color-info);
}
.wire-mega[data-size="sm"] {
font-size: 0.82rem;
}
.wire-mega[data-size="lg"] {
font-size: 1rem;
}
.wire-mega__trigger {
appearance: none;
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.45rem 0.7rem;
border: 1px solid transparent;
border-radius: var(--wire-radius-sm);
background: transparent;
color: var(--wire-color-text-muted);
font: inherit;
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
}
.wire-mega__trigger:hover,
.wire-mega[data-open="true"] .wire-mega__trigger {
background: var(--wire-color-surface-soft);
color: var(--wire-color-text);
}
.wire-mega__trigger:focus-visible {
outline: 2px solid var(--mega-accent);
outline-offset: 2px;
}
.wire-mega__arrow {
display: inline-block;
transition: transform 160ms ease;
}
.wire-mega[data-open="true"] .wire-mega__arrow {
transform: rotate(90deg);
}
/*
* The panel is offset below the trigger for looks, which used to close the
* menu on the way to it: that offset is dead space belonging to neither
* element, so crossing it fired mouseleave on the root. The bridge below
* covers the gap with a descendant, so the pointer never actually leaves.
*/
.wire-mega__panel::before {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: 100%;
height: 0.6rem;
}
.wire-mega__panel {
position: absolute;
z-index: 40;
top: calc(100% + 0.4rem);
left: 0;
width: max-content;
max-width: min(60rem, calc(100vw - 2rem));
padding: 1rem;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-lg);
background: var(--wire-color-surface);
box-shadow: var(--wire-shadow-1);
}
.wire-mega__columns {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
gap: 1.25rem;
}
.wire-mega__column {
min-width: 0;
}
.wire-mega__heading {
margin: 0 0 0.5rem;
color: var(--wire-color-text-muted);
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.wire-mega__list {
display: grid;
gap: 0.15rem;
margin: 0;
padding: 0;
list-style: none;
}
.wire-mega__link {
display: flex;
align-items: flex-start;
gap: 0.5rem;
padding: 0.45rem 0.5rem;
border-radius: var(--wire-radius-sm);
color: var(--wire-color-text);
text-decoration: none;
}
.wire-mega__link:hover {
background: var(--wire-color-surface-soft);
}
.wire-mega__link:focus-visible {
outline: 2px solid var(--mega-accent);
outline-offset: 2px;
}
.wire-mega__link[aria-disabled="true"] {
opacity: 0.5;
pointer-events: none;
}
.wire-mega__link-body {
display: flex;
flex-direction: column;
min-width: 0;
}
.wire-mega__link-label {
font-size: 0.88rem;
font-weight: 600;
}
.wire-mega__link-description {
color: var(--wire-color-text-muted);
font-size: 0.78rem;
line-height: 1.5;
}
.wire-mega__footer {
margin: 1rem 0 0;
padding-top: 0.75rem;
border-top: 1px solid var(--wire-color-border);
color: var(--wire-color-text-muted);
font-size: 0.8rem;
}
/*
* On a phone the panel stops floating and becomes part of the flow. A
* pointer-opened overlay pinned to a trigger is unreachable on touch, and
* a 60rem grid has nowhere to go on a 360px screen.
*/
@media (max-width: 767px) {
.wire-mega {
display: block;
}
.wire-mega__panel {
position: static;
width: auto;
max-width: 100%;
box-shadow: none;
}
.wire-mega__columns {
grid-template-columns: 1fr;
}
}
} }
} }
+338 -6
View File
@@ -1,22 +1,354 @@
// Nav -- a navigation link list, flat or with submenus.
//
// <Nav items='[{"label":"Home","href":"/","value":"home"}]' active="home" />
//
// An item carrying its own items array becomes a submenu. Depth is capped at
// three levels: this template language has no component recursion, so each
// level is written out, and three covers any realistic navigation.
//
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
// and silently swallows the rule that follows it.
component Nav { component Nav {
outputs { outputs {
select(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null) select(payload: { item: object; value: string })
change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
} }
props { props {
size: string = "default"
color: string = "primary" color: string = "primary"
label: string = "Nav" size: string = "default"
items: unknown[] = [] items: unknown[] = []
active: string = "" active: string = ""
orientation: string = "horizontal" orientation: string = "horizontal"
label: string = "Main"
collapsible: boolean = true
toggleLabel: string = "Menu"
class: string = "" class: string = ""
} }
state expanded = false
functions {
shared function itemList() {
return Array.isArray(items) ? items : []
}
shared function childrenOf(item) {
return item && Array.isArray(item.items) ? item.items : []
}
shared function isActive(item) {
return Boolean(item.value) && item.value === active
}
shared function rovingAxis() {
return orientation === "vertical" ? "vertical" : "horizontal"
}
client function choose(item) {
if (item.disabled) {
return
}
output.select({ item: item, value: item.value || "" })
}
client function toggleMenu() {
expanded = !expanded
}
}
view { view {
<nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--nav wire-next--{orientation} {class}" aria-label="{label}"> <nav
{#each items as item}<a href="{item.href}" aria-current="{item.value === active ? 'page' : ''}">{item.label}</a>{/each} {...attrs}
data-ui-component="Nav"
class='wire-nav {class}'
data-orientation='{orientation}'
data-color='{color}'
data-size='{size}'
data-expanded='{expanded}'
role="navigation"
aria-label='{label}'
>
<button
type="button"
class="wire-nav__toggle"
data-show="collapsible"
aria-expanded='{expanded}'
aria-label='{toggleLabel}'
@click='toggleMenu()'
>
<span class="wire-nav__toggle-bar" aria-hidden="true"></span>
<span class="wire-nav__toggle-text">{toggleLabel}</span>
</button>
<ul class="wire-nav__list" data-wrn-roving='{rovingAxis()}'>
{#each itemList() as item}
<li class="wire-nav__item" data-has-children='{childrenOf(item).length > 0}'>
<a
class="wire-nav__link"
href='{item.href || "#"}'
data-wrn-roving-item="true"
data-active='{isActive(item)}'
aria-current='{isActive(item) ? "page" : "false"}'
aria-disabled='{item.disabled ? "true" : "false"}'
@click='choose(item)'
>
<span
class='wire-nav__icon {item.icon}'
data-show="item.icon"
aria-hidden="true"
></span>
<span class="wire-nav__label">{item.label}</span>
<span class="wire-nav__badge" data-show="item.badge">{item.badge}</span>
<span
class="wire-nav__arrow"
data-show="childrenOf(item).length > 0"
aria-hidden="true"
>&#8250;</span>
</a>
<ul
class="wire-nav__submenu"
data-wrn-anchored="true"
data-show="childrenOf(item).length > 0"
>
{#each childrenOf(item) as child}
<li class="wire-nav__item" data-has-children='{childrenOf(child).length > 0}'>
<a
class="wire-nav__link"
href='{child.href || "#"}'
data-active='{isActive(child)}'
aria-current='{isActive(child) ? "page" : "false"}'
aria-disabled='{child.disabled ? "true" : "false"}'
@click='choose(child)'
>
<span
class='wire-nav__icon {child.icon}'
data-show="child.icon"
aria-hidden="true"
></span>
<span class="wire-nav__label">{child.label}</span>
<span class="wire-nav__badge" data-show="child.badge">{child.badge}</span>
<span
class="wire-nav__arrow"
data-show="childrenOf(child).length > 0"
aria-hidden="true"
>&#8250;</span>
</a>
<ul
class="wire-nav__submenu wire-nav__submenu--level3"
data-wrn-anchored="true"
data-show="childrenOf(child).length > 0"
>
{#each childrenOf(child) as leaf}
<li class="wire-nav__item">
<a
class="wire-nav__link"
href='{leaf.href || "#"}'
data-active='{isActive(leaf)}'
aria-current='{isActive(leaf) ? "page" : "false"}'
aria-disabled='{leaf.disabled ? "true" : "false"}'
@click='choose(leaf)'
>
<span
class='wire-nav__icon {leaf.icon}'
data-show="leaf.icon"
aria-hidden="true"
></span>
<span class="wire-nav__label">{leaf.label}</span>
</a>
</li>
{/each}
</ul>
</li>
{/each}
</ul>
</li>
{/each}
</ul>
<slot /> <slot />
</nav> </nav>
} }
style {
.wire-nav {
--nav-accent: var(--wire-color-primary);
max-width: 100%;
}
.wire-nav[data-color="secondary"] {
--nav-accent: var(--wire-color-secondary);
}
.wire-nav[data-color="success"] {
--nav-accent: var(--wire-color-success);
}
.wire-nav[data-color="danger"] {
--nav-accent: var(--wire-color-danger);
}
.wire-nav[data-color="info"] {
--nav-accent: var(--wire-color-info);
}
.wire-nav[data-size="sm"] {
font-size: 0.82rem;
}
.wire-nav[data-size="lg"] {
font-size: 1rem;
}
.wire-nav__list {
display: flex;
align-items: center;
gap: 0.25rem;
margin: 0;
padding: 0;
list-style: none;
}
.wire-nav[data-orientation="vertical"] .wire-nav__list {
flex-direction: column;
align-items: stretch;
}
.wire-nav__item {
position: relative;
}
.wire-nav__link {
display: flex;
align-items: center;
gap: 0.45rem;
padding: 0.45rem 0.7rem;
border-radius: var(--wire-radius-sm);
color: var(--wire-color-text-muted);
font-size: 0.9rem;
font-weight: 600;
text-decoration: none;
}
.wire-nav__link:hover {
background: var(--wire-color-surface-soft);
color: var(--wire-color-text);
}
.wire-nav__link:focus-visible {
outline: 2px solid var(--nav-accent);
outline-offset: 2px;
}
.wire-nav__link[data-active="true"] {
background: color-mix(in srgb, var(--nav-accent) 16%, transparent);
color: var(--nav-accent);
}
.wire-nav__link[aria-disabled="true"] {
opacity: 0.5;
pointer-events: none;
}
.wire-nav__badge {
padding: 0.05rem 0.4rem;
border-radius: 999px;
background: color-mix(in srgb, var(--nav-accent) 16%, transparent);
color: var(--nav-accent);
font-size: 0.72rem;
}
.wire-nav__arrow {
display: inline-block;
transition: transform 160ms ease;
}
.wire-nav__item:hover > .wire-nav__link > .wire-nav__arrow,
.wire-nav__item:focus-within > .wire-nav__link > .wire-nav__arrow {
transform: rotate(90deg);
}
.wire-nav__submenu {
position: absolute;
z-index: 30;
top: 100%;
left: 0;
min-width: 12rem;
margin: 0;
padding: 0.35rem;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-md);
background: var(--wire-color-surface);
box-shadow: var(--wire-shadow-1);
list-style: none;
opacity: 0;
visibility: hidden;
transition: opacity 160ms ease;
}
.wire-nav__item:hover > .wire-nav__submenu,
.wire-nav__item:focus-within > .wire-nav__submenu {
opacity: 1;
visibility: visible;
}
.wire-nav__submenu--level3 {
top: 0;
left: 100%;
}
.wire-nav__toggle {
display: none;
align-items: center;
gap: 0.5rem;
padding: 0.45rem 0.7rem;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-sm);
background: var(--wire-color-surface);
color: var(--wire-color-text);
font: inherit;
font-size: 0.9rem;
cursor: pointer;
}
.wire-nav__toggle-bar {
width: 1rem;
height: 2px;
background: currentColor;
box-shadow:
0 -5px 0 currentColor,
0 5px 0 currentColor;
}
/*
* On a phone the bar becomes a disclosure: submenus stop being floating
* overlays and stack inline, because a hover-opened overlay is
* unreachable on touch.
*/
@media (max-width: 767px) {
.wire-nav__toggle {
display: inline-flex;
}
.wire-nav__list {
flex-direction: column;
align-items: stretch;
}
.wire-nav[data-expanded="false"] .wire-nav__list {
display: none;
}
.wire-nav__submenu,
.wire-nav__submenu--level3 {
position: static;
opacity: 1;
visibility: visible;
border: 0;
box-shadow: none;
padding-left: 1rem;
}
}
}
} }
+441 -5
View File
@@ -86,11 +86,11 @@ size: string = "default"
</button> </button>
<div class="wire-navbar__collapse {mobileOpen ? 'is-open' : ''}"> <div class="wire-navbar__collapse {mobileOpen ? 'is-open' : ''}">
<nav class="wire-navbar__menus" aria-label="{label}"> <nav class="wire-navbar__menus" aria-label="{label}" data-wrn-roving="horizontal">
{#each items as item} {#each items as item}
{#if item.children && item.children.length} {#if item.children && item.children.length}
<details class="wire-navbar__dropdown wire-navbar__dropdown--{item.type || 'dropdown'}" name="wire-navbar-menu" @toggle="toggleDropdown(event, item)"> <details class="wire-navbar__dropdown wire-navbar__dropdown--{item.type || 'dropdown'}" name="wire-navbar-menu" @toggle="toggleDropdown(event, item)">
<summary aria-current="{isItemActive(item) ? 'page' : ''}"> <summary data-wrn-roving-item="true" aria-current="{isItemActive(item) ? 'page' : 'false'}">
{#if item.icon}<span class="{item.icon}" aria-hidden="true"></span>{/if} {#if item.icon}<span class="{item.icon}" aria-hidden="true"></span>{/if}
<span>{item.label}</span> <span>{item.label}</span>
<span class="wire-navbar__chevron" aria-hidden="true"></span> <span class="wire-navbar__chevron" aria-hidden="true"></span>
@@ -103,13 +103,13 @@ size: string = "default"
{#if child.label}<strong class="wire-navbar__group-title">{child.label}</strong>{/if} {#if child.label}<strong class="wire-navbar__group-title">{child.label}</strong>{/if}
{#if child.description}<small>{child.description}</small>{/if} {#if child.description}<small>{child.description}</small>{/if}
{#each child.children as nested} {#each child.children as nested}
<a href="{nested.href || '#'}" target="{nested.target || ''}" rel="{nested.rel || ''}" aria-current="{nested.value === active ? 'page' : ''}" @click="selectItem(nested, 3)"> <a href="{nested.href || '#'}" target="{nested.target || ''}" rel="{nested.rel || ''}" aria-current="{nested.value === active ? 'page' : 'false'}" @click="selectItem(nested, 3)">
{#if nested.icon}<span class="{nested.icon}" aria-hidden="true"></span>{/if} {#if nested.icon}<span class="{nested.icon}" aria-hidden="true"></span>{/if}
<span><strong>{nested.label}</strong>{#if nested.description}<small>{nested.description}</small>{/if}</span> <span><strong>{nested.label}</strong>{#if nested.description}<small>{nested.description}</small>{/if}</span>
</a> </a>
{/each} {/each}
{:else} {:else}
<a href="{child.href || '#'}" target="{child.target || ''}" rel="{child.rel || ''}" aria-current="{child.value === active ? 'page' : ''}" @click="selectItem(child, 2)"> <a href="{child.href || '#'}" target="{child.target || ''}" rel="{child.rel || ''}" aria-current="{child.value === active ? 'page' : 'false'}" @click="selectItem(child, 2)">
{#if child.icon}<span class="{child.icon}" aria-hidden="true"></span>{/if} {#if child.icon}<span class="{child.icon}" aria-hidden="true"></span>{/if}
<span><strong>{child.label}</strong>{#if child.description}<small>{child.description}</small>{/if}</span> <span><strong>{child.label}</strong>{#if child.description}<small>{child.description}</small>{/if}</span>
</a> </a>
@@ -119,7 +119,7 @@ size: string = "default"
</div> </div>
</details> </details>
{:else} {:else}
<a class="wire-navbar__menu-link" href="{item.href || '#'}" target="{item.target || ''}" rel="{item.rel || ''}" aria-current="{item.value === active ? 'page' : ''}" @click="selectItem(item, 1)"> <a class="wire-navbar__menu-link" data-wrn-roving-item="true" href="{item.href || '#'}" target="{item.target || ''}" rel="{item.rel || ''}" aria-current="{item.value === active ? 'page' : 'false'}" @click="selectItem(item, 1)">
{#if item.icon}<span class="{item.icon}" aria-hidden="true"></span>{/if} {#if item.icon}<span class="{item.icon}" aria-hidden="true"></span>{/if}
<span>{item.label}</span> <span>{item.label}</span>
</a> </a>
@@ -140,4 +140,440 @@ size: string = "default"
</div> </div>
</header> </header>
} }
style {
/* Full application navigation */
.wire-navbar {
position: relative;
z-index: 40;
width: 100%;
color: var(--wire-color-text);
background: color-mix(in srgb, var(--wire-color-surface) 96%, transparent);
border-bottom: 1px solid var(--wire-color-border);
}
.wire-navbar--sticky {
position: sticky;
top: 0;
backdrop-filter: blur(16px);
}
.wire-navbar__topbar:empty,
.wire-navbar__topbar:not(:has(*)) {
display: none;
}
.wire-navbar__topbar:not(:empty) {
display: flex;
min-height: 2.25rem;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 0.35rem clamp(1rem, 4vw, 3rem);
color: var(--wire-color-muted);
background: var(--wire-color-surface-2);
border-bottom: 1px solid var(--wire-color-border);
font-size: 0.75rem;
font-weight: 400;
}
.wire-navbar__main {
display: flex;
min-height: 4.25rem;
width: min(100%, 90rem);
margin-inline: auto;
padding: 0.75rem clamp(1rem, 4vw, 3rem);
align-items: center;
gap: 1.5rem;
}
.wire-navbar--width-full .wire-navbar__main {
width: 100%;
max-width: none;
}
.wire-navbar--width-compact .wire-navbar__main {
width: min(86%, 80rem);
}
.wire-navbar__brand,
.wire-navbar__menu-link,
.wire-navbar__dropdown summary,
.wire-navbar__action,
.wire-navbar__panel a {
color: inherit;
text-decoration: none;
}
.wire-navbar__brand {
display: inline-flex;
min-width: 0;
align-items: center;
gap: 0.75rem;
flex: 0 0 auto;
}
.wire-navbar__brand-logo {
display: block;
width: auto;
max-width: 11rem;
height: 2.5rem;
object-fit: contain;
}
.wire-navbar__brand-icon {
width: 2.5rem;
height: 2.5rem;
color: var(--wire-component-color, var(--wire-color-primary));
}
.wire-navbar__brand-copy {
display: grid;
min-width: 0;
line-height: 1.2;
}
.wire-navbar__brand-copy strong {
font-size: 0.9rem;
font-weight: 600;
}
.wire-navbar__brand-copy small {
margin-top: 0.2rem;
color: var(--wire-color-muted);
font-size: 0.6875rem;
font-weight: 400;
}
.wire-navbar__collapse,
.wire-navbar__menus,
.wire-navbar__actions {
display: flex;
align-items: center;
}
.wire-navbar__collapse {
min-width: 0;
flex: 1;
gap: 1rem;
}
.wire-navbar__menus {
justify-content: center;
flex: 1;
gap: 0.25rem;
}
.wire-navbar__actions {
justify-content: flex-end;
gap: 0.5rem;
}
.wire-navbar__menu-link,
.wire-navbar__dropdown > summary,
.wire-navbar__action {
display: inline-flex;
min-height: 2.75rem;
padding: 0.65rem 0.8rem;
align-items: center;
gap: 0.45rem;
border-radius: 0.65rem;
cursor: pointer;
font-size: 0.8125rem;
font-weight: 500;
white-space: nowrap;
}
.wire-navbar__menu-link:hover,
.wire-navbar__dropdown > summary:hover {
color: var(--wire-color-text);
background: var(--wire-color-surface-2);
}
.wire-navbar__menu-link[aria-current="page"],
.wire-navbar__dropdown > summary[aria-current="page"] {
color: var(--wire-component-color, var(--wire-color-primary));
font-weight: 600;
background: color-mix(
in srgb,
var(--wire-component-color, var(--wire-color-primary)) 12%,
transparent
);
box-shadow: inset 0 -2px 0 var(--wire-component-color, var(--wire-color-primary));
}
.wire-navbar__menu-link[aria-current="page"]:hover,
.wire-navbar__dropdown > summary[aria-current="page"]:hover {
color: var(--wire-component-color, var(--wire-color-primary));
background: color-mix(
in srgb,
var(--wire-component-color, var(--wire-color-primary)) 16%,
transparent
);
}
.wire-navbar__dropdown {
position: relative;
}
.wire-navbar__dropdown > summary {
list-style: none;
}
.wire-navbar__dropdown > summary::-webkit-details-marker {
display: none;
}
.wire-navbar__chevron {
width: 0.45rem;
height: 0.45rem;
border-right: 1.5px solid currentColor;
border-bottom: 1.5px solid currentColor;
transform: rotate(45deg) translateY(-0.15rem);
transition: transform var(--wire-motion-fast, 150ms) ease;
}
.wire-navbar__dropdown[open] .wire-navbar__chevron {
transform: rotate(225deg) translate(-0.1rem, -0.1rem);
}
.wire-navbar__panel {
position: absolute;
top: calc(100% + 0.55rem);
left: 50%;
display: grid;
width: max-content;
min-width: 15rem;
max-width: min(90vw, 64rem);
padding: 0.65rem;
gap: 0.45rem;
border: 1px solid var(--wire-color-border);
border-radius: 0.9rem;
color: var(--wire-color-text);
background: var(--wire-color-surface);
box-shadow: 0 18px 48px rgb(15 23 42 / 0.16);
opacity: 1;
transform: translateX(-50%) translateY(0) scale(1);
transform-origin: top center;
transition:
opacity 180ms ease,
transform 180ms cubic-bezier(0.22, 1, 0.36, 1),
display 180ms allow-discrete;
}
.wire-navbar__panel::before {
position: absolute;
right: 0;
bottom: 100%;
left: 0;
height: 0.65rem;
content: "";
}
.wire-navbar__dropdown:not([open]) > .wire-navbar__panel {
display: none;
opacity: 0;
transform: translateX(-50%) translateY(-0.5rem) scale(0.98);
}
@starting-style {
.wire-navbar__dropdown[open] > .wire-navbar__panel {
opacity: 0;
transform: translateX(-50%) translateY(-0.5rem) scale(0.98);
}
}
.wire-navbar__dropdown--mega {
position: static;
}
.wire-navbar__dropdown--mega .wire-navbar__panel {
right: auto;
left: 50%;
width: min(calc(100vw - 2rem), 64rem);
}
.wire-navbar__panel--columns-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.wire-navbar__panel--columns-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.wire-navbar__panel--columns-4 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.wire-navbar__panel-intro {
grid-column: 1 / -1;
margin: 0;
padding: 0.5rem 0.65rem;
color: var(--wire-color-muted);
font-size: 0.8125rem;
font-weight: 400;
}
.wire-navbar__group {
display: grid;
align-content: start;
gap: 0.25rem;
}
.wire-navbar__group-title {
padding: 0.55rem 0.65rem 0.25rem;
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.wire-navbar__group > small {
padding: 0 0.65rem 0.4rem;
color: var(--wire-color-muted);
font-size: 0.7rem;
font-weight: 400;
}
.wire-navbar__panel a {
display: flex;
min-height: 2.75rem;
padding: 0.65rem;
align-items: flex-start;
gap: 0.65rem;
border-radius: 0.6rem;
font-size: 0.8rem;
font-weight: 450;
}
.wire-navbar__panel a:hover {
background: var(--wire-color-surface-2);
}
.wire-navbar__panel a span:last-child {
display: grid;
gap: 0.15rem;
}
.wire-navbar__panel a strong {
font-weight: 500;
}
.wire-navbar__panel a small {
color: var(--wire-color-muted);
font-size: 0.7rem;
font-weight: 400;
}
.wire-navbar__action {
border: 1px solid transparent;
}
.wire-navbar__action--primary {
color: var(--wire-color-on-primary, #fff);
background: var(--wire-component-color, var(--wire-color-primary));
font-weight: 600;
}
.wire-navbar__action--outline {
border-color: var(--wire-color-border);
}
.wire-navbar__toggle {
display: none;
width: 2.75rem;
height: 2.75rem;
margin-left: auto;
padding: 0.65rem;
border: 1px solid var(--wire-color-border);
border-radius: 0.65rem;
color: inherit;
background: var(--wire-color-surface);
}
.wire-navbar__toggle span {
display: block;
height: 2px;
margin: 0.25rem 0;
background: currentColor;
}
@media (max-width: 900px) {
.wire-navbar--width-compact .wire-navbar__main {
width: 100%;
}
.wire-navbar__toggle {
display: block;
}
.wire-navbar__collapse {
position: absolute;
top: 100%;
right: 0;
left: 0;
display: none;
padding: 0.75rem 1rem 1rem;
align-items: stretch;
flex-direction: column;
background: var(--wire-color-surface);
border-bottom: 1px solid var(--wire-color-border);
box-shadow: 0 16px 32px rgb(15 23 42 / 0.12);
max-height: calc(100dvh - 4rem);
overflow-x: hidden;
overflow-y: auto;
overscroll-behavior: contain;
}
.wire-navbar__collapse.is-open {
display: flex;
}
.wire-navbar__menus,
.wire-navbar__actions {
width: 100%;
align-items: stretch;
flex-direction: column;
}
.wire-navbar__menu-link,
.wire-navbar__dropdown > summary,
.wire-navbar__action {
width: 100%;
}
.wire-navbar__panel,
.wire-navbar__dropdown--mega .wire-navbar__panel {
position: static;
width: 100%;
max-width: none;
margin-top: 0.25rem;
box-shadow: none;
transform: none;
overflow: hidden;
animation: wire-navbar-mobile-panel 180ms ease both;
}
.wire-navbar__dropdown:not([open]) > .wire-navbar__panel,
.wire-navbar__dropdown[open] > .wire-navbar__panel {
transform: none;
}
.wire-navbar__panel[class*="wire-navbar__panel--columns-"] {
grid-template-columns: 1fr;
}
.wire-navbar__panel::before {
display: none;
}
}
@media (max-width: 600px) {
.wire-navbar__brand-copy small {
display: none;
}
}
@keyframes wire-navbar-mobile-panel {
from {
opacity: 0;
transform: translateY(-0.35rem);
}
to {
opacity: 1;
transform: translateY(0);
}
}
}
} }
+277 -9
View File
@@ -1,23 +1,291 @@
// Pagination -- page controls over a known total.
//
// <Pagination page={2} pageSize={10} total={137} variant="numbered" />
//
// The component owns no data. It reports the requested page through its
// change output and lets the caller fetch or slice.
//
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
// and silently swallows the rule that follows it.
component Pagination { component Pagination {
outputs { outputs {
change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null) change(payload: { page: number; pageSize: number })
previous(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object }) previous(payload: { page: number })
next(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object }) next(payload: { page: number })
} }
props { props {
size: string = "default"
color: string = "primary" color: string = "primary"
size: string = "default"
page: number = 1
pageSize: number = 10
total: number = 0
variant: string = "compact"
siblingCount: number = 1
showSummary: boolean = true
label: string = "Pagination" label: string = "Pagination"
items: unknown[] = [] previousLabel: string = "Previous"
active: string = "" nextLabel: string = "Next"
orientation: string = "horizontal"
class: string = "" class: string = ""
} }
functions {
shared function lastPage() {
var size = Number(pageSize) > 0 ? Number(pageSize) : 10
var count = Number(total) > 0 ? Number(total) : 0
return Math.max(1, Math.ceil(count / size))
}
// Out-of-range values arrive routinely: page travels as an HTML attribute
// and callers compute it from data that may have shrunk underneath them.
shared function currentPage() {
var value = Number(page)
if (!value || value < 1) {
return 1
}
return Math.min(value, lastPage())
}
shared function firstShown() {
if (Number(total) < 1) {
return 0
}
return (currentPage() - 1) * (Number(pageSize) || 10) + 1
}
shared function lastShown() {
return Math.min(currentPage() * (Number(pageSize) || 10), Number(total) || 0)
}
shared function pageNumbers() {
var last = lastPage()
var current = currentPage()
var siblings = Math.max(0, Number(siblingCount) || 0)
var start = Math.max(1, current - siblings)
var end = Math.min(last, current + siblings)
var pages = []
if (start > 1) {
pages.push({ value: 1, label: "1", gap: false })
if (start > 2) {
pages.push({ value: 0, label: "...", gap: true })
}
}
for (var index = start; index <= end; index += 1) {
pages.push({ value: index, label: String(index), gap: false })
}
if (end < last) {
if (end < last - 1) {
pages.push({ value: 0, label: "...", gap: true })
}
pages.push({ value: last, label: String(last), gap: false })
}
return pages
}
client function goToPage(target) {
var next = Math.min(Math.max(1, Number(target) || 1), lastPage())
output.change({ page: next, pageSize: Number(pageSize) || 10 })
}
client function goPrevious() {
var target = Math.max(1, currentPage() - 1)
output.previous({ page: target })
output.change({ page: target, pageSize: Number(pageSize) || 10 })
}
client function goNext() {
var target = Math.min(lastPage(), currentPage() + 1)
output.next({ page: target })
output.change({ page: target, pageSize: Number(pageSize) || 10 })
}
}
view { view {
<nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--pagination wire-next--{orientation} {class}" aria-label="{label}"> <nav
{#each items as item}<a href="{item.href}" aria-current="{item.value === active ? 'page' : ''}">{item.label}</a>{/each} {...attrs}
data-ui-component="Pagination"
class='wire-pagination {class}'
data-variant='{variant}'
data-color='{color}'
data-size='{size}'
role="navigation"
aria-label='{label}'
>
<p class="wire-pagination__summary" data-show="showSummary">
{firstShown()} to {lastShown()} of {total}
</p>
<div class="wire-pagination__controls">
<button
type="button"
class="wire-pagination__step"
aria-label='{previousLabel}'
@click='goPrevious()'
>
<span class="wire-pagination__step-icon" aria-hidden="true">&#8249;</span>
<span class="wire-pagination__step-label">{previousLabel}</span>
</button>
<ol class="wire-pagination__pages" data-show="variant === 'numbered'">
{#each pageNumbers() as entry}
<li class="wire-pagination__slot">
<span class="wire-pagination__gap" data-show="entry.gap">{entry.label}</span>
<button
type="button"
class="wire-pagination__page"
data-show="!entry.gap"
data-active='{entry.value === currentPage()}'
aria-current='{entry.value === currentPage() ? "page" : "false"}'
@click='goToPage(entry.value)'
>
{entry.label}
</button>
</li>
{/each}
</ol>
<p class="wire-pagination__compact" data-show="variant !== 'numbered'">
{currentPage()} / {lastPage()}
</p>
<button
type="button"
class="wire-pagination__step"
aria-label='{nextLabel}'
@click='goNext()'
>
<span class="wire-pagination__step-label">{nextLabel}</span>
<span class="wire-pagination__step-icon" aria-hidden="true">&#8250;</span>
</button>
</div>
<slot /> <slot />
</nav> </nav>
} }
style {
.wire-pagination {
--pagination-accent: var(--wire-color-primary);
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
max-width: 100%;
}
.wire-pagination[data-color="secondary"] {
--pagination-accent: var(--wire-color-secondary);
}
.wire-pagination[data-color="success"] {
--pagination-accent: var(--wire-color-success);
}
.wire-pagination[data-color="danger"] {
--pagination-accent: var(--wire-color-danger);
}
.wire-pagination[data-color="info"] {
--pagination-accent: var(--wire-color-info);
}
.wire-pagination[data-size="sm"] {
font-size: 0.82rem;
}
.wire-pagination[data-size="lg"] {
font-size: 1rem;
}
.wire-pagination__summary {
margin: 0;
color: var(--wire-color-text-muted);
font-size: 0.82rem;
}
.wire-pagination__controls {
display: flex;
align-items: center;
gap: 0.35rem;
flex-wrap: wrap;
}
.wire-pagination__pages {
display: flex;
align-items: center;
gap: 0.25rem;
margin: 0;
padding: 0;
list-style: none;
}
.wire-pagination__slot {
display: inline-flex;
}
.wire-pagination__page,
.wire-pagination__step {
appearance: none;
display: inline-flex;
align-items: center;
gap: 0.35rem;
min-width: 2.25rem;
justify-content: center;
padding: 0.4rem 0.6rem;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-sm);
background: var(--wire-color-surface);
color: var(--wire-color-text);
font: inherit;
font-size: 0.85rem;
cursor: pointer;
}
.wire-pagination__page:hover,
.wire-pagination__step:hover {
background: var(--wire-color-surface-soft);
}
.wire-pagination__page:focus-visible,
.wire-pagination__step:focus-visible {
outline: 2px solid var(--pagination-accent);
outline-offset: 2px;
}
.wire-pagination__page[data-active="true"] {
border-color: var(--pagination-accent);
background: var(--pagination-accent);
color: var(--wire-color-primary-contrast);
font-weight: 700;
}
.wire-pagination__gap {
padding: 0 0.35rem;
color: var(--wire-color-text-muted);
}
.wire-pagination__compact {
margin: 0;
padding: 0 0.5rem;
color: var(--wire-color-text-muted);
font-size: 0.85rem;
}
/* Below the small breakpoint the word labels crowd the arrows out. */
@media (max-width: 639px) {
.wire-pagination {
justify-content: center;
}
.wire-pagination__step-label {
display: none;
}
.wire-pagination__summary {
width: 100%;
text-align: center;
}
}
}
} }
+157 -6
View File
@@ -1,21 +1,172 @@
// Scrollspy -- a table of contents that follows the reader.
//
// <Scrollspy items='[{"label":"Overview","href":"#overview"}]' />
//
// Each href points at an element on the page. The runtime observes those
// elements and moves aria-current to the link for whichever one is in view.
//
// The runtime writes the marker straight onto the links rather than into
// component state. An IntersectionObserver callback fires long after the
// client function that registered it has returned, and a state write made
// there is dropped -- so the DOM is the only place the answer can live.
//
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
// and silently swallows the rule that follows it.
component Scrollspy { component Scrollspy {
outputs { outputs {
change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null) change(payload: { href: string; label: string })
} }
props { props {
size: string = "default"
color: string = "primary" color: string = "primary"
label: string = "Scrollspy" size: string = "default"
items: unknown[] = [] items: unknown[] = []
active: string = "" active: string = ""
orientation: string = "horizontal" label: string = "On this page"
heading: string = ""
class: string = "" class: string = ""
} }
functions {
shared function itemList() {
return Array.isArray(items) ? items : []
}
shared function isActive(item) {
return Boolean(item.href) && item.href === active
}
}
view { view {
<nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--scrollspy wire-next--{orientation} {class}" aria-label="{label}"> <nav
{#each items as item}<a href="{item.href}" aria-current="{item.value === active ? 'page' : ''}">{item.label}</a>{/each} {...attrs}
data-ui-component="Scrollspy"
class='wire-scrollspy {class}'
data-color='{color}'
data-size='{size}'
data-wrn-scrollspy="true"
role="navigation"
aria-label='{label}'
>
<p class="wire-scrollspy__heading" data-show="heading">{heading}</p>
<ul class="wire-scrollspy__list">
{#each itemList() as item}
<li class="wire-scrollspy__item">
<a
class="wire-scrollspy__link"
href='{item.href || "#"}'
data-active='{isActive(item)}'
aria-current='{isActive(item) ? "location" : "false"}'
>
<span class="wire-scrollspy__label">{item.label}</span>
</a>
</li>
{/each}
</ul>
<slot /> <slot />
</nav> </nav>
} }
style {
.wire-scrollspy {
--scrollspy-accent: var(--wire-color-primary);
max-width: 100%;
}
.wire-scrollspy[data-color="secondary"] {
--scrollspy-accent: var(--wire-color-secondary);
}
.wire-scrollspy[data-color="success"] {
--scrollspy-accent: var(--wire-color-success);
}
.wire-scrollspy[data-color="danger"] {
--scrollspy-accent: var(--wire-color-danger);
}
.wire-scrollspy[data-color="info"] {
--scrollspy-accent: var(--wire-color-info);
}
.wire-scrollspy[data-size="sm"] {
font-size: 0.82rem;
}
.wire-scrollspy[data-size="lg"] {
font-size: 1rem;
}
.wire-scrollspy__heading {
margin: 0 0 0.5rem;
color: var(--wire-color-text-muted);
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.wire-scrollspy__list {
display: grid;
gap: 0.1rem;
margin: 0;
padding: 0;
list-style: none;
border-left: 1px solid var(--wire-color-border);
}
.wire-scrollspy__link {
display: block;
padding: 0.3rem 0.75rem;
margin-left: -1px;
border-left: 2px solid transparent;
color: var(--wire-color-text-muted);
font-size: 0.85rem;
text-decoration: none;
}
.wire-scrollspy__link:hover {
color: var(--wire-color-text);
}
.wire-scrollspy__link:focus-visible {
outline: 2px solid var(--scrollspy-accent);
outline-offset: -2px;
}
.wire-scrollspy__link[data-active="true"] {
border-left-color: var(--scrollspy-accent);
color: var(--scrollspy-accent);
font-weight: 600;
}
/*
* A table of contents is a sidebar affordance. On a phone it stops being
* a rail and becomes a horizontal strip that scrolls, so it costs one
* line rather than a screenful.
*/
@media (max-width: 767px) {
.wire-scrollspy__list {
grid-auto-flow: column;
grid-auto-columns: max-content;
overflow-x: auto;
border-left: 0;
border-bottom: 1px solid var(--wire-color-border);
}
.wire-scrollspy__link {
margin-left: 0;
border-left: 0;
border-bottom: 2px solid transparent;
white-space: nowrap;
}
.wire-scrollspy__link[data-active="true"] {
border-left-color: transparent;
border-bottom-color: var(--scrollspy-accent);
}
}
}
} }
+7 -2
View File
@@ -1,4 +1,9 @@
component SearchBox { component SearchBox {
outputs {
search(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
clear(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
}
props { props {
size: string = "default" size: string = "default"
color: string = "primary" color: string = "primary"
@@ -22,7 +27,7 @@ component SearchBox {
data-ui-component="SearchBox" data-ui-component="SearchBox"
role="search" role="search"
class='w-full {class}' class='w-full {class}'
@submit='event.preventDefault(); event.currentTarget.dispatchEvent(new CustomEvent("search", { bubbles: true, detail: { value: query, name: name } }))' @submit='event.preventDefault(); output.search({ value: query, name: name })'
> >
<label <label
class="mb-2 block text-sm font-semibold text-[var(--wire-color-text)]" class="mb-2 block text-sm font-semibold text-[var(--wire-color-text)]"
@@ -61,7 +66,7 @@ component SearchBox {
aria-label="Clear search" aria-label="Clear search"
data-show='query.length > 0 && !disabled' data-show='query.length > 0 && !disabled'
class="absolute right-12 inline-flex size-8 items-center justify-center rounded-lg text-[var(--wire-color-text-muted)] transition hover:bg-[var(--wire-color-surface-soft)] hover:text-[var(--wire-color-text)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]" class="absolute right-12 inline-flex size-8 items-center justify-center rounded-lg text-[var(--wire-color-text-muted)] transition hover:bg-[var(--wire-color-surface-soft)] hover:text-[var(--wire-color-text)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]"
@click='query = ""; event.currentTarget.parentElement.querySelector("input")?.focus(); event.currentTarget.dispatchEvent(new CustomEvent("clear", { bubbles: true, detail: { value: "", name: name } }))' @click='query = ""; event.currentTarget.parentElement.querySelector("input")?.focus(); output.clear({ value: "", name: name })'
> >
<span class="icon-[lucide--x] size-4" aria-hidden="true"></span> <span class="icon-[lucide--x] size-4" aria-hidden="true"></span>
</button> </button>
+302 -49
View File
@@ -1,82 +1,335 @@
// Sidebar -- a vertical navigation rail that becomes a drawer on small screens.
//
// <Sidebar items='[{"label":"Dashboard","href":"/","value":"dash"}]' active="dash" />
//
// An entry is one of three shapes:
// { label, href, value } a single link
// { heading, items: [...] } a labelled group
// { label, items: [...] } a collapsible branch, nested to 3 levels
//
// The off-canvas presentation is Drawer rather than a second implementation,
// so the focus trap and the body scroll lock come from one place.
//
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
// and silently swallows the rule that follows it.
import Drawer from "./Drawer.wrn"
component Sidebar { component Sidebar {
outputs { outputs {
toggle(payload: { open: boolean }) toggle(payload: { open: boolean })
open(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object }) open(payload: { sourceEvent: Event })
close(payload: { source: string }) close(payload: { source: string })
select(payload: { item: string | number | boolean | null | object; value: string | number | boolean; level: string }) select(payload: { item: object; value: string; level: number })
} }
props { props {
size: string = "default"
color: string = "primary" color: string = "primary"
size: string = "default"
label: string = "Sidebar" label: string = "Sidebar"
items: unknown[] = [] items: unknown[] = []
active: string = "" active: string = ""
orientation: string = "horizontal"
mobileLabel: string = "Open navigation" mobileLabel: string = "Open navigation"
drawerTitle: string = "Navigation"
class: string = "" class: string = ""
} }
state mobileOpen: boolean = false state drawerOpen = false
functions { functions {
client function toggleSidebar() { shared function entryList() {
mobileOpen = !mobileOpen return Array.isArray(items) ? items : []
output.toggle({ open: mobileOpen })
output[mobileOpen ? "open" : "close"]({ source: "mobile" })
} }
client function closeSidebar() { // Accepts children as well as items: children is what shipped in 0.8.5
mobileOpen = false // and existing callers should keep working.
output.close({ source: "mobile" }) shared function childrenOf(entry) {
if (!entry) {
return []
}
if (Array.isArray(entry.items)) {
return entry.items
}
return Array.isArray(entry.children) ? entry.children : []
} }
client function selectItem(item, level) { shared function isGroup(entry) {
mobileOpen = false return Boolean(entry.heading)
output.select({ item: item, value: item.value || "", level: level }) }
shared function isActive(entry) {
return Boolean(entry.value) && entry.value === active
}
client function choose(entry, level) {
if (entry.disabled) {
return
}
output.select({ item: entry, value: entry.value || "", level: level })
if (drawerOpen) {
drawerOpen = false
output.close({ source: "select" })
}
}
client function openDrawer(sourceEvent) {
drawerOpen = true
output.open({ sourceEvent: sourceEvent })
output.toggle({ open: true })
}
client function closeDrawer() {
drawerOpen = false
output.close({ source: "drawer" })
output.toggle({ open: false })
} }
} }
view { view {
<div class="wire-sidebar-shell wire-next--color-{color} wire-next--size-{size} {mobileOpen ? 'is-open' : ''} {class}"> <div
<button type="button" class="wire-sidebar-toggle" aria-label="{mobileLabel}" aria-expanded="{mobileOpen}" @click="toggleSidebar()"> {...attrs}
<span class="icon-[lucide--panel-left-open]" aria-hidden="true"></span> data-ui-component="Sidebar"
<span>{label}</span> class='wire-sidebar {class}'
data-color='{color}'
data-size='{size}'
>
<button
type="button"
class="wire-sidebar__launcher"
aria-label='{mobileLabel}'
aria-expanded='{drawerOpen}'
@click='openDrawer(event)'
>
<span class="wire-sidebar__launcher-bar" aria-hidden="true"></span>
<span class="wire-sidebar__launcher-text">{mobileLabel}</span>
</button> </button>
<button type="button" class="wire-sidebar-backdrop" aria-label="Close navigation" @click="closeSidebar()"></button>
<nav class="wire-next wire-next--sidebar wire-next--{orientation} wire-sidebar-panel" aria-label="{label}"> <nav class="wire-sidebar__rail" aria-label='{label}'>
<div class="wire-sidebar-panel__head"> <ul class="wire-sidebar__list" data-wrn-roving="vertical">
<strong>{label}</strong> {#each entryList() as entry}
<button type="button" aria-label="Close navigation" @click="closeSidebar()"><span class="icon-[lucide--x]" aria-hidden="true"></span></button> <li class="wire-sidebar__entry" data-group='{isGroup(entry)}'>
</div> <p class="wire-sidebar__heading" data-show="entry.heading">{entry.heading}</p>
<div class="wire-sidebar-items">
{#each items as item} <a
{#if item.children && item.children.length} class="wire-sidebar__link"
<details class="wire-sidebar-group" name="wire-sidebar-group"> data-show="!entry.heading"
<summary> href='{entry.href || "#"}'
{#if item.icon}<span class="{item.icon}" aria-hidden="true"></span>{/if} data-wrn-roving-item="true"
<span>{item.label}</span> data-active='{isActive(entry)}'
<span class="icon-[lucide--chevron-down]" aria-hidden="true"></span> aria-current='{isActive(entry) ? "page" : "false"}'
</summary> aria-disabled='{entry.disabled ? "true" : "false"}'
<div class="wire-sidebar-group__children"> @click='choose(entry, 1)'
{#each item.children as child} >
<a href="{child.href || '#'}" aria-current="{child.value === active ? 'page' : ''}" @click="selectItem(child, 2)"> <span
{#if child.icon}<span class="{child.icon}" aria-hidden="true"></span>{/if} class='wire-sidebar__icon {entry.icon}'
<span>{child.label}</span> data-show="entry.icon"
</a> aria-hidden="true"
{/each} ></span>
</div> <span class="wire-sidebar__label">{entry.label}</span>
</details> <span class="wire-sidebar__badge" data-show="entry.badge">{entry.badge}</span>
{:else}
<a href="{item.href || '#'}" aria-current="{item.value === active ? 'page' : ''}" @click="selectItem(item, 1)">
{#if item.icon}<span class="{item.icon}" aria-hidden="true"></span>{/if}
<span>{item.label}</span>
</a> </a>
{/if}
<ul class="wire-sidebar__sublist" data-show="childrenOf(entry).length > 0">
{#each childrenOf(entry) as child}
<li class="wire-sidebar__entry">
<a
class="wire-sidebar__link"
href='{child.href || "#"}'
data-active='{isActive(child)}'
aria-current='{isActive(child) ? "page" : "false"}'
aria-disabled='{child.disabled ? "true" : "false"}'
@click='choose(child, 2)'
>
<span
class='wire-sidebar__icon {child.icon}'
data-show="child.icon"
aria-hidden="true"
></span>
<span class="wire-sidebar__label">{child.label}</span>
<span class="wire-sidebar__badge" data-show="child.badge">{child.badge}</span>
</a>
<ul
class="wire-sidebar__sublist wire-sidebar__sublist--level3"
data-show="childrenOf(child).length > 0"
>
{#each childrenOf(child) as leaf}
<li class="wire-sidebar__entry">
<a
class="wire-sidebar__link"
href='{leaf.href || "#"}'
data-active='{isActive(leaf)}'
aria-current='{isActive(leaf) ? "page" : "false"}'
@click='choose(leaf, 3)'
>
<span class="wire-sidebar__label">{leaf.label}</span>
</a>
</li>
{/each}
</ul>
</li>
{/each}
</ul>
</li>
{/each} {/each}
</div> </ul>
<slot /> <slot />
</nav> </nav>
<Drawer
open={drawerOpen}
placement="left"
title={drawerTitle}
label={label}
@close="closeDrawer()"
>
<slot name="drawer"></slot>
</Drawer>
</div> </div>
} }
style {
.wire-sidebar {
--sidebar-accent: var(--wire-color-primary);
max-width: 100%;
}
.wire-sidebar[data-color="secondary"] {
--sidebar-accent: var(--wire-color-secondary);
}
.wire-sidebar[data-color="success"] {
--sidebar-accent: var(--wire-color-success);
}
.wire-sidebar[data-color="danger"] {
--sidebar-accent: var(--wire-color-danger);
}
.wire-sidebar[data-color="info"] {
--sidebar-accent: var(--wire-color-info);
}
.wire-sidebar[data-size="sm"] {
font-size: 0.82rem;
}
.wire-sidebar[data-size="lg"] {
font-size: 1rem;
}
.wire-sidebar__rail {
width: 100%;
}
.wire-sidebar__list,
.wire-sidebar__sublist {
display: grid;
gap: 0.1rem;
margin: 0;
padding: 0;
list-style: none;
}
.wire-sidebar__sublist {
margin-left: 0.85rem;
padding-left: 0.5rem;
border-left: 1px solid var(--wire-color-border);
}
.wire-sidebar__entry[data-group="true"] + .wire-sidebar__entry {
margin-top: 0.35rem;
}
.wire-sidebar__heading {
margin: 0.9rem 0 0.35rem;
color: var(--wire-color-text-muted);
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.wire-sidebar__link {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.45rem 0.6rem;
border-radius: var(--wire-radius-sm);
color: var(--wire-color-text-muted);
font-size: 0.88rem;
font-weight: 600;
text-decoration: none;
}
.wire-sidebar__link:hover {
background: var(--wire-color-surface-soft);
color: var(--wire-color-text);
}
.wire-sidebar__link:focus-visible {
outline: 2px solid var(--sidebar-accent);
outline-offset: 2px;
}
.wire-sidebar__link[data-active="true"] {
background: color-mix(in srgb, var(--sidebar-accent) 16%, transparent);
color: var(--sidebar-accent);
}
.wire-sidebar__link[aria-disabled="true"] {
opacity: 0.5;
pointer-events: none;
}
.wire-sidebar__label {
min-width: 0;
}
.wire-sidebar__badge {
margin-left: auto;
padding: 0.05rem 0.4rem;
border-radius: 999px;
background: color-mix(in srgb, var(--sidebar-accent) 16%, transparent);
color: var(--sidebar-accent);
font-size: 0.72rem;
}
.wire-sidebar__launcher {
display: none;
align-items: center;
gap: 0.5rem;
padding: 0.45rem 0.7rem;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-sm);
background: var(--wire-color-surface);
color: var(--wire-color-text);
font: inherit;
font-size: 0.9rem;
cursor: pointer;
}
.wire-sidebar__launcher-bar {
width: 1rem;
height: 2px;
background: currentColor;
box-shadow:
0 -5px 0 currentColor,
0 5px 0 currentColor;
}
/*
* Below the tablet breakpoint the rail is replaced by the drawer launcher.
* A permanent rail eats most of a phone screen, and Drawer already handles
* the focus trap and the scroll lock.
*/
@media (max-width: 767px) {
.wire-sidebar__launcher {
display: inline-flex;
}
.wire-sidebar__rail {
display: none;
}
}
}
} }
+429 -11
View File
@@ -1,24 +1,442 @@
// Stepper -- ordered progress through a sequence.
//
// <Stepper steps='[{"label":"Account"},{"label":"Billing"}]' active={1} />
//
// Each step can be authored by hand instead of using the built-in body, by
// passing a slot named for its index:
//
// <Stepper steps={steps} active={1}>
// <div data-slot="step-1">...anything...</div>
// </Stepper>
//
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
// and silently swallows the rule that follows it.
component Stepper { component Stepper {
outputs { outputs {
change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null) change(payload: { index: number; step: object })
previous(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object }) back(payload: { index: number; step: object })
next(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object }) next(payload: { index: number; step: object })
complete(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object }) skip(payload: { index: number; step: object })
finish(payload: { index: number; step: object })
} }
props { props {
size: string = "default"
color: string = "primary" color: string = "primary"
label: string = "Stepper" size: string = "default"
items: unknown[] = [] steps: unknown[] = []
active: string = "" active: number = 0
orientation: string = "horizontal" orientation: string = "horizontal"
clickable: boolean = false
label: string = "Progress"
// Render each step body, showing only the active one -- same contract as
// Tabs, so a wizard does not have to hand-roll the panel switching.
showPanel: boolean = false
controls: boolean = false
allowSkip: boolean = false
// Lets a form hold the step. The component never validates anything
// itself: the page owns the form, so it sets this while the step is
// incomplete and clears it once the step passes.
nextDisabled: boolean = false
backLabel: string = "Back"
nextLabel: string = "Next"
skipLabel: string = "Skip"
finishLabel: string = "Finish"
class: string = "" class: string = ""
} }
functions {
shared function stepList() {
return Array.isArray(steps) ? steps : []
}
shared function activeIndex() {
var count = stepList().length
if (count < 1) {
return 0
}
var value = Number(active)
if (!value || value < 0) {
return 0
}
return Math.min(value, count - 1)
}
shared function statusFor(index) {
if (index < activeIndex()) {
return "complete"
}
if (index === activeIndex()) {
return "current"
}
return "upcoming"
}
// An empty orientation is how the roving runtime is told to stay out of
// the way, so a read-only stepper never takes arrow-key focus.
shared function rovingAxis() {
if (!clickable) {
return ""
}
return orientation === "vertical" ? "vertical" : "horizontal"
}
shared function isActiveStep(index) {
return index === activeIndex()
}
shared function lastIndex() {
return Math.max(0, stepList().length - 1)
}
shared function onLastStep() {
return activeIndex() >= lastIndex()
}
shared function activeStep() {
var list = stepList()
return list.length ? list[activeIndex()] : {}
}
client function goBack() {
var target = Math.max(0, activeIndex() - 1)
output.back({ index: target, step: stepList()[target] || {} })
output.change({ index: target, step: stepList()[target] || {} })
}
client function goNext() {
if (nextDisabled) {
return
}
if (onLastStep()) {
output.finish({ index: activeIndex(), step: activeStep() })
return
}
var target = Math.min(lastIndex(), activeIndex() + 1)
output.next({ index: target, step: stepList()[target] || {} })
output.change({ index: target, step: stepList()[target] || {} })
}
client function goSkip() {
var target = Math.min(lastIndex(), activeIndex() + 1)
output.skip({ index: target, step: stepList()[target] || {} })
output.change({ index: target, step: stepList()[target] || {} })
}
client function selectStep(index, step) {
if (!clickable) {
return
}
output.change({ index: index, step: step })
}
}
view { view {
<nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--stepper wire-next--{orientation} {class}" aria-label="{label}"> <div
{#each items as item}<a href="{item.href}" aria-current="{item.value === active ? 'page' : ''}">{item.label}</a>{/each} {...attrs}
data-ui-component="Stepper"
class='wire-stepper {class}'
data-orientation='{orientation}'
data-color='{color}'
data-size='{size}'
data-clickable='{clickable}'
>
<ol
class="wire-stepper__list"
data-wrn-roving='{rovingAxis()}'
aria-label='{label}'
>
{#each stepList() as step, index}
<li
class="wire-stepper__step"
data-status='{statusFor(index)}'
aria-current='{statusFor(index) === "current" ? "step" : "false"}'
>
<button
type="button"
class="wire-stepper__button"
data-wrn-roving-item='{clickable ? "true" : "false"}'
@click='selectStep(index, step)'
>
<span class="wire-stepper__marker" aria-hidden="true">
<span class='wire-stepper__icon {step.icon}' data-show="step.icon"></span>
<span class="wire-stepper__number" data-show="!step.icon">{index + 1}</span>
</span>
<span class="wire-stepper__body">
<span class="wire-stepper__label">{step.label}</span>
<span class="wire-stepper__description" data-show="step.description">
{step.description}
</span>
</span>
</button>
<span class="wire-stepper__custom">
<slot name="step-{index}"></slot>
</span>
</li>
{/each}
</ol>
<div class="wire-stepper__panels" data-show="showPanel">
{#each stepList() as step, index}
<div
class="wire-stepper__panel"
data-show='isActiveStep(index)'
aria-hidden='{index === activeIndex() ? "false" : "true"}'
>
<h4 class="wire-stepper__panel-title" data-show="step.title">{step.title}</h4>
<p class="wire-stepper__panel-body" data-show="step.content">{step.content}</p>
<slot name="panel-{index}"></slot>
</div>
{/each}
</div>
<div class="wire-stepper__controls" data-show="controls">
<button
type="button"
class="wire-stepper__button-control wire-stepper__back"
disabled='{activeIndex() === 0}'
@click='goBack()'
>
{backLabel}
</button>
<span class="wire-stepper__controls-spacer"></span>
<button
type="button"
class="wire-stepper__button-control wire-stepper__skip"
data-show="allowSkip && !onLastStep()"
@click='goSkip()'
>
{skipLabel}
</button>
<button
type="button"
class="wire-stepper__button-control wire-stepper__next"
data-primary="true"
disabled='{nextDisabled}'
@click='goNext()'
>
{onLastStep() ? finishLabel : nextLabel}
</button>
</div>
<slot /> <slot />
</nav> </div>
}
style {
.wire-stepper {
--stepper-accent: var(--wire-color-primary);
max-width: 100%;
}
.wire-stepper__list {
display: flex;
gap: 0.5rem;
margin: 0;
padding: 0;
list-style: none;
max-width: 100%;
}
.wire-stepper[data-color="secondary"] {
--stepper-accent: var(--wire-color-secondary);
}
.wire-stepper[data-color="success"] {
--stepper-accent: var(--wire-color-success);
}
.wire-stepper[data-color="danger"] {
--stepper-accent: var(--wire-color-danger);
}
.wire-stepper[data-color="info"] {
--stepper-accent: var(--wire-color-info);
}
.wire-stepper[data-size="sm"] {
font-size: 0.82rem;
}
.wire-stepper[data-size="lg"] {
font-size: 1rem;
}
.wire-stepper[data-orientation="vertical"] .wire-stepper__list {
flex-direction: column;
}
.wire-stepper__step {
display: flex;
flex-direction: column;
flex: 1 1 0;
min-width: 0;
gap: 0.35rem;
}
.wire-stepper__button {
appearance: none;
display: flex;
align-items: center;
gap: 0.6rem;
width: 100%;
padding: 0.5rem;
border: 0;
border-radius: var(--wire-radius-sm);
background: transparent;
color: inherit;
font: inherit;
text-align: left;
cursor: default;
}
.wire-stepper[data-clickable="true"] .wire-stepper__button {
cursor: pointer;
}
.wire-stepper[data-clickable="true"] .wire-stepper__button:hover {
background: var(--wire-color-surface-soft);
}
.wire-stepper__button:focus-visible {
outline: 2px solid var(--stepper-accent);
outline-offset: 2px;
}
.wire-stepper__marker {
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
width: 2rem;
height: 2rem;
border: 1px solid var(--wire-color-border);
border-radius: 999px;
background: var(--wire-color-surface);
font-size: 0.85rem;
font-weight: 700;
}
.wire-stepper__step[data-status="complete"] .wire-stepper__marker {
border-color: var(--stepper-accent);
background: var(--stepper-accent);
color: var(--wire-color-primary-contrast);
}
.wire-stepper__step[data-status="current"] .wire-stepper__marker {
border-color: var(--stepper-accent);
color: var(--stepper-accent);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--stepper-accent) 22%, transparent);
}
.wire-stepper__step[data-status="upcoming"] .wire-stepper__marker {
color: var(--wire-color-text-muted);
}
.wire-stepper__body {
display: flex;
flex-direction: column;
min-width: 0;
}
.wire-stepper__label {
font-size: 0.9rem;
font-weight: 600;
}
.wire-stepper__description {
color: var(--wire-color-text-muted);
font-size: 0.78rem;
}
.wire-stepper__step[data-status="upcoming"] .wire-stepper__label {
color: var(--wire-color-text-muted);
}
.wire-stepper__panels {
margin-top: 1rem;
}
.wire-stepper__panel {
padding: 1rem;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-md);
background: var(--wire-color-surface);
animation: wire-stepper-in 200ms ease both;
}
.wire-stepper__panel-title {
margin: 0 0 0.35rem;
font-size: 0.95rem;
font-weight: 700;
}
.wire-stepper__panel-body {
margin: 0;
color: var(--wire-color-text-muted);
line-height: 1.6;
}
.wire-stepper__controls {
display: flex;
align-items: center;
gap: 0.5rem;
margin-top: 0.85rem;
}
.wire-stepper__controls-spacer {
flex: 1 1 auto;
}
.wire-stepper__button-control {
appearance: none;
padding: 0.45rem 0.9rem;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-sm);
background: var(--wire-color-surface);
color: var(--wire-color-text);
font: inherit;
font-size: 0.86rem;
font-weight: 600;
cursor: pointer;
}
.wire-stepper__button-control:hover:not(:disabled) {
background: var(--wire-color-surface-soft);
}
.wire-stepper__button-control:focus-visible {
outline: 2px solid var(--stepper-accent);
outline-offset: 2px;
}
.wire-stepper__button-control[data-primary="true"] {
border-color: var(--stepper-accent);
background: var(--stepper-accent);
color: var(--wire-color-primary-contrast);
}
/* A held step has to look held, or the button reads as broken. */
.wire-stepper__button-control:disabled {
opacity: 0.45;
cursor: not-allowed;
}
@keyframes wire-stepper-in {
from {
opacity: 0;
transform: translateX(0.75rem);
}
to {
opacity: 1;
transform: none;
}
}
/* A horizontal stepper cannot stay side by side on a phone. */
@media (max-width: 639px) {
.wire-stepper__list {
flex-direction: column;
}
}
} }
} }
+325 -53
View File
@@ -1,90 +1,362 @@
// Tabs -- a tablist over panels.
//
// <Tabs items='[{"label":"Overview","value":"overview"}]' active="overview" />
//
// With mode="url" the selection is mirrored into a query parameter using
// history.pushState, so the panel swaps without a page load and the back
// button works. The query parameter is used rather than the hash because it
// survives a reload and does not collide with in-page anchors or Scrollspy.
//
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
// and silently swallows the rule that follows it.
component Tabs { component Tabs {
outputs {
change(payload: { value: string; item: object; index: number })
select(payload: { value: string; item: object; index: number })
}
props { props {
size: string = "default"
color: string = "primary" color: string = "primary"
label: string = "Tabs" size: string = "default"
items: unknown[] = [] items: unknown[] = []
active: string = "" active: string = ""
orientation: string = "horizontal" orientation: string = "horizontal"
mode: string = "client"
param: string = "tab"
label: string = "Tabs"
class: string = "" class: string = ""
} }
state activeValue = active || (items[0] ? (items[0].value || items[0].id || "0") : "") state activeValue = ""
// Which way the panel should slide in. Set on every change so the animation
// follows the direction of travel rather than always coming from one side.
state slideFrom = "forward"
functions {
shared function itemList() {
return Array.isArray(items) ? items : []
}
shared function valueOf(item, index) {
return String(item.value || item.id || index)
}
/*
* In url mode the query parameter is the source of truth, not local state.
*
* The client router owns popstate and swaps the whole page on back and
* forward, which discards component state anyway. Reading the URL means
* the right tab simply falls out of whatever render happens next, with no
* listener to lose and nothing to keep in step.
*/
shared function currentValue() {
if (mode === "url" && typeof window !== "undefined" && window.location) {
var fromUrl = new URLSearchParams(window.location.search).get(param || "tab")
return fromUrl ? fromUrl : defaultValue()
}
if (activeValue) {
return activeValue
}
return defaultValue()
}
// The selection this instance started with. The runtime falls back to it
// when the back button lands on a URL that has no tab parameter at all.
shared function defaultValue() {
if (active) {
return active
}
var list = itemList()
return list.length ? valueOf(list[0], 0) : ""
}
shared function isSelected(item, index) {
return valueOf(item, index) === currentValue()
}
shared function rovingAxis() {
return orientation === "vertical" ? "vertical" : "horizontal"
}
client function selectTab(item, index, sourceEvent) {
if (item.disabled) {
return
}
var value = valueOf(item, index)
var list = itemList()
var previous = -1
for (var scan = 0; scan < list.length; scan += 1) {
if (valueOf(list[scan], scan) === currentValue()) {
previous = scan
}
}
slideFrom = previous > index ? "back" : "forward"
activeValue = value
if (mode === "url" && window.history && window.history.pushState) {
var url = new URL(window.location.href)
url.searchParams.set(param || "tab", value)
window.history.pushState({}, "", url.toString())
}
output.change({ value: value, item: item, index: index })
output.select({ value: value, item: item, index: index })
}
}
view { view {
<section <section
{...attrs}
data-ui-component="Tabs" data-ui-component="Tabs"
class='w-full {class}' class='wire-tabs {class}'
class:flex='orientation === "vertical"' data-orientation='{orientation}'
class:items-start='orientation === "vertical"' data-color='{color}'
class:gap-6='orientation === "vertical"' data-size='{size}'
data-slide='{slideFrom}'
data-mode='{mode}'
data-param='{param}'
> >
<div <div
class="wire-tabs__list"
role="tablist" role="tablist"
aria-label='{label}' aria-label='{label}'
aria-orientation='{orientation}' aria-orientation='{orientation}'
class="flex min-w-0 gap-1 overflow-x-auto rounded-xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-soft)] p-1" data-wrn-roving='{rovingAxis()}'
class:flex-col='orientation === "vertical"'
class:w-56='orientation === "vertical"'
class:shrink-0='orientation === "vertical"'
> >
{#each items as item, index} {#each itemList() as item, index}
<button <button
type="button" type="button"
class="wire-tabs__tab"
role="tab" role="tab"
id='tab-{item.value || item.id || index}' data-value='{valueOf(item, index)}'
aria-selected='{activeValue === (item.value || item.id || String(index))}' data-wrn-roving-item="true"
aria-controls='panel-{item.value || item.id || index}' id='wire-tab-{valueOf(item, index)}'
tabindex='{activeValue === (item.value || item.id || String(index)) ? "0" : "-1"}' aria-controls='wire-panel-{valueOf(item, index)}'
disabled='{item.disabled || false}' aria-selected='{isSelected(item, index) ? "true" : "false"}'
class="inline-flex min-w-max flex-1 items-center justify-center gap-2 rounded-lg px-4 py-2.5 text-sm font-semibold outline-none transition disabled:cursor-not-allowed disabled:opacity-50" aria-disabled='{item.disabled ? "true" : "false"}'
class:bg-[var(--wire-color-surface-raised)]='activeValue === (item.value || item.id || String(index))' @click='selectTab(item, index, event)'
class:text-[var(--wire-color-primary)]='activeValue === (item.value || item.id || String(index))'
class:shadow-sm='activeValue === (item.value || item.id || String(index))'
class:text-[var(--wire-color-text-muted)]='activeValue !== (item.value || item.id || String(index))'
class:hover:text-[var(--wire-color-text)]='activeValue !== (item.value || item.id || String(index))'
class:justify-start='orientation === "vertical"'
class:px-3='size === "sm"'
class:py-2='size === "sm"'
class:px-5='size === "lg"'
class:py-3='size === "lg"'
@click='activeValue = item.value || item.id || String(index); event.currentTarget.dispatchEvent(new CustomEvent("change", { bubbles: true, detail: { item: item, index: index, value: activeValue } })); event.currentTarget.dispatchEvent(new CustomEvent("select", { bubbles: true, detail: { item: item, index: index, value: activeValue } }))'
> >
{#if item.icon} <span class='wire-tabs__icon {item.icon}' data-show="item.icon" aria-hidden="true"></span>
<span class='{item.icon + " size-4"}' aria-hidden="true"></span> <span class="wire-tabs__label">{item.label || item.title}</span>
{/if} <span class="wire-tabs__badge" data-show="item.badge">{item.badge}</span>
<span>{item.label || item.title}</span>
{#if item.badge}
<span class="rounded-full bg-[var(--wire-color-primary-soft)] px-2 py-0.5 text-xs text-[var(--wire-color-primary)]">{item.badge}</span>
{/if}
</button> </button>
{/each} {/each}
</div> </div>
<div class="min-w-0 flex-1 pt-4" class:pt-0='orientation === "vertical"'> <div class="wire-tabs__panels">
{#each items as item, index} {#each itemList() as item, index}
<div <div
class="wire-tabs__panel"
role="tabpanel" role="tabpanel"
id='panel-{item.value || item.id || index}' id='wire-panel-{valueOf(item, index)}'
aria-labelledby='tab-{item.value || item.id || index}' aria-labelledby='wire-tab-{valueOf(item, index)}'
tabindex="0" tabindex="0"
data-show='activeValue === (item.value || item.id || String(index))' data-show='isSelected(item, index)'
class="rounded-xl outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]"
> >
{#if item.title && item.title !== item.label} <h3 class="wire-tabs__title" data-show="item.title && item.title !== item.label">
<h3 class="text-lg font-bold text-[var(--wire-color-text)]">{item.title}</h3> {item.title}
{/if} </h3>
{#if item.description} <p class="wire-tabs__description" data-show="item.description">{item.description}</p>
<p class="mt-2 leading-7 text-[var(--wire-color-text-muted)]">{item.description}</p> <div class="wire-tabs__content" data-show="item.content">{item.content}</div>
{/if} <slot name="panel-{valueOf(item, index)}"></slot>
{#if item.content}
<div class="mt-4 text-[var(--wire-color-text)]">{item.content}</div>
{/if}
</div> </div>
{/each} {/each}
<slot />
<slot></slot>
</div> </div>
</section> </section>
} }
style {
.wire-tabs {
--tabs-accent: var(--wire-color-primary);
display: flex;
flex-direction: column;
gap: 1rem;
max-width: 100%;
}
.wire-tabs[data-color="secondary"] {
--tabs-accent: var(--wire-color-secondary);
}
.wire-tabs[data-color="success"] {
--tabs-accent: var(--wire-color-success);
}
.wire-tabs[data-color="danger"] {
--tabs-accent: var(--wire-color-danger);
}
.wire-tabs[data-color="info"] {
--tabs-accent: var(--wire-color-info);
}
.wire-tabs[data-size="sm"] {
font-size: 0.82rem;
}
.wire-tabs[data-size="lg"] {
font-size: 1rem;
}
.wire-tabs[data-orientation="vertical"] {
flex-direction: row;
align-items: flex-start;
}
.wire-tabs__list {
display: flex;
gap: 0.25rem;
min-width: 0;
padding: 0.25rem;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-md);
background: var(--wire-color-surface-soft);
/* A long tablist scrolls rather than wrapping into an unusable stack. */
overflow-x: auto;
}
.wire-tabs[data-orientation="vertical"] .wire-tabs__list {
flex-direction: column;
flex: 0 0 auto;
width: 14rem;
overflow-x: visible;
}
.wire-tabs__tab {
appearance: none;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.4rem;
flex: 1 0 auto;
padding: 0.55rem 0.9rem;
border: 0;
border-radius: var(--wire-radius-sm);
background: transparent;
color: var(--wire-color-text-muted);
font: inherit;
font-size: 0.88rem;
font-weight: 600;
white-space: nowrap;
cursor: pointer;
transition:
background 160ms ease,
color 160ms ease;
}
.wire-tabs[data-orientation="vertical"] .wire-tabs__tab {
justify-content: flex-start;
}
.wire-tabs__tab:hover {
color: var(--wire-color-text);
}
.wire-tabs__tab:focus-visible {
outline: 2px solid var(--tabs-accent);
outline-offset: 2px;
}
.wire-tabs__tab[aria-selected="true"] {
background: var(--wire-color-surface);
color: var(--tabs-accent);
box-shadow: var(--wire-shadow-1);
}
.wire-tabs__tab[aria-disabled="true"] {
opacity: 0.5;
pointer-events: none;
}
.wire-tabs__badge {
padding: 0.05rem 0.4rem;
border-radius: 999px;
background: color-mix(in srgb, var(--tabs-accent) 16%, transparent);
color: var(--tabs-accent);
font-size: 0.72rem;
}
.wire-tabs__panels {
min-width: 0;
flex: 1 1 auto;
}
.wire-tabs__panel {
outline: none;
animation: wire-tabs-slide-forward 220ms cubic-bezier(0.22, 1, 0.36, 1) both;
}
.wire-tabs[data-slide="back"] .wire-tabs__panel {
animation-name: wire-tabs-slide-back;
}
/* The panels clip their own slide so it never widens the page. */
.wire-tabs__panels {
overflow-x: clip;
}
.wire-tabs__panel:focus-visible {
outline: 2px solid var(--tabs-accent);
outline-offset: 4px;
border-radius: var(--wire-radius-sm);
}
.wire-tabs__title {
margin: 0 0 0.35rem;
font-size: 1rem;
font-weight: 700;
}
.wire-tabs__description {
margin: 0;
color: var(--wire-color-text-muted);
line-height: 1.6;
}
.wire-tabs__content {
margin-top: 0.6rem;
}
@keyframes wire-tabs-slide-forward {
from {
opacity: 0;
transform: translateX(1.25rem);
}
to {
opacity: 1;
transform: none;
}
}
@keyframes wire-tabs-slide-back {
from {
opacity: 0;
transform: translateX(-1.25rem);
}
to {
opacity: 1;
transform: none;
}
}
/* Respect a reduced-motion preference: swap instantly instead. */
@media (prefers-reduced-motion: reduce) {
.wire-tabs__panel {
animation: none;
}
}
@media (max-width: 639px) {
.wire-tabs[data-orientation="vertical"] {
flex-direction: column;
}
.wire-tabs[data-orientation="vertical"] .wire-tabs__list {
width: 100%;
flex-direction: row;
overflow-x: auto;
}
}
}
} }
+5 -1
View File
@@ -1,4 +1,8 @@
component Timeline { component Timeline {
outputs {
select(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
}
props { props {
size: string = "default" size: string = "default"
color: string = "primary" color: string = "primary"
@@ -30,7 +34,7 @@ component Timeline {
{#each items as item, index} {#each items as item, index}
<li <li
class="relative pb-8 pl-8 last:pb-0" class="relative pb-8 pl-8 last:pb-0"
@click='event.currentTarget.dispatchEvent(new CustomEvent("select", { bubbles: true, detail: { item: item, index: index } }))' @click='output.select({ item: item, index: index })'
> >
<span <span
class="absolute -left-[1.05rem] top-0 flex size-8 items-center justify-center rounded-full border-4 border-[var(--wire-color-background)] bg-[var(--wire-color-primary)] text-[var(--wire-color-on-primary)] shadow-sm" class="absolute -left-[1.05rem] top-0 flex size-8 items-center justify-center rounded-full border-4 border-[var(--wire-color-background)] bg-[var(--wire-color-primary)] text-[var(--wire-color-on-primary)] shadow-sm"
+10 -27
View File
@@ -57,34 +57,17 @@ component ToggleCount {
: emptyValue : emptyValue
} }
client function dispatchToggleEvent(sourceEvent, previousValue, root, customEvent) { client function dispatchToggleEvent(sourceEvent, previousValue, payload) {
root = sourceEvent.currentTarget.closest("[data-wrn-toggle-count]") payload = {
component: "ToggleCount",
if (!root) { name: name,
return value: selectedValue,
previousValue: previousValue,
firstValue: firstValue,
secondValue: secondValue
} }
output.change(payload)
customEvent = document.createEvent("CustomEvent") output.toggle(payload)
customEvent.initCustomEvent("change", true, false, {
component: "ToggleCount",
name: name,
value: selectedValue,
previousValue: previousValue,
firstValue: firstValue,
secondValue: secondValue
})
root.dispatchEvent(customEvent)
customEvent = document.createEvent("CustomEvent")
customEvent.initCustomEvent("toggle", true, false, {
component: "ToggleCount",
name: name,
value: selectedValue,
previousValue: previousValue,
firstValue: firstValue,
secondValue: secondValue
})
root.dispatchEvent(customEvent)
} }
client function selectValue(sourceEvent, nextValue, previousValue) { client function selectValue(sourceEvent, nextValue, previousValue) {
+62 -14
View File
@@ -11,26 +11,74 @@ component Typography {
view { view {
<div <div
data-ui-component="Typography" data-ui-component="Typography"
class='wire-typography text-[var(--wire-color-text)] {class}' class='wire-typography {class}'
class:max-w-3xl='maxWidth === "md"' data-size='{size}'
class:max-w-5xl='maxWidth === "lg"' data-columns='{columns}'
class:max-w-7xl='maxWidth === "xl"' data-gap='{gap}'
class:max-w-none='maxWidth === "full"' data-max-width='{maxWidth}'
class:text-sm='size === "sm"'
class:text-base='size === "default" || size === "md"'
class:text-lg='size === "lg"'
class:columns-1='columns === 1'
class:md:columns-2='columns === 2'
class:lg:columns-3='columns === 3'
class:gap-4='gap === "sm"'
class:gap-8='gap === "md"'
class:gap-12='gap === "lg"'
> >
<slot></slot> <slot></slot>
</div> </div>
} }
style { style {
.wire-typography {
color: var(--wire-color-text);
font-size: 1rem;
}
.wire-typography[data-size="sm"] {
font-size: 0.875rem;
}
.wire-typography[data-size="lg"] {
font-size: 1.125rem;
}
.wire-typography[data-max-width="md"] {
max-width: 48rem;
}
.wire-typography[data-max-width="lg"] {
max-width: 64rem;
}
.wire-typography[data-max-width="xl"] {
max-width: 80rem;
}
.wire-typography[data-max-width="full"] {
max-width: none;
}
/*
* Multi-column prose only below a comfortable reading width; a narrow
* screen split into columns is unreadable.
*/
.wire-typography[data-gap="sm"] {
column-gap: 1rem;
}
.wire-typography[data-gap="md"] {
column-gap: 2rem;
}
.wire-typography[data-gap="lg"] {
column-gap: 3rem;
}
@media (min-width: 768px) {
.wire-typography[data-columns="2"] {
column-count: 2;
}
}
@media (min-width: 1024px) {
.wire-typography[data-columns="3"] {
column-count: 3;
}
}
.wire-typography :where(h1, h2, h3, h4) { .wire-typography :where(h1, h2, h3, h4) {
color: var(--wire-color-text); color: var(--wire-color-text);
font-weight: 700; font-weight: 700;
+12 -11
View File
@@ -35,22 +35,23 @@ component Alert {
state visible: boolean = true state visible: boolean = true
functions { functions {
client function dispatchAlertEvent(sourceEvent, eventName, action, root, customEvent) { // The output name has to be written out rather than computed: outputs are
root = sourceEvent.currentTarget.closest("[data-wrn-alert]") // resolved as named properties, so a dynamic key would not reach a parent
// binding. Only two names exist here, so a branch is honest and cheap.
if (!root) { client function dispatchAlertEvent(sourceEvent, eventName, action, payload) {
return payload = {
}
customEvent = document.createEvent("CustomEvent")
customEvent.initCustomEvent(eventName, true, false, {
component: "Alert", component: "Alert",
title: title, title: title,
color: color, color: color,
variant: variant, variant: variant,
action: action action: action
}) }
root.dispatchEvent(customEvent)
if (eventName === "dismiss") {
output.dismiss(payload)
} else {
output.action(payload)
}
} }
client function dismissAlert(sourceEvent) { client function dismissAlert(sourceEvent) {
+6 -8
View File
@@ -35,15 +35,13 @@ component Badge {
state visible: boolean = true state visible: boolean = true
functions { functions {
client function dismissBadge(sourceEvent, root, customEvent) { // output.dismiss reaches a parent @dismiss binding; a raw dispatchEvent on
// the root does not. The runtime registers parent handlers in a registry
// that only the output proxy consults, so the CustomEvent this used to
// build bubbled past every binding and was never seen by anyone.
client function dismissBadge() {
visible = false visible = false
root = sourceEvent.currentTarget.closest("[data-wrn-badge]") output.dismiss({ component: "Badge", label: label })
customEvent = document.createEvent("CustomEvent")
customEvent.initCustomEvent("dismiss", true, false, {
component: "Badge",
label: label
})
root.dispatchEvent(customEvent)
} }
} }
+21 -61
View File
@@ -45,67 +45,27 @@ component Card {
state dismissed: boolean = false state dismissed: boolean = false
functions { functions {
client function dispatchCardNavigation(sourceEvent, item, index, root, customEvent) { // Outputs must go through output.*; a CustomEvent dispatched on the root
root = sourceEvent.currentTarget.closest("[data-wrn-card]") // bubbles past every parent @binding without being seen, because the
// runtime keeps parent handlers in a registry only the output proxy reads.
if (!root) { client function dispatchCardNavigation(sourceEvent, item, index) {
return output.navigate({ component: "Card", item: item, index: index })
}
customEvent = document.createEvent("CustomEvent")
customEvent.initCustomEvent("navigate", true, false, {
component: "Card",
item: item,
index: index
})
root.dispatchEvent(customEvent)
} }
client function dispatchCardNavigationValue(sourceEvent, root, customEvent) { client function dispatchCardNavigationValue(sourceEvent) {
root = sourceEvent.currentTarget.closest("[data-wrn-card]") output.navigate({
if (!root) {
return
}
customEvent = document.createEvent("CustomEvent")
customEvent.initCustomEvent("navigate", true, false, {
component: "Card", component: "Card",
value: sourceEvent.currentTarget.value value: sourceEvent.currentTarget.value
}) })
root.dispatchEvent(customEvent)
} }
client function dispatchCardHeaderAction(sourceEvent, action, index, root, customEvent) { client function dispatchCardHeaderAction(sourceEvent, action, index) {
root = sourceEvent.currentTarget.closest("[data-wrn-card]") output.action({ component: "Card", action: action, index: index })
if (!root) {
return
}
customEvent = document.createEvent("CustomEvent")
customEvent.initCustomEvent("action", true, false, {
component: "Card",
action: action,
index: index
})
root.dispatchEvent(customEvent)
} }
client function dismissCard(sourceEvent, root, customEvent) { client function dismissCard() {
dismissed = true dismissed = true
root = sourceEvent.currentTarget.closest("[data-wrn-card]") output.dismiss({ component: "Card", title: title })
if (!root) {
return
}
customEvent = document.createEvent("CustomEvent")
customEvent.initCustomEvent("dismiss", true, false, {
component: "Card",
title: title
})
root.dispatchEvent(customEvent)
} }
} }
@@ -137,8 +97,8 @@ component Card {
alt='{item.imageAlt || item.alt || ""}' alt='{item.imageAlt || item.alt || ""}'
loading='{item.loading || "lazy"}' loading='{item.loading || "lazy"}'
decoding="async" decoding="async"
@load='event.currentTarget.dispatchEvent(new CustomEvent("load", { bubbles: true, detail: { item: item, index: itemIndex } }))' @load='output.load({ item: item, index: itemIndex })'
@error='event.currentTarget.dispatchEvent(new CustomEvent("error", { bubbles: true, detail: { item: item, index: itemIndex } }))' @error='output.error({ item: item, index: itemIndex })'
/> />
</div> </div>
{/if} {/if}
@@ -157,7 +117,7 @@ component Card {
<a <a
href='{item.actionHref || item.href || "#"}' href='{item.actionHref || item.href || "#"}'
class="wire-next__card-action" class="wire-next__card-action"
@click='event.currentTarget.dispatchEvent(new CustomEvent("action", { bubbles: true, detail: { item: item, index: itemIndex } }))' @click='output.action({ item: item, index: itemIndex })'
> >
<span>{item.actionLabel || "Learn more"}</span> <span>{item.actionLabel || "Learn more"}</span>
<span class="icon-[lucide--arrow-right]" aria-hidden="true"></span> <span class="icon-[lucide--arrow-right]" aria-hidden="true"></span>
@@ -180,8 +140,8 @@ component Card {
alt='{imageAlt}' alt='{imageAlt}'
loading="lazy" loading="lazy"
decoding="async" decoding="async"
@load='event.currentTarget.dispatchEvent(new CustomEvent("load", { bubbles: true, detail: { src: imageSrc } }))' @load='output.load({ src: imageSrc })'
@error='event.currentTarget.dispatchEvent(new CustomEvent("error", { bubbles: true, detail: { src: imageSrc } }))' @error='output.error({ src: imageSrc })'
/> />
<div class="wire-next__card-overlay-shade" aria-hidden="true"></div> <div class="wire-next__card-overlay-shade" aria-hidden="true"></div>
</div> </div>
@@ -194,8 +154,8 @@ component Card {
alt='{imageAlt}' alt='{imageAlt}'
loading="lazy" loading="lazy"
decoding="async" decoding="async"
@load='event.currentTarget.dispatchEvent(new CustomEvent("load", { bubbles: true, detail: { src: imageSrc } }))' @load='output.load({ src: imageSrc })'
@error='event.currentTarget.dispatchEvent(new CustomEvent("error", { bubbles: true, detail: { src: imageSrc } }))' @error='output.error({ src: imageSrc })'
/> />
</div> </div>
{/if} {/if}
@@ -323,7 +283,7 @@ component Card {
<a <a
href='{actionHref || "#"}' href='{actionHref || "#"}'
class="wire-next__card-action" class="wire-next__card-action"
@click='event.currentTarget.dispatchEvent(new CustomEvent("action", { bubbles: true, detail: { href: actionHref, label: actionLabel } }))' @click='output.action({ href: actionHref, label: actionLabel })'
> >
<span>{actionLabel}</span> <span>{actionLabel}</span>
<span class="icon-[lucide--arrow-right]" aria-hidden="true"></span> <span class="icon-[lucide--arrow-right]" aria-hidden="true"></span>
@@ -340,8 +300,8 @@ component Card {
alt='{imageAlt}' alt='{imageAlt}'
loading="lazy" loading="lazy"
decoding="async" decoding="async"
@load='event.currentTarget.dispatchEvent(new CustomEvent("load", { bubbles: true, detail: { src: imageSrc } }))' @load='output.load({ src: imageSrc })'
@error='event.currentTarget.dispatchEvent(new CustomEvent("error", { bubbles: true, detail: { src: imageSrc } }))' @error='output.error({ src: imageSrc })'
/> />
</div> </div>
{/if} {/if}
+140 -24
View File
@@ -1,46 +1,162 @@
// Container -- a centred, width-limited page wrapper.
//
// <Container maxWidth="lg">...</Container>
//
// It also accepts columns and gap, because it always has and applications
// depend on it. Reach for Grid when a grid is the point; reach for Container
// when the point is the reading width.
//
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
// and silently swallows the rule that follows it.
component Container { component Container {
props { props {
size: string = "default" size: string = "default"
color: string = "primary" color: string = "primary"
// Stays 2 to match what shipped. A published default cannot be changed
// without silently reflowing every Container already in use.
columns: number = 2 columns: number = 2
gap: string = "md" gap: string = "md"
maxWidth: string = "xl" maxWidth: string = "xl"
centered: boolean = true
class: string = "" class: string = ""
} }
functions {
shared function columnCount() {
var value = Number(columns)
if (!value || value < 1) {
return 1
}
return Math.min(6, value)
}
}
view { view {
<div <div
{...attrs}
data-ui-component="Container" data-ui-component="Container"
class='wire-container {class}'
data-size='{size}' data-size='{size}'
data-color='{color}' data-color='{color}'
data-columns='{columns}'
data-gap='{gap}' data-gap='{gap}'
data-max-width='{maxWidth}' data-max-width='{maxWidth}'
class='mx-auto w-full {class}' data-centered='{centered}'
class:max-w-3xl='maxWidth === "md"' data-columns='{columnCount()}'
class:max-w-5xl='maxWidth === "lg"'
class:max-w-7xl='maxWidth === "xl" || maxWidth === "wide"'
class:max-w-screen-2xl='maxWidth === "2xl"'
class:max-w-none='maxWidth === "full"'
class:px-4='size === "compact" || size === "default"'
class:px-5='size === "comfortable"'
class:px-6='size === "spacious"'
class:sm:px-6='size !== "compact"'
class:lg:px-8='size === "default" || size === "comfortable" || size === "spacious"'
class:grid='columns > 1'
class:grid-cols-1='columns > 1'
class:sm:grid-cols-2='columns === 2 || columns === 3 || columns === 4 || columns === 5 || columns === 6'
class:lg:grid-cols-3='columns === 3'
class:lg:grid-cols-4='columns === 4'
class:lg:grid-cols-5='columns === 5'
class:lg:grid-cols-6='columns === 6'
class:gap-2='gap === "xs"'
class:gap-3='gap === "sm"'
class:gap-5='gap === "md"'
class:gap-8='gap === "lg"'
class:gap-10='gap === "xl"'
> >
<slot></slot> <slot></slot>
</div> </div>
} }
style {
.wire-container {
--container-gap: 1.25rem;
width: 100%;
min-width: 0;
padding-inline: 1rem;
}
.wire-container[data-centered="true"] {
margin-inline: auto;
}
/* Reading widths, not breakpoints: the cap is about line length. */
.wire-container[data-max-width="md"] {
max-width: 48rem;
}
.wire-container[data-max-width="lg"] {
max-width: 64rem;
}
.wire-container[data-max-width="xl"],
.wire-container[data-max-width="wide"] {
max-width: 80rem;
}
.wire-container[data-max-width="2xl"] {
max-width: 96rem;
}
.wire-container[data-max-width="full"] {
max-width: none;
}
.wire-container[data-size="compact"] {
padding-inline: 1rem;
}
.wire-container[data-size="comfortable"] {
padding-inline: 1.25rem;
}
.wire-container[data-size="spacious"] {
padding-inline: 1.5rem;
}
.wire-container[data-gap="xs"] {
--container-gap: 0.5rem;
}
.wire-container[data-gap="sm"] {
--container-gap: 0.75rem;
}
.wire-container[data-gap="lg"] {
--container-gap: 2rem;
}
.wire-container[data-gap="xl"] {
--container-gap: 2.5rem;
}
/*
* Columns are opt-in. One column stays plain flow so a container does not
* quietly turn every page into a grid.
*/
.wire-container:not([data-columns="1"]) {
display: grid;
gap: var(--container-gap);
grid-template-columns: 1fr;
}
@media (min-width: 640px) {
.wire-container[data-columns="2"],
.wire-container[data-columns="3"],
.wire-container[data-columns="4"],
.wire-container[data-columns="5"],
.wire-container[data-columns="6"] {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.wire-container[data-size="default"],
.wire-container[data-size="comfortable"],
.wire-container[data-size="spacious"] {
padding-inline: 1.5rem;
}
}
@media (min-width: 1024px) {
.wire-container[data-columns="3"] {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.wire-container[data-columns="4"] {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.wire-container[data-columns="5"] {
grid-template-columns: repeat(5, minmax(0, 1fr));
}
.wire-container[data-columns="6"] {
grid-template-columns: repeat(6, minmax(0, 1fr));
}
.wire-container[data-size="default"],
.wire-container[data-size="comfortable"],
.wire-container[data-size="spacious"] {
padding-inline: 2rem;
}
}
}
} }
+121 -49
View File
@@ -1,69 +1,141 @@
// Divider -- a rule between sections, optionally labelled.
//
// <Divider />
// <Divider label="or" />
// <Divider orientation="vertical" />
//
// The label sits between two rules rather than on top of one, so the text
// never overlaps the line at any width.
//
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
// and silently swallows the rule that follows it.
component Divider { component Divider {
props { props {
size: string = "default" size: string = "default"
color: string = "primary" color: string = "primary"
label: string = "" label: string = ""
orientation: string = "horizontal" orientation: string = "horizontal"
variant: string = "solid"
class: string = "" class: string = ""
} }
functions {
shared function isVertical() {
return orientation === "vertical"
}
// The rule only takes the component colour when there is a label. A plain
// divider is chrome and should stay the border token.
shared function tone() {
return label ? color : "border"
}
}
view { view {
<div <div
{...attrs} {...attrs}
data-ui-component="Divider" data-ui-component="Divider"
class='wire-divider {class}'
data-size='{size}' data-size='{size}'
data-color='{color}' data-color='{color}'
data-tone='{tone()}'
data-variant='{variant}'
data-orientation='{orientation}' data-orientation='{orientation}'
class='w-full {class}' data-labelled='{label ? "true" : "false"}'
class:h-full='orientation === "vertical"' role="separator"
class:w-auto='orientation === "vertical"' aria-orientation='{isVertical() ? "vertical" : "horizontal"}'
> >
{#if orientation === "vertical"} <span class="wire-divider__rule" aria-hidden="true"></span>
<div <span class="wire-divider__label" data-show="label">{label}</span>
role="separator" <span class="wire-divider__rule" data-show="label" aria-hidden="true"></span>
aria-orientation="vertical"
class="mx-3 h-full min-h-6 border-l border-[var(--wire-color-border)]"
class:border-l-2='size === "lg"'
class:border-[var(--wire-color-primary)]='color === "primary"'
class:border-[var(--wire-color-secondary)]='color === "secondary"'
class:border-[var(--wire-color-success)]='color === "success"'
class:border-[var(--wire-color-warning)]='color === "warning"'
class:border-[var(--wire-color-danger)]='color === "danger"'
></div>
{:else}
<div
role="separator"
aria-orientation="horizontal"
class="flex w-full items-center gap-4"
>
<span
class="h-px flex-1 bg-[var(--wire-color-border)]"
class:h-0.5='size === "lg"'
class:bg-[var(--wire-color-primary)]='color === "primary" && label !== ""'
class:bg-[var(--wire-color-secondary)]='color === "secondary" && label !== ""'
class:bg-[var(--wire-color-success)]='color === "success" && label !== ""'
class:bg-[var(--wire-color-warning)]='color === "warning" && label !== ""'
class:bg-[var(--wire-color-danger)]='color === "danger" && label !== ""'
aria-hidden="true"
></span>
{#if label}
<span class="shrink-0 text-xs font-bold uppercase tracking-[0.16em] text-[var(--wire-color-text-muted)]">
{label}
</span>
<span
class="h-px flex-1 bg-[var(--wire-color-border)]"
class:h-0.5='size === "lg"'
class:bg-[var(--wire-color-primary)]='color === "primary"'
class:bg-[var(--wire-color-secondary)]='color === "secondary"'
class:bg-[var(--wire-color-success)]='color === "success"'
class:bg-[var(--wire-color-warning)]='color === "warning"'
class:bg-[var(--wire-color-danger)]='color === "danger"'
aria-hidden="true"
></span>
{/if}
</div>
{/if}
</div> </div>
} }
style {
.wire-divider {
--divider-tone: var(--wire-color-border);
--divider-thickness: 1px;
display: flex;
align-items: center;
gap: 1rem;
width: 100%;
min-width: 0;
}
.wire-divider[data-tone="primary"] {
--divider-tone: var(--wire-color-primary);
}
.wire-divider[data-tone="secondary"] {
--divider-tone: var(--wire-color-secondary);
}
.wire-divider[data-tone="success"] {
--divider-tone: var(--wire-color-success);
}
.wire-divider[data-tone="warning"] {
--divider-tone: var(--wire-color-warning);
}
.wire-divider[data-tone="danger"] {
--divider-tone: var(--wire-color-danger);
}
.wire-divider[data-size="lg"] {
--divider-thickness: 2px;
}
.wire-divider__rule {
flex: 1 1 auto;
height: var(--divider-thickness);
background: var(--divider-tone);
}
.wire-divider[data-variant="dashed"] .wire-divider__rule {
height: 0;
background: transparent;
border-top: var(--divider-thickness) dashed var(--divider-tone);
}
.wire-divider[data-variant="dotted"] .wire-divider__rule {
height: 0;
background: transparent;
border-top: var(--divider-thickness) dotted var(--divider-tone);
}
.wire-divider__label {
flex: 0 0 auto;
color: var(--wire-color-text-muted);
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.16em;
text-transform: uppercase;
}
.wire-divider[data-orientation="vertical"] {
flex-direction: column;
width: auto;
height: 100%;
min-height: 1.5rem;
margin-inline: 0.75rem;
}
.wire-divider[data-orientation="vertical"] .wire-divider__rule {
width: var(--divider-thickness);
height: auto;
}
.wire-divider[data-orientation="vertical"][data-variant="dashed"] .wire-divider__rule {
width: 0;
border-top: 0;
border-left: var(--divider-thickness) dashed var(--divider-tone);
}
.wire-divider[data-orientation="vertical"][data-variant="dotted"] .wire-divider__rule {
width: 0;
border-top: 0;
border-left: var(--divider-thickness) dotted var(--divider-tone);
}
}
} }
+117 -19
View File
@@ -1,3 +1,13 @@
// Grid -- an equal-track grid that collapses on small screens.
//
// <Grid columns={3} gap="lg">...</Grid>
//
// Pass minItemWidth to let the browser decide the count instead: the track
// list becomes auto-fit, which reflows continuously rather than at fixed
// breakpoints and suits card decks whose item count is not known.
//
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
// and silently swallows the rule that follows it.
component Grid { component Grid {
props { props {
size: string = "default" size: string = "default"
@@ -5,36 +15,124 @@ component Grid {
columns: number = 2 columns: number = 2
gap: string = "md" gap: string = "md"
maxWidth: string = "xl" maxWidth: string = "xl"
// When set, the count is derived from the available width instead of the
// columns prop. Accepts any CSS length.
minItemWidth: string = ""
class: string = "" class: string = ""
} }
functions {
shared function columnCount() {
var value = Number(columns)
if (!value || value < 1) {
return 1
}
return Math.min(6, value)
}
shared function autoTrack() {
return minItemWidth ? "repeat(auto-fit, minmax(" + minItemWidth + ", 1fr))" : ""
}
}
view { view {
<div <div
{...attrs}
data-ui-component="Grid" data-ui-component="Grid"
class='wire-grid {class}'
data-size='{size}' data-size='{size}'
data-color='{color}' data-color='{color}'
data-columns='{columns}'
data-gap='{gap}' data-gap='{gap}'
class='grid w-full grid-cols-1 {class}' data-max-width='{maxWidth}'
class:max-w-3xl='maxWidth === "md"' data-columns='{columnCount()}'
class:max-w-5xl='maxWidth === "lg"' data-auto='{minItemWidth ? "true" : "false"}'
class:max-w-7xl='maxWidth === "xl"' style='--grid-auto-track:{autoTrack()};'
class:max-w-screen-2xl='maxWidth === "2xl"'
class:max-w-none='maxWidth === "full"'
class:sm:grid-cols-2='columns >= 2'
class:lg:grid-cols-3='columns === 3'
class:lg:grid-cols-4='columns === 4'
class:lg:grid-cols-5='columns === 5'
class:lg:grid-cols-6='columns === 6'
class:gap-2='gap === "xs"'
class:gap-3='gap === "sm"'
class:gap-5='gap === "md"'
class:gap-8='gap === "lg"'
class:gap-10='gap === "xl"'
class:items-start='size === "compact"'
class:items-stretch='size !== "compact"'
> >
<slot></slot> <slot></slot>
</div> </div>
} }
style {
.wire-grid {
--grid-gap: 1.25rem;
display: grid;
width: 100%;
min-width: 0;
gap: var(--grid-gap);
grid-template-columns: 1fr;
align-items: stretch;
}
.wire-grid[data-size="compact"] {
align-items: start;
}
.wire-grid[data-max-width="md"] {
max-width: 48rem;
}
.wire-grid[data-max-width="lg"] {
max-width: 64rem;
}
.wire-grid[data-max-width="xl"] {
max-width: 80rem;
}
.wire-grid[data-max-width="2xl"] {
max-width: 96rem;
}
.wire-grid[data-max-width="full"] {
max-width: none;
}
.wire-grid[data-gap="xs"] {
--grid-gap: 0.5rem;
}
.wire-grid[data-gap="sm"] {
--grid-gap: 0.75rem;
}
.wire-grid[data-gap="lg"] {
--grid-gap: 2rem;
}
.wire-grid[data-gap="xl"] {
--grid-gap: 2.5rem;
}
/*
* auto-fit needs no breakpoints, so it wins over the fixed counts below
* and applies at every width.
*/
.wire-grid[data-auto="true"] {
grid-template-columns: var(--grid-auto-track);
}
@media (min-width: 640px) {
.wire-grid[data-auto="false"]:not([data-columns="1"]) {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (min-width: 1024px) {
.wire-grid[data-auto="false"][data-columns="3"] {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.wire-grid[data-auto="false"][data-columns="4"] {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.wire-grid[data-auto="false"][data-columns="5"] {
grid-template-columns: repeat(5, minmax(0, 1fr));
}
.wire-grid[data-auto="false"][data-columns="6"] {
grid-template-columns: repeat(6, minmax(0, 1fr));
}
}
}
} }
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ui", "name": "@wrnexus/ui",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "@wrnexus/ui — part of the WrNexus framework.", "description": "@wrnexus/ui — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -46,7 +46,7 @@
"./ui.css": "./ui.css" "./ui.css": "./ui.css"
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.5" "@wrnexus/core": "^0.8.6"
}, },
"files": [ "files": [
"dist", "dist",
-2012
View File
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/uploader", "name": "@wrnexus/uploader",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "Secure upload drivers, policies, client runtime, helper functions, and reusable upload components.", "description": "Secure upload drivers, policies, client runtime, helper functions, and reusable upload components.",
"license": "MIT", "license": "MIT",
@@ -42,9 +42,9 @@
"./components/*": "./components/*" "./components/*": "./components/*"
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.5", "@wrnexus/core": "^0.8.6",
"@wrnexus/plugin": "^0.8.5", "@wrnexus/plugin": "^0.8.6",
"@wrnexus/ui": "^0.8.5" "@wrnexus/ui": "^0.8.6"
}, },
"wrnexus": { "wrnexus": {
"plugin": { "plugin": {
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/validation", "name": "@wrnexus/validation",
"version": "0.8.5", "version": "0.8.6",
"type": "module", "type": "module",
"description": "Shared server/browser schemas, validation helpers, form runtime, and reusable error components.", "description": "Shared server/browser schemas, validation helpers, form runtime, and reusable error components.",
"license": "MIT", "license": "MIT",
@@ -42,9 +42,9 @@
"./components/*": "./components/*" "./components/*": "./components/*"
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.5", "@wrnexus/core": "^0.8.6",
"@wrnexus/plugin": "^0.8.5", "@wrnexus/plugin": "^0.8.6",
"@wrnexus/ui": "^0.8.5" "@wrnexus/ui": "^0.8.6"
}, },
"wrnexus": { "wrnexus": {
"plugin": { "plugin": {
+398 -49
View File
@@ -105,13 +105,114 @@
"typescript": "^5.9.2", "typescript": "^5.9.2",
}, },
}, },
"examples/inter-app-api-showcase": {
"name": "inter-app-api-showcase",
"devDependencies": {
"@eslint/js": "^9.0.0",
"@types/bun": "^1.3.14",
"@wrnexus/cli": "0.8.6",
"eslint": "^9.0.0",
"prettier": "^3.9.6",
"typescript": "^5.5.0",
"typescript-eslint": "^8.65.0",
},
},
"examples/inter-app-api-showcase/apps/admin": {
"name": "admin",
"version": "0.1.0",
"dependencies": {
"@wrnexus/ai": "0.8.6",
"@wrnexus/auth": "0.8.6",
"@wrnexus/authz": "0.8.6",
"@wrnexus/captcha": "0.8.6",
"@wrnexus/core": "0.8.6",
"@wrnexus/csr": "0.8.6",
"@wrnexus/db": "0.8.6",
"@wrnexus/dev-server": "0.8.6",
"@wrnexus/encryption": "0.8.6",
"@wrnexus/helpers": "0.8.6",
"@wrnexus/i18n": "0.8.6",
"@wrnexus/image": "0.8.6",
"@wrnexus/jwt": "0.8.6",
"@wrnexus/observability": "0.8.6",
"@wrnexus/realtime": "0.8.6",
"@wrnexus/security": "0.8.6",
"@wrnexus/store": "0.8.6",
"@wrnexus/styles": "0.8.6",
"@wrnexus/tracking": "0.8.6",
"@wrnexus/ui": "0.8.6",
"@wrnexus/uploader": "0.8.6",
"@wrnexus/validation": "0.8.6",
},
"devDependencies": {
"@eslint/js": "^9.0.0",
"@iconify-json/lucide": "^1.2.118",
"@iconify/tailwind4": "^1.2.3",
"@tailwindcss/cli": "^4.0.0",
"@types/bun": "^1.3.14",
"@wrnexus/cli": "0.8.6",
"eslint": "^9.0.0",
"prettier": "^3.9.6",
"tailwindcss": "^4.0.0",
"typescript": "^5.5.0",
"typescript-eslint": "^8.65.0",
},
},
"examples/inter-app-api-showcase/apps/web": {
"name": "web",
"version": "0.1.0",
"dependencies": {
"@wrnexus/ai": "0.8.6",
"@wrnexus/auth": "0.8.6",
"@wrnexus/authz": "0.8.6",
"@wrnexus/captcha": "0.8.6",
"@wrnexus/core": "0.8.6",
"@wrnexus/csr": "0.8.6",
"@wrnexus/db": "0.8.6",
"@wrnexus/dev-server": "0.8.6",
"@wrnexus/encryption": "0.8.6",
"@wrnexus/helpers": "0.8.6",
"@wrnexus/i18n": "0.8.6",
"@wrnexus/image": "0.8.6",
"@wrnexus/jwt": "0.8.6",
"@wrnexus/observability": "0.8.6",
"@wrnexus/realtime": "0.8.6",
"@wrnexus/security": "0.8.6",
"@wrnexus/store": "0.8.6",
"@wrnexus/styles": "0.8.6",
"@wrnexus/tracking": "0.8.6",
"@wrnexus/ui": "0.8.6",
"@wrnexus/uploader": "0.8.6",
"@wrnexus/validation": "0.8.6",
},
"devDependencies": {
"@eslint/js": "^9.0.0",
"@iconify-json/lucide": "^1.2.118",
"@iconify/tailwind4": "^1.2.3",
"@tailwindcss/cli": "^4.0.0",
"@types/bun": "^1.3.14",
"@wrnexus/cli": "0.8.6",
"eslint": "^9.0.0",
"prettier": "^3.9.6",
"tailwindcss": "^4.0.0",
"typescript": "^5.5.0",
"typescript-eslint": "^8.65.0",
},
},
"examples/inter-app-api-showcase/packages/shared": {
"name": "@app/shared",
"version": "0.0.0",
"dependencies": {
"@wrnexus/pubsub": "0.8.6",
},
},
"packages/ai": { "packages/ai": {
"name": "@wrnexus/ai", "name": "@wrnexus/ai",
"version": "0.8.5", "version": "0.8.6",
}, },
"packages/auth": { "packages/auth": {
"name": "@wrnexus/auth", "name": "@wrnexus/auth",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/authz": "workspace:*", "@wrnexus/authz": "workspace:*",
"@wrnexus/captcha": "workspace:*", "@wrnexus/captcha": "workspace:*",
@@ -133,7 +234,7 @@
}, },
"packages/authz": { "packages/authz": {
"name": "@wrnexus/authz", "name": "@wrnexus/authz",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/db": "workspace:*", "@wrnexus/db": "workspace:*",
@@ -141,18 +242,18 @@
}, },
"packages/benchmark": { "packages/benchmark": {
"name": "@wrnexus/benchmark", "name": "@wrnexus/benchmark",
"version": "0.8.5", "version": "0.8.6",
}, },
"packages/cache": { "packages/cache": {
"name": "@wrnexus/cache", "name": "@wrnexus/cache",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
}, },
"packages/captcha": { "packages/captcha": {
"name": "@wrnexus/captcha", "name": "@wrnexus/captcha",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
@@ -167,7 +268,7 @@
}, },
"packages/cli": { "packages/cli": {
"name": "@wrnexus/cli", "name": "@wrnexus/cli",
"version": "0.8.5", "version": "0.8.6",
"bin": { "bin": {
"wrnexus": "src/index.ts", "wrnexus": "src/index.ts",
}, },
@@ -194,7 +295,7 @@
}, },
"packages/compiler": { "packages/compiler": {
"name": "@wrnexus/compiler", "name": "@wrnexus/compiler",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/csr": "workspace:*", "@wrnexus/csr": "workspace:*",
"@wrnexus/store": "workspace:*", "@wrnexus/store": "workspace:*",
@@ -204,7 +305,7 @@
}, },
"packages/content": { "packages/content": {
"name": "@wrnexus/content", "name": "@wrnexus/content",
"version": "0.8.5", "version": "0.8.6",
"devDependencies": { "devDependencies": {
"@types/bun": "^1.3.14", "@types/bun": "^1.3.14",
"typescript": "^5.9.2", "typescript": "^5.9.2",
@@ -212,18 +313,18 @@
}, },
"packages/core": { "packages/core": {
"name": "@wrnexus/core", "name": "@wrnexus/core",
"version": "0.8.5", "version": "0.8.6",
}, },
"packages/csr": { "packages/csr": {
"name": "@wrnexus/csr", "name": "@wrnexus/csr",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
}, },
"packages/db": { "packages/db": {
"name": "@wrnexus/db", "name": "@wrnexus/db",
"version": "0.8.5", "version": "0.8.6",
"devDependencies": { "devDependencies": {
"@types/bun": "^1.3.14", "@types/bun": "^1.3.14",
"typescript": "^5.9.2", "typescript": "^5.9.2",
@@ -231,7 +332,7 @@
}, },
"packages/dev-server": { "packages/dev-server": {
"name": "@wrnexus/dev-server", "name": "@wrnexus/dev-server",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/authz": "workspace:*", "@wrnexus/authz": "workspace:*",
"@wrnexus/cache": "workspace:*", "@wrnexus/cache": "workspace:*",
@@ -258,7 +359,7 @@
}, },
"packages/dev-toolbar": { "packages/dev-toolbar": {
"name": "@wrnexus/dev-toolbar", "name": "@wrnexus/dev-toolbar",
"version": "0.8.5", "version": "0.8.6",
"devDependencies": { "devDependencies": {
"@types/bun": "^1.3.14", "@types/bun": "^1.3.14",
"typescript": "^5.9.2", "typescript": "^5.9.2",
@@ -266,7 +367,7 @@
}, },
"packages/encryption": { "packages/encryption": {
"name": "@wrnexus/encryption", "name": "@wrnexus/encryption",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
@@ -277,21 +378,21 @@
}, },
"packages/graphql": { "packages/graphql": {
"name": "@wrnexus/graphql", "name": "@wrnexus/graphql",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
}, },
}, },
"packages/helpers": { "packages/helpers": {
"name": "@wrnexus/helpers", "name": "@wrnexus/helpers",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
}, },
"packages/i18n": { "packages/i18n": {
"name": "@wrnexus/i18n", "name": "@wrnexus/i18n",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
@@ -305,7 +406,7 @@
}, },
"packages/identity": { "packages/identity": {
"name": "@wrnexus/identity", "name": "@wrnexus/identity",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/auth": "workspace:*", "@wrnexus/auth": "workspace:*",
"@wrnexus/authz": "workspace:*", "@wrnexus/authz": "workspace:*",
@@ -314,7 +415,7 @@
}, },
"packages/image": { "packages/image": {
"name": "@wrnexus/image", "name": "@wrnexus/image",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
"@wrnexus/security": "workspace:*", "@wrnexus/security": "workspace:*",
@@ -334,7 +435,7 @@
}, },
"packages/jwt": { "packages/jwt": {
"name": "@wrnexus/jwt", "name": "@wrnexus/jwt",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
@@ -345,7 +446,7 @@
}, },
"packages/language-server": { "packages/language-server": {
"name": "@wrnexus/language-server", "name": "@wrnexus/language-server",
"version": "0.8.5", "version": "0.8.6",
"bin": { "bin": {
"wrnexus-language-server": "src/server.ts", "wrnexus-language-server": "src/server.ts",
}, },
@@ -356,39 +457,39 @@
}, },
"packages/mcp": { "packages/mcp": {
"name": "@wrnexus/mcp", "name": "@wrnexus/mcp",
"version": "0.8.5", "version": "0.8.6",
"bin": { "bin": {
"wrnexus-mcp": "src/stdio.ts", "wrnexus-mcp": "src/stdio.ts",
}, },
}, },
"packages/mobile": { "packages/mobile": {
"name": "@wrnexus/mobile", "name": "@wrnexus/mobile",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/native": "workspace:*", "@wrnexus/native": "workspace:*",
}, },
}, },
"packages/native": { "packages/native": {
"name": "@wrnexus/native", "name": "@wrnexus/native",
"version": "0.8.5", "version": "0.8.6",
}, },
"packages/oauth": { "packages/oauth": {
"name": "@wrnexus/oauth", "name": "@wrnexus/oauth",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/jwt": "workspace:*", "@wrnexus/jwt": "workspace:*",
}, },
}, },
"packages/observability": { "packages/observability": {
"name": "@wrnexus/observability", "name": "@wrnexus/observability",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
}, },
"packages/playground": { "packages/playground": {
"name": "@wrnexus/playground", "name": "@wrnexus/playground",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/compiler": "workspace:*", "@wrnexus/compiler": "workspace:*",
"@wrnexus/syntax": "workspace:*", "@wrnexus/syntax": "workspace:*",
@@ -396,18 +497,22 @@
}, },
"packages/plugin": { "packages/plugin": {
"name": "@wrnexus/plugin", "name": "@wrnexus/plugin",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/syntax": "workspace:*", "@wrnexus/syntax": "workspace:*",
}, },
}, },
"packages/pubsub": { "packages/pubsub": {
"name": "@wrnexus/pubsub", "name": "@wrnexus/pubsub",
"version": "0.8.5", "version": "0.8.6",
"dependencies": {
"@wrnexus/core": "workspace:*",
"@wrnexus/rpc": "workspace:*",
},
}, },
"packages/pwa": { "packages/pwa": {
"name": "@wrnexus/pwa", "name": "@wrnexus/pwa",
"version": "0.8.5", "version": "0.8.6",
"devDependencies": { "devDependencies": {
"@types/bun": "^1.3.14", "@types/bun": "^1.3.14",
"typescript": "^5.9.2", "typescript": "^5.9.2",
@@ -415,18 +520,19 @@
}, },
"packages/queue": { "packages/queue": {
"name": "@wrnexus/queue", "name": "@wrnexus/queue",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/rpc": "workspace:*",
}, },
}, },
"packages/reactive": { "packages/reactive": {
"name": "@wrnexus/reactive", "name": "@wrnexus/reactive",
"version": "0.8.5", "version": "0.8.6",
}, },
"packages/realtime": { "packages/realtime": {
"name": "@wrnexus/realtime", "name": "@wrnexus/realtime",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
@@ -440,7 +546,7 @@
}, },
"packages/router": { "packages/router": {
"name": "@wrnexus/router", "name": "@wrnexus/router",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/compiler": "workspace:*", "@wrnexus/compiler": "workspace:*",
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
@@ -448,9 +554,8 @@
}, },
"packages/rpc": { "packages/rpc": {
"name": "@wrnexus/rpc", "name": "@wrnexus/rpc",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/authz": "workspace:*",
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/helpers": "workspace:*", "@wrnexus/helpers": "workspace:*",
"@wrnexus/jwt": "workspace:*", "@wrnexus/jwt": "workspace:*",
@@ -463,14 +568,14 @@
}, },
"packages/security": { "packages/security": {
"name": "@wrnexus/security", "name": "@wrnexus/security",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
}, },
"packages/ssr": { "packages/ssr": {
"name": "@wrnexus/ssr", "name": "@wrnexus/ssr",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/security": "workspace:*", "@wrnexus/security": "workspace:*",
@@ -479,11 +584,11 @@
}, },
"packages/store": { "packages/store": {
"name": "@wrnexus/store", "name": "@wrnexus/store",
"version": "0.8.5", "version": "0.8.6",
}, },
"packages/styles": { "packages/styles": {
"name": "@wrnexus/styles", "name": "@wrnexus/styles",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
@@ -492,19 +597,19 @@
}, },
"packages/syntax": { "packages/syntax": {
"name": "@wrnexus/syntax", "name": "@wrnexus/syntax",
"version": "0.8.5", "version": "0.8.6",
}, },
"packages/test": { "packages/test": {
"name": "@wrnexus/test", "name": "@wrnexus/test",
"version": "0.8.5", "version": "0.8.6",
}, },
"packages/tracking": { "packages/tracking": {
"name": "@wrnexus/tracking", "name": "@wrnexus/tracking",
"version": "0.8.5", "version": "0.8.6",
}, },
"packages/typecheck": { "packages/typecheck": {
"name": "@wrnexus/typecheck", "name": "@wrnexus/typecheck",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/syntax": "workspace:*", "@wrnexus/syntax": "workspace:*",
"typescript": "^5.5.0", "typescript": "^5.5.0",
@@ -512,14 +617,14 @@
}, },
"packages/ui": { "packages/ui": {
"name": "@wrnexus/ui", "name": "@wrnexus/ui",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
}, },
"packages/uploader": { "packages/uploader": {
"name": "@wrnexus/uploader", "name": "@wrnexus/uploader",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
@@ -533,7 +638,7 @@
}, },
"packages/validation": { "packages/validation": {
"name": "@wrnexus/validation", "name": "@wrnexus/validation",
"version": "0.8.5", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
@@ -547,7 +652,7 @@
}, },
"services/managed-captcha": { "services/managed-captcha": {
"name": "@wrnexus/managed-captcha-service", "name": "@wrnexus/managed-captcha-service",
"version": "0.8.0", "version": "0.8.6",
"dependencies": { "dependencies": {
"@wrnexus/captcha": "workspace:*", "@wrnexus/captcha": "workspace:*",
}, },
@@ -564,6 +669,8 @@
"packages": { "packages": {
"@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="], "@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="],
"@app/shared": ["@app/shared@workspace:examples/inter-app-api-showcase/packages/shared"],
"@cyberalien/svg-utils": ["@cyberalien/svg-utils@1.2.19", "", { "dependencies": { "@iconify/types": "^2.0.0" } }, "sha512-paDJoDu+LhuH5Ma1q0BDqBpg6d16Xk22cZZrlg/s/J9oB8KgnnpU/snaEETJu2G1pwYcWQINUz6WUqu0Wm9k6A=="], "@cyberalien/svg-utils": ["@cyberalien/svg-utils@1.2.19", "", { "dependencies": { "@iconify/types": "^2.0.0" } }, "sha512-paDJoDu+LhuH5Ma1q0BDqBpg6d16Xk22cZZrlg/s/J9oB8KgnnpU/snaEETJu2G1pwYcWQINUz6WUqu0Wm9k6A=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
@@ -628,6 +735,8 @@
"@eslint/core": ["@eslint/core@1.2.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ=="], "@eslint/core": ["@eslint/core@1.2.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ=="],
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.6", "", { "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.3.0", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" } }, "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA=="],
"@eslint/js": ["@eslint/js@10.0.1", "", { "peerDependencies": { "eslint": "^10.0.0" }, "optionalPeers": ["eslint"] }, "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA=="], "@eslint/js": ["@eslint/js@10.0.1", "", { "peerDependencies": { "eslint": "^10.0.0" }, "optionalPeers": ["eslint"] }, "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA=="],
"@eslint/object-schema": ["@eslint/object-schema@3.0.5", "", {}, "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw=="], "@eslint/object-schema": ["@eslint/object-schema@3.0.5", "", {}, "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw=="],
@@ -932,10 +1041,16 @@
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
"admin": ["admin@workspace:examples/inter-app-api-showcase/apps/admin"],
"ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], "ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="],
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="],
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"asn1js": ["asn1js@3.0.10", "", { "dependencies": { "pvtsutils": "^1.3.6", "pvutils": "^1.1.5", "tslib": "^2.8.1" } }, "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg=="], "asn1js": ["asn1js@3.0.10", "", { "dependencies": { "pvtsutils": "^1.3.6", "pvutils": "^1.1.5", "tslib": "^2.8.1" } }, "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg=="],
"auth-showcase": ["auth-showcase@workspace:examples/auth-showcase"], "auth-showcase": ["auth-showcase@workspace:examples/auth-showcase"],
@@ -960,10 +1075,18 @@
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
"captcha-showcase": ["captcha-showcase@workspace:examples/captcha-showcase"], "captcha-showcase": ["captcha-showcase@workspace:examples/captcha-showcase"],
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
"component-showcase": ["component-showcase@workspace:examples/component-showcase"], "component-showcase": ["component-showcase@workspace:examples/component-showcase"],
@@ -1046,18 +1169,26 @@
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
"globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"happy-dom": ["happy-dom@20.11.1", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "buffer-image-size": "^0.6.4", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.21.0" } }, "sha512-XSt8tMzbW9ymE7687xztkO1ckR7qJNQ3LywY9vlYGhGi3zXrGBHuUo2Cl1ztZaICW+1eAGdkLbj6iwVqDT33kg=="], "happy-dom": ["happy-dom@20.11.1", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "buffer-image-size": "^0.6.4", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.21.0" } }, "sha512-XSt8tMzbW9ymE7687xztkO1ckR7qJNQ3LywY9vlYGhGi3zXrGBHuUo2Cl1ztZaICW+1eAGdkLbj6iwVqDT33kg=="],
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
"i18n-showcase": ["i18n-showcase@workspace:examples/i18n-showcase"], "i18n-showcase": ["i18n-showcase@workspace:examples/i18n-showcase"],
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
"import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="],
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
"inter-app-api-showcase": ["inter-app-api-showcase@workspace:examples/inter-app-api-showcase"],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
@@ -1070,6 +1201,8 @@
"joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="],
"js-yaml": ["js-yaml@4.3.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ=="],
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
@@ -1112,6 +1245,8 @@
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
"mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="], "mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="],
@@ -1146,6 +1281,8 @@
"package-manager-detector": ["package-manager-detector@1.8.0", "", {}, "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A=="], "package-manager-detector": ["package-manager-detector@1.8.0", "", {}, "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A=="],
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
@@ -1196,8 +1333,12 @@
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
"sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="],
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"svgo": ["svgo@4.0.2", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.5.0" }, "bin": "./bin/svgo.js" }, "sha512-ekx94z1rRc5LDi6oSUaeRnYhd0UOJxdtQCL2rF8xpWxD3TPAsISWOrxezqGovqS38GRZOdpDfvQe3ts6F7nsng=="], "svgo": ["svgo@4.0.2", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.5.0" }, "bin": "./bin/svgo.js" }, "sha512-ekx94z1rRc5LDi6oSUaeRnYhd0UOJxdtQCL2rF8xpWxD3TPAsISWOrxezqGovqS38GRZOdpDfvQe3ts6F7nsng=="],
"tailwindcss": ["tailwindcss@4.3.2", "", {}, "sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA=="], "tailwindcss": ["tailwindcss@4.3.2", "", {}, "sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA=="],
@@ -1238,6 +1379,8 @@
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"web": ["web@workspace:examples/inter-app-api-showcase/apps/web"],
"whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="], "whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
@@ -1252,6 +1395,10 @@
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"@eslint/eslintrc/espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
"@eslint/eslintrc/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="],
@@ -1266,10 +1413,24 @@
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
"admin/@eslint/js": ["@eslint/js@9.39.5", "", {}, "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A=="],
"admin/eslint": ["eslint@9.39.5", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.6", "@eslint/js": "9.39.5", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw=="],
"admin/typescript-eslint": ["typescript-eslint@8.66.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.66.0", "@typescript-eslint/parser": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0", "@typescript-eslint/utils": "8.66.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-QlEbBPz/RuJ1XUHj29nm3t0F/O/cSlEnntozqPOYHnnTGAXFamnMBu5i9Vn6vhUPHGAjR+Vl+5J8vPN/BMUrJw=="],
"csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="], "csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="],
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], "dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
"import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
"inter-app-api-showcase/@eslint/js": ["@eslint/js@9.39.5", "", {}, "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A=="],
"inter-app-api-showcase/eslint": ["eslint@9.39.5", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.6", "@eslint/js": "9.39.5", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw=="],
"inter-app-api-showcase/typescript-eslint": ["typescript-eslint@8.66.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.66.0", "@typescript-eslint/parser": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0", "@typescript-eslint/utils": "8.66.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-QlEbBPz/RuJ1XUHj29nm3t0F/O/cSlEnntozqPOYHnnTGAXFamnMBu5i9Vn6vhUPHGAjR+Vl+5J8vPN/BMUrJw=="],
"lightningcss/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "lightningcss/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
@@ -1278,6 +1439,194 @@
"tsyringe/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], "tsyringe/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="],
"web/@eslint/js": ["@eslint/js@9.39.5", "", {}, "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A=="],
"web/eslint": ["eslint@9.39.5", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.6", "@eslint/js": "9.39.5", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw=="],
"web/typescript-eslint": ["typescript-eslint@8.66.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.66.0", "@typescript-eslint/parser": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0", "@typescript-eslint/utils": "8.66.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-QlEbBPz/RuJ1XUHj29nm3t0F/O/cSlEnntozqPOYHnnTGAXFamnMBu5i9Vn6vhUPHGAjR+Vl+5J8vPN/BMUrJw=="],
"@eslint/eslintrc/espree/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
"admin/eslint/@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="],
"admin/eslint/@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="],
"admin/eslint/@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
"admin/eslint/@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
"admin/eslint/eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
"admin/eslint/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
"admin/eslint/espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
"admin/eslint/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
"admin/typescript-eslint/@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.66.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.66.0", "@typescript-eslint/type-utils": "8.66.0", "@typescript-eslint/utils": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.66.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A=="],
"admin/typescript-eslint/@typescript-eslint/parser": ["@typescript-eslint/parser@8.66.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g=="],
"admin/typescript-eslint/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.66.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.66.0", "@typescript-eslint/tsconfig-utils": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg=="],
"admin/typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.66.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA=="],
"csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="], "csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="],
"inter-app-api-showcase/eslint/@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="],
"inter-app-api-showcase/eslint/@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="],
"inter-app-api-showcase/eslint/@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
"inter-app-api-showcase/eslint/@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
"inter-app-api-showcase/eslint/eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
"inter-app-api-showcase/eslint/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
"inter-app-api-showcase/eslint/espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
"inter-app-api-showcase/eslint/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.66.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.66.0", "@typescript-eslint/type-utils": "8.66.0", "@typescript-eslint/utils": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.66.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A=="],
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/parser": ["@typescript-eslint/parser@8.66.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g=="],
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.66.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.66.0", "@typescript-eslint/tsconfig-utils": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg=="],
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.66.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA=="],
"web/eslint/@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="],
"web/eslint/@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="],
"web/eslint/@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
"web/eslint/@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
"web/eslint/eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
"web/eslint/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
"web/eslint/espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
"web/eslint/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
"web/typescript-eslint/@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.66.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.66.0", "@typescript-eslint/type-utils": "8.66.0", "@typescript-eslint/utils": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.66.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A=="],
"web/typescript-eslint/@typescript-eslint/parser": ["@typescript-eslint/parser@8.66.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g=="],
"web/typescript-eslint/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.66.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.66.0", "@typescript-eslint/tsconfig-utils": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg=="],
"web/typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.66.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA=="],
"admin/eslint/@eslint/config-array/@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
"admin/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0" } }, "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g=="],
"admin/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0", "@typescript-eslint/utils": "8.66.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-LG2dWfjZQQp0ADtAu/EWJVayefGL2UEZ3CDeI44D9v3rXB/WYUqE/jpO28KrEKul5AySrmI+Zh1v6v+xW2U9+g=="],
"admin/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
"admin/typescript-eslint/@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
"admin/typescript-eslint/@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0" } }, "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g=="],
"admin/typescript-eslint/@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"admin/typescript-eslint/@typescript-eslint/parser/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
"admin/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.66.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.66.0", "@typescript-eslint/types": "^8.66.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g=="],
"admin/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.66.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g=="],
"admin/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"admin/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
"admin/typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0" } }, "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g=="],
"admin/typescript-eslint/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"inter-app-api-showcase/eslint/@eslint/config-array/@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0" } }, "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g=="],
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0", "@typescript-eslint/utils": "8.66.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-LG2dWfjZQQp0ADtAu/EWJVayefGL2UEZ3CDeI44D9v3rXB/WYUqE/jpO28KrEKul5AySrmI+Zh1v6v+xW2U9+g=="],
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0" } }, "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g=="],
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/parser/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.66.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.66.0", "@typescript-eslint/types": "^8.66.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g=="],
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.66.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g=="],
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0" } }, "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g=="],
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"web/eslint/@eslint/config-array/@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
"web/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0" } }, "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g=="],
"web/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0", "@typescript-eslint/utils": "8.66.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-LG2dWfjZQQp0ADtAu/EWJVayefGL2UEZ3CDeI44D9v3rXB/WYUqE/jpO28KrEKul5AySrmI+Zh1v6v+xW2U9+g=="],
"web/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
"web/typescript-eslint/@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
"web/typescript-eslint/@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0" } }, "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g=="],
"web/typescript-eslint/@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"web/typescript-eslint/@typescript-eslint/parser/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
"web/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.66.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.66.0", "@typescript-eslint/types": "^8.66.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g=="],
"web/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.66.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g=="],
"web/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"web/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
"web/typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0" } }, "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g=="],
"web/typescript-eslint/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"admin/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"admin/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"admin/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"admin/typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"inter-app-api-showcase/typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
"web/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"web/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"web/typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="],
"web/typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="],
} }
} }
+883
View File
@@ -0,0 +1,883 @@
# Framework remediation plan
**Status: completed.** The
repository has 102 maintained UI components, zero
undeclared non-native output emitters, zero components matching the scaffold
definition, and local styles on every component. The core reactive runtime is
48,124 minified bytes; component controllers ship separately at 24,027 bytes.
The §4.1 ownership migration is complete: `ui.css` contains global and
cross-cutting primitives only. Shared component foundations are carried by
three internal style-only components and render on demand; the former global
component families and standalone `wire-next` generation class are absent.
Final validation passed the production, package, example, service, editor,
showcase-generation, typecheck, lint, formatting, public-API, visual-contract,
security, and runtime-size gates. The only skipped test is the explicitly
environment-dependent live PostgreSQL/MySQL test.
Production CSS delivery now links the selected palette/accent sheet (6,632
decoded bytes for dark/violet) instead of the complete 74,764-byte theme
matrix. The compatibility `theme.css` endpoint remains available but is not
linked by generated pages. Generated application declarations are committed
and checked for drift by `check:production`.
Written 2026-08-09, after the 0.8.6 release. Every number here was measured
against the completed tree, not estimated. Where a cause is not yet
proven the item says so and makes proving it step one — nothing in this document
should be implemented on the strength of a guess.
Each work item has the same four fields:
- **Issue** — what is wrong, stated so it can be disagreed with.
- **Evidence** — how we know, with the file or the measurement.
- **Change** — what to do.
- **How to test** — the check that fails before and passes after. If an item's
test cannot fail before the change, the test is wrong; see the note on
vacuous gates in §0.2.
---
## 0. Standing conventions
These are not tasks. They are the rules the tasks below are written against, and
they apply to all new work from now on.
### 0.1 A component owns everything it needs
**Every `.wrn` component is self-contained.** Markup, behaviour and styles live
in that one file. A component that needs a rule writes it in its own `style {}`
block, scoped to its own classes.
`ui.css` holds **global styles only** — resets, tokens, base element styling and
genuinely cross-cutting primitives. It is not a place to put component rules,
and no new component rule may be added to it. The reason is delivery: a
component's own styles ship only when that component renders, while `ui.css` is
served whole to every page on the site. See §4.1 for the migration and the
numbers.
Practical rules:
- CSS comments in a `style {}` block are `/* */` only. `//` is not a CSS comment
and silently swallows the rule that follows it.
- Style the component's own BEM classes (`wire-thing`, `wire-thing__part`).
Variants are data attributes: `[data-variant="soft"]`, not a class explosion.
- No Tailwind utility classes in component markup. See §4.3.
### 0.2 A test that cannot fail is not a test
Two gates in this repo passed for months while testing nothing, because both
were written against `getBoundingClientRect()`, which returns zeros in
happy-dom. The 0.8.5 focus trap had no coverage at all as a result.
**Before committing any test in this plan, delete the fix, watch the test fail,
then restore the fix.** If it passes with the fix removed, it is measuring
nothing. Never gate testable behaviour on measured geometry.
---
## 1. Bugs from this session
All five are **fixed and shipped in 0.8.6**. They are recorded here because each
one has a permanent guard that must not be removed, and because the pattern they
share is the subject of §2.
### 1.1 camelCase outputs never reached a parent binding — FIXED
**Issue.** HTML lowercases attribute names, so a parent writing `@sizeChange`
registered a handler under `sizechange`, while the component emitted
`sizeChange`. The registry lookup missed, fell through to a DOM dispatch, and
the parent binding was never invoked. No error at either end.
**Evidence.** All 17 camelCase outputs in the library were undeliverable:
`DataTable.pageChange`, `DataTable.rowClick`, `Map.markerClick`,
`ChatBubble.messageClick`, `LayoutSplitter.sizeChange`, the four `carousel.*`
and four `DragAndDrop.*` names, and the rest. Confirmed in a browser by reading
`__wrnexusOutputHandlers` off the component root and seeing the key casing.
**Change.** Done — `invokeComponentOutput` in
`packages/csr/src/reactive-runtime.ts` now falls back to a case-insensitive
lookup.
**How to test.**
```bash
bun test packages/csr/test/reactive.test.ts -t "camelCase output"
```
Verified failing with the fallback removed. Keep it that way.
### 1.2 Eighteen components emitted into the void — FIXED
**Issue.** An output only reaches a parent `@binding` when the component calls
`output.<name>()`. The runtime resolves parent handlers from a registry that
only `invokeComponentOutput` reads, so a component dispatching its own
`CustomEvent` — even a bubbling one on its own root — emits to nobody.
**Evidence.** 18 components did this. Card, Footer, Breadcrumb, Accordion,
alert, Badge, AnnouncementBar, AvatarGroup, ToggleCount and InputNumber were
converted; Marquee, Map, Timeline, List and SearchBox additionally had to
_declare_ the outputs they were already firing.
**Change.** Done. Note the deliberate exception: dispatching on `window` is
still correct and is left alone — that is how Toaster, Modal and DataTable
signal across component boundaries, where there is no parent binding to reach.
**How to test.**
```bash
bun test packages/ui/test/ui.test.ts -t "hand-built CustomEvent"
```
### 1.3 Ten theme tokens were referenced and never defined — FIXED
**Issue.** An undefined custom property does not warn; it resolves to nothing.
Focus rings drew with no colour in 14 components, every soft surface rendered
transparent in 27.
**Change.** Done — derived in `packages/styles/src/theme.ts`. Two traps worth
remembering: a check that reads the theme _source_ produces about a dozen false
positives because most tokens are generated per palette, so any such check must
read the **rendered** CSS; and the semantic spread must sit _before_ the
primary/secondary palette entries or it overrides them.
**How to test.** Load a page, then in the console collect every `var(--wire-*)`
referenced by stylesheet rules and check each against
`getComputedStyle(document.documentElement)`. Expect 38 referenced, 0 undefined.
### 1.4 The modal focus trap had no coverage — FIXED
**Issue.** `isDialogVisible` and `focusableWithin` both gated on
`getBoundingClientRect()`, which is all zeros in happy-dom. The trap never ran
under test.
**Change.** Done — both read markers instead. One regression to remember: the
first fix used `data-show`, which broke Drawer, because a Drawer _animates_ open
and so cannot use `data-show`; every closed Drawer then looked open and held
`body { overflow: hidden }` forever. It reads `data-open` now.
**How to test.** `bun test packages/csr` — the dialog tests must fail if the
marker checks are reverted to geometry.
### 1.5 Roving focus matched `="false"` — FIXED
**Issue.** Attribute-presence selectors match any value, including `"false"`, so
read-only Steppers captured arrow keys from the page.
**Change.** Done — `rovingItemOff` treats bare as on and `"false"` as off.
**How to test.** `bun test packages/csr` roving cases; a read-only Stepper must
not consume `ArrowRight`.
---
## 2. The model is incomplete
This section is the real work. These are capability gaps, not defects, and they
are what currently limits what anyone can build. **§2.1 is the highest-value
item in this entire document.**
### 2.1 Development diagnostics — FIXED
**Issue.** Nothing in §1 produced a single diagnostic at any layer. That is not
five unlucky bugs; it is a systemic property. The failure mode is always
"nothing happens", which is the most expensive kind to debug because a bug is
indistinguishable from something you configured wrong.
**Evidence.** `packages/csr/src/reactive-runtime.ts` has 55 `catch` blocks, 11
of which swallow entirely, and 18 `console.error`/`console.warn` calls across
~70 kB of runtime. Undefined theme token → renders nothing. Wrong output casing
→ delivers nothing. Deferred state write → dropped. Component in a slot →
dropped. Object prop written inline → parsed as interpolation. None warn.
**Change.** Add a **dev-mode diagnostic layer**, stripped from production
builds. It should warn on at least:
1. `output.<name>()` called with no handler registered **and** no listener for
the event — include the registry keys actually present, which is exactly what
would have made §1.1 a five-minute bug.
2. A `@binding` naming a function that does not exist in scope.
3. A `var(--wire-*)` referenced by a component but absent from the rendered
theme.
4. A state write discarded because it happened after the client function
returned (see §2.2).
5. A component tag dropped during slot flattening (see §2.4).
6. A prop declared as an object or array that arrived as an unparsed string.
Gate it on `process.env.NODE_ENV !== "production"` so the production bundle is
byte-identical to today. Route everything through one `warnOnce(code, message)`
helper with stable `WRN-` codes, so warnings are greppable and documentable and
a loop cannot spam the console.
**How to test.** A test per diagnostic that asserts the warning fires, plus one
asserting the production build contains none of them:
```bash
bun test packages/csr/test/diagnostics.test.ts
bun run build && grep -c "WRN-DEV-" examples/basic-app/dist/reactive.js # expect 0
```
The production size ratchet now caps the split core runtime at 49,000 bytes; it
currently measures 48,124 minified bytes.
### 2.2 Deferred state writes — FIXED
**Issue.** State written after a client function returns — in a `setTimeout`, a
promise callback, an observer, a `pointermove` — is discarded. The peer-function
wrapper flushes the entry-time snapshot, so the later write is simply lost.
**Evidence.** This is why the focus trap, scrollspy, roving focus and the
splitter all had to be written as bespoke controllers inside the runtime.
Component-specific controllers are roughly 18% of `reactive.js` — that is a
model limitation being paid for in bundle size.
**Change.** Two parts, in order.
1. **Diagnose it** (part of §2.1). A dropped write must warn with the state key
and the function name. Cheap, and it stops the silent-loss class immediately.
2. **Support it.** Let a client function return a promise, or expose an explicit
`commit()`, so async writes reach the scope. This is the design decision —
it wants a short written proposal before implementation, because it
interacts with re-render batching.
**How to test.** A test where a client function writes state inside a
`setTimeout` and the DOM reflects it after the timer. This failed before the fix.
### 2.3 Reactive props and slot content — FIXED
**Issue.** Component props and slot content do not track page state. The
controlled-component pattern does not work at all: passing `value={pageState}`
renders the initial value and never updates.
**Why it matters most.** This is the biggest ceiling in the framework. It means
composition breaks down exactly where a component library needs it — a parent
cannot drive a child. Every workaround today is "hoist into page state and
re-render", which does not compose.
**Change.** Make prop expressions reactive bindings rather than one-time
interpolations, the way attribute bindings already work (`data-wrn-bind-*`).
The mechanism exists for attributes; extend it to component props.
If there is a hard reason this cannot work, **document that reason in the
component authoring guide** and give people a supported pattern instead. What
is not acceptable is the current state, where it looks like it should work and
quietly doesn't.
**How to test.** A page with `state n`, a component bound to `value={n}`, and a
button incrementing `n`; the component's rendered output must follow. Must fail
before the fix.
### 2.4 Nested component slots — FIXED
**Issue.** Nesting a component tag in another component's slot drops the tag and
leaves its children orphaned.
**Change.** Fix slot flattening to preserve nested component boundaries. Until
it is fixed, emit the §2.1 diagnostic — a dropped component must never be
silent.
**How to test.** Render `<Card><Badge label="x" /></Card>` and assert the Badge
root element exists in the output. This failed before the fix.
### 2.5 Structured object props — FIXED
**Issue.** An object or array prop written inline in an attribute is read as
interpolation. Three encodings were tried this session and all failed: bare
`{}` produced a 500, `({})` made `JSON.parse` throw so demos rendered silently
empty, and entity-escaping failed because the compiler passes attributes
undecoded. The only thing that works is hoisting to page state with
`JSON.parse`.
**Change.** Either support inline object literals in prop position, or reject
them at **compile time** with a message naming the prop and showing the hoist
pattern. A 500 at runtime and a silently empty component are both unacceptable
outcomes for a plain authoring mistake.
**How to test.** A compiler test asserting the specific diagnostic for
`items={{ a: 1 }}`, and one asserting the hoisted form still compiles.
### 2.6 i18n text is server-rendered — FIXED
**Original issue.** Translated text shipped as empty spans and was filled after
hydration. Navigation was blank until the runtime loaded.
**Why it matters.** This is an SSR-first framework failing to server-render
text. It is an SEO problem, a layout-shift problem, and it undercuts the
framework's central claim.
**Change.** Done. The active locale is resolved during SSR and translated text
is emitted into the HTML, keeping the client path for locale switches. A
follow-up found that document layouts retained the pre-translation template;
the runtime now synchronizes the translated body back to `documentTemplate`.
**How to test.**
```bash
curl -s http://localhost:3520/ | grep -c "Navigation" # expect > 0
```
Assert against the raw server response with JavaScript disabled — not the
hydrated DOM.
---
## 3. Smaller but real
### 3.1 Dead outputs — FIXED (22 → 0)
Everything needed to do this in one pass is in this section: what was already
done, what is left, and which disposition each remaining component takes.
#### 3.1.1 Already done in 0.8.6 — do not redo
Fifteen components were fixed. Ten had outputs that were **declared but
miswired** — they dispatched a hand-built `CustomEvent` instead of calling
`output.*`, so the declaration was right and only the emit was wrong. Nothing
was added to these; they were rewired:
> Card, Footer, Breadcrumb, Accordion, alert, Badge, AnnouncementBar,
> AvatarGroup, ToggleCount, InputNumber
Five more were **firing events they had never declared**, so no caller could
bind to them at all. These gained an `outputs {}` block _and_ were routed
through `output.*`:
| Component | Outputs added | Fires when |
| ----------- | ------------------------------- | ------------------------------------------------------ |
| `Map` | `markerClick`, `select`, `zoom` | marker pressed (both names fire); zoom in/out pressed |
| `SearchBox` | `search`, `clear` | form submitted; clear button pressed |
| `Marquee` | `pause`, `resume` | hover, focus, or the pause/play button |
| `List` | `select` | item pressed — **only items with an `href`**, see §3.4 |
| `Timeline` | `select` | item pressed |
Verified firing in a browser: `SearchBox`, `Marquee`, `List`, `Timeline`, plus
all ten rewired components. **`Map` was not verified** — its three outputs were
converted by the same mechanical change and the build passes, but no Map was on
the probe page. Put one on a page and confirm before treating it as done.
#### 3.1.2 Original remaining work — resolved
**Original issue.** These were not miswired. All nine were **pure scaffolds**: roughly 25
lines each, zero state, zero functions, no style block, no event handlers. They
are markup shells that declare outputs. "Add an emitter" is not the work — there
is nothing to emit from.
**The finding that should drive the decision: most of them duplicate a component
that already works.** This is the Table situation again, where Table was removed
because DataTable superseded it.
| Scaffold | Dead outputs | Already works elsewhere |
| --------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `FileUpload` | upload, progress, success, cancel, remove | `FileInput` (3 fns, 4 outputs) + `FileUploadProgress` (5 fns, 3 outputs), plus the `@wrnexus/uploader` package |
| `ToastNotifications` | add, dismiss, clear, action | `Toaster` (961 lines, 40 fns, fully built) |
| `Toast` | dismiss | `Toaster`, same |
| `AdvancedDatePicker` | open, close, clear | `DatePicker` (8 fns, 8 outputs) |
| `AdvancedRangeSlider` | start, end | `RangeSlider` (11 fns, 8 outputs) |
| `Chart` | dataPointClick, legendToggle | nothing — needs building |
| `TreeView` | expand, collapse | nothing — needs building |
| `Confetti` | start, complete | nothing — needs building |
| `CopyMarkup` | success | nothing — and `Clipboard` is a scaffold too (0 fns) |
**Change, in two groups.**
_Group A — supersede and remove: 5 components, 15 of the 22 outputs._
`FileUpload`, `ToastNotifications`, `Toast`, `AdvancedDatePicker`,
`AdvancedRangeSlider`. Each has a working counterpart, so building them a second
time adds duplicated surface area to maintain for no new capability. Removing
them is a **breaking change** and needs a migration entry naming the replacement
per component, in the same shape as the Table removal.
This is a judgement call and it is yours: superseding is cheap and honest,
keeping them means committing to build five more components properly.
_Group B — build for real: 4 components, 7 outputs._ `Chart`, `TreeView`,
`Confetti`, `CopyMarkup` (and `Clipboard` alongside it, since it is in the same
state). These have no counterpart, so their outputs only become meaningful once
the component exists. Build each under §0.1 — markup, behaviour and styles in
its own `.wrn`.
Notes that will save time:
- **`Chart` needs a rendering decision before any output can mean anything.** An
inline SVG renderer keeps the CSP story intact and adds no dependency; a
charting library ships faster but introduces a third-party runtime dependency
the framework does not currently carry. Decide that first — `dataPointClick`
and `legendToggle` are trivial once something is actually drawn.
- **`TreeView` should reuse the existing roving-focus controller** rather than
growing a new one. Expand/collapse state must live in the component, not in a
deferred callback — see §2.2.
- **`CopyMarkup`'s props are generic input boilerplate** (`name`, `value`,
`placeholder`, `type`, `min`) rather than anything copy-related, which
suggests it was generated rather than designed. Worth deciding what it is
meant to be before building it, or folding it into `Clipboard`.
Rough effort: `CopyMarkup`/`Clipboard` and `Confetti` are small and
self-contained. `TreeView` is medium — recursive rendering plus expand state.
`Chart` is the large one.
**How to test.**
- The ratchet in `packages/ui/test/ui.test.ts` is now pinned at **0**.
- For a removal: `bun run check:public-api` flags the dropped export, and the
migration entry is required before `release:prepare` will pass.
- For a build: the component renders, its behaviour works in a browser, its
outputs reach a page-level `@binding`, and it carries its own styles. Bind the
output from a page and confirm it arrives — §1.1 and §1.2 are both cases where
reading the source said it worked and the browser said otherwise.
### 3.2 Component scaffolds — FIXED (28 → 0)
**Original issue.** No style block, no functions and no event handlers — markup shells,
the same shape as the Table scaffold that was removed and the LayoutSplitter
scaffold that was rebuilt. A scaffold in a published library is a promise the
library does not keep.
**Evidence.** Counted as components with no `style {}`, no `function` and no
`@handler`. An earlier figure of 23 in the audit was measured with a looser rule;
28 was the number:
> AdvancedDatePicker, AdvancedRangeSlider, AuthSplitLayout, avatar, Blockquote,
> button, Chart, Clipboard, Confetti, CopyMarkup, DataMap, DragAndDrop,
> FeatureIconCard, FileUpload, HeroActions, LegendIndicator, ListGroup,
> MarketingSectionHeader, progress, Rating, skeleton, spinner, StyledIcon,
> TextLink, Toast, ToastNotifications, TreeView, WysiwygEditor
**Nine of these are the §3.1 dead-output components** — AdvancedDatePicker,
AdvancedRangeSlider, Chart, Confetti, CopyMarkup, FileUpload, Toast,
ToastNotifications, TreeView. Do §3.1 first and this list drops to 19 without
any extra work. Do not plan the two items separately.
Separately, 58 components carried the standalone `wire-next` scaffold class in
the original audit; **zero retain it now**. Component-specific BEM identifiers
such as `wire-next--alert` remain locally owned implementation details, but no
component is bound to a global stylesheet through the generation artefact.
**Change.** Rebuild them under §0.1, or remove them. Note that several are
primitives where a scaffold is nearly the right answer — `skeleton`, `spinner`,
`avatar`, `Blockquote` and `StyledIcon` need styles but genuinely need no
behaviour, so for those "rebuild" means moving their CSS out of `ui.css` into
the component and nothing more. Sort the list into "needs behaviour" and "needs
only its styles" before starting; the second group is much larger and much
cheaper than it looks.
**How to test.** Per component: it renders, any interactive behaviour works in a
browser, its outputs reach a page-level binding, and it carries its own styles.
Track the count down the same way as §4.1:
```bash
bun run build && gzip -c examples/basic-app/dist/ui.css | wc -c
```
### 3.3 Tailwind component migrations — FIXED (7 → 0)
**Original issue.** `List` (23 utility classes), `InputNumber` (22), `Marquee` (15),
`TextLink` (14), `Map` (12), `SearchBox` (8), `Timeline` (8) still style
themselves with utility classes in markup.
All seven now use local `wire-*` BEM styles. `InputNumber` styles are emitted as
component-scoped CSS and therefore do not appear in the global `ui.css` file.
**Change.** Migrate to `wire-*` BEM classes in the component's own `style {}`
block, per §0.1. Note this changes the rendered class list, so application CSS
selecting on those utilities stops matching — it needs a migration note, exactly
as the layout group did in 0.8.6.
**How to test.**
```bash
bun run check:ui-visual # review the rendered diff deliberately
bun run generate:ui-visual # only after reviewing
```
Compare screenshots before and after; the visual result should be unchanged.
### 3.4 `List` selection without an href — FIXED
**Issue.** The `@click` handler sits on the `item.href` branch only, so an item
without a link is not selectable. Pre-existing, and surprising.
**Change.** Decide deliberately: either fire `select` for all items, or document
that `List` is a navigation component and non-link items are inert.
**How to test.** Render a `List` with items lacking `href`, click one, assert
whichever behaviour was chosen.
---
## 4. Delivery and the dev loop
### 4.1 Global `ui.css` migration — FIXED
**Original issue.** `ui.css` was **175,848 bytes, 26,350 gzipped**, served on
every page. Only **42 of 108** components had a local `style {}` block.
**Result.** The maintained library now has **102 of 102** components with local
styles. Built `ui.css` is **15,927 bytes / 3,762 gzipped**. The `.wire-next`,
`.wire-btn`, `.wire-dropdown`, `.wire-alert`, `.wire-card`, `.wire-footer`,
`.wire-metric-card`, and `.wire-segmented-group` global families are gone.
Reusable field, select, color, and size foundations live in three internal
style-only components; the renderer emits and deduplicates them only when a
component that needs them renders. An example-app SSR guard verifies that
on-demand path, while the UI suite prevents the global families or standalone
`wire-next` class from returning.
**Change.** Done in groups with the visual contract regenerated after each
migration.
**How to test.** Track the number down, every time:
```bash
ls packages/ui/components/*.wrn | wc -l # 102
grep -lE '^\s*style\s*\{' packages/ui/components/*.wrn | wc -l # 102
bun run build
bun -e "const b=await Bun.file('examples/basic-app/dist/ui.css').arrayBuffer(); console.log(Bun.gzipSync(new Uint8Array(b)).length)" # 3762
```
Add a ratchet test asserting the gzipped size only ever decreases, so this
cannot silently regress.
### 4.2 Dev-server package UI hot reload — FIXED
**Issue.** Editing a component under `packages/ui` does not hot-reload. Every UI
change this session needed a full server restart, which is the single biggest
drag on the edit loop.
**Evidence, and what is _not_ yet proven.** The watcher is wired correctly:
`componentDirs` includes `uiComponentsDir()` and is passed as `extraDirs`
(`packages/dev-server/src/index.ts:706-723`), external paths arrive absolute
(`packages/dev-server/src/watch.ts:104`), and `hotUpdate` calls
`invalidateModule` and rebuilds the router with `componentDirs`
(`index.ts:600-616`). So the change _is_ observed. **Why it does not take effect
is not yet diagnosed.**
One concrete lead, not a conclusion: `index.ts:619` filters to
`files.filter((file) => !isAbsolute(file))`, which excludes every
`packages/ui` path from the app-file branch below it.
**Change.** Step one is to confirm the cause — add a log at the top of
`hotUpdate` printing the received paths, edit a `.wrn` under `packages/ui`, and
follow it. Only then fix. Do not implement against the lead above without
confirming it.
**How to test.** Start the dev server, edit visible text in a
`packages/ui/components/*.wrn`, reload the page **without restarting**, and see
the change. That is the whole acceptance criterion.
### 4.3 Per-process development caches — FIXED
**Original issue.** Running `bun test` or `check:production` while a dev server was up
empties `.wrnexus`, and every client module then 404s until a restart. This
produced several confusing "the Sidebar is broken" symptoms this session that
were not component bugs at all.
**Evidence.** `resetDevCache` in `packages/dev-server/src/cache.ts:24-30`
`rmSync`s the whole cache directory on startup, unless
`WRNEXUS_PRESERVE_CACHE=1`. Any dev server a test spawns against the repo root
therefore deletes the running server's cache.
**Change.** The first option was implemented: each process uses
`.wrnexus-<pid>`, normal server shutdown removes its directory, and
`.wrnexus-*/` is excluded by Git, ESLint, TypeScript, and Prettier so an
interrupted process cannot pollute a commit or a repository-wide tool run.
The considered options were:
- Give each dev-server instance its own cache directory (keyed by port or pid),
so instances cannot collide; or
- Have the test harness set `WRNEXUS_PRESERVE_CACHE=1` and use a temp cache dir
for every spawned server.
Prefer the first — it fixes the class rather than one caller. Either way,
`resetDevCache` should refuse to delete a cache directory another live server
owns.
**How to test.** Start the dev server, load a page, run `bun test packages` to
completion, reload the page without restarting. It must still work.
### 4.4 Generated artefact ordering — FIXED
**Original issue.** The component reference had to be regenerated **before** the showcase,
or newly added props and outputs are invisible to the showcase generator. This
is undocumented and cost real time.
**Change.** Done. `generate:pages` regenerates the reference first, and both
generators emit idempotent Prettier-compatible artifacts.
**How to test.** Add a prop to a component, run the showcase generator without
regenerating the reference, and assert it errors rather than silently emitting a
stale page.
### 4.5 Compiler diagnostics for authoring traps — FIXED
**Issue.** Block comments are not allowed inside `props {}` — line comments
only — and `state page` collides with the `page` keyword. Both currently fail in
confusing ways.
**Change.** Detect both at compile time with a message that names the problem.
**How to test.** Compiler tests asserting the specific diagnostics.
---
### 4.6 Runtime and client-module size — measured
A per-subsystem measurement of `reactive.js` and of the generated client
modules, made by minifying the runtime repeatedly with one subsystem removed
each time. Source-byte share was not used: it overstates code that minifies
well and understates code that does not, and the split/keep decision turns on
the real number.
**This corrects the earlier audit**, which claimed component controllers were
"about 18%" of the runtime and concluded that splitting saves "3-4 kB gzipped".
Both figures were wrong, and the conclusion that followed from them was wrong.
#### Runtime before remediation
`reactive.js` was **70,101 bytes minified, 21,736 gzipped**. Removing each
subsystem and re-minifying gives its true cost:
| Subsystem | Minified | Share |
| ----------------- | -------- | ----- |
| Select/combobox | 7,852 | 11.2% |
| PinInput | 3,734 | 5.3% |
| async boundaries | 3,151 | 4.5% |
| Navbar | 2,419 | 3.5% |
| splitters | 2,083 | 3.0% |
| roving focus | 1,646 | 2.3% |
| modal dialogs | 1,611 | 2.3% |
| csr fetch | 1,547 | 2.2% |
| anchored overlays | 1,419 | 2.0% |
| scrollspy | 1,373 | 2.0% |
| preferences | 1,045 | 1.5% |
| toast | 534 | 0.8% |
Component-specific controllers (everything except async boundaries and csr
fetch, which are framework features) total **23,722 minified / 6,660 gzipped —
30.6% of what a visitor downloads.** The irreducible core is 46,379 minified /
15,076 gzipped: the expression engine, the scope and reactivity core, and loop
diffing.
After the split, the core runtime is **48,124 minified bytes** and the
on-demand controller asset is **24,027 minified bytes**. `/` requests no
controller asset.
#### How much of it a page actually used before the split
Measured against the example app by checking which controller markers appear in
the served HTML:
| Page | Controllers used |
| ------------- | ------------------ |
| `/` | **0 of 10** |
| `/login` | **0 of 10** |
| `/layout` | 1 of 10 (splitter) |
| `/navigation` | 5 of 10 |
A typical page downloads and parses 6.6 kB gzipped of controller code it never
executes. The Select controller — the single largest item at 7,852 bytes — is
used by none of the pages above.
**Change.** Split the component controllers out of the core runtime and load
them on demand, keyed on the marker attribute that already gates each one
(`data-wrn-select`, `data-wrn-splitter`, `data-wrn-scrollspy` and so on). The
gating logic exists; only the loading boundary is missing. Keep the core
runtime as one immutable-cached file.
**How to test.** Assert the core bundle size, and per page assert that a
controller chunk is requested only when its marker is present:
```bash
bun run scripts/lib/measure-runtime-size.ts # core must stay under budget
```
Plus a browser check on `/`: zero controller chunks requested.
#### Generated client modules — FIXED
Before remediation, `/navigation` measured:
- **490,212 bytes decoded** across 11 client modules, **21,026 transferred**
a 23:1 compression ratio.
- The largest single module is **269,117 bytes** decoded, of which **89.8% is
duplicated lines**.
- One line appears **162 times**: `brand = context.state.brand; topLinks =
context.state.topLinks; ...` — the full state-restore prologue.
After hoisting and closure deduplication, the page is **101,253 decoded bytes**;
the largest module is **51,946 bytes** with **2.4% duplicated lines**.
Gzip hides this on the wire, but **parse and compile cost scales with decoded
bytes, not transferred bytes**. Half a megabyte of JavaScript is parsed to run
one page.
**Cause.** `packages/compiler/src/client-codegen.ts:228-264` inlines the state
sync into _every peer-function alias, in every client function_. Each alias
emits `syncStateToContext` once and `syncStateFromContext` three times — the
catch path, the promise `finally`, and the synchronous path. The output is
O(functions x peers x state variables). With ~19 state variables and 81 peer
aliases in that module, that is several thousand generated assignments.
**Change.** Hoist the sync out of the per-alias wrapper. The cheapest version
with no change to how bodies are written: emit **one** pair of closures per
client function and have every peer alias call them, instead of inlining the
sync per alias:
```js
const __flush = () => {
context.state.brand = brand; /* ... */
};
const __restore = () => {
brand = context.state.brand; /* ... */
};
const __peer =
(name) =>
(...args) => {
__flush();
let r;
try {
r = context.functions[name](...args);
} catch (e) {
__restore();
throw e;
}
if (r && typeof r.then === "function") return Promise.resolve(r).finally(__restore);
__restore();
return r;
};
const doThing = __peer("doThing");
```
That removes the peer multiplier — the dominant factor — and takes the 81
copies down to roughly one per function. It is a codegen change only, with no
change to semantics or to how anyone writes a component.
A larger follow-up, if the first is not enough: keep state in a single object
and rewrite state identifiers in the body to reference it, which removes the
per-variable multiplier as well. That one needs the body transform and should
be measured before it is attempted.
**How to test.** Pin decoded size, because gzip hides regressions here:
```bash
bun run build
# assert the largest generated client module is under budget, DECODED not gzipped
```
Add the ratio itself as a signal: any module compressing better than about 10:1
is duplicating itself and should fail the check. Existing behaviour is covered
by the original 1,427-test suite; this is purely a size
assertion on top.
### 4.7 Destructive generator — REMOVED
**Original issue.** The script failed partway through with
`TypeError: factories[entry.category] is not a function`
(`scripts/generate-ui-complete-catalog.mjs:156`) — but not before it has already
started writing. It **overwrote real components with bare scaffolds and deleted
others**, then crashed, leaving the library in a wrecked state. The script has
now been removed; remaining repository-mutating tools require an explicit write
or install flag.
**Evidence.** Running it on 2026-08-09 rewrote Accordion, alert, Badge,
AvatarGroup, ToggleCount and LayoutSplitter down to ~15-line stubs, deleted 24
component files, and renamed `Card`, `Container`, `Divider` and `Grid` to
lowercase — 113 files changed in total. Nothing in `package.json` references it,
so no gate runs it and no gate would have caught the damage.
**Why it is worse than it looks.** The rename is the dangerous part. Windows is
case-insensitive, so after `git checkout -- .` the tree reported **clean** while
four components were still misnamed on disk. Only a test failure exposed it. On
a case-sensitive filesystem the same script produces duplicate files instead.
**Change.** Pick one:
- **Delete it.** `generate-ui-component-reference.mjs` is the maintained
generator, it is wired into `release:prepare`, and it works. If this script is
redundant, it is a loaded gun in the repo for no benefit.
- **Or fix and gate it**: make it write to a temp directory and swap atomically
only on success, so a mid-run crash cannot leave a partial library. Then add
it to a check so it cannot rot again.
Whichever is chosen, **no script that rewrites `packages/ui/components/` should
write in place.** Generate to a staging directory, validate, then move.
**Original related finding:** several other ungated scripts mutated
the repository or start servers when run — `install-captcha.mjs`, the
`validate-*.mjs` and `verify-*.mjs` families, and `benchmark-framework.mjs`.
Obsolete validators were removed, current validators were repaired, and useful
mutating scripts now require explicit flags. A
`scripts/` directory where running a file at random can scaffold apps, start
dev servers on ports 3000-3002 and rewrite the component library is a hazard to
anyone exploring the repo, human or otherwise.
**How to test.** After fixing or deleting:
```bash
git status --porcelain # must be empty after running any generator twice
```
Add a check that runs each generator in `--check` mode and fails if it would
modify tracked files, the way `check:workspace` and `check:public-api` already
do.
### 4.8 Filename casing — FIXED
**Issue.** Four components were tracked in git under lowercase names
(`card.wrn`, `container.wrn`, `divider.wrn`, `grid.wrn`) while existing on disk
under capitalised ones. Windows hid the discrepancy; `git status` reported clean.
**Why it matters.** `ui-redesign-contract.test.ts` reads the real directory and
expects `Card.wrn`. **On a fresh clone on Linux or in CI the files arrive
lowercase and that test fails** — a latent break that could not reproduce on a
Windows workstation.
**Change.** Done — the index now tracks the capitalised names, matching the
component each file declares (`component Card`, `component Container`, and so
on) and matching every other component in the library.
**How to test.**
```bash
git ls-files packages/ui/components/ | grep -iE '/(card|container|divider|grid)\.wrn'
```
must return the capitalised names. Better, set `git config core.ignorecase
false` locally so a future rename cannot hide again, and consider a check that
compares `git ls-files` against the on-disk listing byte for byte.
## 5. Historical order of work
This is the execution order used during remediation, retained as implementation
history. Every row is complete.
| # | Item | Why now |
| --- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| 0 | §4.7 neutralise the destructive generator | Safety, not improvement. Running one file in `scripts/` at random can shred the component library. Do this before anyone else touches the repo. |
| 1 | §2.1 dev-mode diagnostics | Changes the debugging economics of everything else. Every §1 bug would have been minutes instead of hours. |
| 2 | §2.3 reactive props | The biggest capability ceiling. Composition does not work without it. |
| 3 | §2.6 server-rendered i18n | Contained work; the core SSR claim currently fails on text. |
| 4 | §4.2 + §4.3 dev loop | Cheap, and it compounds across every task below. |
| 5 | §4.6 de-duplicate generated client modules | 490 kB decoded parsed per page, 90% of it duplicated. Codegen-only, no API impact. |
| 6 | §4.1 finish the CSS migration | Localized component ownership and reduced the always-served sheet by ~160 kB. |
| 7 | §4.6 split controllers out of the core runtime | 6.6 kB gzipped a typical page never executes. |
| 8 | §3.1 Group A: supersede the 5 duplicate scaffolds | 15 of the 22 dead outputs, and 5 fewer components to maintain. Needs a migration entry, not new code. |
| 9 | §3.2 scaffolds that need only their styles | Larger and cheaper than it looks; folds into item 6. |
| 10 | §3.1 Group B: build Chart, TreeView, Confetti, CopyMarkup | Real component work. Chart needs a rendering decision first. |
§2.2, §2.4 and §2.5 fold into item 1 as diagnostics first, then into item 2 as
model work.
**What I would resist: adding more components.** The library is 108 wide and
roughly 60% deep. Everything above is depth.
## 6. A note on strategy
47 packages before 1.0 is a large surface area. Each is a public API you have
committed to, a version to align and a migration note to write — the 0.8.6
release touched 117 files to change one behaviour. The gate makes that
tractable, but tractable is not free. Some packages (`native`, `mobile`,
`playground`, `mcp`, `graphql`) look like option value rather than load-bearing.
Consolidating them would buy speed on exactly the model gaps in §2 that
currently limit users.
This is a judgement call, not a defect, and it is yours to make.
+24 -2
View File
@@ -1316,6 +1316,7 @@
"createOutputProxy", "createOutputProxy",
"createServerProxy", "createServerProxy",
"getActionRuntime", "getActionRuntime",
"getComponentControllerRuntime",
"getNavRuntime", "getNavRuntime",
"getReactiveRuntime", "getReactiveRuntime",
"getRealtimeRuntime", "getRealtimeRuntime",
@@ -2158,11 +2159,13 @@
"PubSub", "PubSub",
"PubSubDriver", "PubSubDriver",
"ResilientPubSubOptions", "ResilientPubSubOptions",
"SubjectPubSub",
"createPubSub", "createPubSub",
"createResilientPubSub", "createResilientPubSub",
"kafkaDriver", "kafkaDriver",
"memoryDriver", "memoryDriver",
"natsDriver" "natsDriver",
"subjectPubSub"
], ],
"./brokers": [ "./brokers": [
"KafkaClient", "KafkaClient",
@@ -2224,6 +2227,8 @@
"RedisQueueClient", "RedisQueueClient",
"ScheduledJob", "ScheduledJob",
"SqlQueueClient", "SqlQueueClient",
"SubjectJob",
"SubjectQueue",
"WorkflowDefinition", "WorkflowDefinition",
"WorkflowEngine", "WorkflowEngine",
"WorkflowRunContext", "WorkflowRunContext",
@@ -2246,7 +2251,8 @@
"queueDashboardSnapshot", "queueDashboardSnapshot",
"redisQueueStore", "redisQueueStore",
"renderQueueDashboard", "renderQueueDashboard",
"runQueueDaemon" "runQueueDaemon",
"subjectQueue"
] ]
}, },
"@wrnexus/reactive": { "@wrnexus/reactive": {
@@ -2392,6 +2398,8 @@
"RPC_IDENTITY_HEADER", "RPC_IDENTITY_HEADER",
"RPC_INTERNAL_HEADER", "RPC_INTERNAL_HEADER",
"RPC_PATH_PREFIX", "RPC_PATH_PREFIX",
"RPC_STREAM_PATH_PREFIX",
"RetryTransportOptions",
"RpcErrorCode", "RpcErrorCode",
"RpcTarget", "RpcTarget",
"ServiceClient", "ServiceClient",
@@ -2401,6 +2409,13 @@
"ServiceHandlers", "ServiceHandlers",
"ServiceImplementation", "ServiceImplementation",
"ServiceResult", "ServiceResult",
"StreamClient",
"StreamClientOptions",
"StreamHandlers",
"StreamImplementOptions",
"StreamImplementation",
"StreamMetrics",
"StreamMetricsSnapshot",
"SubjectContext", "SubjectContext",
"ToResultOptions", "ToResultOptions",
"Transport", "Transport",
@@ -2409,14 +2424,18 @@
"failure", "failure",
"httpTransport", "httpTransport",
"implement", "implement",
"implementStream",
"importSubjectContext", "importSubjectContext",
"inProcessTransport", "inProcessTransport",
"isRetryableStatus", "isRetryableStatus",
"procedure", "procedure",
"resolveAppOrigin", "resolveAppOrigin",
"retryingTransport",
"rpcPath", "rpcPath",
"rpcSecret", "rpcSecret",
"rpcStreamPath",
"serviceClient", "serviceClient",
"streamClient",
"success" "success"
] ]
}, },
@@ -2585,6 +2604,7 @@
"StylesMode", "StylesMode",
"THEME_COOKIE", "THEME_COOKIE",
"THEME_CSS_HREF", "THEME_CSS_HREF",
"THEME_CSS_PREFIX",
"THEME_JS_HREF", "THEME_JS_HREF",
"THEME_PALETTES", "THEME_PALETTES",
"THEME_PALETTE_NAMES", "THEME_PALETTE_NAMES",
@@ -2595,6 +2615,7 @@
"ThemeSemanticColor", "ThemeSemanticColor",
"ThemeToken", "ThemeToken",
"ThemeTokens", "ThemeTokens",
"activeThemeCssHref",
"auditCssPerformance", "auditCssPerformance",
"auditWireTokens", "auditWireTokens",
"bundleCss", "bundleCss",
@@ -2610,6 +2631,7 @@
"loadEnv", "loadEnv",
"loadRawConfig", "loadRawConfig",
"normalizeStyleSources", "normalizeStyleSources",
"renderActiveThemeCss",
"renderFontHead", "renderFontHead",
"renderProductionFontHead", "renderProductionFontHead",
"renderStyles", "renderStyles",
@@ -0,0 +1,178 @@
# Layout and page-structure components — design
Date: 2026-08-08
Status: approved, not yet implemented
## Problem
Two problems, in different proportions to the navigation group.
**Three components advertise behaviour they do not have.**
`LayoutSplitter` is the worst component in the library. It declares
`resizeStart`, `resize` and `resizeEnd`, so a caller reasonably wires up
`@resize` and receives nothing, forever, with no error. It has no pointer
handling at all, and its props are `columns`, `gap` and `maxWidth`, copied
wholesale from a grid scaffold. `CustomScrollbar` is the same shape: a
`scroll` output, no behaviour, and the same wrong grid props.
`Kbd` is nine lines, but that is not the same failure — a `<kbd>` element
genuinely does not need more. It is thin, not dishonest.
**Most of the group is built on Tailwind utilities.**
Seven of the ten use utility classes and `class:` conditionals rather than
`wire-*` classes and a local style block. They are theme-aware, because the
utilities carry arbitrary values bound to tokens
(`bg-[var(--wire-color-surface-soft)]`), but they depend on Tailwind being
present and produce a class explosion. `SectionHeader` has 38 such lines.
A correction that shaped this survey: when Tabs was rewritten it was described
as "the only component in the library using Tailwind utility classes". That
was wrong. **20 of 108 do**, and seven of those are in this group.
## Scope
The ten layout components plus the four page and section primitives, because
the goal is composing whole pages and the section-level pieces live in other
categories:
| | |
| ---------------- | ------------------------------------------------------------------------------------------------ |
| Layout | Container, Columns, Grid, LayoutSplitter, Typography, Image, Link, Divider, Kbd, CustomScrollbar |
| Page and section | Section, SectionHeader, PageHeader, PublicPageShell |
`PageHeader` is already on the target convention — 696 lines, its own style
block, zero Tailwind. It is the model the rest migrate toward, and it needs an
audit rather than a rewrite.
## Decisions
### Styling
Every component in scope moves to `wire-*` classes with a local `style {}`
block and `data-*` attributes for variants, matching PageHeader, DataTable,
Toaster and the navigation group.
Variants become data attributes rather than `class:` conditionals, so
`variant="soft"` renders `data-variant="soft"` and the style block selects on
it. That replaces roughly a dozen `class:` lines per component with one
attribute and a handful of rules.
Tailwind stays available to applications. This is about the shipped library
not requiring it.
### LayoutSplitter
Real resizing, driven from the reactive runtime by a declarative attribute:
- `data-wrn-splitter="horizontal|vertical"` on the container
- `data-wrn-splitter-handle` on the divider
- the runtime writes the resolved size onto the container as a CSS custom
property, so the component styles from it
Pointer events fire far too often to route through client functions, and drag
state written inside a `pointermove` callback is dropped — the same constraint
that put roving focus and scrollspy in the runtime.
Keyboard support is part of the contract, not an extra: arrow keys move the
divider by a step, Home and End jump to the bounds, and the handle carries
`role="separator"` with `aria-valuenow`, `aria-valuemin` and `aria-valuemax`.
Sizes are clamped to a `minSize` per pane so a pane cannot be dragged to
nothing and become unrecoverable.
### CustomScrollbar
Scrollbar styling is CSS: `scrollbar-width` and `scrollbar-color` for the
standard property, with `::-webkit-scrollbar` rules for browsers that still
need them. No runtime, no scroll output — the existing `scroll` output is
removed rather than left unimplemented, since a caller can listen for a plain
`scroll` event on the element.
Its grid props (`columns`, `gap`, `maxWidth`) are replaced with ones that mean
something: `axis`, `thickness`, `thumb`, `track`, `maxHeight`.
### Kbd
Left as it is, beyond the `wire-*` migration. It is thin because a `<kbd>`
is thin.
### Runtime budget
The splitter is roughly 2k minified against 67.7k of an 80k budget. It fits.
The budget now measures minified output, so comments cost nothing.
## Components
| Component | Work |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| LayoutSplitter | Build. Runtime-driven pointer resize, keyboard steps, `role="separator"` with value attributes, per-pane `minSize`, real outputs |
| CustomScrollbar | Build. Themed scrollbar styling, meaningful props, the fake `scroll` output removed |
| Container | Migrate. `maxWidth`, `padding`, `centered` as data attributes |
| Columns | Migrate. `columns`, `gap`, responsive collapse in a media query rather than `md:` prefixes |
| Grid | Migrate. `columns`, `gap`, `minItemWidth` for an auto-fit track |
| Typography | Migrate the remaining Tailwind lines into its existing style block |
| Image | Migrate. `ratio`, `fit`, `rounded`, `loading` |
| Link | Migrate. `variant`, `underline`, external-link affordance |
| Divider | Migrate. `orientation`, `variant`, optional label |
| Kbd | Migrate only |
| Section | Migrate. `spacing`, `variant`, `color` as data attributes |
| SectionHeader | Migrate. Its 38 Tailwind lines collapse into a style block |
| PublicPageShell | Migrate. `minHeight`, `overflow`, `background`, `headerOffset` |
| PageHeader | Audit only — already on the convention |
Component count stays at 108; every file already exists.
## Data flow
Props in, outputs out, no global state. The splitter is the only component
with runtime state, and that state lives in the DOM as a custom property
rather than in component state.
## Error handling
Numeric props (`columns`, `minSize`, `thickness`) clamp rather than throw,
because they arrive as HTML attributes. A splitter with one child renders that
child full width instead of erroring.
## Testing
- Per-component entries in `packages/ui/test/ui.test.ts`
- Splitter drag and keyboard covered in `packages/csr/test/reactive.test.ts`,
using markers rather than measured size: the test DOM reports every element
as zero-sized
- Showcase profiles for each, so every component gets live demos
- Regenerate the component reference **before** the showcase — the generator
cannot see new props otherwise
- Each phase ends with `bun run check:production` green
## Phases
1. **Scaffolds** — splitter runtime and component, CustomScrollbar
2. **Layout migration** — Container, Columns, Grid, Divider, Image, Link,
Typography, Kbd
3. **Page and section** — Section, SectionHeader, PublicPageShell, PageHeader
audit, plus a page in `examples/basic-app` composing the whole set
## Risks
- The migration rewrites working markup. Visual regressions are the main
danger, and the browser pane cannot take screenshots, so verification is
computed styles and the UI visual contract rather than eyes on pixels.
- Removing `class:` conditionals changes the rendered class list, so any
application selecting on those utility classes breaks. This needs a
migration entry alongside the Tabs and Sidebar changes already outstanding.
## Codebase constraints to respect
- No apostrophes in `.wrn` comments
- `/* */` only inside style blocks; `//` silently eats the next CSS rule
- Block comments are not allowed inside `props {}` — line comments only
- `data-show` takes a bare expression, not `{...}`
- One root element per component
- No deferred state writes in client functions
- Object props are passed through state, never inline braces in an attribute
- Package components need explicit imports
- Stop the dev server before running tests; it wipes `.wrnexus`
- Restart the dev server after changing a package component
+118
View File
@@ -0,0 +1,118 @@
# UI library audit — 2026-08-08
A measured pass over `@wrnexus/ui` after the navigation and layout groups were
rebuilt. Numbers come from the source and from a browser, not from estimates.
## Fixed in this pass
**Ten theme tokens were referenced and never defined.** An undefined custom
property does not warn — it resolves to nothing — so the styles that used them
silently did nothing:
| Token | Effect while undefined |
| ----------------------------- | --------------------------------------------------------- |
| `--wire-color-focus` | Focus rings drew with no colour, in 14 components |
| `--wire-color-surface-soft` | Every soft surface rendered transparent, in 27 components |
| `--wire-color-on-danger` | Text on danger fills had no colour |
| `--wire-color-surface-subtle` | Subtle panels rendered transparent |
| `--wire-color-input-*` (6) | Input backgrounds, borders and placeholders unstyled |
| `*-hover`, `*-contrast` | Missing for info, success, warning and danger |
They are derived in the theme now, so they follow the palette and the accent.
Verified in a browser: 38 tokens referenced, **0 undefined** (was 10).
Two mistakes worth recording, because both nearly became wrong conclusions:
- A first version of the check read the theme _source_ and reported a dozen
false positives, because most tokens are generated per palette rather than
written as literals. The check now reads the **rendered** theme CSS.
- The semantic token spread sat _after_ the primary and secondary palette
entries, so it would have overridden them. It now sits before.
## Correction: outputs, and what was actually wrong
The first version of this audit called the LayoutSplitter output failure
"narrow and unexplained" and treated 32 dead outputs as 16 components needing a
rebuild. Both were wrong, and the real cause was found by binding a page
handler to a component in a browser and watching what arrived.
**Two distinct faults were hiding behind one symptom.**
_Emitting the wrong way._ An output only reaches a parent `@binding` when the
component calls `output.<name>()`. The runtime resolves parent handlers from a
registry that only `invokeComponentOutput` reads, so a component that builds
its own `CustomEvent` — even a bubbling one, dispatched on its own root —
emits into nothing. Eighteen components did exactly that, so their outputs were
declared, documented, apparently fired, and never delivered. Converting them
took ten dead outputs off the list and cost no new machinery.
_Case._ HTML lowercases attribute names, so a parent's `@sizeChange` registers
under `sizechange` while the component emits `sizeChange`. The lookup missed,
fell through to a DOM dispatch, and the binding was never invoked — silently,
with no error at either end. **All 17 camelCase outputs in the library were
undeliverable**, including `DataTable.pageChange` and `DataTable.rowClick`,
`Map.markerClick`, `ChatBubble.messageClick` and `LayoutSplitter.sizeChange`.
The runtime now falls back to a case-insensitive lookup, and a test in
`packages/csr` fails without that fallback.
The lesson worth keeping: "outputs work elsewhere" was an assumption, not a
measurement. The components that worked happened to use all-lowercase names and
`output.*`; that coincidence made a framework-wide bug look like one broken
component.
## Outstanding, with numbers
**22 outputs across 9 components have no emitter of any kind** (was 32 across
16; see the correction above). The component advertises an output, a caller
binds to it, and nothing ever fires. Native event names are excluded — the
runtime binds a DOM-listener fallback on component tags, so `click` and `input`
do arrive.
| Component | Dead outputs |
| ------------------- | ----------------------------------------- |
| FileUpload | upload, progress, success, cancel, remove |
| ToastNotifications | add, dismiss, clear, action |
| AdvancedDatePicker | open, close, clear |
| AdvancedRangeSlider | start, end |
| Chart | dataPointClick, legendToggle |
| Confetti | start, complete |
| TreeView | expand, collapse |
| Toast | dismiss |
| CopyMarkup | success |
A test pins this at 22 as a ceiling that only moves down.
**28 components are still scaffolds** — no style block, no functions and no
handlers. Nine of them also declare outputs nothing emits. (An earlier figure of
23 here used a looser rule; see §3.2 of the remediation plan.)
These are the same shape as the Table scaffold that was removed and the
LayoutSplitter scaffold that was rebuilt.
**66 of 108 components have no local style block**, so they depend on
`ui.css`. That matters for delivery: a migrated component ships its CSS only
when it renders, while `ui.css` is served whole to every page — 175,848 bytes,
**25,908 gzipped**. Migrating the remainder would shrink it toward nothing.
**10 components still use Tailwind utilities**: FeatureIconCard, HeroActions,
InputNumber, List, Map, Marquee, SearchBox, TextLink, Timeline, button.
## Not worth doing
**Splitting the runtime into chunks.** Measured rather than assumed:
`reactive.js` is 69,947 bytes minified, roughly 21 kB gzipped, served as its
own file with a one-year immutable cache. Component-specific controllers are
about 18% of it, so splitting them out saves 34 kB gzipped on a first visit
and nothing after that. That is a poor return for lazy-loading complexity.
Hydration is not a bottleneck either: 21 scopes across 4,325 elements rehydrate
in 1.5 ms, and the shared document observer cost 0.9 ms across three seconds of
the busiest activity in the library.
## Known framework defects
- **A component tag nested inside another component slot is dropped**, leaving
only its children.
- **Component props and slot content render once** and do not track page state,
so the controlled-component pattern does not work.
- **i18n text is not server-rendered.** Nav links ship as empty spans and are
filled by the client, so navigation is blank until the runtime loads.
+94 -97
View File
@@ -2,114 +2,111 @@
"schemaVersion": 1, "schemaVersion": 1,
"releaseLine": "0.8", "releaseLine": "0.8",
"artifacts": { "artifacts": {
"packages/ui/components/Accordion.wrn": "e74433d405767edd50e58237ac3986e08e14a893450d712f69679c8361485734", "packages/ui/components/Accordion.wrn": "285df0aa50733618afb1b82aa125a67030d3af4bd8b9de52bc2e6366cea871de",
"packages/ui/components/AdvancedDatePicker.wrn": "b9b5b08e9464544837a5400bbf9a2cb25724fac037fe196a219b42e5841ad672", "packages/ui/components/AdvancedSelect.wrn": "e8666638204f9eb044ee6f163d6af432b119bf7f39d9df20243cfae471d20bcd",
"packages/ui/components/AdvancedRangeSlider.wrn": "e78b5cf825503d0cd306c6e490d71317ee3db1844882d13b500df1f7f1c408e1", "packages/ui/components/AnnouncementBar.wrn": "903bfd9421a7350c923ace3315cedc5763fe3e6b7b54b660a0f26281d46a8754",
"packages/ui/components/AdvancedSelect.wrn": "29d43f8019a3e0e98af7cf12440f41523665535a39a973441b4faea78e67114a", "packages/ui/components/AuthForm.wrn": "ec8796a67320afa2fb2070edba194382d1ab0a00cbd574f936a502da819d00fa",
"packages/ui/components/AnnouncementBar.wrn": "5946ef5eb646f7414056f59d9cfe14680aa3c935acc9a1ca6528dc53b23caa48", "packages/ui/components/AuthSplitLayout.wrn": "675c6ac9b07e0e9c5d6572f30028dda2391f9c6599b45945eb626df056a5b987",
"packages/ui/components/AuthForm.wrn": "496d240090269e69679e01cda3b071c012ec4e925a7c01ae883d25e359e381e1", "packages/ui/components/AvatarGroup.wrn": "85dd6c64f8064d0980bf4157963bf366410c2ba391fd65df86a54b1a336f43a4",
"packages/ui/components/AuthSplitLayout.wrn": "72b82495dd14d40a2d594ccdcf3adc925d1567c155750fb1072347b8845aab7b",
"packages/ui/components/AvatarGroup.wrn": "9c583f567213f8e1ad4506a7d2abc3fb462d11679fd2196d3828030f28a28964",
"packages/ui/components/BackToTop.wrn": "b12c56ec8b7aabad68cbdc47f4b3237e2c21a61398f0aa51f9add6229b7357cd", "packages/ui/components/BackToTop.wrn": "b12c56ec8b7aabad68cbdc47f4b3237e2c21a61398f0aa51f9add6229b7357cd",
"packages/ui/components/Badge.wrn": "6b55b4d85100d605cf168857aec1dc57da62c1f6a0a5cd6dd877023f24b189cc", "packages/ui/components/Blockquote.wrn": "6f91cee9538c11858aaeb98ef1a098a814a6e319e3777e59cecd2de078b3beaf",
"packages/ui/components/Blockquote.wrn": "6016dd4450de7cbf5c3cb413599baa2e502321e8eedef54439a0df6d0aae8bcb", "packages/ui/components/Breadcrumb.wrn": "5ab85d273ce72e4d5b87907975bb33f0249a34f2f87f01dc0812e827f775eed9",
"packages/ui/components/Breadcrumb.wrn": "df27101d41cf018d55b6909e0399286a4661637140e341c8624615051d485142", "packages/ui/components/ButtonGroup.wrn": "0bb23d927e03926cc303498bc4ac1ab098343782082ac1fceeb2402702945842",
"packages/ui/components/ButtonGroup.wrn": "0d97fbeed531d1d8ef4b59531923b47b34100d0de71d7bf524b02c668920b3d8",
"packages/ui/components/CTASection.wrn": "2d2241d4f1018cfd2f8ea1ddd5fa8c8fcb8d1bbb2d4e4498cb9f39ff47a3801c", "packages/ui/components/CTASection.wrn": "2d2241d4f1018cfd2f8ea1ddd5fa8c8fcb8d1bbb2d4e4498cb9f39ff47a3801c",
"packages/ui/components/Card.wrn": "4370df341235819b8a968a3f812209c2e6c29e4471d06578aaa5b577fb6e2dc8", "packages/ui/components/Card.wrn": "ba1b577ae4ffa9bd93c1e6a650ce90745df3f78cd6ff9ee85530e32dab56860a",
"packages/ui/components/Chart.wrn": "841def6b024720bd24898975a1434a1cb39c89e1272d42db431d35d27c98c553", "packages/ui/components/Carousel.wrn": "0d39e17a623fb623022f48e79ea1ab9f268e2eb359c491609c137f711a1a3ffc",
"packages/ui/components/ChatBubble.wrn": "0da2bc86830ac712481559bd2fe7214d26f25101f2e8c2e4629e25c004388b52", "packages/ui/components/Chart.wrn": "d2cedd0e02818d0908708c2d07a495c629fad13fa021a2e035d5023dc5c367de",
"packages/ui/components/Checkbox.wrn": "45e6046e3ad4acb1c1805b51fd5bf0b95b346d3d53973ededc5e43ea2fd91f70", "packages/ui/components/ChatBubble.wrn": "c46b9aef38f54e6243663046fe1b18ba6f9223cbe9b479d9335f96f217d401cc",
"packages/ui/components/Clipboard.wrn": "bb8651c7e44517db827603d0c0e546b6891093d7e6589c0b6e45d91b6187591b", "packages/ui/components/Clipboard.wrn": "03fcfb9786008fd7fb0a7719c3279711253afc5bbb9b0262b805dd553c44da8a",
"packages/ui/components/Collapse.wrn": "c537239831c6938f3c39be0ca3ae8ba928c2490e21cfe5853c10f7221a4678ca", "packages/ui/components/Collapse.wrn": "8869ff4a39541b7e0d538e2b63b9a3dcdcfcd4055fa362519c7e73f164f1be9d",
"packages/ui/components/ColorPicker.wrn": "6940b7ae48f1358008b8c3ae6dc7da31f96a1aefadeef18252568b2cda0b5cbf", "packages/ui/components/ColorPicker.wrn": "02f5744c3eb984de4b24de26fbfd3f69021633d4b0b33aceeba84df32f6a7284",
"packages/ui/components/Columns.wrn": "449a427c2f4dd846b5239b73efe5b8d15ad4f20fa28598802a5eb57c33332391", "packages/ui/components/Columns.wrn": "e01496aa53025444cc51671efcce40d6d24d86775b2cbb1ee0def1862bb6315d",
"packages/ui/components/Combobox.wrn": "62a7d861b0e536ceb088cea3552c59acbafaea5184101e3651dc8f4976b99dbc", "packages/ui/components/Combobox.wrn": "140b038735a5902eb901386281a5a01e16fd1c345560a48ff2b143ebaebe1f3d",
"packages/ui/components/Confetti.wrn": "22d687beefd5047055f65e806b4e39f81e18887532ec18d439ac364a044b230a", "packages/ui/components/Confetti.wrn": "b9aece64735c721c24356122e52ca84602d9f646c7d8be1e0b80d9de3163a501",
"packages/ui/components/Container.wrn": "be8fce140043eced8b78c8dd14ff85ace4ed2d91671251641ef83e0fe4da8f2a", "packages/ui/components/Container.wrn": "4a0acbb896f4c11714e3925d912b10ef9aeedfa904b4ad8561aa2cbd9a0fdca0",
"packages/ui/components/ContextMenu.wrn": "2e011e9cb1c09a3a2344ed3fa29dcc74331cd39f838535d8aee4b249dceef0ce", "packages/ui/components/ContextMenu.wrn": "33fb0f60268b12411d66e3031dc0557cb231149e7a8d83340b4b453a6e37cb09",
"packages/ui/components/CopyMarkup.wrn": "8d57a7d72e02c126f855a181fc36e1b969edb90f683dce6b6ffe546d49d2b29b", "packages/ui/components/CustomScrollbar.wrn": "fedb2ab06cc2cf9499c130590945ca6971a3edfb60407b6658ef7756e4be9823",
"packages/ui/components/CustomScrollbar.wrn": "4c1f7758b9cd47e20ecf922403e12b5b15b8127a2744383c6bd280ee20e5fdd1", "packages/ui/components/DataMap.wrn": "6058f92be7fb95e9eedcc714cad1ad43fa70e40c9dd50fd8bba5aae9935953a7",
"packages/ui/components/DataMap.wrn": "65552d73ecd1a148427dbb10d611f9352ca36bd490f595ab1e1a47e173445ba1", "packages/ui/components/DataTable.wrn": "edb935b091750ee3d1aff354007eabd4998fc7e5584d50a6c726c6657765d800",
"packages/ui/components/DataTable.wrn": "5a76e50e8559c724aaf01ec954958ac640c99758d44f1eef8e6eb1cb6dc4f862", "packages/ui/components/DatePicker.wrn": "2c46f92612528cf60aa0b7e8aaae9826094acb5518d3982c2db65aca6d01e103",
"packages/ui/components/DatePicker.wrn": "18b07e59c27c2720bf960e9c04ad70d9e422ddcf0817fede74e71899811bb387", "packages/ui/components/DeviceFrame.wrn": "d918d213e6e0b74d690911ab3b3f440e9b62fa019fd9e8140c5a56c5c0a0843d",
"packages/ui/components/DeviceFrame.wrn": "a881cf2cc1f859b43cc3c7a1e1ca0fb40cea7a9e530ab5bdcb985aa9592d11c0", "packages/ui/components/Divider.wrn": "f5cc449611022b3d3eedf982ae48f6b1a289db7855cc9b6ea0248dcda807ffb3",
"packages/ui/components/Divider.wrn": "e377f8005249cf4f6c51dd4d04ff9c6a70a3f4e3a79d2c02a8ab601754c8553a", "packages/ui/components/DragAndDrop.wrn": "31ffe9d41fc655a38257c841a520c947d3ce2338e174b5e89e32fe9ccf23b9b3",
"packages/ui/components/DragAndDrop.wrn": "9a403ce9ed20d36211d8916911871875c9c7d0952a7de1a21a433eaa0b3c3324", "packages/ui/components/Drawer.wrn": "e6e2778e515361a3475439ab91b6efec3895efab2a4dfdcfb535713f656cc7ed",
"packages/ui/components/Drawer.wrn": "04a77a69edfad6793b5ab60981a719f11061984c087d0d61663566e322649fef", "packages/ui/components/Dropdown.wrn": "627319de0f555f0f07f80e19a59f9fd52e7f74af7cf2cf9ff09e114678ad9eb8",
"packages/ui/components/Dropdown.wrn": "c61b46ae6e54f6aacac9f4ed422f6c659deea9a637fd944645acd37bf270b01d", "packages/ui/components/FeatureCard.wrn": "2a1bfb01cec8a9892d3ff066e48c276fa7c548cb59c15bcd9b4bbf85450a07a2",
"packages/ui/components/FeatureCard.wrn": "a6afd6d4080917106b2c504ea3836b92ac3c18e85f1aafd74ffe49592782b5ec", "packages/ui/components/FeatureGrid.wrn": "9bd3e9becb5d7359841ed21d607131c58da20f2e8e87e25a9fb48f6b1a2b17e5",
"packages/ui/components/FeatureGrid.wrn": "9e3c0440d08c6982861d3e732eeb20f1023413a58243844dfb9d7033bac13d2b", "packages/ui/components/FeatureIconCard.wrn": "620f66a36ca5c8ad8f2ea09e94dc4471e4146189c42149e9d31db58501281639",
"packages/ui/components/FeatureIconCard.wrn": "afe56213543dd78932b060fa19c4e645e558b211b380c42b6d4b0d18e9d543df", "packages/ui/components/FileInput.wrn": "cc4b8f95f11d91ead9a5796e202b2264eeea24aa1c3b1718327a6ea1dd28d785",
"packages/ui/components/FileInput.wrn": "8b63811deb90a03763620bedf0d5d3c7d05a20b8ae34292eaface9b756b32ecd", "packages/ui/components/FileUploadProgress.wrn": "c4b4174025a1de75c35a3497e0afd0b8acc645ad5f85c3ed7296ea295bb05749",
"packages/ui/components/FileUpload.wrn": "3346d8978e134c3a1e6bc742201a3fbb0cffb89da40da6fcd856a2adbb971393", "packages/ui/components/Footer.wrn": "71790c99e4b15a62fdaf4b7a65d7016ca427408b62a2b1c7695ef1b39ba29440",
"packages/ui/components/FileUploadProgress.wrn": "83bcc8558c70fbe0e0670642f65998abd222c235f1031be46bd7c65e6b5c5154", "packages/ui/components/Grid.wrn": "d089d8657298b4d33993ba4bf7b581f816c65db513d8e48cf1fdd18da19e05a8",
"packages/ui/components/Footer.wrn": "a5dfaa5f9548b7fdd4b556c63428e0ea8d22e5e776a5714d76619999438cf618",
"packages/ui/components/Grid.wrn": "b5546b899c509c1f3e8f5edad801bc59acf9fe3008a627f0e4476cd61b5b672e",
"packages/ui/components/Hero.wrn": "e4e39dcd59c273192c7e4136f6583279e20b0956bcba4cfbc3f6e01a1fade152", "packages/ui/components/Hero.wrn": "e4e39dcd59c273192c7e4136f6583279e20b0956bcba4cfbc3f6e01a1fade152",
"packages/ui/components/HeroActions.wrn": "c5720efe2ce7f6c114eb2ea4ccc3e8102a1099078ba23fd13770eddeb6aec640", "packages/ui/components/HeroActions.wrn": "77f69085db73b946c00aa684d50fb5ae474684e440e81edbccfd8ef5dc357e33",
"packages/ui/components/Image.wrn": "4294358e2eddf06ae4384217dbff7693b609c16bf6549f54d6ad27df74d7ae27", "packages/ui/components/Image.wrn": "8113441bf90ab0d1d8618d477125ad339021f39e853db89d656c72c492e7a20b",
"packages/ui/components/Input.wrn": "9ad394eed60f1b05bbd2ccd50dc2de7b78245c52c87afd46055d772c38b7b0c9", "packages/ui/components/InputGroup.wrn": "95333e7cb883c52958e3d6112899f337b00825e7678a2323baed1724983a7238",
"packages/ui/components/InputGroup.wrn": "0e23ea541a60e893c9d9e6f95113911ee459468d8d6105bf03aeb55395359123", "packages/ui/components/InputNumber.wrn": "0ec020084f3e556d438f8264d61f836cfab4951c52d4df60e4944fb734fb38a9",
"packages/ui/components/InputNumber.wrn": "a0aa4f4566bf54ffbef54c99648eab05b5f9870be6ea07639a4ecf3930afb631", "packages/ui/components/Kbd.wrn": "5afaabbb15902bd8fb82cb4155cf6cbfff55600927b54d564d27e2dd8b752386",
"packages/ui/components/Kbd.wrn": "d51fa614b36f971f885410785b481c2e6900b2416e6e7c8868dfda1ebd5f3b4c", "packages/ui/components/LayoutSplitter.wrn": "6bd6812e364420ed5d3439862116c58d0b011b298679005795bdccdbb7455066",
"packages/ui/components/LayoutSplitter.wrn": "dac5a80acb76bf58dfd25233c68190c67d1aa1b3b0d4250a0957ea40912cf722", "packages/ui/components/LegendIndicator.wrn": "11f13413242e106daf347638ee2b2313fd20b05fc7001f03c0f4c00a558dfec0",
"packages/ui/components/LegendIndicator.wrn": "6a7607b27a17196f899132eb952203a1073e4077365a94935f485e700cbac665", "packages/ui/components/Link.wrn": "b986abcc66bc0b1c6296f3ff4e560812e35d89cc680d5251d7a3883e68c1d053",
"packages/ui/components/Link.wrn": "935ac24e8ebd067860dc9709ea9121710315ad6e02ce95cd5e982ae4d5978a02", "packages/ui/components/List.wrn": "9bfa9613f388e5b126fc27d505e8165d7aa4a058e72132b755585464e116490e",
"packages/ui/components/List.wrn": "02f960a8e82fa049aac477bac9c1d9371394c86d9c47513138f61bffc79dc7d8", "packages/ui/components/ListGroup.wrn": "885020eea4dbdfc90b5a659e48a339cba7a205ddb539e30d7f1aa57244d728a5",
"packages/ui/components/ListGroup.wrn": "37e2ec61ceab20ed020a834da03208c493702b41abcf586816e29f2e50d8ee11", "packages/ui/components/Map.wrn": "9023a7a188480b24222a3910d26f6926918470369a1ab610f841c31a6120b9aa",
"packages/ui/components/Map.wrn": "7b8a0d5412a464fd7cab8977d816542c506d8c5dab8230a3c984c2caee407c91", "packages/ui/components/MarketingSectionHeader.wrn": "6f89a3b298ea299db686ddf903f92c4fe5d321719213943b16be82ba36c23302",
"packages/ui/components/MarketingSectionHeader.wrn": "7d6ffcc01a9331474475ea57ca58598be757abd9428b66bdfaf6f3603c5b145b", "packages/ui/components/Marquee.wrn": "2a88c8bd8378469add26b0d937adccac4aa9f2c2005393010298e8e4fa89693e",
"packages/ui/components/Marquee.wrn": "b2b35eedf3297ba12ab3776385a9f3eab001027648d87a2a1968f576eee6c025", "packages/ui/components/MegaMenu.wrn": "8a9a9b348ca1c5e182914ff55d981a91f47a49c52ef5e5efd8e4cb6e56361a43",
"packages/ui/components/MegaMenu.wrn": "48f4ced485df0e8de5217630c8bdcec0b2317bf04e6b279399f2c93d98d56995",
"packages/ui/components/MetricCard.wrn": "6451182739298691908f68258c0250cce2a78b0dc27c97115579ece30d7d9f92", "packages/ui/components/MetricCard.wrn": "6451182739298691908f68258c0250cce2a78b0dc27c97115579ece30d7d9f92",
"packages/ui/components/MetricGrid.wrn": "6018a98c10628ed240ed236ed916c0ff60994d192c3aadafd10bc876be0f9364", "packages/ui/components/MetricGrid.wrn": "6018a98c10628ed240ed236ed916c0ff60994d192c3aadafd10bc876be0f9364",
"packages/ui/components/Modal.wrn": "59d4ae9d6dd2700692d9edecfe53ee868e9f864363a4885961d1813cb2bee51f", "packages/ui/components/Modal.wrn": "59d4ae9d6dd2700692d9edecfe53ee868e9f864363a4885961d1813cb2bee51f",
"packages/ui/components/Nav.wrn": "7dc456b879e9248bde267b986cdcb138b8f3127d98b51e27bc9708129953f5e2", "packages/ui/components/Nav.wrn": "7dc456b879e9248bde267b986cdcb138b8f3127d98b51e27bc9708129953f5e2",
"packages/ui/components/Navbar.wrn": "7989d25819312014c0a0855370b80b925d53ff735d682fdb61e9677a9dacb7f3", "packages/ui/components/Navbar.wrn": "70640f9ff6e615c70ea9a5e996058bad45ebf0e97b02c9d827228a8f475f72e2",
"packages/ui/components/PageHeader.wrn": "0761235a4924eec09877be40b292d27b70db22d210be7d8e989493165c20df86", "packages/ui/components/PageHeader.wrn": "59264dfb26a132fd3eb265c4374f9fcae781f30dd94a087c57e6e8256717cf14",
"packages/ui/components/Pagination.wrn": "9ceb74745b159150b015bbbb1974c68e14a7d4b92bd03491cb23b8855aa07983", "packages/ui/components/Pagination.wrn": "9ceb74745b159150b015bbbb1974c68e14a7d4b92bd03491cb23b8855aa07983",
"packages/ui/components/PinInput.wrn": "5196f584de8d548a5dfa03688c3c95da3c948cead2662b05e927386ccea74299", "packages/ui/components/PinInput.wrn": "2b656cf832c02fa9c28e2fe78bf1147ff05b17a9d8e744c33e1a63bad7c7ebb3",
"packages/ui/components/Popover.wrn": "167f6c476cf3114ac5062ecf7739bddd9b5a81b1d209396a3675067dad1577b2", "packages/ui/components/Popover.wrn": "f2996f00fad3e35fba2d48e70af17f592854eb73b9e8c6a83526aa78c4e41293",
"packages/ui/components/PortalDashboard.wrn": "037d4300b59c7d60543abc7d4aba5c738efc143e9b61abc48aad0ddfcfe6845b", "packages/ui/components/PortalDashboard.wrn": "79b54868f150a551cede0731cc5390a45ce22375f1929f2d537ce67a987bde50",
"packages/ui/components/PreferenceSwitcher.wrn": "2cc186d4dcb6580b4b152e3a265d9ed5ad210d3fdc76330b9092db21894465f0", "packages/ui/components/PreferenceSwitcher.wrn": "241060fb24a16e795c53981506d273fe9110cbb250c73c85928c788c75d05ded",
"packages/ui/components/PublicPageShell.wrn": "507baad0e83dc05c24db42b8af8bd45b18f4e32e418843d77ec906428c687199", "packages/ui/components/PublicPageShell.wrn": "369b74f65e27227311a2ed73e9ca56c8b60908100d6840a798e8ba5f2c8b1040",
"packages/ui/components/Radio.wrn": "0b2d38a5aee3e859280e4590fa1d509d789cc2239082054872fa342817735f1b", "packages/ui/components/RangeSlider.wrn": "eff9db1616d424f9c803185b4efe011942866acad3328179438a200210d97710",
"packages/ui/components/RangeSlider.wrn": "21eb7a5df0ee2cb5e78f628eb920265998eb9f1a4933d4e5c57db0323fd66b41", "packages/ui/components/Rating.wrn": "41bbce2eb7fb6ad08a3aacde97d03af6e346824962148e7655743028c59185d3",
"packages/ui/components/Rating.wrn": "a2d74dc748fc7d98892684c760518b87fa65411b1102e3611252b87245b3ffcb",
"packages/ui/components/Scrollspy.wrn": "76d1c17580c8d460f1222ce97585bdcac9779141f2f30d5460c84ff9f75413d8", "packages/ui/components/Scrollspy.wrn": "76d1c17580c8d460f1222ce97585bdcac9779141f2f30d5460c84ff9f75413d8",
"packages/ui/components/SearchBox.wrn": "315f54f1feaaa47a815d1e2d7a35b492f09fbfadfc6d37228e1009e19e0652ad", "packages/ui/components/SearchBox.wrn": "717f7647fabd762f8ed60a7c3306eac3ddb83c0da292a53c76d91886c2b8f94f",
"packages/ui/components/Section.wrn": "21485de07d8bb986837a65c61c5dd01ba6c2f3d4e37d58c154a584c7b58de0ed", "packages/ui/components/Section.wrn": "33eb8a82a2102229c2b1f64cec497e9e8e70b67adab8dbe396be860fccbb699c",
"packages/ui/components/SectionHeader.wrn": "512cb96636ee1f0540a0f4b4c75603fc0fe7536e7ed7ed7dc82173a16f48a4d3", "packages/ui/components/SectionHeader.wrn": "8eecf2ea7a93139512260e3d46ca2a1afeb341b8bd46b7c30baa6e2c75e25d10",
"packages/ui/components/Select.wrn": "7f046bb11b7c2470dae26d91a4b2261c66a40ae054d0e04c69b6748912d1e204",
"packages/ui/components/Sidebar.wrn": "03f1bbce75ca6d50ed940e62df39c539610e89eadd2064fe24abc7d5470f98bf", "packages/ui/components/Sidebar.wrn": "03f1bbce75ca6d50ed940e62df39c539610e89eadd2064fe24abc7d5470f98bf",
"packages/ui/components/SplitHero.wrn": "70e843565ff869bcf413b11b6d9830b2e2bd229863a00904596f71e2dff0e76d", "packages/ui/components/SplitHero.wrn": "70e843565ff869bcf413b11b6d9830b2e2bd229863a00904596f71e2dff0e76d",
"packages/ui/components/StatsBar.wrn": "c7d1df3895f25b6d3a2e1012a18b97592c7f33e65418b880d486f20c2d490686", "packages/ui/components/StatsBar.wrn": "c7d1df3895f25b6d3a2e1012a18b97592c7f33e65418b880d486f20c2d490686",
"packages/ui/components/Stepper.wrn": "09f216a44f888421091bd1bce760a076927500a573550d5b2b10c4f27f608e92", "packages/ui/components/Stepper.wrn": "df9093e4222ef3250963a8c6fbfec74477242735d23eabe896c29731c54fc14d",
"packages/ui/components/StrongPassword.wrn": "c7c5ef26ece6170dd7db9882f0dc98cb2e4607f1e5d43eb3fd39e5d15bbd3a2e", "packages/ui/components/StrongPassword.wrn": "0df060bf01ace77ad0158df489588df75fd5d426dc04f000abacc2b3ddd93fc5",
"packages/ui/components/StyledIcon.wrn": "4a4504e357dee9dd0418edbe85fc90b824ccdb3752123a2125da95b77bf0b336", "packages/ui/components/StyledIcon.wrn": "bf9b41e9c402c4886feddda504c3f12348bbbe49b9003de69d807db4bf52d1b1",
"packages/ui/components/Switch.wrn": "ccb74599fab72b0d68b09a7f1f90b7732cb2f9cbed67a84219e897575ce30c28", "packages/ui/components/Tabs.wrn": "e38336d28876caa4e97da32a569749e3803b030284a85ce9f35fa788e51cc5b0",
"packages/ui/components/Tabs.wrn": "4fa0c0854700532960043290700d7cf99663ec41692068101f0aae4c2b1a1181", "packages/ui/components/TextLink.wrn": "6e18906337cd3d30c2a0122593d249bde8f7cedc697b90ae388e2bd0e8eff3e1",
"packages/ui/components/TextLink.wrn": "30782039293eb36d63b7b3a4f32a71a47177a3a68c7e90184be7cf7b4385eb19", "packages/ui/components/TimePicker.wrn": "d2fb7cd156bb2abb997a9e85d27c36b80f4ba81515b523a125e793363568475c",
"packages/ui/components/Textarea.wrn": "ddf0b4f124b2cf0c0ab3d820d3ac0085f7c20466e977231949be264cd0cee8cf", "packages/ui/components/Timeline.wrn": "686a37ebc62732842846d004491336970db94945bca942f226097d863e159d1c",
"packages/ui/components/TimePicker.wrn": "2e8e7a90f6b6069a07e7ffd2725ba1e1031e84d55a4f1254025befbb314fa695", "packages/ui/components/Toaster.wrn": "288b2b370f65bf823e8d2a6f8c8cf66972332b8174303d33ebe8546d5e1d52c4",
"packages/ui/components/Timeline.wrn": "5708c656eefd12f31c93490075ee482bf527059028844bd58cdcddc88461e2b5", "packages/ui/components/ToggleCount.wrn": "285abf0ed5c7156c5a6f404af0c2519f181b591975515e845cbc3c21349c5cfd",
"packages/ui/components/Toast.wrn": "f37c584d1c1a66401deb53d915c8ee0c70aaf7baa1d3c297fa74c5bdb914dd1d", "packages/ui/components/TogglePassword.wrn": "94cd3579bb48e53283c82400bff58ea0f2ca92646b87385502e3b474ba787a96",
"packages/ui/components/ToastNotifications.wrn": "33ff76b2a0a129ff896baea8979b4be97f7ec4471a92a2da970403a5c46e8b03", "packages/ui/components/TreeView.wrn": "c354fce9c5e81fed6eec4089dfd2a7f6bb84eb189c94efc88e00251efdc12c0b",
"packages/ui/components/Toaster.wrn": "7481ecddde9ed5bf1f448d45a78da7ee0009f84963814bf681735db4e5ab75f9", "packages/ui/components/Typography.wrn": "1b4d1b91dee0ff522d27c566becc3bc49bd9ec979ae6334e6a15fca5d88eccac",
"packages/ui/components/ToggleCount.wrn": "70a75b2bdcc89103f8ca300ee6d21cd61baf8c6a4aeade9b68d1dc529f77064b", "packages/ui/components/WysiwygEditor.wrn": "231da25f4064b065adb2783252d430aa336585e008547a010a41174a41284524",
"packages/ui/components/TogglePassword.wrn": "405a85cbfa3d0ff0185b52d2805fba88497a5f25501f01b2438ed3a28591a38d", "packages/ui/components/alert.wrn": "6dde2cee818a66e1d80df6b93e9f971d5389f95387961593350b0c297e3ed367",
"packages/ui/components/Tooltip.wrn": "e6c8c14a75062d04a2e64245df1a40e67785240573c73bae4b0b32e76ef822bc", "packages/ui/components/avatar.wrn": "ac2239cc746ec35b81a34ec6e27f4ee74ca04dc6058bad5853aa66640f2a9426",
"packages/ui/components/TreeView.wrn": "1791a10135595b17b5c746af9e0476e8520d516ba46d64c79dc4efe0af2b500d", "packages/ui/components/badge.wrn": "9e1e884e9e2e9d881348c5121f53f4c85b91355cbfa49eeb2bff43f5ab38fab5",
"packages/ui/components/Typography.wrn": "a667e800e11d23da74b00047bd9c560a65a5920e5394f58f66a6e0b32572ce8e", "packages/ui/components/button.wrn": "da1660c671daf2feffe1024203ac1a8bc947beb07691bef595c1552c664b9cde",
"packages/ui/components/WysiwygEditor.wrn": "636e60b9f9be7a5807cca7ad20e0b0f370ee1ec6c1ecba5d0a577726cc86bc98", "packages/ui/components/checkbox.wrn": "a5fb8410558696b3c8e63d5cd8dd49b44d51ed35b809cb2b189501e2e7b323e2",
"packages/ui/components/alert.wrn": "a6020b3fbf02481f76cea4cfa563a7165c7ccc2920492f6f6b79e98cbf2cf0fa", "packages/ui/components/input.wrn": "184066a837982ef3fe44e518730d0cccb7dd6772d5af58116c0774b71cb894e2",
"packages/ui/components/avatar.wrn": "571b70790aff28a5b7d6adbf0196e77ece1ce153a95b384148cb2bb5f74ab2e9", "packages/ui/components/progress.wrn": "61b01ba575d35691e3dfebfcb03ccd1c6b79f1df1c0d41857ec3b01944081a5d",
"packages/ui/components/button.wrn": "647af3918142fd9151e46555345532980633cc3677ec26d0940c253ea789bc31", "packages/ui/components/radio.wrn": "cc65395aad6c0bac690c18cb7c36b1d2784e4c3284be3d3cb277b745f2f29bbc",
"packages/ui/components/carousel.wrn": "37bdde82de59e90cf02c9fed0ee11f5ddd2979246932701dad9412c4fd358a6a", "packages/ui/components/select.wrn": "d58b454958edccce99f96c642eb67d5a0e64e2b97e58e742d445fe45164a7660",
"packages/ui/components/progress.wrn": "ba6f4dfcc00f04f34e9533be675bb4a6200c3cdcd7d03fc597377e48520fdec7", "packages/ui/components/skeleton.wrn": "dda95a2c59c6060ab2b9f916eb046fbe74a68b398a051afc0bde41808e059c3a",
"packages/ui/components/skeleton.wrn": "ceec8af147b8be08155500e378393ac7c72d819da61b62191c076c80e943d5a9", "packages/ui/components/spinner.wrn": "ffbd28ec6fc017fc94c67778f8966eab2eefa3b47f3f27f064c77dbeb65d84c1",
"packages/ui/components/spinner.wrn": "abc4ee3ede2e019289257e9009eee012c880a85839cc220fac6dbe1b780caf52", "packages/ui/components/switch.wrn": "917219d77b2089584bc444fe94f2cb0670f800d73f69ae70f0368bfece16be0c",
"packages/ui/ui.css": "f71641efb0c98b673af70c10dbcea8e693de258ce8620b4f84c64553ea70204e" "packages/ui/components/textarea.wrn": "602ef2c8a081476f790408119b96ec2b51fb18abe76a43ee2b955dcb60debd17",
"packages/ui/components/tooltip.wrn": "40028dfbb17f6725c76c2e091af9c30ea4db6aa4dea919d38f53874ea70315d1",
"packages/ui/styles/ComponentBaseStyles.wrn": "566724a7bcc8f7caa067d5415177bf982ebae7b579a34052ce6f2092cbade4d7",
"packages/ui/styles/FieldStyles.wrn": "460fc8cbb0228151927fd6a61caceae0a148fdbc3009c13ba511ab0a4010b42d",
"packages/ui/styles/SelectStyles.wrn": "2c45e9c04678aaff76632e908b3c061410be0d6999a54a9203a39a3817d698f2",
"packages/ui/ui.css": "b7e2ae796ffbb738451364f63fce13022a81d81207f7e49c7f6220d24c36acd9"
} }
} }
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "wrnexus", "name": "wrnexus",
"version": "0.8.5", "version": "0.8.6",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "wrnexus", "name": "wrnexus",
"version": "0.8.5", "version": "0.8.6",
"license": "SEE LICENSE IN LICENSE", "license": "SEE LICENSE IN LICENSE",
"dependencies": { "dependencies": {
"vscode-languageclient": "^10.1.0" "vscode-languageclient": "^10.1.0"
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "wrnexus", "name": "wrnexus",
"displayName": "WRNexus Language Support", "displayName": "WRNexus Language Support",
"description": "Complete WRNexus v0.8.3 language support for typed imports, props, state, outputs, runtime functions, stores, diagnostics, formatting, navigation, and migration assistance.", "description": "Complete WRNexus v0.8.3 language support for typed imports, props, state, outputs, runtime functions, stores, diagnostics, formatting, navigation, and migration assistance.",
"version": "0.8.5", "version": "0.8.6",
"publisher": "wrnexus", "publisher": "wrnexus",
"private": true, "private": true,
"license": "SEE LICENSE IN LICENSE", "license": "SEE LICENSE IN LICENSE",
+168 -87
View File
@@ -1,6 +1,6 @@
"use strict"; "use strict";
// Generated by scripts/build-editor-compiler.mjs. Do not edit directly. // Generated by scripts/build-editor-compiler.mjs. Do not edit directly.
// WRN editor compiler source hash: 451706f47a7734fd5808cdeeb5786e1b97a183afec98db99fc041369eb16a205 // WRN editor compiler source hash: 196a514b18fb6e31fdd8f4ca667a76b0b62cab547af33725ba78564d70bbc01b
// WRN editor compiler generator hash: c71e7fe4258c97b73b384ff14b321f0cf0b30cc2ed0322f5f84b04e757159b18 // WRN editor compiler generator hash: c71e7fe4258c97b73b384ff14b321f0cf0b30cc2ed0322f5f84b04e757159b18
// Generated with TypeScript: 5.9.3 // Generated with TypeScript: 5.9.3
const __nodeRequire = require; const __nodeRequire = require;
@@ -558,7 +558,7 @@ function browserModuleRequired(ast) {
const functions = ast.runtimeFunctions.filter((fn) => ["legacy", "client", "shared"].includes(fn.runtime)); const functions = ast.runtimeFunctions.filter((fn) => ["legacy", "client", "shared"].includes(fn.runtime));
return functions.length > 0 || selectedBrowserImports(ast, functions).length > 0; return functions.length > 0 || selectedBrowserImports(ast, functions).length > 0;
} }
function functionEntry(ast, fn, availableFunctions) { function _functionEntry(ast, fn, availableFunctions) {
const parameterNames = new Set(fn.parameters.map((parameter) => parameter.name)); const parameterNames = new Set(fn.parameters.map((parameter) => parameter.name));
/* /*
* Names the function body declares for itself. * Names the function body declares for itself.
@@ -608,32 +608,42 @@ function functionEntry(ast, fn, availableFunctions) {
const syncStateFromContext = stateNames const syncStateFromContext = stateNames
.map((name) => `${name} = context.state.${name};`) .map((name) => `${name} = context.state.${name};`)
.join(" "); .join(" ");
const peerAliases = functionAliases const peerAliases = !stateNames.length
.map((name) => { ? functionAliases
const call = `context.functions[${JSON.stringify(name)}](...__wrnexusPeerArgs)`; .map((name) => `const ${name} = (...__wrnexusPeerArgs) => context.functions[${JSON.stringify(name)}](...__wrnexusPeerArgs);`)
if (!stateNames.length) { .join("\n")
return `const ${name} = (...__wrnexusPeerArgs) => ${call};`; : `const __wrnexusFlush = () => { ${syncStateToContext} };
} const __wrnexusRestore = () => { ${syncStateFromContext} };
return `const ${name} = (...__wrnexusPeerArgs) => { const __wrnexusPeer = (name, args) => {
${syncStateToContext} __wrnexusFlush();
let __wrnexusPeerResult; let result;
try { try {
__wrnexusPeerResult = ${call}; result = context.functions[name](...args);
} catch (__wrnexusPeerError) { } catch (error) {
${syncStateFromContext} __wrnexusRestore();
throw __wrnexusPeerError; throw error;
} }
if (__wrnexusPeerResult && typeof __wrnexusPeerResult.then === "function") { if (result && typeof result.then === "function") {
return Promise.resolve(__wrnexusPeerResult).finally(() => { ${syncStateFromContext} }); return Promise.resolve(result).finally(__wrnexusRestore);
} }
${syncStateFromContext} __wrnexusRestore();
return __wrnexusPeerResult; return result;
};`; };
}) ${functionAliases.map((name) => `const ${name} = (...__wrnexusPeerArgs) => __wrnexusPeer(${JSON.stringify(name)}, __wrnexusPeerArgs);`).join("\n")}`;
.join("\n");
const copyBack = stateNames const copyBack = stateNames
.map((name) => `if (!Object.is(${name}, __wrnexusInitialState[${JSON.stringify(name)}])) context.state.${name} = ${name};`) .map((name) => `if (!Object.is(${name}, __wrnexusInitialState[${JSON.stringify(name)}])) context.state.${name} = ${name};`)
.join("\n"); .join("\n");
const commitBinding = stateNames.length
? `const __wrnexusCommit = () => { ${copyBack} };
${!parameterNames.has("commit") && !declaredLocals.has("commit") && !functionAliases.includes("commit") ? "const commit = __wrnexusCommit;" : ""}
${!parameterNames.has("setTimeout") &&
!declaredLocals.has("setTimeout") &&
!functionAliases.includes("setTimeout")
? `const setTimeout = (callback, delay, ...args) => globalThis.setTimeout(() => {
try { return callback(...args); } finally { __wrnexusCommit(); }
}, delay);`
: ""}`
: "";
const body = (0, syntax_1.eraseFunctionTypes)(fn.body); const body = (0, syntax_1.eraseFunctionTypes)(fn.body);
const runtimeBindings = [ const runtimeBindings = [
!parameterNames.has("output") ? "const output = context.output;" : "", !parameterNames.has("output") ? "const output = context.output;" : "",
@@ -647,12 +657,13 @@ function functionEntry(ast, fn, availableFunctions) {
${initialStateSnapshot} ${initialStateSnapshot}
${stateAliases} ${stateAliases}
${propAliases} ${propAliases}
${commitBinding}
${peerAliases} ${peerAliases}
${runtimeBindings} ${runtimeBindings}
try { try {
${body} ${body}
} finally { } finally {
${copyBack} ${stateNames.length ? "__wrnexusCommit();" : ""}
} }
}`; }`;
} }
@@ -663,22 +674,74 @@ function generateBrowserModule(ast) {
const selectedImports = selectedBrowserImports(ast, functions); const selectedImports = selectedBrowserImports(ast, functions);
const imports = selectedImports.map((entry) => entry.code).join("\n"); const imports = selectedImports.map((entry) => entry.code).join("\n");
const importedBindings = [...new Set(selectedImports.flatMap((entry) => entry.bindings))]; const importedBindings = [...new Set(selectedImports.flatMap((entry) => entry.bindings))];
const sharedState = state.filter((name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name));
const sharedProps = ast.props
.map((entry) => entry.name)
.filter((name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name) && !sharedState.includes(name));
const callableAliases = functionNames.filter((name) => safeIdentifier(name) &&
!RUNTIME_BINDINGS.has(name) &&
!sharedState.includes(name) &&
!sharedProps.includes(name));
const sharedCommit = sharedState.map((name) => `context.state.${name} = ${name};`).join(" ");
const sharedRestore = [
...sharedState.map((name) => `${name} = context.state.${name};`),
...sharedProps.map((name) => `${name} = context.props.${name};`),
].join(" ");
const hasAuthoredCommit = callableAliases.includes("commit");
const hasAuthoredSetTimeout = callableAliases.includes("setTimeout");
const implementations = functions
.map((fn) => {
const parameters = fn.parameters.map((parameter) => parameter.name).join(", ");
return `${JSON.stringify(fn.name)}: ${fn.async ? "async " : ""}function(${parameters}) {
try { ${(0, syntax_1.eraseFunctionTypes)(fn.body)} } finally { __wrnexusCommit(); }
}`;
})
.join(",\n");
return `// generated WRNexusJS browser module for ${ast.name} return `// generated WRNexusJS browser module for ${ast.name}
${imports} ${imports}
export const __wrnexusClientFunctions = { export const __wrnexusClientFunctions = {
${functions.map((fn) => ` ${functionEntry(ast, fn, functionNames)}`).join(",\n")} ${functions
.map((fn) => ` ${JSON.stringify(fn.name)}: (context, ...args) => __wrnexusBindings(context)[${JSON.stringify(fn.name)}](...args)`)
.join(",\n")}
}; };
export const __wrnexusClientState = ${JSON.stringify(state)}; export const __wrnexusClientState = ${JSON.stringify(state)};
export const __wrnexusOutputs = ${JSON.stringify(ast.outputs)}; export const __wrnexusOutputs = ${JSON.stringify(ast.outputs)};
export const __wrnexusImportedBindings = { ${importedBindings.join(", ")} }; export const __wrnexusImportedBindings = { ${importedBindings.join(", ")} };
export function bindClientScope(context) { function __wrnexusCreateClientFunctions(context) {
${sharedState.length ? `let { ${sharedState.join(", ")} } = context.state;` : ""}
${sharedProps.length ? `let { ${sharedProps.join(", ")} } = context.props;` : ""}
const output = context.output;
const server = context.server;
const props = context.props;
const refs = context.refs;
const __wrnexusCommit = () => { ${sharedCommit} };
const __wrnexusRestore = () => { ${sharedRestore} };
${!hasAuthoredCommit ? "const commit = __wrnexusCommit;" : ""}
${!hasAuthoredSetTimeout
? `const setTimeout = (callback, delay, ...args) => globalThis.setTimeout(() => {
try { return callback(...args); } finally { __wrnexusCommit(); }
}, delay);`
: ""}
const implementations = {
${implementations}
};
${callableAliases.map((name) => `const ${name} = (...args) => implementations[${JSON.stringify(name)}](...args);`).join("\n ")}
const functions = {}; const functions = {};
const scopedContext = { ...context, functions }; for (const name of Object.keys(implementations)) {
for (const [name, handler] of Object.entries(__wrnexusClientFunctions)) { functions[name] = (...args) => {
functions[name] = (...args) => handler(scopedContext, ...args); __wrnexusRestore();
return implementations[name](...args);
};
} }
return functions; return functions;
} }
function __wrnexusBindings(context) {
return context.__wrnexusBoundFunctions ||
(context.__wrnexusBoundFunctions = __wrnexusCreateClientFunctions(context));
}
export function bindClientScope(context) {
return __wrnexusBindings(context);
}
`; `;
} }
@@ -1282,13 +1345,12 @@ function renderNestedComponentInvocation(node, ctx) {
? `\${__wireProp(${ctx.resolveExpr(wholeExpression)})}` ? `\${__wireProp(${ctx.resolveExpr(wholeExpression)})}`
: compileAttrValue(attr.value, ctx); : compileAttrValue(attr.value, ctx);
const rendered = ` ${attr.name}="${compiledValue}"`; const rendered = ` ${attr.name}="${compiledValue}"`;
if (wholeExpression || if (!attr.value.includes("{") ||
!attr.value.includes("{") || (!exprRefsState(attr.value, ctx.stateNames) && !exprRefsState(attr.value, ctx.propNames))) {
!exprRefsState(attr.value, ctx.stateNames)) {
return rendered; return rendered;
} }
const marker = attrEscape(JSON.stringify([attr.name, attr.value])); const marker = attrEscape(JSON.stringify([attr.name, attr.value]));
return rendered + ` data-wrn-bind-${bindIndex++}="${escLit(marker)}"`; return rendered + ` data-wrn-prop-bind-${bindIndex++}="${escLit(marker)}"`;
}) })
.join(""); .join("");
const loops = loopVarsOf(node); const loops = loopVarsOf(node);
@@ -1392,7 +1454,12 @@ function __wrnexusEscapeHtml(value: unknown): string {
return String(value).replace(/[&<>"']/g, (ch) => __wrnexusHtmlEscapes[ch] ?? ch); return String(value).replace(/[&<>"']/g, (ch) => __wrnexusHtmlEscapes[ch] ?? ch);
} }
function __wrnexusEvalData(data: unknown, body: string, helpers = "", ctx: any): unknown { type __WrnexusContext = import("@wrnexus/core").Context & {
__wrnexusCallApi?: (path: string, method: string) => Promise<unknown>;
localStorage?: unknown;
};
function __wrnexusEvalData(data: unknown, body: string, helpers = "", ctx: __WrnexusContext): unknown {
const adapters = { const adapters = {
cookies: ctx.cookies, cookies: ctx.cookies,
session: ctx.session, session: ctx.session,
@@ -1423,7 +1490,7 @@ function __wrnexusPropAttr(
); );
} }
async function __wrnexusCallApi(path: string, method: string, ctx: any): Promise<unknown> { async function __wrnexusCallApi(path: string, method: string, ctx: __WrnexusContext): Promise<unknown> {
if (typeof ctx.__wrnexusCallApi === "function") { if (typeof ctx.__wrnexusCallApi === "function") {
return await ctx.__wrnexusCallApi(path, method); return await ctx.__wrnexusCallApi(path, method);
} }
@@ -1438,7 +1505,7 @@ async function __wrnexusCallApi(path: string, method: string, ctx: any): Promise
return type.includes("application/json") ? await res.json() : await res.text(); return type.includes("application/json") ? await res.json() : await res.text();
} }
async function __wrnexusRenderSsrBindings(html: string, ctx: any): Promise<string> { async function __wrnexusRenderSsrBindings(html: string, ctx: __WrnexusContext): Promise<string> {
for (const binding of __wrnexusSsrBindings) { for (const binding of __wrnexusSsrBindings) {
const data = await __wrnexusCallApi(binding.path, binding.method, ctx); const data = await __wrnexusCallApi(binding.path, binding.method, ctx);
const value = __wrnexusEvalData(data, binding.body, binding.helpers, ctx); const value = __wrnexusEvalData(data, binding.body, binding.helpers, ctx);
@@ -1871,14 +1938,14 @@ function generate(ast) {
out.push(ssrRuntimeSource()); out.push(ssrRuntimeSource());
out.push(`const __wrnexusSsrBindings = ${JSON.stringify(ssrBindings, null, 2)};`); out.push(`const __wrnexusSsrBindings = ${JSON.stringify(ssrBindings, null, 2)};`);
const decls = loopConsts.length > 0 ? loopConsts.join("\n") + "\n" : ""; const decls = loopConsts.length > 0 ? loopConsts.join("\n") + "\n" : "";
out.push(`export default async function ${ast.name}(ctx: any) { out.push(`export default async function ${ast.name}(ctx: __WrnexusContext) {
${storeDeclarations} ${storeDeclarations}
${serverLoadAliases} ${serverLoadAliases}
${decls} ${decls}
const __state: ${stateType} = ${dynamicStateInitializer}; const __state: ${stateType} = ${dynamicStateInitializer};
${ssrStateAliases} ${ssrStateAliases}
${ssrComputedAliases} ${ssrComputedAliases}
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]])); const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)]));
const __scopeValue = Object.entries(__hydrationState) const __scopeValue = Object.entries(__hydrationState)
.map(([key, value]) => { .map(([key, value]) => {
let encoded: string; let encoded: string;
@@ -1904,13 +1971,13 @@ function generate(ast) {
}`); }`);
} }
else { else {
out.push(`export default ${storeBindings.length > 0 ? "async " : ""}function ${ast.name}(ctx: any) { out.push(`export default ${storeBindings.length > 0 ? "async " : ""}function ${ast.name}(ctx: import("@wrnexus/core").Context) {
${storeDeclarations} ${storeDeclarations}
${serverLoadAliases} ${serverLoadAliases}
const __state: ${stateType} = ${dynamicStateInitializer}; const __state: ${stateType} = ${dynamicStateInitializer};
${ssrStateAliases} ${ssrStateAliases}
${ssrComputedAliases} ${ssrComputedAliases}
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]])); const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)]));
const __scopeValue = Object.entries(__hydrationState) const __scopeValue = Object.entries(__hydrationState)
.map(([key, value]) => { .map(([key, value]) => {
let encoded: string; let encoded: string;
@@ -1934,14 +2001,14 @@ function generate(ast) {
}`); }`);
} }
if (staticShellBody !== undefined) { if (staticShellBody !== undefined) {
out.push(`export async function __wrnexusBuildStaticShell(ctx: any = {}) { out.push(`export async function __wrnexusBuildStaticShell(ctx: import("@wrnexus/core").Context = {} as import("@wrnexus/core").Context) {
${storeDeclarations} ${storeDeclarations}
${serverLoadAliases} ${serverLoadAliases}
${loopConsts.length > 0 ? loopConsts.join("\n") : ""} ${loopConsts.length > 0 ? loopConsts.join("\n") : ""}
const __state: ${stateType} = ${dynamicStateInitializer}; const __state: ${stateType} = ${dynamicStateInitializer};
${ssrStateAliases} ${ssrStateAliases}
${ssrComputedAliases} ${ssrComputedAliases}
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]])); const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)]));
const __scopeValue = Object.entries(__hydrationState) const __scopeValue = Object.entries(__hydrationState)
.map(([key, value]) => { .map(([key, value]) => {
let encoded: string; let encoded: string;
@@ -1991,7 +2058,7 @@ function generate(ast) {
}) })
.join("\n"); .join("\n");
const visible = exposed.filter((entry) => entry.name); const visible = exposed.filter((entry) => entry.name);
return `export async function ${exportName}(ctx: any) { return `export async function ${exportName}(ctx: import("@wrnexus/core").Context) {
${exposed ${exposed
.filter((entry) => !entry.name) .filter((entry) => !entry.name)
.map((entry) => entry.body) .map((entry) => entry.body)
@@ -2014,7 +2081,7 @@ ${visible.length
out.push(`export async function ${action.name}(${action.args.join(", ")}) {${action.body}}`); out.push(`export async function ${action.name}(${action.args.join(", ")}) {${action.body}}`);
continue; continue;
} }
out.push(`export async function ${action.name}(input: any, ctx: any) { out.push(`export async function ${action.name}(input: InferSchema<typeof ${action.schema}>, ctx: import("@wrnexus/core").Context) {
const invalidate = (...tags: string[]) => { const invalidate = (...tags: string[]) => {
const bucket = (ctx.locals.__wrnexusInvalidatedTags ??= []); const bucket = (ctx.locals.__wrnexusInvalidatedTags ??= []);
bucket.push(...tags.flat()); bucket.push(...tags.flat());
@@ -2036,7 +2103,7 @@ ${ast.actions
ast.apis.forEach((api, index) => { ast.apis.forEach((api, index) => {
const name = `__wrnexusApi_${api.method}_${index}`; const name = `__wrnexusApi_${api.method}_${index}`;
out.push(`// ${api.method} ${apiRoutePath(api.path)} out.push(`// ${api.method} ${apiRoutePath(api.path)}
const ${name} = async (ctx: any) => {${api.body}};`); const ${name} = async (ctx: import("@wrnexus/core").Context) => {${api.body}};`);
}); });
const entries = ast.apis.map((api, index) => ` ${JSON.stringify(`${api.method} ${apiRoutePath(api.path)}`)}: __wrnexusApi_${api.method}_${index},`); const entries = ast.apis.map((api, index) => ` ${JSON.stringify(`${api.method} ${apiRoutePath(api.path)}`)}: __wrnexusApi_${api.method}_${index},`);
out.push(`export const __wrnexusApi = {\n${entries.join("\n")}\n};`); out.push(`export const __wrnexusApi = {\n${entries.join("\n")}\n};`);
@@ -2052,7 +2119,7 @@ const ${name} = async (ctx: any) => {${api.body}};`);
if (ast.realtimes.length > 0) { if (ast.realtimes.length > 0) {
const handlers = ast.realtimes.flatMap((rt) => rt.handlers.map((h) => { const handlers = ast.realtimes.flatMap((rt) => rt.handlers.map((h) => {
const params = ["ws", ...h.args].join(", "); const params = ["ws", ...h.args].join(", ");
return ` ${h.event}(${params}: any) {${h.body}},`; return ` ${h.event}(${params}: Event) {${h.body}},`;
})); }));
out.push(`export const websocket = {\n${handlers.join("\n")}\n};`); out.push(`export const websocket = {\n${handlers.join("\n")}\n};`);
} }
@@ -2178,7 +2245,9 @@ function exprRefsState(expr, stateNames) {
return false; return false;
} }
function exprRefsComponentReactiveValue(expr, ctx) { function exprRefsComponentReactiveValue(expr, ctx) {
return exprRefsState(expr, ctx.stateNames) || exprRefsState(expr, ctx.functionNames); return (exprRefsState(expr, ctx.stateNames) ||
exprRefsState(expr, ctx.propNames) ||
exprRefsState(expr, ctx.functionNames));
} }
function viewHasEvents(nodes) { function viewHasEvents(nodes) {
return nodes.some((node) => { return nodes.some((node) => {
@@ -2242,6 +2311,9 @@ function compileText(raw, ctx) {
// list renderer fills it per item; it has no server-side value. // list renderer fills it per item; it has no server-side value.
out += escLit(`{${expr}}`); out += escLit(`{${expr}}`);
} }
else if (expr === "content") {
out += `\${__wireRaw(${ctx.resolveExpr(expr)})}`;
}
else if (exprRefsComponentReactiveValue(expr, ctx)) { else if (exprRefsComponentReactiveValue(expr, ctx)) {
// State interpolation: bake the initial value AND keep it reactive via a // State interpolation: bake the initial value AND keep it reactive via a
// data-text span, so no-JS clients see the real value and hydration // data-text span, so no-JS clients see the real value and hydration
@@ -2251,9 +2323,6 @@ function compileText(raw, ctx) {
`\${__wireHtml(${ctx.resolveExpr(expr)})}` + `\${__wireHtml(${ctx.resolveExpr(expr)})}` +
escLit(`</span>`); escLit(`</span>`);
} }
else if (expr === "content") {
out += `\${__wireRaw(${ctx.resolveExpr(expr)})}`;
}
else { else {
out += `\${__wireHtml(${ctx.resolveExpr(expr)})}`; out += `\${__wireHtml(${ctx.resolveExpr(expr)})}`;
} }
@@ -2593,6 +2662,7 @@ function generateComponent(ast) {
}; };
const ctx = { const ctx = {
stateNames, stateNames,
propNames: new Set(effectiveProps.map((entry) => entry.name)),
functionNames: new Set(ast.runtimeFunctions.filter((fn) => fn.runtime !== "server").map((fn) => fn.name)), functionNames: new Set(ast.runtimeFunctions.filter((fn) => fn.runtime !== "server").map((fn) => fn.name)),
resolveExpr, resolveExpr,
eventNames: publicOutputNames(ast), eventNames: publicOutputNames(ast),
@@ -2610,12 +2680,12 @@ function generateComponent(ast) {
.join(""); .join("");
const styles = ast.styles.map((body) => body.trim()).filter(Boolean); const styles = ast.styles.map((body) => body.trim()).filter(Boolean);
const styleTag = escLit(localStyleTag(ast, styles)); const styleTag = escLit(localStyleTag(ast, styles));
// A component needs a reactive scope only when it has state or event handlers. // Props remain server-rendered and also become signals so a parent can drive
// Prop-driven text/attributes are baked server-side, so static components ship // a mounted child after hydration.
// no JavaScript at all.
const behavior = componentBehavior(ast); const behavior = componentBehavior(ast);
const needsScope = ast.runtime !== "server" && const needsScope = ast.runtime !== "server" &&
(browserStates.length > 0 || (effectiveProps.length > 0 ||
browserStates.length > 0 ||
ast.computed.length > 0 || ast.computed.length > 0 ||
viewHasEvents(ast.view) || viewHasEvents(ast.view) ||
behavior !== null); behavior !== null);
@@ -2632,7 +2702,7 @@ function generateComponent(ast) {
if (prop.required) { if (prop.required) {
decls.push(` if (__p[${JSON.stringify(prop.name)}] === undefined) throw new TypeError(${JSON.stringify(`${ast.name} requires prop '${prop.name}' (${prop.valueType ?? "unknown"})`)});`); decls.push(` if (__p[${JSON.stringify(prop.name)}] === undefined) throw new TypeError(${JSON.stringify(`${ast.name} requires prop '${prop.name}' (${prop.valueType ?? "unknown"})`)});`);
} }
decls.push(` const ${nameRefs.get(prop.name)}: ${prop.valueType ?? "any"} = __coerce(__p[${JSON.stringify(prop.name)}], (${resolveExpr(prop.default)}), ${JSON.stringify((0, types_ts_1.runtimeTypeOf)(prop.valueType))});`); decls.push(` const ${nameRefs.get(prop.name)}: ${prop.valueType ?? "unknown"} = __coerce(__p[${JSON.stringify(prop.name)}], (${resolveExpr(prop.default)}), ${JSON.stringify((0, types_ts_1.runtimeTypeOf)(prop.valueType))}, ${JSON.stringify(prop.name)}) as ${prop.valueType ?? "unknown"};`);
} }
if (!effectiveProps.some((prop) => prop.name === "attrs")) { if (!effectiveProps.some((prop) => prop.name === "attrs")) {
decls.push(` const __attrs = __restProps(__p, new Set(${JSON.stringify(effectiveProps.map((prop) => prop.name))}));`); decls.push(` const __attrs = __restProps(__p, new Set(${JSON.stringify(effectiveProps.map((prop) => prop.name))}));`);
@@ -2697,7 +2767,7 @@ function generateComponent(ast) {
.map((output) => ` ${JSON.stringify(output.name)}(${output.payload ? `${output.payload.name}${output.payload.optional ? "?" : ""}: ${output.payload.valueType}` : ""}): void;`) .map((output) => ` ${JSON.stringify(output.name)}(${output.payload ? `${output.payload.name}${output.payload.optional ? "?" : ""}: ${output.payload.valueType}` : ""}): void;`)
.join("\n")}\n}`); .join("\n")}\n}`);
} }
out.push(`function __coerce(v: any, def: any, declared: string = "unknown"): any { out.push(`function __coerce(v: unknown, def: unknown, declared: string = "unknown", propName: string = "prop"): unknown {
if (v === undefined || v === null) { if (v === undefined || v === null) {
return def; return def;
} }
@@ -2722,14 +2792,14 @@ function generateComponent(ast) {
if (typeof v === "string") { if (typeof v === "string") {
try { try {
const parsed = JSON.parse(v); const parsed = JSON.parse(v);
return Array.isArray(parsed) ? parsed : def; if (!Array.isArray(parsed)) throw new TypeError("Expected an array prop '" + propName + "'");
return parsed;
} catch { } catch {
if (declared === "array") throw new TypeError("Expected an array prop"); throw new TypeError("Expected an array prop '" + propName + "'");
return def;
} }
} }
return def; throw new TypeError("Expected an array prop '" + propName + "'");
} }
if (declared === "object" || (def !== null && typeof def === "object")) { if (declared === "object" || (def !== null && typeof def === "object")) {
@@ -2745,23 +2815,19 @@ function generateComponent(ast) {
try { try {
const parsed = JSON.parse(v); const parsed = JSON.parse(v);
return ( if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
parsed !== null && throw new TypeError("Expected an object prop '" + propName + "'");
typeof parsed === "object" && }
!Array.isArray(parsed) return parsed;
)
? parsed
: def;
} catch { } catch {
if (declared === "object") throw new TypeError("Expected an object prop"); throw new TypeError("Expected an object prop '" + propName + "'");
return def;
} }
} }
return def; throw new TypeError("Expected an object prop '" + propName + "'");
} }
if (declared === "bigint") return BigInt(v); if (declared === "bigint") return BigInt(v as string | number | bigint | boolean);
if (declared === "function" && typeof v !== "function") { if (declared === "function" && typeof v !== "function") {
throw new TypeError("Expected a function prop"); throw new TypeError("Expected a function prop");
} }
@@ -2769,15 +2835,15 @@ function generateComponent(ast) {
} }
function __restProps( function __restProps(
props: Record<string, any>, props: Record<string, unknown>,
declared: Set<string>, declared: Set<string>,
): Record<string, any> { ): Record<string, unknown> {
return Object.fromEntries( return Object.fromEntries(
Object.entries(props).filter(([name]) => !declared.has(name)), Object.entries(props).filter(([name]) => !declared.has(name)),
); );
} }
function __wireHtml(v: any): string { function __wireHtml(v: unknown): string {
return String(v == null ? "" : v).replace( return String(v == null ? "" : v).replace(
/[&<>]/g, /[&<>]/g,
(c) => (c) =>
@@ -2789,7 +2855,7 @@ function __wireHtml(v: any): string {
); );
} }
function __wireAttr(v: any): string { function __wireAttr(v: unknown): string {
return String(v == null ? "" : v).replace( return String(v == null ? "" : v).replace(
/[&<>"]/g, /[&<>"]/g,
(c) => (c) =>
@@ -2803,7 +2869,7 @@ function __wireAttr(v: any): string {
); );
} }
function __wireBooleanAttr(name: string, value: any): string { function __wireBooleanAttr(name: string, value: unknown): string {
return value === true || return value === true ||
value === "true" || value === "true" ||
value === "" || value === "" ||
@@ -2814,7 +2880,7 @@ function __wireBooleanAttr(name: string, value: any): string {
: ""; : "";
} }
function __wireSpreadAttrs(value: any): string { function __wireSpreadAttrs(value: unknown): string {
if (value === null || typeof value !== "object" || Array.isArray(value)) return ""; if (value === null || typeof value !== "object" || Array.isArray(value)) return "";
const booleanAttributes = new Set(${JSON.stringify([...HTML_BOOLEAN_ATTRIBUTES])}); const booleanAttributes = new Set(${JSON.stringify([...HTML_BOOLEAN_ATTRIBUTES])});
@@ -2828,10 +2894,12 @@ function __wireSpreadAttrs(value: any): string {
lowerName === "style" || lowerName === "style" ||
lowerName === "slot" || lowerName === "slot" ||
lowerName === "data-component" || lowerName === "data-component" ||
// Internal markers must not leak through a spread -- except the // Internal markers must not leak through a spread. Parent-owned output
// parent's output handlers, whose whole job is to ride from the mount // and prop bindings ride from the mount onto the
// onto the view root so the mounting scope can bind them there. // rendered child root so the mounting scope can bind them there.
(lowerName.startsWith("data-wrn") && !lowerName.startsWith("data-wrn-out-")) (lowerName.startsWith("data-wrn") &&
!lowerName.startsWith("data-wrn-out-") &&
!lowerName.startsWith("data-wrn-prop-bind-"))
) { ) {
continue; continue;
} }
@@ -2848,7 +2916,7 @@ function __wireSpreadAttrs(value: any): string {
return attributes.join(""); return attributes.join("");
} }
function __wireProp(v: any): string { function __wireProp(v: unknown): string {
const value = const value =
v !== null && typeof v === "object" v !== null && typeof v === "object"
? JSON.stringify(v) ? JSON.stringify(v)
@@ -2857,16 +2925,16 @@ function __wireProp(v: any): string {
return __wireAttr(value); return __wireAttr(value);
} }
function __wireRaw(v: any): string { function __wireRaw(v: unknown): string {
return String(v == null ? "" : v); return String(v == null ? "" : v);
}`); }`);
if (hasServerEach) { if (hasServerEach) {
out.push(`function __wrnexusEncodeLoopLocals(value: Record<string, any>): string { out.push(`function __wrnexusEncodeLoopLocals(value: Record<string, unknown>): string {
return __WrnexusBuffer.from(JSON.stringify(value), "utf8").toString("base64"); return __WrnexusBuffer.from(JSON.stringify(value), "utf8").toString("base64");
}`); }`);
} }
if (needsScope) { if (needsScope) {
out.push(`function __wrnexusSerializeScopeValue(value: any): string { out.push(`function __wrnexusSerializeScopeValue(value: unknown): string {
if (value === undefined) { if (value === undefined) {
return "undefined"; return "undefined";
} }
@@ -2900,7 +2968,7 @@ function __wireRaw(v: any): string {
} }
} }
function __wrnexusScopeDecl(obj: Record<string, any>): string { function __wrnexusScopeDecl(obj: Record<string, unknown>): string {
return Object.keys(obj) return Object.keys(obj)
.map( .map(
(key) => (key) =>
@@ -2918,7 +2986,7 @@ function __wireRaw(v: any): string {
}`); }`);
} }
const serverFunctionSource = serverFunctions ? `${serverFunctions}\n` : ""; const serverFunctionSource = serverFunctions ? `${serverFunctions}\n` : "";
out.push(`export function render(props: ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, any>"} = {} as ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, any>"}): string {\n` + out.push(`export function render(props: ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, unknown>"} = {} as ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, unknown>"}): string {\n` +
` const __p = props || {};\n` + ` const __p = props || {};\n` +
(decls.length > 0 ? decls.join("\n") + "\n" : "") + (decls.length > 0 ? decls.join("\n") + "\n" : "") +
serverFunctionSource + serverFunctionSource +
@@ -5634,7 +5702,12 @@ function parse(source) {
// props { name: Type = <default>; @event name = function } // props { name: Type = <default>; @event name = function }
lx.next(); lx.next();
expect("lbrace"); expect("lbrace");
while (lx.peek().type !== "rbrace") { while (true) {
if (lx.startsWithBlockComment()) {
throw new ParseError("Block comments are not allowed inside props {}; use // line comments instead", "WRN-PROPS-BLOCK-COMMENT");
}
if (lx.peek().type === "rbrace")
break;
const t = lx.peek(); const t = lx.peek();
if (t.type === "eof") if (t.type === "eof")
throw new ParseError("Unexpected end of input inside props"); throw new ParseError("Unexpected end of input inside props");
@@ -6013,6 +6086,9 @@ function parse(source) {
} }
expect("rbrace"); expect("rbrace");
const declaredStates = new Set(states.map((state) => state.name)); const declaredStates = new Set(states.map((state) => state.name));
if (declaredStates.has("page")) {
throw new ParseError("State name 'page' collides with the WRN 'page' keyword; choose another state name", "WRN-STATE-RESERVED-NAME");
}
for (const watcher of watches) { for (const watcher of watches) {
if (!declaredStates.has(watcher.state)) { if (!declaredStates.has(watcher.state)) {
throw new ParseError(`Cannot watch undeclared state '${watcher.state}'`); throw new ParseError(`Cannot watch undeclared state '${watcher.state}'`);
@@ -6543,6 +6619,11 @@ class Lexer {
break; break;
} }
} }
/** True when the next non-trivia characters open a block comment. */
startsWithBlockComment() {
this.skipTrivia();
return this.src[this.pos] === "/" && this.src[this.pos + 1] === "*";
}
/** Read and consume the next structural token. */ /** Read and consume the next structural token. */
next() { next() {
this.skipTrivia(); this.skipTrivia();
+143 -80
View File
@@ -1,4 +1,4 @@
// WRN editor extension source hash: 6afaf7958331407986761b491ad81d56458dcb162f56369a4cc29f86ff95d981 // WRN editor extension source hash: 47188196a996fce0b8f1e2a4302e563767451d9b56df885abc60e9c925653b21
// WRN editor extension generator hash: 456d1d614e44e5fb1f19b784176c09cf2ade9b64ef73a17934c2698150b62728 // WRN editor extension generator hash: 456d1d614e44e5fb1f19b784176c09cf2ade9b64ef73a17934c2698150b62728
"use strict"; "use strict";
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports); var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
@@ -23202,7 +23202,7 @@ ${(0, codegen_ts_1.generate)(ast)}`,
const functions = ast.runtimeFunctions.filter((fn) => ["legacy", "client", "shared"].includes(fn.runtime)); const functions = ast.runtimeFunctions.filter((fn) => ["legacy", "client", "shared"].includes(fn.runtime));
return functions.length > 0 || selectedBrowserImports(ast, functions).length > 0; return functions.length > 0 || selectedBrowserImports(ast, functions).length > 0;
} }
function functionEntry(ast, fn, availableFunctions) { function _functionEntry(ast, fn, availableFunctions) {
const parameterNames = new Set(fn.parameters.map((parameter) => parameter.name)); const parameterNames = new Set(fn.parameters.map((parameter) => parameter.name));
const declaredLocals = new Set; const declaredLocals = new Set;
for (const match of fn.body.matchAll(/\b(?:var|let|const)\s+([A-Za-z_$][\w$]*)|\bfunction\s+([A-Za-z_$][\w$]*)/g)) { for (const match of fn.body.matchAll(/\b(?:var|let|const)\s+([A-Za-z_$][\w$]*)|\bfunction\s+([A-Za-z_$][\w$]*)/g)) {
@@ -23220,30 +23220,33 @@ ${(0, codegen_ts_1.generate)(ast)}`,
const propAliases = propNames.length ? `const { ${propNames.join(", ")} } = context.props;` : ""; const propAliases = propNames.length ? `const { ${propNames.join(", ")} } = context.props;` : "";
const syncStateToContext = stateNames.map((name) => `context.state.${name} = ${name};`).join(" "); const syncStateToContext = stateNames.map((name) => `context.state.${name} = ${name};`).join(" ");
const syncStateFromContext = stateNames.map((name) => `${name} = context.state.${name};`).join(" "); const syncStateFromContext = stateNames.map((name) => `${name} = context.state.${name};`).join(" ");
const peerAliases = functionAliases.map((name) => { const peerAliases = !stateNames.length ? functionAliases.map((name) => `const ${name} = (...__wrnexusPeerArgs) => context.functions[${JSON.stringify(name)}](...__wrnexusPeerArgs);`).join(`
const call = `context.functions[${JSON.stringify(name)}](...__wrnexusPeerArgs)`; `) : `const __wrnexusFlush = () => { ${syncStateToContext} };
if (!stateNames.length) { const __wrnexusRestore = () => { ${syncStateFromContext} };
return `const ${name} = (...__wrnexusPeerArgs) => ${call};`; const __wrnexusPeer = (name, args) => {
} __wrnexusFlush();
return `const ${name} = (...__wrnexusPeerArgs) => { let result;
${syncStateToContext}
let __wrnexusPeerResult;
try { try {
__wrnexusPeerResult = ${call}; result = context.functions[name](...args);
} catch (__wrnexusPeerError) { } catch (error) {
${syncStateFromContext} __wrnexusRestore();
throw __wrnexusPeerError; throw error;
} }
if (__wrnexusPeerResult && typeof __wrnexusPeerResult.then === "function") { if (result && typeof result.then === "function") {
return Promise.resolve(__wrnexusPeerResult).finally(() => { ${syncStateFromContext} }); return Promise.resolve(result).finally(__wrnexusRestore);
} }
${syncStateFromContext} __wrnexusRestore();
return __wrnexusPeerResult; return result;
};`; };
}).join(` ${functionAliases.map((name) => `const ${name} = (...__wrnexusPeerArgs) => __wrnexusPeer(${JSON.stringify(name)}, __wrnexusPeerArgs);`).join(`
`); `)}`;
const copyBack = stateNames.map((name) => `if (!Object.is(${name}, __wrnexusInitialState[${JSON.stringify(name)}])) context.state.${name} = ${name};`).join(` const copyBack = stateNames.map((name) => `if (!Object.is(${name}, __wrnexusInitialState[${JSON.stringify(name)}])) context.state.${name} = ${name};`).join(`
`); `);
const commitBinding = stateNames.length ? `const __wrnexusCommit = () => { ${copyBack} };
${!parameterNames.has("commit") && !declaredLocals.has("commit") && !functionAliases.includes("commit") ? "const commit = __wrnexusCommit;" : ""}
${!parameterNames.has("setTimeout") && !declaredLocals.has("setTimeout") && !functionAliases.includes("setTimeout") ? `const setTimeout = (callback, delay, ...args) => globalThis.setTimeout(() => {
try { return callback(...args); } finally { __wrnexusCommit(); }
}, delay);` : ""}` : "";
const body = (0, syntax_1.eraseFunctionTypes)(fn.body); const body = (0, syntax_1.eraseFunctionTypes)(fn.body);
const runtimeBindings = [ const runtimeBindings = [
!parameterNames.has("output") ? "const output = context.output;" : "", !parameterNames.has("output") ? "const output = context.output;" : "",
@@ -23256,12 +23259,13 @@ ${(0, codegen_ts_1.generate)(ast)}`,
${initialStateSnapshot} ${initialStateSnapshot}
${stateAliases} ${stateAliases}
${propAliases} ${propAliases}
${commitBinding}
${peerAliases} ${peerAliases}
${runtimeBindings} ${runtimeBindings}
try { try {
${body} ${body}
} finally { } finally {
${copyBack} ${stateNames.length ? "__wrnexusCommit();" : ""}
} }
}`; }`;
} }
@@ -23273,23 +23277,66 @@ ${(0, codegen_ts_1.generate)(ast)}`,
const imports = selectedImports.map((entry) => entry.code).join(` const imports = selectedImports.map((entry) => entry.code).join(`
`); `);
const importedBindings = [...new Set(selectedImports.flatMap((entry) => entry.bindings))]; const importedBindings = [...new Set(selectedImports.flatMap((entry) => entry.bindings))];
const sharedState = state.filter((name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name));
const sharedProps = ast.props.map((entry) => entry.name).filter((name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name) && !sharedState.includes(name));
const callableAliases = functionNames.filter((name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name) && !sharedState.includes(name) && !sharedProps.includes(name));
const sharedCommit = sharedState.map((name) => `context.state.${name} = ${name};`).join(" ");
const sharedRestore = [
...sharedState.map((name) => `${name} = context.state.${name};`),
...sharedProps.map((name) => `${name} = context.props.${name};`)
].join(" ");
const hasAuthoredCommit = callableAliases.includes("commit");
const hasAuthoredSetTimeout = callableAliases.includes("setTimeout");
const implementations = functions.map((fn) => {
const parameters = fn.parameters.map((parameter) => parameter.name).join(", ");
return `${JSON.stringify(fn.name)}: ${fn.async ? "async " : ""}function(${parameters}) {
try { ${(0, syntax_1.eraseFunctionTypes)(fn.body)} } finally { __wrnexusCommit(); }
}`;
}).join(`,
`);
return `// generated WRNexusJS browser module for ${ast.name} return `// generated WRNexusJS browser module for ${ast.name}
${imports} ${imports}
export const __wrnexusClientFunctions = { export const __wrnexusClientFunctions = {
${functions.map((fn) => ` ${functionEntry(ast, fn, functionNames)}`).join(`, ${functions.map((fn) => ` ${JSON.stringify(fn.name)}: (context, ...args) => __wrnexusBindings(context)[${JSON.stringify(fn.name)}](...args)`).join(`,
`)} `)}
}; };
export const __wrnexusClientState = ${JSON.stringify(state)}; export const __wrnexusClientState = ${JSON.stringify(state)};
export const __wrnexusOutputs = ${JSON.stringify(ast.outputs)}; export const __wrnexusOutputs = ${JSON.stringify(ast.outputs)};
export const __wrnexusImportedBindings = { ${importedBindings.join(", ")} }; export const __wrnexusImportedBindings = { ${importedBindings.join(", ")} };
export function bindClientScope(context) { function __wrnexusCreateClientFunctions(context) {
${sharedState.length ? `let { ${sharedState.join(", ")} } = context.state;` : ""}
${sharedProps.length ? `let { ${sharedProps.join(", ")} } = context.props;` : ""}
const output = context.output;
const server = context.server;
const props = context.props;
const refs = context.refs;
const __wrnexusCommit = () => { ${sharedCommit} };
const __wrnexusRestore = () => { ${sharedRestore} };
${!hasAuthoredCommit ? "const commit = __wrnexusCommit;" : ""}
${!hasAuthoredSetTimeout ? `const setTimeout = (callback, delay, ...args) => globalThis.setTimeout(() => {
try { return callback(...args); } finally { __wrnexusCommit(); }
}, delay);` : ""}
const implementations = {
${implementations}
};
${callableAliases.map((name) => `const ${name} = (...args) => implementations[${JSON.stringify(name)}](...args);`).join(`
`)}
const functions = {}; const functions = {};
const scopedContext = { ...context, functions }; for (const name of Object.keys(implementations)) {
for (const [name, handler] of Object.entries(__wrnexusClientFunctions)) { functions[name] = (...args) => {
functions[name] = (...args) => handler(scopedContext, ...args); __wrnexusRestore();
return implementations[name](...args);
};
} }
return functions; return functions;
} }
function __wrnexusBindings(context) {
return context.__wrnexusBoundFunctions ||
(context.__wrnexusBoundFunctions = __wrnexusCreateClientFunctions(context));
}
export function bindClientScope(context) {
return __wrnexusBindings(context);
}
`; `;
} }
}, },
@@ -23719,11 +23766,11 @@ export function bindClientScope(context) {
const wholeExpression = wholeAttributeExpression(attr.value); const wholeExpression = wholeAttributeExpression(attr.value);
const compiledValue = wholeExpression ? `\${__wireProp(${ctx.resolveExpr(wholeExpression)})}` : compileAttrValue(attr.value, ctx); const compiledValue = wholeExpression ? `\${__wireProp(${ctx.resolveExpr(wholeExpression)})}` : compileAttrValue(attr.value, ctx);
const rendered = ` ${attr.name}="${compiledValue}"`; const rendered = ` ${attr.name}="${compiledValue}"`;
if (wholeExpression || !attr.value.includes("{") || !exprRefsState(attr.value, ctx.stateNames)) { if (!attr.value.includes("{") || !exprRefsState(attr.value, ctx.stateNames) && !exprRefsState(attr.value, ctx.propNames)) {
return rendered; return rendered;
} }
const marker = attrEscape(JSON.stringify([attr.name, attr.value])); const marker = attrEscape(JSON.stringify([attr.name, attr.value]));
return rendered + ` data-wrn-bind-${bindIndex++}="${escLit(marker)}"`; return rendered + ` data-wrn-prop-bind-${bindIndex++}="${escLit(marker)}"`;
}).join(""); }).join("");
const loops = loopVarsOf(node); const loops = loopVarsOf(node);
const childCtx = loops.length > 0 ? { const childCtx = loops.length > 0 ? {
@@ -23816,7 +23863,12 @@ function __wrnexusEscapeHtml(value: unknown): string {
return String(value).replace(/[&<>"']/g, (ch) => __wrnexusHtmlEscapes[ch] ?? ch); return String(value).replace(/[&<>"']/g, (ch) => __wrnexusHtmlEscapes[ch] ?? ch);
} }
function __wrnexusEvalData(data: unknown, body: string, helpers = "", ctx: any): unknown { type __WrnexusContext = import("@wrnexus/core").Context & {
__wrnexusCallApi?: (path: string, method: string) => Promise<unknown>;
localStorage?: unknown;
};
function __wrnexusEvalData(data: unknown, body: string, helpers = "", ctx: __WrnexusContext): unknown {
const adapters = { const adapters = {
cookies: ctx.cookies, cookies: ctx.cookies,
session: ctx.session, session: ctx.session,
@@ -23847,7 +23899,7 @@ function __wrnexusPropAttr(
); );
} }
async function __wrnexusCallApi(path: string, method: string, ctx: any): Promise<unknown> { async function __wrnexusCallApi(path: string, method: string, ctx: __WrnexusContext): Promise<unknown> {
if (typeof ctx.__wrnexusCallApi === "function") { if (typeof ctx.__wrnexusCallApi === "function") {
return await ctx.__wrnexusCallApi(path, method); return await ctx.__wrnexusCallApi(path, method);
} }
@@ -23862,7 +23914,7 @@ async function __wrnexusCallApi(path: string, method: string, ctx: any): Promise
return type.includes("application/json") ? await res.json() : await res.text(); return type.includes("application/json") ? await res.json() : await res.text();
} }
async function __wrnexusRenderSsrBindings(html: string, ctx: any): Promise<string> { async function __wrnexusRenderSsrBindings(html: string, ctx: __WrnexusContext): Promise<string> {
for (const binding of __wrnexusSsrBindings) { for (const binding of __wrnexusSsrBindings) {
const data = await __wrnexusCallApi(binding.path, binding.method, ctx); const data = await __wrnexusCallApi(binding.path, binding.method, ctx);
const value = __wrnexusEvalData(data, binding.body, binding.helpers, ctx); const value = __wrnexusEvalData(data, binding.body, binding.helpers, ctx);
@@ -24253,14 +24305,14 @@ ${helpers}`);
const decls = loopConsts.length > 0 ? loopConsts.join(` const decls = loopConsts.length > 0 ? loopConsts.join(`
`) + ` `) + `
` : ""; ` : "";
out.push(`export default async function ${ast.name}(ctx: any) { out.push(`export default async function ${ast.name}(ctx: __WrnexusContext) {
${storeDeclarations} ${storeDeclarations}
${serverLoadAliases} ${serverLoadAliases}
${decls} ${decls}
const __state: ${stateType} = ${dynamicStateInitializer}; const __state: ${stateType} = ${dynamicStateInitializer};
${ssrStateAliases} ${ssrStateAliases}
${ssrComputedAliases} ${ssrComputedAliases}
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]])); const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)]));
const __scopeValue = Object.entries(__hydrationState) const __scopeValue = Object.entries(__hydrationState)
.map(([key, value]) => { .map(([key, value]) => {
let encoded: string; let encoded: string;
@@ -24285,13 +24337,13 @@ ${helpers}`);
return await __wrnexusRenderSsrBindings(html, ctx); return await __wrnexusRenderSsrBindings(html, ctx);
}`); }`);
} else { } else {
out.push(`export default ${storeBindings.length > 0 ? "async " : ""}function ${ast.name}(ctx: any) { out.push(`export default ${storeBindings.length > 0 ? "async " : ""}function ${ast.name}(ctx: import("@wrnexus/core").Context) {
${storeDeclarations} ${storeDeclarations}
${serverLoadAliases} ${serverLoadAliases}
const __state: ${stateType} = ${dynamicStateInitializer}; const __state: ${stateType} = ${dynamicStateInitializer};
${ssrStateAliases} ${ssrStateAliases}
${ssrComputedAliases} ${ssrComputedAliases}
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]])); const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)]));
const __scopeValue = Object.entries(__hydrationState) const __scopeValue = Object.entries(__hydrationState)
.map(([key, value]) => { .map(([key, value]) => {
let encoded: string; let encoded: string;
@@ -24315,7 +24367,7 @@ ${helpers}`);
}`); }`);
} }
if (staticShellBody !== undefined) { if (staticShellBody !== undefined) {
out.push(`export async function __wrnexusBuildStaticShell(ctx: any = {}) { out.push(`export async function __wrnexusBuildStaticShell(ctx: import("@wrnexus/core").Context = {} as import("@wrnexus/core").Context) {
${storeDeclarations} ${storeDeclarations}
${serverLoadAliases} ${serverLoadAliases}
${loopConsts.length > 0 ? loopConsts.join(` ${loopConsts.length > 0 ? loopConsts.join(`
@@ -24323,7 +24375,7 @@ ${helpers}`);
const __state: ${stateType} = ${dynamicStateInitializer}; const __state: ${stateType} = ${dynamicStateInitializer};
${ssrStateAliases} ${ssrStateAliases}
${ssrComputedAliases} ${ssrComputedAliases}
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]])); const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)]));
const __scopeValue = Object.entries(__hydrationState) const __scopeValue = Object.entries(__hydrationState)
.map(([key, value]) => { .map(([key, value]) => {
let encoded: string; let encoded: string;
@@ -24370,7 +24422,7 @@ ${helpers}`);
}).join(` }).join(`
`); `);
const visible = exposed.filter((entry) => entry.name); const visible = exposed.filter((entry) => entry.name);
return `export async function ${exportName}(ctx: any) { return `export async function ${exportName}(ctx: import("@wrnexus/core").Context) {
${exposed.filter((entry) => !entry.name).map((entry) => entry.body).join(` ${exposed.filter((entry) => !entry.name).map((entry) => entry.body).join(`
`)} `)}
${declarations} ${declarations}
@@ -24389,7 +24441,7 @@ ${visible.length ? ` const __values = await Promise.all([${visible.map((entry)
out.push(`export async function ${action.name}(${action.args.join(", ")}) {${action.body}}`); out.push(`export async function ${action.name}(${action.args.join(", ")}) {${action.body}}`);
continue; continue;
} }
out.push(`export async function ${action.name}(input: any, ctx: any) { out.push(`export async function ${action.name}(input: InferSchema<typeof ${action.schema}>, ctx: import("@wrnexus/core").Context) {
const invalidate = (...tags: string[]) => { const invalidate = (...tags: string[]) => {
const bucket = (ctx.locals.__wrnexusInvalidatedTags ??= []); const bucket = (ctx.locals.__wrnexusInvalidatedTags ??= []);
bucket.push(...tags.flat()); bucket.push(...tags.flat());
@@ -24407,7 +24459,7 @@ ${ast.actions.map((action) => ` ${action.name}: createActionClient<${action.sch
ast.apis.forEach((api, index) => { ast.apis.forEach((api, index) => {
const name = `__wrnexusApi_${api.method}_${index}`; const name = `__wrnexusApi_${api.method}_${index}`;
out.push(`// ${api.method} ${apiRoutePath(api.path)} out.push(`// ${api.method} ${apiRoutePath(api.path)}
const ${name} = async (ctx: any) => {${api.body}};`); const ${name} = async (ctx: import("@wrnexus/core").Context) => {${api.body}};`);
}); });
const entries = ast.apis.map((api, index) => ` ${JSON.stringify(`${api.method} ${apiRoutePath(api.path)}`)}: __wrnexusApi_${api.method}_${index},`); const entries = ast.apis.map((api, index) => ` ${JSON.stringify(`${api.method} ${apiRoutePath(api.path)}`)}: __wrnexusApi_${api.method}_${index},`);
out.push(`export const __wrnexusApi = { out.push(`export const __wrnexusApi = {
@@ -24425,7 +24477,7 @@ ${entries.join(`
if (ast.realtimes.length > 0) { if (ast.realtimes.length > 0) {
const handlers = ast.realtimes.flatMap((rt) => rt.handlers.map((h) => { const handlers = ast.realtimes.flatMap((rt) => rt.handlers.map((h) => {
const params = ["ws", ...h.args].join(", "); const params = ["ws", ...h.args].join(", ");
return ` ${h.event}(${params}: any) {${h.body}},`; return ` ${h.event}(${params}: Event) {${h.body}},`;
})); }));
out.push(`export const websocket = { out.push(`export const websocket = {
${handlers.join(` ${handlers.join(`
@@ -24547,7 +24599,7 @@ ${handlers.join(`
return false; return false;
} }
function exprRefsComponentReactiveValue(expr, ctx) { function exprRefsComponentReactiveValue(expr, ctx) {
return exprRefsState(expr, ctx.stateNames) || exprRefsState(expr, ctx.functionNames); return exprRefsState(expr, ctx.stateNames) || exprRefsState(expr, ctx.propNames) || exprRefsState(expr, ctx.functionNames);
} }
function viewHasEvents(nodes) { function viewHasEvents(nodes) {
return nodes.some((node) => { return nodes.some((node) => {
@@ -24599,10 +24651,10 @@ ${handlers.join(`
out += escLit(`<span data-t="${attrEscape(expr.slice(2).trim())}"></span>`); out += escLit(`<span data-t="${attrEscape(expr.slice(2).trim())}"></span>`);
} else if (ctx.loopVars && exprRefsState(expr, ctx.loopVars)) { } else if (ctx.loopVars && exprRefsState(expr, ctx.loopVars)) {
out += escLit(`{${expr}}`); out += escLit(`{${expr}}`);
} else if (exprRefsComponentReactiveValue(expr, ctx)) {
out += escLit(`<span data-text="${attrEscape(expr)}">`) + `\${__wireHtml(${ctx.resolveExpr(expr)})}` + escLit(`</span>`);
} else if (expr === "content") { } else if (expr === "content") {
out += `\${__wireRaw(${ctx.resolveExpr(expr)})}`; out += `\${__wireRaw(${ctx.resolveExpr(expr)})}`;
} else if (exprRefsComponentReactiveValue(expr, ctx)) {
out += escLit(`<span data-text="${attrEscape(expr)}">`) + `\${__wireHtml(${ctx.resolveExpr(expr)})}` + escLit(`</span>`);
} else { } else {
out += `\${__wireHtml(${ctx.resolveExpr(expr)})}`; out += `\${__wireHtml(${ctx.resolveExpr(expr)})}`;
} }
@@ -24855,6 +24907,7 @@ ${handlers.join(`
}; };
const ctx = { const ctx = {
stateNames, stateNames,
propNames: new Set(effectiveProps.map((entry) => entry.name)),
functionNames: new Set(ast.runtimeFunctions.filter((fn) => fn.runtime !== "server").map((fn) => fn.name)), functionNames: new Set(ast.runtimeFunctions.filter((fn) => fn.runtime !== "server").map((fn) => fn.name)),
resolveExpr, resolveExpr,
eventNames: publicOutputNames(ast) eventNames: publicOutputNames(ast)
@@ -24867,7 +24920,7 @@ ${handlers.join(`
const styles = ast.styles.map((body) => body.trim()).filter(Boolean); const styles = ast.styles.map((body) => body.trim()).filter(Boolean);
const styleTag = escLit(localStyleTag(ast, styles)); const styleTag = escLit(localStyleTag(ast, styles));
const behavior = componentBehavior(ast); const behavior = componentBehavior(ast);
const needsScope = ast.runtime !== "server" && (browserStates.length > 0 || ast.computed.length > 0 || viewHasEvents(ast.view) || behavior !== null); const needsScope = ast.runtime !== "server" && (effectiveProps.length > 0 || browserStates.length > 0 || ast.computed.length > 0 || viewHasEvents(ast.view) || behavior !== null);
if (hasServerEach || needsScope) { if (hasServerEach || needsScope) {
out.push(`import { Buffer as __WrnexusBuffer } from "node:buffer";`); out.push(`import { Buffer as __WrnexusBuffer } from "node:buffer";`);
} }
@@ -24881,7 +24934,7 @@ ${handlers.join(`
if (prop.required) { if (prop.required) {
decls.push(` if (__p[${JSON.stringify(prop.name)}] === undefined) throw new TypeError(${JSON.stringify(`${ast.name} requires prop '${prop.name}' (${prop.valueType ?? "unknown"})`)});`); decls.push(` if (__p[${JSON.stringify(prop.name)}] === undefined) throw new TypeError(${JSON.stringify(`${ast.name} requires prop '${prop.name}' (${prop.valueType ?? "unknown"})`)});`);
} }
decls.push(` const ${nameRefs.get(prop.name)}: ${prop.valueType ?? "any"} = __coerce(__p[${JSON.stringify(prop.name)}], (${resolveExpr(prop.default)}), ${JSON.stringify((0, types_ts_1.runtimeTypeOf)(prop.valueType))});`); decls.push(` const ${nameRefs.get(prop.name)}: ${prop.valueType ?? "unknown"} = __coerce(__p[${JSON.stringify(prop.name)}], (${resolveExpr(prop.default)}), ${JSON.stringify((0, types_ts_1.runtimeTypeOf)(prop.valueType))}, ${JSON.stringify(prop.name)}) as ${prop.valueType ?? "unknown"};`);
} }
if (!effectiveProps.some((prop) => prop.name === "attrs")) { if (!effectiveProps.some((prop) => prop.name === "attrs")) {
decls.push(` const __attrs = __restProps(__p, new Set(${JSON.stringify(effectiveProps.map((prop) => prop.name))}));`); decls.push(` const __attrs = __restProps(__p, new Set(${JSON.stringify(effectiveProps.map((prop) => prop.name))}));`);
@@ -24941,7 +24994,7 @@ ${ast.outputs.map((output) => ` ${JSON.stringify(output.name)}(${output.payload
`)} `)}
}`); }`);
} }
out.push(`function __coerce(v: any, def: any, declared: string = "unknown"): any { out.push(`function __coerce(v: unknown, def: unknown, declared: string = "unknown", propName: string = "prop"): unknown {
if (v === undefined || v === null) { if (v === undefined || v === null) {
return def; return def;
} }
@@ -24966,14 +25019,14 @@ ${ast.outputs.map((output) => ` ${JSON.stringify(output.name)}(${output.payload
if (typeof v === "string") { if (typeof v === "string") {
try { try {
const parsed = JSON.parse(v); const parsed = JSON.parse(v);
return Array.isArray(parsed) ? parsed : def; if (!Array.isArray(parsed)) throw new TypeError("Expected an array prop '" + propName + "'");
return parsed;
} catch { } catch {
if (declared === "array") throw new TypeError("Expected an array prop"); throw new TypeError("Expected an array prop '" + propName + "'");
return def;
} }
} }
return def; throw new TypeError("Expected an array prop '" + propName + "'");
} }
if (declared === "object" || (def !== null && typeof def === "object")) { if (declared === "object" || (def !== null && typeof def === "object")) {
@@ -24989,23 +25042,19 @@ ${ast.outputs.map((output) => ` ${JSON.stringify(output.name)}(${output.payload
try { try {
const parsed = JSON.parse(v); const parsed = JSON.parse(v);
return ( if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
parsed !== null && throw new TypeError("Expected an object prop '" + propName + "'");
typeof parsed === "object" && }
!Array.isArray(parsed) return parsed;
)
? parsed
: def;
} catch { } catch {
if (declared === "object") throw new TypeError("Expected an object prop"); throw new TypeError("Expected an object prop '" + propName + "'");
return def;
} }
} }
return def; throw new TypeError("Expected an object prop '" + propName + "'");
} }
if (declared === "bigint") return BigInt(v); if (declared === "bigint") return BigInt(v as string | number | bigint | boolean);
if (declared === "function" && typeof v !== "function") { if (declared === "function" && typeof v !== "function") {
throw new TypeError("Expected a function prop"); throw new TypeError("Expected a function prop");
} }
@@ -25013,15 +25062,15 @@ ${ast.outputs.map((output) => ` ${JSON.stringify(output.name)}(${output.payload
} }
function __restProps( function __restProps(
props: Record<string, any>, props: Record<string, unknown>,
declared: Set<string>, declared: Set<string>,
): Record<string, any> { ): Record<string, unknown> {
return Object.fromEntries( return Object.fromEntries(
Object.entries(props).filter(([name]) => !declared.has(name)), Object.entries(props).filter(([name]) => !declared.has(name)),
); );
} }
function __wireHtml(v: any): string { function __wireHtml(v: unknown): string {
return String(v == null ? "" : v).replace( return String(v == null ? "" : v).replace(
/[&<>]/g, /[&<>]/g,
(c) => (c) =>
@@ -25033,7 +25082,7 @@ function __wireHtml(v: any): string {
); );
} }
function __wireAttr(v: any): string { function __wireAttr(v: unknown): string {
return String(v == null ? "" : v).replace( return String(v == null ? "" : v).replace(
/[&<>"]/g, /[&<>"]/g,
(c) => (c) =>
@@ -25047,7 +25096,7 @@ function __wireAttr(v: any): string {
); );
} }
function __wireBooleanAttr(name: string, value: any): string { function __wireBooleanAttr(name: string, value: unknown): string {
return value === true || return value === true ||
value === "true" || value === "true" ||
value === "" || value === "" ||
@@ -25058,7 +25107,7 @@ function __wireBooleanAttr(name: string, value: any): string {
: ""; : "";
} }
function __wireSpreadAttrs(value: any): string { function __wireSpreadAttrs(value: unknown): string {
if (value === null || typeof value !== "object" || Array.isArray(value)) return ""; if (value === null || typeof value !== "object" || Array.isArray(value)) return "";
const booleanAttributes = new Set(${JSON.stringify([...HTML_BOOLEAN_ATTRIBUTES])}); const booleanAttributes = new Set(${JSON.stringify([...HTML_BOOLEAN_ATTRIBUTES])});
@@ -25072,10 +25121,12 @@ function __wireSpreadAttrs(value: any): string {
lowerName === "style" || lowerName === "style" ||
lowerName === "slot" || lowerName === "slot" ||
lowerName === "data-component" || lowerName === "data-component" ||
// Internal markers must not leak through a spread -- except the // Internal markers must not leak through a spread. Parent-owned output
// parent's output handlers, whose whole job is to ride from the mount // and prop bindings ride from the mount onto the
// onto the view root so the mounting scope can bind them there. // rendered child root so the mounting scope can bind them there.
(lowerName.startsWith("data-wrn") && !lowerName.startsWith("data-wrn-out-")) (lowerName.startsWith("data-wrn") &&
!lowerName.startsWith("data-wrn-out-") &&
!lowerName.startsWith("data-wrn-prop-bind-"))
) { ) {
continue; continue;
} }
@@ -25092,7 +25143,7 @@ function __wireSpreadAttrs(value: any): string {
return attributes.join(""); return attributes.join("");
} }
function __wireProp(v: any): string { function __wireProp(v: unknown): string {
const value = const value =
v !== null && typeof v === "object" v !== null && typeof v === "object"
? JSON.stringify(v) ? JSON.stringify(v)
@@ -25101,16 +25152,16 @@ function __wireProp(v: any): string {
return __wireAttr(value); return __wireAttr(value);
} }
function __wireRaw(v: any): string { function __wireRaw(v: unknown): string {
return String(v == null ? "" : v); return String(v == null ? "" : v);
}`); }`);
if (hasServerEach) { if (hasServerEach) {
out.push(`function __wrnexusEncodeLoopLocals(value: Record<string, any>): string { out.push(`function __wrnexusEncodeLoopLocals(value: Record<string, unknown>): string {
return __WrnexusBuffer.from(JSON.stringify(value), "utf8").toString("base64"); return __WrnexusBuffer.from(JSON.stringify(value), "utf8").toString("base64");
}`); }`);
} }
if (needsScope) { if (needsScope) {
out.push(`function __wrnexusSerializeScopeValue(value: any): string { out.push(`function __wrnexusSerializeScopeValue(value: unknown): string {
if (value === undefined) { if (value === undefined) {
return "undefined"; return "undefined";
} }
@@ -25144,7 +25195,7 @@ function __wireRaw(v: any): string {
} }
} }
function __wrnexusScopeDecl(obj: Record<string, any>): string { function __wrnexusScopeDecl(obj: Record<string, unknown>): string {
return Object.keys(obj) return Object.keys(obj)
.map( .map(
(key) => (key) =>
@@ -25163,7 +25214,7 @@ function __wireRaw(v: any): string {
} }
const serverFunctionSource = serverFunctions ? `${serverFunctions} const serverFunctionSource = serverFunctions ? `${serverFunctions}
` : ""; ` : "";
out.push(`export function render(props: ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, any>"} = {} as ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, any>"}): string { out.push(`export function render(props: ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, unknown>"} = {} as ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, unknown>"}): string {
` + ` const __p = props || {}; ` + ` const __p = props || {};
` + (decls.length > 0 ? decls.join(` ` + (decls.length > 0 ? decls.join(`
`) + ` `) + `
@@ -27916,7 +27967,12 @@ ${serverFunctions}
case "props": { case "props": {
lx.next(); lx.next();
expect("lbrace"); expect("lbrace");
while (lx.peek().type !== "rbrace") { while (true) {
if (lx.startsWithBlockComment()) {
throw new ParseError("Block comments are not allowed inside props {}; use // line comments instead", "WRN-PROPS-BLOCK-COMMENT");
}
if (lx.peek().type === "rbrace")
break;
const t = lx.peek(); const t = lx.peek();
if (t.type === "eof") if (t.type === "eof")
throw new ParseError("Unexpected end of input inside props"); throw new ParseError("Unexpected end of input inside props");
@@ -28275,6 +28331,9 @@ ${serverFunctions}
} }
expect("rbrace"); expect("rbrace");
const declaredStates = new Set(states.map((state) => state.name)); const declaredStates = new Set(states.map((state) => state.name));
if (declaredStates.has("page")) {
throw new ParseError("State name 'page' collides with the WRN 'page' keyword; choose another state name", "WRN-STATE-RESERVED-NAME");
}
for (const watcher of watches) { for (const watcher of watches) {
if (!declaredStates.has(watcher.state)) { if (!declaredStates.has(watcher.state)) {
throw new ParseError(`Cannot watch undeclared state '${watcher.state}'`); throw new ParseError(`Cannot watch undeclared state '${watcher.state}'`);
@@ -28748,6 +28807,10 @@ ${serverFunctions}
break; break;
} }
} }
startsWithBlockComment() {
this.skipTrivia();
return this.src[this.pos] === "/" && this.src[this.pos + 1] === "*";
}
next() { next() {
this.skipTrivia(); this.skipTrivia();
const { src } = this; const { src } = this;
+14 -2
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
// WRN editor language server source hash: c4362f7edf117e0c403c9bce9435bc02ae0febd963edb4af9a18d2d74d87eb47 // WRN editor language server source hash: 4c7e59ebeb512e23d95f4169dd80da2b951ae39c31d31919994368b8e90f5cec
// WRN editor language server generator hash: f593a44aaf05495b789ce7a3086bee1eebb951b884d41c0e017bbcfe5f547e72 // WRN editor language server generator hash: f593a44aaf05495b789ce7a3086bee1eebb951b884d41c0e017bbcfe5f547e72
// @bun @bun-cjs // @bun @bun-cjs
(function(exports, require, module, __filename, __dirname) {var __create = Object.create; (function(exports, require, module, __filename, __dirname) {var __create = Object.create;
@@ -168786,6 +168786,10 @@ class Lexer {
break; break;
} }
} }
startsWithBlockComment() {
this.skipTrivia();
return this.src[this.pos] === "/" && this.src[this.pos + 1] === "*";
}
next() { next() {
this.skipTrivia(); this.skipTrivia();
const { src } = this; const { src } = this;
@@ -170521,7 +170525,12 @@ function parse(source) {
case "props": { case "props": {
lx.next(); lx.next();
expect("lbrace"); expect("lbrace");
while (lx.peek().type !== "rbrace") { while (true) {
if (lx.startsWithBlockComment()) {
throw new ParseError("Block comments are not allowed inside props {}; use // line comments instead", "WRN-PROPS-BLOCK-COMMENT");
}
if (lx.peek().type === "rbrace")
break;
const t = lx.peek(); const t = lx.peek();
if (t.type === "eof") if (t.type === "eof")
throw new ParseError("Unexpected end of input inside props"); throw new ParseError("Unexpected end of input inside props");
@@ -170880,6 +170889,9 @@ function parse(source) {
} }
expect("rbrace"); expect("rbrace");
const declaredStates = new Set(states.map((state) => state.name)); const declaredStates = new Set(states.map((state) => state.name));
if (declaredStates.has("page")) {
throw new ParseError("State name 'page' collides with the WRN 'page' keyword; choose another state name", "WRN-STATE-RESERVED-NAME");
}
for (const watcher of watches) { for (const watcher of watches) {
if (!declaredStates.has(watcher.state)) { if (!declaredStates.has(watcher.state)) {
throw new ParseError(`Cannot watch undeclared state '${watcher.state}'`); throw new ParseError(`Cannot watch undeclared state '${watcher.state}'`);
+1
View File
@@ -13,6 +13,7 @@ export default tseslint.config(
"**/dist/**", "**/dist/**",
".publish/**", ".publish/**",
"**/.wrnexus/**", "**/.wrnexus/**",
"**/.wrnexus-*/**",
"coverage/**", "coverage/**",
"bun.lock", "bun.lock",
"bun.lockb", "bun.lockb",
+2
View File
@@ -1,6 +1,8 @@
node_modules/ node_modules/
dist/ dist/
.wrnexus/ .wrnexus/
.wrnexus-*/
**/.wrnexus-*/
.wirefw/ .wirefw/
**/.wirefw/ **/.wirefw/
**/*.gen.ts **/*.gen.ts
+29 -1
View File
@@ -60,10 +60,27 @@ describe("full app", () => {
}); });
afterAll(() => app?.close()); afterAll(() => app?.close());
test("home page responds with HTML", async () => { test("home page server-renders translated page and layout text", async () => {
const res = await app.fetch("/"); const res = await app.fetch("/");
expect(res.status).toBe(200); expect(res.status).toBe(200);
expect(res.headers.get("content-type")).toContain("text/html"); expect(res.headers.get("content-type")).toContain("text/html");
const html = await res.text();
expect(html).toContain('<span data-t="nav.home">Home</span>');
expect(html).toContain('<span data-t="home.title">Hello from WrNexus</span>');
expect(html).not.toContain('<span data-t="nav.home"></span>');
});
test("home page ships only its active theme stylesheet", async () => {
const response = await app.fetch("/");
const html = await response.text();
const href = html.match(/data-wrnexus-theme href="([^"]+)"/)?.[1];
expect(href).toContain("/__wrnexus/theme/dark/");
expect(html).not.toContain('href="/__wrnexus/theme.css');
const cssResponse = await app.fetch(String(href));
expect(cssResponse.status).toBe(200);
const css = await cssResponse.text();
expect(css.length).toBeLessThan(10_000);
expect(css).not.toContain("[data-theme=");
}); });
test("GET /api/hello returns a translated greeting", async () => { test("GET /api/hello returns a translated greeting", async () => {
@@ -94,6 +111,17 @@ describe("full app", () => {
expect(html).not.toContain("Card title"); expect(html).not.toContain("Card title");
}); });
test("shared UI foundations render on demand instead of shipping in ui.css", async () => {
const fieldResponse = await app.fetch("/modal");
expect(fieldResponse.status).toBe(200);
const fieldHtml = await fieldResponse.text();
expect(fieldHtml).toContain('data-wrnexus-style-owner="FieldStyles"');
expect(fieldHtml).toContain('data-wrnexus-style-owner="ComponentBaseStyles"');
const selectResponse = await app.fetch("/test");
expect(selectResponse.status).toBe(200);
expect(await selectResponse.text()).toContain('data-wrnexus-style-owner="SelectStyles"');
});
test("platform showcase uses the public layout and styled reactive primitives", async () => { test("platform showcase uses the public layout and styled reactive primitives", async () => {
const res = await app.fetch("/platform-showcase"); const res = await app.fetch("/platform-showcase");
expect(res.status).toBe(200); expect(res.status).toBe(200);
@@ -11,6 +11,8 @@ component PublicLayout {
<a href="/chat">{t:nav.chat}</a> <a href="/chat">{t:nav.chat}</a>
<a href="/dashboard">{t:nav.dashboard}</a> <a href="/dashboard">{t:nav.dashboard}</a>
<a href="/about">{t:nav.about}</a> <a href="/about">{t:nav.about}</a>
<a href="/navigation">{t:nav.navigation}</a>
<a href="/layout">{t:nav.layout}</a>
<button type="button" data-wire-theme-toggle aria-label="Toggle color theme">Theme</button> <button type="button" data-wire-theme-toggle aria-label="Toggle color theme">Theme</button>
<button class="wire-btn wire-btn--ghost wire-btn--sm" data-wire-lang-set="en">EN</button> <button class="wire-btn wire-btn--ghost wire-btn--sm" data-wire-lang-set="en">EN</button>
<button class="wire-btn wire-btn--ghost wire-btn--sm" data-wire-lang-set="es">ES</button> <button class="wire-btn wire-btn--ghost wire-btn--sm" data-wire-lang-set="es">ES</button>
+3 -1
View File
@@ -4,7 +4,9 @@
"ui": "UI", "ui": "UI",
"chat": "Chat", "chat": "Chat",
"dashboard": "Dashboard", "dashboard": "Dashboard",
"about": "About" "about": "About",
"navigation": "Navigation",
"layout": "Layout"
}, },
"home": { "home": {
"title": "Hello from WrNexus", "title": "Hello from WrNexus",
+3 -1
View File
@@ -4,7 +4,9 @@
"ui": "UI", "ui": "UI",
"chat": "Chat", "chat": "Chat",
"dashboard": "Panel", "dashboard": "Panel",
"about": "Acerca de" "about": "Acerca de",
"navigation": "Navegación",
"layout": "Diseño"
}, },
"home": { "home": {
"title": "Hola desde WrNexus", "title": "Hola desde WrNexus",
+211
View File
@@ -0,0 +1,211 @@
// Layout tour. Route: /layout
//
// One page built out of the layout set, rather than showing each piece alone:
//
// PublicPageShell the outer shell, holding the page background
// Section each band down the page, with its own spacing and tint
// SectionHeader the eyebrow, heading and description of each band
// Container the reading width
// Grid the card deck, including an auto-fit track
// Columns the coarse two-way split
// LayoutSplitter a resizable pane, by drag or by arrow key
// CustomScrollbar a themed scrolling region
// Divider the rules between bands
// Typography the prose column
// Image, Link, Kbd the smaller pieces inside it
//
// CustomScrollbar sits beside the splitter rather than inside a pane of it: a
// component tag nested in another component slot is dropped, and only its
// children survive.
//
// The splitter does not report into a status line here. It emits a sizeChange
// output and the component does fire it, but a parent binding on the tag is
// not invoked, so wiring one up would show something that never updates.
page LayoutTour {
layout = "public"
seo {
title = "Layout tour"
description = "Every layout component working together on one page."
}
view {
<PublicPageShell background="default" minHeight="dvh" fullWidth={true}>
<Section spacing="lg" variant="soft" borderBottom={true}>
<Container maxWidth="lg" columns={1}>
<SectionHeader
eyebrow="Layout"
title="Composing a page"
description="Every band on this page is a Section. The width you are reading at is a Container, and the pieces inside are the rest of the layout set."
align="center"
maxWidth="2xl"
/>
</Container>
</Section>
<Section spacing="lg">
<Container maxWidth="lg" columns={1}>
<SectionHeader
eyebrow="Grid"
title="Card decks"
description="A fixed column count collapses at each breakpoint. Pass minItemWidth instead and the browser decides the count, reflowing continuously."
headingLevel={3}
/>
<Divider />
<Grid columns={3} gap="md">
<div class="tour-card">
<h4>Fixed columns</h4>
<p>Three tracks on a wide screen, two on a tablet, one on a phone.</p>
</div>
<div class="tour-card">
<h4>Themed</h4>
<p>Every surface here is a theme token, so light and dark both work.</p>
</div>
<div class="tour-card">
<h4>No Tailwind</h4>
<p>These components carry their own styles and no longer need it.</p>
</div>
</Grid>
<Divider label="auto fit" />
<Grid minItemWidth="12rem" gap="sm">
<div class="tour-card tour-card--compact"><h4>One</h4></div>
<div class="tour-card tour-card--compact"><h4>Two</h4></div>
<div class="tour-card tour-card--compact"><h4>Three</h4></div>
<div class="tour-card tour-card--compact"><h4>Four</h4></div>
<div class="tour-card tour-card--compact"><h4>Five</h4></div>
</Grid>
</Container>
</Section>
<Section spacing="lg" variant="soft" borderTop={true} borderBottom={true}>
<Container maxWidth="lg" columns={1}>
<SectionHeader
eyebrow="Split"
title="Columns and panes"
description="Columns is the coarse split of a page. LayoutSplitter is the one the reader can move."
headingLevel={3}
/>
<Columns columns={2} gap="lg">
<Typography maxWidth="full">
<h4>Prose</h4>
<p>
Typography caps the measure so a line never runs too long to
follow. Inside it you get the smaller pieces: a
<Link href="/navigation" label="link" /> to somewhere else, and
<Kbd label="Ctrl" /> <Kbd label="K" /> for a shortcut.
</p>
</Typography>
<Image src="" alt="Placeholder artwork" size="video" rounded={true} />
</Columns>
</Container>
</Section>
<Section spacing="lg">
<Container maxWidth="lg" columns={1}>
<SectionHeader
eyebrow="Interactive"
title="Resize and scroll"
description="The splitter resolves its size in the runtime and reports it here. The scrolling region themes its own scrollbar."
headingLevel={3}
/>
<p class="tour-status">
Drag the divider, or focus it and use the arrow keys. Home and End
go to the bounds. The live value is on the handle itself, as
aria-valuenow, which is what a screen reader announces.
</p>
<LayoutSplitter size={45} minSize={20} step={5} label="Resize the panes">
<div data-slot="start">
<div class="tour-pane">
<h4>Start pane</h4>
<p>Drag the divider, or focus it and use the arrow keys. Home and End go to the bounds.</p>
</div>
</div>
<div data-slot="end">
<div class="tour-pane">
<h4>End pane</h4>
<p>The size is resolved in the runtime and written onto the container, so the panes are plain grid tracks.</p>
</div>
</div>
</LayoutSplitter>
<Divider label="scrolling" />
<CustomScrollbar maxHeight="12rem" thickness={10}>
<div class="tour-scroll">
<p>This region scrolls, and its scrollbar follows the theme.</p>
<p>Scrollbar appearance is CSS rather than script.</p>
<p>scrollbar-width and scrollbar-color are the standard properties.</p>
<p>The webkit rules cover the engines that still need them.</p>
<p>Enough lines here that the region actually overflows.</p>
<p>Otherwise there would be no scrollbar to look at.</p>
<p>Which is the mistake the showcase demo made first time.</p>
</div>
</CustomScrollbar>
</Container>
</Section>
</PublicPageShell>
}
style {
.tour-card {
padding: 1rem;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-md);
background: var(--wire-color-surface);
}
.tour-card h4,
.tour-pane h4 {
margin: 0 0 0.35rem;
font-size: 0.95rem;
font-weight: 700;
}
.tour-card p,
.tour-pane p,
.tour-scroll p {
margin: 0;
color: var(--wire-color-text-muted);
font-size: 0.86rem;
line-height: 1.6;
}
.tour-card--compact {
text-align: center;
}
.tour-status {
margin: 0 0 0.75rem;
padding: 0.5rem 0.75rem;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-sm);
background: var(--wire-color-surface-soft);
color: var(--wire-color-text-muted);
font-size: 0.82rem;
}
.tour-pane {
padding: 1rem;
}
.tour-scroll {
display: grid;
gap: 0.6rem;
padding: 0.75rem;
}
/* The splitter panes need a floor or they collapse to their text. */
.tour-pane,
.tour-scroll {
min-height: 8rem;
}
}
}
+4 -7
View File
@@ -1,3 +1,7 @@
import Input from "@wrnexus/ui/components/input.wrn"
import Modal from "@wrnexus/ui/components/Modal.wrn"
import Toaster from "@wrnexus/ui/components/Toaster.wrn"
// Modal demo page. Route: /modal // Modal demo page. Route: /modal
// //
// Five real-world use cases for the shared @wrnexus/ui Modal component — each // Five real-world use cases for the shared @wrnexus/ui Modal component — each
@@ -9,13 +13,6 @@
// this page. Any client expression can raise one with the runtime global — // this page. Any client expression can raise one with the runtime global —
// toast("..."), toast.success(...), toast.error(...) — so pages never // toast("..."), toast.success(...), toast.error(...) — so pages never
// hand-roll a status banner and never reach for a blocking alert(). // hand-roll a status banner and never reach for a blocking alert().
//
// No import here on purpose: component tags resolve by directory scan, and
// @wrnexus/ui's index.ts intentionally has no runtime exports (registry.ts
// holds the server-only filesystem helpers instead) — an explicit
// `import { Modal } from "@wrnexus/ui"` fails to bundle since there's no
// matching export. The WRN-IMPORT-IMPLICIT warning this triggers is safe to
// ignore for component tags.
page ModalPage { page ModalPage {
state noteText = "Remember to ship the release notes" state noteText = "Remember to ship the release notes"
+438
View File
@@ -0,0 +1,438 @@
// Navigation tour. Route: /navigation
//
// One page that wires every navigation component together the way a real
// console would, rather than showing each in isolation:
//
// Navbar the top bar, with a dropdown built from nested children
// MegaMenu a wide product panel beside it
// Breadcrumb the trail under the bar
// Sidebar the left rail, which becomes a Drawer on a phone
// Tabs the main content, backed by a tab query parameter
// Stepper a wizard inside one tab, gated on a checkbox
// Pagination under the list in another tab
// Scrollspy a contents rail that follows the reader down the article
// Nav the secondary links in the footer
//
// Object props are held in state and bound. Passing one inline as an
// attribute would put a brace at the start of the value, which the compiler
// reads as an interpolation.
page NavigationTour {
layout = "public"
seo {
title = "Navigation tour"
description = "Every navigation component working together in one shell."
}
state brand = { label: "Acme", description: "Console", href: "/navigation" }
state topLinks = [
{ label: "Overview", href: "/navigation", value: "overview" },
{
label: "Workspace",
value: "workspace",
columns: 2,
description: "Everything your team touches.",
children: [
{
label: "Projects",
children: [
{ label: "Active", href: "/navigation", description: "In flight right now." },
{ label: "Archived", href: "/navigation", description: "Kept for the record." }
]
},
{
label: "People",
children: [
{ label: "Members", href: "/navigation", description: "Who can sign in." },
{ label: "Roles", href: "/navigation", description: "What they may do." }
]
}
]
},
{ label: "Reports", href: "/navigation", value: "reports" }
]
state topActions = [
{ label: "Docs", href: "/about", variant: "link" },
{ label: "New project", href: "/navigation", variant: "primary" }
]
state megaColumns = [
{
heading: "Platform",
items: [
{ label: "Runtime", href: "/navigation", description: "The browser half.", icon: "icon-[lucide--cpu]" },
{ label: "Compiler", href: "/navigation", description: "WRN to JavaScript.", icon: "icon-[lucide--hammer]" }
]
},
{
heading: "Tooling",
items: [
{ label: "CLI", href: "/navigation", description: "Scaffold and deploy.", icon: "icon-[lucide--terminal]" },
{ label: "Editor", href: "/navigation", description: "Language server.", icon: "icon-[lucide--code]" }
]
}
]
state crumbs = [
{ label: "Home", href: "/" },
{ label: "Console", href: "/navigation" },
{ label: "Navigation tour" }
]
state sideItems = [
{ label: "Overview", href: "#overview", value: "overview", icon: "icon-[lucide--gauge]" },
{
heading: "Build",
items: [
{ label: "Projects", href: "#projects", value: "projects", badge: "4" },
{
label: "Environments",
value: "envs",
items: [
{ label: "Production", href: "#projects", value: "prod" },
{ label: "Staging", href: "#projects", value: "staging" }
]
}
]
},
{
heading: "Account",
items: [{ label: "Settings", href: "#settings", value: "settings", icon: "icon-[lucide--settings]" }]
}
]
state contentTabs = [
{ label: "Article", value: "article", title: "Release notes" },
{ label: "Wizard", value: "wizard", title: "Create a project" },
{ label: "Members", value: "members", title: "Who can sign in" }
]
state spyLinks = [
{ label: "Overview", href: "#overview" },
{ label: "Projects", href: "#projects" },
{ label: "Settings", href: "#settings" }
]
state wizardSteps = [
{ label: "Details", title: "Name the project", content: "Pick something the team will recognise." },
{ label: "Access", title: "Who can see it", content: "Invite people now or later." },
{ label: "Review", title: "Check and create", content: "Nothing is created until you finish." }
]
state footerLinks = [
{ label: "Status", href: "/navigation", value: "status" },
{ label: "Changelog", href: "/navigation", value: "changelog" },
{ label: "Support", href: "/about", value: "support" }
]
state members = [
"A. Okafor", "R. Silva", "M. Chen", "J. Dubois", "P. Novak",
"T. Ahmed", "L. Rossi", "K. Bauer", "S. Haddad", "N. Ito"
]
// Tabs reports the selection and this page owns what is shown. Slot content
// and component props are rendered once and do not track page state, so
// anything that has to react lives out here in page scope.
state section = "article"
state sideActive = "overview"
state wizardStep = 0
// Not named page: that is the page keyword and shadows the runtime binding.
state currentPage = 1
state pageSize = 4
state lastEvent = "Nothing yet. Interact with any control."
functions {
client function onSideSelect(payload) {
sideActive = payload.value || ""
lastEvent = "Sidebar select: " + (payload.value || "(none)")
}
client function onTabChange(payload) {
section = payload.value
lastEvent = "Tab change: " + payload.value
}
// The stepper never validates anything itself. This page owns the
// checkbox, so it decides when the step may advance.
client function onWizardChange(payload) {
wizardStep = payload.index
lastEvent = "Wizard step: " + (payload.index + 1)
}
client function onWizardFinish() {
lastEvent = "Wizard finished. A real app would create the project here."
}
client function onWizardSkip(payload) {
wizardStep = payload.index
lastEvent = "Wizard skipped to step " + (payload.index + 1)
}
client function onPageChange(payload) {
currentPage = payload.page
lastEvent = "Page " + payload.page
}
client function onSpyChange(payload) {
lastEvent = "Reading: " + payload.label
}
client function onMegaSelect(payload) {
lastEvent = "Mega menu: " + payload.value
}
client function onNavSelect(payload) {
lastEvent = "Footer nav: " + payload.value
}
}
view {
<div class="tour">
<Navbar
brand={brand}
items={topLinks}
actions={topActions}
active="overview"
label="Primary navigation"
mobileLabel="Toggle navigation"
/>
<div class="tour__toolbar">
<MegaMenu
label="Products"
icon="icon-[lucide--box]"
columns={megaColumns}
footer="Not sure where to start? Talk to us."
@select="onMegaSelect(payload)"
/>
<Breadcrumb items={crumbs} />
</div>
<div class="tour__shell">
<aside class="tour__rail">
<Sidebar
items={sideItems}
active={sideActive}
label="Console"
mobileLabel="Open navigation"
drawerTitle="Console"
@select="onSideSelect(payload)"
/>
</aside>
<main class="tour__main">
<p class="tour__status" role="status">{lastEvent}</p>
<Tabs
items={contentTabs}
active="article"
mode="url"
param="tab"
label="Console sections"
@change="onTabChange(payload)"
/>
<section class="tour__panel" data-show="section === 'article'">
<section id="overview" class="tour__section">
<h3>Overview</h3>
<p>
Scroll this column and the contents rail on the right follows along.
The runtime observes these sections against the viewport and moves
aria-current to the matching link.
</p>
</section>
<section id="projects" class="tour__section">
<h3>Projects</h3>
<p>
The rail on the left is the Sidebar. Below the tablet breakpoint it
collapses behind a launcher and opens as a Drawer, which is where the
focus trap and the body scroll lock come from.
</p>
</section>
<section id="settings" class="tour__section">
<h3>Settings</h3>
<p>
The tab you are reading is mirrored into the URL. Switch tabs and the
address bar follows, then use the browser back button.
</p>
</section>
</section>
<section class="tour__panel" data-show="section === 'wizard'">
<Stepper
steps={wizardSteps}
active={wizardStep}
showPanel={true}
controls={true}
allowSkip={true}
nextDisabled={false}
backLabel="Back"
nextLabel="Next"
skipLabel="Skip"
finishLabel="Create project"
label="Create a project"
@change="onWizardChange(payload)"
@skip="onWizardSkip(payload)"
@finish="onWizardFinish()"
/>
<p class="tour__note">
Back, Skip and Next each emit an output, shown in the status line
above, and Next becomes Create project on the last step. The marker
does not advance here: active is a component prop, and props are
rendered once rather than tracking page state. Set nextDisabled to
hold Next while a form is incomplete.
</p>
</section>
<section class="tour__panel" data-show="section === 'members'">
<p class="tour__note">
Showing page {currentPage} of 3. Pagination reports the requested
page through its change output and the page decides what to do with
it; the counter above is bound to that state. Re-slicing the list
itself would need the list re-rendered, which an each block does not
do when state changes.
</p>
<ul class="tour__members">
{#each members as person}
<li>{person}</li>
{/each}
</ul>
<Pagination
page={currentPage}
pageSize={pageSize}
total={members.length}
variant="numbered"
@change="onPageChange(payload)"
/>
</section>
</main>
<aside class="tour__toc">
<Scrollspy
items={spyLinks}
heading="On this page"
active="#overview"
@change="onSpyChange(payload)"
/>
</aside>
</div>
<footer class="tour__footer">
<Nav
items={footerLinks}
active="status"
label="Secondary"
collapsible={false}
@select="onNavSelect(payload)"
/>
</footer>
</div>
}
style {
.tour {
display: grid;
gap: 1rem;
padding-bottom: 3rem;
}
.tour__toolbar {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 1rem;
}
/*
* Three columns: the rail, the reading column and the contents. The middle
* column carries min-width 0 so a wide table or code block inside a tab
* cannot push the whole shell sideways.
*/
.tour__shell {
display: grid;
grid-template-columns: 15rem minmax(0, 1fr) 12rem;
gap: 1.5rem;
align-items: start;
}
.tour__main {
min-width: 0;
}
.tour__rail,
.tour__toc {
position: sticky;
top: 1rem;
}
.tour__status {
margin: 0 0 0.75rem;
padding: 0.5rem 0.75rem;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-sm);
background: var(--wire-color-surface-soft);
color: var(--wire-color-text-muted);
font-size: 0.82rem;
}
/* Tall enough that scrolling actually moves between sections. */
.tour__section {
min-height: 60vh;
padding-top: 0.5rem;
scroll-margin-top: 1rem;
}
.tour__section h3 {
margin: 0 0 0.4rem;
font-size: 1rem;
font-weight: 700;
}
.tour__section p {
margin: 0;
max-width: 46rem;
color: var(--wire-color-text-muted);
line-height: 1.7;
}
.tour__members {
display: grid;
gap: 0.35rem;
margin: 0 0 1rem;
padding: 0;
list-style: none;
}
.tour__members li {
padding: 0.5rem 0.75rem;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-sm);
font-size: 0.88rem;
}
.tour__footer {
padding-top: 1rem;
border-top: 1px solid var(--wire-color-border);
}
/* The rails fold away first; the reading column is what matters. */
@media (max-width: 1023px) {
.tour__shell {
grid-template-columns: minmax(0, 1fr);
}
.tour__rail,
.tour__toc {
position: static;
}
}
}
}
+3
View File
@@ -1,3 +1,6 @@
import AdvancedSelect from "@wrnexus/ui/components/AdvancedSelect.wrn"
import ComboBox from "@wrnexus/ui/components/Combobox.wrn"
// Component event test page. Route: /test // Component event test page. Route: /test
// //
// Events bubble from the exact ComboBox or AdvancedSelect that emitted them. // Events bubble from the exact ComboBox or AdvancedSelect that emitted them.
+6
View File
@@ -10,8 +10,10 @@ export interface Routes {
"/dashboard": Record<string, never>; "/dashboard": Record<string, never>;
"/hello": Record<string, never>; "/hello": Record<string, never>;
"/language-tools": Record<string, never>; "/language-tools": Record<string, never>;
"/layout": Record<string, never>;
"/login": Record<string, never>; "/login": Record<string, never>;
"/modal": Record<string, never>; "/modal": Record<string, never>;
"/navigation": Record<string, never>;
"/partial-static": Record<string, never>; "/partial-static": Record<string, never>;
"/platform-showcase": Record<string, never>; "/platform-showcase": Record<string, never>;
"/reactive": Record<string, never>; "/reactive": Record<string, never>;
@@ -30,8 +32,10 @@ export interface RouteNames {
"dashboard": "/dashboard"; "dashboard": "/dashboard";
"hello": "/hello"; "hello": "/hello";
"language.tools": "/language-tools"; "language.tools": "/language-tools";
"layout": "/layout";
"login": "/login"; "login": "/login";
"modal": "/modal"; "modal": "/modal";
"navigation": "/navigation";
"partial.static": "/partial-static"; "partial.static": "/partial-static";
"platform.showcase": "/platform-showcase"; "platform.showcase": "/platform-showcase";
"reactive": "/reactive"; "reactive": "/reactive";
@@ -119,8 +123,10 @@ export function route<N extends RouteName>(
"dashboard": "/dashboard", "dashboard": "/dashboard",
"hello": "/hello", "hello": "/hello",
"language.tools": "/language-tools", "language.tools": "/language-tools",
"layout": "/layout",
"login": "/login", "login": "/login",
"modal": "/modal", "modal": "/modal",
"navigation": "/navigation",
"partial.static": "/partial-static", "partial.static": "/partial-static",
"platform.showcase": "/platform-showcase", "platform.showcase": "/platform-showcase",
"reactive": "/reactive", "reactive": "/reactive",
+20 -6
View File
@@ -13,12 +13,21 @@
/* --- App theme layered on top of Tailwind's utilities & preflight --- */ /* --- App theme layered on top of Tailwind's utilities & preflight --- */
/*
* The app palette is derived from the theme tokens rather than fixed.
*
* These used to be hardcoded dark values. Wire UI surfaces follow the theme,
* so switching to light turned the cards light while this text stayed light
* too -- the sign-in form rendered at a contrast of about 1.1 and was
* unreadable. Anything an app hardcodes here has to be themed as well, or it
* only ever looks right in one mode.
*/
:root { :root {
--bg: #0b1020; --bg: var(--wire-color-bg);
--surface: #141a30; --surface: var(--wire-color-surface);
--text: #e7ecff; --text: var(--wire-color-text);
--muted: #9aa6d0; --muted: var(--wire-color-text-muted);
--brand: #6c8cff; --brand: var(--wire-color-primary);
--radius: 10px; --radius: 10px;
--maxw: 720px; --maxw: 720px;
} }
@@ -36,7 +45,12 @@ body {
Roboto, Roboto,
sans-serif; sans-serif;
color: var(--text); color: var(--text);
background: radial-gradient(1200px 600px at 50% -10%, #1b2750 0%, var(--bg) 60%); /* Tinted from the primary token so the wash follows the theme too. */
background: radial-gradient(
1200px 600px at 50% -10%,
color-mix(in srgb, var(--wire-color-primary) 18%, var(--bg)) 0%,
var(--bg) 60%
);
background-color: var(--bg); background-color: var(--bg);
} }
+5 -4
View File
@@ -13,15 +13,14 @@ declare namespace WRNexusGenerated {
: never; : never;
type RealtimeMessage<T> = T extends import("@wrnexus/core").RoomDefinition<any, infer M> ? M : unknown; type RealtimeMessage<T> = T extends import("@wrnexus/core").RoomDefinition<any, infer M> ? M : unknown;
type QueuePayload<T> = T extends import("@wrnexus/queue").JobDefinition<infer I> ? I : unknown; type QueuePayload<T> = T extends import("@wrnexus/queue").JobDefinition<infer I> ? I : unknown;
type RouteName = "about" | "async.data" | "chat" | "client.only" | "dashboard" | "hello" | "index" | "language.tools" | "login" | "modal" | "partial.static" | "platform.showcase" | "reactive" | "server.actions" | "test" | "ui"; type RouteName = "about" | "async.data" | "chat" | "client.only" | "dashboard" | "hello" | "index" | "language.tools" | "layout" | "login" | "modal" | "navigation" | "partial.static" | "platform.showcase" | "reactive" | "server.actions" | "table" | "test" | "ui";
type ApiRoute = "/api/echo" | "/api/graphql-example" | "/api/hello" | "/api/login" | "/api/logout" | "/api/me" | "/api/typed-user" | "/api/users/csr" | "/api/users/ssr" | "/api/webhooks/payment"; type ApiRoute = "/api/accounts" | "/api/echo" | "/api/graphql-example" | "/api/hello" | "/api/invite" | "/api/login" | "/api/logout" | "/api/me" | "/api/typed-user" | "/api/users/csr" | "/api/users/ssr" | "/api/webhooks/payment";
type RealtimeRoute = "/realtime/chat" | "/realtime/hello"; type RealtimeRoute = "/realtime/chat" | "/realtime/hello";
type EnvironmentKey = "APP_LABEL" | "DATABASE_URL" | "DEMO_SHARED" | "HOST" | "NODE_ENV" | "PORT" | "SESSION_SECRET" | "UAT_ONLY"; type EnvironmentKey = "APP_LABEL" | "DATABASE_URL" | "DEMO_SHARED" | "HOST" | "NODE_ENV" | "PORT" | "SESSION_SECRET" | "UAT_ONLY";
type TranslationKey = "api.greeting" | "home.intro" | "home.title" | "nav.about" | "nav.chat" | "nav.dashboard" | "nav.home" | "nav.ui"; type TranslationKey = "api.greeting" | "home.intro" | "home.title" | "nav.about" | "nav.chat" | "nav.dashboard" | "nav.home" | "nav.layout" | "nav.navigation" | "nav.ui";
type QueueName = "welcome-email"; type QueueName = "welcome-email";
type CacheKey = "users"; type CacheKey = "users";
interface Components { interface Components {
"Modal": { props: Record<string, never>; outputs: Record<string, never> };
"Counter": { props: { "start"?: unknown; "label"?: unknown }; outputs: Record<string, never> }; "Counter": { props: { "start"?: unknown; "label"?: unknown }; outputs: Record<string, never> };
} }
interface ApiContracts { interface ApiContracts {
@@ -30,6 +29,8 @@ declare namespace WRNexusGenerated {
"/api/users/ssr": { GET: ApiContract<typeof import("../api/users/ssr.ts")["GET"]> }; "/api/users/ssr": { GET: ApiContract<typeof import("../api/users/ssr.ts")["GET"]> };
"/api/graphql-example": { POST: ApiContract<typeof import("../api/graphql-example.ts")["POST"]> }; "/api/graphql-example": { POST: ApiContract<typeof import("../api/graphql-example.ts")["POST"]> };
"/api/typed-user": { POST: ApiContract<typeof import("../api/typed-user.ts")["POST"]> }; "/api/typed-user": { POST: ApiContract<typeof import("../api/typed-user.ts")["POST"]> };
"/api/accounts": { GET: ApiContract<typeof import("../api/accounts.ts")["GET"]> };
"/api/invite": { POST: ApiContract<typeof import("../api/invite.ts")["POST"]> };
"/api/logout": { POST: ApiContract<typeof import("../api/logout.ts")["POST"]> }; "/api/logout": { POST: ApiContract<typeof import("../api/logout.ts")["POST"]> };
"/api/hello": { GET: ApiContract<typeof import("../api/hello.ts")["GET"]> }; "/api/hello": { GET: ApiContract<typeof import("../api/hello.ts")["GET"]> };
"/api/login": { POST: ApiContract<typeof import("../api/login.ts")["POST"]> }; "/api/login": { POST: ApiContract<typeof import("../api/login.ts")["POST"]> };
+8 -1
View File
@@ -7,7 +7,14 @@ const tsconfigRootDir = dirname(fileURLToPath(import.meta.url));
export default tseslint.config( export default tseslint.config(
{ {
ignores: ["node_modules/**", "dist/**", ".wrnexus/**", "**/.wrnexus/**"], ignores: [
"node_modules/**",
"dist/**",
".wrnexus/**",
"**/.wrnexus/**",
".wrnexus-*/**",
"**/.wrnexus-*/**",
],
}, },
{ {
languageOptions: { languageOptions: {
+2 -1
View File
@@ -3,5 +3,6 @@
"compilerOptions": { "compilerOptions": {
"lib": ["ESNext", "DOM", "DOM.Iterable"] "lib": ["ESNext", "DOM", "DOM.Iterable"]
}, },
"include": ["app"] "include": ["app"],
"exclude": ["node_modules", "dist", ".wrnexus-*", "**/.wrnexus-*"]
} }
@@ -31,11 +31,11 @@ layout Showcase {
<section class="docs-directory-group"> <section class="docs-directory-group">
<a class="docs-directory-heading" href="/advanced-forms">Advanced Forms</a> <a class="docs-directory-heading" href="/advanced-forms">Advanced Forms</a>
<div><a data-docs-component-link href="/components/advanced-select">Advanced Select</a><a data-docs-component-link href="/components/combo-box">Combo Box</a><a data-docs-component-link href="/components/copy-markup">Copy Markup</a><a data-docs-component-link href="/components/input-number">Input Number</a><a data-docs-component-link href="/components/pin-input">Pin Input</a><a data-docs-component-link href="/components/strong-password">Strong Password</a><a data-docs-component-link href="/components/toggle-count">Toggle Count</a><a data-docs-component-link href="/components/toggle-password">Toggle Password</a></div> <div><a data-docs-component-link href="/components/advanced-select">Advanced Select</a><a data-docs-component-link href="/components/combo-box">Combo Box</a><a data-docs-component-link href="/components/input-number">Input Number</a><a data-docs-component-link href="/components/pin-input">Pin Input</a><a data-docs-component-link href="/components/strong-password">Strong Password</a><a data-docs-component-link href="/components/toggle-count">Toggle Count</a><a data-docs-component-link href="/components/toggle-password">Toggle Password</a></div>
</section> </section>
<section class="docs-directory-group"> <section class="docs-directory-group">
<a class="docs-directory-heading" href="/base">Base</a> <a class="docs-directory-heading" href="/base">Base</a>
<div><a data-docs-component-link href="/components/accordion">Accordion</a><a data-docs-component-link href="/components/alert">Alert</a><a data-docs-component-link href="/components/avatar">Avatar</a><a data-docs-component-link href="/components/avatar-group">Avatar Group</a><a data-docs-component-link href="/components/badge">Badge</a><a data-docs-component-link href="/components/blockquote">Blockquote</a><a data-docs-component-link href="/components/button">Button</a><a data-docs-component-link href="/components/button-group">Button Group</a><a data-docs-component-link href="/components/card">Card</a><a data-docs-component-link href="/components/carousel">Carousel</a><a data-docs-component-link href="/components/chat-bubble">Chat Bubble</a><a data-docs-component-link href="/components/collapse">Collapse</a><a data-docs-component-link href="/components/date-picker">Date Picker</a><a data-docs-component-link href="/components/device-frame">Device Frame</a><a data-docs-component-link href="/components/file-upload-progress">File Upload Progress</a><a data-docs-component-link href="/components/legend-indicator">Legend Indicator</a><a data-docs-component-link href="/components/list">List</a><a data-docs-component-link href="/components/list-group">List Group</a><a data-docs-component-link href="/components/marquee">Marquee</a><a data-docs-component-link href="/components/progress">Progress</a><a data-docs-component-link href="/components/rating">Rating</a><a data-docs-component-link href="/components/skeleton">Skeleton</a><a data-docs-component-link href="/components/spinner">Spinner</a><a data-docs-component-link href="/components/styled-icon">Styled Icon</a><a data-docs-component-link href="/components/timeline">Timeline</a><a data-docs-component-link href="/components/toast">Toast</a><a data-docs-component-link href="/components/tree-view">Tree View</a></div> <div><a data-docs-component-link href="/components/accordion">Accordion</a><a data-docs-component-link href="/components/alert">Alert</a><a data-docs-component-link href="/components/avatar">Avatar</a><a data-docs-component-link href="/components/avatar-group">Avatar Group</a><a data-docs-component-link href="/components/badge">Badge</a><a data-docs-component-link href="/components/blockquote">Blockquote</a><a data-docs-component-link href="/components/button">Button</a><a data-docs-component-link href="/components/button-group">Button Group</a><a data-docs-component-link href="/components/card">Card</a><a data-docs-component-link href="/components/carousel">Carousel</a><a data-docs-component-link href="/components/chat-bubble">Chat Bubble</a><a data-docs-component-link href="/components/collapse">Collapse</a><a data-docs-component-link href="/components/date-picker">Date Picker</a><a data-docs-component-link href="/components/device-frame">Device Frame</a><a data-docs-component-link href="/components/file-upload-progress">File Upload Progress</a><a data-docs-component-link href="/components/legend-indicator">Legend Indicator</a><a data-docs-component-link href="/components/list">List</a><a data-docs-component-link href="/components/list-group">List Group</a><a data-docs-component-link href="/components/marquee">Marquee</a><a data-docs-component-link href="/components/progress">Progress</a><a data-docs-component-link href="/components/rating">Rating</a><a data-docs-component-link href="/components/skeleton">Skeleton</a><a data-docs-component-link href="/components/spinner">Spinner</a><a data-docs-component-link href="/components/styled-icon">Styled Icon</a><a data-docs-component-link href="/components/timeline">Timeline</a><a data-docs-component-link href="/components/tree-view">Tree View</a></div>
</section> </section>
<section class="docs-directory-group"> <section class="docs-directory-group">
<a class="docs-directory-heading" href="/core">Core</a> <a class="docs-directory-heading" href="/core">Core</a>
@@ -51,7 +51,7 @@ layout Showcase {
</section> </section>
<section class="docs-directory-group"> <section class="docs-directory-group">
<a class="docs-directory-heading" href="/integrations">Integrations</a> <a class="docs-directory-heading" href="/integrations">Integrations</a>
<div><a data-docs-component-link href="/components/advanced-date-picker">Advanced Date Picker</a><a data-docs-component-link href="/components/advanced-range-slider">Advanced Range Slider</a><a data-docs-component-link href="/components/chart">Chart</a><a data-docs-component-link href="/components/clipboard">Clipboard</a><a data-docs-component-link href="/components/confetti">Confetti</a><a data-docs-component-link href="/components/data-map">Data Map</a><a data-docs-component-link href="/components/drag-and-drop">Drag And Drop</a><a data-docs-component-link href="/components/file-upload">File Upload</a><a data-docs-component-link href="/components/map">Map</a><a data-docs-component-link href="/components/toast-notifications">Toast Notifications</a><a data-docs-component-link href="/components/wysiwyg-editor">Wysiwyg Editor</a></div> <div><a data-docs-component-link href="/components/chart">Chart</a><a data-docs-component-link href="/components/clipboard">Clipboard</a><a data-docs-component-link href="/components/confetti">Confetti</a><a data-docs-component-link href="/components/data-map">Data Map</a><a data-docs-component-link href="/components/drag-and-drop">Drag And Drop</a><a data-docs-component-link href="/components/map">Map</a><a data-docs-component-link href="/components/wysiwyg-editor">Wysiwyg Editor</a></div>
</section> </section>
<section class="docs-directory-group"> <section class="docs-directory-group">
<a class="docs-directory-heading" href="/layout">Layout</a> <a class="docs-directory-heading" href="/layout">Layout</a>
@@ -3,14 +3,14 @@ page AdvancedFormsShowcase {
layout = "showcase" layout = "showcase"
seo { seo {
title = "Advanced Forms components" title = "Advanced Forms components"
description = "8 advanced-forms components from @wrnexus/ui." description = "7 advanced-forms components from @wrnexus/ui."
} }
view { view {
<header class="showcase-hero showcase-hero--category"> <header class="showcase-hero showcase-hero--category">
<span class="showcase-eyebrow">Component category</span> <span class="showcase-eyebrow">Component category</span>
<h1 class="showcase-title">Advanced Forms</h1> <h1 class="showcase-title">Advanced Forms</h1>
<p class="showcase-description">8 unique, responsive, theme-aware components. Open a component to inspect multiple live configurations and its complete props API.</p> <p class="showcase-description">7 unique, responsive, theme-aware components. Open a component to inspect multiple live configurations and its complete props API.</p>
<div class="category-meta"><span>8 components</span><span>Multiple use cases</span><span>112 live configurations</span></div> <div class="category-meta"><span>7 components</span><span>Multiple use cases</span><span>109 live configurations</span></div>
</header> </header>
<div class="catalog-grid"> <div class="catalog-grid">
<article class="catalog-card" data-interactive-preview="false"> <article class="catalog-card" data-interactive-preview="false">
@@ -33,16 +33,6 @@ page AdvancedFormsShowcase {
<div class="catalog-card-footer"><span>41 props · 0 slots</span><a href="/components/combo-box">Explore component <span aria-hidden="true">→</span></a></div> <div class="catalog-card-footer"><span>41 props · 0 slots</span><a href="/components/combo-box">Explore component <span aria-hidden="true">→</span></a></div>
</div> </div>
</article> </article>
<article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div>
<div class="catalog-card-stage"><CopyMarkup size="default" label="Copy Markup" value="sample-1" placeholder="Enter a sample value" disabled="false" required="false" class="showcase-instance showcase-instance--1" /></div>
</div>
<div class="catalog-card-body">
<div><span class="catalog-card-category">Advanced Forms</span><h2>Copy Markup</h2><p>Theme-aware, responsive copy markup component.</p></div>
<div class="catalog-card-footer"><span>13 props · 0 slots</span><a href="/components/copy-markup">Explore component <span aria-hidden="true">→</span></a></div>
</div>
</article>
<article class="catalog-card" data-interactive-preview="false"> <article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview"> <div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div> <div class="catalog-card-glow" aria-hidden="true"></div>
+5 -15
View File
@@ -3,14 +3,14 @@ page BaseShowcase {
layout = "showcase" layout = "showcase"
seo { seo {
title = "Base components" title = "Base components"
description = "27 base components from @wrnexus/ui." description = "26 base components from @wrnexus/ui."
} }
view { view {
<header class="showcase-hero showcase-hero--category"> <header class="showcase-hero showcase-hero--category">
<span class="showcase-eyebrow">Component category</span> <span class="showcase-eyebrow">Component category</span>
<h1 class="showcase-title">Base</h1> <h1 class="showcase-title">Base</h1>
<p class="showcase-description">27 unique, responsive, theme-aware components. Open a component to inspect multiple live configurations and its complete props API.</p> <p class="showcase-description">26 unique, responsive, theme-aware components. Open a component to inspect multiple live configurations and its complete props API.</p>
<div class="category-meta"><span>27 components</span><span>Multiple use cases</span><span>86 live configurations</span></div> <div class="category-meta"><span>26 components</span><span>Multiple use cases</span><span>83 live configurations</span></div>
</header> </header>
<div class="catalog-grid"> <div class="catalog-grid">
<article class="catalog-card" data-interactive-preview="false"> <article class="catalog-card" data-interactive-preview="false">
@@ -266,21 +266,11 @@ page BaseShowcase {
<article class="catalog-card" data-interactive-preview="false"> <article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview"> <div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div> <div class="catalog-card-glow" aria-hidden="true"></div>
<div class="catalog-card-stage"><Toast size="default" title="Toast example" description="A polished default toast for a modern application." items='[{"id":"toast-0-1","key":"toast-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#toast-demo","actionHref":"#toast-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"toast-0-2","key":"toast-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#toast-demo","actionHref":"#toast-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div> <div class="catalog-card-stage"><TreeView title="Tree View example" description="A polished default tree view for a modern application." items='[{"id":"tree-view-0-1","key":"tree-view-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"tree-view-0-2","key":"tree-view-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' defaultExpanded='[{"id":"tree-view-0-1","key":"tree-view-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"tree-view-0-2","key":"tree-view-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' size="default" class="showcase-instance showcase-instance--1" /></div>
</div>
<div class="catalog-card-body">
<div><span class="catalog-card-category">Base</span><h2>Toast</h2><p>Theme-aware, responsive toast component.</p></div>
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/toast">Explore component <span aria-hidden="true">→</span></a></div>
</div>
</article>
<article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div>
<div class="catalog-card-stage"><TreeView size="default" title="Tree View example" description="A polished default tree view for a modern application." items='[{"id":"tree-view-0-1","key":"tree-view-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"tree-view-0-2","key":"tree-view-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#tree-view-demo","actionHref":"#tree-view-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
</div> </div>
<div class="catalog-card-body"> <div class="catalog-card-body">
<div><span class="catalog-card-category">Base</span><h2>Tree View</h2><p>Theme-aware, responsive tree view component.</p></div> <div><span class="catalog-card-category">Base</span><h2>Tree View</h2><p>Theme-aware, responsive tree view component.</p></div>
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/tree-view">Explore component <span aria-hidden="true">→</span></a></div> <div class="catalog-card-footer"><span>10 props · 1 slots</span><a href="/components/tree-view">Explore component <span aria-hidden="true">→</span></a></div>
</div> </div>
</article></div> </article></div>
} }
@@ -1,696 +0,0 @@
// Generated by scripts/generate-showcase.mjs. Do not edit directly.
page AdvancedDatePickerDetail {
layout = "showcase"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
state playground_title = ctx.url.searchParams.get("pg_title") ?? "Advanced Date Picker example"
state playground_description = ctx.url.searchParams.get("pg_description") ?? "A polished default advanced date picker for a modern application."
state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"advanced-date-picker-0-1\",\"key\":\"advanced-date-picker-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#advanced-date-picker-demo\",\"actionHref\":\"#advanced-date-picker-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"advanced-date-picker-0-2\",\"key\":\"advanced-date-picker-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#advanced-date-picker-demo\",\"actionHref\":\"#advanced-date-picker-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]")
state playground_variant = ctx.url.searchParams.get("pg_variant") ?? "default"
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
seo {
title = "Advanced Date Picker"
description = "Theme-aware, responsive advanced date picker component."
}
view {
<nav class="docs-breadcrumbs" aria-label="Breadcrumb">
<a href="/">Components</a><span aria-hidden="true">/</span><a href="/integrations">Integrations</a><span aria-hidden="true">/</span><span>Advanced Date Picker</span>
</nav>
<header class="detail-hero">
<div class="detail-hero-copy">
<span class="showcase-eyebrow">Integrations component</span>
<h1>Advanced Date Picker</h1>
<p>Theme-aware, responsive advanced date picker component.</p>
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>5 outputs</span><span>Theme ready</span><span>Responsive</span></div>
</div>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header>
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="AdvancedDatePicker" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="input,change,open,close,clear">
<div class="detail-section-heading"><span>Interactive playground</span><h2>Configure Advanced Date Picker</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div>
<div class="playground-workbench">
<div class="playground-preview">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="playground-preview-source" data-playground-preview><AdvancedDatePicker size='{playground_size}' color='{playground_color}' title='{playground_title}' description='{playground_description}' items='{playground_items}' variant='{playground_variant}' class='{playground_class}' /></div>
<section class="playground-code" aria-label="Current component code">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;AdvancedDatePicker
title="Advanced Date Picker example"
description="A polished default advanced date picker for a modern application."
items='[
<span>&#123;</span>
"id": "advanced-date-picker-0-1",
"key": "advanced-date-picker-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#advanced-date-picker-demo",
"actionHref": "#advanced-date-picker-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "advanced-date-picker-0-2",
"key": "advanced-date-picker-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#advanced-date-picker-demo",
"actionHref": "#advanced-date-picker-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
<div class="playground-status" data-playground-status aria-live="polite"></div>
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
</div>
<form class="playground-controls" data-playground-form>
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
<div class="playground-fields"><label class="playground-field" for="playground-advanced-date-picker-size"><span><strong>size</strong><small>string</small></span><select id="playground-advanced-date-picker-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-advanced-date-picker-color"><span><strong>color</strong><small>string</small></span><select id="playground-advanced-date-picker-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-advanced-date-picker-title"><span><strong>title</strong><small>string</small></span><input id="playground-advanced-date-picker-title" name="pg_title" type="text" value="Advanced Date Picker example" /></label><label class="playground-field" for="playground-advanced-date-picker-description"><span><strong>description</strong><small>string</small></span><input id="playground-advanced-date-picker-description" name="pg_description" type="text" value="A polished default advanced date picker for a modern application." /></label><label class="playground-field" for="playground-advanced-date-picker-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-advanced-date-picker-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[
&#123;
"id": "advanced-date-picker-0-1",
"key": "advanced-date-picker-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#advanced-date-picker-demo",
"actionHref": "#advanced-date-picker-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
"label": "Nested option B",
"value": "nested-b"
&#125;
],
"links": [
&#123;
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
"label": "API reference",
"href": "#api-reference"
&#125;
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
"id": "advanced-date-picker-0-2",
"key": "advanced-date-picker-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#advanced-date-picker-demo",
"actionHref": "#advanced-date-picker-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
"label": "Nested option B",
"value": "nested-b"
&#125;
],
"links": [
&#123;
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
"label": "API reference",
"href": "#api-reference"
&#125;
],
"values": [
"Included",
"Standard"
]
&#125;
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-advanced-date-picker-variant"><span><strong>variant</strong><small>string</small></span><input id="playground-advanced-date-picker-variant" name="pg_variant" type="text" value="default" /></label><label class="playground-field" for="playground-advanced-date-picker-class"><span><strong>class</strong><small>string</small></span><input id="playground-advanced-date-picker-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
</form>
</div>
</section>
<div class="detail-layout">
<main class="detail-content">
<section class="detail-section"><div class="detail-section-heading"><span>Live examples</span><h2>Designed for real product surfaces</h2><p>Compare configurations and resize the browser to check responsive behavior.</p></div><div class="demo-list">
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Recommended</span><h2>Production default</h2><p>Balanced spacing, hierarchy, and content for the most common product workflow.</p></div>
<span class="demo-case-number">01</span>
</header>
<div class="demo-workbench">
<div id="advanced-date-picker-demo-1" class="demo-canvas demo-canvas--1">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><AdvancedDatePicker size="default" title="Advanced Date Picker example" description="A polished default advanced date picker for a modern application." items='[{"id":"advanced-date-picker-0-1","key":"advanced-date-picker-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#advanced-date-picker-demo","actionHref":"#advanced-date-picker-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"advanced-date-picker-0-2","key":"advanced-date-picker-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#advanced-date-picker-demo","actionHref":"#advanced-date-picker-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
</div>
<section class="demo-code" aria-label="Production default usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;AdvancedDatePicker
title="Advanced Date Picker example"
description="A polished default advanced date picker for a modern application."
items='[
<span>&#123;</span>
"id": "advanced-date-picker-0-1",
"key": "advanced-date-picker-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#advanced-date-picker-demo",
"actionHref": "#advanced-date-picker-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "advanced-date-picker-0-2",
"key": "advanced-date-picker-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#advanced-date-picker-demo",
"actionHref": "#advanced-date-picker-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
</div>
</article>
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Dense UI</span><h2>Compact application</h2><p>A tighter variation for dashboards, side panels, tables, and operational interfaces.</p></div>
<span class="demo-case-number">02</span>
</header>
<div class="demo-workbench">
<div id="advanced-date-picker-demo-2" class="demo-canvas demo-canvas--2">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><AdvancedDatePicker size="sm" title="Compact Advanced Date Picker" description="A compact configuration for dashboards and dense product surfaces." items='[{"id":"advanced-date-picker-1-1","key":"advanced-date-picker-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#advanced-date-picker-demo","actionHref":"#advanced-date-picker-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"advanced-date-picker-1-2","key":"advanced-date-picker-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#advanced-date-picker-demo","actionHref":"#advanced-date-picker-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="secondary" class="showcase-instance showcase-instance--2" /></div>
</div>
<section class="demo-code" aria-label="Compact application usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;AdvancedDatePicker
size="sm"
title="Compact Advanced Date Picker"
description="A compact configuration for dashboards and dense product surfaces."
items='[
<span>&#123;</span>
"id": "advanced-date-picker-1-1",
"key": "advanced-date-picker-1-1",
"value": "primary",
"label": "Secondary workflow",
"title": "Secondary workflow",
"description": "A compact configuration designed for dense application layouts.",
"text": "A configurable sample message.",
"href": "#advanced-date-picker-demo",
"actionHref": "#advanced-date-picker-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 24,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "advanced-date-picker-1-2",
"key": "advanced-date-picker-1-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A compact configuration designed for dense application layouts.",
"text": "Another configurable message.",
"href": "#advanced-date-picker-demo",
"actionHref": "#advanced-date-picker-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 48,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
variant="secondary"
/&gt;</code></pre>
</section>
</div>
</article>
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Extended</span><h2>Rich configuration</h2><p>A more expressive variation using additional data, stronger emphasis, and optional states.</p></div>
<span class="demo-case-number">03</span>
</header>
<div class="demo-workbench">
<div id="advanced-date-picker-demo-3" class="demo-canvas demo-canvas--3">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><AdvancedDatePicker size="lg" title="Advanced Advanced Date Picker" description="A richer configuration demonstrating additional content and hierarchy." items='[{"id":"advanced-date-picker-2-1","key":"advanced-date-picker-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#advanced-date-picker-demo","actionHref":"#advanced-date-picker-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"advanced-date-picker-2-2","key":"advanced-date-picker-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#advanced-date-picker-demo","actionHref":"#advanced-date-picker-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' variant="success" class="showcase-instance showcase-instance--3" /></div>
</div>
<section class="demo-code" aria-label="Rich configuration usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;AdvancedDatePicker
size="lg"
title="Advanced Advanced Date Picker"
description="A richer configuration demonstrating additional content and hierarchy."
items='[
<span>&#123;</span>
"id": "advanced-date-picker-2-1",
"key": "advanced-date-picker-2-1",
"value": "primary",
"label": "Advanced workflow",
"title": "Advanced workflow",
"description": "A richer configuration with more supporting information and actions.",
"text": "A configurable sample message.",
"href": "#advanced-date-picker-demo",
"actionHref": "#advanced-date-picker-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 32,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "advanced-date-picker-2-2",
"key": "advanced-date-picker-2-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A richer configuration with more supporting information and actions.",
"text": "Another configurable message.",
"href": "#advanced-date-picker-demo",
"actionHref": "#advanced-date-picker-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": false,
"selected": false,
"checked": false,
"disabled": true,
"outgoing": true,
"open": true,
"number": 2,
"count": 64,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>
]'
variant="success"
/&gt;</code></pre>
</section>
</div>
</article></div></section>
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@input</code><span>Fires as the editable value changes.</span></div><div><code>@change</code><span>Fires when the component's committed value or selection changes.</span></div><div><code>@open</code><span>Fires after the component opens.</span></div><div><code>@close</code><span>Fires after the component closes.</span></div><div><code>@clear</code><span>Fires after the current value or selection is cleared.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;AdvancedDatePicker
@input='console.log(payload)'
@change='console.log(payload)'
@open='console.log(payload)'
@close='console.log(payload)'
@clear='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"AdvancedDatePicker\"], [data-component=\"AdvancedDatePicker\"]")
if (component) registerOutputHandler(component, "input", (payload) =&gt; <span>&#123;</span>
console.log("input", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "open", (payload) =&gt; <span>&#123;</span>
console.log("open", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "close", (payload) =&gt; <span>&#123;</span>
console.log("close", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "clear", (payload) =&gt; <span>&#123;</span>
console.log("clear", payload)
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Advanced Date Picker"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main>
<aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#advanced-date-picker-demo-1">Production default</a><a href="#advanced-date-picker-demo-2">Compact application</a><a href="#advanced-date-picker-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
</aside>
</div>
<nav class="detail-pagination">
<span></span>
<a href="/components/advanced-range-slider"><small>Next</small><strong>Advanced Range Slider →</strong></a>
</nav>
}
}
@@ -1,691 +0,0 @@
// Generated by scripts/generate-showcase.mjs. Do not edit directly.
page AdvancedRangeSliderDetail {
layout = "showcase"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
state playground_title = ctx.url.searchParams.get("pg_title") ?? "Advanced Range Slider example"
state playground_description = ctx.url.searchParams.get("pg_description") ?? "A polished default advanced range slider for a modern application."
state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"advanced-range-slider-0-1\",\"key\":\"advanced-range-slider-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#advanced-range-slider-demo\",\"actionHref\":\"#advanced-range-slider-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"advanced-range-slider-0-2\",\"key\":\"advanced-range-slider-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#advanced-range-slider-demo\",\"actionHref\":\"#advanced-range-slider-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]")
state playground_variant = ctx.url.searchParams.get("pg_variant") ?? "default"
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
seo {
title = "Advanced Range Slider"
description = "Theme-aware, responsive advanced range slider component."
}
view {
<nav class="docs-breadcrumbs" aria-label="Breadcrumb">
<a href="/">Components</a><span aria-hidden="true">/</span><a href="/integrations">Integrations</a><span aria-hidden="true">/</span><span>Advanced Range Slider</span>
</nav>
<header class="detail-hero">
<div class="detail-hero-copy">
<span class="showcase-eyebrow">Integrations component</span>
<h1>Advanced Range Slider</h1>
<p>Theme-aware, responsive advanced range slider component.</p>
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>4 outputs</span><span>Theme ready</span><span>Responsive</span></div>
</div>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header>
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="AdvancedRangeSlider" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="input,change,start,end">
<div class="detail-section-heading"><span>Interactive playground</span><h2>Configure Advanced Range Slider</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div>
<div class="playground-workbench">
<div class="playground-preview">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="playground-preview-source" data-playground-preview><AdvancedRangeSlider size='{playground_size}' color='{playground_color}' title='{playground_title}' description='{playground_description}' items='{playground_items}' variant='{playground_variant}' class='{playground_class}' /></div>
<section class="playground-code" aria-label="Current component code">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;AdvancedRangeSlider
title="Advanced Range Slider example"
description="A polished default advanced range slider for a modern application."
items='[
<span>&#123;</span>
"id": "advanced-range-slider-0-1",
"key": "advanced-range-slider-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#advanced-range-slider-demo",
"actionHref": "#advanced-range-slider-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "advanced-range-slider-0-2",
"key": "advanced-range-slider-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#advanced-range-slider-demo",
"actionHref": "#advanced-range-slider-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
<div class="playground-status" data-playground-status aria-live="polite"></div>
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
</div>
<form class="playground-controls" data-playground-form>
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
<div class="playground-fields"><label class="playground-field" for="playground-advanced-range-slider-size"><span><strong>size</strong><small>string</small></span><select id="playground-advanced-range-slider-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-advanced-range-slider-color"><span><strong>color</strong><small>string</small></span><select id="playground-advanced-range-slider-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-advanced-range-slider-title"><span><strong>title</strong><small>string</small></span><input id="playground-advanced-range-slider-title" name="pg_title" type="text" value="Advanced Range Slider example" /></label><label class="playground-field" for="playground-advanced-range-slider-description"><span><strong>description</strong><small>string</small></span><input id="playground-advanced-range-slider-description" name="pg_description" type="text" value="A polished default advanced range slider for a modern application." /></label><label class="playground-field" for="playground-advanced-range-slider-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-advanced-range-slider-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[
&#123;
"id": "advanced-range-slider-0-1",
"key": "advanced-range-slider-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#advanced-range-slider-demo",
"actionHref": "#advanced-range-slider-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
"label": "Nested option B",
"value": "nested-b"
&#125;
],
"links": [
&#123;
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
"label": "API reference",
"href": "#api-reference"
&#125;
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
"id": "advanced-range-slider-0-2",
"key": "advanced-range-slider-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#advanced-range-slider-demo",
"actionHref": "#advanced-range-slider-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
"label": "Nested option B",
"value": "nested-b"
&#125;
],
"links": [
&#123;
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
"label": "API reference",
"href": "#api-reference"
&#125;
],
"values": [
"Included",
"Standard"
]
&#125;
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-advanced-range-slider-variant"><span><strong>variant</strong><small>string</small></span><input id="playground-advanced-range-slider-variant" name="pg_variant" type="text" value="default" /></label><label class="playground-field" for="playground-advanced-range-slider-class"><span><strong>class</strong><small>string</small></span><input id="playground-advanced-range-slider-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
</form>
</div>
</section>
<div class="detail-layout">
<main class="detail-content">
<section class="detail-section"><div class="detail-section-heading"><span>Live examples</span><h2>Designed for real product surfaces</h2><p>Compare configurations and resize the browser to check responsive behavior.</p></div><div class="demo-list">
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Recommended</span><h2>Production default</h2><p>Balanced spacing, hierarchy, and content for the most common product workflow.</p></div>
<span class="demo-case-number">01</span>
</header>
<div class="demo-workbench">
<div id="advanced-range-slider-demo-1" class="demo-canvas demo-canvas--1">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><AdvancedRangeSlider size="default" title="Advanced Range Slider example" description="A polished default advanced range slider for a modern application." items='[{"id":"advanced-range-slider-0-1","key":"advanced-range-slider-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#advanced-range-slider-demo","actionHref":"#advanced-range-slider-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"advanced-range-slider-0-2","key":"advanced-range-slider-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#advanced-range-slider-demo","actionHref":"#advanced-range-slider-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div>
</div>
<section class="demo-code" aria-label="Production default usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;AdvancedRangeSlider
title="Advanced Range Slider example"
description="A polished default advanced range slider for a modern application."
items='[
<span>&#123;</span>
"id": "advanced-range-slider-0-1",
"key": "advanced-range-slider-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#advanced-range-slider-demo",
"actionHref": "#advanced-range-slider-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "advanced-range-slider-0-2",
"key": "advanced-range-slider-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#advanced-range-slider-demo",
"actionHref": "#advanced-range-slider-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
/&gt;</code></pre>
</section>
</div>
</article>
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Dense UI</span><h2>Compact application</h2><p>A tighter variation for dashboards, side panels, tables, and operational interfaces.</p></div>
<span class="demo-case-number">02</span>
</header>
<div class="demo-workbench">
<div id="advanced-range-slider-demo-2" class="demo-canvas demo-canvas--2">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><AdvancedRangeSlider size="sm" title="Compact Advanced Range Slider" description="A compact configuration for dashboards and dense product surfaces." items='[{"id":"advanced-range-slider-1-1","key":"advanced-range-slider-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#advanced-range-slider-demo","actionHref":"#advanced-range-slider-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"advanced-range-slider-1-2","key":"advanced-range-slider-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#advanced-range-slider-demo","actionHref":"#advanced-range-slider-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="secondary" class="showcase-instance showcase-instance--2" /></div>
</div>
<section class="demo-code" aria-label="Compact application usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;AdvancedRangeSlider
size="sm"
title="Compact Advanced Range Slider"
description="A compact configuration for dashboards and dense product surfaces."
items='[
<span>&#123;</span>
"id": "advanced-range-slider-1-1",
"key": "advanced-range-slider-1-1",
"value": "primary",
"label": "Secondary workflow",
"title": "Secondary workflow",
"description": "A compact configuration designed for dense application layouts.",
"text": "A configurable sample message.",
"href": "#advanced-range-slider-demo",
"actionHref": "#advanced-range-slider-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 24,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "advanced-range-slider-1-2",
"key": "advanced-range-slider-1-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A compact configuration designed for dense application layouts.",
"text": "Another configurable message.",
"href": "#advanced-range-slider-demo",
"actionHref": "#advanced-range-slider-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 48,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
variant="secondary"
/&gt;</code></pre>
</section>
</div>
</article>
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Extended</span><h2>Rich configuration</h2><p>A more expressive variation using additional data, stronger emphasis, and optional states.</p></div>
<span class="demo-case-number">03</span>
</header>
<div class="demo-workbench">
<div id="advanced-range-slider-demo-3" class="demo-canvas demo-canvas--3">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><AdvancedRangeSlider size="lg" title="Advanced Advanced Range Slider" description="A richer configuration demonstrating additional content and hierarchy." items='[{"id":"advanced-range-slider-2-1","key":"advanced-range-slider-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#advanced-range-slider-demo","actionHref":"#advanced-range-slider-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"advanced-range-slider-2-2","key":"advanced-range-slider-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#advanced-range-slider-demo","actionHref":"#advanced-range-slider-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' variant="success" class="showcase-instance showcase-instance--3" /></div>
</div>
<section class="demo-code" aria-label="Rich configuration usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;AdvancedRangeSlider
size="lg"
title="Advanced Advanced Range Slider"
description="A richer configuration demonstrating additional content and hierarchy."
items='[
<span>&#123;</span>
"id": "advanced-range-slider-2-1",
"key": "advanced-range-slider-2-1",
"value": "primary",
"label": "Advanced workflow",
"title": "Advanced workflow",
"description": "A richer configuration with more supporting information and actions.",
"text": "A configurable sample message.",
"href": "#advanced-range-slider-demo",
"actionHref": "#advanced-range-slider-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 32,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "advanced-range-slider-2-2",
"key": "advanced-range-slider-2-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A richer configuration with more supporting information and actions.",
"text": "Another configurable message.",
"href": "#advanced-range-slider-demo",
"actionHref": "#advanced-range-slider-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": false,
"selected": false,
"checked": false,
"disabled": true,
"outgoing": true,
"open": true,
"number": 2,
"count": 64,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>
]'
variant="success"
/&gt;</code></pre>
</section>
</div>
</article></div></section>
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@input</code><span>Fires as the editable value changes.</span></div><div><code>@change</code><span>Fires when the component's committed value or selection changes.</span></div><div><code>@start</code><span>Fires when the component emits the start event.</span></div><div><code>@end</code><span>Fires when the component emits the end event.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;AdvancedRangeSlider
@input='console.log(payload)'
@change='console.log(payload)'
@start='console.log(payload)'
@end='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"AdvancedRangeSlider\"], [data-component=\"AdvancedRangeSlider\"]")
if (component) registerOutputHandler(component, "input", (payload) =&gt; <span>&#123;</span>
console.log("input", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "change", (payload) =&gt; <span>&#123;</span>
console.log("change", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "start", (payload) =&gt; <span>&#123;</span>
console.log("start", payload)
<span>&#125;</span>)
if (component) registerOutputHandler(component, "end", (payload) =&gt; <span>&#123;</span>
console.log("end", payload)
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Advanced Range Slider"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main>
<aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#advanced-range-slider-demo-1">Production default</a><a href="#advanced-range-slider-demo-2">Compact application</a><a href="#advanced-range-slider-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
</aside>
</div>
<nav class="detail-pagination">
<a href="/components/advanced-date-picker"><small>Previous</small><strong>← Advanced Date Picker</strong></a>
<a href="/components/chart"><small>Next</small><strong>Chart →</strong></a>
</nav>
}
}
@@ -1,12 +1,17 @@
// Generated by scripts/generate-showcase.mjs. Do not edit directly. // Generated by scripts/generate-showcase.mjs. Do not edit directly.
page ChartDetail { page ChartDetail {
layout = "showcase" layout = "showcase"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
state playground_title = ctx.url.searchParams.get("pg_title") ?? "Chart example" state playground_title = ctx.url.searchParams.get("pg_title") ?? "Chart example"
state playground_description = ctx.url.searchParams.get("pg_description") ?? "A polished default chart for a modern application." state playground_description = ctx.url.searchParams.get("pg_description") ?? "A polished default chart for a modern application."
state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"chart-0-1\",\"key\":\"chart-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#chart-demo\",\"actionHref\":\"#chart-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"chart-0-2\",\"key\":\"chart-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#chart-demo\",\"actionHref\":\"#chart-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]") state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"chart-0-1\",\"key\":\"chart-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#chart-demo\",\"actionHref\":\"#chart-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"chart-0-2\",\"key\":\"chart-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#chart-demo\",\"actionHref\":\"#chart-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]")
state playground_variant = ctx.url.searchParams.get("pg_variant") ?? "default" state playground_valueKey = ctx.url.searchParams.get("pg_valueKey") ?? "value"
state playground_labelKey = ctx.url.searchParams.get("pg_labelKey") ?? "label"
state playground_height = Number(ctx.url.searchParams.get("pg_height") ?? "240")
state playground_showLegend = (ctx.url.searchParams.get("pg_showLegend") ?? "true") === "true"
state playground_showValues = (ctx.url.searchParams.get("pg_showValues") ?? "true") === "true"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
state playground_variant = ctx.url.searchParams.get("pg_variant") ?? "bar"
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1" state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
seo { seo {
title = "Chart" title = "Chart"
@@ -21,7 +26,7 @@ page ChartDetail {
<span class="showcase-eyebrow">Integrations component</span> <span class="showcase-eyebrow">Integrations component</span>
<h1>Chart</h1> <h1>Chart</h1>
<p>Theme-aware, responsive chart component.</p> <p>Theme-aware, responsive chart component.</p>
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>3 outputs</span><span>Theme ready</span><span>Responsive</span></div> <div class="detail-badges"><span>12 props</span><span>1 slots</span><span>3 outputs</span><span>Theme ready</span><span>Responsive</span></div>
</div> </div>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div> <div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header> </header>
@@ -30,7 +35,7 @@ page ChartDetail {
<div class="playground-workbench"> <div class="playground-workbench">
<div class="playground-preview"> <div class="playground-preview">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="playground-preview-source" data-playground-preview><Chart size='{playground_size}' color='{playground_color}' title='{playground_title}' description='{playground_description}' items='{playground_items}' variant='{playground_variant}' class='{playground_class}' /></div> <div class="playground-preview-source" data-playground-preview><Chart title='{playground_title}' description='{playground_description}' items='{playground_items}' valueKey='{playground_valueKey}' labelKey='{playground_labelKey}' height='{playground_height}' showLegend='{playground_showLegend}' showValues='{playground_showValues}' size='{playground_size}' color='{playground_color}' variant='{playground_variant}' class='{playground_class}' /></div>
<section class="playground-code" aria-label="Current component code"> <section class="playground-code" aria-label="Current component code">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;Chart <pre><code data-playground-code>&lt;Chart
@@ -150,8 +155,8 @@ page ChartDetail {
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div> <div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
</div> </div>
<form class="playground-controls" data-playground-form> <form class="playground-controls" data-playground-form>
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header> <header><div><span>Component props</span><strong>12 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
<div class="playground-fields"><label class="playground-field" for="playground-chart-size"><span><strong>size</strong><small>string</small></span><select id="playground-chart-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-chart-color"><span><strong>color</strong><small>string</small></span><select id="playground-chart-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-chart-title"><span><strong>title</strong><small>string</small></span><input id="playground-chart-title" name="pg_title" type="text" value="Chart example" /></label><label class="playground-field" for="playground-chart-description"><span><strong>description</strong><small>string</small></span><input id="playground-chart-description" name="pg_description" type="text" value="A polished default chart for a modern application." /></label><label class="playground-field" for="playground-chart-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-chart-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[ <div class="playground-fields"><label class="playground-field" for="playground-chart-title"><span><strong>title</strong><small>string</small></span><input id="playground-chart-title" name="pg_title" type="text" value="Chart example" /></label><label class="playground-field" for="playground-chart-description"><span><strong>description</strong><small>string</small></span><input id="playground-chart-description" name="pg_description" type="text" value="A polished default chart for a modern application." /></label><label class="playground-field" for="playground-chart-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-chart-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[
&#123; &#123;
"id": "chart-0-1", "id": "chart-0-1",
"key": "chart-0-1", "key": "chart-0-1",
@@ -258,7 +263,7 @@ page ChartDetail {
"Standard" "Standard"
] ]
&#125; &#125;
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-chart-variant"><span><strong>variant</strong><small>string</small></span><input id="playground-chart-variant" name="pg_variant" type="text" value="default" /></label><label class="playground-field" for="playground-chart-class"><span><strong>class</strong><small>string</small></span><input id="playground-chart-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div> ]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-chart-valueKey"><span><strong>value Key</strong><small>string</small></span><input id="playground-chart-valueKey" name="pg_valueKey" type="text" value="value" /></label><label class="playground-field" for="playground-chart-labelKey"><span><strong>label Key</strong><small>string</small></span><input id="playground-chart-labelKey" name="pg_labelKey" type="text" value="label" /></label><label class="playground-field" for="playground-chart-height"><span><strong>height</strong><small>number</small></span><input id="playground-chart-height" name="pg_height" type="number" value="240" /></label><label class="playground-toggle" for="playground-chart-showLegend"><span><strong>show Legend</strong><small>boolean</small></span><input id="playground-chart-showLegend" name="pg_showLegend" type="checkbox" value="true" checked data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-toggle" for="playground-chart-showValues"><span><strong>show Values</strong><small>boolean</small></span><input id="playground-chart-showValues" name="pg_showValues" type="checkbox" value="true" checked data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-field" for="playground-chart-size"><span><strong>size</strong><small>string</small></span><select id="playground-chart-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-chart-color"><span><strong>color</strong><small>string</small></span><select id="playground-chart-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-chart-variant"><span><strong>variant</strong><small>string</small></span><input id="playground-chart-variant" name="pg_variant" type="text" value="bar" /></label><label class="playground-field" for="playground-chart-class"><span><strong>class</strong><small>string</small></span><input id="playground-chart-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
</form> </form>
</div> </div>
</section> </section>
@@ -274,7 +279,7 @@ page ChartDetail {
<div id="chart-demo-1" class="demo-canvas demo-canvas--1"> <div id="chart-demo-1" class="demo-canvas demo-canvas--1">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Chart size="default" title="Chart example" description="A polished default chart for a modern application." items='[{"id":"chart-0-1","key":"chart-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"chart-0-2","key":"chart-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div> <div class="demo-render"><Chart title="Chart example" description="A polished default chart for a modern application." items='[{"id":"chart-0-1","key":"chart-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"chart-0-2","key":"chart-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' height="240" showLegend="true" showValues="true" size="default" variant="bar" class="showcase-instance showcase-instance--1" /></div>
</div> </div>
<section class="demo-code" aria-label="Production default usage"> <section class="demo-code" aria-label="Production default usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
@@ -402,12 +407,11 @@ page ChartDetail {
<div id="chart-demo-2" class="demo-canvas demo-canvas--2"> <div id="chart-demo-2" class="demo-canvas demo-canvas--2">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Chart size="sm" title="Compact Chart" description="A compact configuration for dashboards and dense product surfaces." items='[{"id":"chart-1-1","key":"chart-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"chart-1-2","key":"chart-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="secondary" class="showcase-instance showcase-instance--2" /></div> <div class="demo-render"><Chart title="Compact Chart" description="A compact configuration for dashboards and dense product surfaces." items='[{"id":"chart-1-1","key":"chart-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"chart-1-2","key":"chart-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' height="240" showLegend="true" showValues="true" size="sm" variant="secondary" class="showcase-instance showcase-instance--2" /></div>
</div> </div>
<section class="demo-code" aria-label="Compact application usage"> <section class="demo-code" aria-label="Compact application usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Chart <pre><code>&lt;Chart
size="sm"
title="Compact Chart" title="Compact Chart"
description="A compact configuration for dashboards and dense product surfaces." description="A compact configuration for dashboards and dense product surfaces."
items='[ items='[
@@ -518,6 +522,7 @@ page ChartDetail {
] ]
<span>&#125;</span> <span>&#125;</span>
]' ]'
size="sm"
variant="secondary" variant="secondary"
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
@@ -532,12 +537,11 @@ page ChartDetail {
<div id="chart-demo-3" class="demo-canvas demo-canvas--3"> <div id="chart-demo-3" class="demo-canvas demo-canvas--3">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Chart size="lg" title="Advanced Chart" description="A richer configuration demonstrating additional content and hierarchy." items='[{"id":"chart-2-1","key":"chart-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"chart-2-2","key":"chart-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' variant="success" class="showcase-instance showcase-instance--3" /></div> <div class="demo-render"><Chart title="Advanced Chart" description="A richer configuration demonstrating additional content and hierarchy." items='[{"id":"chart-2-1","key":"chart-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"chart-2-2","key":"chart-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#chart-demo","actionHref":"#chart-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' height="240" showLegend="true" showValues="true" size="lg" variant="success" class="showcase-instance showcase-instance--3" /></div>
</div> </div>
<section class="demo-code" aria-label="Rich configuration usage"> <section class="demo-code" aria-label="Rich configuration usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Chart <pre><code>&lt;Chart
size="lg"
title="Advanced Chart" title="Advanced Chart"
description="A richer configuration demonstrating additional content and hierarchy." description="A richer configuration demonstrating additional content and hierarchy."
items='[ items='[
@@ -648,6 +652,7 @@ page ChartDetail {
] ]
<span>&#125;</span> <span>&#125;</span>
]' ]'
size="lg"
variant="success" variant="success"
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
@@ -672,14 +677,14 @@ if (component) registerOutputHandler(component, "dataPointClick", (payload) =&gt
if (component) registerOutputHandler(component, "legendToggle", (payload) =&gt; <span>&#123;</span> if (component) registerOutputHandler(component, "legendToggle", (payload) =&gt; <span>&#123;</span>
console.log("legendToggle", payload) console.log("legendToggle", payload)
<span>&#125;</span>)</code></pre></section></div></div></section> <span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Chart"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section> <section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>title</code></td><td>string</td><td><code>"Chart"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>valueKey</code></td><td>string</td><td><code>"value"</code></td><td>No</td></tr><tr><td><code>labelKey</code></td><td>string</td><td><code>"label"</code></td><td>No</td></tr><tr><td><code>height</code></td><td>number</td><td><code>240</code></td><td>No</td></tr><tr><td><code>showLegend</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>showValues</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"bar"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main> </main>
<aside class="detail-aside"> <aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#chart-demo-1">Production default</a><a href="#chart-demo-2">Compact application</a><a href="#chart-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div> <div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#chart-demo-1">Production default</a><a href="#chart-demo-2">Compact application</a><a href="#chart-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
</aside> </aside>
</div> </div>
<nav class="detail-pagination"> <nav class="detail-pagination">
<a href="/components/advanced-range-slider"><small>Previous</small><strong>← Advanced Range Slider</strong></a> <span></span>
<a href="/components/clipboard"><small>Next</small><strong>Clipboard →</strong></a> <a href="/components/clipboard"><small>Next</small><strong>Clipboard →</strong></a>
</nav> </nav>
} }
@@ -1,12 +1,16 @@
// Generated by scripts/generate-showcase.mjs. Do not edit directly. // Generated by scripts/generate-showcase.mjs. Do not edit directly.
page ClipboardDetail { page ClipboardDetail {
layout = "showcase" layout = "showcase"
state playground_size = ctx.url.searchParams.get("pg_size") ?? "default" state playground_value = ctx.url.searchParams.get("pg_value") ?? "sample-1"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary" state playground_label = ctx.url.searchParams.get("pg_label") ?? "Clipboard"
state playground_copiedLabel = ctx.url.searchParams.get("pg_copiedLabel") ?? "Clipboard"
state playground_errorLabel = ctx.url.searchParams.get("pg_errorLabel") ?? "Clipboard"
state playground_title = ctx.url.searchParams.get("pg_title") ?? "Clipboard example" state playground_title = ctx.url.searchParams.get("pg_title") ?? "Clipboard example"
state playground_description = ctx.url.searchParams.get("pg_description") ?? "A polished default clipboard for a modern application." state playground_description = ctx.url.searchParams.get("pg_description") ?? "A polished default clipboard for a modern application."
state playground_items = JSON.parse(ctx.url.searchParams.get("pg_items") ?? "[{\"id\":\"clipboard-0-1\",\"key\":\"clipboard-0-1\",\"value\":\"primary\",\"label\":\"Primary workflow\",\"title\":\"Primary workflow\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"A configurable sample message.\",\"href\":\"#clipboard-demo\",\"actionHref\":\"#clipboard-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":true,\"selected\":true,\"checked\":true,\"disabled\":false,\"outgoing\":false,\"open\":true,\"number\":1,\"count\":16,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 1\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]},{\"id\":\"clipboard-0-2\",\"key\":\"clipboard-0-2\",\"value\":\"secondary\",\"label\":\"Additional option\",\"title\":\"Supporting example\",\"description\":\"A clean default configuration for everyday product interfaces.\",\"text\":\"Another configurable message.\",\"href\":\"#clipboard-demo\",\"actionHref\":\"#clipboard-demo\",\"actionLabel\":\"View details\",\"icon\":\"icon-[lucide--sparkles]\",\"iconClass\":\"icon-[lucide--sparkles]\",\"variant\":\"primary\",\"status\":\"Active\",\"time\":\"09:30\",\"current\":false,\"selected\":false,\"checked\":false,\"disabled\":false,\"outgoing\":true,\"open\":true,\"number\":2,\"count\":32,\"percentage\":72,\"color\":\"#7c3aed\",\"target\":\"_self\",\"ariaLabel\":\"Open primary workflow 2\",\"items\":[{\"label\":\"Nested option A\",\"value\":\"nested-a\"},{\"label\":\"Nested option B\",\"value\":\"nested-b\"}],\"links\":[{\"label\":\"Documentation\",\"href\":\"#documentation\"},{\"label\":\"API reference\",\"href\":\"#api-reference\"}],\"values\":[\"Included\",\"Standard\"]}]") state playground_code = (ctx.url.searchParams.get("pg_code") ?? "true") === "true"
state playground_variant = ctx.url.searchParams.get("pg_variant") ?? "default" state playground_size = ctx.url.searchParams.get("pg_size") ?? "default"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
state playground_disabled = (ctx.url.searchParams.get("pg_disabled") ?? "false") === "true"
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1" state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
seo { seo {
title = "Clipboard" title = "Clipboard"
@@ -21,7 +25,7 @@ page ClipboardDetail {
<span class="showcase-eyebrow">Integrations component</span> <span class="showcase-eyebrow">Integrations component</span>
<h1>Clipboard</h1> <h1>Clipboard</h1>
<p>Theme-aware, responsive clipboard component.</p> <p>Theme-aware, responsive clipboard component.</p>
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>3 outputs</span><span>Theme ready</span><span>Responsive</span></div> <div class="detail-badges"><span>11 props</span><span>1 slots</span><span>3 outputs</span><span>Theme ready</span><span>Responsive</span></div>
</div> </div>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div> <div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header> </header>
@@ -30,235 +34,24 @@ page ClipboardDetail {
<div class="playground-workbench"> <div class="playground-workbench">
<div class="playground-preview"> <div class="playground-preview">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="playground-preview-source" data-playground-preview><Clipboard size='{playground_size}' color='{playground_color}' title='{playground_title}' description='{playground_description}' items='{playground_items}' variant='{playground_variant}' class='{playground_class}' /></div> <div class="playground-preview-source" data-playground-preview><Clipboard value='{playground_value}' label='{playground_label}' copiedLabel='{playground_copiedLabel}' errorLabel='{playground_errorLabel}' title='{playground_title}' description='{playground_description}' code='{playground_code}' size='{playground_size}' color='{playground_color}' disabled='{playground_disabled}' class='{playground_class}' /></div>
<section class="playground-code" aria-label="Current component code"> <section class="playground-code" aria-label="Current component code">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;Clipboard <pre><code data-playground-code>&lt;Clipboard
value="sample-1"
label="Clipboard"
copiedLabel="Clipboard"
errorLabel="Clipboard"
title="Clipboard example" title="Clipboard example"
description="A polished default clipboard for a modern application." description="A polished default clipboard for a modern application."
items='[
<span>&#123;</span>
"id": "clipboard-0-1",
"key": "clipboard-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#clipboard-demo",
"actionHref": "#clipboard-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "clipboard-0-2",
"key": "clipboard-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#clipboard-demo",
"actionHref": "#clipboard-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
<div class="playground-status" data-playground-status aria-live="polite"></div> <div class="playground-status" data-playground-status aria-live="polite"></div>
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div> <div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
</div> </div>
<form class="playground-controls" data-playground-form> <form class="playground-controls" data-playground-form>
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header> <header><div><span>Component props</span><strong>11 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
<div class="playground-fields"><label class="playground-field" for="playground-clipboard-size"><span><strong>size</strong><small>string</small></span><select id="playground-clipboard-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-clipboard-color"><span><strong>color</strong><small>string</small></span><select id="playground-clipboard-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-clipboard-title"><span><strong>title</strong><small>string</small></span><input id="playground-clipboard-title" name="pg_title" type="text" value="Clipboard example" /></label><label class="playground-field" for="playground-clipboard-description"><span><strong>description</strong><small>string</small></span><input id="playground-clipboard-description" name="pg_description" type="text" value="A polished default clipboard for a modern application." /></label><label class="playground-field" for="playground-clipboard-items"><span><strong>items</strong><small>unknown[]</small></span><textarea id="playground-clipboard-items" name="pg_items" rows="5" spellcheck="false" data-playground-json>[ <div class="playground-fields"><label class="playground-field" for="playground-clipboard-value"><span><strong>value</strong><small>string</small></span><input id="playground-clipboard-value" name="pg_value" type="text" value="sample-1" /></label><label class="playground-field" for="playground-clipboard-label"><span><strong>label</strong><small>string</small></span><input id="playground-clipboard-label" name="pg_label" type="text" value="Clipboard" /></label><label class="playground-field" for="playground-clipboard-copiedLabel"><span><strong>copied Label</strong><small>string</small></span><input id="playground-clipboard-copiedLabel" name="pg_copiedLabel" type="text" value="Clipboard" /></label><label class="playground-field" for="playground-clipboard-errorLabel"><span><strong>error Label</strong><small>string</small></span><input id="playground-clipboard-errorLabel" name="pg_errorLabel" type="text" value="Clipboard" /></label><label class="playground-field" for="playground-clipboard-title"><span><strong>title</strong><small>string</small></span><input id="playground-clipboard-title" name="pg_title" type="text" value="Clipboard example" /></label><label class="playground-field" for="playground-clipboard-description"><span><strong>description</strong><small>string</small></span><input id="playground-clipboard-description" name="pg_description" type="text" value="A polished default clipboard for a modern application." /></label><label class="playground-toggle" for="playground-clipboard-code"><span><strong>code</strong><small>boolean</small></span><input id="playground-clipboard-code" name="pg_code" type="checkbox" value="true" checked data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-field" for="playground-clipboard-size"><span><strong>size</strong><small>string</small></span><select id="playground-clipboard-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-clipboard-color"><span><strong>color</strong><small>string</small></span><select id="playground-clipboard-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-toggle" for="playground-clipboard-disabled"><span><strong>disabled</strong><small>boolean</small></span><input id="playground-clipboard-disabled" name="pg_disabled" type="checkbox" value="true" data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-field" for="playground-clipboard-class"><span><strong>class</strong><small>string</small></span><input id="playground-clipboard-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
&#123;
"id": "clipboard-0-1",
"key": "clipboard-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#clipboard-demo",
"actionHref": "#clipboard-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
&#123;
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
"label": "Nested option B",
"value": "nested-b"
&#125;
],
"links": [
&#123;
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
"label": "API reference",
"href": "#api-reference"
&#125;
],
"values": [
"Included",
"Standard"
]
&#125;,
&#123;
"id": "clipboard-0-2",
"key": "clipboard-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#clipboard-demo",
"actionHref": "#clipboard-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
&#123;
"label": "Nested option A",
"value": "nested-a"
&#125;,
&#123;
"label": "Nested option B",
"value": "nested-b"
&#125;
],
"links": [
&#123;
"label": "Documentation",
"href": "#documentation"
&#125;,
&#123;
"label": "API reference",
"href": "#api-reference"
&#125;
],
"values": [
"Included",
"Standard"
]
&#125;
]</textarea><em data-playground-error></em></label><label class="playground-field" for="playground-clipboard-variant"><span><strong>variant</strong><small>string</small></span><input id="playground-clipboard-variant" name="pg_variant" type="text" value="default" /></label><label class="playground-field" for="playground-clipboard-class"><span><strong>class</strong><small>string</small></span><input id="playground-clipboard-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
</form> </form>
</div> </div>
</section> </section>
@@ -274,121 +67,17 @@ page ClipboardDetail {
<div id="clipboard-demo-1" class="demo-canvas demo-canvas--1"> <div id="clipboard-demo-1" class="demo-canvas demo-canvas--1">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Clipboard size="default" title="Clipboard example" description="A polished default clipboard for a modern application." items='[{"id":"clipboard-0-1","key":"clipboard-0-1","value":"primary","label":"Primary workflow","title":"Primary workflow","description":"A clean default configuration for everyday product interfaces.","text":"A configurable sample message.","href":"#clipboard-demo","actionHref":"#clipboard-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":16,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"clipboard-0-2","key":"clipboard-0-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A clean default configuration for everyday product interfaces.","text":"Another configurable message.","href":"#clipboard-demo","actionHref":"#clipboard-demo","actionLabel":"View details","icon":"icon-[lucide--sparkles]","iconClass":"icon-[lucide--sparkles]","variant":"primary","status":"Active","time":"09:30","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":32,"percentage":72,"color":"#7c3aed","target":"_self","ariaLabel":"Open primary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="default" class="showcase-instance showcase-instance--1" /></div> <div class="demo-render"><Clipboard value="sample-1" label="Clipboard" copiedLabel="Clipboard" errorLabel="Clipboard" title="Clipboard example" description="A polished default clipboard for a modern application." code="true" size="default" disabled="false" class="showcase-instance showcase-instance--1" /></div>
</div> </div>
<section class="demo-code" aria-label="Production default usage"> <section class="demo-code" aria-label="Production default usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Clipboard <pre><code>&lt;Clipboard
value="sample-1"
label="Clipboard"
copiedLabel="Clipboard"
errorLabel="Clipboard"
title="Clipboard example" title="Clipboard example"
description="A polished default clipboard for a modern application." description="A polished default clipboard for a modern application."
items='[
<span>&#123;</span>
"id": "clipboard-0-1",
"key": "clipboard-0-1",
"value": "primary",
"label": "Primary workflow",
"title": "Primary workflow",
"description": "A clean default configuration for everyday product interfaces.",
"text": "A configurable sample message.",
"href": "#clipboard-demo",
"actionHref": "#clipboard-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 16,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "clipboard-0-2",
"key": "clipboard-0-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A clean default configuration for everyday product interfaces.",
"text": "Another configurable message.",
"href": "#clipboard-demo",
"actionHref": "#clipboard-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--sparkles]",
"iconClass": "icon-[lucide--sparkles]",
"variant": "primary",
"status": "Active",
"time": "09:30",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 32,
"percentage": 72,
"color": "#7c3aed",
"target": "_self",
"ariaLabel": "Open primary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
</div> </div>
@@ -402,123 +91,18 @@ page ClipboardDetail {
<div id="clipboard-demo-2" class="demo-canvas demo-canvas--2"> <div id="clipboard-demo-2" class="demo-canvas demo-canvas--2">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Clipboard size="sm" title="Compact Clipboard" description="A compact configuration for dashboards and dense product surfaces." items='[{"id":"clipboard-1-1","key":"clipboard-1-1","value":"primary","label":"Secondary workflow","title":"Secondary workflow","description":"A compact configuration designed for dense application layouts.","text":"A configurable sample message.","href":"#clipboard-demo","actionHref":"#clipboard-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":24,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]},{"id":"clipboard-1-2","key":"clipboard-1-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A compact configuration designed for dense application layouts.","text":"Another configurable message.","href":"#clipboard-demo","actionHref":"#clipboard-demo","actionLabel":"View details","icon":"icon-[lucide--zap]","iconClass":"icon-[lucide--zap]","variant":"secondary","status":"Active","time":"10:15","current":false,"selected":false,"checked":false,"disabled":false,"outgoing":true,"open":true,"number":2,"count":48,"percentage":48,"color":"#0284c7","target":"_self","ariaLabel":"Open secondary workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Standard"]}]' variant="secondary" class="showcase-instance showcase-instance--2" /></div> <div class="demo-render"><Clipboard value="sample-2" label="Compact example" copiedLabel="Compact example" errorLabel="Compact example" title="Compact Clipboard" description="A compact configuration for dashboards and dense product surfaces." code="true" size="sm" disabled="false" class="showcase-instance showcase-instance--2" /></div>
</div> </div>
<section class="demo-code" aria-label="Compact application usage"> <section class="demo-code" aria-label="Compact application usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Clipboard <pre><code>&lt;Clipboard
size="sm" value="sample-2"
label="Compact example"
copiedLabel="Compact example"
errorLabel="Compact example"
title="Compact Clipboard" title="Compact Clipboard"
description="A compact configuration for dashboards and dense product surfaces." description="A compact configuration for dashboards and dense product surfaces."
items='[ size="sm"
<span>&#123;</span>
"id": "clipboard-1-1",
"key": "clipboard-1-1",
"value": "primary",
"label": "Secondary workflow",
"title": "Secondary workflow",
"description": "A compact configuration designed for dense application layouts.",
"text": "A configurable sample message.",
"href": "#clipboard-demo",
"actionHref": "#clipboard-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 24,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "clipboard-1-2",
"key": "clipboard-1-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A compact configuration designed for dense application layouts.",
"text": "Another configurable message.",
"href": "#clipboard-demo",
"actionHref": "#clipboard-demo",
"actionLabel": "View details",
"icon": "icon-[lucide--zap]",
"iconClass": "icon-[lucide--zap]",
"variant": "secondary",
"status": "Active",
"time": "10:15",
"current": false,
"selected": false,
"checked": false,
"disabled": false,
"outgoing": true,
"open": true,
"number": 2,
"count": 48,
"percentage": 48,
"color": "#0284c7",
"target": "_self",
"ariaLabel": "Open secondary workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Standard"
]
<span>&#125;</span>
]'
variant="secondary"
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
</div> </div>
@@ -532,123 +116,18 @@ page ClipboardDetail {
<div id="clipboard-demo-3" class="demo-canvas demo-canvas--3"> <div id="clipboard-demo-3" class="demo-canvas demo-canvas--3">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div> <div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><Clipboard size="lg" title="Advanced Clipboard" description="A richer configuration demonstrating additional content and hierarchy." items='[{"id":"clipboard-2-1","key":"clipboard-2-1","value":"primary","label":"Advanced workflow","title":"Advanced workflow","description":"A richer configuration with more supporting information and actions.","text":"A configurable sample message.","href":"#clipboard-demo","actionHref":"#clipboard-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":true,"selected":true,"checked":true,"disabled":false,"outgoing":false,"open":true,"number":1,"count":32,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 1","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]},{"id":"clipboard-2-2","key":"clipboard-2-2","value":"secondary","label":"Additional option","title":"Supporting example","description":"A richer configuration with more supporting information and actions.","text":"Another configurable message.","href":"#clipboard-demo","actionHref":"#clipboard-demo","actionLabel":"Explore workflow","icon":"icon-[lucide--circle-check]","iconClass":"icon-[lucide--circle-check]","variant":"success","status":"Complete","time":"11:45","current":false,"selected":false,"checked":false,"disabled":true,"outgoing":true,"open":true,"number":2,"count":64,"percentage":91,"color":"#059669","target":"_self","ariaLabel":"Open advanced workflow 2","items":[{"label":"Nested option A","value":"nested-a"},{"label":"Nested option B","value":"nested-b"}],"links":[{"label":"Documentation","href":"#documentation"},{"label":"API reference","href":"#api-reference"}],"values":["Included","Unlimited"]}]' variant="success" class="showcase-instance showcase-instance--3" /></div> <div class="demo-render"><Clipboard value="sample-3" label="Advanced example" copiedLabel="Advanced example" errorLabel="Advanced example" title="Advanced Clipboard" description="A richer configuration demonstrating additional content and hierarchy." code="true" size="lg" disabled="false" class="showcase-instance showcase-instance--3" /></div>
</div> </div>
<section class="demo-code" aria-label="Rich configuration usage"> <section class="demo-code" aria-label="Rich configuration usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header> <header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;Clipboard <pre><code>&lt;Clipboard
size="lg" value="sample-3"
label="Advanced example"
copiedLabel="Advanced example"
errorLabel="Advanced example"
title="Advanced Clipboard" title="Advanced Clipboard"
description="A richer configuration demonstrating additional content and hierarchy." description="A richer configuration demonstrating additional content and hierarchy."
items='[ size="lg"
<span>&#123;</span>
"id": "clipboard-2-1",
"key": "clipboard-2-1",
"value": "primary",
"label": "Advanced workflow",
"title": "Advanced workflow",
"description": "A richer configuration with more supporting information and actions.",
"text": "A configurable sample message.",
"href": "#clipboard-demo",
"actionHref": "#clipboard-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": true,
"selected": true,
"checked": true,
"disabled": false,
"outgoing": false,
"open": true,
"number": 1,
"count": 32,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 1",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>,
<span>&#123;</span>
"id": "clipboard-2-2",
"key": "clipboard-2-2",
"value": "secondary",
"label": "Additional option",
"title": "Supporting example",
"description": "A richer configuration with more supporting information and actions.",
"text": "Another configurable message.",
"href": "#clipboard-demo",
"actionHref": "#clipboard-demo",
"actionLabel": "Explore workflow",
"icon": "icon-[lucide--circle-check]",
"iconClass": "icon-[lucide--circle-check]",
"variant": "success",
"status": "Complete",
"time": "11:45",
"current": false,
"selected": false,
"checked": false,
"disabled": true,
"outgoing": true,
"open": true,
"number": 2,
"count": 64,
"percentage": 91,
"color": "#059669",
"target": "_self",
"ariaLabel": "Open advanced workflow 2",
"items": [
<span>&#123;</span>
"label": "Nested option A",
"value": "nested-a"
<span>&#125;</span>,
<span>&#123;</span>
"label": "Nested option B",
"value": "nested-b"
<span>&#125;</span>
],
"links": [
<span>&#123;</span>
"label": "Documentation",
"href": "#documentation"
<span>&#125;</span>,
<span>&#123;</span>
"label": "API reference",
"href": "#api-reference"
<span>&#125;</span>
],
"values": [
"Included",
"Unlimited"
]
<span>&#125;</span>
]'
variant="success"
/&gt;</code></pre> /&gt;</code></pre>
</section> </section>
</div> </div>
@@ -672,7 +151,7 @@ if (component) registerOutputHandler(component, "success", (payload) =&gt; <span
if (component) registerOutputHandler(component, "error", (payload) =&gt; <span>&#123;</span> if (component) registerOutputHandler(component, "error", (payload) =&gt; <span>&#123;</span>
console.log("error", payload) console.log("error", payload)
<span>&#125;</span>)</code></pre></section></div></div></section> <span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Clipboard"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>items</code></td><td>unknown[]</td><td><code>[]</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section> <section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>value</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Copy"</code></td><td>No</td></tr><tr><td><code>copiedLabel</code></td><td>string</td><td><code>"Copied"</code></td><td>No</td></tr><tr><td><code>errorLabel</code></td><td>string</td><td><code>"Copy failed"</code></td><td>No</td></tr><tr><td><code>title</code></td><td>string</td><td><code>"Clipboard"</code></td><td>No</td></tr><tr><td><code>description</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>code</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>disabled</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main> </main>
<aside class="detail-aside"> <aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#clipboard-demo-1">Production default</a><a href="#clipboard-demo-2">Compact application</a><a href="#clipboard-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div> <div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#clipboard-demo-1">Production default</a><a href="#clipboard-demo-2">Compact application</a><a href="#clipboard-demo-3">Rich configuration</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
@@ -1393,7 +1393,7 @@ if (component) registerOutputHandler(component, "error", (payload) =&gt; <span>&
</div> </div>
<nav class="detail-pagination"> <nav class="detail-pagination">
<a href="/components/advanced-select"><small>Previous</small><strong>← Advanced Select</strong></a> <a href="/components/advanced-select"><small>Previous</small><strong>← Advanced Select</strong></a>
<a href="/components/copy-markup"><small>Next</small><strong>Copy Markup →</strong></a> <a href="/components/input-number"><small>Next</small><strong>Input Number →</strong></a>
</nav> </nav>
} }
} }

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